eq_json 1.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9ec463196fe5607b1e9b24b069a789c237c4dd28
4
+ data.tar.gz: aa249d10540740c92146fc97766617812409cefa
5
+ SHA512:
6
+ metadata.gz: 2a431e39d5628ab69001a5fde73f84713227395baac64f038cf7d29ed443c758ae492397637e053836bbf4564ba810408ae31ef0b2e76eda70ffe2762a5883e0
7
+ data.tar.gz: 17f55e32ef6021a364cae492e213fd50b733114234fa74067891dfcb4888ac2672fde88b82c917f157e3c173d37c834e737de0366f73ab9a27e1130f19360f71
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.so
11
+ *.o
12
+ *.a
13
+ mkmf.log
14
+ .idea
15
+ *.iml
16
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
data/CHANGELOG ADDED
@@ -0,0 +1,10 @@
1
+ Version 0.0.1 eq_wo_order is released
2
+ Version 0.1.0 Support for arrays of arrays and more complicated structures.
3
+ Version 0.2.0 Support for arrays of hashes of arrays etc - more complicated nesting. Initial support for arrays of disparate types.
4
+ Version 0.2.1 Small description update
5
+ Version 0.2.2 Include CHANGELOG
6
+ Version 0.2.3 Update description with github link
7
+ Version 0.2.4 Ruby-ism refactors for terseness
8
+ Version 0.3.0 Support for nil-case
9
+ Version 0.3.1 Refactor large amounts of code to use Enumerable::any/all, thanks to @jacobsimeon
10
+ Version 0.4.0 Added diffable macro for better output (shows diff of expectation and actual)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Jean de Klerk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # EqJson
2
+
3
+ <!-- [![Build Status](https://travis-ci.org/jadekler/eq_wo_order.svg?branch=master)](https://travis-ci.org/jadekler/eq_wo_order) -->
4
+
5
+ RSpec equality matcher that JSON. Outputs meaningful failure messages.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'eq_json'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ ```
19
+ $ bundle
20
+ ```
21
+
22
+ Or install it yourself as:
23
+
24
+ ```
25
+ $ gem install eq_json
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```ruby
31
+ require 'eq_json'
32
+
33
+ actual = {
34
+ name: 'Harry Potter and the Sorcerer\'s Stone',
35
+ publisherInfo: {
36
+ publishDate: {
37
+ year: 2015,
38
+ month: 3,
39
+ day: 23
40
+ },
41
+ name: "ACME Publisher Inc."
42
+ },
43
+ author: 'J.K. Rowling'
44
+ }
45
+
46
+ expected = {
47
+ name: 'Harry Potter and the Sorcerer\'s Stone',
48
+ author: 'J.K. Rowling',
49
+ publisherInfo: {
50
+ name: "ACME Publisher Inc.",
51
+ publishDate: {
52
+ month: 3,
53
+ day: 23,
54
+ year: 2015
55
+ }
56
+ }
57
+ }
58
+
59
+ expect(expected).to eq_json(actual)
60
+ ```
61
+ # More Documentation
62
+ [Keynote Slides](https://github.com/davidmrhodes/eq_json/blob/master/doc/eqJsonPresentation.key)
63
+
64
+ If you don't have keynote here is
65
+ [Quicktime Slide show](https://github.com/davidmrhodes/eq_json/blob/master/doc/eqJsonPresentation.m4v)
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it ( https://github.com/[my-github-username]/eq_json/fork )
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ task :default do
4
+ puts system('bundle exec rspec')
5
+ end
data/TODO.txt ADDED
@@ -0,0 +1,31 @@
1
+ 1) Implement array equals
2
+ a) make sure to test that arrays with same objects but different number of
3
+ same objects is not equal. Must test with same number of total objects in
4
+ array
5
+
6
+ 2) Document that did not use matcher DSL. Easier to test if just used class.
7
+
8
+ 3) Look to see on diff what should get actual is green or red?
9
+ Answer: expected is - in red
10
+
11
+ 4) If object is a string and is multi line use differ in failure_message
12
+
13
+ 5) test for json object combinations of (do for actual also)
14
+ first key in map not in expected
15
+ middle key in map not in expected
16
+ last key in map not in expected
17
+
18
+ 6) Do I need to check expected and actual are valid josn?
19
+
20
+
21
+ 7) Add comparable function with an selection criteria for and id to compare arrays
22
+
23
+ 8) If objects are large write them to a files in temp
24
+ Could be done via configuration to find if configuration exists:
25
+
26
+ if RSpec.configuration.methods.include? :json_debug_config
27
+ puts "json_debug_config #{RSpec.configuration.json_debug_config}"
28
+ end
29
+
30
+ Then put this in spec_helper:
31
+ c.json_debug_config=true
data/eq_json.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'eq_json'
3
+ s.version = '1.0.2'
4
+ s.date = '2016-02-29'
5
+ s.summary = 'RSpec equality matcher that to compare JSON'
6
+ s.description = 'RSpec equality matcher that deeply compares JSON. Examples at github.com/davidmrhodes/eq_json'
7
+ s.authors = ['David M. Rhodes']
8
+ s.email = 'barnaby71@gmail.com'
9
+ s.homepage = 'http://rubygems.org/gems/eq_json'
10
+ s.license = 'MIT'
11
+
12
+ s.files = `git ls-files -z`.split("\x0").find_all{|file| file !~ /doc\//}
13
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
+ s.require_paths = ['lib']
16
+
17
+ s.add_runtime_dependency 'rspec', '~> 3.0'
18
+ s.add_development_dependency 'bundler', '~> 1.7'
19
+ s.add_development_dependency 'rake', '~> 10.0'
20
+ end
data/lib/colorizer.rb ADDED
@@ -0,0 +1,23 @@
1
+ class EqJsonColorizer
2
+ def initialize
3
+ end
4
+
5
+ def green(text)
6
+ return colorize(text, 32);
7
+ end
8
+
9
+ def red(text)
10
+ colorize(text, 31)
11
+ end
12
+
13
+ def blue(text)
14
+ colorize(text, 34)
15
+ end
16
+
17
+ private
18
+
19
+ def colorize(text, colorCode)
20
+ "\e[#{colorCode}m#{text}\e[0m"
21
+ end
22
+
23
+ end
data/lib/eq_json.rb ADDED
@@ -0,0 +1,142 @@
1
+ require 'pp'
2
+ require 'message_generator'
3
+ require 'eq_json_array'
4
+
5
+ class EqualWithOutOrderJson
6
+
7
+ attr_accessor :actual, :expected, :jsonPath, :jsonPathRoot, :currentActualObj,
8
+ :currentExpectedObj, :currentJsonKey
9
+
10
+ def initialize(expected)
11
+ @expected = expected
12
+ @jsonPathRoot = "$."
13
+ @jsonPath = @jsonPathRoot
14
+ @messageGenerator = EqJsonMessageGenerator.new(self)
15
+ end
16
+
17
+ def matches?(actual)
18
+ @actual = actual
19
+
20
+ matchesObject?(@expected, @actual)
21
+ end
22
+
23
+ def failure_message
24
+ return @failureMessage
25
+ end
26
+
27
+ def failure_message_when_negated
28
+ "Expeced failure_message_when_nagated"
29
+ end
30
+
31
+ def description
32
+ "Excpect {@expected}"
33
+ end
34
+
35
+ private
36
+
37
+ def matchesObject?(expectedObj, actualObj)
38
+
39
+ @currentActualObj = actualObj
40
+ @currentExpectedObj = expectedObj
41
+
42
+ case actualObj
43
+ when Array
44
+ return arrays_match?(expectedObj, actualObj)
45
+ when Hash
46
+ return hashes_match?(expectedObj, actualObj)
47
+ else
48
+ unless expectedObj == actualObj
49
+ @failureMessage = @messageGenerator.generateDifferentValueMessage();
50
+ return false;
51
+ end
52
+ end
53
+
54
+ return true;
55
+
56
+ end
57
+
58
+ def arrays_match?(expectedObj, actualArray)
59
+ unless actualArray.class == expectedObj.class
60
+ @failureMessage = @messageGenerator.generateTypeMissMatchFailureMessage()
61
+ return false;
62
+ end
63
+
64
+ unless actualArray.length == expectedObj.length
65
+ @failureMessage = @messageGenerator.generateDifferentSizeArrayMessage();
66
+ return false;
67
+ end
68
+
69
+ arrayUtil = EqualJsonArray.new
70
+
71
+ expectedObj.each do |expectedItem|
72
+
73
+ expectedCount = expectedObj.count do |item|
74
+ arrayUtil.itemEqual?(expectedItem, item)
75
+ end
76
+
77
+ actualCount = actualArray.count do |candidate|
78
+ arrayUtil.itemEqual?(expectedItem, candidate)
79
+ end
80
+
81
+ if expectedCount != actualCount
82
+ @failureMessage = @messageGenerator.generateExpectedItemNotFoundInArray(expectedItem, expectedCount, actualCount)
83
+ return false
84
+ end
85
+
86
+ end
87
+
88
+ return true
89
+ end
90
+
91
+ def hashes_match?(expectedObj, actualHash)
92
+ unless actualHash.class == expectedObj.class
93
+ @failureMessage = @messageGenerator.generateTypeMissMatchFailureMessage()
94
+ return false;
95
+ end
96
+
97
+ unless actualHash.length == expectedObj.length
98
+ @failureMessage = @messageGenerator.generateDifferentKeyMessage();
99
+ return false;
100
+ end
101
+
102
+ expectedObj.each do |expectedKey, expectedValue|
103
+ @currentJsonKey = expectedKey
104
+ actualValue = actualHash[expectedKey]
105
+ if actualValue.nil?
106
+ @currentActualObj = actualHash
107
+ @currentExpectedObj = expectedObj
108
+ @failureMessage = @messageGenerator.generateDifferentKeyMessage()
109
+ return false
110
+ end
111
+
112
+ addKeyToPath(expectedKey)
113
+ match = matchesObject?(expectedValue, actualHash[expectedKey])
114
+ removeKeyFromPath(expectedKey)
115
+ if match == false
116
+ return false;
117
+ end
118
+ end
119
+
120
+ return true
121
+
122
+ end
123
+
124
+ def addKeyToPath(jsonKey)
125
+ if @jsonPath[@jsonPath.length-1] != "."
126
+ @jsonPath << "."
127
+ end
128
+ @jsonPath << "#{jsonKey}"
129
+ end
130
+
131
+ def removeKeyFromPath(jsonKey)
132
+ @jsonPath = @jsonPath[0, @jsonPath.length - "#{jsonKey}".length - 1]
133
+ if @jsonPath == "$"
134
+ @jsonPath << "."
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ def eq_json(*args)
141
+ EqualWithOutOrderJson.new(*args)
142
+ end
@@ -0,0 +1,33 @@
1
+ class EqualJsonArray
2
+
3
+ def itemEqual?(item1, item2)
4
+ return false unless item1.class == item2.class
5
+
6
+ case item1
7
+ when Array
8
+ arraysMatch?(item1, item2)
9
+ when Hash
10
+ hashesMatch?(item1, item2)
11
+ else
12
+ item1 == item2
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def arraysMatch?(actual, expected)
19
+ return false unless actual.length == expected.length
20
+ expected.all? do |expectedItem|
21
+ actual.any? do |candidate|
22
+ itemEqual?(candidate, expectedItem)
23
+ end
24
+ end
25
+ end
26
+
27
+ def hashesMatch?(actual, expected)
28
+ return false unless arraysMatch?(actual.keys, expected.keys)
29
+ expected.all? do |expectedKey, expectedValue|
30
+ itemEqual?(actual[expectedKey], expectedValue)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,172 @@
1
+ require 'colorizer'
2
+
3
+ class EqJsonMessageGenerator
4
+
5
+ def initialize(matcher)
6
+ @matcher = matcher
7
+ @colorizer = EqJsonColorizer.new
8
+ end
9
+
10
+ def generateTypeMissMatchFailureMessage()
11
+
12
+ if @matcher.currentJsonKey.nil?
13
+ actualType = getJsonType(@matcher.actual)
14
+ expectedType = getJsonType(@matcher.expected)
15
+ else
16
+ actualType = getJsonType(@matcher.currentActualObj)
17
+ expectedType = getJsonType(@matcher.currentExpectedObj)
18
+ currentJsonDiff = "\tExpected: #{@matcher.currentExpectedObj.to_json}\n" +
19
+ @colorizer.green("\t Actual: #{@matcher.currentActualObj.to_json}") + "\n"
20
+ end
21
+
22
+ jsonErrorInfo = "JSON path #{@matcher.jsonPath} expected #{expectedType} type but actual is #{actualType}\n"
23
+ unless currentJsonDiff.nil?
24
+ jsonErrorInfo << currentJsonDiff
25
+ end
26
+
27
+ return getExpectedActualJson() +"\n" +
28
+ "Diff:\n" +
29
+ "#{jsonErrorInfo}"
30
+ end
31
+
32
+ def generateDifferentValueMessage()
33
+
34
+ # TODO have item in todo list to use a diff if the value is a String
35
+ # with mutiple lines so leaving this diff code even though it is not
36
+ # being used
37
+ differ = RSpec::Support::Differ.new
38
+
39
+ differ = RSpec::Support::Differ.new(
40
+ :object_preparer => lambda {|expected| RSpec::Matchers::Composable.surface_descriptions_in(expected)},
41
+ :color => RSpec::Matchers.configuration.color?
42
+ )
43
+
44
+ @difference = differ.diff_as_object(@matcher.currentExpectedObj, @matcher.currentActualObj)
45
+ # End unused code
46
+
47
+ return getExpectedActualJson() + "\n" +
48
+ "Diff:\n" +
49
+ "JSON path #{@matcher.jsonPath}\n" +
50
+ "\texpected: \"#{@matcher.currentExpectedObj}\"\n" +
51
+ @colorizer.green("\t got: \"#{@matcher.currentActualObj}\"")
52
+ end
53
+
54
+ def generateDifferentKeyMessage()
55
+ if @matcher.currentActualObj.nil?
56
+ objectsNotInExpected = getObjectsNotIn(@matcher.actual, @matcher.expected);
57
+ objectsNotInActual = getObjectsNotIn(@matcher.expected, @matcher.actual);
58
+ else
59
+ objectsNotInExpected = getObjectsNotIn(@matcher.currentActualObj, @matcher.currentExpectedObj);
60
+ objectsNotInActual = getObjectsNotIn(@matcher.currentExpectedObj, @matcher.currentActualObj);
61
+ end
62
+
63
+ jsonErrorInfo = "JSON path #{@matcher.jsonPath}\n"
64
+
65
+ unless objectsNotInExpected.empty?
66
+ jsonErrorInfo << "expected does not contain #{objectsNotInExpected.to_json}\n"
67
+ end
68
+
69
+ unless objectsNotInActual.empty?
70
+ jsonErrorInfo << @colorizer.green("actual does not contain #{objectsNotInActual.to_json}\n")
71
+ end
72
+
73
+ differ = RSpec::Support::Differ.new
74
+
75
+ differ = RSpec::Support::Differ.new(
76
+ :object_preparer => lambda {|expected| RSpec::Matchers::Composable.surface_descriptions_in(expected)},
77
+ :color => RSpec::Matchers.configuration.color?
78
+ )
79
+
80
+ if @matcher.currentActualObj.nil?
81
+ @difference = differ.diff(@matcher.expected, @matcher.actual)
82
+ else
83
+ @difference = differ.diff(@matcher.currentExpectedObj, @matcher.currentActualObj)
84
+ end
85
+
86
+ return getExpectedActualJson() + "\n" +
87
+ "\nDiff:\n" +
88
+ jsonErrorInfo +
89
+ @difference
90
+ end
91
+
92
+ def getExpectedActualJson
93
+ expectedJson=@matcher.expected.to_json;
94
+ actualJson=@matcher.actual.to_json;
95
+
96
+ return "Expected: #{expectedJson}\n" +
97
+ @colorizer.green(" Actual: #{actualJson}")
98
+ end
99
+
100
+ def getObjectsNotIn(hash1, hash2)
101
+ missing = {}
102
+ hash1.each do |hash1_key, hash1_value|
103
+ unless hash2.has_key?(hash1_key)
104
+ missing[hash1_key] = hash1_value
105
+ end
106
+ end
107
+ return missing
108
+ end
109
+
110
+ def getJsonType(rubyJsonObject)
111
+ case rubyJsonObject
112
+ when Array
113
+ return "array"
114
+ when Hash
115
+ return "object"
116
+ else
117
+ return "not json"
118
+ end
119
+ end
120
+
121
+ def generateDifferentSizeArrayMessage()
122
+ if @matcher.currentActualObj.nil?
123
+ expectedLength = @matcher.expected.length
124
+ actualLength = @matcher.actual.length
125
+ else
126
+ expectedLength = @matcher.currentExpectedObj.length
127
+ actualLength = @matcher.currentActualObj.length
128
+ end
129
+
130
+ jsonErrorInfo = "JSON path #{@matcher.jsonPath}[] expected length #{expectedLength} " +
131
+ "actual length #{actualLength}\n"
132
+
133
+ return getExpectedActualJson() + "\n" +
134
+ "\nDiff:\n" +
135
+ jsonErrorInfo
136
+ end
137
+
138
+ def generateExpectedItemNotFoundInArray(expected_item, expected_count, actual_count)
139
+ # if @matcher.currentActualObj.nil?
140
+ # objectsNotInExpected = getObjectsNotInArray(@matcher.actual, @matcher.expected);
141
+ # objectsNotInActual = getObjectsNotInArray(@matcher.expected, @matcher.actual);
142
+ # else
143
+ # objectsNotInExpected = getObjectsNotIn(@matcher.currentActualObj, @matcher.currentExpectedObj);
144
+ # objectsNotInActual = getObjectsNotIn(@matcher.currentExpectedObj, @matcher.currentActualObj);
145
+ # end
146
+
147
+ if actual_count == 0
148
+ jsonErrorInfo = "JSON path #{@matcher.jsonPath}[] could not find:\n" +
149
+ "#{expected_item.to_json}\n" +
150
+ "in actual\n"
151
+ else
152
+ jsonErrorInfo = "JSON path #{@matcher.jsonPath}[] wrong number of:\n" +
153
+ "#{expected_item.to_json}\n" +
154
+ "in actual\n" +
155
+ "expected: #{expected_count}\n" +
156
+ @colorizer.green(" got: #{actual_count}") + "\n"
157
+ end
158
+
159
+ # unless objectsNotInExpected.empty?
160
+ # jsonErrorInfo << "expected does not contain #{objectsNotInExpected.to_json}\n"
161
+ # end
162
+ #
163
+ # unless objectsNotInActual.empty?
164
+ # jsonErrorInfo << @colorizer.green("actual does not contain #{objectsNotInActual.to_json}\n")
165
+ # end
166
+
167
+ return getExpectedActualJson() + "\n" +
168
+ "\nDiff:\n" +
169
+ jsonErrorInfo
170
+ end
171
+
172
+ end
@@ -0,0 +1,3 @@
1
+ module EqWoOrder
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,19 @@
1
+ require 'colorizer'
2
+
3
+ describe 'test color' do
4
+
5
+ it 'expected red text' do
6
+ colorizer = EqJsonColorizer.new()
7
+ expect("\e[31mtest42\e[0m").to eq(colorizer.red("test42"))
8
+ end
9
+
10
+ it 'expected green text' do
11
+ colorizer = EqJsonColorizer.new()
12
+ expect("\e[32mtest42\e[0m").to eq(colorizer.green("test42"))
13
+ end
14
+
15
+ it 'expected blue text' do
16
+ colorizer = EqJsonColorizer.new()
17
+ expect("\e[34mtest42\e[0m").to eq(colorizer.blue("test42"))
18
+ end
19
+ end