jsi 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,24 +1,50 @@
1
1
  module JSI
2
+ # a module relating to objects that act like Hash or Array instances
2
3
  module Typelike
3
- def self.modified_copy(other, &block)
4
- if other.respond_to?(:modified_copy)
5
- other.modified_copy(&block)
4
+ # yields the content of the given param `object`. for objects which have a
5
+ # #modified_copy method of their own (JSI::Base, JSI::JSON::Node) that
6
+ # method is invoked with the given block. otherwise the given object itself
7
+ # is yielded.
8
+ #
9
+ # the given block must result in a modified copy of its block parameter
10
+ # (not destructively modifying the yielded content).
11
+ #
12
+ # @yield [Object] the content of the given object. the block should result
13
+ # in a (nondestructively) modified copy of this.
14
+ # @return [object.class] modified copy of the given object
15
+ def self.modified_copy(object, &block)
16
+ if object.respond_to?(:modified_copy)
17
+ object.modified_copy(&block)
6
18
  else
7
- return yield(other)
19
+ return yield(object)
8
20
  end
9
21
  end
10
22
 
11
- # I could require 'json/add/core' and use #as_json but I like this better.
23
+ # recursive method to express the given argument object in json-compatible
24
+ # types of Hash, Array, and basic types of String/boolean/numeric/nil. this
25
+ # will raise TypeError if an object is given that is not a type that seems
26
+ # to be expressable as json.
27
+ #
28
+ # similar effect could be achieved by requiring 'json/add/core' and using
29
+ # #as_json, but I don't much care for how it represents classes that are
30
+ # not naturally expressable in JSON, and prefer not to load its
31
+ # monkey-patching.
32
+ #
33
+ # @param object [Object] the object to be converted to jsonifiability
34
+ # @return [Array, Hash, String, Boolean, NilClass, Numeric] jsonifiable
35
+ # expression of param object
36
+ # @raise [TypeError] when the object (or an object nested with a hash or
37
+ # array of object) cannot be expressed as json
12
38
  def self.as_json(object, *opt)
13
39
  if object.respond_to?(:to_hash)
14
- object.map do |k, v|
40
+ (object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
15
41
  unless k.is_a?(Symbol) || k.respond_to?(:to_str)
16
42
  raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
17
43
  end
18
44
  {k.to_s => as_json(v, *opt)}
19
45
  end.inject({}, &:update)
20
46
  elsif object.respond_to?(:to_ary)
21
- object.map { |e| as_json(e, *opt) }
47
+ (object.respond_to?(:map) ? object : object.to_ary).map { |e| as_json(e, *opt) }
22
48
  elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| object.is_a?(c) }
23
49
  object
24
50
  elsif object.is_a?(Symbol)
@@ -32,6 +58,11 @@ module JSI
32
58
  end
33
59
  end
34
60
  end
61
+
62
+ # a module of methods for objects which behave like Hash but are not Hash.
63
+ #
64
+ # this module is intended to be internal to JSI. no guarantees or API promises
65
+ # are made for non-JSI classes including this module.
35
66
  module Hashlike
36
67
  # safe methods which can be delegated to #to_hash (which the includer is assumed to have defined).
37
68
  # 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
@@ -52,33 +83,40 @@ module JSI
52
83
  safe_modified_copy_methods.each do |method_name|
53
84
  define_method(method_name) do |*a, &b|
54
85
  JSI::Typelike.modified_copy(self) do |object_to_modify|
55
- object_to_modify.public_send(method_name, *a, &b)
86
+ responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
87
+ responsive_object.public_send(method_name, *a, &b)
56
88
  end
57
89
  end
58
90
  end
59
91
  safe_kv_block_modified_copy_methods.each do |method_name|
60
92
  define_method(method_name) do |*a, &b|
61
93
  JSI::Typelike.modified_copy(self) do |object_to_modify|
62
- object_to_modify.public_send(method_name, *a) do |k, _v|
94
+ responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
95
+ responsive_object.public_send(method_name, *a) do |k, _v|
63
96
  b.call(k, self[k])
64
97
  end
65
98
  end
66
99
  end
67
100
  end
68
101
 
102
+ # @return [String] basically the same #inspect as Hash, but has the
103
+ # class name and, if responsive, self's #object_group_text
69
104
  def inspect
70
105
  object_group_text = respond_to?(:object_group_text) ? ' ' + self.object_group_text : ''
71
- "\#{<#{self.class.to_s}#{object_group_text}>#{empty? ? '' : ' '}#{self.map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(', ')}}"
106
+ "\#{<#{self.class}#{object_group_text}>#{empty? ? '' : ' '}#{self.map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(', ')}}"
72
107
  end
73
108
 
109
+ # @return [String] see #inspect
74
110
  def to_s
75
111
  inspect
76
112
  end
77
113
 
114
+ # pretty-prints a representation this node to the given printer
115
+ # @return [void]
78
116
  def pretty_print(q)
79
117
  q.instance_exec(self) do |obj|
80
118
  object_group_text = obj.respond_to?(:object_group_text) ? ' ' + obj.object_group_text : ''
81
- text "\#{<#{obj.class.to_s}#{object_group_text}>"
119
+ text "\#{<#{obj.class}#{object_group_text}>"
82
120
  group_sub {
83
121
  nest(2) {
84
122
  breakable(obj.any? { true } ? ' ' : '')
@@ -96,6 +134,11 @@ module JSI
96
134
  end
97
135
  end
98
136
  end
137
+
138
+ # a module of methods for objects which behave like Array but are not Array.
139
+ #
140
+ # this module is intended to be internal to JSI. no guarantees or API promises
141
+ # are made for non-JSI classes including this module.
99
142
  module Arraylike
100
143
  # safe methods which can be delegated to #to_ary (which the includer is assumed to have defined).
101
144
  # 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
@@ -120,7 +163,8 @@ module JSI
120
163
  safe_modified_copy_methods.each do |method_name|
121
164
  define_method(method_name) do |*a, &b|
122
165
  JSI::Typelike.modified_copy(self) do |object_to_modify|
123
- object_to_modify.public_send(method_name, *a, &b)
166
+ responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
167
+ responsive_object.public_send(method_name, *a, &b)
124
168
  end
125
169
  end
126
170
  end
@@ -128,26 +172,32 @@ module JSI
128
172
  define_method(method_name) do |*a, &b|
129
173
  JSI::Typelike.modified_copy(self) do |object_to_modify|
130
174
  i = 0
131
- object_to_modify.public_send(method_name, *a) do |_e|
175
+ responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
176
+ responsive_object.public_send(method_name, *a) do |_e|
132
177
  b.call(self[i]).tap { i += 1 }
133
178
  end
134
179
  end
135
180
  end
136
181
  end
137
182
 
183
+ # @return [String] basically the same #inspect as Array, but has the
184
+ # class name and, if responsive, self's #object_group_text
138
185
  def inspect
139
186
  object_group_text = respond_to?(:object_group_text) ? ' ' + self.object_group_text : ''
140
- "\#[<#{self.class.to_s}#{object_group_text}>#{empty? ? '' : ' '}#{self.map { |e| e.inspect }.join(', ')}]"
187
+ "\#[<#{self.class}#{object_group_text}>#{empty? ? '' : ' '}#{self.map { |e| e.inspect }.join(', ')}]"
141
188
  end
142
189
 
190
+ # @return [String] see #inspect
143
191
  def to_s
144
192
  inspect
145
193
  end
146
194
 
195
+ # pretty-prints a representation this node to the given printer
196
+ # @return [void]
147
197
  def pretty_print(q)
148
198
  q.instance_exec(self) do |obj|
149
199
  object_group_text = obj.respond_to?(:object_group_text) ? ' ' + obj.object_group_text : ''
150
- text "\#[<#{obj.class.to_s}#{object_group_text}>"
200
+ text "\#[<#{obj.class}#{object_group_text}>"
151
201
  group_sub {
152
202
  nest(2) {
153
203
  breakable(obj.any? { true } ? ' ' : '')
@@ -1,5 +1,19 @@
1
1
  module JSI
2
2
  module Util
3
+ # returns a version of the given hash, in which any symbol keys are
4
+ # converted to strings. behavior on collisions is undefined (but in the
5
+ # future could take a block like
6
+ # ActiveSupport::HashWithIndifferentAccess#update)
7
+ #
8
+ # at the moment it is undefined whether the returned hash is the same
9
+ # instance as the `hash` param. if `hash` is already a hash which contains
10
+ # no symbol keys, this method MAY return that same instance. use #dup on
11
+ # the return if you need to ensure it is not the same instance as the
12
+ # argument instance.
13
+ #
14
+ # @param hash [#to_hash] the hash from which to convert symbol keys to strings
15
+ # @return [same class as the param `hash`, or Hash if the former cannot be done] a
16
+ # hash(-like) instance containing no symbol keys
3
17
  def stringify_symbol_keys(hash)
4
18
  unless hash.respond_to?(:to_hash)
5
19
  raise(ArgumentError, "expected argument to be a hash; got #{hash.class.inspect}: #{hash.pretty_inspect.chomp}")
@@ -23,7 +37,7 @@ module JSI
23
37
  JSI::Typelike.modified_copy(object) do |hash|
24
38
  changed = false
25
39
  out = {}
26
- hash.each do |k, v|
40
+ (hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
27
41
  if k.is_a?(Symbol)
28
42
  changed = true
29
43
  k = k.to_s
@@ -39,7 +53,7 @@ module JSI
39
53
  elsif object.respond_to?(:to_ary)
40
54
  JSI::Typelike.modified_copy(object) do |ary|
41
55
  changed = false
42
- out = ary.map do |e|
56
+ out = (ary.respond_to?(:each) ? ary : ary.to_ary).map do |e|
43
57
  out_e = deep_stringify_symbol_keys(e)
44
58
  changed = true if out_e.object_id != e.object_id
45
59
  out_e
@@ -50,6 +64,20 @@ module JSI
50
64
  object
51
65
  end
52
66
  end
67
+
68
+ # this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
69
+ # to define a recursive function to return the length of an array:
70
+ #
71
+ # length = ycomb do |len|
72
+ # proc{|list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
73
+ # end
74
+ #
75
+ # see https://secure.wikimedia.org/wikipedia/en/wiki/Fixed_point_combinator#Y_combinator
76
+ # and chapter 9 of the little schemer, available as the sample chapter at http://www.ccs.neu.edu/home/matthias/BTLS/
77
+ def ycomb
78
+ proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
79
+ end
80
+ module_function :ycomb
53
81
  end
54
82
  extend Util
55
83
 
@@ -86,18 +114,4 @@ module JSI
86
114
  end
87
115
  end
88
116
  extend Memoize
89
-
90
- # this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
91
- # to define a recursive function to return the length of an array:
92
- #
93
- # length = ycomb do |len|
94
- # proc{|list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
95
- # end
96
- #
97
- # see https://secure.wikimedia.org/wikipedia/en/wiki/Fixed_point_combinator#Y_combinator
98
- # and chapter 9 of the little schemer, available as the sample chapter at http://www.ccs.neu.edu/home/matthias/BTLS/
99
- def ycomb
100
- proc { |f| f.call(f) }.call(proc { |f| yield proc{|*x| f.call(f).call(*x) } })
101
- end
102
- module_function :ycomb
103
117
  end
@@ -1,3 +1,3 @@
1
1
  module JSI
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2".freeze
3
3
  end
@@ -112,12 +112,12 @@ describe JSI::BaseArray do
112
112
  it('#zip') { assert_equal([['foo', 'foo'], [subject[1], subject[1]], [subject[2], subject[2]]], subject.zip(subject)) }
113
113
  end
114
114
  describe 'modified copy methods' do
115
- it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], [])), subject.reject { |e| e != 'foo' }) }
115
+ it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], [])), subject.reject { |e| e != 'foo' }) }
116
116
  it('#reject block var') do
117
117
  subj_a = subject.to_a
118
118
  subject.reject { |e| assert_equal(e, subj_a.shift) }
119
119
  end
120
- it('#select') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], [])), subject.select { |e| e == 'foo' }) }
120
+ it('#select') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], [])), subject.select { |e| e == 'foo' }) }
121
121
  it('#select block var') do
122
122
  subj_a = subject.to_a
123
123
  subject.select { |e| assert_equal(e, subj_a.shift) }
@@ -30,7 +30,7 @@ describe JSI::BaseHash do
30
30
  assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['foo']), subject['foo'])
31
31
  end
32
32
  it 'sets a property to a schema instance with a different schema' do
33
- orig_foo = subject['foo']
33
+ assert(subject['foo'])
34
34
 
35
35
  subject['foo'] = subject['bar']
36
36
 
@@ -41,7 +41,7 @@ describe JSI::BaseHash do
41
41
  assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['bar']), subject['bar'])
42
42
  end
43
43
  it 'sets a property to a schema instance with the same schema' do
44
- other_subject = class_for_schema.new(JSI::JSON::Node.new_by_type({'foo' => {'x' => 'y'}, 'bar' => [9], 'baz' => true}, []))
44
+ other_subject = class_for_schema.new(JSI::JSON::Node.new_doc({'foo' => {'x' => 'y'}, 'bar' => [9], 'baz' => true}))
45
45
  # Given
46
46
  assert_equal(other_subject, subject)
47
47
 
@@ -103,7 +103,7 @@ describe JSI::BaseHash do
103
103
  it('#to_h') { assert_equal({'foo' => subject['foo'], 'bar' => subject['bar'], 'baz' => true}, subject.to_h) }
104
104
  it('#to_proc') { assert_equal(true, subject.to_proc.call('baz')) } if {}.respond_to?(:to_proc)
105
105
  if {}.respond_to?(:transform_values)
106
- it('#transform_values') { assert_equal({'foo' => nil, 'bar' => nil, 'baz' => nil}, subject.transform_values { |_| nil}) }
106
+ it('#transform_values') { assert_equal({'foo' => nil, 'bar' => nil, 'baz' => nil}, subject.transform_values { |_| nil }) }
107
107
  end
108
108
  it('#value?') { assert_equal(false, subject.value?('0')) }
109
109
  it('#values') { assert_equal([subject['foo'], subject['bar'], true], subject.values) }
@@ -112,9 +112,9 @@ describe JSI::BaseHash do
112
112
  describe 'modified copy methods' do
113
113
  # I'm going to rely on the #merge test above to test the modified copy functionality and just do basic
114
114
  # tests of all the modified copy methods here
115
- it('#merge') { assert_equal(subject, subject.merge({})) }
116
- it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::HashNode.new({}, [])), subject.reject { true }) }
117
- it('#select') { assert_equal(class_for_schema.new(JSI::JSON::HashNode.new({}, [])), subject.select { false }) }
115
+ it('#merge') { assert_equal(subject, subject.merge({})) }
116
+ it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::HashNode.new({}, [])), subject.reject { true }) }
117
+ it('#select') { assert_equal(class_for_schema.new(JSI::JSON::HashNode.new({}, [])), subject.select { false }) }
118
118
  describe '#select' do
119
119
  it 'yields properly too' do
120
120
  subject.select do |k, v|
@@ -124,7 +124,7 @@ describe JSI::BaseHash do
124
124
  end
125
125
  # Hash#compact only available as of ruby 2.5.0
126
126
  if {}.respond_to?(:compact)
127
- it('#compact') { assert_equal(subject, subject.compact) }
127
+ it('#compact') { assert_equal(subject, subject.compact) }
128
128
  end
129
129
  end
130
130
  JSI::Hashlike::DESTRUCTIVE_METHODS.each do |destructive_method_name|
@@ -51,12 +51,12 @@ describe JSI::Base do
51
51
  end
52
52
  describe 'module for schema .inspect' do
53
53
  it '.inspect' do
54
- assert_match(%r(\A#<Module for Schema: .+#>\z), JSI.module_for_schema(schema).inspect)
54
+ assert_match(%r(\A#<Module for Schema: .+#>\z), JSI::SchemaClasses.module_for_schema(schema).inspect)
55
55
  end
56
56
  end
57
57
  describe 'module for schema .schema' do
58
58
  it '.schema' do
59
- assert_equal(schema, JSI.module_for_schema(schema).schema)
59
+ assert_equal(schema, JSI::SchemaClasses.module_for_schema(schema).schema)
60
60
  end
61
61
  end
62
62
  describe 'SchemaClasses[]' do
@@ -81,20 +81,20 @@ describe JSI::Base do
81
81
  assert_equal(JSI.class_for_schema(schema), JSI.class_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
82
82
  end
83
83
  end
84
- describe '.module_for_schema' do
84
+ describe 'JSI::SchemaClasses.module_for_schema' do
85
85
  it 'returns a module from a schema' do
86
- module_for_schema = JSI.module_for_schema(schema)
86
+ module_for_schema = JSI::SchemaClasses.module_for_schema(schema)
87
87
  # same module every time
88
- assert_equal(JSI.module_for_schema(schema), module_for_schema)
88
+ assert_equal(JSI::SchemaClasses.module_for_schema(schema), module_for_schema)
89
89
  end
90
90
  it 'returns a module from a hash' do
91
- assert_equal(JSI.module_for_schema(schema), JSI.module_for_schema(schema.schema_node.content))
91
+ assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.schema_node.content))
92
92
  end
93
93
  it 'returns a module from a schema node' do
94
- assert_equal(JSI.module_for_schema(schema), JSI.module_for_schema(schema.schema_node))
94
+ assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.schema_node))
95
95
  end
96
96
  it 'returns a module from a Base' do
97
- assert_equal(JSI.module_for_schema(schema), JSI.module_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
97
+ assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
98
98
  end
99
99
  end
100
100
  describe 'initialization' do
@@ -107,7 +107,7 @@ describe JSI::Base do
107
107
  describe 'nil' do
108
108
  let(:instance) { nil }
109
109
  it 'initializes with nil instance' do
110
- assert_equal(JSI::JSON::Node.new_by_type(nil, []), subject.instance)
110
+ assert_equal(JSI::JSON::Node.new_doc(nil), subject.instance)
111
111
  assert(!subject.respond_to?(:to_ary))
112
112
  assert(!subject.respond_to?(:to_hash))
113
113
  end
@@ -115,7 +115,7 @@ describe JSI::Base do
115
115
  describe 'arbitrary instance' do
116
116
  let(:instance) { Object.new }
117
117
  it 'initializes' do
118
- assert_equal(JSI::JSON::Node.new_by_type(instance, []), subject.instance)
118
+ assert_equal(JSI::JSON::Node.new_doc(instance), subject.instance)
119
119
  assert(!subject.respond_to?(:to_ary))
120
120
  assert(!subject.respond_to?(:to_hash))
121
121
  end
@@ -124,7 +124,7 @@ describe JSI::Base do
124
124
  let(:instance) { {'foo' => 'bar'} }
125
125
  let(:schema_content) { {'type' => 'object'} }
126
126
  it 'initializes' do
127
- assert_equal(JSI::JSON::Node.new_by_type({'foo' => 'bar'}, []), subject.instance)
127
+ assert_equal(JSI::JSON::Node.new_doc({'foo' => 'bar'}), subject.instance)
128
128
  assert(!subject.respond_to?(:to_ary))
129
129
  assert(subject.respond_to?(:to_hash))
130
130
  end
@@ -142,7 +142,7 @@ describe JSI::Base do
142
142
  let(:instance) { ['foo'] }
143
143
  let(:schema_content) { {'type' => 'array'} }
144
144
  it 'initializes' do
145
- assert_equal(JSI::JSON::Node.new_by_type(['foo'], []), subject.instance)
145
+ assert_equal(JSI::JSON::Node.new_doc(['foo']), subject.instance)
146
146
  assert(subject.respond_to?(:to_ary))
147
147
  assert(!subject.respond_to?(:to_hash))
148
148
  end
@@ -168,8 +168,8 @@ describe JSI::Base do
168
168
  end
169
169
  end
170
170
  describe '#parents, #parent' do
171
- let(:schema_content) { {properties: {foo: {properties: {bar: {properties: {baz: {}}}}}}} }
172
- let(:document) { {foo: {bar: {baz: {}}}} }
171
+ let(:schema_content) { {'properties' => {'foo' => {'properties' => {'bar' => {'properties' => {'baz' => {}}}}}}} }
172
+ let(:document) { {'foo' => {'bar' => {'baz' => {}}}} }
173
173
  describe 'no parents' do
174
174
  it 'has none' do
175
175
  assert_equal([], subject.parents)
@@ -317,7 +317,7 @@ describe JSI::Base do
317
317
  end
318
318
  it 'does not define readers' do
319
319
  assert_equal('bar', subject.foo)
320
- assert_equal(JSI.module_for_schema(subject.schema), subject.method(:foo).owner)
320
+ assert_equal(JSI::SchemaClasses.module_for_schema(subject.schema), subject.method(:foo).owner)
321
321
 
322
322
  assert_equal(JSI::Base, subject.method(:initialize).owner)
323
323
  assert_equal('hi', subject['initialize'])
@@ -377,10 +377,10 @@ describe JSI::Base do
377
377
  end
378
378
  describe '#as_json' do
379
379
  it '#as_json' do
380
- assert_equal({'a' => 'b'}, JSI.class_for_schema({}).new(JSI::JSON::Node.new_by_type({'a' => 'b'}, [])).as_json)
381
- assert_equal({'a' => 'b'}, JSI.class_for_schema({'type' => 'object'}).new(JSI::JSON::Node.new_by_type({'a' => 'b'}, [])).as_json)
382
- assert_equal(['a', 'b'], JSI.class_for_schema({'type' => 'array'}).new(JSI::JSON::Node.new_by_type(['a', 'b'], [])).as_json)
383
- assert_equal(['a'], JSI::class_for_schema({}).new(['a']).as_json(some_option: true))
380
+ assert_equal({'a' => 'b'}, JSI.class_for_schema({}).new(JSI::JSON::Node.new_doc({'a' => 'b'})).as_json)
381
+ assert_equal({'a' => 'b'}, JSI.class_for_schema({'type' => 'object'}).new(JSI::JSON::Node.new_doc({'a' => 'b'})).as_json)
382
+ assert_equal(['a', 'b'], JSI.class_for_schema({'type' => 'array'}).new(JSI::JSON::Node.new_doc(['a', 'b'])).as_json)
383
+ assert_equal(['a'], JSI.class_for_schema({}).new(['a']).as_json(some_option: true))
384
384
  end
385
385
  end
386
386
  describe 'overwrite schema instance with instance=' do
@@ -1,133 +1,149 @@
1
1
  require_relative 'test_helper'
2
2
 
3
- describe JSI::JSON::ArrayNode do
4
- # document of the node being tested
5
- let(:document) { ['a', ['b', 'q'], {'c' => {'d' => 'e'}}] }
6
- # by default the node is the whole document
7
- let(:path) { [] }
8
- # the node being tested
9
- let(:node) { JSI::JSON::Node.new_by_type(document, path) }
3
+ document_types = [
4
+ {
5
+ make_document: -> (d) { d },
6
+ document: ['a', ['b', 'q'], {'c' => {'d' => 'e'}}],
7
+ type_desc: 'Array',
8
+ },
9
+ {
10
+ make_document: -> (d) { SortOfArray.new(d) },
11
+ document: SortOfArray.new(['a', SortOfArray.new(['b', 'q']), SortOfHash.new({'c' => SortOfHash.new({'d' => 'e'})})]),
12
+ type_desc: 'sort of Array-like',
13
+ },
14
+ ]
15
+ document_types.each do |document_type|
16
+ describe "JSI::JSON::ArrayNode with #{document_type[:type_desc]}" do
17
+ # document of the node being tested
18
+ let(:document) { document_type[:document] }
19
+ # by default the node is the whole document
20
+ let(:path) { [] }
21
+ # the node being tested
22
+ let(:node) { JSI::JSON::Node.new_by_type(document, path) }
10
23
 
11
- describe '#[] bad index' do
12
- it 'improves TypeError for Array subsript' do
13
- err = assert_raises(TypeError) do
14
- node[:x]
24
+ describe '#[] bad index' do
25
+ it 'improves TypeError for Array subsript' do
26
+ err = assert_raises(TypeError) do
27
+ node[:x]
28
+ end
29
+ assert_match(/^subscripting with :x \(Symbol\) from Array. content is: \[.*\]\z/m, err.message)
15
30
  end
16
- assert_match(/^subscripting with :x \(Symbol\) from Array. self is: #\[<JSI::JSON::ArrayNode fragment="#">/, err.message)
17
31
  end
18
- end
19
- describe '#each' do
20
- it 'iterates, one argument' do
21
- out = []
22
- node.each do |arg|
23
- out << arg
32
+ describe '#each' do
33
+ it 'iterates, one argument' do
34
+ out = []
35
+ node.each do |arg|
36
+ out << arg
37
+ end
38
+ assert_instance_of(JSI::JSON::ArrayNode, node[1])
39
+ assert_instance_of(JSI::JSON::HashNode, node[2])
40
+ assert_equal(['a', node[1], node[2]], out)
41
+ end
42
+ it 'returns self' do
43
+ assert_equal(node.each { }.object_id, node.object_id)
44
+ end
45
+ it 'returns an enumerator when called with no block' do
46
+ enum = node.each
47
+ assert_instance_of(Enumerator, enum)
48
+ assert_equal(['a', node[1], node[2]], enum.to_a)
24
49
  end
25
- assert_instance_of(JSI::JSON::ArrayNode, node[1])
26
- assert_instance_of(JSI::JSON::HashNode, node[2])
27
- assert_equal(['a', node[1], node[2]], out)
28
50
  end
29
- it 'returns self' do
30
- assert_equal(node.each { }.object_id, node.object_id)
51
+ describe '#to_ary' do
52
+ it 'returns a Array with Nodes in' do
53
+ assert_instance_of(Array, node.to_ary)
54
+ assert_equal(['a', node[1], node[2]], node.to_ary)
55
+ end
31
56
  end
32
- it 'returns an enumerator when called with no block' do
33
- enum = node.each
34
- assert_instance_of(Enumerator, enum)
35
- assert_equal(['a', node[1], node[2]], enum.to_a)
57
+ describe '#as_json' do
58
+ let(:document) { document_type[:make_document].call(['a', 'b']) }
59
+ it '#as_json' do
60
+ assert_equal(['a', 'b'], node.as_json)
61
+ assert_equal(['a', 'b'], node.as_json(some_option: false))
62
+ end
36
63
  end
37
- end
38
- describe '#to_ary' do
39
- it 'returns a Array with Nodes in' do
40
- assert_instance_of(Array, node.to_ary)
41
- assert_equal(['a', node[1], node[2]], node.to_ary)
64
+ # these methods just delegate to Array so not going to test excessively
65
+ describe 'index only methods' do
66
+ it('#each_index') { assert_equal([0, 1, 2], node.each_index.to_a) }
67
+ it('#empty?') { assert_equal(false, node.empty?) }
68
+ it('#length') { assert_equal(3, node.length) }
69
+ it('#size') { assert_equal(3, node.size) }
42
70
  end
43
- end
44
- describe '#as_json' do
45
- let(:document) { ['a', 'b'] }
46
- it '#as_json' do
47
- assert_equal(['a', 'b'], node.as_json)
48
- assert_equal(['a', 'b'], node.as_json(some_option: false))
71
+ describe 'index + element methods' do
72
+ it('#|') { assert_equal(['a', node[1], node[2], 0], node | [0]) }
73
+ it('#&') { assert_equal(['a'], node & ['a']) }
74
+ it('#*') { assert_equal(node.to_a, node * 1) }
75
+ it('#+') { assert_equal(node.to_a, node + []) }
76
+ it('#-') { assert_equal([node[1], node[2]], node - ['a']) }
77
+ it('#<=>') { assert_equal(1, node <=> []) }
78
+ it('#<=>') { assert_equal(-1, [] <=> node) }
79
+ require 'abbrev'
80
+ it('#abbrev') { assert_equal({'a' => 'a'}, JSI::JSON::Node.new_doc(['a']).abbrev) }
81
+ it('#assoc') { assert_equal(['b', 'q'], node.assoc('b')) }
82
+ it('#at') { assert_equal('a', node.at(0)) }
83
+ it('#bsearch') { assert_equal(nil, node.bsearch { false }) }
84
+ it('#bsearch_index') { assert_equal(nil, node.bsearch_index { false }) } if [].respond_to?(:bsearch_index)
85
+ it('#combination') { assert_equal([['a'], [node[1]], [node[2]]], node.combination(1).to_a) }
86
+ it('#count') { assert_equal(1, node.count('a')) }
87
+ it('#cycle') { assert_equal(node.to_a, node.cycle(1).to_a) }
88
+ it('#dig') { assert_equal('e', node.dig(2, 'c', 'd')) } if [].respond_to?(:dig)
89
+ it('#drop') { assert_equal([node[2]], node.drop(2)) }
90
+ it('#drop_while') { assert_equal([node[1], node[2]], node.drop_while { |e| e == 'a' }) }
91
+ it('#fetch') { assert_equal('a', node.fetch(0)) }
92
+ it('#find_index') { assert_equal(0, node.find_index { true }) }
93
+ it('#first') { assert_equal('a', node.first) }
94
+ it('#include?') { assert_equal(true, node.include?('a')) }
95
+ it('#index') { assert_equal(0, node.index('a')) }
96
+ it('#join') { assert_equal('a b', JSI::JSON::Node.new_doc(['a', 'b']).join(' ')) }
97
+ it('#last') { assert_equal(node[2], node.last) }
98
+ it('#pack') { assert_equal(' ', JSI::JSON::Node.new_doc([32]).pack('c')) }
99
+ it('#permutation') { assert_equal([['a'], [node[1]], [node[2]]], node.permutation(1).to_a) }
100
+ it('#product') { assert_equal([], node.product([])) }
101
+ # due to differences in implementation between #assoc and #rassoc, the reason for which
102
+ # I cannot begin to fathom, assoc works but rassoc does not because rassoc has different
103
+ # type checking than assoc for the array(like) array elements.
104
+ # compare:
105
+ # assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813
106
+ # rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847
107
+ # for this reason, rassoc is NOT defined on Arraylike. it's here with as_json.
108
+ #
109
+ # I've never even seen anybody use rassoc. of all the methods to put into the standard library ...
110
+ it('#rassoc') { assert_equal(['b', 'q'], node.as_json.rassoc('q')) }
111
+ it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) }
112
+ it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) }
113
+ it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) }
114
+ it('#reverse_each') { assert_equal([node[2], node[1], 'a'], node.reverse_each.to_a) }
115
+ it('#rindex') { assert_equal(0, node.rindex('a')) }
116
+ it('#rotate') { assert_equal([node[1], node[2], 'a'], node.rotate) }
117
+ it('#sample') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).sample) }
118
+ it('#shelljoin') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).shelljoin) } if [].respond_to?(:shelljoin)
119
+ it('#shuffle') { assert_equal(3, node.shuffle.size) }
120
+ it('#slice') { assert_equal(['a'], node.slice(0, 1)) }
121
+ it('#sort') { assert_equal(['a'], JSI::JSON::Node.new_doc(['a']).sort) }
122
+ it('#take') { assert_equal(['a'], node.take(1)) }
123
+ it('#take_while') { assert_equal([], node.take_while { false }) }
124
+ it('#transpose') { assert_equal([], JSI::JSON::Node.new_doc([]).transpose) }
125
+ it('#uniq') { assert_equal(node.to_a, node.uniq) }
126
+ it('#values_at') { assert_equal(['a'], node.values_at(0)) }
127
+ it('#zip') { assert_equal([['a', 'a'], [node[1], node[1]], [node[2], node[2]]], node.zip(node)) }
49
128
  end
50
- end
51
- # these methods just delegate to Array so not going to test excessively
52
- describe 'index only methods' do
53
- it('#each_index') { assert_equal([0, 1, 2], node.each_index.to_a) }
54
- it('#empty?') { assert_equal(false, node.empty?) }
55
- it('#length') { assert_equal(3, node.length) }
56
- it('#size') { assert_equal(3, node.size) }
57
- end
58
- describe 'index + element methods' do
59
- it('#|') { assert_equal(['a', node[1], node[2], 0], node | [0]) }
60
- it('#&') { assert_equal(['a'], node & ['a']) }
61
- it('#*') { assert_equal(node.to_a, node * 1) }
62
- it('#+') { assert_equal(node.to_a, node + []) }
63
- it('#-') { assert_equal([node[1], node[2]], node - ['a']) }
64
- it('#<=>') { assert_equal(1, node <=> []) }
65
- it('#<=>') { assert_equal(-1, [] <=> node) }
66
- require 'abbrev'
67
- it('#abbrev') { assert_equal({'a' => 'a'}, JSI::JSON::Node.new_by_type(['a'], []).abbrev) }
68
- it('#assoc') { assert_equal(['b', 'q'], node.assoc('b')) }
69
- it('#at') { assert_equal('a', node.at(0)) }
70
- it('#bsearch') { assert_equal(nil, node.bsearch { false }) }
71
- it('#bsearch_index') { assert_equal(nil, node.bsearch_index { false }) } if [].respond_to?(:bsearch_index)
72
- it('#combination') { assert_equal([['a'], [node[1]], [node[2]]], node.combination(1).to_a) }
73
- it('#count') { assert_equal(1, node.count('a')) }
74
- it('#cycle') { assert_equal(node.to_a, node.cycle(1).to_a) }
75
- it('#dig') { assert_equal('e', node.dig(2, 'c', 'd')) } if [].respond_to?(:dig)
76
- it('#drop') { assert_equal([node[2]], node.drop(2)) }
77
- it('#drop_while') { assert_equal([node[1], node[2]], node.drop_while { |e| e == 'a' }) }
78
- it('#fetch') { assert_equal('a', node.fetch(0)) }
79
- it('#find_index') { assert_equal(0, node.find_index { true }) }
80
- it('#first') { assert_equal('a', node.first) }
81
- it('#include?') { assert_equal(true, node.include?('a')) }
82
- it('#index') { assert_equal(0, node.index('a')) }
83
- it('#join') { assert_equal('a b', JSI::JSON::Node.new_by_type(['a', 'b'], []).join(' ')) }
84
- it('#last') { assert_equal(node[2], node.last) }
85
- it('#pack') { assert_equal(' ', JSI::JSON::Node.new_by_type([32], []).pack('c')) }
86
- it('#permutation') { assert_equal([['a'], [node[1]], [node[2]]], node.permutation(1).to_a) }
87
- it('#product') { assert_equal([], node.product([])) }
88
- # due to differences in implementation between #assoc and #rassoc, the reason for which
89
- # I cannot begin to fathom, assoc works but rassoc does not because rassoc has different
90
- # type checking than assoc for the array(like) array elements.
91
- # compare:
92
- # assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813
93
- # rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847
94
- # for this reason, rassoc is NOT defined on Arraylike and #content must be called.
95
- it('#rassoc') { assert_equal(['b', 'q'], node.content.rassoc('q')) }
96
- it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) }
97
- it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) }
98
- it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) }
99
- it('#reverse_each') { assert_equal([node[2], node[1], 'a'], node.reverse_each.to_a) }
100
- it('#rindex') { assert_equal(0, node.rindex('a')) }
101
- it('#rotate') { assert_equal([node[1], node[2], 'a'], node.rotate) }
102
- it('#sample') { assert_equal('a', JSI::JSON::Node.new_by_type(['a'], []).sample) }
103
- it('#shelljoin') { assert_equal('a', JSI::JSON::Node.new_by_type(['a'], []).shelljoin) } if [].respond_to?(:shelljoin)
104
- it('#shuffle') { assert_equal(3, node.shuffle.size) }
105
- it('#slice') { assert_equal(['a'], node.slice(0, 1)) }
106
- it('#sort') { assert_equal(['a'], JSI::JSON::Node.new_by_type(['a'], []).sort) }
107
- it('#take') { assert_equal(['a'], node.take(1)) }
108
- it('#take_while') { assert_equal([], node.take_while { false }) }
109
- it('#transpose') { assert_equal([], JSI::JSON::Node.new_by_type([], []).transpose) }
110
- it('#uniq') { assert_equal(node.to_a, node.uniq) }
111
- it('#values_at') { assert_equal(['a'], node.values_at(0)) }
112
- it('#zip') { assert_equal([['a', 'a'], [node[1], node[1]], [node[2], node[2]]], node.zip(node)) }
113
- end
114
- describe 'modified copy methods' do
115
- it('#reject') { assert_equal(JSI::JSON::Node.new_by_type(['a'], []), node.reject { |e| e != 'a' }) }
116
- it('#select') { assert_equal(JSI::JSON::Node.new_by_type(['a'], []), node.select { |e| e == 'a' }) }
117
- it('#compact') { assert_equal(node, node.compact) }
118
- describe 'at a depth' do
119
- let(:document) { [['b', 'q'], {'c' => ['d', 'e']}] }
120
- let(:path) { ['1', 'c'] }
121
- it('#select') do
122
- selected = node.select { |e| e == 'd' }
123
- equivalent = JSI::JSON::Node.new_by_type([['b', 'q'], {'c' => ['d']}], ['1', 'c'])
124
- assert_equal(equivalent, selected)
129
+ describe 'modified copy methods' do
130
+ it('#reject') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.reject { |e| e != 'a' }) }
131
+ it('#select') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.select { |e| e == 'a' }) }
132
+ it('#compact') { assert_equal(JSI::JSON::Node.new_doc(node.content.to_ary), node.compact) }
133
+ describe 'at a depth' do
134
+ let(:document) { document_type[:make_document].call([['b', 'q'], {'c' => ['d', 'e']}]) }
135
+ let(:path) { ['1', 'c'] }
136
+ it('#select') do
137
+ selected = node.select { |e| e == 'd' }
138
+ equivalent = JSI::JSON::Node.new_by_type([['b', 'q'], {'c' => ['d']}], ['1', 'c'])
139
+ assert_equal(equivalent, selected)
140
+ end
125
141
  end
126
142
  end
127
- end
128
- JSI::Arraylike::DESTRUCTIVE_METHODS.each do |destructive_method_name|
129
- it("does not respond to destructive method #{destructive_method_name}") do
130
- assert(!node.respond_to?(destructive_method_name))
143
+ JSI::Arraylike::DESTRUCTIVE_METHODS.each do |destructive_method_name|
144
+ it("does not respond to destructive method #{destructive_method_name}") do
145
+ assert(!node.respond_to?(destructive_method_name))
146
+ end
131
147
  end
132
148
  end
133
149
  end