hashdiff 0.3.4 → 0.4.0
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 +4 -4
- data/.rubocop.yml +24 -0
- data/.travis.yml +6 -5
- data/Gemfile +3 -1
- data/README.md +94 -29
- data/Rakefile +9 -4
- data/changelog.md +28 -3
- data/hashdiff.gemspec +27 -12
- data/lib/hashdiff/compare_hashes.rb +56 -0
- data/lib/hashdiff/diff.rb +65 -111
- data/lib/hashdiff/lcs.rb +27 -30
- data/lib/hashdiff/lcs_compare_arrays.rb +32 -0
- data/lib/hashdiff/linear_compare_array.rb +159 -0
- data/lib/hashdiff/patch.rb +16 -12
- data/lib/hashdiff/util.rb +59 -37
- data/lib/hashdiff/version.rb +4 -2
- data/lib/hashdiff.rb +9 -0
- data/spec/hashdiff/best_diff_spec.rb +47 -37
- data/spec/hashdiff/diff_array_spec.rb +19 -19
- data/spec/hashdiff/diff_spec.rb +217 -139
- data/spec/hashdiff/lcs_spec.rb +27 -26
- data/spec/hashdiff/linear_compare_array_spec.rb +50 -0
- data/spec/hashdiff/patch_spec.rb +129 -105
- data/spec/hashdiff/util_spec.rb +60 -43
- data/spec/spec_helper.rb +2 -0
- metadata +50 -11
data/spec/hashdiff/diff_spec.rb
CHANGED
@@ -1,248 +1,250 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
|
-
describe
|
4
|
-
it
|
5
|
-
diff =
|
5
|
+
describe Hashdiff do
|
6
|
+
it 'is able to diff two empty hashes' do
|
7
|
+
diff = described_class.diff({}, {})
|
6
8
|
diff.should == []
|
7
9
|
end
|
8
10
|
|
9
|
-
it
|
11
|
+
it 'is able to diff an hash with an empty hash' do
|
10
12
|
a = { 'a' => 3, 'b' => 2 }
|
11
13
|
b = {}
|
12
14
|
|
13
|
-
diff =
|
14
|
-
diff.
|
15
|
+
diff = described_class.diff(a, b)
|
16
|
+
expect(diff).to eq([['-', 'a', 3], ['-', 'b', 2]])
|
15
17
|
|
16
|
-
diff =
|
18
|
+
diff = described_class.diff(b, a)
|
17
19
|
diff.should == [['+', 'a', 3], ['+', 'b', 2]]
|
18
20
|
end
|
19
21
|
|
20
|
-
it
|
21
|
-
diff =
|
22
|
+
it 'is able to diff two equal hashes' do
|
23
|
+
diff = described_class.diff({ 'a' => 2, 'b' => 2 }, 'a' => 2, 'b' => 2)
|
22
24
|
diff.should == []
|
23
25
|
end
|
24
26
|
|
25
|
-
it
|
27
|
+
it 'is able to diff two equal hashes with mixed key types' do
|
26
28
|
a = { 'a' => 1, :b => 1 }
|
27
|
-
diff =
|
29
|
+
diff = described_class.diff(a, a)
|
28
30
|
diff.should == []
|
29
31
|
end
|
30
32
|
|
31
|
-
it
|
33
|
+
it 'is able to diff if mixed key types are removed' do
|
32
34
|
a = { 'a' => 1, :b => 1 }
|
33
35
|
b = {}
|
34
|
-
diff =
|
35
|
-
diff.should == [[
|
36
|
+
diff = described_class.diff(a, b)
|
37
|
+
diff.should == [['-', 'a', 1], ['-', 'b', 1]]
|
36
38
|
end
|
37
39
|
|
38
|
-
it
|
40
|
+
it 'is able to diff if mixed key types are added' do
|
39
41
|
a = { 'a' => 1, :b => 1 }
|
40
42
|
b = {}
|
41
|
-
diff =
|
42
|
-
diff.should == [[
|
43
|
+
diff = described_class.diff(b, a)
|
44
|
+
diff.should == [['+', 'a', 1], ['+', 'b', 1]]
|
43
45
|
end
|
44
46
|
|
45
|
-
it
|
46
|
-
diff =
|
47
|
+
it 'is able to diff two hashes with equivalent numerics, when strict is false' do
|
48
|
+
diff = described_class.diff({ 'a' => 2.0, 'b' => 2 }, { 'a' => 2, 'b' => 2.0 }, strict: false)
|
47
49
|
diff.should == []
|
48
50
|
end
|
49
51
|
|
50
|
-
it
|
51
|
-
diff =
|
52
|
-
diff.should == [['~', 'b', 3, 4], ['~', 'c',
|
52
|
+
it 'is able to diff changes in hash value' do
|
53
|
+
diff = described_class.diff({ 'a' => 2, 'b' => 3, 'c' => ' hello' }, 'a' => 2, 'b' => 4, 'c' => 'hello')
|
54
|
+
diff.should == [['~', 'b', 3, 4], ['~', 'c', ' hello', 'hello']]
|
53
55
|
end
|
54
56
|
|
55
|
-
it
|
56
|
-
diff =
|
57
|
+
it 'is able to diff changes in hash value which is array' do
|
58
|
+
diff = described_class.diff({ 'a' => 2, 'b' => [1, 2, 3] }, 'a' => 2, 'b' => [1, 3, 4])
|
57
59
|
diff.should == [['-', 'b[1]', 2], ['+', 'b[2]', 4]]
|
58
60
|
end
|
59
61
|
|
60
|
-
it
|
61
|
-
diff =
|
62
|
-
|
62
|
+
it 'is able to diff changes in hash value which is hash' do
|
63
|
+
diff = described_class.diff({ 'a' => { 'x' => 2, 'y' => 3, 'z' => 4 }, 'b' => { 'x' => 3, 'z' => 45 } },
|
64
|
+
'a' => { 'y' => 3 }, 'b' => { 'y' => 3, 'z' => 30 })
|
63
65
|
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]
|
64
66
|
end
|
65
67
|
|
66
|
-
it
|
67
|
-
diff =
|
68
|
-
|
68
|
+
it 'is able to best diff similar objects in array' do
|
69
|
+
diff = described_class.best_diff({ 'a' => [{ 'x' => 2, 'y' => 3, 'z' => 4 }, { 'x' => 11, 'y' => 22, 'z' => 33 }], 'b' => { 'x' => 3, 'z' => 45 } },
|
70
|
+
'a' => [{ 'y' => 3 }, { 'x' => 11, 'z' => 33 }], 'b' => { 'y' => 22 })
|
69
71
|
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
|
70
72
|
end
|
71
73
|
|
72
|
-
it '
|
73
|
-
a = {
|
74
|
-
b = {
|
74
|
+
it 'is able to diff addition of key value pair' do
|
75
|
+
a = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200 }
|
76
|
+
b = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200, 'g' => 300 }
|
75
77
|
|
76
|
-
diff =
|
77
|
-
diff.
|
78
|
+
diff = described_class.diff(a, b)
|
79
|
+
expect(diff).to eq([['+', 'g', 300]])
|
78
80
|
|
79
|
-
diff =
|
81
|
+
diff = described_class.diff(b, a)
|
80
82
|
diff.should == [['-', 'g', 300]]
|
81
83
|
end
|
82
84
|
|
83
|
-
it '
|
84
|
-
a = {
|
85
|
-
b = {
|
85
|
+
it 'is able to diff value type changes' do
|
86
|
+
a = { 'a' => 3 }
|
87
|
+
b = { 'a' => { 'a1' => 1, 'a2' => 2 } }
|
86
88
|
|
87
|
-
diff =
|
88
|
-
diff.
|
89
|
+
diff = described_class.diff(a, b)
|
90
|
+
expect(diff).to eq([['~', 'a', 3, { 'a1' => 1, 'a2' => 2 }]])
|
89
91
|
|
90
|
-
diff =
|
91
|
-
diff.should
|
92
|
+
diff = described_class.diff(b, a)
|
93
|
+
diff.should == [['~', 'a', { 'a1' => 1, 'a2' => 2 }, 3]]
|
92
94
|
end
|
93
95
|
|
94
|
-
it
|
95
|
-
a = {
|
96
|
-
b = {
|
96
|
+
it 'is able to diff value changes: array <=> []' do
|
97
|
+
a = { 'a' => 1, 'b' => [1, 2] }
|
98
|
+
b = { 'a' => 1, 'b' => [] }
|
97
99
|
|
98
|
-
diff =
|
100
|
+
diff = described_class.diff(a, b)
|
99
101
|
diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]]
|
100
102
|
end
|
101
103
|
|
102
|
-
it
|
103
|
-
a = {
|
104
|
-
b = {
|
104
|
+
it 'is able to diff value changes: array <=> nil' do
|
105
|
+
a = { 'a' => 1, 'b' => [1, 2] }
|
106
|
+
b = { 'a' => 1, 'b' => nil }
|
105
107
|
|
106
|
-
diff =
|
107
|
-
diff.should == [[
|
108
|
+
diff = described_class.diff(a, b)
|
109
|
+
diff.should == [['~', 'b', [1, 2], nil]]
|
108
110
|
end
|
109
111
|
|
110
|
-
it
|
111
|
-
a = {
|
112
|
-
b = {
|
112
|
+
it 'is able to diff value chagnes: remove array completely' do
|
113
|
+
a = { 'a' => 1, 'b' => [1, 2] }
|
114
|
+
b = { 'a' => 1 }
|
113
115
|
|
114
|
-
diff =
|
115
|
-
diff.should == [[
|
116
|
+
diff = described_class.diff(a, b)
|
117
|
+
diff.should == [['-', 'b', [1, 2]]]
|
116
118
|
end
|
117
119
|
|
118
|
-
it
|
119
|
-
a = {
|
120
|
-
b = {
|
120
|
+
it 'is able to diff value changes: remove whole hash' do
|
121
|
+
a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } }
|
122
|
+
b = { 'a' => 1 }
|
121
123
|
|
122
|
-
diff =
|
123
|
-
diff.should == [[
|
124
|
+
diff = described_class.diff(a, b)
|
125
|
+
diff.should == [['-', 'b', { 'b1' => 1, 'b2' => 2 }]]
|
124
126
|
end
|
125
127
|
|
126
|
-
it
|
127
|
-
a = {
|
128
|
-
b = {
|
128
|
+
it 'is able to diff value changes: hash <=> {}' do
|
129
|
+
a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } }
|
130
|
+
b = { 'a' => 1, 'b' => {} }
|
129
131
|
|
130
|
-
diff =
|
132
|
+
diff = described_class.diff(a, b)
|
131
133
|
diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
|
132
134
|
end
|
133
135
|
|
134
|
-
it
|
135
|
-
a = {
|
136
|
-
b = {
|
136
|
+
it 'is able to diff value changes: hash <=> nil' do
|
137
|
+
a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } }
|
138
|
+
b = { 'a' => 1, 'b' => nil }
|
137
139
|
|
138
|
-
diff =
|
139
|
-
diff.should == [[
|
140
|
+
diff = described_class.diff(a, b)
|
141
|
+
diff.should == [['~', 'b', { 'b1' => 1, 'b2' => 2 }, nil]]
|
140
142
|
end
|
141
143
|
|
142
|
-
it
|
143
|
-
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
144
|
-
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
144
|
+
it 'is able to diff similar objects in array' do
|
145
|
+
a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, 3]
|
146
|
+
b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }]
|
145
147
|
|
146
|
-
diff =
|
148
|
+
diff = described_class.diff(a, b)
|
147
149
|
diff.should == [['-', '[0].d', 4], ['+', '[0]', 1], ['-', '[2]', 3]]
|
148
150
|
end
|
149
151
|
|
150
|
-
it
|
151
|
-
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3]
|
152
|
-
b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3]
|
152
|
+
it 'is able to diff similar & equal objects in array' do
|
153
|
+
a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, { 'x' => 5, 'y' => 6, 'z' => 3 }, 3]
|
154
|
+
b = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }, 3]
|
153
155
|
|
154
|
-
diff =
|
155
|
-
diff.should == [[
|
156
|
+
diff = described_class.diff(a, b)
|
157
|
+
diff.should == [['-', '[0].d', 4], ['-', '[1]', { 'x' => 5, 'y' => 6, 'z' => 3 }]]
|
156
158
|
end
|
157
159
|
|
158
|
-
it
|
159
|
-
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3]
|
160
|
-
b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3]
|
160
|
+
it 'uses custom delimiter when provided' do
|
161
|
+
a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, { 'x' => 5, 'y' => 6, 'z' => 3 }, 3]
|
162
|
+
b = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }, 3]
|
161
163
|
|
162
|
-
diff =
|
163
|
-
diff.should == [[
|
164
|
+
diff = described_class.diff(a, b, similarity: 0.8, delimiter: "\t")
|
165
|
+
diff.should == [['-', "[0]\td", 4], ['-', '[1]', { 'x' => 5, 'y' => 6, 'z' => 3 }]]
|
164
166
|
end
|
165
167
|
|
166
168
|
context 'when :numeric_tolerance requested' do
|
167
|
-
it
|
168
|
-
a = {'a' => 0.558, 'b' => 0.0, 'c' => 0.65, 'd' => 'fin'}
|
169
|
-
b = {'a' => 0.557, 'b' => 'hats', 'c' => 0.67, 'd' => 'fin'}
|
169
|
+
it 'is able to diff changes in hash value' do
|
170
|
+
a = { 'a' => 0.558, 'b' => 0.0, 'c' => 0.65, 'd' => 'fin' }
|
171
|
+
b = { 'a' => 0.557, 'b' => 'hats', 'c' => 0.67, 'd' => 'fin' }
|
170
172
|
|
171
|
-
diff =
|
172
|
-
diff.
|
173
|
+
diff = described_class.diff(a, b, numeric_tolerance: 0.01)
|
174
|
+
expect(diff).to eq([['~', 'b', 0.0, 'hats'], ['~', 'c', 0.65, 0.67]])
|
173
175
|
|
174
|
-
diff =
|
175
|
-
diff.should == [[
|
176
|
+
diff = described_class.diff(b, a, numeric_tolerance: 0.01)
|
177
|
+
diff.should == [['~', 'b', 'hats', 0.0], ['~', 'c', 0.67, 0.65]]
|
176
178
|
end
|
177
179
|
|
178
|
-
it
|
179
|
-
a = {'a' => {'x' => 0.4, 'y' => 0.338}, 'b' => [13, 68.03]}
|
180
|
-
b = {'a' => {'x' => 0.6, 'y' => 0.341}, 'b' => [14, 68.025]}
|
180
|
+
it 'is able to diff changes in nested values' do
|
181
|
+
a = { 'a' => { 'x' => 0.4, 'y' => 0.338 }, 'b' => [13, 68.03] }
|
182
|
+
b = { 'a' => { 'x' => 0.6, 'y' => 0.341 }, 'b' => [14, 68.025] }
|
181
183
|
|
182
|
-
diff =
|
183
|
-
diff.
|
184
|
+
diff = described_class.diff(a, b, numeric_tolerance: 0.01)
|
185
|
+
expect(diff).to eq([['~', 'a.x', 0.4, 0.6], ['-', 'b[0]', 13], ['+', 'b[0]', 14]])
|
184
186
|
|
185
|
-
diff =
|
186
|
-
diff.should == [[
|
187
|
+
diff = described_class.diff(b, a, numeric_tolerance: 0.01)
|
188
|
+
diff.should == [['~', 'a.x', 0.6, 0.4], ['-', 'b[0]', 14], ['+', 'b[0]', 13]]
|
187
189
|
end
|
188
190
|
end
|
189
191
|
|
190
192
|
context 'when :strip requested' do
|
191
|
-
it
|
192
|
-
a = { 'a' =>
|
193
|
-
b = { 'a' =>
|
194
|
-
diff =
|
195
|
-
diff.should == [['~', 'b',
|
193
|
+
it 'strips strings before comparing' do
|
194
|
+
a = { 'a' => ' foo', 'b' => 'fizz buzz' }
|
195
|
+
b = { 'a' => 'foo', 'b' => 'fizzbuzz' }
|
196
|
+
diff = described_class.diff(a, b, strip: true)
|
197
|
+
diff.should == [['~', 'b', 'fizz buzz', 'fizzbuzz']]
|
196
198
|
end
|
197
199
|
|
198
|
-
it
|
199
|
-
a = { 'a' => { 'x' =>
|
200
|
-
b = { 'a' => { 'x' =>
|
201
|
-
diff =
|
202
|
-
diff.should == [['-', 'b[0]',
|
200
|
+
it 'strips nested strings before comparing' do
|
201
|
+
a = { 'a' => { 'x' => ' foo' }, 'b' => ['fizz buzz', 'nerf'] }
|
202
|
+
b = { 'a' => { 'x' => 'foo' }, 'b' => %w[fizzbuzz nerf] }
|
203
|
+
diff = described_class.diff(a, b, strip: true)
|
204
|
+
diff.should == [['-', 'b[0]', 'fizz buzz'], ['+', 'b[0]', 'fizzbuzz']]
|
203
205
|
end
|
204
206
|
end
|
205
207
|
|
206
208
|
context 'when :case_insensitive requested' do
|
207
|
-
it
|
208
|
-
a = { 'a' =>
|
209
|
-
b = { 'a' =>
|
210
|
-
diff =
|
211
|
-
diff.should == [['~', 'b',
|
209
|
+
it 'strips strings before comparing' do
|
210
|
+
a = { 'a' => 'Foo', 'b' => 'fizz buzz' }
|
211
|
+
b = { 'a' => 'foo', 'b' => 'fizzBuzz' }
|
212
|
+
diff = described_class.diff(a, b, case_insensitive: true)
|
213
|
+
diff.should == [['~', 'b', 'fizz buzz', 'fizzBuzz']]
|
212
214
|
end
|
213
215
|
|
214
|
-
it
|
215
|
-
a = { 'a' => { 'x' =>
|
216
|
-
b = { 'a' => { 'x' =>
|
217
|
-
diff =
|
218
|
-
diff.should == [['-', 'b[0]',
|
216
|
+
it 'ignores case on nested strings before comparing' do
|
217
|
+
a = { 'a' => { 'x' => 'Foo' }, 'b' => ['fizz buzz', 'nerf'] }
|
218
|
+
b = { 'a' => { 'x' => 'foo' }, 'b' => %w[fizzbuzz nerf] }
|
219
|
+
diff = described_class.diff(a, b, case_insensitive: true)
|
220
|
+
diff.should == [['-', 'b[0]', 'fizz buzz'], ['+', 'b[0]', 'fizzbuzz']]
|
219
221
|
end
|
220
222
|
end
|
221
223
|
|
222
224
|
context 'when both :strip and :numeric_tolerance requested' do
|
223
|
-
it '
|
224
|
-
a = { 'a' =>
|
225
|
-
b = { 'a' =>
|
226
|
-
diff =
|
227
|
-
diff.should == [['~', 'd',
|
225
|
+
it 'applies filters to proper object types' do
|
226
|
+
a = { 'a' => ' foo', 'b' => 35, 'c' => 'bar', 'd' => 'baz' }
|
227
|
+
b = { 'a' => 'foo', 'b' => 35.005, 'c' => 'bar', 'd' => 18.5 }
|
228
|
+
diff = described_class.diff(a, b, strict: false, numeric_tolerance: 0.01, strip: true)
|
229
|
+
diff.should == [['~', 'd', 'baz', 18.5]]
|
228
230
|
end
|
229
231
|
end
|
230
232
|
|
231
|
-
context
|
232
|
-
it
|
233
|
-
a = { 'a' =>
|
234
|
-
b = { 'a' =>
|
235
|
-
diff =
|
236
|
-
diff.should == [['~', 'b',
|
233
|
+
context 'when both :strip and :case_insensitive requested' do
|
234
|
+
it 'applies both filters to strings' do
|
235
|
+
a = { 'a' => ' Foo', 'b' => 'fizz buzz' }
|
236
|
+
b = { 'a' => 'foo', 'b' => 'fizzBuzz' }
|
237
|
+
diff = described_class.diff(a, b, case_insensitive: true, strip: true)
|
238
|
+
diff.should == [['~', 'b', 'fizz buzz', 'fizzBuzz']]
|
237
239
|
end
|
238
240
|
end
|
239
241
|
|
240
242
|
context 'with custom comparison' do
|
241
|
-
let(:a) { { 'a' => 'car', 'b' => 'boat', 'c' => 'plane'} }
|
242
|
-
let(:b) { { 'a' => 'bus', 'b' => 'truck', 'c' => ' plan'} }
|
243
|
+
let(:a) { { 'a' => 'car', 'b' => 'boat', 'c' => 'plane' } }
|
244
|
+
let(:b) { { 'a' => 'bus', 'b' => 'truck', 'c' => ' plan' } }
|
243
245
|
|
244
|
-
it '
|
245
|
-
diff =
|
246
|
+
it 'compares using proc specified in block' do
|
247
|
+
diff = described_class.diff(a, b) do |prefix, obj1, obj2|
|
246
248
|
case prefix
|
247
249
|
when /a|b|c/
|
248
250
|
obj1.length == obj2.length
|
@@ -251,11 +253,11 @@ describe HashDiff do
|
|
251
253
|
diff.should == [['~', 'b', 'boat', 'truck']]
|
252
254
|
end
|
253
255
|
|
254
|
-
it '
|
255
|
-
x = { 'a' => 'car', 'b' => 'boat'}
|
256
|
+
it 'yields added keys' do
|
257
|
+
x = { 'a' => 'car', 'b' => 'boat' }
|
256
258
|
y = { 'a' => 'car' }
|
257
259
|
|
258
|
-
diff =
|
260
|
+
diff = described_class.diff(x, y) do |prefix, _obj1, _obj2|
|
259
261
|
case prefix
|
260
262
|
when /b/
|
261
263
|
true
|
@@ -264,8 +266,8 @@ describe HashDiff do
|
|
264
266
|
diff.should == []
|
265
267
|
end
|
266
268
|
|
267
|
-
it '
|
268
|
-
diff =
|
269
|
+
it 'compares with both proc and :strip when both provided' do
|
270
|
+
diff = described_class.diff(a, b, strip: true) do |prefix, obj1, obj2|
|
269
271
|
case prefix
|
270
272
|
when 'a'
|
271
273
|
obj1.length == obj2.length
|
@@ -273,5 +275,81 @@ describe HashDiff do
|
|
273
275
|
end
|
274
276
|
diff.should == [['~', 'b', 'boat', 'truck'], ['~', 'c', 'plane', ' plan']]
|
275
277
|
end
|
278
|
+
|
279
|
+
it 'compares nested arrays using proc specified in block' do
|
280
|
+
a = { a: 'car', b: %w[boat plane] }
|
281
|
+
b = { a: 'bus', b: ['truck', ' plan'] }
|
282
|
+
|
283
|
+
diff = described_class.diff(a, b) do |path, obj1, obj2|
|
284
|
+
case path
|
285
|
+
when 'b[*]'
|
286
|
+
obj1.length == obj2.length
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
expect(diff).to eq [['~', 'a', 'car', 'bus'], ['~', 'b[1]', 'plane', ' plan'], ['-', 'b[0]', 'boat'], ['+', 'b[0]', 'truck']]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'when :array_path is true' do
|
295
|
+
it 'returns the diff path in an array rather than a string' do
|
296
|
+
x = { 'a' => 'foo' }
|
297
|
+
y = { 'a' => 'bar' }
|
298
|
+
diff = described_class.diff(x, y, array_path: true)
|
299
|
+
|
300
|
+
diff.should == [['~', ['a'], 'foo', 'bar']]
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'shows array indexes in paths' do
|
304
|
+
x = { 'a' => [0, 1, 2] }
|
305
|
+
y = { 'a' => [0, 1, 2, 3] }
|
306
|
+
|
307
|
+
diff = described_class.diff(x, y, array_path: true)
|
308
|
+
|
309
|
+
diff.should == [['+', ['a', 3], 3]]
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'shows differences with string and symbol keys' do
|
313
|
+
x = { 'a' => 'foo' }
|
314
|
+
y = { a: 'bar' }
|
315
|
+
|
316
|
+
diff = described_class.diff(x, y, array_path: true)
|
317
|
+
diff.should == [['-', ['a'], 'foo'], ['+', [:a], 'bar']]
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'supports other key types' do
|
321
|
+
time = Time.now
|
322
|
+
x = { time => 'foo' }
|
323
|
+
y = { 0 => 'bar' }
|
324
|
+
|
325
|
+
diff = described_class.diff(x, y, array_path: true)
|
326
|
+
diff.should == [['-', [time], 'foo'], ['+', [0], 'bar']]
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
context 'when :use_lcs is false' do
|
331
|
+
it 'shows items in an array as changed' do
|
332
|
+
x = %i[a b]
|
333
|
+
y = %i[c d]
|
334
|
+
diff = described_class.diff(x, y, use_lcs: false)
|
335
|
+
|
336
|
+
diff.should == [['~', '[0]', :a, :c], ['~', '[1]', :b, :d]]
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'shows additions to arrays' do
|
340
|
+
x = { a: [0] }
|
341
|
+
y = { a: [0, 1] }
|
342
|
+
diff = described_class.diff(x, y, use_lcs: false)
|
343
|
+
|
344
|
+
diff.should == [['+', 'a[1]', 1]]
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'shows changes to nested arrays' do
|
348
|
+
x = { a: [[0, 1]] }
|
349
|
+
y = { a: [[1, 2]] }
|
350
|
+
diff = described_class.diff(x, y, use_lcs: false)
|
351
|
+
|
352
|
+
diff.should == [['~', 'a[0][0]', 0, 1], ['~', 'a[0][1]', 1, 2]]
|
353
|
+
end
|
276
354
|
end
|
277
355
|
end
|
data/spec/hashdiff/lcs_spec.rb
CHANGED
@@ -1,75 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
|
-
describe
|
4
|
-
it
|
5
|
+
describe Hashdiff do
|
6
|
+
it 'is able to find LCS between two equal array' do
|
5
7
|
a = [1, 2, 3]
|
6
8
|
b = [1, 2, 3]
|
7
9
|
|
8
|
-
lcs =
|
10
|
+
lcs = described_class.lcs(a, b)
|
9
11
|
lcs.should == [[0, 0], [1, 1], [2, 2]]
|
10
12
|
end
|
11
13
|
|
12
|
-
it
|
14
|
+
it 'is able to find LCS between two close arrays' do
|
13
15
|
a = [1.05, 2, 3.25]
|
14
16
|
b = [1.06, 2, 3.24]
|
15
17
|
|
16
|
-
lcs =
|
18
|
+
lcs = described_class.lcs(a, b, numeric_tolerance: 0.1)
|
17
19
|
lcs.should == [[0, 0], [1, 1], [2, 2]]
|
18
20
|
end
|
19
21
|
|
20
|
-
it
|
21
|
-
a = [
|
22
|
+
it 'strips strings when finding LCS if requested' do
|
23
|
+
a = %w[foo bar baz]
|
22
24
|
b = [' foo', 'bar', 'zab']
|
23
25
|
|
24
|
-
lcs =
|
26
|
+
lcs = described_class.lcs(a, b, strip: true)
|
25
27
|
lcs.should == [[0, 0], [1, 1]]
|
26
28
|
end
|
27
29
|
|
28
|
-
it
|
30
|
+
it 'is able to find LCS with one common elements' do
|
29
31
|
a = [1, 2, 3]
|
30
32
|
b = [1, 8, 7]
|
31
33
|
|
32
|
-
lcs =
|
34
|
+
lcs = described_class.lcs(a, b)
|
33
35
|
lcs.should == [[0, 0]]
|
34
36
|
end
|
35
37
|
|
36
|
-
it
|
38
|
+
it 'is able to find LCS with two common elements' do
|
37
39
|
a = [1, 3, 5, 7]
|
38
40
|
b = [2, 3, 7, 5]
|
39
41
|
|
40
|
-
lcs =
|
42
|
+
lcs = described_class.lcs(a, b)
|
41
43
|
lcs.should == [[1, 1], [2, 3]]
|
42
44
|
end
|
43
45
|
|
44
|
-
it
|
46
|
+
it 'is able to find LCS with two close elements' do
|
45
47
|
a = [1, 3.05, 5, 7]
|
46
48
|
b = [2, 3.06, 7, 5]
|
47
49
|
|
48
|
-
lcs =
|
50
|
+
lcs = described_class.lcs(a, b, numeric_tolerance: 0.1)
|
49
51
|
lcs.should == [[1, 1], [2, 3]]
|
50
52
|
end
|
51
53
|
|
52
|
-
it
|
54
|
+
it 'is able to find LCS with two common elements in different ordering' do
|
53
55
|
a = [1, 3, 4, 7]
|
54
56
|
b = [2, 3, 7, 5]
|
55
57
|
|
56
|
-
lcs =
|
58
|
+
lcs = described_class.lcs(a, b)
|
57
59
|
lcs.should == [[1, 1], [3, 2]]
|
58
60
|
end
|
59
61
|
|
60
|
-
it
|
62
|
+
it 'is able to find LCS with a similarity value' do
|
61
63
|
a = [
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
{ 'value' => 'New', 'onclick' => 'CreateNewDoc()' },
|
65
|
+
{ 'value' => 'Close', 'onclick' => 'CloseDoc()' }
|
66
|
+
]
|
65
67
|
b = [
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
{ 'value' => 'New1', 'onclick' => 'CreateNewDoc()' },
|
69
|
+
{ 'value' => 'Open', 'onclick' => 'OpenDoc()' },
|
70
|
+
{ 'value' => 'Close', 'onclick' => 'CloseDoc()' }
|
71
|
+
]
|
70
72
|
|
71
|
-
lcs =
|
73
|
+
lcs = described_class.lcs(a, b, similarity: 0.5)
|
72
74
|
lcs.should == [[0, 0], [1, 2]]
|
73
75
|
end
|
74
76
|
end
|
75
|
-
|