formalist 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -3
- data/Gemfile.lock +36 -38
- data/README.md +8 -4
- data/Rakefile +0 -7
- data/lib/formalist/element/attributes.rb +54 -0
- data/lib/formalist/element/class_interface.rb +133 -0
- data/lib/formalist/element/definition.rb +55 -0
- data/lib/formalist/element/permitted_children.rb +46 -0
- data/lib/formalist/element.rb +51 -0
- data/lib/formalist/elements/attr.rb +74 -0
- data/lib/formalist/elements/compound_field.rb +49 -0
- data/lib/formalist/elements/field.rb +73 -0
- data/lib/formalist/elements/group.rb +50 -0
- data/lib/formalist/elements/many.rb +125 -0
- data/lib/formalist/elements/section.rb +58 -0
- data/lib/formalist/elements/standard/check_box.rb +20 -0
- data/lib/formalist/elements/standard/date_field.rb +12 -0
- data/lib/formalist/elements/standard/date_time_field.rb +12 -0
- data/lib/formalist/elements/standard/hidden_field.rb +11 -0
- data/lib/formalist/elements/standard/multi_selection_field.rb +16 -0
- data/lib/formalist/elements/standard/number_field.rb +17 -0
- data/lib/formalist/elements/standard/radio_buttons.rb +13 -0
- data/lib/formalist/elements/standard/select_box.rb +13 -0
- data/lib/formalist/elements/standard/selection_field.rb +16 -0
- data/lib/formalist/elements/standard/text_area.rb +15 -0
- data/lib/formalist/elements/standard/text_field.rb +14 -0
- data/lib/formalist/elements/standard.rb +11 -0
- data/lib/formalist/elements.rb +20 -0
- data/lib/formalist/form/definition_context.rb +58 -4
- data/lib/formalist/form/result.rb +5 -27
- data/lib/formalist/form.rb +15 -35
- data/lib/formalist/types.rb +30 -0
- data/lib/formalist/version.rb +1 -1
- data/lib/formalist.rb +0 -20
- data/spec/examples.txt +8 -7
- data/spec/integration/dependency_injection_spec.rb +54 -0
- data/spec/integration/form_spec.rb +86 -13
- data/spec/spec_helper.rb +12 -5
- data/spec/support/constants.rb +11 -0
- data/spec/unit/elements/standard/check_box_spec.rb +33 -0
- metadata +36 -63
- data/lib/formalist/definition_compiler.rb +0 -61
- data/lib/formalist/display_adapters/default.rb +0 -9
- data/lib/formalist/display_adapters/radio.rb +0 -19
- data/lib/formalist/display_adapters/select.rb +0 -19
- data/lib/formalist/display_adapters/textarea.rb +0 -14
- data/lib/formalist/display_adapters.rb +0 -16
- data/lib/formalist/form/definition/attr.rb +0 -20
- data/lib/formalist/form/definition/component.rb +0 -31
- data/lib/formalist/form/definition/field.rb +0 -29
- data/lib/formalist/form/definition/group.rb +0 -31
- data/lib/formalist/form/definition/many.rb +0 -41
- data/lib/formalist/form/definition/section.rb +0 -23
- data/lib/formalist/form/definition.rb +0 -37
- data/lib/formalist/form/result/attr.rb +0 -82
- data/lib/formalist/form/result/component.rb +0 -51
- data/lib/formalist/form/result/field.rb +0 -77
- data/lib/formalist/form/result/group.rb +0 -51
- data/lib/formalist/form/result/many.rb +0 -123
- data/lib/formalist/form/result/section.rb +0 -54
- data/lib/formalist/form/validated_result.rb +0 -35
- data/lib/formalist/output_compiler.rb +0 -43
- data/lib/formalist/validation/collection_rules_compiler.rb +0 -77
- data/lib/formalist/validation/predicate_list_compiler.rb +0 -73
- data/lib/formalist/validation/value_rules_compiler.rb +0 -96
- data/spec/integration/display_adapters_spec.rb +0 -55
- data/spec/integration/validation_spec.rb +0 -86
- data/spec/unit/output_compiler_spec.rb +0 -70
@@ -1,35 +0,0 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
|
3
|
-
module Formalist
|
4
|
-
class Form
|
5
|
-
class ValidatedResult
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
def_delegators :@result,
|
9
|
-
:input,
|
10
|
-
:schema,
|
11
|
-
:elements,
|
12
|
-
:validation,
|
13
|
-
:output
|
14
|
-
|
15
|
-
# @api private
|
16
|
-
attr_reader :result
|
17
|
-
|
18
|
-
def initialize(result)
|
19
|
-
@result = result
|
20
|
-
end
|
21
|
-
|
22
|
-
def success?
|
23
|
-
validation.success?
|
24
|
-
end
|
25
|
-
|
26
|
-
def messages
|
27
|
-
validation.messages
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_ast
|
31
|
-
elements.map { |el| el.(output, schema.rules.map(&:to_ary), messages).to_ast }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module Formalist
|
2
|
-
class OutputCompiler
|
3
|
-
def call(ast)
|
4
|
-
ast.map { |node| visit(node) }.inject(:merge)
|
5
|
-
end
|
6
|
-
|
7
|
-
private
|
8
|
-
|
9
|
-
def visit(node)
|
10
|
-
send(:"visit_#{node[0]}", node[1])
|
11
|
-
end
|
12
|
-
|
13
|
-
def visit_attr(data)
|
14
|
-
name, predicates, errors, children = data
|
15
|
-
|
16
|
-
{name => children.map { |node| visit(node) }.inject(:merge) }
|
17
|
-
end
|
18
|
-
|
19
|
-
def visit_field(data)
|
20
|
-
name, _type, _display_variant, value, _predicates, _errors = data
|
21
|
-
|
22
|
-
{name => value.to_s}
|
23
|
-
end
|
24
|
-
|
25
|
-
def visit_group(data)
|
26
|
-
config, children = data
|
27
|
-
|
28
|
-
children.map { |node| visit(node) }.inject(:merge)
|
29
|
-
end
|
30
|
-
|
31
|
-
def visit_many(data)
|
32
|
-
name, predicates, errors, config, template, children = data
|
33
|
-
|
34
|
-
{name => children.map { |item| item.map { |node| visit(node) }.inject(:merge) }}
|
35
|
-
end
|
36
|
-
|
37
|
-
def visit_section(data)
|
38
|
-
name, config, children = data
|
39
|
-
|
40
|
-
children.map { |node| visit(node) }.inject(:merge)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
module Formalist
|
2
|
-
module Validation
|
3
|
-
class CollectionRulesCompiler
|
4
|
-
attr_reader :target_name
|
5
|
-
|
6
|
-
def initialize(target_name)
|
7
|
-
@target_name = target_name
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(ast)
|
11
|
-
ast.map { |node| visit(node) }.reduce([], :concat).each_slice(2).to_a
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def visit(node)
|
17
|
-
name, nodes = node
|
18
|
-
send(:"visit_#{name}", nodes)
|
19
|
-
end
|
20
|
-
|
21
|
-
def visit_set(node)
|
22
|
-
name, rules = node
|
23
|
-
return [] unless name == target_name
|
24
|
-
|
25
|
-
rules.flatten(1)
|
26
|
-
end
|
27
|
-
|
28
|
-
def visit_each(node)
|
29
|
-
name, rule = node
|
30
|
-
return [] unless name == target_name
|
31
|
-
|
32
|
-
visit(rule)
|
33
|
-
end
|
34
|
-
|
35
|
-
def visit_predicate(node)
|
36
|
-
name, args = node
|
37
|
-
[:predicate, node]
|
38
|
-
end
|
39
|
-
|
40
|
-
def visit_and(node)
|
41
|
-
left, right = node
|
42
|
-
flatten_logical_operation(:and, [visit(left), visit(right)])
|
43
|
-
end
|
44
|
-
|
45
|
-
def visit_or(node)
|
46
|
-
left, right = node
|
47
|
-
flatten_logical_operation(:or, [visit(left), visit(right)])
|
48
|
-
end
|
49
|
-
|
50
|
-
def visit_xor(node)
|
51
|
-
left, right = node
|
52
|
-
flatten_logical_operation(:xor, [visit(left), visit(right)])
|
53
|
-
end
|
54
|
-
|
55
|
-
def visit_implication(node)
|
56
|
-
left, right = node
|
57
|
-
flatten_logical_operation(:implication, [visit(left), visit(right)])
|
58
|
-
end
|
59
|
-
|
60
|
-
def method_missing(name, *args)
|
61
|
-
[]
|
62
|
-
end
|
63
|
-
|
64
|
-
def flatten_logical_operation(name, contents)
|
65
|
-
contents = contents.select(&:any?)
|
66
|
-
|
67
|
-
if contents.length == 0
|
68
|
-
[]
|
69
|
-
elsif contents.length == 1
|
70
|
-
contents.first
|
71
|
-
else
|
72
|
-
[name, contents]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
module Formalist
|
2
|
-
module Validation
|
3
|
-
class PredicateListCompiler
|
4
|
-
IGNORED_PREDICATES = [:key?].freeze
|
5
|
-
|
6
|
-
def call(ast)
|
7
|
-
ast.map { |node| visit(node) }.reduce([], :concat).each_slice(2).to_a
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def visit(node)
|
13
|
-
name, nodes = node
|
14
|
-
send(:"visit_#{name}", nodes)
|
15
|
-
end
|
16
|
-
|
17
|
-
def visit_key(node)
|
18
|
-
name, predicate = node
|
19
|
-
|
20
|
-
visit(predicate)
|
21
|
-
end
|
22
|
-
|
23
|
-
def visit_val(node)
|
24
|
-
name, predicate = node
|
25
|
-
|
26
|
-
visit(predicate)
|
27
|
-
end
|
28
|
-
|
29
|
-
def visit_predicate(node)
|
30
|
-
name, args = node
|
31
|
-
return [] if IGNORED_PREDICATES.include?(name)
|
32
|
-
|
33
|
-
[:predicate, node]
|
34
|
-
end
|
35
|
-
|
36
|
-
def visit_and(node)
|
37
|
-
left, right = node
|
38
|
-
flatten_logical_operation(:and, [visit(left), visit(right)])
|
39
|
-
end
|
40
|
-
|
41
|
-
def visit_or(node)
|
42
|
-
left, right = node
|
43
|
-
flatten_logical_operation(:or, [visit(left), visit(right)])
|
44
|
-
end
|
45
|
-
|
46
|
-
def visit_xor(node)
|
47
|
-
left, right = node
|
48
|
-
flatten_logical_operation(:xor, [visit(left), visit(right)])
|
49
|
-
end
|
50
|
-
|
51
|
-
def visit_implication(node)
|
52
|
-
left, right = node
|
53
|
-
flatten_logical_operation(:implication, [visit(left), visit(right)])
|
54
|
-
end
|
55
|
-
|
56
|
-
def method_missing(name, *args)
|
57
|
-
[]
|
58
|
-
end
|
59
|
-
|
60
|
-
def flatten_logical_operation(name, contents)
|
61
|
-
contents = contents.select(&:any?)
|
62
|
-
|
63
|
-
if contents.length == 0
|
64
|
-
[]
|
65
|
-
elsif contents.length == 1
|
66
|
-
contents.first
|
67
|
-
else
|
68
|
-
[name, contents]
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
module Formalist
|
2
|
-
module Validation
|
3
|
-
class ValueRulesCompiler
|
4
|
-
attr_reader :target_name
|
5
|
-
|
6
|
-
def initialize(target_name)
|
7
|
-
@target_name = target_name
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(ast)
|
11
|
-
ast.map { |node| visit(node) }.reduce([], :concat).each_slice(2).to_a
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def visit(node)
|
17
|
-
name, nodes = node
|
18
|
-
send(:"visit_#{name}", nodes)
|
19
|
-
end
|
20
|
-
|
21
|
-
def visit_key(node)
|
22
|
-
# We can ignore "key" checks - we'll only pick up rules for keys we
|
23
|
-
# know will exist, since they're attached to fields.
|
24
|
-
[]
|
25
|
-
end
|
26
|
-
|
27
|
-
def visit_val(node)
|
28
|
-
name, predicate = node
|
29
|
-
|
30
|
-
# Support names that show as keypaths, e.g. [:reviews, :rating]
|
31
|
-
name = name.last if name.is_a?(Array)
|
32
|
-
|
33
|
-
return [] unless name == target_name
|
34
|
-
|
35
|
-
# Skip the "val" prefix
|
36
|
-
|
37
|
-
[:val, [name, visit(predicate)]]
|
38
|
-
end
|
39
|
-
|
40
|
-
# def visit_set(node)
|
41
|
-
# name, rules = node
|
42
|
-
# return [] unless name == target_name
|
43
|
-
|
44
|
-
# rules.flatten(1)
|
45
|
-
# end
|
46
|
-
|
47
|
-
# def visit_each(node)
|
48
|
-
# name, rule = node
|
49
|
-
# return [] unless name == target_name
|
50
|
-
|
51
|
-
# visit(rule)
|
52
|
-
# end
|
53
|
-
|
54
|
-
def visit_predicate(node)
|
55
|
-
name, args = node
|
56
|
-
[:predicate, node]
|
57
|
-
end
|
58
|
-
|
59
|
-
def visit_and(node)
|
60
|
-
left, right = node
|
61
|
-
flatten_logical_operation(:and, [visit(left), visit(right)])
|
62
|
-
end
|
63
|
-
|
64
|
-
def visit_or(node)
|
65
|
-
left, right = node
|
66
|
-
flatten_logical_operation(:or, [visit(left), visit(right)])
|
67
|
-
end
|
68
|
-
|
69
|
-
def visit_xor(node)
|
70
|
-
left, right = node
|
71
|
-
flatten_logical_operation(:xor, [visit(left), visit(right)])
|
72
|
-
end
|
73
|
-
|
74
|
-
def visit_implication(node)
|
75
|
-
left, right = node
|
76
|
-
flatten_logical_operation(:implication, [visit(left), visit(right)])
|
77
|
-
end
|
78
|
-
|
79
|
-
def method_missing(name, *args)
|
80
|
-
[]
|
81
|
-
end
|
82
|
-
|
83
|
-
def flatten_logical_operation(name, contents)
|
84
|
-
contents = contents.select(&:any?)
|
85
|
-
|
86
|
-
if contents.length == 0
|
87
|
-
[]
|
88
|
-
elsif contents.length == 1
|
89
|
-
contents.first
|
90
|
-
else
|
91
|
-
[name, contents]
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
RSpec.describe "Display adapters" do
|
2
|
-
let(:schema) {
|
3
|
-
Class.new(Dry::Validation::Schema) do
|
4
|
-
key(:temperature_unit)
|
5
|
-
end.new
|
6
|
-
}
|
7
|
-
|
8
|
-
subject(:form) {
|
9
|
-
Class.new(Formalist::Form) do
|
10
|
-
field :temperature_unit, type: "string", display: "select", option_values: [%w[c c], %w[f f]]
|
11
|
-
end.new(schema)
|
12
|
-
}
|
13
|
-
|
14
|
-
it "outputs an AST" do
|
15
|
-
expect(form.build({}).to_ast).to eq [
|
16
|
-
[:field, [
|
17
|
-
:temperature_unit,
|
18
|
-
"string",
|
19
|
-
"select",
|
20
|
-
nil,
|
21
|
-
[],
|
22
|
-
[],
|
23
|
-
[
|
24
|
-
[:option_values, [["c", "c"], ["f", "f"]]]
|
25
|
-
]
|
26
|
-
]]
|
27
|
-
]
|
28
|
-
end
|
29
|
-
|
30
|
-
it "supports custom disply adapters in a provided container" do
|
31
|
-
adapter_class = Class.new do
|
32
|
-
def call(field)
|
33
|
-
field.to_display_variant("custom")
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
container = Class.new(Formalist::DisplayAdapters) do
|
38
|
-
register "custom", adapter_class.new
|
39
|
-
end
|
40
|
-
|
41
|
-
form = Class.new(Formalist::Form) do
|
42
|
-
configure do |config|
|
43
|
-
config.display_adapters = container
|
44
|
-
end
|
45
|
-
|
46
|
-
field :name, type: "string", display: "custom"
|
47
|
-
field :email, type: "string"
|
48
|
-
end.new(schema)
|
49
|
-
|
50
|
-
expect(form.build({}).to_ast).to eq [
|
51
|
-
[:field, [:name, "string", "custom", nil, [], [], []]],
|
52
|
-
[:field, [:email, "string", "default", nil, [], [], []]],
|
53
|
-
]
|
54
|
-
end
|
55
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
require "dry-validation"
|
2
|
-
require "pp"
|
3
|
-
|
4
|
-
RSpec.describe "Form validation" do
|
5
|
-
subject(:schema) {
|
6
|
-
Class.new(Dry::Validation::Schema) do
|
7
|
-
key(:title) { |title| title.filled? }
|
8
|
-
key(:rating) { |rating| rating.gteq?(1) & rating.lteq?(10) }
|
9
|
-
|
10
|
-
key(:reviews) do |reviews|
|
11
|
-
reviews.filled? & \
|
12
|
-
reviews.each do |review|
|
13
|
-
review.key(:summary) { |summary| summary.filled? }
|
14
|
-
review.key(:rating) { |rating| rating.gteq?(1) & rating.lteq?(10) }
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
# TODO: uncomment/fix the "meta" related sections (here and below) once
|
20
|
-
# we have a clarification on dryrb/dry-validation#58.
|
21
|
-
#
|
22
|
-
# key(:meta) do |meta|
|
23
|
-
# meta.key(:pages) { |pages| pages.filled? }
|
24
|
-
# end
|
25
|
-
end.new
|
26
|
-
}
|
27
|
-
|
28
|
-
subject(:form) {
|
29
|
-
Class.new(Formalist::Form) do
|
30
|
-
field :title, type: "string"
|
31
|
-
field :rating, type: "int"
|
32
|
-
|
33
|
-
many :reviews do |review|
|
34
|
-
review.field :summary, type: "string"
|
35
|
-
review.field :rating, type: "int"
|
36
|
-
end
|
37
|
-
|
38
|
-
# attr :meta do |meta|
|
39
|
-
# meta.field :pages, type: "int"
|
40
|
-
# end
|
41
|
-
end.new(schema)
|
42
|
-
}
|
43
|
-
|
44
|
-
it "includes validation rules and errors in the AST" do
|
45
|
-
input = {
|
46
|
-
reviews: [{summary: "Great", rating: 0}, {summary: "", rating: 1}],
|
47
|
-
meta: {pages: nil}
|
48
|
-
}
|
49
|
-
|
50
|
-
expect(form.build(input).validate.to_ast).to eq [
|
51
|
-
[:field, [:title, "string", "default", nil, [[:predicate, [:filled?, []]]], ["title is missing"], []]],
|
52
|
-
[:field, [:rating, "int", "default", nil, [[:and, [[:predicate, [:gteq?, [1]]], [:predicate, [:lteq?, [10]]]]]], ["rating is missing", "rating must be greater than or equal to 1", "rating must be less than or equal to 10"], []]],
|
53
|
-
[:many, [:reviews,
|
54
|
-
[[:predicate, [:filled?, []]]],
|
55
|
-
[],
|
56
|
-
[
|
57
|
-
[:allow_create, true],
|
58
|
-
[:allow_update, true],
|
59
|
-
[:allow_destroy, true],
|
60
|
-
[:allow_reorder, true],
|
61
|
-
],
|
62
|
-
[
|
63
|
-
[:field, [:summary, "string", "default", nil, [[:predicate, [:filled?, []]]], [], []]],
|
64
|
-
[:field, [:rating, "int", "default", nil, [[:and, [[:predicate, [:gteq?, [1]]], [:predicate, [:lteq?, [10]]]]]], [], []]],
|
65
|
-
],
|
66
|
-
[
|
67
|
-
[
|
68
|
-
[:field, [:summary, "string", "default", "Great", [[:predicate, [:filled?, []]]], [], []]],
|
69
|
-
[:field, [:rating, "int", "default", 0, [[:and, [[:predicate, [:gteq?, [1]]], [:predicate, [:lteq?, [10]]]]]], ["rating must be greater than or equal to 1"], []]],
|
70
|
-
],
|
71
|
-
[
|
72
|
-
[:field, [:summary, "string", "default", "", [[:predicate, [:filled?, []]]], ["summary must be filled"], []]],
|
73
|
-
[:field, [:rating, "int", "default", 1, [[:and, [[:predicate, [:gteq?, [1]]], [:predicate, [:lteq?, [10]]]]]], [], []]],
|
74
|
-
]
|
75
|
-
],
|
76
|
-
]],
|
77
|
-
# [:attr, [:meta,
|
78
|
-
# [],
|
79
|
-
# [],
|
80
|
-
# [
|
81
|
-
# [:field, [:pages, "int", "default", nil, [[:predicate, [:filled?, []]]], ["pages must be filled"], []]]
|
82
|
-
# ],
|
83
|
-
# ]]
|
84
|
-
]
|
85
|
-
end
|
86
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
RSpec.describe Formalist::OutputCompiler do
|
2
|
-
subject(:compiler) { Formalist::OutputCompiler.new }
|
3
|
-
|
4
|
-
let(:schema) {
|
5
|
-
Class.new(Dry::Validation::Schema) do
|
6
|
-
key(:title, &:str?)
|
7
|
-
key(:rating, &:int?)
|
8
|
-
|
9
|
-
key(:reviews) do |reviews|
|
10
|
-
reviews.each do |review|
|
11
|
-
review.key(:description, &:str?)
|
12
|
-
review.key(:rating, &:int?)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
key(:meta) do |meta|
|
17
|
-
meta.key(:pages, &:int?)
|
18
|
-
meta.key(:publisher, &:str?)
|
19
|
-
end
|
20
|
-
end.new
|
21
|
-
}
|
22
|
-
|
23
|
-
let(:form) {
|
24
|
-
Class.new(Formalist::Form) do
|
25
|
-
field :title, type: "string"
|
26
|
-
field :rating, type: "int"
|
27
|
-
|
28
|
-
many :reviews do |review|
|
29
|
-
review.field :description, type: "string"
|
30
|
-
review.field :rating, type: "int"
|
31
|
-
end
|
32
|
-
|
33
|
-
attr :meta do |meta|
|
34
|
-
meta.section "Metadata" do |section|
|
35
|
-
section.group do |group|
|
36
|
-
group.field :pages, type: "int"
|
37
|
-
group.field :publisher, type: "string"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end.new(schema)
|
42
|
-
}
|
43
|
-
|
44
|
-
let(:input) {
|
45
|
-
{
|
46
|
-
title: "Aurora",
|
47
|
-
rating: "10",
|
48
|
-
reviews: [
|
49
|
-
{
|
50
|
-
description: "Wonderful",
|
51
|
-
rating: "10",
|
52
|
-
},
|
53
|
-
{
|
54
|
-
description: "Enchanting",
|
55
|
-
rating: "9",
|
56
|
-
}
|
57
|
-
],
|
58
|
-
meta: {
|
59
|
-
pages: "321",
|
60
|
-
publisher: "Orbit",
|
61
|
-
},
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
let(:ast) { form.build(input).to_ast }
|
66
|
-
|
67
|
-
it "works" do
|
68
|
-
expect(compiler.call(ast)).to eq input
|
69
|
-
end
|
70
|
-
end
|