json_model_rb 0.1.15 → 0.1.17
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.
- checksums.yaml +4 -4
- data/lib/json_model/config.rb +2 -0
- data/lib/json_model/schema_meta.rb +12 -2
- data/lib/json_model/type_spec/castable.rb +34 -0
- data/lib/json_model/type_spec/primitive/string.rb +2 -3
- data/lib/json_model/type_spec.rb +17 -10
- data/lib/json_model/version.rb +1 -1
- data/spec/examples/file_system_spec.rb +0 -5
- data/spec/examples/user_spec.rb +9 -3
- data/spec/schema_meta_spec.rb +22 -2
- data/spec/schema_spec.rb +1 -8
- data/spec/type_spec/castable_spec.rb +19 -0
- data/spec/type_spec/composition_spec.rb +0 -1
- metadata +4 -3
- data/spec/examples/component_spec.rb +0 -57
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 601bb6f47fc30b627b972036213a1388f0e844a42476988cbeb19156edf59ea0
|
|
4
|
+
data.tar.gz: 4550f13f61deaf8b455bf7b8437e05e13401aae8d33932460e722574e0bc6ee7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45305cffcc227dd58959f1b5ae64662d378f6d703e85b53da9319e9b6366d075c0c20a70465fc8f1fbb319b83d58d5637682f4a29faf3e8513bf78aa632f2b20
|
|
7
|
+
data.tar.gz: 21de5df72ff9f5b1244e530133d0c6d2e6cddf6123a25146a07cd79db73f1e8083488f7dc37fe32fab72c169f05baba296ba96fa6292f52f188889adb5a48cf4
|
data/lib/json_model/config.rb
CHANGED
|
@@ -17,12 +17,12 @@ module JsonModel
|
|
|
17
17
|
def self.inherited(subclass)
|
|
18
18
|
super
|
|
19
19
|
subclass.schema_id(JsonModel.config.schema_id_naming_strategy.call(subclass))
|
|
20
|
-
subclass.
|
|
20
|
+
subclass.schema_version(JsonModel.config.schema_version)
|
|
21
21
|
subclass.meta_attributes[:$ref] = schema_id
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
schema_id(JsonModel.config.schema_id_naming_strategy.call(self))
|
|
25
|
-
|
|
25
|
+
schema_version(JsonModel.config.schema_version)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
class_methods do
|
|
@@ -71,6 +71,16 @@ module JsonModel
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# @param [Boolean, nil] value
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def unevaluated_properties(value = nil)
|
|
77
|
+
if value.nil?
|
|
78
|
+
meta_attributes[:unevaluatedProperties] || false
|
|
79
|
+
else
|
|
80
|
+
meta_attributes[:unevaluatedProperties] = value
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
74
84
|
# @param [Symbol, nil] version
|
|
75
85
|
# @return [Boolean]
|
|
76
86
|
def schema_version(version = nil)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonModel
|
|
4
|
+
class TypeSpec
|
|
5
|
+
class Castable < TypeSpec
|
|
6
|
+
# @param [String] format
|
|
7
|
+
# @param [Proc] cast_block
|
|
8
|
+
def initialize(format:, &cast_block)
|
|
9
|
+
super()
|
|
10
|
+
@format = format
|
|
11
|
+
@cast_block = cast_block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param [Hash] _options
|
|
15
|
+
# @return [Hash]
|
|
16
|
+
def as_schema(**_options)
|
|
17
|
+
{
|
|
18
|
+
type: 'string',
|
|
19
|
+
format: @format,
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param [::Object] json
|
|
24
|
+
# @return [::Object, nil]
|
|
25
|
+
def cast(json)
|
|
26
|
+
if json.nil?
|
|
27
|
+
nil
|
|
28
|
+
else
|
|
29
|
+
@cast_block.call(json)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -62,7 +62,7 @@ module JsonModel
|
|
|
62
62
|
end
|
|
63
63
|
},
|
|
64
64
|
uuid: lambda { |v|
|
|
65
|
-
|
|
65
|
+
v.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i)
|
|
66
66
|
},
|
|
67
67
|
regex: lambda { |v|
|
|
68
68
|
begin
|
|
@@ -79,7 +79,7 @@ module JsonModel
|
|
|
79
79
|
# @param [Integer, nil] min_length
|
|
80
80
|
# @param [Integer, nil] max_length
|
|
81
81
|
# @param [Regexp, nil] pattern
|
|
82
|
-
# @param [
|
|
82
|
+
# @param [Symbol, nil] format
|
|
83
83
|
def initialize(min_length: nil, max_length: nil, pattern: nil, format: nil)
|
|
84
84
|
super(types: [::String], schema_type: 'string')
|
|
85
85
|
|
|
@@ -115,7 +115,6 @@ module JsonModel
|
|
|
115
115
|
end
|
|
116
116
|
if @format
|
|
117
117
|
if JSON_SCHEMA_FORMATS.key?(@format)
|
|
118
|
-
|
|
119
118
|
register_format_validation(klass, name)
|
|
120
119
|
else
|
|
121
120
|
raise(ArgumentError, "Invalid format: #{@format}")
|
data/lib/json_model/type_spec.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative('type_spec/array')
|
|
4
|
+
require_relative('type_spec/castable')
|
|
4
5
|
require_relative('type_spec/composition')
|
|
5
6
|
require_relative('type_spec/const')
|
|
6
7
|
require_relative('type_spec/enum')
|
|
@@ -9,6 +10,20 @@ require_relative('type_spec/primitive')
|
|
|
9
10
|
|
|
10
11
|
module JsonModel
|
|
11
12
|
class TypeSpec
|
|
13
|
+
TYPE_MAP = {
|
|
14
|
+
Date => Castable.new(format: 'date') { |v| ::DateTime.iso8601(v) },
|
|
15
|
+
DateTime => Castable.new(format: 'date-time') { |v| ::DateTime.iso8601(v) },
|
|
16
|
+
FalseClass => Primitive::Boolean.new,
|
|
17
|
+
Float => Primitive::Number.new,
|
|
18
|
+
Integer => Primitive::Integer.new,
|
|
19
|
+
NilClass => Primitive::Null.new,
|
|
20
|
+
Regexp => Castable.new(format: 'regex') { |v| Regexp.new(v) },
|
|
21
|
+
String => Primitive::String.new,
|
|
22
|
+
Time => Castable.new(format: 'time') { |v| ::Time.iso8601(v) },
|
|
23
|
+
TrueClass => Primitive::Boolean.new,
|
|
24
|
+
URI => Castable.new(format: 'uri') { |v| URI.parse(v) },
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
12
27
|
# @param [Hash] options
|
|
13
28
|
# @return [Hash]
|
|
14
29
|
def as_schema(**options)
|
|
@@ -53,16 +68,8 @@ module JsonModel
|
|
|
53
68
|
# @param [Object, Class] type
|
|
54
69
|
# @return [TypeSpec]
|
|
55
70
|
def resolve_type_from_class(type)
|
|
56
|
-
if type
|
|
57
|
-
|
|
58
|
-
elsif type == Integer
|
|
59
|
-
Primitive::Integer.new
|
|
60
|
-
elsif type == Float
|
|
61
|
-
Primitive::Number.new
|
|
62
|
-
elsif [TrueClass, FalseClass].include?(type)
|
|
63
|
-
Primitive::Boolean.new
|
|
64
|
-
elsif type == NilClass
|
|
65
|
-
Primitive::Null.new
|
|
71
|
+
if TYPE_MAP.key?(type)
|
|
72
|
+
TYPE_MAP[type]
|
|
66
73
|
elsif type < Schema
|
|
67
74
|
TypeSpec::Object.new(type)
|
|
68
75
|
else
|
data/lib/json_model/version.rb
CHANGED
|
@@ -98,7 +98,6 @@ RSpec.describe('File system schema') do
|
|
|
98
98
|
type: 'boolean',
|
|
99
99
|
},
|
|
100
100
|
},
|
|
101
|
-
additionalProperties: false,
|
|
102
101
|
'$defs': {
|
|
103
102
|
DiskDevice: {
|
|
104
103
|
properties: {
|
|
@@ -111,7 +110,6 @@ RSpec.describe('File system schema') do
|
|
|
111
110
|
},
|
|
112
111
|
},
|
|
113
112
|
required: %i(device type),
|
|
114
|
-
additionalProperties: false,
|
|
115
113
|
type: 'object',
|
|
116
114
|
},
|
|
117
115
|
DiskUuid: {
|
|
@@ -123,7 +121,6 @@ RSpec.describe('File system schema') do
|
|
|
123
121
|
},
|
|
124
122
|
},
|
|
125
123
|
required: %i(label type),
|
|
126
|
-
additionalProperties: false,
|
|
127
124
|
type: 'object',
|
|
128
125
|
},
|
|
129
126
|
Nfs: {
|
|
@@ -139,7 +136,6 @@ RSpec.describe('File system schema') do
|
|
|
139
136
|
},
|
|
140
137
|
},
|
|
141
138
|
required: %i(remotePath server type),
|
|
142
|
-
additionalProperties: false,
|
|
143
139
|
type: 'object',
|
|
144
140
|
},
|
|
145
141
|
Tmpfs: {
|
|
@@ -148,7 +144,6 @@ RSpec.describe('File system schema') do
|
|
|
148
144
|
sizeInMB: { type: 'integer', minimum: 16, maximum: 512 },
|
|
149
145
|
},
|
|
150
146
|
required: %i(sizeInMB type),
|
|
151
|
-
additionalProperties: false,
|
|
152
147
|
type: 'object',
|
|
153
148
|
},
|
|
154
149
|
},
|
data/spec/examples/user_spec.rb
CHANGED
|
@@ -33,6 +33,7 @@ RSpec.describe('User schema') do
|
|
|
33
33
|
property(:active, type: T::Boolean, default: true, optional: true)
|
|
34
34
|
property(:addresses, type: T::Array[Address], ref_mode: JsonModel::RefMode::LOCAL)
|
|
35
35
|
property(:tags, type: T::Array[String], optional: true)
|
|
36
|
+
property(:birthday, type: Date, optional: true)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
stub_const('User', user_class)
|
|
@@ -54,8 +55,8 @@ RSpec.describe('User schema') do
|
|
|
54
55
|
items: { '$ref': '#/$defs/Address' },
|
|
55
56
|
},
|
|
56
57
|
tags: { type: 'array', items: { type: 'string' } },
|
|
58
|
+
birthday: { type: 'string', format: 'date' },
|
|
57
59
|
},
|
|
58
|
-
additionalProperties: false,
|
|
59
60
|
required: %i(addresses email name),
|
|
60
61
|
'$defs': {
|
|
61
62
|
Address: {
|
|
@@ -68,7 +69,6 @@ RSpec.describe('User schema') do
|
|
|
68
69
|
street: { type: 'string' },
|
|
69
70
|
},
|
|
70
71
|
required: %i(city country street),
|
|
71
|
-
additionalProperties: false,
|
|
72
72
|
},
|
|
73
73
|
},
|
|
74
74
|
},
|
|
@@ -77,7 +77,12 @@ RSpec.describe('User schema') do
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
it('can instantiate a model') do
|
|
80
|
-
user = User.new(
|
|
80
|
+
user = User.new(
|
|
81
|
+
name: 'Foo',
|
|
82
|
+
email: 'foo@example.com',
|
|
83
|
+
addresses: [{ street: '123 Main St', city: 'Anytown' }],
|
|
84
|
+
birthday: '2000-01-01',
|
|
85
|
+
)
|
|
81
86
|
|
|
82
87
|
expect(user.name).to(eq('Foo'))
|
|
83
88
|
expect(user.email).to(eq('foo@example.com'))
|
|
@@ -88,5 +93,6 @@ RSpec.describe('User schema') do
|
|
|
88
93
|
expect(user.addresses.first.postal_code).to(be_nil)
|
|
89
94
|
expect(user.addresses.first.state).to(be_nil)
|
|
90
95
|
expect(user.addresses.first.city).to(eq('Anytown'))
|
|
96
|
+
expect(user.birthday).to(eq(Date.new(2000, 1, 1)))
|
|
91
97
|
end
|
|
92
98
|
end
|
data/spec/schema_meta_spec.rb
CHANGED
|
@@ -103,9 +103,9 @@ RSpec.describe(JsonModel::SchemaMeta) do
|
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
it('is
|
|
106
|
+
it('is missing by default') do
|
|
107
107
|
expect(klass.meta_attributes)
|
|
108
|
-
.to(eq({
|
|
108
|
+
.to(eq({}))
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
it('can be changed') do
|
|
@@ -115,4 +115,24 @@ RSpec.describe(JsonModel::SchemaMeta) do
|
|
|
115
115
|
.to(eq({ additionalProperties: true }))
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
|
+
|
|
119
|
+
describe('.additional_properties') do
|
|
120
|
+
let(:klass) do
|
|
121
|
+
Class.new do
|
|
122
|
+
include(JsonModel::SchemaMeta)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it('is missing by default') do
|
|
127
|
+
expect(klass.meta_attributes)
|
|
128
|
+
.to(eq({}))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it('can be changed') do
|
|
132
|
+
klass.unevaluated_properties(true)
|
|
133
|
+
|
|
134
|
+
expect(klass.meta_attributes)
|
|
135
|
+
.to(eq({ unevaluatedProperties: true }))
|
|
136
|
+
end
|
|
137
|
+
end
|
|
118
138
|
end
|
data/spec/schema_spec.rb
CHANGED
|
@@ -100,7 +100,7 @@ RSpec.describe(JsonModel::Schema) do
|
|
|
100
100
|
|
|
101
101
|
it('returns an empty schema') do
|
|
102
102
|
expect(klass.as_schema)
|
|
103
|
-
.to(eq({ type: 'object'
|
|
103
|
+
.to(eq({ type: 'object' }))
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
it('includes the schema id') do
|
|
@@ -112,7 +112,6 @@ RSpec.describe(JsonModel::Schema) do
|
|
|
112
112
|
{
|
|
113
113
|
'$id': 'https://example.com/schemas/example.json',
|
|
114
114
|
type: 'object',
|
|
115
|
-
additionalProperties: false,
|
|
116
115
|
},
|
|
117
116
|
),
|
|
118
117
|
)
|
|
@@ -145,7 +144,6 @@ RSpec.describe(JsonModel::Schema) do
|
|
|
145
144
|
foo: { type: 'string' },
|
|
146
145
|
},
|
|
147
146
|
required: %i(bam baz foo),
|
|
148
|
-
additionalProperties: false,
|
|
149
147
|
},
|
|
150
148
|
),
|
|
151
149
|
)
|
|
@@ -206,16 +204,13 @@ RSpec.describe(JsonModel::Schema) do
|
|
|
206
204
|
type: 'object',
|
|
207
205
|
properties: { foo: { type: 'string' } },
|
|
208
206
|
required: %i(foo),
|
|
209
|
-
additionalProperties: false,
|
|
210
207
|
},
|
|
211
208
|
},
|
|
212
|
-
additionalProperties: false,
|
|
213
209
|
'$defs': {
|
|
214
210
|
Bar: {
|
|
215
211
|
type: 'object',
|
|
216
212
|
properties: { bar: { type: 'string' } },
|
|
217
213
|
required: %i(bar),
|
|
218
|
-
additionalProperties: false,
|
|
219
214
|
},
|
|
220
215
|
},
|
|
221
216
|
required: %i(bam bar foo),
|
|
@@ -250,7 +245,6 @@ RSpec.describe(JsonModel::Schema) do
|
|
|
250
245
|
'$id': 'https://example.com/schemas/child.json',
|
|
251
246
|
'$ref': 'https://example.com/schemas/example.json',
|
|
252
247
|
type: 'object',
|
|
253
|
-
additionalProperties: false,
|
|
254
248
|
properties: { baz: { type: 'string' } },
|
|
255
249
|
required: %i(baz),
|
|
256
250
|
},
|
|
@@ -265,7 +259,6 @@ RSpec.describe(JsonModel::Schema) do
|
|
|
265
259
|
'$ref': 'https://example.com/schemas/example.json',
|
|
266
260
|
title: 'SecondChild',
|
|
267
261
|
type: 'object',
|
|
268
|
-
additionalProperties: false,
|
|
269
262
|
properties: { bar: { type: 'string' } },
|
|
270
263
|
required: %i(bar),
|
|
271
264
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require('spec_helper')
|
|
4
|
+
|
|
5
|
+
RSpec.describe(JsonModel::TypeSpec::Castable) do
|
|
6
|
+
describe('#as_schema') do
|
|
7
|
+
it('returns a string schema') do
|
|
8
|
+
expect(described_class.new(format: 'date') { |v| v }.as_schema)
|
|
9
|
+
.to(eq({ type: 'string', format: 'date' }))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe('#cast') do
|
|
14
|
+
it('casts a string') do
|
|
15
|
+
expect(described_class.new(format: 'date') { |v| Date.parse(v) }.cast('2020-01-01'))
|
|
16
|
+
.to(eq(Date.new(2020, 1, 1)))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json_model_rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paul Gillesberger
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-01-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rake
|
|
@@ -117,6 +117,7 @@ files:
|
|
|
117
117
|
- lib/json_model/schema_meta.rb
|
|
118
118
|
- lib/json_model/type_spec.rb
|
|
119
119
|
- lib/json_model/type_spec/array.rb
|
|
120
|
+
- lib/json_model/type_spec/castable.rb
|
|
120
121
|
- lib/json_model/type_spec/composition.rb
|
|
121
122
|
- lib/json_model/type_spec/composition/all_of.rb
|
|
122
123
|
- lib/json_model/type_spec/composition/any_of.rb
|
|
@@ -145,7 +146,6 @@ files:
|
|
|
145
146
|
- lib/json_model/types/string.rb
|
|
146
147
|
- lib/json_model/version.rb
|
|
147
148
|
- spec/config_spec.rb
|
|
148
|
-
- spec/examples/component_spec.rb
|
|
149
149
|
- spec/examples/file_system_spec.rb
|
|
150
150
|
- spec/examples/user_spec.rb
|
|
151
151
|
- spec/json_model_spec.rb
|
|
@@ -155,6 +155,7 @@ files:
|
|
|
155
155
|
- spec/schema_spec.rb
|
|
156
156
|
- spec/spec_helper.rb
|
|
157
157
|
- spec/type_spec/array_spec.rb
|
|
158
|
+
- spec/type_spec/castable_spec.rb
|
|
158
159
|
- spec/type_spec/composition/all_of_spec.rb
|
|
159
160
|
- spec/type_spec/composition/any_of_spec.rb
|
|
160
161
|
- spec/type_spec/composition/one_of_spec.rb
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require('spec_helper')
|
|
4
|
-
|
|
5
|
-
RSpec.describe('User schema') do
|
|
6
|
-
it('todo') do
|
|
7
|
-
stub_const(
|
|
8
|
-
'BaseComponent',
|
|
9
|
-
Class.new do
|
|
10
|
-
include(JsonModel::Schema)
|
|
11
|
-
|
|
12
|
-
property(:type, type: String)
|
|
13
|
-
property(:styles, type: T::Array[String], optional: true)
|
|
14
|
-
end,
|
|
15
|
-
)
|
|
16
|
-
stub_const(
|
|
17
|
-
'TextComponent',
|
|
18
|
-
Class.new(BaseComponent) do
|
|
19
|
-
include(JsonModel::Schema)
|
|
20
|
-
|
|
21
|
-
property(:type, type: T::Enum['text'])
|
|
22
|
-
property(:value, type: String)
|
|
23
|
-
end,
|
|
24
|
-
)
|
|
25
|
-
stub_const(
|
|
26
|
-
'NumberComponent',
|
|
27
|
-
Class.new(BaseComponent) do
|
|
28
|
-
include(JsonModel::Schema)
|
|
29
|
-
|
|
30
|
-
property(:type, type: T::Const['number'])
|
|
31
|
-
property(:value, type: Integer)
|
|
32
|
-
end,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
all_component_types = ObjectSpace
|
|
36
|
-
.each_object(Class)
|
|
37
|
-
.select { |klass| klass.ancestors.include?(BaseComponent) && klass != BaseComponent }
|
|
38
|
-
stub_const(
|
|
39
|
-
'GridComponent',
|
|
40
|
-
Class.new(BaseComponent) do
|
|
41
|
-
include(JsonModel::Schema)
|
|
42
|
-
|
|
43
|
-
property(:type, type: T::Const['grid'])
|
|
44
|
-
property(:components, type: T::Array[T::OneOf[*all_component_types]], optional: true, ref_mode: JsonModel::RefMode::EXTERNAL)
|
|
45
|
-
end,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
instance = GridComponent.new(
|
|
50
|
-
type: 'grid',
|
|
51
|
-
components: [{ type: 'text', value: 'foo' }, { type: 'number', value: 1 }],
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
expect(instance.components.first).to(be_instance_of(TextComponent))
|
|
55
|
-
expect(instance.components.last).to(be_instance_of(NumberComponent))
|
|
56
|
-
end
|
|
57
|
-
end
|