minitest-hashdiff 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +49 -0
- data/lib/minitest/hashdiff/version.rb +7 -0
- data/lib/minitest/hashdiff.rb +86 -0
- data/lib/minitest/hashdiff_plugin.rb +10 -0
- data/lib/minitest-hashdiff.rb +6 -0
- metadata +68 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 62ffed814bbebbcf613fa11125780d0b09fd1c02959718115b203cdd66fcc436
|
|
4
|
+
data.tar.gz: 5555f3146e81cd840b5559c9d18ee0db1055810d64a6acf359647a3823f16fa3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5ebbf349e05b283ea574c0673e8696ef6a9501bb516186a9da54a858215e2fbf2d7660b0b29d77c5b50edd97be0d18ad7f58e3a135c34c9549a0b935ecc530e4
|
|
7
|
+
data.tar.gz: 94382db83217c19236e4eeccce4f98404767efa7e617ba68d75a6592a707026282cfdcee13a06f50243d4444d1af0cf7be7c5af3a496e912d159917fbc1d236d
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lachlan Young
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# minitest-hashdiff
|
|
2
|
+
|
|
3
|
+
When a Minitest assertion fails on two hashes, the default output is two nearly identical walls of `inspect` text, and the one value that changed is somewhere in there. This gem replaces that with a report of exactly what changed.
|
|
4
|
+
|
|
5
|
+
Before:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Expected: {"uid"=>"user@example.test", "active"=>true, "role"=>"admin", "last_login"=>nil}
|
|
9
|
+
Actual: {"uid"=>"user@example.test", "active"=>true, "role"=>"editor", "last_login"=>"2026-07-10"}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
After:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Hash diff (expected => actual):
|
|
16
|
+
changed "last_login": nil => "2026-07-10" (nil -> String)
|
|
17
|
+
changed "role": "admin" => "editor"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Type changes are flagged explicitly because they are the differences most likely to cause a downstream bug and the easiest to miss: `nil` to `String`, `Integer` to `Float`, and symbol keys versus string keys (which are different keys in Ruby, even though they look almost the same).
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Gemfile
|
|
26
|
+
group :test do
|
|
27
|
+
gem "minitest-hashdiff"
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's it for almost everyone: Rails and any app using `Bundler.require` loads the gem automatically, and on minitest 5 the plugin also self-discovers with no Bundler involved. The one exception: minitest 6 made plugin loading opt-in, so if you are on minitest 6 *and* not using `Bundler.require`, add `require "minitest/hashdiff"` to your test helper.
|
|
32
|
+
|
|
33
|
+
Only `assert_equal` failures where both sides are hashes are affected; every other assertion behaves exactly as before.
|
|
34
|
+
|
|
35
|
+
## What the report covers
|
|
36
|
+
|
|
37
|
+
- Changed values, with before and after
|
|
38
|
+
- Added and removed keys
|
|
39
|
+
- Explicit type changes
|
|
40
|
+
- Nested hashes and arrays, with the path to each difference
|
|
41
|
+
- Keys sorted, so ordering noise never masquerades as a real change
|
|
42
|
+
|
|
43
|
+
## Prefer a visual diff?
|
|
44
|
+
|
|
45
|
+
This gem's output is built for terminals. For a side-by-side visual comparison of two large hashes, paste your failing test output into [rubyhash.dev](https://rubyhash.dev), which does the same parsing and type detection in your browser, with nothing uploaded.
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest"
|
|
4
|
+
require "minitest/test"
|
|
5
|
+
require_relative "hashdiff/version"
|
|
6
|
+
|
|
7
|
+
module Minitest
|
|
8
|
+
# Formats the difference between two hashes as a readable, sorted,
|
|
9
|
+
# type-aware report instead of two walls of inspect output.
|
|
10
|
+
module HashDiff
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def display_type(value)
|
|
14
|
+
case value
|
|
15
|
+
when nil then "nil"
|
|
16
|
+
when true, false then "Boolean"
|
|
17
|
+
when Integer then "Integer"
|
|
18
|
+
when Float then "Float"
|
|
19
|
+
when String then "String"
|
|
20
|
+
when Symbol then "Symbol"
|
|
21
|
+
when Hash then "Hash"
|
|
22
|
+
when Array then "Array"
|
|
23
|
+
else value.class.name
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns a readable diff report, or nil when nothing differs.
|
|
28
|
+
def format(expected, actual)
|
|
29
|
+
lines = []
|
|
30
|
+
walk(expected, actual, nil, lines)
|
|
31
|
+
return nil if lines.empty?
|
|
32
|
+
|
|
33
|
+
"Hash diff (expected => actual):\n#{lines.join("\n")}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def walk(expected, actual, path, lines)
|
|
37
|
+
if expected.is_a?(Hash) && actual.is_a?(Hash)
|
|
38
|
+
keys = (expected.keys | actual.keys).sort_by(&:inspect)
|
|
39
|
+
keys.each do |key|
|
|
40
|
+
key_path = path ? "#{path} > #{key.inspect}" : key.inspect
|
|
41
|
+
if !expected.key?(key)
|
|
42
|
+
lines << " added #{key_path}: #{actual[key].inspect}"
|
|
43
|
+
elsif !actual.key?(key)
|
|
44
|
+
lines << " removed #{key_path}: #{expected[key].inspect}"
|
|
45
|
+
elsif expected[key] != actual[key]
|
|
46
|
+
walk(expected[key], actual[key], key_path, lines)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
elsif expected.is_a?(Array) && actual.is_a?(Array)
|
|
50
|
+
[expected.size, actual.size].max.times do |i|
|
|
51
|
+
item_path = "#{path}[#{i}]"
|
|
52
|
+
if i >= expected.size
|
|
53
|
+
lines << " added #{item_path}: #{actual[i].inspect}"
|
|
54
|
+
elsif i >= actual.size
|
|
55
|
+
lines << " removed #{item_path}: #{expected[i].inspect}"
|
|
56
|
+
elsif expected[i] != actual[i]
|
|
57
|
+
walk(expected[i], actual[i], item_path, lines)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
expected_type = display_type(expected)
|
|
62
|
+
actual_type = display_type(actual)
|
|
63
|
+
note = expected_type == actual_type ? "" : " (#{expected_type} -> #{actual_type})"
|
|
64
|
+
lines << " changed #{path}: #{expected.inspect} => #{actual.inspect}#{note}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Prepended so assert_equal failures on two hashes show the readable
|
|
70
|
+
# report instead of the default diff.
|
|
71
|
+
module HashDiffAssertions
|
|
72
|
+
def diff(exp, act)
|
|
73
|
+
if exp.is_a?(Hash) && act.is_a?(Hash) && exp != act
|
|
74
|
+
Minitest::HashDiff.format(exp, act) || super
|
|
75
|
+
else
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Prepend into the class, not just the Assertions module: before Ruby 3.0,
|
|
82
|
+
# prepending into an already-included module does not propagate to its
|
|
83
|
+
# includers, so Assertions alone silently misses on Ruby 2.7.
|
|
84
|
+
Test.prepend(HashDiffAssertions)
|
|
85
|
+
Assertions.prepend(HashDiffAssertions)
|
|
86
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Minitest auto-discovers minitest/*_plugin.rb files across installed gems,
|
|
4
|
+
# so simply having this gem in the bundle activates readable hash diffs.
|
|
5
|
+
# No require needed in your test helper.
|
|
6
|
+
require "minitest/hashdiff"
|
|
7
|
+
|
|
8
|
+
module Minitest
|
|
9
|
+
def self.plugin_hashdiff_init(_options); end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: minitest-hashdiff
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lachlan Young
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
27
|
+
description: 'When assert_equal fails on two hashes, show exactly what changed: added,
|
|
28
|
+
removed, and changed keys with type-change detection (nil to String, Integer to
|
|
29
|
+
Float, symbol vs string keys), instead of two walls of inspect output. Zero configuration,
|
|
30
|
+
Minitest loads it automatically. From the maker of rubyhash.dev.'
|
|
31
|
+
email:
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.md
|
|
38
|
+
- lib/minitest-hashdiff.rb
|
|
39
|
+
- lib/minitest/hashdiff.rb
|
|
40
|
+
- lib/minitest/hashdiff/version.rb
|
|
41
|
+
- lib/minitest/hashdiff_plugin.rb
|
|
42
|
+
homepage: https://rubyhash.dev
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata:
|
|
46
|
+
homepage_uri: https://rubyhash.dev
|
|
47
|
+
documentation_uri: https://rubyhash.dev/blog/minitest-hash-diff-hard-to-read
|
|
48
|
+
rubygems_mfa_required: 'true'
|
|
49
|
+
post_install_message:
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '2.7'
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 3.5.11
|
|
65
|
+
signing_key:
|
|
66
|
+
specification_version: 4
|
|
67
|
+
summary: Readable hash diffs for failing Minitest assertions
|
|
68
|
+
test_files: []
|