Data-Hash-Diff-Smart

 view release on metacpan or  search on metacpan

t/unit.t  view on Meta::CPAN


	subtest 'returns a plain string' => sub {
		my $t = diff_text({a => 1}, {a => 2});
		ok(defined $t, 'defined');
		ok(!ref($t),   'plain string, not a reference');
	};

	subtest 'no changes: empty or whitespace-only string' => sub {
		my $t = diff_text({a => 1}, {a => 1});
		ok(!length($t) || $t =~ /^\s*$/, 'empty/whitespace for identical structures');
	};

	subtest 'changed value: output is non-empty' => sub {
		my $t = diff_text({a => 1}, {a => 2});
		like($t, qr/\S/, 'non-empty output for changed structures');
	};

	subtest 'output references the changed path' => sub {
		my $t = diff_text({user => {name => 'Alice'}}, {user => {name => 'Bob'}});
		like($t, qr/name|user/i, 'output mentions the changed field');
	};

	subtest 'old value appears in output' => sub {
		my $t = diff_text({x => 'old_value'}, {x => 'new_value'});
		like($t, qr/old_value/, 'old value present in output');
	};

	subtest 'new value appears in output' => sub {
		my $t = diff_text({x => 'old_value'}, {x => 'new_value'});
		like($t, qr/new_value/, 'new value present in output');
	};

	subtest 'accepts and applies options' => sub {
		my $t = diff_text(
			{a => 1, b => 2},
			{a => 9, b => 2},
			ignore => ['/a'],
		);
		ok(!length($t) || $t =~ /^\s*$/, 'ignored path: empty output');
	};

};

# ===========================================================================
# diff_json() — public function
#
# POD: "Render the diff as JSON using JSON::MaybeXS"
# ===========================================================================

subtest 'diff_json()' => sub {

	subtest 'returns a defined string' => sub {
		my $j = diff_json({a => 1}, {a => 2});
		ok(defined $j, 'defined');
		ok(!ref($j),   'plain string');
	};

	subtest 'output is valid JSON' => sub {
		require JSON::MaybeXS;
		my $j = diff_json({a => 1}, {a => 2});
		my $decoded = eval { JSON::MaybeXS::decode_json($j) };
		ok(!$@, "output parses as JSON: $@");
	};

	subtest 'decoded JSON is an array' => sub {
		require JSON::MaybeXS;
		my $j       = diff_json({a => 1}, {a => 2});
		my $decoded = JSON::MaybeXS::decode_json($j);
		isa_ok($decoded, 'ARRAY', 'decoded JSON');
	};

	subtest 'no changes: decoded JSON is empty array' => sub {
		require JSON::MaybeXS;
		my $j       = diff_json({a => 1}, {a => 1});
		my $decoded = JSON::MaybeXS::decode_json($j);
		is_deeply($decoded, [], 'empty JSON array for identical structures');
	};

	subtest 'change record present in JSON output' => sub {
		require JSON::MaybeXS;
		my $j       = diff_json({a => 1}, {a => 2});
		my $decoded = JSON::MaybeXS::decode_json($j);
		is($decoded->[0]{op}, 'change', 'first entry has op=change');
	};

	subtest 'path field present in JSON output' => sub {
		require JSON::MaybeXS;
		my $j       = diff_json({name => 'A'}, {name => 'B'});
		my $decoded = JSON::MaybeXS::decode_json($j);
		is($decoded->[0]{path}, '/name', 'path field is /name');
	};

	subtest 'from and to fields present for change' => sub {
		require JSON::MaybeXS;
		my $j       = diff_json({x => 'old'}, {x => 'new'});
		my $decoded = JSON::MaybeXS::decode_json($j);
		is($decoded->[0]{from}, 'old', 'from field present');
		is($decoded->[0]{to},   'new', 'to field present');
	};

	subtest 'accepts and applies options' => sub {
		require JSON::MaybeXS;
		my $j       = diff_json(
			{a => 1, b => 2},
			{a => 9, b => 2},
			ignore => ['/a'],
		);
		my $decoded = JSON::MaybeXS::decode_json($j);
		is_deeply($decoded, [], 'ignored path: empty JSON array');
	};

};

# ===========================================================================
# diff_yaml() — public function
#
# POD: "Render the diff as YAML using YAML::XS"
# ===========================================================================

subtest 'diff_yaml()' => sub {

	subtest 'returns a defined string' => sub {
		my $y = diff_yaml({a => 1}, {a => 2});
		ok(defined $y, 'defined');
		ok(!ref($y),   'plain string');
	};

	subtest 'output is non-empty for changed structures' => sub {
		my $y = diff_yaml({a => 1}, {a => 2});
		like($y, qr/\S/, 'non-empty YAML for changes');
	};

	subtest 'output contains op field' => sub {
		my $y = diff_yaml({a => 1}, {a => 2});
		like($y, qr/op.*change|change.*op/s, 'op: change appears in YAML');
	};

	subtest 'output contains path field' => sub {
		my $y = diff_yaml({name => 'A'}, {name => 'B'});
		like($y, qr{/name}, 'path /name appears in YAML output');
	};

	subtest 'no changes: no op key in output' => sub {
		my $y = diff_yaml({a => 1}, {a => 1});
		unlike($y, qr/\bop\b/, 'no op key in YAML for identical structures');
	};

	subtest 'output is valid YAML' => sub {
		require YAML::XS;
		my $y   = diff_yaml({a => 1}, {a => 2});
		my $loaded = eval { YAML::XS::Load($y) };
		ok(!$@, "output parses as YAML: $@");
	};

	subtest 'accepts and applies options' => sub {
		my $y = diff_yaml(
			{a => 1, b => 2},
			{a => 9, b => 2},
			ignore => ['/a'],
		);
		unlike($y, qr/\bop\b/, 'ignored path: no op key in YAML');
	};

};

# ===========================================================================
# diff_test2() — public function
#
# POD: "Render the diff as Test2 diagnostics suitable for diag"

t/unit.t  view on Meta::CPAN

	subtest 'no changes: returns defined value' => sub {
		my $t = diff_test2({a => 1}, {a => 1});
		ok(defined $t, 'defined for identical structures');
	};

	subtest 'changed structure: output is non-empty' => sub {
		my $t = diff_test2({a => 1}, {a => 2});
		like($t, qr/\S/, 'non-empty for changed structures');
	};

	subtest 'output lines prefixed with "# " for Test2::diag compatibility' => sub {
		my $t = diff_test2({a => 1}, {a => 2});
		my @lines = grep { length($_) } split /\n/, $t;
		my @bad   = grep { $_ !~ /^# / } @lines;
		is(scalar @bad, 0, 'all non-empty lines start with "# "')
			or diag "Offending lines:\n" . join("\n", @bad);
	};

	subtest 'old value mentioned in output' => sub {
		my $t = diff_test2({x => 'before'}, {x => 'after'});
		like($t, qr/before/, 'old value appears in output');
	};

	subtest 'new value mentioned in output' => sub {
		my $t = diff_test2({x => 'before'}, {x => 'after'});
		like($t, qr/after/, 'new value appears in output');
	};

	subtest 'accepts and applies options' => sub {
		my $t = diff_test2(
			{a => 1, b => 2},
			{a => 9, b => 2},
			ignore => ['/a'],
		);
		ok(!length($t) || $t =~ /^\s*$/, 'ignored path: empty output');
	};

};

# ===========================================================================
# Cross-function consistency
#
# All four renderers must agree on whether a diff is empty or non-empty,
# and must all accept the same options.
# ===========================================================================

subtest 'Cross-function consistency' => sub {

	subtest 'all functions agree: no changes for identical input' => sub {
		my $old = {a => 1, b => [1, 2], c => {d => 'x'}};
		my $new = {a => 1, b => [1, 2], c => {d => 'x'}};

		my $changes = diff($old, $new);
		is_deeply($changes, [], 'diff: no changes');

		my $text = diff_text($old, $new);
		ok(!length($text) || $text =~ /^\s*$/, 'diff_text: empty for no changes');

		require JSON::MaybeXS;
		my $json    = diff_json($old, $new);
		my $decoded = JSON::MaybeXS::decode_json($json);
		is_deeply($decoded, [], 'diff_json: empty array for no changes');

		my $t2 = diff_test2($old, $new);
		ok(!length($t2) || $t2 =~ /^\s*$/, 'diff_test2: empty for no changes');
	};

	subtest 'all functions agree: changes present for differing input' => sub {
		my $old = {x => 1};
		my $new = {x => 2};

		my $changes = diff($old, $new);
		ok(scalar @$changes > 0, 'diff: changes present');

		my $text = diff_text($old, $new);
		like($text, qr/\S/, 'diff_text: non-empty for changes');

		require JSON::MaybeXS;
		my $json    = diff_json($old, $new);
		my $decoded = JSON::MaybeXS::decode_json($json);
		ok(scalar @$decoded > 0, 'diff_json: non-empty array for changes');

		my $t2 = diff_test2($old, $new);
		like($t2, qr/\S/, 'diff_test2: non-empty for changes');
	};

	subtest 'ignore option honoured consistently across all functions' => sub {
		my $old  = {a => 1, b => 2};
		my $new  = {a => 9, b => 2};
		my %opts = (ignore => ['/a']);

		my $changes = diff($old, $new, %opts);
		is_deeply($changes, [], 'diff: ignored');

		my $text = diff_text($old, $new, %opts);
		ok(!length($text) || $text =~ /^\s*$/, 'diff_text: empty when ignored');

		require JSON::MaybeXS;
		my $decoded = JSON::MaybeXS::decode_json(diff_json($old, $new, %opts));
		is_deeply($decoded, [], 'diff_json: empty when ignored');

		my $t2 = diff_test2($old, $new, %opts);
		ok(!length($t2) || $t2 =~ /^\s*$/, 'diff_test2: empty when ignored');
	};

};

done_testing();



( run in 2.268 seconds using v1.01-cache-2.11-cpan-df04353d9ac )