calificador 0.1.0 → 0.2.0

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 (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