enum-x 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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