Data-Hash-Diff-Smart
view release on metacpan or search on metacpan
t/edge_cases.t view on Meta::CPAN
subtest 'empty ignore list: same as no ignore' => sub {
my $r1 = diff({a => 1}, {a => 2});
my $r2 = diff({a => 1}, {a => 2}, ignore => []);
is(scalar @$r2, scalar @$r1, 'empty ignore list: same result as no ignore');
};
subtest 'ignore rule that matches nothing' => sub {
my $r = diff({a => 1}, {a => 2}, ignore => ['/no/such/path']);
is(scalar @$r, 1, 'non-matching ignore: change still reported');
};
subtest 'regex that matches nothing' => sub {
my $r = diff({a => 1}, {a => 2}, ignore => [qr{^/zzz}]);
is(scalar @$r, 1, 'non-matching regex: change still reported');
};
subtest 'wildcard that matches nothing (wrong depth)' => sub {
my $r = diff(
{a => {b => 1}},
{a => {b => 2}},
ignore => ['/a/*/b/extra'], # too deep
);
is(scalar @$r, 1, 'wrong-depth wildcard: change still reported');
};
subtest 'regex that matches everything: all changes suppressed' => sub {
my $r = diff(
{a => 1, b => 2},
{a => 9, b => 9},
ignore => [qr{.*}],
);
is_deeply($r, [], 'catch-all regex: all changes suppressed');
};
subtest 'ignore root path' => sub {
# Ignoring '' (root) should suppress a root-level scalar change
my $r = diff('old', 'new', ignore => ['']);
is_deeply($r, [], 'ignoring root path suppresses root scalar change');
};
subtest 'overlapping ignore rules: each still suppresses its path' => sub {
my $r = diff(
{a => 1, b => 2, c => 3},
{a => 9, b => 9, c => 9},
ignore => ['/a', '/b', qr{^/c$}],
);
is_deeply($r, [], 'overlapping rules: all paths suppressed');
};
subtest 'ignore undef element' => sub {
# Must not die if ignore list contains undef somehow â or just confirm
# that a valid list with one rule works when other value is changed
my $r = diff({a => 1, b => 2}, {a => 9, b => 2}, ignore => ['/a']);
is_deeply($r, [], 'valid single ignore rule works correctly');
};
};
# ===========================================================================
# 13. Adversarial compare callbacks
# ===========================================================================
subtest 'Adversarial compare callbacks' => sub {
subtest 'comparator that always returns true: no changes ever' => sub {
my $r = diff(
{a => 1, b => 'hello'},
{a => 2, b => 'world'},
compare => {
'/a' => sub { 1 },
'/b' => sub { 1 },
},
);
is_deeply($r, [], 'always-true comparator: no changes');
};
subtest 'comparator that always returns false: always changes' => sub {
my $r = diff(
{x => 42},
{x => 42},
compare => { '/x' => sub { 0 } },
);
is($r->[0]{op}, 'change', 'always-false comparator: change even for equal values');
};
subtest 'comparator that dies: change record with error field' => sub {
my $r = diff(
{x => 1},
{x => 2},
compare => { '/x' => sub { die "boom\n" } },
);
is($r->[0]{op}, 'change', 'dying comparator: change recorded');
ok(exists $r->[0]{error}, 'error field present');
like($r->[0]{error}, qr/boom/, 'error message captured');
};
subtest 'comparator that modifies $_[0]: no exception' => sub {
lives_ok(sub {
diff(
{x => 'hello'},
{x => 'world'},
compare => { '/x' => sub { $_[0] = 'modified'; 0 } },
)
}, 'comparator modifying arg: no exception');
};
subtest 'comparator registered for non-existent path: no effect' => sub {
my $r = diff(
{a => 1},
{a => 2},
compare => { '/no/such/path' => sub { 1 } },
);
is($r->[0]{op}, 'change', 'comparator for absent path: normal change still reported');
};
};
# ===========================================================================
# 14. Array mode boundary conditions
# ===========================================================================
subtest 'Array mode boundary conditions' => sub {
( run in 1.673 second using v1.01-cache-2.11-cpan-140bd7fdf52 )