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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -8
  3. data/Rakefile +1 -1
  4. data/Readme.md +65 -25
  5. data/bin/seeing_is_believing +1 -0
  6. data/docs/sib-streaming.gif +0 -0
  7. data/features/deprecated-flags.feature +62 -2
  8. data/features/errors.feature +12 -7
  9. data/features/examples.feature +143 -4
  10. data/features/flags.feature +89 -29
  11. data/features/regression.feature +58 -14
  12. data/features/support/env.rb +4 -0
  13. data/features/xmpfilter-style.feature +181 -36
  14. data/lib/seeing_is_believing.rb +44 -33
  15. data/lib/seeing_is_believing/binary.rb +31 -88
  16. data/lib/seeing_is_believing/binary/align_chunk.rb +30 -11
  17. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +10 -16
  18. data/lib/seeing_is_believing/binary/annotate_every_line.rb +5 -25
  19. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +136 -0
  20. data/lib/seeing_is_believing/binary/comment_lines.rb +8 -10
  21. data/lib/seeing_is_believing/binary/commentable_lines.rb +20 -26
  22. data/lib/seeing_is_believing/binary/config.rb +392 -0
  23. data/lib/seeing_is_believing/binary/data_structures.rb +57 -0
  24. data/lib/seeing_is_believing/binary/engine.rb +104 -0
  25. data/lib/seeing_is_believing/binary/{comment_formatter.rb → format_comment.rb} +6 -6
  26. data/lib/seeing_is_believing/binary/remove_annotations.rb +29 -28
  27. data/lib/seeing_is_believing/binary/rewrite_comments.rb +42 -43
  28. data/lib/seeing_is_believing/code.rb +105 -49
  29. data/lib/seeing_is_believing/debugger.rb +6 -5
  30. data/lib/seeing_is_believing/error.rb +6 -17
  31. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +78 -129
  32. data/lib/seeing_is_believing/event_stream/consumer.rb +114 -64
  33. data/lib/seeing_is_believing/event_stream/events.rb +169 -11
  34. data/lib/seeing_is_believing/event_stream/handlers/debug.rb +57 -0
  35. data/lib/seeing_is_believing/event_stream/handlers/record_exitstatus.rb +18 -0
  36. data/lib/seeing_is_believing/event_stream/handlers/stream_json_events.rb +45 -0
  37. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +39 -0
  38. data/lib/seeing_is_believing/event_stream/producer.rb +25 -24
  39. data/lib/seeing_is_believing/hash_struct.rb +206 -0
  40. data/lib/seeing_is_believing/result.rb +20 -3
  41. data/lib/seeing_is_believing/the_matrix.rb +20 -12
  42. data/lib/seeing_is_believing/version.rb +1 -1
  43. data/lib/seeing_is_believing/wrap_expressions.rb +55 -115
  44. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +14 -0
  45. data/seeing_is_believing.gemspec +1 -1
  46. data/spec/binary/alignment_specs.rb +27 -0
  47. data/spec/binary/comment_lines_spec.rb +3 -2
  48. data/spec/binary/config_spec.rb +657 -0
  49. data/spec/binary/engine_spec.rb +97 -0
  50. data/spec/binary/{comment_formatter_spec.rb → format_comment_spec.rb} +2 -2
  51. data/spec/binary/marker_spec.rb +71 -0
  52. data/spec/binary/options_spec.rb +0 -0
  53. data/spec/binary/remove_annotations_spec.rb +31 -18
  54. data/spec/binary/rewrite_comments_spec.rb +26 -11
  55. data/spec/code_spec.rb +190 -6
  56. data/spec/debugger_spec.rb +4 -0
  57. data/spec/evaluate_by_moving_files_spec.rb +38 -20
  58. data/spec/event_stream_spec.rb +265 -116
  59. data/spec/hash_struct_spec.rb +514 -0
  60. data/spec/seeing_is_believing_spec.rb +108 -46
  61. data/spec/spec_helper.rb +9 -0
  62. data/spec/wrap_expressions_spec.rb +207 -172
  63. metadata +30 -18
  64. data/docs/for-presentations +0 -33
  65. data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +0 -128
  66. data/lib/seeing_is_believing/binary/interpret_flags.rb +0 -156
  67. data/lib/seeing_is_believing/binary/parse_args.rb +0 -263
  68. data/lib/seeing_is_believing/event_stream/update_result.rb +0 -24
  69. data/lib/seeing_is_believing/inspect_expressions.rb +0 -21
  70. data/lib/seeing_is_believing/parser_helpers.rb +0 -82
  71. data/spec/binary/interpret_flags_spec.rb +0 -332
  72. 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