enum-x 1.0.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.
@@ -0,0 +1,345 @@
1
+ require 'spec_helper'
2
+ require 'active_model'
3
+
4
+ describe EnumX::DSL do
5
+
6
+ let(:klass) do
7
+ Class.new do
8
+ include ActiveModel::Validations; include EnumX::DSL
9
+ def self.model_name; ActiveModel::Name.new(self, nil, 'TestClass') end
10
+ end
11
+ end
12
+ let(:object) { klass.new }
13
+ let(:statuses) { EnumX.new(:statuses, %w[ new sent ]) }
14
+ let(:alternate_statuses) { EnumX.new(:statuses, %w[ new sent delivered ]) }
15
+ let(:roles) { EnumX.new(:roles, %w[ admin user ]) }
16
+
17
+ before do
18
+ allow(EnumX).to receive(:[]) { |name| case name.to_s when 'statuses' then statuses when 'alternate_statuses' then alternate_statuses else roles end }
19
+ allow(EnumX).to receive(:statuses) { statuses }
20
+ allow(EnumX).to receive(:alternate_statuses) { alternate_statuses }
21
+ allow(EnumX).to receive(:roles) { roles }
22
+ end
23
+
24
+ describe 'convenience accessors' do
25
+
26
+ it "should create a convenience accessor for the enum (single)" do
27
+ klass.class_eval { attr_accessor :status; enum :status }
28
+ expect(klass.statuses).to be(statuses)
29
+ end
30
+
31
+ it "should create a convenience accessor to an alternate enum if provided" do
32
+ klass.class_eval { attr_accessor :status; enum :status, :alternate_statuses }
33
+ expect(klass.statuses).to be(alternate_statuses)
34
+ end
35
+
36
+ it "should create a convience accessor to an ad-hoc enum" do
37
+ use_this_enum = EnumX.new(:alternative, %w[ one two three ])
38
+ klass.class_eval { attr_accessor :status; enum :status, use_this_enum }
39
+ expect(klass.statuses).to be(use_this_enum)
40
+ end
41
+
42
+ it "should create a convenience accessor for the enum flags" do
43
+ klass.class_eval { attr_accessor :roles; enum :roles, :flags => true }
44
+ expect(klass.roles).to be(roles)
45
+ end
46
+
47
+ it "should be accessible on a subclass as well" do
48
+ subclass = Class.new(klass)
49
+ klass.class_eval { attr_accessor :status; enum :status }
50
+ expect(subclass.statuses).to be(statuses)
51
+ end
52
+
53
+ end
54
+
55
+ describe "reader registration" do
56
+ context "when the class has an existing attribute_reader" do
57
+ before { klass.class_eval { attr_accessor :status; enum :status } }
58
+
59
+ it "should use the original reader upon reading" do
60
+ expect(object).to receive(:status_without_enums).and_return(:new)
61
+ expect(object.status).to be(EnumX.statuses[:new])
62
+ end
63
+ it "should call the original writer upon writing" do
64
+ expect(object).to receive(:status_without_enums=).with(EnumX.statuses[:new])
65
+ object.status = :new
66
+ end
67
+ end
68
+
69
+ context "when the class has a read_attribute and write_attribute method (ActiveRecord)" do
70
+ before do
71
+ klass.class_eval do
72
+ # These will be stubbed later.
73
+ def read_attribute(attribute) end
74
+ def write_attribute(attribute, value) end
75
+ enum :status
76
+ end
77
+ end
78
+
79
+ it "should use read_attribute upon reading" do
80
+ expect(object).to receive(:read_attribute).with(:status).and_return(:new)
81
+ expect(object.status).to be(EnumX.statuses[:new])
82
+ end
83
+ it "should call the original writer upon writing" do
84
+ expect(object).to receive(:write_attribute).with(:status, EnumX.statuses[:new])
85
+ object.status = :new
86
+ end
87
+ end
88
+
89
+ context "when the class has neither" do
90
+ it "should raise an error" do
91
+ expect{ klass.class_eval{ enum :status } }.to raise_error(/cannot overwrite/)
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "single enum" do
97
+
98
+ before do
99
+ klass.class_eval { attr_accessor :status; enum :status }
100
+ end
101
+
102
+ context "validation" do
103
+
104
+ it "should allow any value which is any of the enum values" do
105
+ object.status = :new
106
+ expect(object).to be_valid
107
+ end
108
+
109
+ it "shoult not allow any other value" do
110
+ object.status = :doesnotexist
111
+ expect(object).to_not be_valid
112
+ end
113
+
114
+ it "should allow a blank value by default" do
115
+ object.status = nil
116
+ expect(object).to be_valid
117
+ end
118
+
119
+ it "should not allow a blank value if :allow_blank => false" do
120
+ klass.class_eval { attr_accessor :status2; enum :status2, :status, :allow_blank => false }
121
+ object.status2 = nil
122
+ expect(object).to_not be_valid
123
+ end
124
+
125
+ end
126
+
127
+ context "reading" do
128
+
129
+ it "should convert a Symbol into an enum value" do
130
+ object.instance_variable_set('@status', :new)
131
+ expect(object.status).to be(EnumX.statuses[:new])
132
+ end
133
+
134
+ it "should convert a String into an enum value" do
135
+ object.instance_variable_set('@status', 'new')
136
+ expect(object.status).to be(EnumX.statuses[:new])
137
+ end
138
+
139
+ it "should keep any EnumX::Value instance" do
140
+ object.instance_variable_set('@status', EnumX.statuses[:new])
141
+ expect(object.status).to be(EnumX.statuses[:new])
142
+ end
143
+
144
+ it "should pass the original value if no corresponding enum value was found" do
145
+ object.instance_variable_set('@status', :doesnotexist)
146
+ expect(object.status).to be_a(Symbol)
147
+ expect(object.status).to be(:doesnotexist)
148
+ end
149
+
150
+ it "should pass a nil value through" do
151
+ object.instance_variable_set('@status', nil)
152
+ expect(object.status).to be_nil
153
+ end
154
+
155
+ end
156
+
157
+ context "writing" do
158
+
159
+ it "should convert a Symbol into an enum value" do
160
+ object.status = :new
161
+ expect(object.instance_variable_get('@status')).to be(EnumX.statuses[:new])
162
+ end
163
+
164
+ it "should convert a String into an enum value" do
165
+ object.status = 'new'
166
+ expect(object.instance_variable_get('@status')).to be(EnumX.statuses[:new])
167
+ end
168
+
169
+ it "should keep any EnumX::Value instance" do
170
+ object.status = EnumX.statuses[:new]
171
+ expect(object.instance_variable_get('@status')).to be(EnumX.statuses[:new])
172
+ end
173
+
174
+ it "should pass the original value if no corresponding enum value was found" do
175
+ object.status = :doesnotexist
176
+ expect(object.instance_variable_get('@status')).to be_a(Symbol)
177
+ expect(object.instance_variable_get('@status')).to be(:doesnotexist)
178
+ end
179
+
180
+ it "should pass a nil value through" do
181
+ object.status = nil
182
+ expect(object.instance_variable_get('@status')).to be_nil
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
189
+ describe "enum flags (multiple enum)" do
190
+
191
+ before do
192
+ klass.class_eval { attr_accessor :roles; enum :roles, :flags => true }
193
+ end
194
+
195
+ context "validation" do
196
+
197
+ it "should allow a single value" do
198
+ object.roles = :admin
199
+ expect(object).to be_valid
200
+ end
201
+
202
+ it "should allow an array with only valid values" do
203
+ object.roles = [ :admin, :user ]
204
+ expect(object).to be_valid
205
+ end
206
+
207
+ it "shoult not allow any invalid value to exist" do
208
+ object.roles = [ :admin, :doesnotexist ]
209
+ expect(object).to_not be_valid
210
+ end
211
+
212
+ it "should allow a nil value by default" do
213
+ object.roles = nil
214
+ expect(object).to be_valid
215
+ end
216
+
217
+ it "should allow an empty array value by default" do
218
+ object.roles = []
219
+ expect(object).to be_valid
220
+ end
221
+
222
+ it "should not allow a nil value or empty array if :allow_blank => false" do
223
+ klass.class_eval { attr_accessor :roles2; enum :roles2, :roles, :allow_blank => false }
224
+ object.roles2 = nil
225
+ expect(object).to_not be_valid
226
+
227
+ object.roles2 = []
228
+ expect(object).to_not be_valid
229
+ end
230
+
231
+ end
232
+
233
+ context "reading" do
234
+
235
+ it "should convert an array of Symbols and Strings into a value list" do
236
+ object.instance_variable_set('@roles', [ :admin, 'user' ])
237
+ expect(object.roles).to be_a(EnumX::ValueList)
238
+ expect(object.roles).to eq([ EnumX.roles[:admin], EnumX.roles[:user] ])
239
+ end
240
+
241
+ it "should convert a single Symbol into a value list" do
242
+ object.instance_variable_set('@roles', :admin)
243
+ expect(object.roles).to be_a(EnumX::ValueList)
244
+ expect(object.roles).to eq([ EnumX.roles[:admin] ])
245
+ end
246
+
247
+ it "should convert a single String into a value list" do
248
+ object.instance_variable_set('@roles', 'admin')
249
+ expect(object.roles).to be_a(EnumX::ValueList)
250
+ expect(object.roles).to eq([ EnumX.roles[:admin] ])
251
+ end
252
+
253
+ it "should convert a single EnumX::Value into a value list" do
254
+ object.instance_variable_set('@roles', EnumX.roles[:admin])
255
+ expect(object.roles).to be_a(EnumX::ValueList)
256
+ expect(object.roles).to eq([ EnumX.roles[:admin] ])
257
+ end
258
+
259
+ it "should keep any EnumX::ValueList instance" do
260
+ list = EnumX::ValueList.new(EnumX.roles, [ :admin, :user ])
261
+ object.instance_variable_set('@roles', list)
262
+ expect(object.roles).to be(list)
263
+ end
264
+
265
+ it "should keep invalid enum values as their originals" do
266
+ object.instance_variable_set('@roles', [ :admin, :doesnotexist ])
267
+ expect(object.roles).to be_a(EnumX::ValueList)
268
+ expect(object.roles).to eq([ EnumX.roles[:admin], :doesnotexist ])
269
+ end
270
+
271
+ it "should pass a nil value through" do
272
+ object.instance_variable_set('@roles', nil)
273
+ expect(object.roles).to be_nil
274
+ end
275
+
276
+ end
277
+
278
+ describe 'sifters' do
279
+
280
+ # Note - this doesn't check the syntactical correctness of the sifter. This is hard to test without
281
+ # depending on the correct workings of Squeel. They have been manually tested.
282
+ #
283
+ # Usage:
284
+ #
285
+ # klass.where{ sift(:roles2_include, 'admin') }.to_sql
286
+ # # => SELECT * FROM `<table>` WHERE `<table>`.`roles2` LIKE '%|admin|%'
287
+ it "should create a sifter <enum>_include" do
288
+ expect(klass).to receive(:sifter).with(:roles2_include)
289
+ expect(klass).to receive(:sifter).with(:roles2_exclude)
290
+ klass.class_eval { attr_accessor :roles2; enum :roles2, :flags => true }
291
+ end
292
+
293
+ end
294
+
295
+ end
296
+
297
+ describe "mnemonics" do
298
+
299
+ it "should create an mnemonic for each enum value (single enum)" do
300
+ klass.class_eval { attr_accessor :status; enum :status, :mnemonics => true }
301
+
302
+ object.status = :new
303
+ expect(object.new?).to eql(true)
304
+ expect(object.sent?).to eql(false)
305
+
306
+ object.status = :sent
307
+ expect(object.new?).to eql(false)
308
+ expect(object.sent?).to eql(true)
309
+ end
310
+
311
+ it "should create an mnemonic for each enum value (flags enum)" do
312
+ klass.class_eval { attr_accessor :roles; enum :roles, :flags => true, :mnemonics => true }
313
+
314
+ object.roles = []
315
+ expect(object.admin?).to eql(false)
316
+ expect(object.user?).to eql(false)
317
+
318
+ object.roles = [ :admin ]
319
+ expect(object.admin?).to eql(true)
320
+ expect(object.user?).to eql(false)
321
+
322
+ object.roles = [ :admin, :user ]
323
+ expect(object.admin?).to eql(true)
324
+ expect(object.user?).to eql(true)
325
+ end
326
+
327
+ end
328
+
329
+ describe EnumX::DSL::FlagsSerializer do
330
+ let(:serializer) { EnumX::DSL::FlagsSerializer.new(EnumX.roles) }
331
+
332
+ it "should load a pipe-separated string" do
333
+ list = serializer.load("|admin|user|doesnotexist|")
334
+ expect(list).to be_a(EnumX::ValueList)
335
+ expect(list).to eq([ EnumX.roles[:admin], EnumX.roles[:user], 'doesnotexist' ])
336
+ end
337
+
338
+ it "should dump a list to a pipe-separated string" do
339
+ list = EnumX::ValueList.new(EnumX.roles, [ :admin, :user ])
340
+ expect(serializer.dump(list)).to eql("|admin|user|")
341
+ expect(serializer.dump("|admin|user|")).to eql("|admin|user|")
342
+ end
343
+ end
344
+
345
+ end