Data-Hash-Diff-Smart
view release on metacpan or search on metacpan
t/extended_tests.t view on Meta::CPAN
subtest 'Renderer::Test2: undef to value: no exception' => sub {
lives_ok(
sub { $render_t2->([{op => 'remove', path => '/x', from => undef}]) },
'Test2 renderer: undef remove from: no exception'
);
};
subtest 'diff_text: undef hash value change renders without exception' => sub {
lives_ok(
sub { diff_text({x => undef}, {x => 'now set'}) },
'diff_text: undef->defined: no exception'
);
};
subtest 'diff_json: undef hash value: valid JSON output' => sub {
require JSON::MaybeXS;
my $j;
lives_ok(sub { $j = diff_json({x => undef}, {x => 'now set'}) },
'diff_json: undef value: no exception');
my $d = eval { JSON::MaybeXS::decode_json($j) };
ok(!$@, 'diff_json: undef value: valid JSON');
};
};
# ===========================================================================
# 10. Renderer: multiple changes to same path (hand-crafted)
# The engine never produces this but a caller could pass it to a renderer.
# ===========================================================================
subtest 'Renderer: duplicate path entries in change list' => sub {
subtest 'Renderer::Text: two changes to same path: both rendered' => sub {
my $out = $render_text->([
{op => 'change', path => '/x', from => 1, to => 2},
{op => 'change', path => '/x', from => 2, to => 3},
]);
my @headers = ($out =~ /^~ \/x$/mg);
is(scalar @headers, 2, 'Text renderer: both changes rendered');
};
subtest 'Renderer::Test2: two changes to same path: both rendered' => sub {
my $out = $render_t2->([
{op => 'change', path => '/x', from => 1, to => 2},
{op => 'change', path => '/x', from => 2, to => 3},
]);
my @headers = ($out =~ /^# Difference at \/x$/mg);
is(scalar @headers, 2, 'Test2 renderer: both changes rendered');
};
};
# ===========================================================================
# 11. diff_yaml: structural round-trip
# The YAML output must decode back to a structure that faithfully
# represents the change list.
# ===========================================================================
subtest 'diff_yaml: structural round-trip' => sub {
subtest 'decoded YAML is an arrayref of hashrefs' => sub {
require YAML::XS;
my $y = diff_yaml({a => 1}, {a => 2});
my $decoded = YAML::XS::Load($y);
isa_ok($decoded, 'ARRAY', 'decoded YAML is arrayref');
isa_ok($decoded->[0], 'HASH', 'first element is hashref');
};
subtest 'decoded YAML preserves op field' => sub {
require YAML::XS;
my $decoded = YAML::XS::Load(diff_yaml({a => 1}, {a => 2}));
is($decoded->[0]{op}, 'change', 'op=change preserved through YAML');
};
subtest 'decoded YAML preserves path field' => sub {
require YAML::XS;
my $decoded = YAML::XS::Load(diff_yaml({name => 'A'}, {name => 'B'}));
is($decoded->[0]{path}, '/name', 'path preserved through YAML');
};
subtest 'decoded YAML preserves from and to fields' => sub {
require YAML::XS;
my $decoded = YAML::XS::Load(diff_yaml({x => 'old'}, {x => 'new'}));
is($decoded->[0]{from}, 'old', 'from preserved through YAML');
is($decoded->[0]{to}, 'new', 'to preserved through YAML');
};
subtest 'decoded YAML for add op has value field' => sub {
require YAML::XS;
my $decoded = YAML::XS::Load(diff_yaml({}, {added => 42}));
is($decoded->[0]{op}, 'add', 'op=add preserved');
is($decoded->[0]{value}, 42, 'value=42 preserved');
};
subtest 'decoded YAML for remove op has from field' => sub {
require YAML::XS;
my $decoded = YAML::XS::Load(diff_yaml({gone => 'bye'}, {}));
is($decoded->[0]{op}, 'remove', 'op=remove preserved');
is($decoded->[0]{from}, 'bye', 'from=bye preserved');
};
subtest 'decoded YAML entry count matches diff() count' => sub {
require YAML::XS;
my $old = {a => 1, b => 2, c => 3};
my $new = {a => 9, b => 2, c => 99};
my $changes = diff($old, $new);
my $decoded = YAML::XS::Load(diff_yaml($old, $new));
is(scalar @$decoded, scalar @$changes,
'YAML entry count matches diff() count');
};
};
# ===========================================================================
# 12. array_mode interactions with other options
# ===========================================================================
subtest 'array_mode interactions with ignore and compare' => sub {
subtest 'lcs + ignore: ignored path within array element suppressed' => sub {
my $r = diff(
[{a => 1, b => 10}, {a => 2, b => 20}],
[{a => 1, b => 99}, {a => 2, b => 99}],
array_mode => 'lcs',
ignore => [qr{/b$}],
);
is_deeply($r, [], 'lcs + ignore: b changes suppressed');
};
subtest 'unordered + ignore: ignored path within matched element suppressed' => sub {
my $r = diff(
[{id => 1, ts => 'old', val => 'x'}],
[{id => 1, ts => 'new', val => 'x'}],
array_mode => 'unordered',
array_key => 'id',
ignore => [qr{/ts$}],
);
is_deeply($r, [], 'unordered + array_key + ignore: ts change suppressed');
};
subtest 'index + compare: comparator fires for array element' => sub {
my $r = diff(
[1.001, 2.0],
[1.002, 3.0],
array_mode => 'index',
compare => { '/0' => sub { abs($_[0] - $_[1]) < 0.01 } },
);
is(scalar @$r, 1, 'index + compare: only /1 change reported');
is($r->[0]{path}, '/1', 'surviving change is at /1');
};
};
# ===========================================================================
# 13. Sorting stability in _diff_hash
# Changes must always be emitted in sorted key order regardless of the
# order keys were inserted into the hash.
# ===========================================================================
subtest '_diff_hash: sorted key order is stable' => sub {
subtest 'keys in reverse insertion order: paths still sorted' => sub {
my %old;
my %new;
for my $k (reverse 'a'..'f') {
$old{$k} = 1;
$new{$k} = 2;
}
( run in 0.712 second using v1.01-cache-2.11-cpan-df04353d9ac )