seeing_is_believing 3.0.0.beta.4 → 3.0.0.beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +0 -8
- data/Rakefile +1 -1
- data/Readme.md +65 -25
- data/bin/seeing_is_believing +1 -0
- data/docs/sib-streaming.gif +0 -0
- data/features/deprecated-flags.feature +62 -2
- data/features/errors.feature +12 -7
- data/features/examples.feature +143 -4
- data/features/flags.feature +89 -29
- data/features/regression.feature +58 -14
- data/features/support/env.rb +4 -0
- data/features/xmpfilter-style.feature +181 -36
- data/lib/seeing_is_believing.rb +44 -33
- data/lib/seeing_is_believing/binary.rb +31 -88
- data/lib/seeing_is_believing/binary/align_chunk.rb +30 -11
- data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +10 -16
- data/lib/seeing_is_believing/binary/annotate_every_line.rb +5 -25
- data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +136 -0
- data/lib/seeing_is_believing/binary/comment_lines.rb +8 -10
- data/lib/seeing_is_believing/binary/commentable_lines.rb +20 -26
- data/lib/seeing_is_believing/binary/config.rb +392 -0
- data/lib/seeing_is_believing/binary/data_structures.rb +57 -0
- data/lib/seeing_is_believing/binary/engine.rb +104 -0
- data/lib/seeing_is_believing/binary/{comment_formatter.rb → format_comment.rb} +6 -6
- data/lib/seeing_is_believing/binary/remove_annotations.rb +29 -28
- data/lib/seeing_is_believing/binary/rewrite_comments.rb +42 -43
- data/lib/seeing_is_believing/code.rb +105 -49
- data/lib/seeing_is_believing/debugger.rb +6 -5
- data/lib/seeing_is_believing/error.rb +6 -17
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +78 -129
- data/lib/seeing_is_believing/event_stream/consumer.rb +114 -64
- data/lib/seeing_is_believing/event_stream/events.rb +169 -11
- data/lib/seeing_is_believing/event_stream/handlers/debug.rb +57 -0
- data/lib/seeing_is_believing/event_stream/handlers/record_exitstatus.rb +18 -0
- data/lib/seeing_is_believing/event_stream/handlers/stream_json_events.rb +45 -0
- data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +39 -0
- data/lib/seeing_is_believing/event_stream/producer.rb +25 -24
- data/lib/seeing_is_believing/hash_struct.rb +206 -0
- data/lib/seeing_is_believing/result.rb +20 -3
- data/lib/seeing_is_believing/the_matrix.rb +20 -12
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing/wrap_expressions.rb +55 -115
- data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +14 -0
- data/seeing_is_believing.gemspec +1 -1
- data/spec/binary/alignment_specs.rb +27 -0
- data/spec/binary/comment_lines_spec.rb +3 -2
- data/spec/binary/config_spec.rb +657 -0
- data/spec/binary/engine_spec.rb +97 -0
- data/spec/binary/{comment_formatter_spec.rb → format_comment_spec.rb} +2 -2
- data/spec/binary/marker_spec.rb +71 -0
- data/spec/binary/options_spec.rb +0 -0
- data/spec/binary/remove_annotations_spec.rb +31 -18
- data/spec/binary/rewrite_comments_spec.rb +26 -11
- data/spec/code_spec.rb +190 -6
- data/spec/debugger_spec.rb +4 -0
- data/spec/evaluate_by_moving_files_spec.rb +38 -20
- data/spec/event_stream_spec.rb +265 -116
- data/spec/hash_struct_spec.rb +514 -0
- data/spec/seeing_is_believing_spec.rb +108 -46
- data/spec/spec_helper.rb +9 -0
- data/spec/wrap_expressions_spec.rb +207 -172
- metadata +30 -18
- data/docs/for-presentations +0 -33
- data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +0 -128
- data/lib/seeing_is_believing/binary/interpret_flags.rb +0 -156
- data/lib/seeing_is_believing/binary/parse_args.rb +0 -263
- data/lib/seeing_is_believing/event_stream/update_result.rb +0 -24
- data/lib/seeing_is_believing/inspect_expressions.rb +0 -21
- data/lib/seeing_is_believing/parser_helpers.rb +0 -82
- data/spec/binary/interpret_flags_spec.rb +0 -332
- data/spec/binary/parse_args_spec.rb +0 -415
@@ -0,0 +1,514 @@
|
|
1
|
+
require 'seeing_is_believing/hash_struct'
|
2
|
+
|
3
|
+
RSpec.describe SeeingIsBelieving::HashStruct do
|
4
|
+
let(:klass) { described_class.anon }
|
5
|
+
|
6
|
+
def eq!(expected, actual, *message)
|
7
|
+
expect(actual).to eq(expected), *message
|
8
|
+
end
|
9
|
+
|
10
|
+
def neq!(expected, actual, *message)
|
11
|
+
expect(actual).to_not eq(expected), *message
|
12
|
+
end
|
13
|
+
|
14
|
+
def raises!(*exception_class_and_matcher, &block)
|
15
|
+
expect(&block).to raise_error(*exception_class_and_matcher)
|
16
|
+
end
|
17
|
+
|
18
|
+
def include!(needle, haystack)
|
19
|
+
expect(haystack).to include needle
|
20
|
+
end
|
21
|
+
|
22
|
+
def ninclude!(needle, haystack)
|
23
|
+
expect(haystack).to_not include needle
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'declaration' do
|
27
|
+
describe 'attributes' do
|
28
|
+
specify 'can be individually declared, providing a default value or init block using .attribute' do
|
29
|
+
eq! 1, klass.attribute(:a, 1 ).new.a
|
30
|
+
eq! 2, klass.attribute(:b) { 2 }.new.b
|
31
|
+
end
|
32
|
+
|
33
|
+
specify 'the block value is not cached' do
|
34
|
+
klass.attribute(:a, "a" ).new.a << "-modified"
|
35
|
+
eq! "a-modified", klass.new.a
|
36
|
+
|
37
|
+
klass.attribute(:b) { "b" }.new.b << "-modified"
|
38
|
+
eq! "b", klass.new.b
|
39
|
+
end
|
40
|
+
|
41
|
+
specify 'can be group declared with a default value using .attributes(hash)' do
|
42
|
+
klass.attributes a: 1, b: 2
|
43
|
+
eq! 1, klass.new.a
|
44
|
+
eq! 2, klass.new.b
|
45
|
+
end
|
46
|
+
|
47
|
+
specify 'can omit a default if they are initialized with one' do
|
48
|
+
klass.attribute :a
|
49
|
+
eq! 1, klass.new(a: 1).a
|
50
|
+
raises!(ArgumentError, /:a/) { klass.new }
|
51
|
+
end
|
52
|
+
|
53
|
+
specify 'can group declare uninitialized attributes with .attributes(*names)' do
|
54
|
+
klass.attributes(:a, :b)
|
55
|
+
eq! 1, klass.new(a: 1, b: 2).a
|
56
|
+
raises!(ArgumentError, /:b/) { klass.new a: 1 }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'predicates are attributes which' do
|
61
|
+
specify 'can be individually declared with .predicate' do
|
62
|
+
eq! 1, klass.predicate(:a, 1 ).new.a
|
63
|
+
eq! 2, klass.predicate(:b) { 2 }.new.b
|
64
|
+
end
|
65
|
+
specify 'can be group declared with .predicates' do
|
66
|
+
klass.predicates a: 1, b: 2
|
67
|
+
eq! 1, klass.new.a
|
68
|
+
eq! 2, klass.new.b
|
69
|
+
end
|
70
|
+
specify 'can omit a default if they are initialized with one' do
|
71
|
+
klass.predicate :a
|
72
|
+
eq! true, klass.new(a: 1).a?
|
73
|
+
raises!(ArgumentError, /:a/) { klass.new }
|
74
|
+
end
|
75
|
+
specify 'can group declare uninitialized attributes with .attributes(*names)' do
|
76
|
+
klass.predicates(:a, :b)
|
77
|
+
eq! true, klass.new(a: 1, b: 2).a?
|
78
|
+
raises!(ArgumentError, /:b/) { klass.new a: 1 }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'conflicts' do
|
83
|
+
it 'raises if you double-declare an attribute' do
|
84
|
+
klass.attribute :a, 1
|
85
|
+
raises!(ArgumentError) { klass.attribute :a, 2 }
|
86
|
+
raises!(ArgumentError) { klass.predicate :a, 3 }
|
87
|
+
raises!(ArgumentError) { klass.attributes a: 4 }
|
88
|
+
raises!(ArgumentError) { klass.predicates a: 5 }
|
89
|
+
eq! 1, klass.new.a
|
90
|
+
|
91
|
+
klass.predicate :b, 1
|
92
|
+
raises!(ArgumentError) { klass.attribute :b, 2 }
|
93
|
+
raises!(ArgumentError) { klass.predicate :b, 3 }
|
94
|
+
raises!(ArgumentError) { klass.attributes b: 4 }
|
95
|
+
raises!(ArgumentError) { klass.predicates b: 5 }
|
96
|
+
eq! 1, klass.new.b
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '.attribute / .attributes / .predicate / .predicates' do
|
101
|
+
specify 'raise if a key is not a symbol (you shouldn\'t be dynamically creating this class with strings)' do
|
102
|
+
raises!(ArgumentError) { klass.attribute 'a', 1 }
|
103
|
+
raises!(ArgumentError) { klass.predicate 'b', 1 }
|
104
|
+
raises!(ArgumentError) { klass.attributes 'c' => 1 }
|
105
|
+
raises!(ArgumentError) { klass.predicates 'd' => 1 }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'anonymous subclasses try to be generally terse and useful to be valid replacements over Struct' do
|
111
|
+
specify '.anon / .for / .for? return a subclass of HashStruct' do
|
112
|
+
klass = described_class.anon
|
113
|
+
neq! described_class, klass
|
114
|
+
eq! described_class, klass.superclass
|
115
|
+
|
116
|
+
klass = described_class.for
|
117
|
+
neq! described_class, klass
|
118
|
+
eq! described_class, klass.superclass
|
119
|
+
|
120
|
+
klass = described_class.for?
|
121
|
+
neq! described_class, klass
|
122
|
+
eq! described_class, klass.superclass
|
123
|
+
end
|
124
|
+
|
125
|
+
specify '.anon / .for / .for? take blocks which get class_eval\'d' do
|
126
|
+
klass = described_class.anon { def a; 1 end }
|
127
|
+
eq! 1, klass.new.a
|
128
|
+
|
129
|
+
klass = described_class.for { def b; 2 end }
|
130
|
+
eq! 2, klass.new.b
|
131
|
+
|
132
|
+
klass = described_class.for? { def c; 3 end }
|
133
|
+
eq! 3, klass.new.c
|
134
|
+
end
|
135
|
+
|
136
|
+
specify '.for? passes its args to .predicates' do
|
137
|
+
klass = described_class.for?(:a, b: 3)
|
138
|
+
eq! 1, klass.new(a: 1).a
|
139
|
+
eq! 3, klass.new(a: 1).b
|
140
|
+
eq! true, klass.new(a: 1).b?
|
141
|
+
eq! false, klass.new(a: 1, b: nil).b?
|
142
|
+
end
|
143
|
+
|
144
|
+
specify '.for passes its args to .attributes' do
|
145
|
+
klass = described_class.for(:a, b: 3)
|
146
|
+
eq! 1, klass.new(a: 1).a
|
147
|
+
eq! 3, klass.new(a: 1).b
|
148
|
+
raises!(NoMethodError) { klass.new(a: 1).b? }
|
149
|
+
end
|
150
|
+
|
151
|
+
specify 'subclasses retain their parents attributes without them mixing' do
|
152
|
+
parent = klass.for(a: 1)
|
153
|
+
child = parent.for(b: 2)
|
154
|
+
eq! 1, child.new.a
|
155
|
+
eq! 2, child.new.b
|
156
|
+
eq! 1, parent.new.a
|
157
|
+
raises!(NoMethodError) { parent.new.b }
|
158
|
+
end
|
159
|
+
|
160
|
+
specify 'subclasses can override their parents attributes' do
|
161
|
+
c1 = klass.for(a: 1, b: 1, c: 1)
|
162
|
+
c2 = c1.for(a: 2, b: 2)
|
163
|
+
c3 = c2.for(a: 3)
|
164
|
+
eq! 3, c3.new.a
|
165
|
+
eq! 2, c3.new.b
|
166
|
+
eq! 1, c3.new.c
|
167
|
+
|
168
|
+
eq! 2, c2.new.a
|
169
|
+
eq! 2, c2.new.b
|
170
|
+
eq! 1, c2.new.c
|
171
|
+
|
172
|
+
eq! 1, c1.new.a
|
173
|
+
eq! 1, c1.new.b
|
174
|
+
eq! 1, c1.new.c
|
175
|
+
|
176
|
+
raises!(ArgumentError) { c2.attribute :a } # still can't redefine their own
|
177
|
+
end
|
178
|
+
|
179
|
+
specify '.inspect returns HashStruct.anon when it does not have a name' do
|
180
|
+
expect(klass.anon.inspect).to eq 'HashStruct.anon'
|
181
|
+
|
182
|
+
named_class = klass.anon
|
183
|
+
allow(named_class).to receive(:name).and_return("SomeClass")
|
184
|
+
expect(named_class.inspect).to eq 'SomeClass'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
describe 'use' do
|
190
|
+
describe 'initialization' do
|
191
|
+
it 'sets all values to their defaults, calling the init blocks at that time, with the instance' do
|
192
|
+
calls = []
|
193
|
+
klass.attribute(:a) { calls << :a; 1 }.attribute(:b, 2).attributes(c: 3)
|
194
|
+
.predicate(:d) { calls << :d; 4 }.predicate(:e, 5).predicates(f: 6)
|
195
|
+
.attribute(:g) { |i| i.b + i.c }
|
196
|
+
.predicate(:h) { |i| i.e + i.f }
|
197
|
+
eq! [], calls
|
198
|
+
instance = klass.new
|
199
|
+
eq! [:a, :d], calls
|
200
|
+
eq! 1, instance.a
|
201
|
+
eq! 2, instance.b
|
202
|
+
eq! 3, instance.c
|
203
|
+
eq! 4, instance.d
|
204
|
+
eq! 5, instance.e
|
205
|
+
eq! 6, instance.f
|
206
|
+
eq! 5, instance.g
|
207
|
+
eq! 11, instance.h
|
208
|
+
eq! [:a, :d], calls
|
209
|
+
end
|
210
|
+
it 'accepts a hash of any declard attribute overrides' do
|
211
|
+
instance = klass.attributes(a: 1, b: 2).new(a: 3)
|
212
|
+
eq! 3, instance.a
|
213
|
+
eq! 2, instance.b
|
214
|
+
end
|
215
|
+
it 'accepts string and symbol keys' do
|
216
|
+
instance = klass.attributes(a: 1, b: 2).new(a: 3, 'b' => 4)
|
217
|
+
eq! 3, instance.a
|
218
|
+
eq! 4, instance.b
|
219
|
+
end
|
220
|
+
it 'raises if initialized with attributes it doesn\'t know' do
|
221
|
+
klass.attribute :a, 1
|
222
|
+
raises!(KeyError) { klass.new b: 2 }
|
223
|
+
end
|
224
|
+
it 'raises an ArgumentError if all its values aren\'t initialized between defaults and init hash' do
|
225
|
+
klass.attribute :a, 1
|
226
|
+
klass.attribute :b
|
227
|
+
klass.new(b: 1)
|
228
|
+
raises!(ArgumentError) { klass.new }
|
229
|
+
end
|
230
|
+
it 'won\'t raise until after a provided block is invoked' do
|
231
|
+
klass.attributes(:a, :b)
|
232
|
+
eq! 1, klass.new(b: 1) { |i| i.a = 1 }.a
|
233
|
+
eq! nil, klass.new(b: 1) { |i| i.a = nil }.a
|
234
|
+
raises!(ArgumentError) { klass.new {} }
|
235
|
+
klass.new(b: 2) { |instance|
|
236
|
+
raises!(ArgumentError) { instance.a }
|
237
|
+
raises!(ArgumentError) { instance[:a] }
|
238
|
+
instance.a = 1
|
239
|
+
eq! 1, instance.a
|
240
|
+
eq! 1, instance[:a]
|
241
|
+
eq! 2, instance.b
|
242
|
+
}
|
243
|
+
end
|
244
|
+
it 'gives you a helpful message when you pass it a non-enumerable argument (ie when used to normal Struct)' do
|
245
|
+
klass.attributes(a: 1)
|
246
|
+
expect { klass.new 1 }.to raise_error ArgumentError, /\b1\b/
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe '#[] / #[]=' do
|
251
|
+
specify 'get/set an attribute using string or symbol' do
|
252
|
+
instance = klass.attribute(:a, 1).new
|
253
|
+
eq! 1, instance[:a]
|
254
|
+
eq! 1, instance['a']
|
255
|
+
instance[:a] = 2
|
256
|
+
eq! 2, instance[:a]
|
257
|
+
eq! 2, instance['a']
|
258
|
+
instance['a'] = 3
|
259
|
+
eq! 3, instance[:a]
|
260
|
+
eq! 3, instance['a']
|
261
|
+
end
|
262
|
+
specify 'raise if given a key that is not an attribute' do
|
263
|
+
instance = klass.attribute(:a, 1).new
|
264
|
+
instance[:a]
|
265
|
+
raises!(KeyError) { instance[:b] }
|
266
|
+
|
267
|
+
instance[:a] = 2
|
268
|
+
raises!(KeyError) { instance[:b] = 2 }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe '#fetch' do
|
273
|
+
let(:instance) { klass.attributes(a: :value).new }
|
274
|
+
it 'returns the key if it exists' do
|
275
|
+
eq! :value, instance.fetch(:a)
|
276
|
+
end
|
277
|
+
it 'accepts a second argument, which it just ignores' do
|
278
|
+
eq! :value, instance.fetch(:a, :default)
|
279
|
+
end
|
280
|
+
it 'raises a KeyError if the key doesn\'t exist, regardless of the second argument or a default block -- point of this is that you know what\'s in the hashes' do
|
281
|
+
raises!(KeyError) { instance.fetch(:b, :default) }
|
282
|
+
raises!(KeyError) { instance.fetch(:b) { :default } }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
describe 'setter, getter, predicate' do
|
287
|
+
specify '#<attr> gets the attribute' do
|
288
|
+
eq! 1, klass.attribute(:a, 1).new.a
|
289
|
+
end
|
290
|
+
specify '#<attr>= sets the attribute' do
|
291
|
+
instance = klass.attribute(:a, 1).new
|
292
|
+
eq! 1, instance.a
|
293
|
+
instance.a = 2
|
294
|
+
eq! 2, instance.a
|
295
|
+
end
|
296
|
+
specify '#<attr>? is an additional predicate getter' do
|
297
|
+
klass.attribute(:a, 1).attributes(b: 2)
|
298
|
+
.predicate(:c, 3).predicates(d: 4)
|
299
|
+
instance = klass.new
|
300
|
+
raises!(NoMethodError) { instance.a? }
|
301
|
+
raises!(NoMethodError) { instance.b? }
|
302
|
+
instance.c?
|
303
|
+
instance.d?
|
304
|
+
end
|
305
|
+
specify '#<attr>? returns true or false based on what the value would do in a conditional' do
|
306
|
+
instance = klass.predicates(nil: nil, false: false, true: true, object: Object.new).new
|
307
|
+
eq! false, instance.nil?
|
308
|
+
eq! false, instance.false?
|
309
|
+
eq! true, instance.true?
|
310
|
+
eq! true, instance.object?
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# include a fancy inspect with optional color?, optional width? tabular format?
|
315
|
+
describe 'inspection' do
|
316
|
+
class Example < described_class
|
317
|
+
attributes a: 1, b: "c"
|
318
|
+
end
|
319
|
+
it 'inspects prettily' do
|
320
|
+
eq! '#<HashStruct Example: {a: 1, b: "c"}>', Example.new.inspect
|
321
|
+
klass.attributes(c: /d/)
|
322
|
+
eq! '#<HashStruct.anon: {c: /d/}>', klass.new.inspect
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe 'pretty printed' do
|
327
|
+
require 'pp'
|
328
|
+
|
329
|
+
def pretty_inspect(attrs)
|
330
|
+
klass.for(attrs.keys.map &:intern)
|
331
|
+
.new(attrs)
|
332
|
+
.pretty_inspect
|
333
|
+
.chomp
|
334
|
+
end
|
335
|
+
|
336
|
+
class EmptySubclass < SeeingIsBelieving::HashStruct
|
337
|
+
end
|
338
|
+
it 'begins with instantiation of the class or HashStruct.anon { ... }.new(' do
|
339
|
+
eq! "HashStruct.anon { ... }.new()", klass.new.pretty_inspect.chomp
|
340
|
+
eq! "EmptySubclass.new()", EmptySubclass.new.pretty_inspect.chomp
|
341
|
+
end
|
342
|
+
|
343
|
+
it "uses 1.9 hash syntax" do
|
344
|
+
include! "key:", pretty_inspect(key: :value)
|
345
|
+
ninclude! "=>", pretty_inspect(key: :value)
|
346
|
+
end
|
347
|
+
it "puts the key/value pairs inline when they are short" do
|
348
|
+
include! "(key: :value)", pretty_inspect(key: :value)
|
349
|
+
end
|
350
|
+
it "puts the key/value pairs on their own indented line, when they are long" do
|
351
|
+
include! "(\n #{"k"*30}: \"#{"v"*30}\"\n)",
|
352
|
+
pretty_inspect("k"*30 => "v"*30)
|
353
|
+
end
|
354
|
+
it "indents the key/value pairs" do
|
355
|
+
attrs = {"a"*30 => "b"*30,
|
356
|
+
"c"*30 => "d"*30}
|
357
|
+
include! "(\n"\
|
358
|
+
" #{"a"*30}: \"#{"b"*30}\",\n"\
|
359
|
+
" #{"c"*30}: \"#{"d"*30}\"\n"\
|
360
|
+
")",
|
361
|
+
pretty_inspect(attrs)
|
362
|
+
end
|
363
|
+
it "breaks the value onto an indented next line when long" do
|
364
|
+
include! " #{"a"*50}:\n \"#{"b"*50}\"",
|
365
|
+
pretty_inspect("a"*50 => "b"*50)
|
366
|
+
end
|
367
|
+
it "pretty prints the value" do
|
368
|
+
ary = [*1..25]
|
369
|
+
include! " k:\n [#{ary.map { |n| " #{n}"}.join(",\n").lstrip}]",
|
370
|
+
pretty_inspect(k: ary)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
describe '#to_hash / #to_h' do
|
375
|
+
it 'returns a dup\'d Ruby hash of the internal attributes' do
|
376
|
+
klass.attributes(a: 1, b: 2)
|
377
|
+
eq!({a: 1, b: 3}, klass.new(b: 3).to_hash)
|
378
|
+
eq!({a: 3, b: 2}, klass.new(a: 3).to_h)
|
379
|
+
|
380
|
+
instance = klass.new
|
381
|
+
instance.to_h[:a] = :injected
|
382
|
+
eq!({a: 1, b: 2}, instance.to_h)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'merge' do
|
387
|
+
before { klass.attributes(a: 1, b: 2, c: 3) }
|
388
|
+
|
389
|
+
it 'returns a new instance with the merged values overriding its own' do
|
390
|
+
merged = klass.new(b: -2).merge c: -3
|
391
|
+
eq! klass, merged.class
|
392
|
+
eq!({a: 1, b: -2, c: -3}, merged.to_h)
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'does not modify the LHS or RHS' do
|
396
|
+
instance = klass.new b: -2
|
397
|
+
merge_hash = {c: -3}
|
398
|
+
instance.merge merge_hash
|
399
|
+
eq!({a: 1, b: -2, c: 3}, instance.to_h)
|
400
|
+
eq!({c: -3}, merge_hash)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
describe 'enumerability' do
|
405
|
+
it 'is enumerable, iterating over each attribute(as symbol)/value pair' do
|
406
|
+
klass.attributes(a: 1, b: 2)
|
407
|
+
eq! [[:a, 1], [:b, 2]], klass.new.to_a
|
408
|
+
eq! "a1b2", klass.new.each.with_object("") { |(k, v), s| s << "#{k}#{v}" }
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
describe 'keys/values' do
|
413
|
+
specify 'keys returns an array of symbols of all its attributes' do
|
414
|
+
eq! [:a, :b], klass.attributes(a: 1, b: 2).new(b: 3).keys
|
415
|
+
end
|
416
|
+
specify 'values returns an array of symbol values' do
|
417
|
+
eq! [1, 3], klass.attributes(a: 1, b: 2).new(b: 3).values
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
describe '#key? / #has_key? / #include? / #member?' do
|
422
|
+
specify 'return true iff the key (symbolic or string) is an attribute' do
|
423
|
+
instance = klass.attributes(a: 1, b: nil, c: false).new
|
424
|
+
[:key?, :has_key?, :include?, :member?].each do |predicate|
|
425
|
+
[:a, :b, :c, 'a', 'b', 'c'].each do |key|
|
426
|
+
eq! true, instance.__send__(predicate, key), "#{instance.inspect}.#{predicate}(#{key.inspect}) returned false"
|
427
|
+
end
|
428
|
+
eq! false, instance.__send__(predicate, :d)
|
429
|
+
eq! false, instance.__send__(predicate, 'd')
|
430
|
+
eq! false, instance.__send__(predicate, /b/)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
describe '#==' do
|
436
|
+
it 'is true if the RHS\'s to_h has the same key/value pairs' do
|
437
|
+
instance1 = described_class.for(a: 1, b: 2).new
|
438
|
+
instance2 = described_class.for(a: 1, b: 2).new
|
439
|
+
instance3 = described_class.for(a: 1, c: 2).new
|
440
|
+
eq! instance1, instance1
|
441
|
+
eq! instance1, instance2
|
442
|
+
eq! instance1, {a: 1, b: 2}
|
443
|
+
instance2.b = 3
|
444
|
+
neq! instance1, instance2
|
445
|
+
neq! instance1, instance3
|
446
|
+
neq! instance1, {a: 1}
|
447
|
+
neq! instance1, {a: 1, b: 2, c: 1}
|
448
|
+
eq! false, 1.respond_to?(:to_h)
|
449
|
+
eq! false, 1.respond_to?(:to_hash)
|
450
|
+
neq! instance1, 1
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
specify '#eql? is an alias of #==' do
|
455
|
+
instance1 = described_class.for(a: 1, b: 2).new
|
456
|
+
instance2 = described_class.for(a: 1, b: 2).new
|
457
|
+
expect(instance1).to eql instance2
|
458
|
+
end
|
459
|
+
|
460
|
+
specify '#hash is the same as Hash#hash' do
|
461
|
+
instance = described_class.for(a: 1, b: 2).new
|
462
|
+
eq! instance.hash, {a: 1, b: 2}.hash
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'can be used in set methods, e.g. as a hash key' do
|
466
|
+
instance1 = described_class.for(a: 1, b: 2).new
|
467
|
+
instance2 = described_class.for(a: 1, b: 2).new
|
468
|
+
eq! [], [instance1] - [instance2]
|
469
|
+
eq! [], [instance2] - [instance1]
|
470
|
+
eq! [], [instance1] - [{a: 1, b: 2}]
|
471
|
+
eq! [], [{a: 1, b: 2}] - [instance1]
|
472
|
+
eq! [instance1], [instance1, instance2].uniq
|
473
|
+
end
|
474
|
+
|
475
|
+
specify 'accepts nil as a value (common edge case)' do
|
476
|
+
klass.attributes default_is_nil: nil, default_is_1: 1
|
477
|
+
|
478
|
+
# default init block
|
479
|
+
instance = klass.new
|
480
|
+
eq! nil, instance.default_is_nil
|
481
|
+
eq! nil, instance[:default_is_nil]
|
482
|
+
|
483
|
+
# overridden on initialization
|
484
|
+
instance = klass.new default_is_1: nil
|
485
|
+
eq! nil, instance.default_is_1
|
486
|
+
eq! nil, instance[:default_is_1]
|
487
|
+
|
488
|
+
# set with setter
|
489
|
+
instance = klass.new
|
490
|
+
instance.default_is_1 = nil
|
491
|
+
eq! nil, instance.default_is_1
|
492
|
+
eq! nil, instance[:default_is_1]
|
493
|
+
|
494
|
+
# set with []= and symbol
|
495
|
+
instance = klass.new
|
496
|
+
instance[:default_is_1] = nil
|
497
|
+
eq! nil, instance.default_is_1
|
498
|
+
eq! nil, instance[:default_is_1]
|
499
|
+
|
500
|
+
# set with []= and string
|
501
|
+
instance = klass.new
|
502
|
+
instance['default_is_1'] = nil
|
503
|
+
eq! nil, instance.default_is_1
|
504
|
+
eq! nil, instance[:default_is_1]
|
505
|
+
|
506
|
+
# set after its been set to nil
|
507
|
+
instance = klass.new
|
508
|
+
instance[:default_is_nil] = nil
|
509
|
+
instance[:default_is_nil] = nil
|
510
|
+
instance.default_is_nil = nil
|
511
|
+
instance.default_is_nil = nil
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|