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 )