lutaml-model 0.3.24 → 0.3.25
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/.rubocop_todo.yml +35 -16
- data/README.adoc +274 -28
- data/lib/lutaml/model/attribute.rb +18 -8
- data/lib/lutaml/model/error/type_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
- data/lib/lutaml/model/error/validation_error.rb +0 -1
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/serialize.rb +6 -1
- data/lib/lutaml/model/type/boolean.rb +38 -0
- data/lib/lutaml/model/type/date.rb +35 -0
- data/lib/lutaml/model/type/date_time.rb +32 -4
- data/lib/lutaml/model/type/decimal.rb +42 -0
- data/lib/lutaml/model/type/float.rb +37 -0
- data/lib/lutaml/model/type/hash.rb +62 -0
- data/lib/lutaml/model/type/integer.rb +41 -0
- data/lib/lutaml/model/type/string.rb +49 -0
- data/lib/lutaml/model/type/time.rb +49 -0
- data/lib/lutaml/model/type/time_without_date.rb +37 -5
- data/lib/lutaml/model/type/value.rb +52 -0
- data/lib/lutaml/model/type.rb +50 -114
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
- data/lutaml-model.gemspec +1 -1
- data/spec/address_spec.rb +170 -0
- data/spec/fixtures/address.rb +33 -0
- data/spec/fixtures/person.rb +73 -0
- data/spec/fixtures/sample_model.rb +40 -0
- data/spec/fixtures/vase.rb +38 -0
- data/spec/fixtures/xml/special_char.xml +13 -0
- data/spec/lutaml/model/attribute_spec.rb +112 -0
- data/spec/lutaml/model/collection_spec.rb +299 -0
- data/spec/lutaml/model/comparable_model_spec.rb +106 -0
- data/spec/lutaml/model/custom_model_spec.rb +410 -0
- data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
- data/spec/lutaml/model/defaults_spec.rb +221 -0
- data/spec/lutaml/model/delegation_spec.rb +340 -0
- data/spec/lutaml/model/inheritance_spec.rb +92 -0
- data/spec/lutaml/model/json_adapter_spec.rb +37 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
- data/spec/lutaml/model/map_content_spec.rb +118 -0
- data/spec/lutaml/model/mixed_content_spec.rb +625 -0
- data/spec/lutaml/model/namespace_spec.rb +57 -0
- data/spec/lutaml/model/ordered_content_spec.rb +83 -0
- data/spec/lutaml/model/render_nil_spec.rb +138 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
- data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
- data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
- data/spec/lutaml/model/serializable_spec.rb +297 -0
- data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
- data/spec/lutaml/model/simple_model_spec.rb +314 -0
- data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
- data/spec/lutaml/model/type/boolean_spec.rb +54 -0
- data/spec/lutaml/model/type/date_spec.rb +118 -0
- data/spec/lutaml/model/type/date_time_spec.rb +127 -0
- data/spec/lutaml/model/type/decimal_spec.rb +125 -0
- data/spec/lutaml/model/type/float_spec.rb +191 -0
- data/spec/lutaml/model/type/hash_spec.rb +63 -0
- data/spec/lutaml/model/type/integer_spec.rb +145 -0
- data/spec/lutaml/model/type/string_spec.rb +150 -0
- data/spec/lutaml/model/type/time_spec.rb +142 -0
- data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
- data/spec/lutaml/model/type_spec.rb +276 -0
- data/spec/lutaml/model/utils_spec.rb +79 -0
- data/spec/lutaml/model/validation_spec.rb +83 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
- data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
- data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
- data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
- data/spec/lutaml/model_spec.rb +1 -0
- data/spec/person_spec.rb +161 -0
- data/spec/spec_helper.rb +33 -0
- metadata +66 -2
@@ -0,0 +1,299 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "lutaml/model"
|
3
|
+
|
4
|
+
module CollectionTests
|
5
|
+
class Pot < Lutaml::Model::Serializable
|
6
|
+
attribute :material, Lutaml::Model::Type::String
|
7
|
+
|
8
|
+
xml do
|
9
|
+
root "pot"
|
10
|
+
map_element "material", to: :material
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Kiln < Lutaml::Model::Serializable
|
15
|
+
attribute :brand, Lutaml::Model::Type::String
|
16
|
+
attribute :pots, Pot, collection: 0..2
|
17
|
+
attribute :temperatures, Lutaml::Model::Type::Integer, collection: true
|
18
|
+
attribute :operators, Lutaml::Model::Type::String, collection: (1..),
|
19
|
+
default: -> {
|
20
|
+
["Default Operator"]
|
21
|
+
}
|
22
|
+
attribute :sensors, Lutaml::Model::Type::String, collection: 1..3,
|
23
|
+
default: -> {
|
24
|
+
["Default Sensor"]
|
25
|
+
}
|
26
|
+
|
27
|
+
xml do
|
28
|
+
root "kiln"
|
29
|
+
map_attribute "brand", to: :brand
|
30
|
+
map_element "pot", to: :pots
|
31
|
+
map_element "temperature", to: :temperatures
|
32
|
+
map_element "operator", to: :operators
|
33
|
+
map_element "sensor", to: :sensors
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Address < Lutaml::Model::Serializable
|
38
|
+
attribute :street, :string
|
39
|
+
attribute :city, :string
|
40
|
+
attribute :address, Address
|
41
|
+
|
42
|
+
xml do
|
43
|
+
root "address"
|
44
|
+
map_element "street", to: :street
|
45
|
+
map_element "city", with: { from: :city_from_xml, to: :city_to_xml }
|
46
|
+
map_element "address", to: :address
|
47
|
+
end
|
48
|
+
|
49
|
+
def city_from_xml(model, node)
|
50
|
+
model.city = node
|
51
|
+
end
|
52
|
+
|
53
|
+
def city_to_xml(model, parent, doc)
|
54
|
+
doc.add_element(parent, "<city>#{model.city}</city>")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
RSpec.describe CollectionTests do
|
60
|
+
let(:pots) { [{ material: "clay" }, { material: "ceramic" }] }
|
61
|
+
let(:temperatures) { [1200, 1300, 1400] }
|
62
|
+
let(:operators) { ["John", "Jane"] }
|
63
|
+
let(:sensors) { ["Temp1", "Temp2"] }
|
64
|
+
let(:attributes) do
|
65
|
+
{
|
66
|
+
brand: "Skutt",
|
67
|
+
pots: pots,
|
68
|
+
temperatures: temperatures,
|
69
|
+
operators: operators,
|
70
|
+
sensors: sensors,
|
71
|
+
}
|
72
|
+
end
|
73
|
+
let(:model) { CollectionTests::Kiln.new(attributes) }
|
74
|
+
|
75
|
+
let(:model_xml) do
|
76
|
+
<<~XML
|
77
|
+
<kiln brand="Skutt">
|
78
|
+
<pot>
|
79
|
+
<material>clay</material>
|
80
|
+
</pot>
|
81
|
+
<pot>
|
82
|
+
<material>ceramic</material>
|
83
|
+
</pot>
|
84
|
+
<temperature>1200</temperature>
|
85
|
+
<temperature>1300</temperature>
|
86
|
+
<temperature>1400</temperature>
|
87
|
+
<operator>John</operator>
|
88
|
+
<operator>Jane</operator>
|
89
|
+
<sensor>Temp1</sensor>
|
90
|
+
<sensor>Temp2</sensor>
|
91
|
+
</kiln>
|
92
|
+
XML
|
93
|
+
end
|
94
|
+
|
95
|
+
it "initializes with default values" do
|
96
|
+
default_model = CollectionTests::Kiln.new
|
97
|
+
expect(default_model.brand).to be_nil
|
98
|
+
expect(default_model.pots).to eq([])
|
99
|
+
expect(default_model.temperatures).to eq([])
|
100
|
+
expect(default_model.operators).to eq(["Default Operator"])
|
101
|
+
expect(default_model.sensors).to eq(["Default Sensor"])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "serializes to XML" do
|
105
|
+
expected_xml = model_xml.strip
|
106
|
+
expect(model.to_xml.strip).to eq(expected_xml)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "deserializes from XML" do
|
110
|
+
sample = CollectionTests::Kiln.from_xml(model_xml)
|
111
|
+
expect(sample.brand).to eq("Skutt")
|
112
|
+
expect(sample.pots.size).to eq(2)
|
113
|
+
expect(sample.pots[0].material).to eq("clay")
|
114
|
+
expect(sample.pots[1].material).to eq("ceramic")
|
115
|
+
expect(sample.temperatures).to eq([1200, 1300, 1400])
|
116
|
+
expect(sample.operators).to eq(["John", "Jane"])
|
117
|
+
expect(sample.sensors).to eq(["Temp1", "Temp2"])
|
118
|
+
end
|
119
|
+
|
120
|
+
it "round-trips XML" do
|
121
|
+
xml = model.to_xml
|
122
|
+
new_model = CollectionTests::Kiln.from_xml(xml)
|
123
|
+
expect(new_model.brand).to eq(model.brand)
|
124
|
+
expect(new_model.pots.size).to eq(model.pots.size)
|
125
|
+
model.pots.each_with_index do |pot, index|
|
126
|
+
expect(new_model.pots[index].material).to eq(pot.material)
|
127
|
+
end
|
128
|
+
expect(new_model.temperatures).to eq(model.temperatures)
|
129
|
+
expect(new_model.operators).to eq(model.operators)
|
130
|
+
expect(new_model.sensors).to eq(model.sensors)
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when model contains self as attribute" do
|
134
|
+
let(:xml) do
|
135
|
+
<<~XML
|
136
|
+
<address>
|
137
|
+
<street>A</street>
|
138
|
+
<city>B</city>
|
139
|
+
<address>
|
140
|
+
<street>C</street>
|
141
|
+
<city>D</city>
|
142
|
+
</address>
|
143
|
+
</address>
|
144
|
+
XML
|
145
|
+
end
|
146
|
+
|
147
|
+
it "deserializes from XML" do
|
148
|
+
model = CollectionTests::Address.from_xml(xml)
|
149
|
+
|
150
|
+
expect(model.street).to eq("A")
|
151
|
+
expect(model.city).to eq("B")
|
152
|
+
expect(model.address.street).to eq("C")
|
153
|
+
expect(model.address.city).to eq("D")
|
154
|
+
end
|
155
|
+
|
156
|
+
it "round-trips XML" do
|
157
|
+
model = CollectionTests::Address.from_xml(xml)
|
158
|
+
|
159
|
+
expect(model.to_xml).to be_equivalent_to(xml)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when collection counts are below given ranges" do
|
164
|
+
let(:invalid_attributes) do
|
165
|
+
attributes.merge(operators: [], sensors: [])
|
166
|
+
end
|
167
|
+
|
168
|
+
it "raises ValidationError containing CollectionCountOutOfRangeError for operators" do
|
169
|
+
kiln = CollectionTests::Kiln.new(invalid_attributes)
|
170
|
+
expect do
|
171
|
+
kiln.validate!
|
172
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
173
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
174
|
+
expect(error.error_messages).to include(a_string_matching(/operators count is 0, must be at least 1/))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it "raises ValidationError containing CollectionCountOutOfRangeError for sensors" do
|
179
|
+
kiln = CollectionTests::Kiln.new(attributes.merge(sensors: []))
|
180
|
+
expect do
|
181
|
+
kiln.validate!
|
182
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
183
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
184
|
+
expect(error.error_messages).to include(a_string_matching(/sensors count is 0, must be between 1 and 3/))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when collection counts are below given ranges" do
|
190
|
+
let(:invalid_attributes) do
|
191
|
+
attributes.merge(operators: [], sensors: [])
|
192
|
+
end
|
193
|
+
|
194
|
+
it "raises CollectionCountOutOfRangeError" do
|
195
|
+
kiln = CollectionTests::Kiln.new(invalid_attributes)
|
196
|
+
expect do
|
197
|
+
kiln.validate!
|
198
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
199
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
200
|
+
expect(error.error_messages).to include(a_string_matching(/operators count is 0, must be at least 1/))
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context "when collection with unbounded maximum exceeds minimum" do
|
206
|
+
let(:valid_attributes) do
|
207
|
+
attributes.merge(operators: ["John", "Jane", "Jim", "Jessica"])
|
208
|
+
end
|
209
|
+
|
210
|
+
it "creates the model without errors" do
|
211
|
+
expect { CollectionTests::Kiln.new(valid_attributes) }.not_to raise_error
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when deserializing XML with invalid collection counts" do
|
216
|
+
let(:invalid_xml) do
|
217
|
+
<<~XML
|
218
|
+
<kiln brand="Skutt">
|
219
|
+
<pot>
|
220
|
+
<material>clay</material>
|
221
|
+
</pot>
|
222
|
+
<pot>
|
223
|
+
<material>ceramic</material>
|
224
|
+
</pot>
|
225
|
+
<pot>
|
226
|
+
<material>porcelain</material>
|
227
|
+
</pot>
|
228
|
+
<temperature>1200</temperature>
|
229
|
+
<operator>John</operator>
|
230
|
+
<sensor>Temp1</sensor>
|
231
|
+
<sensor>Temp2</sensor>
|
232
|
+
<sensor>Temp3</sensor>
|
233
|
+
<sensor>Temp4</sensor>
|
234
|
+
</kiln>
|
235
|
+
XML
|
236
|
+
end
|
237
|
+
|
238
|
+
it "raises ValidationError containing CollectionCountOutOfRangeError" do
|
239
|
+
expect do
|
240
|
+
CollectionTests::Kiln.from_xml(invalid_xml).validate!
|
241
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
242
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
243
|
+
expect(error.error_messages).to include(a_string_matching(/pots count is 3, must be between 0 and 2/))
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when specifying invalid collection ranges" do
|
249
|
+
it "raises an error for a range with only an upper bound" do
|
250
|
+
expect do
|
251
|
+
Class.new(Lutaml::Model::Serializable) do
|
252
|
+
attribute :invalid_range, Lutaml::Model::Type::String, collection: ..3
|
253
|
+
end
|
254
|
+
end.to raise_error(ArgumentError, /Invalid collection range/)
|
255
|
+
end
|
256
|
+
|
257
|
+
it "raises an error for a range where max is less than min" do
|
258
|
+
expect do
|
259
|
+
Class.new(Lutaml::Model::Serializable) do
|
260
|
+
attribute :invalid_range, Lutaml::Model::Type::String,
|
261
|
+
collection: 9..3
|
262
|
+
end
|
263
|
+
end.to raise_error(ArgumentError, /Invalid collection range/)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "raises an error for a negative range" do
|
267
|
+
expect do
|
268
|
+
Class.new(Lutaml::Model::Serializable) do
|
269
|
+
attribute :invalid_range, Lutaml::Model::Type::String,
|
270
|
+
collection: -2..1
|
271
|
+
end
|
272
|
+
end.to raise_error(ArgumentError, /Invalid collection range/)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "allows a range with only a lower bound" do
|
276
|
+
expect do
|
277
|
+
Class.new(Lutaml::Model::Serializable) do
|
278
|
+
attribute :valid_range, Lutaml::Model::Type::String, collection: 1..
|
279
|
+
end
|
280
|
+
end.not_to raise_error
|
281
|
+
end
|
282
|
+
|
283
|
+
it "allows a range with both lower and upper bounds" do
|
284
|
+
expect do
|
285
|
+
Class.new(Lutaml::Model::Serializable) do
|
286
|
+
attribute :valid_range, Lutaml::Model::Type::String, collection: 1..3
|
287
|
+
end
|
288
|
+
end.not_to raise_error
|
289
|
+
end
|
290
|
+
|
291
|
+
it "allows a range with zero as the lower bound" do
|
292
|
+
expect do
|
293
|
+
Class.new(Lutaml::Model::Serializable) do
|
294
|
+
attribute :valid_range, Lutaml::Model::Type::String, collection: 0..3
|
295
|
+
end
|
296
|
+
end.not_to raise_error
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
# Simple model with basic attributes
|
4
|
+
class ComparableGlaze < Lutaml::Model::Serializable
|
5
|
+
attribute :color, :string
|
6
|
+
attribute :temperature, :integer
|
7
|
+
attribute :food_safe, :boolean
|
8
|
+
end
|
9
|
+
|
10
|
+
# Model with a nested Serializable object
|
11
|
+
class ComparableCeramic < Lutaml::Model::Serializable
|
12
|
+
attribute :type, :string
|
13
|
+
attribute :glaze, ComparableGlaze
|
14
|
+
end
|
15
|
+
|
16
|
+
# Model with a deeply nested Serializable object
|
17
|
+
class ComparableCeramicCollection < Lutaml::Model::Serializable
|
18
|
+
attribute :name, :string
|
19
|
+
attribute :featured_piece, ComparableCeramic # This creates a two-level nesting
|
20
|
+
end
|
21
|
+
|
22
|
+
RSpec.describe Lutaml::Model::ComparableModel do
|
23
|
+
describe "comparisons" do
|
24
|
+
context "with simple types (Glaze)" do
|
25
|
+
it "compares equal objects with basic attributes" do
|
26
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
27
|
+
food_safe: true)
|
28
|
+
glaze2 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
29
|
+
food_safe: true)
|
30
|
+
expect(glaze1).to eq(glaze2)
|
31
|
+
expect(glaze1.hash).to eq(glaze2.hash)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "compares unequal objects with basic attributes" do
|
35
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
36
|
+
food_safe: true)
|
37
|
+
glaze2 = ComparableGlaze.new(color: "Red", temperature: 1000,
|
38
|
+
food_safe: false)
|
39
|
+
expect(glaze1).not_to eq(glaze2)
|
40
|
+
expect(glaze1.hash).not_to eq(glaze2.hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with nested Serializable objects (Ceramic)" do
|
45
|
+
it "compares equal objects with one level of nesting" do
|
46
|
+
# Here, we're comparing Ceramic objects that contain Glaze objects
|
47
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
48
|
+
food_safe: true)
|
49
|
+
glaze2 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
50
|
+
food_safe: true)
|
51
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
52
|
+
ceramic2 = ComparableCeramic.new(type: "Bowl", glaze: glaze2)
|
53
|
+
expect(ceramic1).to eq(ceramic2)
|
54
|
+
expect(ceramic1.hash).to eq(ceramic2.hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "compares unequal objects with one level of nesting" do
|
58
|
+
# Here, we're comparing Ceramic objects with different Glaze objects
|
59
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
60
|
+
food_safe: true)
|
61
|
+
glaze2 = ComparableGlaze.new(color: "Red", temperature: 1000,
|
62
|
+
food_safe: false)
|
63
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
64
|
+
ceramic2 = ComparableCeramic.new(type: "Plate", glaze: glaze2)
|
65
|
+
expect(ceramic1).not_to eq(ceramic2)
|
66
|
+
expect(ceramic1.hash).not_to eq(ceramic2.hash)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with deeply nested Serializable objects (CeramicCollection)" do
|
71
|
+
it "compares equal objects with two levels of nesting" do
|
72
|
+
# This test compares CeramicCollection objects that contain Ceramic objects,
|
73
|
+
# which in turn contain Glaze objects - a two-level deep nesting
|
74
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
75
|
+
food_safe: true)
|
76
|
+
glaze2 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
77
|
+
food_safe: true)
|
78
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
79
|
+
ceramic2 = ComparableCeramic.new(type: "Bowl", glaze: glaze2)
|
80
|
+
collection1 = ComparableCeramicCollection.new(name: "Blue Collection",
|
81
|
+
featured_piece: ceramic1)
|
82
|
+
collection2 = ComparableCeramicCollection.new(name: "Blue Collection",
|
83
|
+
featured_piece: ceramic2)
|
84
|
+
expect(collection1).to eq(collection2)
|
85
|
+
expect(collection1.hash).to eq(collection2.hash)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "compares unequal objects with two levels of nesting" do
|
89
|
+
# This test compares CeramicCollection objects that are different at every level:
|
90
|
+
# the collection name, the ceramic type, and the glaze properties
|
91
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
92
|
+
food_safe: true)
|
93
|
+
glaze2 = ComparableGlaze.new(color: "Red", temperature: 1000,
|
94
|
+
food_safe: false)
|
95
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
96
|
+
ceramic2 = ComparableCeramic.new(type: "Plate", glaze: glaze2)
|
97
|
+
collection1 = ComparableCeramicCollection.new(name: "Blue Collection",
|
98
|
+
featured_piece: ceramic1)
|
99
|
+
collection2 = ComparableCeramicCollection.new(name: "Red Collection",
|
100
|
+
featured_piece: ceramic2)
|
101
|
+
expect(collection1).not_to eq(collection2)
|
102
|
+
expect(collection1.hash).not_to eq(collection2.hash)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|