Data-Hash-Diff-Smart
view release on metacpan or search on metacpan
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"
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 )