calificador 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -16
  3. data/TODO.md +16 -0
  4. data/calificador.gemspec +54 -0
  5. data/lib/calificador.rb +8 -4
  6. data/lib/calificador/assert.rb +15 -0
  7. data/lib/calificador/assertor.rb +79 -35
  8. data/lib/calificador/build/attribute_evaluator.rb +34 -30
  9. data/lib/calificador/build/basic_factory.rb +195 -0
  10. data/lib/calificador/build/mock_factory.rb +151 -0
  11. data/lib/calificador/build/object_factory.rb +85 -0
  12. data/lib/calificador/build/trait.rb +0 -20
  13. data/lib/calificador/context/basic_context.rb +406 -0
  14. data/lib/calificador/context/class_method_context.rb +0 -0
  15. data/lib/calificador/{spec → context}/condition_context.rb +1 -3
  16. data/lib/calificador/{spec/type_context.rb → context/instance_context.rb} +5 -10
  17. data/lib/calificador/context/operation_context.rb +27 -0
  18. data/lib/calificador/context/override/argument_override.rb +73 -0
  19. data/lib/calificador/context/override/basic_override.rb +14 -0
  20. data/lib/calificador/context/override/factory_override.rb +31 -0
  21. data/lib/calificador/context/override/property_override.rb +61 -0
  22. data/lib/calificador/context/test_environment.rb +283 -0
  23. data/lib/calificador/{spec → context}/test_method.rb +2 -31
  24. data/lib/calificador/{spec → context}/test_root.rb +3 -15
  25. data/lib/calificador/{spec/examine_context.rb → context/type_context.rb} +7 -10
  26. data/lib/calificador/key.rb +27 -15
  27. data/lib/calificador/minitest/minitest_patches.rb +0 -2
  28. data/lib/calificador/test.rb +1 -3
  29. data/lib/calificador/test_mixin.rb +143 -139
  30. data/lib/calificador/util/call_formatter.rb +5 -5
  31. data/lib/calificador/util/core_extensions.rb +104 -79
  32. data/lib/calificador/util/proxy_object.rb +63 -0
  33. data/lib/calificador/version.rb +1 -1
  34. metadata +22 -42
  35. data/lib/calificador/build/attribute_container.rb +0 -103
  36. data/lib/calificador/build/factory.rb +0 -132
  37. data/lib/calificador/spec/basic_context.rb +0 -353
  38. data/lib/calificador/spec/class_method_context.rb +0 -42
  39. data/lib/calificador/spec/instance_method_context.rb +0 -38
  40. data/lib/calificador/spec/test_environment.rb +0 -141
  41. data/lib/calificador/spec/value_override.rb +0 -37
@@ -0,0 +1,406 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "ostruct"
5
+
6
+ module Calificador
7
+ module Context
8
+ class BasicContext
9
+ extend Forwardable
10
+
11
+ class << self
12
+ def extract_arguments(subject_key:, values:, names:)
13
+ arguments = OpenStruct.new
14
+ arguments.overrides = [] if names.include?(:overrides)
15
+ arguments.subject_key = subject_key
16
+
17
+ values.each_with_index do |value, value_index|
18
+ name_index = names.index do |name|
19
+ case name
20
+ when :type
21
+ value.is_a?(Module)
22
+ when :trait
23
+ value.is_a?(Symbol)
24
+ when :name
25
+ value.is_a?(Symbol)
26
+ when :description
27
+ value.is_a?(String)
28
+ when :init
29
+ value.is_a?(Proc)
30
+ when :overrides
31
+ value.is_a?(Override::BasicOverride)
32
+ else
33
+ raise ArgumentError, "Unknown option '#{name}'"
34
+ end
35
+ end
36
+
37
+ unless name_index
38
+ raise ArgumentError, "Illegal argument at position #{value_index} for (#{values.join(", ")})"
39
+ end
40
+
41
+ name = names[name_index]
42
+
43
+ case name
44
+ when :type
45
+ arguments.subject_key = Key[value, arguments.subject_key.trait]
46
+ when :trait
47
+ arguments.subject_key = Key[arguments.subject_key.type, value]
48
+ when :overrides
49
+ arguments.overrides << value
50
+ when :init
51
+ arguments.overrides << Override::FactoryOverride.new(key: arguments.subject_key, function: value)
52
+ else
53
+ arguments[name] = value
54
+ end
55
+
56
+ if name == :overrides
57
+ names.shift(name_index)
58
+ else
59
+ names.shift(name_index + 1)
60
+ end
61
+ end
62
+
63
+ arguments
64
+ end
65
+ end
66
+
67
+ attr_reader :description, :parent, :call_context, :operation_arguments
68
+
69
+ def initialize(parent:, subject_key:, description:, overrides: [])
70
+ raise ArgumentError, "Parent must be a #{BasicContext}" unless parent.nil? || parent.is_a?(BasicContext)
71
+ raise ArgumentError, "Subject key must be a #{Key}" unless subject_key.is_a?(Key)
72
+
73
+ @parent = parent
74
+ @subject_key = subject_key
75
+ @description = description
76
+
77
+ @children = []
78
+ @factories = {}
79
+ @named_factories = {}
80
+
81
+ @operation_name = nil
82
+ @operation_arguments = parent&.operation_arguments.dup || {}
83
+
84
+ overrides.map do |override|
85
+ check_override(value: override)
86
+ end.map do |override|
87
+ override.apply(context: self)
88
+ end
89
+ end
90
+
91
+ def setup; end
92
+
93
+ def subtree_root?
94
+ false
95
+ end
96
+
97
+ def add_context(context, &block)
98
+ @children << context
99
+
100
+ context.setup
101
+ end
102
+
103
+ def test_class
104
+ @test_class ||= (@parent&.test_class || raise(StandardError, "No parent context defines a test class"))
105
+ end
106
+
107
+ def subject_key
108
+ @subject_key ||= begin
109
+ @parent&.subject_key || raise(StandardError, "No parent context defines a subject class")
110
+ end
111
+ end
112
+
113
+ def context_path(subtree: true)
114
+ add_context_to_path([], subtree: subtree).freeze
115
+ end
116
+
117
+ def full_description
118
+ context_path.reduce(StringIO.new) do |description, context|
119
+ description << " " if description.length.positive? && context.separate_description_by_space?
120
+ description << context.description
121
+ end.string
122
+ end
123
+
124
+ def root
125
+ @parent&.root || self
126
+ end
127
+
128
+ def operation_name
129
+ @operation_name ||= Util::Nil[@parent&.operation_name]
130
+ @operation_name.unmask_nil
131
+ end
132
+
133
+ def add_factory(factory)
134
+ raise KeyError, "Factory for type #{factory.key.type} already defined" if @factories.key?(factory.key.type)
135
+ raise KeyError, "Factory with name #{factory.name} already defined" if @named_factories.key?(factory.name)
136
+
137
+ @factories[factory.key] = factory
138
+ @named_factories[factory.name] = factory
139
+ end
140
+
141
+ def factories
142
+ @factories.dup.freeze
143
+ end
144
+
145
+ def named_factories
146
+ @named_factories.dup.freeze
147
+ end
148
+
149
+ def lookup_factory(key:, inherited: true)
150
+ @factories[key] || (@parent&.lookup_factory(key: key) if inherited)
151
+ end
152
+
153
+ def lookup_named_factory(name:)
154
+ @named_factories[name] || @parent&.lookup_named_factory(name: name)
155
+ end
156
+
157
+ def override_factory(key:)
158
+ lookup_factory(key: key, inherited: false) || begin
159
+ parent_factory = @parent&.lookup_factory(key: key)
160
+
161
+ factory = Build::ObjectFactory.new(
162
+ parent: parent_factory,
163
+ context: self,
164
+ key: key,
165
+ name: parent_factory&.name || test_class.__default_factory_name(subject_key: key),
166
+ source_location: Kernel.caller_locations.first { |l| !l.first.start_with(Calificador::BASE_DIR.to_s) }
167
+ )
168
+
169
+ add_factory(factory)
170
+ factory
171
+ end
172
+ end
173
+
174
+ def arguments(&block)
175
+ Override::ArgumentOverride.new.config(&block)
176
+ end
177
+
178
+ def_delegator :self, :arguments, :args
179
+
180
+ def properties(type = nil, trait = Key::DEFAULT_TRAIT, &block)
181
+ key = Key[type || subject_key.type, trait]
182
+
183
+ Override::PropertyOverride.new(key: key).config(&block)
184
+ end
185
+
186
+ def_delegator :self, :properties, :props
187
+
188
+ def factory(type, *description_or_name, &block)
189
+ arguments = BasicContext.extract_arguments(
190
+ subject_key: Key[type],
191
+ values: description_or_name,
192
+ names: %i[description name]
193
+ )
194
+
195
+ arguments.name ||= test_class.__default_factory_name(subject_key: arguments.subject_key)
196
+ arguments.description ||= test_class.__default_instance_description(subject_key: arguments.subject_key)
197
+
198
+ factory = Build::ObjectFactory.new(
199
+ context: self,
200
+ key: arguments.subject_key,
201
+ name: arguments.name,
202
+ description: arguments.description,
203
+ source_location: block&.source_location
204
+ )
205
+
206
+ factory.dsl.instance_exec(&block) unless block.nil?
207
+
208
+ add_factory(factory)
209
+ end
210
+
211
+ def mock(type, *description_or_name, &block)
212
+ arguments = BasicContext.extract_arguments(
213
+ subject_key: Key[type],
214
+ values: description_or_name,
215
+ names: %i[description name]
216
+ )
217
+
218
+ arguments.name ||= test_class.__default_factory_name(subject_key: arguments.subject_key)
219
+ arguments.description ||= test_class.__default_instance_description(subject_key: arguments.subject_key)
220
+
221
+ mock = Build::MockFactory.new(
222
+ context: self,
223
+ key: arguments.subject_key,
224
+ name: arguments.name,
225
+ description: arguments.description,
226
+ source_location: block&.source_location
227
+ )
228
+
229
+ mock.dsl.instance_exec(&block) unless block.nil?
230
+
231
+ add_factory(mock)
232
+ end
233
+
234
+ def type(*type_or_description_or_overrides, &block)
235
+ arguments = BasicContext.extract_arguments(
236
+ subject_key: subject_key,
237
+ values: type_or_description_or_overrides,
238
+ names: %i[type description overrides]
239
+ )
240
+
241
+ arguments.description ||= test_class.__default_type_description(subject_key: arguments.subject_key)
242
+
243
+ context = Context::TypeContext.new(
244
+ parent: self,
245
+ subject_key: arguments.subject_key,
246
+ description: arguments.description,
247
+ overrides: arguments.overrides
248
+ )
249
+
250
+ context.configure(block: block)
251
+
252
+ add_context(context, &block)
253
+ end
254
+
255
+ def examine(*type_or_trait_or_description_or_init_or_overrides, &block)
256
+ arguments = BasicContext.extract_arguments(
257
+ subject_key: subject_key,
258
+ values: type_or_trait_or_description_or_init_or_overrides,
259
+ names: %i[type trait description init overrides]
260
+ )
261
+
262
+ arguments.description ||= arguments.subject_key.to_s(base_module: test_class)
263
+
264
+ context = Context::InstanceContext.new(
265
+ parent: self,
266
+ subject_key: arguments.subject_key,
267
+ description: arguments.description,
268
+ overrides: arguments.overrides
269
+ )
270
+
271
+ context.configure(block: block)
272
+
273
+ add_context(context, &block)
274
+ end
275
+
276
+ def operation(operation, *trait_or_description_or_init_or_overrides, &block)
277
+ arguments = BasicContext.extract_arguments(
278
+ subject_key: subject_key,
279
+ values: trait_or_description_or_init_or_overrides,
280
+ names: %i[trait description init overrides]
281
+ )
282
+
283
+ arguments.description ||= "\##{operation}"
284
+
285
+ context = Context::OperationContext.new(
286
+ parent: self,
287
+ subject_key: arguments.subject_key,
288
+ name: operation,
289
+ description: arguments.description,
290
+ overrides: arguments.overrides
291
+ )
292
+
293
+ context.configure(block: block)
294
+
295
+ add_context(context, &block)
296
+ end
297
+
298
+ def must(description, *trait_or_init_or_overrides, &block)
299
+ arguments = BasicContext.extract_arguments(
300
+ subject_key: subject_key,
301
+ values: trait_or_init_or_overrides,
302
+ names: %i[trait init overrides]
303
+ )
304
+
305
+ context = Context::TestMethod.new(
306
+ parent: self,
307
+ subject_key: arguments.subject_key,
308
+ description: "must #{description}",
309
+ overrides: arguments.overrides,
310
+ expected_to_fail: false,
311
+ body: block
312
+ )
313
+
314
+ add_context(context)
315
+ end
316
+
317
+ def must_fail(description, *trait_or_init_or_overrides, &block)
318
+ arguments = BasicContext.extract_arguments(
319
+ subject_key: subject_key,
320
+ values: trait_or_init_or_overrides,
321
+ names: %i[trait init overrides]
322
+ )
323
+
324
+ context = Context::TestMethod.new(
325
+ parent: self,
326
+ subject_key: arguments.subject_key,
327
+ description: "must fail #{description}",
328
+ overrides: arguments.overrides,
329
+ expected_to_fail: true,
330
+ body: block
331
+ )
332
+
333
+ add_context(context)
334
+ end
335
+
336
+ def with(*description_or_trait_or_init_or_overrides, &block)
337
+ condition("with", *description_or_trait_or_init_or_overrides, &block)
338
+ end
339
+
340
+ def without(*description_or_trait_or_init_or_overrides, &block)
341
+ condition("without", *description_or_trait_or_init_or_overrides, &block)
342
+ end
343
+
344
+ def where(*description_or_trait_or_init_or_overrides, &block)
345
+ condition("where", *description_or_trait_or_init_or_overrides, &block)
346
+ end
347
+
348
+ def create_subject(environment:)
349
+ raise "No context defines a text subject" unless parent
350
+
351
+ parent.create_subject(environment: environment)
352
+ end
353
+
354
+ def merge_operation_arguments(arguments)
355
+ @operation_arguments.merge!(arguments)
356
+ end
357
+
358
+ protected
359
+
360
+ def configure(block:)
361
+ test_class.__calificador_configure(context: self, block: block)
362
+ end
363
+
364
+ def condition(conjunction, *description_or_trait_or_init_or_overrides, &block)
365
+ arguments = BasicContext.extract_arguments(
366
+ subject_key: subject_key,
367
+ values: description_or_trait_or_init_or_overrides,
368
+ names: %i[description trait init overrides]
369
+ )
370
+
371
+ arguments.description ||= begin
372
+ raise ArgumentError, "Please provide a description if you override values" unless arguments.overrides.empty?
373
+
374
+ arguments.subject_key.to_s
375
+ end
376
+
377
+ context = Context::ConditionContext.new(
378
+ parent: self,
379
+ subject_key: arguments.subject_key,
380
+ description: [conjunction, arguments.description].compact.join(" "),
381
+ overrides: arguments.overrides
382
+ )
383
+
384
+ context.configure(block: block)
385
+
386
+ add_context(context, &block)
387
+ end
388
+
389
+ def add_context_to_path(path, subtree: true)
390
+ @parent.add_context_to_path(path, subtree: subtree) unless @parent.nil? || (subtree && subtree_root?)
391
+
392
+ path << self
393
+ end
394
+
395
+ def separate_description_by_space?
396
+ true
397
+ end
398
+
399
+ def check_override(value:)
400
+ raise ArgumentError, "Illegal override type #{value.class}" unless value.is_a?(Override::BasicOverride)
401
+
402
+ value
403
+ end
404
+ end
405
+ end
406
+ end
File without changes
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Calificador
4
- module Spec
4
+ module Context
5
5
  class ConditionContext < BasicContext
6
- class Dsl < BasicContext::Dsl
7
- end
8
6
  end
9
7
  end
10
8
  end
@@ -1,14 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- using Calificador::Util::CoreExtensions
4
-
5
3
  module Calificador
6
- module Spec
7
- class ExamineContext < BasicContext
8
- class Dsl < BasicContext::Dsl
9
- end
10
-
11
- def initialize(parent:, subject_key:, description:, overrides: {})
4
+ module Context
5
+ class InstanceContext < BasicContext
6
+ def initialize(parent:, subject_key:, description:, overrides: [])
12
7
  super(
13
8
  parent: parent,
14
9
  description: description,
@@ -17,8 +12,8 @@ module Calificador
17
12
  )
18
13
  end
19
14
 
20
- def create_subject(environment:, subject_key:)
21
- subject_key.type
15
+ def create_subject(environment:)
16
+ environment.create_object(key: environment.subject_key)
22
17
  end
23
18
 
24
19
  def subtree_root?
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calificador
4
+ module Context
5
+ # Context that describes an instance method
6
+ class OperationContext < BasicContext
7
+ attr_reader :operation_name
8
+
9
+ def initialize(parent:, subject_key:, name:, description: nil, overrides: [])
10
+ raise ArgumentError, "Operation name must not be nil" if name.nil?
11
+
12
+ super(
13
+ parent: parent,
14
+ subject_key: subject_key,
15
+ description: description,
16
+ overrides: overrides
17
+ )
18
+
19
+ @operation_name = name
20
+ end
21
+
22
+ def separate_description_by_space?
23
+ @parent && !@parent.is_a?(InstanceContext) && !@parent.is_a?(TypeContext)
24
+ end
25
+ end
26
+ end
27
+ end