morpher 0.2.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of morpher might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.circle.yml +6 -0
  3. data/.gitignore +5 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +8 -0
  6. data/Changelog.md +60 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +20 -0
  9. data/README.md +56 -0
  10. data/Rakefile +95 -0
  11. data/circle.yml +7 -0
  12. data/config/devtools.yml +2 -0
  13. data/config/flay.yml +3 -0
  14. data/config/flog.yml +2 -0
  15. data/config/heckle.yml +3 -0
  16. data/config/mutant.yml +8 -0
  17. data/config/reek.yml +109 -0
  18. data/config/rubocop.yml +138 -0
  19. data/config/yardstick.yml +2 -0
  20. data/examples/README.md +13 -0
  21. data/examples/a.rb +25 -0
  22. data/examples/b.rb +35 -0
  23. data/lib/morpher.rb +111 -0
  24. data/lib/morpher/compiler.rb +17 -0
  25. data/lib/morpher/compiler/emitter.rb +82 -0
  26. data/lib/morpher/compiler/error.rb +84 -0
  27. data/lib/morpher/compiler/evaluator.rb +63 -0
  28. data/lib/morpher/compiler/evaluator/emitter.rb +224 -0
  29. data/lib/morpher/compiler/preprocessor.rb +29 -0
  30. data/lib/morpher/compiler/preprocessor/emitter.rb +54 -0
  31. data/lib/morpher/compiler/preprocessor/emitter/anima.rb +69 -0
  32. data/lib/morpher/compiler/preprocessor/emitter/boolean.rb +31 -0
  33. data/lib/morpher/compiler/preprocessor/emitter/key.rb +87 -0
  34. data/lib/morpher/compiler/preprocessor/emitter/noop.rb +45 -0
  35. data/lib/morpher/compiler/preprocessor/emitter/param.rb +50 -0
  36. data/lib/morpher/evaluation.rb +118 -0
  37. data/lib/morpher/evaluator.rb +40 -0
  38. data/lib/morpher/evaluator/binary.rb +46 -0
  39. data/lib/morpher/evaluator/nary.rb +97 -0
  40. data/lib/morpher/evaluator/nullary.rb +92 -0
  41. data/lib/morpher/evaluator/nullary/parameterized.rb +48 -0
  42. data/lib/morpher/evaluator/predicate.rb +22 -0
  43. data/lib/morpher/evaluator/predicate/boolean.rb +76 -0
  44. data/lib/morpher/evaluator/predicate/contradiction.rb +36 -0
  45. data/lib/morpher/evaluator/predicate/eql.rb +50 -0
  46. data/lib/morpher/evaluator/predicate/negation.rb +52 -0
  47. data/lib/morpher/evaluator/predicate/primitive.rb +49 -0
  48. data/lib/morpher/evaluator/predicate/tautology.rb +36 -0
  49. data/lib/morpher/evaluator/transformer.rb +75 -0
  50. data/lib/morpher/evaluator/transformer/attribute.rb +25 -0
  51. data/lib/morpher/evaluator/transformer/block.rb +81 -0
  52. data/lib/morpher/evaluator/transformer/coerce.rb +166 -0
  53. data/lib/morpher/evaluator/transformer/custom.rb +34 -0
  54. data/lib/morpher/evaluator/transformer/domain.rb +86 -0
  55. data/lib/morpher/evaluator/transformer/domain/attribute_accessors.rb +60 -0
  56. data/lib/morpher/evaluator/transformer/domain/attribute_hash.rb +52 -0
  57. data/lib/morpher/evaluator/transformer/domain/instance_variables.rb +60 -0
  58. data/lib/morpher/evaluator/transformer/domain/param.rb +54 -0
  59. data/lib/morpher/evaluator/transformer/guard.rb +62 -0
  60. data/lib/morpher/evaluator/transformer/hash_transform.rb +149 -0
  61. data/lib/morpher/evaluator/transformer/input.rb +37 -0
  62. data/lib/morpher/evaluator/transformer/key.rb +86 -0
  63. data/lib/morpher/evaluator/transformer/map.rb +100 -0
  64. data/lib/morpher/evaluator/transformer/merge.rb +25 -0
  65. data/lib/morpher/evaluator/transformer/static.rb +27 -0
  66. data/lib/morpher/evaluator/unary.rb +79 -0
  67. data/lib/morpher/node_helpers.rb +19 -0
  68. data/lib/morpher/printer.rb +233 -0
  69. data/lib/morpher/printer/mixin.rb +58 -0
  70. data/lib/morpher/registry.rb +51 -0
  71. data/lib/morpher/type_lookup.rb +51 -0
  72. data/morpher.gemspec +29 -0
  73. data/spec/integration_spec.rb +184 -0
  74. data/spec/rcov.opts +7 -0
  75. data/spec/shared/evaluator_behavior.rb +155 -0
  76. data/spec/spec_helper.rb +36 -0
  77. data/spec/support/ice_nine_config.rb +8 -0
  78. data/spec/support/let_mock_helper.rb +8 -0
  79. data/spec/support/strip_helper.rb +12 -0
  80. data/spec/unit/morpher/compiler/preprocessor_spec.rb +46 -0
  81. data/spec/unit/morpher/evaluator/nullary/parameterized_spec.rb +25 -0
  82. data/spec/unit/morpher/evaluator/predicate/boolean/and_spec.rb +11 -0
  83. data/spec/unit/morpher/evaluator/predicate/boolean/or_spec.rb +26 -0
  84. data/spec/unit/morpher/evaluator/predicate/boolean/xor_spec.rb +26 -0
  85. data/spec/unit/morpher/evaluator/predicate/contrandiction_spec.rb +7 -0
  86. data/spec/unit/morpher/evaluator/predicate/eql_spec.rb +11 -0
  87. data/spec/unit/morpher/evaluator/predicate/negation_spec.rb +10 -0
  88. data/spec/unit/morpher/evaluator/predicate/primitive_spec.rb +17 -0
  89. data/spec/unit/morpher/evaluator/predicate/tautology_spec.rb +7 -0
  90. data/spec/unit/morpher/evaluator/transformer/attribute_spec.rb +9 -0
  91. data/spec/unit/morpher/evaluator/transformer/block_spec.rb +92 -0
  92. data/spec/unit/morpher/evaluator/transformer/coerce/parse_int_spec.rb +23 -0
  93. data/spec/unit/morpher/evaluator/transformer/custom_spec.rb +13 -0
  94. data/spec/unit/morpher/evaluator/transformer/domain/attribute_accessors_spec.rb +48 -0
  95. data/spec/unit/morpher/evaluator/transformer/domain/attribute_hash_spec.rb +40 -0
  96. data/spec/unit/morpher/evaluator/transformer/domain/instance_variables_spec.rb +47 -0
  97. data/spec/unit/morpher/evaluator/transformer/guard_spec.rb +12 -0
  98. data/spec/unit/morpher/evaluator/transformer/hash_transform_spec.rb +47 -0
  99. data/spec/unit/morpher/evaluator/transformer/input_spec.rb +11 -0
  100. data/spec/unit/morpher/evaluator/transformer/map_spec.rb +25 -0
  101. data/spec/unit/morpher/evaluator/transformer/static_spec.rb +10 -0
  102. data/spec/unit/morpher/evaluator_spec.rb +15 -0
  103. data/spec/unit/morpher/printer_spec.rb +21 -0
  104. data/spec/unit/morpher/registry_spec.rb +11 -0
  105. data/spec/unit/morpher_spec.rb +53 -0
  106. metadata +302 -0
@@ -0,0 +1,58 @@
1
+ module Morpher
2
+ class Printer
3
+
4
+ # Printer behavior mixin
5
+ module Mixin
6
+
7
+ # Class level methods to be mixed in
8
+ module ClassMethods
9
+
10
+ # Register printer block for class
11
+ #
12
+ # @return [self]
13
+ #
14
+ # @api private
15
+ #
16
+ def printer(&block)
17
+ REGISTRY[self] = block
18
+ end
19
+
20
+ end # ClassMethods
21
+
22
+ # Instance level methods to be mixed in
23
+ module InstanceMethods
24
+
25
+ # Return description
26
+ #
27
+ # @return [String]
28
+ #
29
+ # @api private
30
+ #
31
+ def description
32
+ io = StringIO.new
33
+ Printer.run(self, io)
34
+ io.rewind
35
+ io.read
36
+ end
37
+
38
+ end # InstanceMethods
39
+
40
+ # Callback whem module gets included
41
+ #
42
+ # @param [Class, Module] descendant
43
+ #
44
+ # @return [undefined]
45
+ #
46
+ # @api private
47
+ #
48
+ def self.included(descendant)
49
+ descendant.class_eval do
50
+ extend ClassMethods
51
+ include InstanceMethods
52
+ end
53
+ end
54
+ private_class_method :included
55
+
56
+ end # Mixin
57
+ end # Printer
58
+ end # Morpher
@@ -0,0 +1,51 @@
1
+ module Morpher
2
+ # Mixin for to provide a node registry
3
+ module Registry
4
+
5
+ # Hook called when module is included
6
+ #
7
+ # @param [Module, Class] descendant
8
+ #
9
+ # @return [undefined]
10
+ #
11
+ # @api private
12
+ #
13
+ def self.included(descendant)
14
+ descendant.const_set(:REGISTRY, {})
15
+ descendant.class_eval do
16
+ extend ClassMethods
17
+ end
18
+ end
19
+
20
+ # Return node type
21
+ #
22
+ # @return [Symbol]
23
+ #
24
+ # @api private
25
+ #
26
+ def type
27
+ self.class::TYPE
28
+ end
29
+
30
+ # Methods to get mixed in at singleton level
31
+ module ClassMethods
32
+
33
+ # Register evaluator under name
34
+ #
35
+ # TODO: Disallow duplicate registration under same name
36
+ #
37
+ # @param [Symbol] name
38
+ #
39
+ # @return [undefined]
40
+ #
41
+ # @api private
42
+ #
43
+ def register(name)
44
+ const_set(:TYPE, name)
45
+ self::REGISTRY[name] = self
46
+ end
47
+
48
+ end # ClassMethods
49
+
50
+ end # Registry
51
+ end # Morpher
@@ -0,0 +1,51 @@
1
+ module Morpher
2
+
3
+ # Type lookup via registry and superclass chaining
4
+ #
5
+ # TODO: Cache results.
6
+ #
7
+ class TypeLookup
8
+ include Adamantium::Flat, Concord.new(:registry)
9
+
10
+ # Error raised on compiling unknown nodes
11
+ class TypeNotFoundError < RuntimeError
12
+ include Concord.new(:type)
13
+
14
+ # Return exception error message
15
+ #
16
+ # @return [String]
17
+ #
18
+ # @api private
19
+ #
20
+ def message
21
+ "Node type: #{type.inspect} is unknown"
22
+ end
23
+
24
+ end # TypeNotFoundError
25
+
26
+ # Perform type lookup
27
+ #
28
+ # @param [Object] object
29
+ #
30
+ # @return [Object]
31
+ # if found
32
+ #
33
+ # @raise [TypeNotFoundError]
34
+ # otherwise
35
+ #
36
+ # @api private
37
+ #
38
+ def call(object)
39
+ current = target = object.class
40
+ while current != Object
41
+ if registry.key?(current)
42
+ return registry.fetch(current)
43
+ end
44
+ current = current.superclass
45
+ end
46
+
47
+ fail TypeNotFoundError, target
48
+ end
49
+
50
+ end # TypeLookup
51
+ end # Morpher
data/morpher.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'morpher'
3
+ gem.version = '0.2.6'
4
+ gem.authors = ['Markus Schirp']
5
+ gem.email = ['mbj@schirp-dso.com']
6
+ gem.description = 'Composeable predicates on POROs'
7
+ gem.summary = gem.description
8
+ gem.homepage = 'https://github.com/mbj/morpher'
9
+ gem.license = 'MIT'
10
+
11
+ gem.require_paths = %w[lib]
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
14
+ gem.extra_rdoc_files = %w[LICENSE]
15
+ gem.license = 'MIT'
16
+
17
+ gem.required_ruby_version = ['>= 2.1']
18
+
19
+ gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
20
+ gem.add_runtime_dependency('adamantium', '~> 0.2.0')
21
+ gem.add_runtime_dependency('anima', '~> 0.3.0')
22
+ gem.add_runtime_dependency('ast', '~> 2.2')
23
+ gem.add_runtime_dependency('concord', '~> 0.1.5')
24
+ gem.add_runtime_dependency('equalizer', '~> 0.0.9')
25
+ gem.add_runtime_dependency('ice_nine', '~> 0.11.0')
26
+ gem.add_runtime_dependency('procto', '~> 0.0.2')
27
+
28
+ gem.add_development_dependency('devtools', '~> 0.1.3')
29
+ end
@@ -0,0 +1,184 @@
1
+ describe Morpher do
2
+
3
+ class Foo
4
+ include Anima.new(:attribute_a, :attribute_b)
5
+ end
6
+
7
+ class Bar
8
+ include Anima.new(:baz)
9
+ end
10
+
11
+ class Baz
12
+ include Anima.new(:value)
13
+ end
14
+
15
+ let(:tree_a) do
16
+ Foo.new(
17
+ attribute_a: Baz.new(value: :value_a),
18
+ attribute_b: :value_b
19
+ )
20
+ end
21
+
22
+ let(:transformer_ast) do
23
+ s(
24
+ :block,
25
+ s(:guard, s(:primitive, Hash)),
26
+ s(
27
+ :hash_transform,
28
+ s(
29
+ :key_symbolize,
30
+ :attribute_a,
31
+ s(:guard, s(:primitive, String))
32
+ ),
33
+ s(
34
+ :key_symbolize,
35
+ :attribute_b,
36
+ s(:guard, s(:primitive, Fixnum))
37
+ )
38
+ ),
39
+ s(:load_attribute_hash, s(:param, Foo))
40
+ )
41
+ end
42
+
43
+ let(:predicate_ast) do
44
+ s(
45
+ :block,
46
+ s(:key_fetch, :attribute_a),
47
+ s(:eql, s(:static, 'foo'), s(:input))
48
+ )
49
+ end
50
+
51
+ specify 'allows to execute a transformation' do
52
+ evaluator = Morpher.compile(transformer_ast)
53
+
54
+ valid = {
55
+ 'attribute_a' => 'a string',
56
+ 'attribute_b' => 8015
57
+ }
58
+
59
+ expect(evaluator.call(valid)).to eql(
60
+ Foo.new(attribute_a: 'a string', attribute_b: 8015)
61
+ )
62
+
63
+ evaluation = evaluator.evaluation(valid)
64
+
65
+ expect(evaluation.output).to eql(
66
+ Foo.new(attribute_a: 'a string', attribute_b: 8015)
67
+ )
68
+
69
+ invalid = {
70
+ 'attribute_a' => 0,
71
+ 'attribute_b' => 8015
72
+ }
73
+
74
+ expect { evaluator.call(invalid) }.to raise_error(Morpher::Evaluator::Transformer::TransformError)
75
+
76
+ evaluation = evaluator.evaluation(invalid)
77
+ expect(evaluation.success?).to be(false)
78
+ end
79
+
80
+ specify 'allows to inverse a transformations' do
81
+ evaluator = Morpher.compile(transformer_ast)
82
+
83
+ expect(evaluator.inverse.inverse).to eql(evaluator)
84
+
85
+ input = Foo.new(attribute_a: 'a string', attribute_b: 8015)
86
+
87
+ valid = {
88
+ 'attribute_a' => 'a string',
89
+ 'attribute_b' => 8015
90
+ }
91
+
92
+ expect(evaluator.inverse.call(input)).to eql(valid)
93
+ end
94
+
95
+ specify 'allows to merge inputs' do
96
+ evaluator = Morpher.compile(s(:merge, foo: :bar))
97
+
98
+ expect(evaluator.call(foo: :bar)).to eql(foo: :bar)
99
+ expect(evaluator.call(bar: :baz)).to eql(foo: :bar, bar: :baz)
100
+ end
101
+
102
+ specify 'allows to coerce inputs from string to int and back' do
103
+ evaluator = Morpher.compile(s(:parse_int, 10))
104
+
105
+ expect(evaluator.call('42')).to be(42)
106
+ expect(evaluator.inverse.call(42)).to eql('42')
107
+
108
+ evaluator = Morpher.compile(s(:int_to_string, 10))
109
+
110
+ expect(evaluator.call(42)).to eql('42')
111
+ expect(evaluator.inverse.call('42')).to be(42)
112
+ end
113
+
114
+ specify 'allows to coerce inputs from ISO8601 string to DateTime and back' do
115
+ evaluator = Morpher.compile(s(:parse_iso8601_date_time, 0))
116
+
117
+ iso8601_string = '2014-08-04T00:00:00+00:00'
118
+ date_time = DateTime.new(2014, 8, 4)
119
+
120
+ expect(evaluator.call(iso8601_string)).to eq(date_time)
121
+ expect(evaluator.inverse.call(date_time)).to eq(iso8601_string)
122
+
123
+ evaluator = Morpher.compile(s(:date_time_to_iso8601_string, 0))
124
+
125
+ expect(evaluator.call(date_time)).to eq(iso8601_string)
126
+ expect(evaluator.inverse.call(iso8601_string)).to eq(date_time)
127
+ end
128
+
129
+ specify 'allows custom transformations' do
130
+ evaluator = Morpher.compile(s(:custom, [->(v) { "changed_#{v}" }]))
131
+
132
+ expect(evaluator.call('test')).to eql('changed_test')
133
+ end
134
+
135
+ specify 'allows predicates to be run from sexp' do
136
+
137
+ valid = { attribute_a: 'foo' }
138
+ invalid = { attribute_a: 'bar' }
139
+
140
+ evaluator = Morpher.compile(predicate_ast)
141
+
142
+ expect(evaluator.call(valid)).to be(true)
143
+ expect(evaluator.call(invalid)).to be(false)
144
+
145
+ evaluation = evaluator.evaluation(valid)
146
+
147
+ expect(evaluation.output).to be(true)
148
+ expect(evaluation.input).to be(valid)
149
+ expect(evaluation.description).to eql(strip(<<-TEXT))
150
+ Morpher::Evaluation::Nary
151
+ input: {:attribute_a=>"foo"}
152
+ output: true
153
+ success?: true
154
+ evaluator: Morpher::Evaluator::Transformer::Block
155
+ evaluations:
156
+ Morpher::Evaluation::Nullary
157
+ input: {:attribute_a=>"foo"}
158
+ output: "foo"
159
+ success?: true
160
+ evaluator:
161
+ Morpher::Evaluator::Transformer::Key::Fetch
162
+ param: :attribute_a
163
+ Morpher::Evaluation::Binary
164
+ input: "foo"
165
+ output: true
166
+ success?: true
167
+ left_evaluation:
168
+ Morpher::Evaluation::Nullary
169
+ input: "foo"
170
+ output: "foo"
171
+ success?: true
172
+ evaluator:
173
+ Morpher::Evaluator::Transformer::Static
174
+ param: "foo"
175
+ right_evaluation:
176
+ Morpher::Evaluation::Nullary
177
+ input: "foo"
178
+ output: "foo"
179
+ success?: true
180
+ evaluator:
181
+ Morpher::Evaluator::Transformer::Input
182
+ TEXT
183
+ end
184
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1,7 @@
1
+ --exclude-only "spec/,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
7
+ --failure-threshold 100
@@ -0,0 +1,155 @@
1
+ shared_examples_for 'inverse evaluator' do
2
+ context '#inverse' do
3
+ subject { object.inverse }
4
+
5
+ it 'returns the expected inverse evaluator' do
6
+ should eql(expected_inverse)
7
+ end
8
+ end
9
+ end
10
+
11
+ shared_examples_for 'evaluator' do
12
+ it 'round trips transitive evaluators via #inverse' do
13
+ if object.kind_of?(Morpher::Evaluator::Transformer) && object.transitive?
14
+ object.inverse.inverse.should eql(object)
15
+ end
16
+ end
17
+
18
+ it 'round trips evaluators via #node' do
19
+ Morpher.compile(object.node).should eql(object)
20
+ end
21
+ end
22
+
23
+ shared_examples_for 'predicate evaluator' do
24
+ include_examples 'evaluator'
25
+
26
+ let(:negative_example?) { true }
27
+ let(:expected_output) { true }
28
+ let(:valid_input) { positive_input }
29
+ let(:expected_positive_output) { true }
30
+ let(:expected_negative_output) { false }
31
+
32
+ context 'with positive input' do
33
+ it 'evaluates to positive output' do
34
+ expect(object.call(positive_input)).to be(expected_positive_output)
35
+ end
36
+
37
+ it 'evaluates to inverted positive output' do
38
+ expect(object.inverse.call(positive_input)).to be(!expected_positive_output)
39
+ end
40
+
41
+ it 'evaluates to the same output under #evaluation' do
42
+ evaluation = object.evaluation(positive_input)
43
+ expect(evaluation.success?).to be(true)
44
+ expect(evaluation.input).to be(positive_input)
45
+ expect(evaluation.output).to be(expected_positive_output)
46
+ end
47
+ end
48
+
49
+ context 'with negative input' do
50
+ it 'evaluates to false' do
51
+ expect(object.call(negative_input)).to be(false)
52
+ end
53
+
54
+ it 'evaluates to true on inverse' do
55
+ expect(object.inverse.call(negative_input)).to be(true)
56
+ end
57
+
58
+ it 'evaluates to the same output under #evaluation' do
59
+ evaluation = object.evaluation(negative_input)
60
+ expect(evaluation.input).to be(negative_input)
61
+ expect(evaluation.success?).to be(true)
62
+ expect(evaluation.output).to be(false)
63
+ end
64
+ end
65
+ end
66
+
67
+ shared_examples_for 'transitive evaluator' do
68
+ include_examples 'evaluator'
69
+
70
+ let(:invalid_input_example?) { true }
71
+
72
+ it 'signals transitivity via #transitive?' do
73
+ expect(object.transitive?).to be(true)
74
+ end
75
+
76
+ it 'round trips valid inputs via #evaluation' do
77
+ evaluation = object.evaluation(valid_input)
78
+ expect(evaluation.success?).to be(true)
79
+ evaluation = object.inverse.evaluation(evaluation.output)
80
+ expect(evaluation.output).to eql(valid_input)
81
+ expect(evaluation.success?).to be(true)
82
+ end
83
+
84
+ it 'round trips valid inputs via #call' do
85
+ forward = object.call(valid_input)
86
+ expect(object.inverse.call(forward)).to eql(valid_input)
87
+ end
88
+ end
89
+
90
+ shared_examples_for 'intransitive evaluator' do
91
+ include_examples 'evaluator'
92
+
93
+ let(:invalid_input_example?) { true }
94
+
95
+ it 'signals intransitivity via #transitive?' do
96
+ expect(object.transitive?).to be(false)
97
+ end
98
+ end
99
+
100
+ shared_examples_for 'transforming evaluator on valid input' do
101
+ include_examples 'evaluator'
102
+
103
+ it 'transforms to expected output via #call' do
104
+ result = object.call(valid_input)
105
+ expect(result).to eql(expected_output)
106
+ end
107
+
108
+ it 'transforms to expected output via #evaluation' do
109
+ evaluation = object.evaluation(valid_input)
110
+ expect(evaluation.success?).to eql(true)
111
+ expect(evaluation.output).to eql(expected_output)
112
+ end
113
+
114
+ specify '#evaluation' do
115
+ evaluation = object.evaluation(valid_input)
116
+ expect(evaluation.success?).to be(true)
117
+ expect(evaluation.evaluator).to eql(object)
118
+ expect(evaluation.input).to eql(valid_input)
119
+ expect(evaluation.output).to eql(expected_output)
120
+ end
121
+
122
+ specify '#call' do
123
+ expect(object.call(valid_input)).to eql(expected_output)
124
+ end
125
+ end
126
+
127
+ shared_examples_for 'transforming evaluator on invalid input' do
128
+ it 'raises error for #call' do
129
+ expect { object.call(invalid_input) }.to raise_error(
130
+ Morpher::Evaluator::Transformer::TransformError
131
+ )
132
+ end
133
+
134
+ it 'returns error evaluator for #evaluation' do
135
+ evaluation = object.evaluation(invalid_input)
136
+ expect(evaluation.success?).to eql(false)
137
+ expect(evaluation.output).to be(Morpher::Undefined)
138
+ end
139
+
140
+ let(:expected_exception) do
141
+ Morpher::Evaluator::Transformer::TransformError.new(object, invalid_input)
142
+ end
143
+
144
+ specify '#call' do
145
+ expect { object.call(invalid_input) }.to raise_error(expected_exception)
146
+ end
147
+
148
+ specify '#evaluation' do
149
+ evaluation = object.evaluation(invalid_input)
150
+ expect(evaluation.success?).to be(false)
151
+ expect(evaluation.evaluator).to eql(object)
152
+ expect(evaluation.input).to eql(invalid_input)
153
+ expect(evaluation.output).to eql(Morpher::Undefined)
154
+ end
155
+ end