diff --git a/lib/json_spec.rb b/lib/json_spec.rb index 8044741..8833e91 100644 --- a/lib/json_spec.rb +++ b/lib/json_spec.rb @@ -5,6 +5,7 @@ require "json_spec/exclusion" require "json_spec/helpers" require "json_spec/messages" +require "json_spec/order_indifference" require "json_spec/matchers" require "json_spec/memory" diff --git a/lib/json_spec/matchers/be_json_eql.rb b/lib/json_spec/matchers/be_json_eql.rb index 951dc5a..ba840b1 100644 --- a/lib/json_spec/matchers/be_json_eql.rb +++ b/lib/json_spec/matchers/be_json_eql.rb @@ -3,6 +3,7 @@ module Matchers class BeJsonEql include JsonSpec::Helpers include JsonSpec::Exclusion + include JsonSpec::OrderIndifference include JsonSpec::Messages attr_reader :expected, :actual @@ -42,6 +43,11 @@ def including(*keys) self end + def in_any_order(state = true) + toggle_order_indifference(state) + self + end + def failure_message message_with_path("Expected equivalent JSON") end @@ -58,7 +64,7 @@ def description private def scrub(json, path = nil) - generate_normalized_json(exclude_keys(parse_json(json, path))).chomp + "\n" + generate_normalized_json(indifferize(exclude_keys(parse_json(json, path)))).chomp + "\n" end end end diff --git a/lib/json_spec/order_indifference.rb b/lib/json_spec/order_indifference.rb new file mode 100644 index 0000000..14718e4 --- /dev/null +++ b/lib/json_spec/order_indifference.rb @@ -0,0 +1,25 @@ +module JsonSpec + module OrderIndifference + extend self + + def toggle_order_indifference(toggle) + @indifferent = !!toggle + end + + def indifferize(ruby) + return ruby unless @indifferent + + case ruby + when Hash + ruby.inject({}) do |hash, (key, value)| + hash[key] = indifferize(value) + hash + end + when Array + ruby.map{|v| indifferize(v) }.sort_by{|v| generate_normalized_json(v) } + else ruby + end + end + + end +end diff --git a/spec/json_spec/matchers/be_json_eql_spec.rb b/spec/json_spec/matchers/be_json_eql_spec.rb index 7eb8e1a..1306d50 100644 --- a/spec/json_spec/matchers/be_json_eql_spec.rb +++ b/spec/json_spec/matchers/be_json_eql_spec.rb @@ -17,6 +17,22 @@ %(["json","spec"]).should_not be_json_eql(%(["spec","json"])) end + it "matches out-of-order arrays when chained with in_any_order" do + %(["json","spec"]).should be_json_eql(%(["spec","json"])).in_any_order + end + + it "matches out-of-order hashes when chained with in_any_order" do + %({"laser":"lemon","json":"spec"}).should be_json_eql(%({"json":"spec","laser":"lemon"})).in_any_order + end + + it "doesn't match out-of-order arrays when chained with in_any_order if passed false" do + %(["json","spec"]).should_not be_json_eql(%(["spec","json"])).in_any_order(false) + end + + it "matches complex nested out-of-order arrays when chained with in_any_order" do + %({"json":[{"spec":4,"laser":[{"id":2,"lemon":"a"},{"id":5,"lemon":"b"}]},{"spec":9,"laser":[{"id":3,"lemon":"c"},{"id":1,"lemon":"d"}]}]}).should be_json_eql(%({"json":[{"laser":[{"lemon":"d","id":1},{"lemon":"c","id":3}],"spec":9},{"laser":[{"lemon":"b","id":5},{"lemon":"a","id":2}],"spec":4}]})).in_any_order + end + it "matches valid JSON values, yet invalid JSON documents" do %("json_spec").should be_json_eql(%("json_spec")) end @@ -25,6 +41,10 @@ %({"json":["spec"]}).should be_json_eql(%("spec")).at_path("json/0") end + it "matches out-of-order arrays at a path when chained with in_any_order" do + %({"json":[{"spec":[4,2,3,1,5]}]}).should be_json_eql(%([1,2,3,4,5])).at_path("json/0/spec").in_any_order + end + it "ignores excluded-by-default hash keys" do JsonSpec.excluded_keys.should_not be_empty @@ -66,6 +86,11 @@ %({"id":1,"json":"spec"}).should be_json_eql(%({"id":2,"json":"spec"})).excluding(:id) end + it "excludes extra hash keys given as symbols and matches out-of-order arrays when chained with in_any_order" do + JsonSpec.excluded_keys = [] + %([{"id":1,"json":"spec"},{"id":4,"json":"laser"}]).should be_json_eql(%([{"id":3,"json":"laser"},{"id":2,"json":"spec"}])).excluding(:id).in_any_order + end + it "excludes multiple keys" do JsonSpec.excluded_keys = [] %({"id":1,"json":"spec"}).should be_json_eql(%({"id":2,"json":"different"})).excluding(:id, :json)