From cde3f780028f0c487051f2121584b5352d990a72 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 03:47:00 -0700 Subject: [PATCH 01/10] SortOfArray and SortOfHash for testing duck typing with #to_ary and #to_hash --- test/test_helper.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index 7cff37c1a..7e713531c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -27,3 +27,31 @@ def assert_equal exp, act, msg = nil # register this to be the base class for specs instead of Minitest::Spec Minitest::Spec.register_spec_type(//, JSISpec) + +# tests support of things that duck-type #to_hash +class SortOfHash + def initialize(hash) + @hash = hash + end + def to_hash + @hash + end + include JSI::FingerprintHash + def fingerprint + {class: self.class, hash: @hash} + end +end + +# tests support of things that duck-type #to_ary +class SortOfArray + def initialize(ary) + @ary = ary + end + def to_ary + @ary + end + include JSI::FingerprintHash + def fingerprint + {class: self.class, ary: @ary} + end +end From 1730c3f1dfe5c0abcd37fe69c657843d825c70dc Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 17 Sep 2018 21:14:13 -0700 Subject: [PATCH 02/10] JSI::JSON::Node duck-types with any content quacking #to_hash, #to_ary --- lib/jsi/json-schema-fragments.rb | 11 ++++--- lib/jsi/json/node.rb | 54 ++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/lib/jsi/json-schema-fragments.rb b/lib/jsi/json-schema-fragments.rb index 26763a95e..6f15eb14b 100644 --- a/lib/jsi/json-schema-fragments.rb +++ b/lib/jsi/json-schema-fragments.rb @@ -84,24 +84,25 @@ def initialize(type, representation) # pointed to by this pointer. def evaluate(document) reference_tokens.inject(document) do |value, token| - if value.is_a?(Array) + if value.respond_to?(:to_ary) if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/ token = token.to_i end unless token.is_a?(Integer) raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}") end - unless (0...value.size).include?(token) + unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token) raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}") end - elsif value.is_a?(Hash) - unless value.key?(token) + (value.respond_to?(:[]) ? value : value.to_ary)[token] + elsif value.respond_to?(:to_hash) + unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token) raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}") end + (value.respond_to?(:[]) ? value : value.to_hash)[token] else raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}") end - value[token] end end diff --git a/lib/jsi/json/node.rb b/lib/jsi/json/node.rb index 3ccd68d89..b70a06ca1 100644 --- a/lib/jsi/json/node.rb +++ b/lib/jsi/json/node.rb @@ -33,9 +33,9 @@ def self.new_doc(document) def self.new_by_type(document, path) node = Node.new(document, path) content = node.content - if content.is_a?(Hash) + if content.respond_to?(:to_hash) HashNode.new(document, path) - elsif content.is_a?(Array) + elsif content.respond_to?(:to_ary) ArrayNode.new(document, path) else node @@ -74,10 +74,13 @@ def content def [](subscript) node = self content = node.content - if content.is_a?(Hash) && !content.key?(subscript) + if content.respond_to?(:to_hash) && !(content.respond_to?(:key?) ? content : content.to_hash).key?(subscript) node = node.deref content = node.content end + unless content.respond_to?(:[]) + raise(NoMethodError, "undefined method `[]`\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. self is: #{pretty_inspect.chomp}", e.backtrace) + end begin subcontent = content[subscript] rescue TypeError => e @@ -108,24 +111,27 @@ def []=(subscript, value) def deref content = self.content - return self unless content.is_a?(Hash) && content['$ref'].is_a?(String) + if content.respond_to?(:to_hash) + ref = (content.respond_to?(:[]) ? content : content.to_hash)['$ref'] + end + return self unless ref.is_a?(String) - if content['$ref'][/\A#/] - return self.class.new_by_type(document, ::JSON::Schema::Pointer.parse_fragment(content['$ref'])).deref + if ref[/\A#/] + return self.class.new_by_type(document, ::JSON::Schema::Pointer.parse_fragment(ref)).deref end # HAX for how google does refs and ids if document_node['schemas'].respond_to?(:to_hash) - if document_node['schemas'][content['$ref']] - return document_node['schemas'][content['$ref']] + if document_node['schemas'][ref] + return document_node['schemas'][ref] end - _, deref_by_id = document_node['schemas'].detect { |_k, schema| schema['id'] == content['$ref'] } + _, deref_by_id = document_node['schemas'].detect { |_k, schema| schema['id'] == ref } if deref_by_id return deref_by_id end end - #raise(NotImplementedError, "cannot dereference #{content['$ref']}") # TODO + #raise(NotImplementedError, "cannot dereference #{ref}") # TODO return self end @@ -177,11 +183,12 @@ def modified_copy car = subpath[0] cdr = subpath[1..-1] if subdocument.respond_to?(:to_hash) - car_object = rec.call(subdocument[car], cdr) - if car_object.object_id == subdocument[car].object_id + subdocument_car = (subdocument.respond_to?(:[]) ? subdocument : subdocument.to_hash)[car] + car_object = rec.call(subdocument_car, cdr) + if car_object.object_id == subdocument_car.object_id subdocument else - subdocument.merge({car => car_object}) + (subdocument.respond_to?(:merge) ? subdocument : subdocument.to_hash).merge({car => car_object}) end elsif subdocument.respond_to?(:to_ary) if car.is_a?(String) && car =~ /\A\d+\z/ @@ -190,11 +197,12 @@ def modified_copy unless car.is_a?(Integer) raise(TypeError, "bad subscript #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for array: #{subdocument.pretty_inspect.chomp}") end - car_object = rec.call(subdocument[car], cdr) - if car_object.object_id == subdocument[car].object_id + subdocument_car = (subdocument.respond_to?(:[]) ? subdocument : subdocument.to_ary)[car] + car_object = rec.call(subdocument_car, cdr) + if car_object.object_id == subdocument_car.object_id subdocument else - subdocument.dup.tap do |arr| + (subdocument.respond_to?(:[]=) ? subdocument : subdocument.to_ary).dup.tap do |arr| arr[car] = car_object end end @@ -247,8 +255,8 @@ def fingerprint class ArrayNode < Node # iterates over each element in the same manner as Array#each def each - return to_enum(__method__) { content.size } unless block_given? - content.each_index { |i| yield self[i] } + return to_enum(__method__) { (content.respond_to?(:size) ? content : content.to_ary).size } unless block_given? + (content.respond_to?(:each_index) ? content : content.to_ary).each_index { |i| yield self[i] } self end @@ -268,7 +276,7 @@ def as_json(*opt) # needs redefined after including Enumerable # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a). # we override these methods from Arraylike SAFE_INDEX_ONLY_METHODS.each do |method_name| - define_method(method_name) { |*a, &b| content.public_send(method_name, *a, &b) } + define_method(method_name) { |*a, &b| (content.respond_to?(method_name) ? content : content.to_ary).public_send(method_name, *a, &b) } end end @@ -277,11 +285,11 @@ def as_json(*opt) # needs redefined after including Enumerable class HashNode < Node # iterates over each element in the same manner as Array#each def each(&block) - return to_enum(__method__) { content.size } unless block_given? + return to_enum(__method__) { content.respond_to?(:size) ? content.size : content.to_ary.size } unless block_given? if block.arity > 1 - content.each_key { |k| yield k, self[k] } + (content.respond_to?(:each_key) ? content : content.to_hash).each_key { |k| yield k, self[k] } else - content.each_key { |k| yield [k, self[k]] } + (content.respond_to?(:each_key) ? content : content.to_hash).each_key { |k| yield [k, self[k]] } end self end @@ -301,7 +309,7 @@ def as_json(*opt) # needs redefined after including Enumerable # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash) SAFE_KEY_ONLY_METHODS.each do |method_name| - define_method(method_name) { |*a, &b| content.public_send(method_name, *a, &b) } + define_method(method_name) { |*a, &b| (content.respond_to?(method_name) ? content : content.to_hash).public_send(method_name, *a, &b) } end end end From 71beb6abe375fb9e1912962c0a37e742b934a21b Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 22:21:32 -0700 Subject: [PATCH 03/10] update Node doc, mostly just referring to hash-like / array-like content. --- lib/jsi/json/node.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/jsi/json/node.rb b/lib/jsi/json/node.rb index b70a06ca1..ccf159792 100644 --- a/lib/jsi/json/node.rb +++ b/lib/jsi/json/node.rb @@ -2,7 +2,7 @@ module JSI module JSON # JSI::JSON::Node is an abstraction of a node within a JSON document. # it aims to act like the underlying data type of the node's content - # (Hash or Array, generally) in most cases. + # (generally Hash or Array-like) in most cases. # # the main advantage offered by using a Node over the underlying data # is in dereferencing. if a Node consists of a hash with a $ref property @@ -10,8 +10,8 @@ module JSON # follow the ref and return the referenced data. # # in most other respects, a Node aims to act like a Hash when the content - # is a Hash, an Array when the content is an array. methods of Hash and - # Array are defined and delegated to the node's content. + # is Hash-like, an Array when the content is Array-like. methods of Hash + # and Array are defined and delegated to the node's content. # # however, destructive methods are for the most part not implemented. # at the moment only #[]= is implemented. since Node thinly wraps the @@ -26,10 +26,10 @@ def self.new_doc(document) new_by_type(document, []) end - # if the content of the document at the given path is a Hash, returns - # a HashNode; if an Array, returns ArrayNode. otherwise returns a - # regular Node, though, for the most part this will be called with Hash - # or Array content. + # if the content of the document at the given path is Hash-like, returns + # a HashNode; if Array-like, returns ArrayNode. otherwise returns a + # regular Node, although Nodes are for the most part instantiated from + # Hash or Array-like content. def self.new_by_type(document, path) node = Node.new(document, path) content = node.content @@ -66,8 +66,8 @@ def content # # if the content cannot be subscripted, raises TypeError. # - # if the subcontent is a hash, it is wrapped as a JSI::JSON::HashNode before being returned. - # if the subcontent is an array, it is wrapped as a JSI::JSON::ArrayNode before being returned. + # if the subcontent is Hash-like, it is wrapped as a JSI::JSON::HashNode before being returned. + # if the subcontent is Array-like, it is wrapped as a JSI::JSON::ArrayNode before being returned. # # if this node's content is a $ref - that is, a hash with a $ref attribute - and the subscript is # not a key of the hash, then the $ref is followed before returning the subcontent. @@ -104,7 +104,7 @@ def []=(subscript, value) end end - # returns a Node, dereferencing a $ref attribute if possible. if this node is not a hash, + # returns a Node, dereferencing a $ref attribute if possible. if this node is not hash-like, # does not have a $ref, or if what its $ref cannot be found, this node is returned. # # currently only $refs pointing within the same document are followed. From 782b4f0d82d0c9b2ef780bad829e274258a03804 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 01:01:11 -0700 Subject: [PATCH 04/10] Node #object_group_text includes content #object_group_text if responsive --- lib/jsi/json/node.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsi/json/node.rb b/lib/jsi/json/node.rb index ccf159792..dafb607a3 100644 --- a/lib/jsi/json/node.rb +++ b/lib/jsi/json/node.rb @@ -217,7 +217,7 @@ def modified_copy # meta-information about the object, outside the content. used by #inspect / #pretty_print def object_group_text - "fragment=#{fragment.inspect}" + "fragment=#{fragment.inspect}" + (content.respond_to?(:object_group_text) ? ' ' + content.object_group_text : '') end # a string representing this node From 2b4036c1e7915fd20bcdc6a984e6793407418ee8 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 30 Aug 2018 15:28:47 -0700 Subject: [PATCH 05/10] Typelike.as_json calls #to_hash / #to_ary before map if the object doesn't respond to #map --- lib/jsi/typelike_modules.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jsi/typelike_modules.rb b/lib/jsi/typelike_modules.rb index 44fe85e47..e4931e8b0 100644 --- a/lib/jsi/typelike_modules.rb +++ b/lib/jsi/typelike_modules.rb @@ -37,14 +37,14 @@ def self.modified_copy(object, &block) # array of object) cannot be expressed as json def self.as_json(object, *opt) if object.respond_to?(:to_hash) - object.map do |k, v| + (object.respond_to?(:map) ? object : object.to_hash).map do |k, v| unless k.is_a?(Symbol) || k.respond_to?(:to_str) raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}") end {k.to_s => as_json(v, *opt)} end.inject({}, &:update) elsif object.respond_to?(:to_ary) - object.map { |e| as_json(e, *opt) } + (object.respond_to?(:map) ? object : object.to_ary).map { |e| as_json(e, *opt) } elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| object.is_a?(c) } object elsif object.is_a?(Symbol) From ab1616a25c1aca8505d665044170d4691d78bc4c Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 03:37:43 -0700 Subject: [PATCH 06/10] JSON::Node#[] calls to_hash/to_ary if the content does not respond to #[] --- lib/jsi/json/node.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/jsi/json/node.rb b/lib/jsi/json/node.rb index dafb607a3..36bf70391 100644 --- a/lib/jsi/json/node.rb +++ b/lib/jsi/json/node.rb @@ -79,7 +79,13 @@ def [](subscript) content = node.content end unless content.respond_to?(:[]) - raise(NoMethodError, "undefined method `[]`\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. self is: #{pretty_inspect.chomp}", e.backtrace) + if content.respond_to?(:to_hash) + content = content.to_hash + elsif content.respond_to?(:to_ary) + content = content.to_ary + else + raise(NoMethodError, "undefined method `[]`\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. content is: #{content.pretty_inspect.chomp}") + end end begin subcontent = content[subscript] From ad182140194863bf079d4a0f58d90de3e72eac03 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 03:38:10 -0700 Subject: [PATCH 07/10] Typelike dynamically defined methods which use modified_copy check if the object_to_modify responds to their method, and if not, public_sends on #to_hash/#to_ary instead. --- lib/jsi/typelike_modules.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/jsi/typelike_modules.rb b/lib/jsi/typelike_modules.rb index e4931e8b0..45967feb8 100644 --- a/lib/jsi/typelike_modules.rb +++ b/lib/jsi/typelike_modules.rb @@ -83,14 +83,16 @@ module Hashlike safe_modified_copy_methods.each do |method_name| define_method(method_name) do |*a, &b| JSI::Typelike.modified_copy(self) do |object_to_modify| - object_to_modify.public_send(method_name, *a, &b) + responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash + responsive_object.public_send(method_name, *a, &b) end end end safe_kv_block_modified_copy_methods.each do |method_name| define_method(method_name) do |*a, &b| JSI::Typelike.modified_copy(self) do |object_to_modify| - object_to_modify.public_send(method_name, *a) do |k, _v| + responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash + responsive_object.public_send(method_name, *a) do |k, _v| b.call(k, self[k]) end end @@ -161,7 +163,8 @@ module Arraylike safe_modified_copy_methods.each do |method_name| define_method(method_name) do |*a, &b| JSI::Typelike.modified_copy(self) do |object_to_modify| - object_to_modify.public_send(method_name, *a, &b) + responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary + responsive_object.public_send(method_name, *a, &b) end end end @@ -169,7 +172,8 @@ module Arraylike define_method(method_name) do |*a, &b| JSI::Typelike.modified_copy(self) do |object_to_modify| i = 0 - object_to_modify.public_send(method_name, *a) do |_e| + responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary + responsive_object.public_send(method_name, *a) do |_e| b.call(self[i]).tap { i += 1 } end end From 2d909fd58df934d19dbcb37f1df2767fa7090eea Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 14:52:40 -0700 Subject: [PATCH 08/10] whitespace only in jsi_json_arraynode_test and jsi_json_hashnode_test --- test/jsi_json_arraynode_test.rb | 236 ++++++++++++++++---------------- test/jsi_json_hashnode_test.rb | 206 ++++++++++++++-------------- 2 files changed, 223 insertions(+), 219 deletions(-) diff --git a/test/jsi_json_arraynode_test.rb b/test/jsi_json_arraynode_test.rb index 77bf345c3..e267c8827 100644 --- a/test/jsi_json_arraynode_test.rb +++ b/test/jsi_json_arraynode_test.rb @@ -1,133 +1,135 @@ require_relative 'test_helper' -describe JSI::JSON::ArrayNode do - # document of the node being tested - let(:document) { ['a', ['b', 'q'], {'c' => {'d' => 'e'}}] } - # by default the node is the whole document - let(:path) { [] } - # the node being tested - let(:node) { JSI::JSON::Node.new_by_type(document, path) } +begin + describe JSI::JSON::ArrayNode do + # document of the node being tested + let(:document) { ['a', ['b', 'q'], {'c' => {'d' => 'e'}}] } + # by default the node is the whole document + let(:path) { [] } + # the node being tested + let(:node) { JSI::JSON::Node.new_by_type(document, path) } - describe '#[] bad index' do - it 'improves TypeError for Array subsript' do - err = assert_raises(TypeError) do - node[:x] + describe '#[] bad index' do + it 'improves TypeError for Array subsript' do + err = assert_raises(TypeError) do + node[:x] + end + assert_match(/^subscripting with :x \(Symbol\) from Array. self is: #\[/, err.message) end - assert_match(/^subscripting with :x \(Symbol\) from Array. self is: #\[/, err.message) end - end - describe '#each' do - it 'iterates, one argument' do - out = [] - node.each do |arg| - out << arg + describe '#each' do + it 'iterates, one argument' do + out = [] + node.each do |arg| + out << arg + end + assert_instance_of(JSI::JSON::ArrayNode, node[1]) + assert_instance_of(JSI::JSON::HashNode, node[2]) + assert_equal(['a', node[1], node[2]], out) + end + it 'returns self' do + assert_equal(node.each { }.object_id, node.object_id) + end + it 'returns an enumerator when called with no block' do + enum = node.each + assert_instance_of(Enumerator, enum) + assert_equal(['a', node[1], node[2]], enum.to_a) end - assert_instance_of(JSI::JSON::ArrayNode, node[1]) - assert_instance_of(JSI::JSON::HashNode, node[2]) - assert_equal(['a', node[1], node[2]], out) end - it 'returns self' do - assert_equal(node.each { }.object_id, node.object_id) + describe '#to_ary' do + it 'returns a Array with Nodes in' do + assert_instance_of(Array, node.to_ary) + assert_equal(['a', node[1], node[2]], node.to_ary) + end end - it 'returns an enumerator when called with no block' do - enum = node.each - assert_instance_of(Enumerator, enum) - assert_equal(['a', node[1], node[2]], enum.to_a) + describe '#as_json' do + let(:document) { ['a', 'b'] } + it '#as_json' do + assert_equal(['a', 'b'], node.as_json) + assert_equal(['a', 'b'], node.as_json(some_option: false)) + end end - end - describe '#to_ary' do - it 'returns a Array with Nodes in' do - assert_instance_of(Array, node.to_ary) - assert_equal(['a', node[1], node[2]], node.to_ary) + # these methods just delegate to Array so not going to test excessively + describe 'index only methods' do + it('#each_index') { assert_equal([0, 1, 2], node.each_index.to_a) } + it('#empty?') { assert_equal(false, node.empty?) } + it('#length') { assert_equal(3, node.length) } + it('#size') { assert_equal(3, node.size) } end - end - describe '#as_json' do - let(:document) { ['a', 'b'] } - it '#as_json' do - assert_equal(['a', 'b'], node.as_json) - assert_equal(['a', 'b'], node.as_json(some_option: false)) + describe 'index + element methods' do + it('#|') { assert_equal(['a', node[1], node[2], 0], node | [0]) } + it('#&') { assert_equal(['a'], node & ['a']) } + it('#*') { assert_equal(node.to_a, node * 1) } + it('#+') { assert_equal(node.to_a, node + []) } + it('#-') { assert_equal([node[1], node[2]], node - ['a']) } + it('#<=>') { assert_equal(1, node <=> []) } + it('#<=>') { assert_equal(-1, [] <=> node) } + require 'abbrev' + it('#abbrev') { assert_equal({'a' => 'a'}, JSI::JSON::Node.new_doc(['a']).abbrev) } + it('#assoc') { assert_equal(['b', 'q'], node.assoc('b')) } + it('#at') { assert_equal('a', node.at(0)) } + it('#bsearch') { assert_equal(nil, node.bsearch { false }) } + it('#bsearch_index') { assert_equal(nil, node.bsearch_index { false }) } if [].respond_to?(:bsearch_index) + it('#combination') { assert_equal([['a'], [node[1]], [node[2]]], node.combination(1).to_a) } + it('#count') { assert_equal(1, node.count('a')) } + it('#cycle') { assert_equal(node.to_a, node.cycle(1).to_a) } + it('#dig') { assert_equal('e', node.dig(2, 'c', 'd')) } if [].respond_to?(:dig) + it('#drop') { assert_equal([node[2]], node.drop(2)) } + it('#drop_while') { assert_equal([node[1], node[2]], node.drop_while { |e| e == 'a' }) } + it('#fetch') { assert_equal('a', node.fetch(0)) } + it('#find_index') { assert_equal(0, node.find_index { true }) } + it('#first') { assert_equal('a', node.first) } + it('#include?') { assert_equal(true, node.include?('a')) } + it('#index') { assert_equal(0, node.index('a')) } + it('#join') { assert_equal('a b', JSI::JSON::Node.new_doc(['a', 'b']).join(' ')) } + it('#last') { assert_equal(node[2], node.last) } + it('#pack') { assert_equal(' ', JSI::JSON::Node.new_doc([32]).pack('c')) } + it('#permutation') { assert_equal([['a'], [node[1]], [node[2]]], node.permutation(1).to_a) } + it('#product') { assert_equal([], node.product([])) } + # due to differences in implementation between #assoc and #rassoc, the reason for which + # I cannot begin to fathom, assoc works but rassoc does not because rassoc has different + # type checking than assoc for the array(like) array elements. + # compare: + # assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813 + # rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847 + # for this reason, rassoc is NOT defined on Arraylike and #content must be called. + it('#rassoc') { assert_equal(['b', 'q'], node.content.rassoc('q')) } + it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) } + it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) } + it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) } + it('#reverse_each') { assert_equal([node[2], node[1], 'a'], node.reverse_each.to_a) } + it('#rindex') { assert_equal(0, node.rindex('a')) } + it('#rotate') { assert_equal([node[1], node[2], 'a'], node.rotate) } + it('#sample') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).sample) } + it('#shelljoin') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).shelljoin) } if [].respond_to?(:shelljoin) + it('#shuffle') { assert_equal(3, node.shuffle.size) } + it('#slice') { assert_equal(['a'], node.slice(0, 1)) } + it('#sort') { assert_equal(['a'], JSI::JSON::Node.new_doc(['a']).sort) } + it('#take') { assert_equal(['a'], node.take(1)) } + it('#take_while') { assert_equal([], node.take_while { false }) } + it('#transpose') { assert_equal([], JSI::JSON::Node.new_doc([]).transpose) } + it('#uniq') { assert_equal(node.to_a, node.uniq) } + it('#values_at') { assert_equal(['a'], node.values_at(0)) } + it('#zip') { assert_equal([['a', 'a'], [node[1], node[1]], [node[2], node[2]]], node.zip(node)) } end - end - # these methods just delegate to Array so not going to test excessively - describe 'index only methods' do - it('#each_index') { assert_equal([0, 1, 2], node.each_index.to_a) } - it('#empty?') { assert_equal(false, node.empty?) } - it('#length') { assert_equal(3, node.length) } - it('#size') { assert_equal(3, node.size) } - end - describe 'index + element methods' do - it('#|') { assert_equal(['a', node[1], node[2], 0], node | [0]) } - it('#&') { assert_equal(['a'], node & ['a']) } - it('#*') { assert_equal(node.to_a, node * 1) } - it('#+') { assert_equal(node.to_a, node + []) } - it('#-') { assert_equal([node[1], node[2]], node - ['a']) } - it('#<=>') { assert_equal(1, node <=> []) } - it('#<=>') { assert_equal(-1, [] <=> node) } - require 'abbrev' - it('#abbrev') { assert_equal({'a' => 'a'}, JSI::JSON::Node.new_doc(['a']).abbrev) } - it('#assoc') { assert_equal(['b', 'q'], node.assoc('b')) } - it('#at') { assert_equal('a', node.at(0)) } - it('#bsearch') { assert_equal(nil, node.bsearch { false }) } - it('#bsearch_index') { assert_equal(nil, node.bsearch_index { false }) } if [].respond_to?(:bsearch_index) - it('#combination') { assert_equal([['a'], [node[1]], [node[2]]], node.combination(1).to_a) } - it('#count') { assert_equal(1, node.count('a')) } - it('#cycle') { assert_equal(node.to_a, node.cycle(1).to_a) } - it('#dig') { assert_equal('e', node.dig(2, 'c', 'd')) } if [].respond_to?(:dig) - it('#drop') { assert_equal([node[2]], node.drop(2)) } - it('#drop_while') { assert_equal([node[1], node[2]], node.drop_while { |e| e == 'a' }) } - it('#fetch') { assert_equal('a', node.fetch(0)) } - it('#find_index') { assert_equal(0, node.find_index { true }) } - it('#first') { assert_equal('a', node.first) } - it('#include?') { assert_equal(true, node.include?('a')) } - it('#index') { assert_equal(0, node.index('a')) } - it('#join') { assert_equal('a b', JSI::JSON::Node.new_doc(['a', 'b']).join(' ')) } - it('#last') { assert_equal(node[2], node.last) } - it('#pack') { assert_equal(' ', JSI::JSON::Node.new_doc([32]).pack('c')) } - it('#permutation') { assert_equal([['a'], [node[1]], [node[2]]], node.permutation(1).to_a) } - it('#product') { assert_equal([], node.product([])) } - # due to differences in implementation between #assoc and #rassoc, the reason for which - # I cannot begin to fathom, assoc works but rassoc does not because rassoc has different - # type checking than assoc for the array(like) array elements. - # compare: - # assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813 - # rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847 - # for this reason, rassoc is NOT defined on Arraylike and #content must be called. - it('#rassoc') { assert_equal(['b', 'q'], node.content.rassoc('q')) } - it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) } - it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) } - it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) } - it('#reverse_each') { assert_equal([node[2], node[1], 'a'], node.reverse_each.to_a) } - it('#rindex') { assert_equal(0, node.rindex('a')) } - it('#rotate') { assert_equal([node[1], node[2], 'a'], node.rotate) } - it('#sample') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).sample) } - it('#shelljoin') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).shelljoin) } if [].respond_to?(:shelljoin) - it('#shuffle') { assert_equal(3, node.shuffle.size) } - it('#slice') { assert_equal(['a'], node.slice(0, 1)) } - it('#sort') { assert_equal(['a'], JSI::JSON::Node.new_doc(['a']).sort) } - it('#take') { assert_equal(['a'], node.take(1)) } - it('#take_while') { assert_equal([], node.take_while { false }) } - it('#transpose') { assert_equal([], JSI::JSON::Node.new_doc([]).transpose) } - it('#uniq') { assert_equal(node.to_a, node.uniq) } - it('#values_at') { assert_equal(['a'], node.values_at(0)) } - it('#zip') { assert_equal([['a', 'a'], [node[1], node[1]], [node[2], node[2]]], node.zip(node)) } - end - describe 'modified copy methods' do - it('#reject') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.reject { |e| e != 'a' }) } - it('#select') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.select { |e| e == 'a' }) } - it('#compact') { assert_equal(node, node.compact) } - describe 'at a depth' do - let(:document) { [['b', 'q'], {'c' => ['d', 'e']}] } - let(:path) { ['1', 'c'] } - it('#select') do - selected = node.select { |e| e == 'd' } - equivalent = JSI::JSON::Node.new_by_type([['b', 'q'], {'c' => ['d']}], ['1', 'c']) - assert_equal(equivalent, selected) + describe 'modified copy methods' do + it('#reject') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.reject { |e| e != 'a' }) } + it('#select') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.select { |e| e == 'a' }) } + it('#compact') { assert_equal(node, node.compact) } + describe 'at a depth' do + let(:document) { [['b', 'q'], {'c' => ['d', 'e']}] } + let(:path) { ['1', 'c'] } + it('#select') do + selected = node.select { |e| e == 'd' } + equivalent = JSI::JSON::Node.new_by_type([['b', 'q'], {'c' => ['d']}], ['1', 'c']) + assert_equal(equivalent, selected) + end end end - end - JSI::Arraylike::DESTRUCTIVE_METHODS.each do |destructive_method_name| - it("does not respond to destructive method #{destructive_method_name}") do - assert(!node.respond_to?(destructive_method_name)) + JSI::Arraylike::DESTRUCTIVE_METHODS.each do |destructive_method_name| + it("does not respond to destructive method #{destructive_method_name}") do + assert(!node.respond_to?(destructive_method_name)) + end end end end diff --git a/test/jsi_json_hashnode_test.rb b/test/jsi_json_hashnode_test.rb index e1a33a75c..61b27d730 100644 --- a/test/jsi_json_hashnode_test.rb +++ b/test/jsi_json_hashnode_test.rb @@ -1,117 +1,119 @@ require_relative 'test_helper' -describe JSI::JSON::HashNode do - # document of the node being tested - let(:document) { {'a' => 'b', 'c' => {'d' => 'e'}} } - # by default the node is the whole document - let(:path) { [] } - # the node being tested - let(:node) { JSI::JSON::Node.new_by_type(document, path) } +begin + describe JSI::JSON::HashNode do + # document of the node being tested + let(:document) { {'a' => 'b', 'c' => {'d' => 'e'}} } + # by default the node is the whole document + let(:path) { [] } + # the node being tested + let(:node) { JSI::JSON::Node.new_by_type(document, path) } - describe '#each' do - it 'iterates, one argument' do - out = [] - node.each do |arg| - out << arg + describe '#each' do + it 'iterates, one argument' do + out = [] + node.each do |arg| + out << arg + end + assert_instance_of(JSI::JSON::HashNode, node['c']) + assert_equal([['a', 'b'], ['c', node['c']]], out) end - assert_instance_of(JSI::JSON::HashNode, node['c']) - assert_equal([['a', 'b'], ['c', node['c']]], out) - end - it 'iterates, two arguments' do - out = [] - node.each do |k, v| - out << [k, v] + it 'iterates, two arguments' do + out = [] + node.each do |k, v| + out << [k, v] + end + assert_instance_of(JSI::JSON::HashNode, node['c']) + assert_equal([['a', 'b'], ['c', node['c']]], out) + end + it 'returns self' do + assert_equal(node.each { }.object_id, node.object_id) + end + it 'returns an enumerator when called with no block' do + enum = node.each + assert_instance_of(Enumerator, enum) + assert_equal([['a', 'b'], ['c', node['c']]], enum.to_a) end - assert_instance_of(JSI::JSON::HashNode, node['c']) - assert_equal([['a', 'b'], ['c', node['c']]], out) end - it 'returns self' do - assert_equal(node.each { }.object_id, node.object_id) + describe '#to_hash' do + it 'returns a Hash with Nodes in' do + assert_instance_of(Hash, node.to_hash) + assert_equal({'a' => 'b', 'c' => node['c']}, node.to_hash) + end end - it 'returns an enumerator when called with no block' do - enum = node.each - assert_instance_of(Enumerator, enum) - assert_equal([['a', 'b'], ['c', node['c']]], enum.to_a) + describe '#merge' do + let(:document) { {'a' => {'b' => 0}, 'c' => {'d' => 'e'}} } + # testing the node at 'c' here, merging a hash at a path within a document. + let(:path) { ['c'] } + it 'merges' do + merged = node.merge('x' => 'y') + # check the content at 'c' was merged with the remainder of the document intact (at 'a') + assert_equal({'a' => {'b' => 0}, 'c' => {'d' => 'e', 'x' => 'y'}}, merged.document) + # check the original node retains its original document + assert_equal({'a' => {'b' => 0}, 'c' => {'d' => 'e'}}, node.document) + # check that unnecessary copies of unaffected parts of the document were not made + assert_equal(node.document['a'].object_id, merged.document['a'].object_id) + end end - end - describe '#to_hash' do - it 'returns a Hash with Nodes in' do - assert_instance_of(Hash, node.to_hash) - assert_equal({'a' => 'b', 'c' => node['c']}, node.to_hash) + describe '#as_json' do + let(:document) { {'a' => 'b'} } + it '#as_json' do + assert_equal({'a' => 'b'}, node.as_json) + assert_equal({'a' => 'b'}, node.as_json(this_option: 'what?')) + end end - end - describe '#merge' do - let(:document) { {'a' => {'b' => 0}, 'c' => {'d' => 'e'}} } - # testing the node at 'c' here, merging a hash at a path within a document. - let(:path) { ['c'] } - it 'merges' do - merged = node.merge('x' => 'y') - # check the content at 'c' was merged with the remainder of the document intact (at 'a') - assert_equal({'a' => {'b' => 0}, 'c' => {'d' => 'e', 'x' => 'y'}}, merged.document) - # check the original node retains its original document - assert_equal({'a' => {'b' => 0}, 'c' => {'d' => 'e'}}, node.document) - # check that unnecessary copies of unaffected parts of the document were not made - assert_equal(node.document['a'].object_id, merged.document['a'].object_id) + # these methods just delegate to Hash so not going to test excessively + describe 'key only methods' do + it('#each_key') { assert_equal(['a', 'c'], node.each_key.to_a) } + it('#empty?') { assert_equal(false, node.empty?) } + it('#has_key?') { assert_equal(true, node.has_key?('a')) } + it('#include?') { assert_equal(false, node.include?('q')) } + it('#key?') { assert_equal(true, node.key?('c')) } + it('#keys') { assert_equal(['a', 'c'], node.keys) } + it('#length') { assert_equal(2, node.length) } + it('#member?') { assert_equal(false, node.member?(0)) } + it('#size') { assert_equal(2, node.size) } end - end - describe '#as_json' do - let(:document) { {'a' => 'b'} } - it '#as_json' do - assert_equal({'a' => 'b'}, node.as_json) - assert_equal({'a' => 'b'}, node.as_json(this_option: 'what?')) - end - end - # these methods just delegate to Hash so not going to test excessively - describe 'key only methods' do - it('#each_key') { assert_equal(['a', 'c'], node.each_key.to_a) } - it('#empty?') { assert_equal(false, node.empty?) } - it('#has_key?') { assert_equal(true, node.has_key?('a')) } - it('#include?') { assert_equal(false, node.include?('q')) } - it('#key?') { assert_equal(true, node.key?('c')) } - it('#keys') { assert_equal(['a', 'c'], node.keys) } - it('#length') { assert_equal(2, node.length) } - it('#member?') { assert_equal(false, node.member?(0)) } - it('#size') { assert_equal(2, node.size) } - end - describe 'key + value methods' do - it('#<') { assert_equal(true, node < {'a' => 'b', 'c' => node['c'], 'x' => 'y'}) } if {}.respond_to?(:<) - it('#<=') { assert_equal(true, node <= node) } if {}.respond_to?(:<=) - it('#>') { assert_equal(true, node > {}) } if {}.respond_to?(:>) - it('#>=') { assert_equal(false, node >= {'foo' => 'bar'}) } if {}.respond_to?(:>=) - it('#any?') { assert_equal(false, node.any? { |k, v| v == 3 }) } - it('#assoc') { assert_equal(['a', 'b'], node.assoc('a')) } - it('#dig') { assert_equal('e', node.dig('c', 'd')) } if {}.respond_to?(:dig) - it('#each_pair') { assert_equal([['a', 'b'], ['c', node['c']]], node.each_pair.to_a) } - it('#each_value') { assert_equal(['b', node['c']], node.each_value.to_a) } - it('#fetch') { assert_equal('b', node.fetch('a')) } - it('#fetch_values') { assert_equal(['b'], node.fetch_values('a')) } if {}.respond_to?(:fetch_values) - it('#has_value?') { assert_equal(true, node.has_value?('b')) } - it('#invert') { assert_equal({'b' => 'a', node['c'] => 'c'}, node.invert) } - it('#key') { assert_equal('a', node.key('b')) } - it('#rassoc') { assert_equal(['a', 'b'], node.rassoc('b')) } - it('#to_h') { assert_equal({'a' => 'b', 'c' => node['c']}, node.to_h) } - it('#to_proc') { assert_equal('b', node.to_proc.call('a')) } if {}.respond_to?(:to_proc) - if {}.respond_to?(:transform_values) - it('#transform_values') { assert_equal({'a' => nil, 'c' => nil}, node.transform_values { |_| nil }) } + describe 'key + value methods' do + it('#<') { assert_equal(true, node < {'a' => 'b', 'c' => node['c'], 'x' => 'y'}) } if {}.respond_to?(:<) + it('#<=') { assert_equal(true, node <= node) } if {}.respond_to?(:<=) + it('#>') { assert_equal(true, node > {}) } if {}.respond_to?(:>) + it('#>=') { assert_equal(false, node >= {'foo' => 'bar'}) } if {}.respond_to?(:>=) + it('#any?') { assert_equal(false, node.any? { |k, v| v == 3 }) } + it('#assoc') { assert_equal(['a', 'b'], node.assoc('a')) } + it('#dig') { assert_equal('e', node.dig('c', 'd')) } if {}.respond_to?(:dig) + it('#each_pair') { assert_equal([['a', 'b'], ['c', node['c']]], node.each_pair.to_a) } + it('#each_value') { assert_equal(['b', node['c']], node.each_value.to_a) } + it('#fetch') { assert_equal('b', node.fetch('a')) } + it('#fetch_values') { assert_equal(['b'], node.fetch_values('a')) } if {}.respond_to?(:fetch_values) + it('#has_value?') { assert_equal(true, node.has_value?('b')) } + it('#invert') { assert_equal({'b' => 'a', node['c'] => 'c'}, node.invert) } + it('#key') { assert_equal('a', node.key('b')) } + it('#rassoc') { assert_equal(['a', 'b'], node.rassoc('b')) } + it('#to_h') { assert_equal({'a' => 'b', 'c' => node['c']}, node.to_h) } + it('#to_proc') { assert_equal('b', node.to_proc.call('a')) } if {}.respond_to?(:to_proc) + if {}.respond_to?(:transform_values) + it('#transform_values') { assert_equal({'a' => nil, 'c' => nil}, node.transform_values { |_| nil }) } + end + it('#value?') { assert_equal(false, node.value?('0')) } + it('#values') { assert_equal(['b', node['c']], node.values) } + it('#values_at') { assert_equal(['b'], node.values_at('a')) } end - it('#value?') { assert_equal(false, node.value?('0')) } - it('#values') { assert_equal(['b', node['c']], node.values) } - it('#values_at') { assert_equal(['b'], node.values_at('a')) } - end - describe 'modified copy methods' do - # I'm going to rely on the #merge test above to test the modified copy functionality and just do basic - # tests of all the modified copy methods here - it('#merge') { assert_equal(node, node.merge({})) } - it('#reject') { assert_equal(JSI::JSON::Node.new_doc({}), node.reject { true }) } - it('#select') { assert_equal(JSI::JSON::Node.new_doc({}), node.select { false }) } - # Hash#compact only available as of ruby 2.5.0 - if {}.respond_to?(:compact) - it('#compact') { assert_equal(node, node.compact) } + describe 'modified copy methods' do + # I'm going to rely on the #merge test above to test the modified copy functionality and just do basic + # tests of all the modified copy methods here + it('#merge') { assert_equal(node, node.merge({})) } + it('#reject') { assert_equal(JSI::JSON::Node.new_doc({}), node.reject { true }) } + it('#select') { assert_equal(JSI::JSON::Node.new_doc({}), node.select { false }) } + # Hash#compact only available as of ruby 2.5.0 + if {}.respond_to?(:compact) + it('#compact') { assert_equal(node, node.compact) } + end end - end - JSI::Hashlike::DESTRUCTIVE_METHODS.each do |destructive_method_name| - it("does not respond to destructive method #{destructive_method_name}") do - assert(!node.respond_to?(destructive_method_name)) + JSI::Hashlike::DESTRUCTIVE_METHODS.each do |destructive_method_name| + it("does not respond to destructive method #{destructive_method_name}") do + assert(!node.respond_to?(destructive_method_name)) + end end end end From 15cd9545d41595dcc2c4119ecf7b971084f31523 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 14:55:41 -0700 Subject: [PATCH 09/10] we will use documents of multiple types to test JSI::JSON::HashNode and ArrayNode. we repeat the test suite across these documents. --- test/jsi_json_arraynode_test.rb | 22 +++++++++++++++++----- test/jsi_json_hashnode_test.rb | 22 +++++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/test/jsi_json_arraynode_test.rb b/test/jsi_json_arraynode_test.rb index e267c8827..19a45c60c 100644 --- a/test/jsi_json_arraynode_test.rb +++ b/test/jsi_json_arraynode_test.rb @@ -1,9 +1,21 @@ require_relative 'test_helper' -begin - describe JSI::JSON::ArrayNode do +document_types = [ + { + make_document: -> (d) { d }, + document: ['a', ['b', 'q'], {'c' => {'d' => 'e'}}], + type_desc: 'Array', + }, + { + make_document: -> (d) { SortOfArray.new(d) }, + document: SortOfArray.new(['a', SortOfArray.new(['b', 'q']), SortOfHash.new({'c' => SortOfHash.new({'d' => 'e'})})]), + type_desc: 'sort of Array-like', + }, +] +document_types.each do |document_type| + describe "JSI::JSON::ArrayNode with #{document_type[:type_desc]}" do # document of the node being tested - let(:document) { ['a', ['b', 'q'], {'c' => {'d' => 'e'}}] } + let(:document) { document_type[:document] } # by default the node is the whole document let(:path) { [] } # the node being tested @@ -43,7 +55,7 @@ end end describe '#as_json' do - let(:document) { ['a', 'b'] } + let(:document) { document_type[:make_document].call(['a', 'b']) } it '#as_json' do assert_equal(['a', 'b'], node.as_json) assert_equal(['a', 'b'], node.as_json(some_option: false)) @@ -117,7 +129,7 @@ it('#select') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.select { |e| e == 'a' }) } it('#compact') { assert_equal(node, node.compact) } describe 'at a depth' do - let(:document) { [['b', 'q'], {'c' => ['d', 'e']}] } + let(:document) { document_type[:make_document].call([['b', 'q'], {'c' => ['d', 'e']}]) } let(:path) { ['1', 'c'] } it('#select') do selected = node.select { |e| e == 'd' } diff --git a/test/jsi_json_hashnode_test.rb b/test/jsi_json_hashnode_test.rb index 61b27d730..1c31a58b9 100644 --- a/test/jsi_json_hashnode_test.rb +++ b/test/jsi_json_hashnode_test.rb @@ -1,9 +1,21 @@ require_relative 'test_helper' -begin - describe JSI::JSON::HashNode do +document_types = [ + { + make_document: -> (d) { d }, + document: {'a' => 'b', 'c' => {'d' => 'e'}}, + type_desc: 'Hash', + }, + { + make_document: -> (d) { SortOfHash.new(d) }, + document: SortOfHash.new({'a' => 'b', 'c' => SortOfHash.new({'d' => 'e'})}), + type_desc: 'sort of Hash-like', + }, +] +document_types.each do |document_type| + describe "JSI::JSON::HashNode with #{document_type[:type_desc]}" do # document of the node being tested - let(:document) { {'a' => 'b', 'c' => {'d' => 'e'}} } + let(:document) { document_type[:document] } # by default the node is the whole document let(:path) { [] } # the node being tested @@ -42,7 +54,7 @@ end end describe '#merge' do - let(:document) { {'a' => {'b' => 0}, 'c' => {'d' => 'e'}} } + let(:document) { document_type[:make_document].call({'a' => {'b' => 0}, 'c' => {'d' => 'e'}}) } # testing the node at 'c' here, merging a hash at a path within a document. let(:path) { ['c'] } it 'merges' do @@ -56,7 +68,7 @@ end end describe '#as_json' do - let(:document) { {'a' => 'b'} } + let(:document) { document_type[:make_document].call({'a' => 'b'}) } it '#as_json' do assert_equal({'a' => 'b'}, node.as_json) assert_equal({'a' => 'b'}, node.as_json(this_option: 'what?')) From 15ad8deb88af6f8406780bd744e04b8f41287d4f Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Sep 2018 15:44:59 -0700 Subject: [PATCH 10/10] fix test of HashNode and ArrayNode with content that is vaguely hash or array like --- test/jsi_json_arraynode_test.rb | 8 +++++--- test/jsi_json_hashnode_test.rb | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/jsi_json_arraynode_test.rb b/test/jsi_json_arraynode_test.rb index 19a45c60c..7d6b601c0 100644 --- a/test/jsi_json_arraynode_test.rb +++ b/test/jsi_json_arraynode_test.rb @@ -104,8 +104,10 @@ # compare: # assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813 # rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847 - # for this reason, rassoc is NOT defined on Arraylike and #content must be called. - it('#rassoc') { assert_equal(['b', 'q'], node.content.rassoc('q')) } + # for this reason, rassoc is NOT defined on Arraylike. it's here with as_json. + # + # I've never even seen anybody use rassoc. of all the methods to put into the standard library ... + it('#rassoc') { assert_equal(['b', 'q'], node.as_json.rassoc('q')) } it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) } it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) } it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) } @@ -127,7 +129,7 @@ describe 'modified copy methods' do it('#reject') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.reject { |e| e != 'a' }) } it('#select') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.select { |e| e == 'a' }) } - it('#compact') { assert_equal(node, node.compact) } + it('#compact') { assert_equal(JSI::JSON::Node.new_doc(node.content.to_ary), node.compact) } describe 'at a depth' do let(:document) { document_type[:make_document].call([['b', 'q'], {'c' => ['d', 'e']}]) } let(:path) { ['1', 'c'] } diff --git a/test/jsi_json_hashnode_test.rb b/test/jsi_json_hashnode_test.rb index 1c31a58b9..8bba2553f 100644 --- a/test/jsi_json_hashnode_test.rb +++ b/test/jsi_json_hashnode_test.rb @@ -62,9 +62,9 @@ # check the content at 'c' was merged with the remainder of the document intact (at 'a') assert_equal({'a' => {'b' => 0}, 'c' => {'d' => 'e', 'x' => 'y'}}, merged.document) # check the original node retains its original document - assert_equal({'a' => {'b' => 0}, 'c' => {'d' => 'e'}}, node.document) + assert_equal(document_type[:make_document].call({'a' => {'b' => 0}, 'c' => {'d' => 'e'}}), node.document) # check that unnecessary copies of unaffected parts of the document were not made - assert_equal(node.document['a'].object_id, merged.document['a'].object_id) + assert_equal(node.document.to_hash['a'].object_id, merged.document['a'].object_id) end end describe '#as_json' do @@ -114,12 +114,12 @@ describe 'modified copy methods' do # I'm going to rely on the #merge test above to test the modified copy functionality and just do basic # tests of all the modified copy methods here - it('#merge') { assert_equal(node, node.merge({})) } + it('#merge') { assert_equal(JSI::JSON::Node.new_doc(node.content.to_hash), node.merge({})) } it('#reject') { assert_equal(JSI::JSON::Node.new_doc({}), node.reject { true }) } it('#select') { assert_equal(JSI::JSON::Node.new_doc({}), node.select { false }) } # Hash#compact only available as of ruby 2.5.0 if {}.respond_to?(:compact) - it('#compact') { assert_equal(node, node.compact) } + it('#compact') { assert_equal(JSI::JSON::Node.new_doc({"a" => "b", "c" => node.content.to_hash["c"]}), node.compact) } end end JSI::Hashlike::DESTRUCTIVE_METHODS.each do |destructive_method_name|