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 )