pursuit 0.4.3 → 1.0.1
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/.github/workflows/rubygem.yaml +46 -0
- data/.ruby-version +1 -1
- data/Gemfile +15 -0
- data/Gemfile.lock +127 -86
- data/LICENSE +174 -21
- data/README.md +210 -27
- data/bin/console +10 -0
- data/config.ru +2 -3
- data/lib/pursuit/aggregate_modifier_not_found.rb +20 -0
- data/lib/pursuit/aggregate_modifier_required.rb +20 -0
- data/lib/pursuit/aggregate_modifiers_not_available.rb +13 -0
- data/lib/pursuit/attribute_not_found.rb +20 -0
- data/lib/pursuit/constants.rb +1 -1
- data/lib/pursuit/error.rb +7 -0
- data/lib/pursuit/predicate_parser.rb +181 -0
- data/lib/pursuit/predicate_search.rb +83 -0
- data/lib/pursuit/predicate_transform.rb +231 -0
- data/lib/pursuit/query_error.rb +7 -0
- data/lib/pursuit/simple_search.rb +64 -0
- data/lib/pursuit/term_parser.rb +44 -0
- data/lib/pursuit/term_search.rb +69 -0
- data/lib/pursuit/term_transform.rb +35 -0
- data/lib/pursuit.rb +19 -5
- data/pursuit.gemspec +5 -18
- data/spec/internal/app/models/application_record.rb +5 -0
- data/spec/internal/app/models/product.rb +25 -9
- data/spec/internal/app/models/product_category.rb +23 -1
- data/spec/internal/app/models/product_variation.rb +26 -1
- data/spec/lib/pursuit/predicate_parser_spec.rb +1604 -0
- data/spec/lib/pursuit/predicate_search_spec.rb +80 -0
- data/spec/lib/pursuit/predicate_transform_spec.rb +624 -0
- data/spec/lib/pursuit/simple_search_spec.rb +59 -0
- data/spec/lib/pursuit/term_parser_spec.rb +271 -0
- data/spec/lib/pursuit/term_search_spec.rb +71 -0
- data/spec/lib/pursuit/term_transform_spec.rb +105 -0
- data/spec/spec_helper.rb +2 -3
- data/travis/gemfiles/{5.2.gemfile → 7.1.gemfile} +2 -2
- metadata +38 -197
- data/.travis.yml +0 -25
- data/lib/pursuit/dsl.rb +0 -28
- data/lib/pursuit/railtie.rb +0 -13
- data/lib/pursuit/search.rb +0 -172
- data/lib/pursuit/search_options.rb +0 -86
- data/lib/pursuit/search_term_parser.rb +0 -46
- data/spec/lib/pursuit/dsl_spec.rb +0 -22
- data/spec/lib/pursuit/search_options_spec.rb +0 -146
- data/spec/lib/pursuit/search_spec.rb +0 -516
- data/spec/lib/pursuit/search_term_parser_spec.rb +0 -34
- data/travis/gemfiles/6.0.gemfile +0 -8
- data/travis/gemfiles/6.1.gemfile +0 -8
- data/travis/gemfiles/7.0.gemfile +0 -8
@@ -0,0 +1,1604 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Pursuit::PredicateParser do
|
4
|
+
subject(:parser) { described_class.new }
|
5
|
+
|
6
|
+
describe '#space' do
|
7
|
+
subject(:space) { parser.space }
|
8
|
+
|
9
|
+
context 'when parsing an empty value' do
|
10
|
+
subject(:parse) { space.parse('') }
|
11
|
+
|
12
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when parsing one or more spaces' do
|
16
|
+
subject(:parse) { space.parse(value) }
|
17
|
+
|
18
|
+
let(:value) { ' ' * rand(1..10) }
|
19
|
+
|
20
|
+
it 'is expected to eq the parsed spaces' do
|
21
|
+
expect(parse).to eq(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#space?' do
|
27
|
+
subject(:space?) { parser.space? }
|
28
|
+
|
29
|
+
context 'when parsing an empty value' do
|
30
|
+
subject(:parse) { space?.parse('') }
|
31
|
+
|
32
|
+
it 'is expected to eq the empty value' do
|
33
|
+
expect(parse).to eq('')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when parsing one or more spaces' do
|
38
|
+
subject(:parse) { space?.parse(value) }
|
39
|
+
|
40
|
+
let(:value) { ' ' * rand(1..10) }
|
41
|
+
|
42
|
+
it 'is expected to eq the parsed spaces' do
|
43
|
+
expect(parse).to eq(value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#boolean_true' do
|
49
|
+
subject(:boolean_true) { parser.boolean_true }
|
50
|
+
|
51
|
+
context 'when parsing "true"' do
|
52
|
+
subject(:parse) { boolean_true.parse('true') }
|
53
|
+
|
54
|
+
it 'is expected to capture the value as :truthy' do
|
55
|
+
expect(parse).to eq(truthy: 'true')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when parsing "True"' do
|
60
|
+
subject(:parse) { boolean_true.parse('True') }
|
61
|
+
|
62
|
+
it 'is expected to capture the value as :truthy' do
|
63
|
+
expect(parse).to eq(truthy: 'True')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when parsing "TRUE"' do
|
68
|
+
subject(:parse) { boolean_true.parse('TRUE') }
|
69
|
+
|
70
|
+
it 'is expected to capture the value as :truthy' do
|
71
|
+
expect(parse).to eq(truthy: 'TRUE')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when parsing "t"' do
|
76
|
+
subject(:parse) { boolean_true.parse('t') }
|
77
|
+
|
78
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when parsing "yes"' do
|
82
|
+
subject(:parse) { boolean_true.parse('yes') }
|
83
|
+
|
84
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when parsing "1"' do
|
88
|
+
subject(:parse) { boolean_true.parse('1') }
|
89
|
+
|
90
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#boolean_false' do
|
95
|
+
subject(:boolean_false) { parser.boolean_false }
|
96
|
+
|
97
|
+
context 'when parsing "false"' do
|
98
|
+
subject(:parse) { boolean_false.parse('false') }
|
99
|
+
|
100
|
+
it 'is expected to capture the value as :falsey' do
|
101
|
+
expect(parse).to eq(falsey: 'false')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when parsing "False"' do
|
106
|
+
subject(:parse) { boolean_false.parse('False') }
|
107
|
+
|
108
|
+
it 'is expected to capture the value as :falsey' do
|
109
|
+
expect(parse).to eq(falsey: 'False')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when parsing "FALSE"' do
|
114
|
+
subject(:parse) { boolean_false.parse('FALSE') }
|
115
|
+
|
116
|
+
it 'is expected to capture the value as :falsey' do
|
117
|
+
expect(parse).to eq(falsey: 'FALSE')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when parsing "f"' do
|
122
|
+
subject(:parse) { boolean_false.parse('f') }
|
123
|
+
|
124
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'when parsing "no"' do
|
128
|
+
subject(:parse) { boolean_false.parse('no') }
|
129
|
+
|
130
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'when parsing "0"' do
|
134
|
+
subject(:parse) { boolean_false.parse('0') }
|
135
|
+
|
136
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#boolean' do
|
141
|
+
subject(:boolean) { parser.boolean }
|
142
|
+
|
143
|
+
context 'when parsing "true"' do
|
144
|
+
subject(:parse) { boolean.parse('true') }
|
145
|
+
|
146
|
+
it 'is expected to capture the value as :truthy' do
|
147
|
+
expect(parse).to eq(truthy: 'true')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when parsing "false"' do
|
152
|
+
subject(:parse) { boolean.parse('false') }
|
153
|
+
|
154
|
+
it 'is expected to capture the value as :falsey' do
|
155
|
+
expect(parse).to eq(falsey: 'false')
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'when parsing a non-boolean value' do
|
160
|
+
subject(:parse) { boolean.parse('not-a-boolean') }
|
161
|
+
|
162
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#integer' do
|
167
|
+
subject(:integer) { parser.integer }
|
168
|
+
|
169
|
+
context 'when parsing one or more digits' do
|
170
|
+
subject(:parse) { integer.parse(value) }
|
171
|
+
|
172
|
+
let(:value) { rand(0..999_999).to_s }
|
173
|
+
|
174
|
+
it 'is expected to capture the value as :integer' do
|
175
|
+
expect(parse).to eq(integer: value)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'when parsing one or more digits prefixed with "+"' do
|
180
|
+
subject(:parse) { integer.parse(value) }
|
181
|
+
|
182
|
+
let(:value) { format('+%<value>d', value: rand(0..999_999)) }
|
183
|
+
|
184
|
+
it 'is expected to capture the value as :integer' do
|
185
|
+
expect(parse).to eq(integer: value)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'when parsing one or more digits prefixed with "-"' do
|
190
|
+
subject(:parse) { integer.parse(value) }
|
191
|
+
|
192
|
+
let(:value) { format('-%<value>d', value: rand(0..999_999)) }
|
193
|
+
|
194
|
+
it 'is expected to capture the value as :integer' do
|
195
|
+
expect(parse).to eq(integer: value)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'when parsing digits separated by "."' do
|
200
|
+
subject(:parse) { integer.parse(value) }
|
201
|
+
|
202
|
+
let(:value) { rand(10.0...11.0).to_s }
|
203
|
+
|
204
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'when parsing a non-numeric value' do
|
208
|
+
subject(:parse) { integer.parse('one') }
|
209
|
+
|
210
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe '#decimal' do
|
215
|
+
subject(:decimal) { parser.decimal }
|
216
|
+
|
217
|
+
context 'when parsing digits separated by "."' do
|
218
|
+
subject(:parse) { decimal.parse(value) }
|
219
|
+
|
220
|
+
let(:value) { rand(0.0...999_999.0).to_s }
|
221
|
+
|
222
|
+
it 'is expected to capture the value as :decimal' do
|
223
|
+
expect(parse).to eq(decimal: value)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'when parsing digits separated by "." and prefixed with "+"' do
|
228
|
+
subject(:parse) { decimal.parse(value) }
|
229
|
+
|
230
|
+
let(:value) { format('+%<value>f', value: rand(0.0...999_999.0)) }
|
231
|
+
|
232
|
+
it 'is expected to capture the value as :decimal' do
|
233
|
+
expect(parse).to eq(decimal: value)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context 'when parsing digits separated by "." and prefixed with "-"' do
|
238
|
+
subject(:parse) { decimal.parse(value) }
|
239
|
+
|
240
|
+
let(:value) { format('-%<value>f', value: rand(0.0...999_999.0)) }
|
241
|
+
|
242
|
+
it 'is expected to capture the value as :decimal' do
|
243
|
+
expect(parse).to eq(decimal: value)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'when parsing digits prefixed with "."' do
|
248
|
+
subject(:parse) { decimal.parse(value) }
|
249
|
+
|
250
|
+
let(:value) { format('.%<value>d', value: rand(0...10)) }
|
251
|
+
|
252
|
+
it 'is expected to capture the value as :decimal' do
|
253
|
+
expect(parse).to eq(decimal: value)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context 'when parsing digits prefixed with "+."' do
|
258
|
+
subject(:parse) { decimal.parse(value) }
|
259
|
+
|
260
|
+
let(:value) { format('+.%<value>d', value: rand(0...10)) }
|
261
|
+
|
262
|
+
it 'is expected to capture the value as :decimal' do
|
263
|
+
expect(parse).to eq(decimal: value)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'when parsing digits prefixed with "-."' do
|
268
|
+
subject(:parse) { decimal.parse(value) }
|
269
|
+
|
270
|
+
let(:value) { format('-.%<value>d', value: rand(0...10)) }
|
271
|
+
|
272
|
+
it 'is expected to capture the value as :decimal' do
|
273
|
+
expect(parse).to eq(decimal: value)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'when parsing digits suffixed with "."' do
|
278
|
+
subject(:parse) { decimal.parse(value) }
|
279
|
+
|
280
|
+
let(:value) { format('%<value>d.', value: rand(0...10)) }
|
281
|
+
|
282
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'when parsing digits separated by "." more than once' do
|
286
|
+
subject(:parse) { decimal.parse(value) }
|
287
|
+
|
288
|
+
let(:value) { format('%<a>d.%<b>d.%<c>d', a: rand(0...10), b: rand(0...10), c: rand(0...10)) }
|
289
|
+
|
290
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
291
|
+
end
|
292
|
+
|
293
|
+
context 'when parsing digits not separated by "."' do
|
294
|
+
subject(:parse) { decimal.parse(value) }
|
295
|
+
|
296
|
+
let(:value) { rand(0...10).to_s }
|
297
|
+
|
298
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
299
|
+
end
|
300
|
+
|
301
|
+
context 'when parsing a non-numeric value' do
|
302
|
+
subject(:parse) { decimal.parse('one') }
|
303
|
+
|
304
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe '#number' do
|
309
|
+
subject(:number) { parser.number }
|
310
|
+
|
311
|
+
context 'when parsing digits separated by "."' do
|
312
|
+
subject(:parse) { number.parse(value) }
|
313
|
+
|
314
|
+
let(:value) { rand(0.0...999_999.0).to_s }
|
315
|
+
|
316
|
+
it 'is expected to capture the value as :decimal' do
|
317
|
+
expect(parse).to eq(decimal: value)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
context 'when parsing digits not separated by "."' do
|
322
|
+
subject(:parse) { number.parse(value) }
|
323
|
+
|
324
|
+
let(:value) { rand(0..999_999).to_s }
|
325
|
+
|
326
|
+
it 'is expected to capture the value as :integer' do
|
327
|
+
expect(parse).to eq(integer: value)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
context 'when parsing a non-numeric value' do
|
332
|
+
subject(:parse) { number.parse('one') }
|
333
|
+
|
334
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe '#escaped_character' do
|
339
|
+
subject(:escaped_character) { parser.escaped_character }
|
340
|
+
|
341
|
+
context 'when parsing an escaped character' do
|
342
|
+
subject(:parse) { escaped_character.parse('\\"') }
|
343
|
+
|
344
|
+
it 'is expected to eq the escaped character' do
|
345
|
+
expect(parse).to eq('\\"')
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context 'when parsing an unescaped character' do
|
350
|
+
subject(:parse) { escaped_character.parse('"') }
|
351
|
+
|
352
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
describe '#string_double_quotes' do
|
357
|
+
subject(:string_double_quotes) { parser.string_double_quotes }
|
358
|
+
|
359
|
+
context 'when parsing a double quoted string' do
|
360
|
+
subject(:parse) { string_double_quotes.parse('"Double \"Quoted\""') }
|
361
|
+
|
362
|
+
it 'is expected to capture the contents of the double quotes as :string_double_quotes' do
|
363
|
+
expect(parse).to eq(string_double_quotes: 'Double \"Quoted\"')
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
context 'when parsing a single quoted string' do
|
368
|
+
subject(:parse) { string_double_quotes.parse("'Single Quoted'") }
|
369
|
+
|
370
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
371
|
+
end
|
372
|
+
|
373
|
+
context 'when parsing an unquoted string' do
|
374
|
+
subject(:parse) { string_double_quotes.parse('Unquoted') }
|
375
|
+
|
376
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
describe '#string_single_quotes' do
|
381
|
+
subject(:string_single_quotes) { parser.string_single_quotes }
|
382
|
+
|
383
|
+
context 'when parsing a single quoted string' do
|
384
|
+
subject(:parse) { string_single_quotes.parse("'Single \\'Quoted\\''") }
|
385
|
+
|
386
|
+
it 'is expected to capture the contents of the single quotes as :string_single_quotes' do
|
387
|
+
expect(parse).to eq(string_single_quotes: "Single \\'Quoted\\'")
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
context 'when parsing a double quoted string' do
|
392
|
+
subject(:parse) { string_single_quotes.parse('"Double Quoted"') }
|
393
|
+
|
394
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
395
|
+
end
|
396
|
+
|
397
|
+
context 'when parsing an unquoted string' do
|
398
|
+
subject(:parse) { string_single_quotes.parse('Unquoted') }
|
399
|
+
|
400
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
describe '#string_no_quotes' do
|
405
|
+
subject(:string_no_quotes) { parser.string_no_quotes }
|
406
|
+
|
407
|
+
context 'when parsing an unquoted string with no spaces or symbols' do
|
408
|
+
subject(:parse) { string_no_quotes.parse('Unquoted') }
|
409
|
+
|
410
|
+
it 'is expected to eq the parsed value' do
|
411
|
+
expect(parse).to eq(string_no_quotes: 'Unquoted')
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
context 'when parsing an unquoted string with spaces' do
|
416
|
+
subject(:parse) { string_no_quotes.parse('No Quotes') }
|
417
|
+
|
418
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
419
|
+
end
|
420
|
+
|
421
|
+
context 'when parsing an unquoted string with unsupported symbols' do
|
422
|
+
subject(:parse) { string_no_quotes.parse('No&Quotes') }
|
423
|
+
|
424
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
425
|
+
end
|
426
|
+
|
427
|
+
context 'when parsing an unquoted string with the "_" symbol' do
|
428
|
+
subject(:parse) { string_no_quotes.parse('no_quotes') }
|
429
|
+
|
430
|
+
it 'is expected to eq the parsed value' do
|
431
|
+
expect(parse).to eq(string_no_quotes: 'no_quotes')
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
context 'when parsing an unquoted string with the "!" symbol' do
|
436
|
+
subject(:parse) { string_no_quotes.parse('no!quotes') }
|
437
|
+
|
438
|
+
it 'is expected to eq the parsed value' do
|
439
|
+
expect(parse).to eq(string_no_quotes: 'no!quotes')
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
context 'when parsing an unquoted string with the "\'" character' do
|
444
|
+
subject(:parse) { string_no_quotes.parse("no'quotes") }
|
445
|
+
|
446
|
+
it 'is expected to eq the parsed value' do
|
447
|
+
expect(parse).to eq(string_no_quotes: "no'quotes")
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
context 'when parsing an unquoted string with the "+" character' do
|
452
|
+
subject(:parse) { string_no_quotes.parse('no+quotes') }
|
453
|
+
|
454
|
+
it 'is expected to eq the parsed value' do
|
455
|
+
expect(parse).to eq(string_no_quotes: 'no+quotes')
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
context 'when parsing an unquoted string with the "," character' do
|
460
|
+
subject(:parse) { string_no_quotes.parse('no,quotes') }
|
461
|
+
|
462
|
+
it 'is expected to eq the parsed value' do
|
463
|
+
expect(parse).to eq(string_no_quotes: 'no,quotes')
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
context 'when parsing an unquoted string with the "-" character' do
|
468
|
+
subject(:parse) { string_no_quotes.parse('no-quotes') }
|
469
|
+
|
470
|
+
it 'is expected to eq the parsed value' do
|
471
|
+
expect(parse).to eq(string_no_quotes: 'no-quotes')
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context 'when parsing an unquoted string with the "." character' do
|
476
|
+
subject(:parse) { string_no_quotes.parse('no.quotes') }
|
477
|
+
|
478
|
+
it 'is expected to eq the parsed value' do
|
479
|
+
expect(parse).to eq(string_no_quotes: 'no.quotes')
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context 'when parsing an unquoted string with the "/" character' do
|
484
|
+
subject(:parse) { string_no_quotes.parse('no/quotes') }
|
485
|
+
|
486
|
+
it 'is expected to eq the parsed value' do
|
487
|
+
expect(parse).to eq(string_no_quotes: 'no/quotes')
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context 'when parsing an unquoted string with the ":" character' do
|
492
|
+
subject(:parse) { string_no_quotes.parse('no:quotes') }
|
493
|
+
|
494
|
+
it 'is expected to eq the parsed value' do
|
495
|
+
expect(parse).to eq(string_no_quotes: 'no:quotes')
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context 'when parsing an unquoted string with the "?" character' do
|
500
|
+
subject(:parse) { string_no_quotes.parse('no?quotes') }
|
501
|
+
|
502
|
+
it 'is expected to eq the parsed value' do
|
503
|
+
expect(parse).to eq(string_no_quotes: 'no?quotes')
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
context 'when parsing an unquoted string with the "@" character' do
|
508
|
+
subject(:parse) { string_no_quotes.parse('no@quotes') }
|
509
|
+
|
510
|
+
it 'is expected to eq the parsed value' do
|
511
|
+
expect(parse).to eq(string_no_quotes: 'no@quotes')
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
describe '#string' do
|
517
|
+
subject(:string) { parser.string }
|
518
|
+
|
519
|
+
context 'when parsing a double quoted string' do
|
520
|
+
subject(:parse) { string.parse('"Double \"Quoted\""') }
|
521
|
+
|
522
|
+
it 'is expected to capture the contents of the double quotes as :string_double_quotes' do
|
523
|
+
expect(parse).to eq(string_double_quotes: 'Double \"Quoted\"')
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
context 'when parsing a single quoted string' do
|
528
|
+
subject(:parse) { string.parse("'Single \\'Quoted\\''") }
|
529
|
+
|
530
|
+
it 'is expected to capture the contents of the single quotes as :string_single_quotes' do
|
531
|
+
expect(parse).to eq(string_single_quotes: "Single \\'Quoted\\'")
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
context 'when parsing an unquoted string' do
|
536
|
+
subject(:parse) { string.parse('Unquoted') }
|
537
|
+
|
538
|
+
it 'is expected to eq the parsed value' do
|
539
|
+
expect(parse).to eq(string_no_quotes: 'Unquoted')
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
context 'when parsing an unquoted string with spaces' do
|
544
|
+
subject(:parse) { string.parse('No Quotes') }
|
545
|
+
|
546
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
547
|
+
end
|
548
|
+
|
549
|
+
context 'when parsing an unquoted string with symbols' do
|
550
|
+
subject(:parse) { string.parse('a@b.c') }
|
551
|
+
|
552
|
+
it 'is expected to eq the parsed value' do
|
553
|
+
expect(parse).to eq(string_no_quotes: 'a@b.c')
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
describe '#operator_equal' do
|
559
|
+
subject(:operator_equal) { parser.operator_equal }
|
560
|
+
|
561
|
+
context 'when parsing "="' do
|
562
|
+
subject(:parse) { operator_equal.parse('=') }
|
563
|
+
|
564
|
+
it 'is expected to eq the parsed value' do
|
565
|
+
expect(parse).to eq('=')
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
describe '#operator_not_equal' do
|
571
|
+
subject(:operator_not_equal) { parser.operator_not_equal }
|
572
|
+
|
573
|
+
context 'when parsing "!="' do
|
574
|
+
subject(:parse) { operator_not_equal.parse('!=') }
|
575
|
+
|
576
|
+
it 'is expected to eq the parsed value' do
|
577
|
+
expect(parse).to eq('!=')
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
describe '#operator_contains' do
|
583
|
+
subject(:operator_contains) { parser.operator_contains }
|
584
|
+
|
585
|
+
context 'when parsing "~"' do
|
586
|
+
subject(:parse) { operator_contains.parse('~') }
|
587
|
+
|
588
|
+
it 'is expected to eq the parsed value' do
|
589
|
+
expect(parse).to eq('~')
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
describe '#operator_not_contains' do
|
595
|
+
subject(:operator_not_contains) { parser.operator_not_contains }
|
596
|
+
|
597
|
+
context 'when parsing "!~"' do
|
598
|
+
subject(:parse) { operator_not_contains.parse('!~') }
|
599
|
+
|
600
|
+
it 'is expected to eq the parsed value' do
|
601
|
+
expect(parse).to eq('!~')
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
describe '#operator_less_than' do
|
607
|
+
subject(:operator_less_than) { parser.operator_less_than }
|
608
|
+
|
609
|
+
context 'when parsing "<"' do
|
610
|
+
subject(:parse) { operator_less_than.parse('<') }
|
611
|
+
|
612
|
+
it 'is expected to eq the parsed value' do
|
613
|
+
expect(parse).to eq('<')
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
describe '#operator_greater_than' do
|
619
|
+
subject(:operator_greater_than) { parser.operator_greater_than }
|
620
|
+
|
621
|
+
context 'when parsing ">"' do
|
622
|
+
subject(:parse) { operator_greater_than.parse('>') }
|
623
|
+
|
624
|
+
it 'is expected to eq the parsed value' do
|
625
|
+
expect(parse).to eq('>')
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
describe '#operator_less_than_or_equal_to' do
|
631
|
+
subject(:operator_less_than_or_equal_to) { parser.operator_less_than_or_equal_to }
|
632
|
+
|
633
|
+
context 'when parsing "<="' do
|
634
|
+
subject(:parse) { operator_less_than_or_equal_to.parse('<=') }
|
635
|
+
|
636
|
+
it 'is expected to eq the parsed value' do
|
637
|
+
expect(parse).to eq('<=')
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
describe '#operator_greater_than_or_equal_to' do
|
643
|
+
subject(:operator_greater_than_or_equal_to) { parser.operator_greater_than_or_equal_to }
|
644
|
+
|
645
|
+
context 'when parsing ">="' do
|
646
|
+
subject(:parse) { operator_greater_than_or_equal_to.parse('>=') }
|
647
|
+
|
648
|
+
it 'is expected to eq the parsed value' do
|
649
|
+
expect(parse).to eq('>=')
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
describe '#operator_and' do
|
655
|
+
subject(:operator_and) { parser.operator_and }
|
656
|
+
|
657
|
+
context 'when parsing "&"' do
|
658
|
+
subject(:parse) { operator_and.parse('&') }
|
659
|
+
|
660
|
+
it 'is expected to eq the parsed value' do
|
661
|
+
expect(parse).to eq('&')
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
describe '#operator_or' do
|
667
|
+
subject(:operator_or) { parser.operator_or }
|
668
|
+
|
669
|
+
context 'when parsing "|"' do
|
670
|
+
subject(:parse) { operator_or.parse('|') }
|
671
|
+
|
672
|
+
it 'is expected to eq the parsed value' do
|
673
|
+
expect(parse).to eq('|')
|
674
|
+
end
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
describe '#comparator' do
|
679
|
+
subject(:comparator) { parser.comparator }
|
680
|
+
|
681
|
+
context 'when parsing "="' do
|
682
|
+
subject(:parse) { comparator.parse('=') }
|
683
|
+
|
684
|
+
it 'is expected to capture the value as :operator' do
|
685
|
+
expect(parse).to eq(comparator: '=')
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
context 'when parsing "!="' do
|
690
|
+
subject(:parse) { comparator.parse('!=') }
|
691
|
+
|
692
|
+
it 'is expected to capture the value as :operator' do
|
693
|
+
expect(parse).to eq(comparator: '!=')
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
context 'when parsing "~"' do
|
698
|
+
subject(:parse) { comparator.parse('~') }
|
699
|
+
|
700
|
+
it 'is expected to capture the value as :operator' do
|
701
|
+
expect(parse).to eq(comparator: '~')
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
context 'when parsing "!~"' do
|
706
|
+
subject(:parse) { comparator.parse('!~') }
|
707
|
+
|
708
|
+
it 'is expected to capture the value as :operator' do
|
709
|
+
expect(parse).to eq(comparator: '!~')
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
context 'when parsing "<"' do
|
714
|
+
subject(:parse) { comparator.parse('<') }
|
715
|
+
|
716
|
+
it 'is expected to capture the value as :operator' do
|
717
|
+
expect(parse).to eq(comparator: '<')
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
context 'when parsing ">"' do
|
722
|
+
subject(:parse) { comparator.parse('>') }
|
723
|
+
|
724
|
+
it 'is expected to capture the value as :operator' do
|
725
|
+
expect(parse).to eq(comparator: '>')
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
context 'when parsing "<="' do
|
730
|
+
subject(:parse) { comparator.parse('<=') }
|
731
|
+
|
732
|
+
it 'is expected to capture the value as :operator' do
|
733
|
+
expect(parse).to eq(comparator: '<=')
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
context 'when parsing ">="' do
|
738
|
+
subject(:parse) { comparator.parse('>=') }
|
739
|
+
|
740
|
+
it 'is expected to capture the value as :operator' do
|
741
|
+
expect(parse).to eq(comparator: '>=')
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
context 'when parsing "&"' do
|
746
|
+
subject(:parse) { comparator.parse('&') }
|
747
|
+
|
748
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
749
|
+
end
|
750
|
+
|
751
|
+
context 'when parsing "|"' do
|
752
|
+
subject(:parse) { comparator.parse('|') }
|
753
|
+
|
754
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
describe '#joiner' do
|
759
|
+
subject(:joiner) { parser.joiner }
|
760
|
+
|
761
|
+
context 'when parsing "&"' do
|
762
|
+
subject(:parse) { joiner.parse('&') }
|
763
|
+
|
764
|
+
it 'is expected to capture the value as :join' do
|
765
|
+
expect(parse).to eq(joiner: '&')
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
context 'when parsing "|"' do
|
770
|
+
subject(:parse) { joiner.parse('|') }
|
771
|
+
|
772
|
+
it 'is expected to capture the value as :join' do
|
773
|
+
expect(parse).to eq(joiner: '|')
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
context 'when parsing "="' do
|
778
|
+
subject(:parse) { joiner.parse('=') }
|
779
|
+
|
780
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
781
|
+
end
|
782
|
+
|
783
|
+
context 'when parsing "!="' do
|
784
|
+
subject(:parse) { joiner.parse('!=') }
|
785
|
+
|
786
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
787
|
+
end
|
788
|
+
|
789
|
+
context 'when parsing "~"' do
|
790
|
+
subject(:parse) { joiner.parse('~') }
|
791
|
+
|
792
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
793
|
+
end
|
794
|
+
|
795
|
+
context 'when parsing "!~"' do
|
796
|
+
subject(:parse) { joiner.parse('!~') }
|
797
|
+
|
798
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
799
|
+
end
|
800
|
+
|
801
|
+
context 'when parsing "<"' do
|
802
|
+
subject(:parse) { joiner.parse('<') }
|
803
|
+
|
804
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
805
|
+
end
|
806
|
+
|
807
|
+
context 'when parsing ">"' do
|
808
|
+
subject(:parse) { joiner.parse('>') }
|
809
|
+
|
810
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
811
|
+
end
|
812
|
+
|
813
|
+
context 'when parsing "<="' do
|
814
|
+
subject(:parse) { joiner.parse('<=') }
|
815
|
+
|
816
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
817
|
+
end
|
818
|
+
|
819
|
+
context 'when parsing ">="' do
|
820
|
+
subject(:parse) { joiner.parse('>=') }
|
821
|
+
|
822
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
describe '#aggregate_modifier' do
|
827
|
+
subject(:aggregate_modifier) { parser.aggregate_modifier }
|
828
|
+
|
829
|
+
context 'when parsing "#"' do
|
830
|
+
subject(:parse) { aggregate_modifier.parse('#') }
|
831
|
+
|
832
|
+
it 'is expected to capture the value as :aggregate_modifier' do
|
833
|
+
expect(parse).to eq(aggregate_modifier: '#')
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
context 'when parsing "*"' do
|
838
|
+
subject(:parse) { aggregate_modifier.parse('*') }
|
839
|
+
|
840
|
+
it 'is expected to capture the value as :aggregate_modifier' do
|
841
|
+
expect(parse).to eq(aggregate_modifier: '*')
|
842
|
+
end
|
843
|
+
end
|
844
|
+
|
845
|
+
context 'when parsing "+"' do
|
846
|
+
subject(:parse) { aggregate_modifier.parse('+') }
|
847
|
+
|
848
|
+
it 'is expected to capture the value as :aggregate_modifier' do
|
849
|
+
expect(parse).to eq(aggregate_modifier: '+')
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
context 'when parsing "-"' do
|
854
|
+
subject(:parse) { aggregate_modifier.parse('-') }
|
855
|
+
|
856
|
+
it 'is expected to capture the value as :aggregate_modifier' do
|
857
|
+
expect(parse).to eq(aggregate_modifier: '-')
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
context 'when parsing "~"' do
|
862
|
+
subject(:parse) { aggregate_modifier.parse('~') }
|
863
|
+
|
864
|
+
it 'is expected to capture the value as :aggregate_modifier' do
|
865
|
+
expect(parse).to eq(aggregate_modifier: '~')
|
866
|
+
end
|
867
|
+
end
|
868
|
+
end
|
869
|
+
|
870
|
+
describe '#attribute' do
|
871
|
+
subject(:attribute) { parser.attribute }
|
872
|
+
|
873
|
+
context 'when parsing a double quoted string' do
|
874
|
+
subject(:parse) { attribute.parse('"First Name"') }
|
875
|
+
|
876
|
+
it 'is expected to capture the value as :attribute' do
|
877
|
+
expect(parse).to match(hash_including(attribute: { string_double_quotes: 'First Name' }))
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
context 'when parsing a single quoted string' do
|
882
|
+
subject(:parse) { attribute.parse("'First Name'") }
|
883
|
+
|
884
|
+
it 'is expected to capture the value as :attribute' do
|
885
|
+
expect(parse).to match(hash_including(attribute: { string_single_quotes: 'First Name' }))
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
context 'when parsing an unquoted string' do
|
890
|
+
subject(:parse) { attribute.parse('first_name') }
|
891
|
+
|
892
|
+
it 'is expected to capture the value as :attribute' do
|
893
|
+
expect(parse).to match(hash_including(attribute: { string_no_quotes: 'first_name' }))
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
897
|
+
context 'when parsing an unquoted string with spaces' do
|
898
|
+
subject(:parse) { attribute.parse('first name') }
|
899
|
+
|
900
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
describe '#value' do
|
905
|
+
subject(:value) { parser.value }
|
906
|
+
|
907
|
+
context 'when parsing a boolean' do
|
908
|
+
subject(:parse) { value.parse('true') }
|
909
|
+
|
910
|
+
it 'is expected to capture the value as :value' do
|
911
|
+
expect(parse).to eq(value: { truthy: 'true' })
|
912
|
+
end
|
913
|
+
end
|
914
|
+
|
915
|
+
context 'when parsing a whole number' do
|
916
|
+
subject(:parse) { value.parse('123') }
|
917
|
+
|
918
|
+
it 'is expected to capture the value as :value' do
|
919
|
+
expect(parse).to eq(value: { integer: '123' })
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
context 'when parsing a decimal number' do
|
924
|
+
subject(:parse) { value.parse('123.456') }
|
925
|
+
|
926
|
+
it 'is expected to capture the value as :value' do
|
927
|
+
expect(parse).to eq(value: { decimal: '123.456' })
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
context 'when parsing a double quoted string' do
|
932
|
+
subject(:parse) { value.parse('"Double Quoted"') }
|
933
|
+
|
934
|
+
it 'is expected to capture the value as :value' do
|
935
|
+
expect(parse).to eq(value: { string_double_quotes: 'Double Quoted' })
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
context 'when parsing a single quoted string' do
|
940
|
+
subject(:parse) { value.parse("'Single Quoted'") }
|
941
|
+
|
942
|
+
it 'is expected to capture the value as :value' do
|
943
|
+
expect(parse).to eq(value: { string_single_quotes: 'Single Quoted' })
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
context 'when parsing an unquoted string' do
|
948
|
+
subject(:parse) { value.parse('Unquoted') }
|
949
|
+
|
950
|
+
it 'is expected to capture the value as :value' do
|
951
|
+
expect(parse).to eq(value: { string_no_quotes: 'Unquoted' })
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
context 'when parsing an unquoted string that starts with a number' do
|
956
|
+
subject(:parse) { value.parse('1a') }
|
957
|
+
|
958
|
+
it 'is expected to capture the value as :value' do
|
959
|
+
expect(parse).to eq(value: { string_no_quotes: '1a' })
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
context 'when parsing an unquoted string that starts with "true"' do
|
964
|
+
subject(:parse) { value.parse('trueness') }
|
965
|
+
|
966
|
+
it 'is expected to capture the value as :value' do
|
967
|
+
expect(parse).to eq(value: { string_no_quotes: 'trueness' })
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
context 'when parsing an unquoted string with spaces' do
|
972
|
+
subject(:parse) { value.parse('No Quotes') }
|
973
|
+
|
974
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
975
|
+
end
|
976
|
+
|
977
|
+
context 'when parsing an unquoted string with symbols' do
|
978
|
+
subject(:parse) { value.parse('a@b.c') }
|
979
|
+
|
980
|
+
it 'is expected to capture the value as :value' do
|
981
|
+
expect(parse).to eq(value: { string_no_quotes: 'a@b.c' })
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
describe '#comparison' do
|
987
|
+
subject(:comparison) { parser.comparison }
|
988
|
+
|
989
|
+
context 'when parsing a comparison without an aggregate modifier' do
|
990
|
+
subject(:parse) { comparison.parse('rating > 3') }
|
991
|
+
|
992
|
+
it 'is expected to capture the attribute as :attribute' do
|
993
|
+
expect(parse).to match(hash_including(attribute: { string_no_quotes: 'rating' }))
|
994
|
+
end
|
995
|
+
|
996
|
+
it 'is expected to capture the comparator as :comparator' do
|
997
|
+
expect(parse).to match(hash_including(comparator: '>'))
|
998
|
+
end
|
999
|
+
|
1000
|
+
it 'is expected to capture the value as :value' do
|
1001
|
+
expect(parse).to match(hash_including(value: { integer: '3' }))
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
context 'when parsing a comparison with an aggregate modifier' do
|
1006
|
+
subject(:parse) { comparison.parse('#variants >= 5') }
|
1007
|
+
|
1008
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
describe '#comparison_group' do
|
1013
|
+
subject(:comparison_group) { parser.comparison_group }
|
1014
|
+
|
1015
|
+
context 'when parsing a comparison with brackets' do
|
1016
|
+
subject(:parse) { comparison_group.parse('(title = Shirt)') }
|
1017
|
+
|
1018
|
+
it 'is expected to eq the comparison result' do
|
1019
|
+
expect(parse).to eq(
|
1020
|
+
attribute: { string_no_quotes: 'title' },
|
1021
|
+
comparator: '=',
|
1022
|
+
value: { string_no_quotes: 'Shirt' }
|
1023
|
+
)
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
context 'when parsing a nested group' do
|
1028
|
+
subject(:parse) { comparison_group.parse('((title = Shirt))') }
|
1029
|
+
|
1030
|
+
it 'is expected to eq the nested group result' do
|
1031
|
+
expect(parse).to eq(
|
1032
|
+
attribute: { string_no_quotes: 'title' },
|
1033
|
+
comparator: '=',
|
1034
|
+
value: { string_no_quotes: 'Shirt' }
|
1035
|
+
)
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
context 'when parsing a join' do
|
1040
|
+
subject(:parse) { comparison_group.parse('(title = Shirt | title = T-Shirt)') }
|
1041
|
+
|
1042
|
+
it 'is expected to eq the join result' do
|
1043
|
+
expect(parse).to eq(
|
1044
|
+
left: {
|
1045
|
+
attribute: { string_no_quotes: 'title' },
|
1046
|
+
comparator: '=',
|
1047
|
+
value: { string_no_quotes: 'Shirt' }
|
1048
|
+
},
|
1049
|
+
joiner: '|',
|
1050
|
+
right: {
|
1051
|
+
attribute: { string_no_quotes: 'title' },
|
1052
|
+
comparator: '=',
|
1053
|
+
value: { string_no_quotes: 'T-Shirt' }
|
1054
|
+
}
|
1055
|
+
)
|
1056
|
+
end
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
context 'when parsing a comparison without brackets' do
|
1060
|
+
subject(:parse) { comparison_group.parse('title = Shirt') }
|
1061
|
+
|
1062
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
1063
|
+
end
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
describe '#comparison_join' do
|
1067
|
+
subject(:comparison_join) { parser.comparison_join }
|
1068
|
+
|
1069
|
+
context 'when parsing a join of two comparisons' do
|
1070
|
+
subject(:parse) { comparison_join.parse('title = Shirt | title = T-Shirt') }
|
1071
|
+
|
1072
|
+
it 'is expected to capture the left comparison as :left' do
|
1073
|
+
expect(parse).to match(
|
1074
|
+
hash_including(
|
1075
|
+
left: {
|
1076
|
+
attribute: { string_no_quotes: 'title' },
|
1077
|
+
comparator: '=',
|
1078
|
+
value: { string_no_quotes: 'Shirt' }
|
1079
|
+
}
|
1080
|
+
)
|
1081
|
+
)
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
it 'is expected to capture the joiner as :joiner' do
|
1085
|
+
expect(parse).to match(hash_including(joiner: '|'))
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
it 'is expected to capture the right comparison as :right' do
|
1089
|
+
expect(parse).to match(
|
1090
|
+
hash_including(
|
1091
|
+
right: {
|
1092
|
+
attribute: { string_no_quotes: 'title' },
|
1093
|
+
comparator: '=',
|
1094
|
+
value: { string_no_quotes: 'T-Shirt' }
|
1095
|
+
}
|
1096
|
+
)
|
1097
|
+
)
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
context 'when parsing a join of a comparison to another join' do
|
1102
|
+
subject(:parse) { comparison_join.parse('rating >= 3 & title = Shirt | title = T-Shirt') }
|
1103
|
+
|
1104
|
+
it 'is expected to capture the left comparison as :left' do
|
1105
|
+
expect(parse).to match(
|
1106
|
+
hash_including(
|
1107
|
+
left: {
|
1108
|
+
attribute: { string_no_quotes: 'rating' },
|
1109
|
+
comparator: '>=',
|
1110
|
+
value: { integer: '3' }
|
1111
|
+
}
|
1112
|
+
)
|
1113
|
+
)
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
it 'is expected to capture the joiner as :joiner' do
|
1117
|
+
expect(parse).to match(hash_including(joiner: '&'))
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
it 'is expected to capture the right join as :right' do
|
1121
|
+
expect(parse).to match(
|
1122
|
+
hash_including(
|
1123
|
+
right: {
|
1124
|
+
left: {
|
1125
|
+
attribute: { string_no_quotes: 'title' },
|
1126
|
+
comparator: '=',
|
1127
|
+
value: { string_no_quotes: 'Shirt' }
|
1128
|
+
},
|
1129
|
+
joiner: '|',
|
1130
|
+
right: {
|
1131
|
+
attribute: { string_no_quotes: 'title' },
|
1132
|
+
comparator: '=',
|
1133
|
+
value: { string_no_quotes: 'T-Shirt' }
|
1134
|
+
}
|
1135
|
+
}
|
1136
|
+
)
|
1137
|
+
)
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
describe '#comparison_node' do
|
1143
|
+
subject(:comparison_node) { parser.comparison_node }
|
1144
|
+
|
1145
|
+
context 'when parsing a comparison' do
|
1146
|
+
subject(:parse) { comparison_node.parse('rating >= 3') }
|
1147
|
+
|
1148
|
+
it 'is expected to eq the comparison result' do
|
1149
|
+
expect(parse).to match(
|
1150
|
+
hash_including(
|
1151
|
+
attribute: { string_no_quotes: 'rating' },
|
1152
|
+
comparator: '>=',
|
1153
|
+
value: { integer: '3' }
|
1154
|
+
)
|
1155
|
+
)
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
context 'when parsing a join' do
|
1160
|
+
subject(:parse) { comparison_node.parse('title = Shirt | title = T-Shirt') }
|
1161
|
+
|
1162
|
+
it 'is expected to eq the join result' do
|
1163
|
+
expect(parse).to match(
|
1164
|
+
hash_including(
|
1165
|
+
left: {
|
1166
|
+
attribute: { string_no_quotes: 'title' },
|
1167
|
+
comparator: '=',
|
1168
|
+
value: { string_no_quotes: 'Shirt' }
|
1169
|
+
},
|
1170
|
+
joiner: '|',
|
1171
|
+
right: {
|
1172
|
+
attribute: { string_no_quotes: 'title' },
|
1173
|
+
comparator: '=',
|
1174
|
+
value: { string_no_quotes: 'T-Shirt' }
|
1175
|
+
}
|
1176
|
+
)
|
1177
|
+
)
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
context 'when parsing a group' do
|
1182
|
+
subject(:parse) { comparison_node.parse('(title = Shirt)') }
|
1183
|
+
|
1184
|
+
it 'is expected to eq the group result' do
|
1185
|
+
expect(parse).to match(
|
1186
|
+
hash_including(
|
1187
|
+
attribute: { string_no_quotes: 'title' },
|
1188
|
+
comparator: '=',
|
1189
|
+
value: { string_no_quotes: 'Shirt' }
|
1190
|
+
)
|
1191
|
+
)
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
describe '#aggregate_comparison' do
|
1197
|
+
subject(:aggregate_comparison) { parser.aggregate_comparison }
|
1198
|
+
|
1199
|
+
context 'when parsing a comparison with an aggregate modifier' do
|
1200
|
+
subject(:parse) { aggregate_comparison.parse('#variations >= 5') }
|
1201
|
+
|
1202
|
+
it 'is expected to capture the aggregate modifier as :aggregate_modifier' do
|
1203
|
+
expect(parse).to match(hash_including(aggregate_modifier: '#'))
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
it 'is expected to capture the attribute as :attribute' do
|
1207
|
+
expect(parse).to match(hash_including(attribute: { string_no_quotes: 'variations' }))
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
it 'is expected to capture the comparator as :comparator' do
|
1211
|
+
expect(parse).to match(hash_including(comparator: '>='))
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
it 'is expected to capture the value as :value' do
|
1215
|
+
expect(parse).to match(hash_including(value: { integer: '5' }))
|
1216
|
+
end
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
context 'when parsing a comparison without an aggregate modifier' do
|
1220
|
+
subject(:parse) { aggregate_comparison.parse('rating > 3') }
|
1221
|
+
|
1222
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
describe '#aggregate_comparison_group' do
|
1227
|
+
subject(:aggregate_comparison_group) { parser.aggregate_comparison_group }
|
1228
|
+
|
1229
|
+
context 'when parsing an aggregate comparison with brackets' do
|
1230
|
+
subject(:parse) { aggregate_comparison_group.parse('(#variations >= 5)') }
|
1231
|
+
|
1232
|
+
it 'is expected to eq the comparison result' do
|
1233
|
+
expect(parse).to eq(
|
1234
|
+
aggregate_modifier: '#',
|
1235
|
+
attribute: { string_no_quotes: 'variations' },
|
1236
|
+
comparator: '>=',
|
1237
|
+
value: { integer: '5' }
|
1238
|
+
)
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
context 'when parsing a nested group' do
|
1243
|
+
subject(:parse) { aggregate_comparison_group.parse('((#variations >= 5))') }
|
1244
|
+
|
1245
|
+
it 'is expected to eq the nested comparison group result' do
|
1246
|
+
expect(parse).to eq(
|
1247
|
+
aggregate_modifier: '#',
|
1248
|
+
attribute: { string_no_quotes: 'variations' },
|
1249
|
+
comparator: '>=',
|
1250
|
+
value: { integer: '5' }
|
1251
|
+
)
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
context 'when parsing a join' do
|
1256
|
+
subject(:parse) { aggregate_comparison_group.parse('(#variations >= 5 | #variations = 1)') }
|
1257
|
+
|
1258
|
+
it 'is expected to eq the aggregate comparison join result' do
|
1259
|
+
expect(parse).to eq(
|
1260
|
+
left: {
|
1261
|
+
aggregate_modifier: '#',
|
1262
|
+
attribute: { string_no_quotes: 'variations' },
|
1263
|
+
comparator: '>=',
|
1264
|
+
value: { integer: '5' }
|
1265
|
+
},
|
1266
|
+
joiner: '|',
|
1267
|
+
right: {
|
1268
|
+
aggregate_modifier: '#',
|
1269
|
+
attribute: { string_no_quotes: 'variations' },
|
1270
|
+
comparator: '=',
|
1271
|
+
value: { integer: '1' }
|
1272
|
+
}
|
1273
|
+
)
|
1274
|
+
end
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
context 'when parsing an aggregate comparison without brackets' do
|
1278
|
+
subject(:parse) { aggregate_comparison_group.parse('#variations >= 5') }
|
1279
|
+
|
1280
|
+
it { expect { parse }.to raise_exception(Parslet::ParseFailed) }
|
1281
|
+
end
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
describe '#aggregate_comparison_join' do
|
1285
|
+
subject(:aggregate_comparison_join) { parser.aggregate_comparison_join }
|
1286
|
+
|
1287
|
+
context 'when parsing a join of two aggregate comparisons' do
|
1288
|
+
subject(:parse) { aggregate_comparison_join.parse('#variations >= 5 | #variations = 1') }
|
1289
|
+
|
1290
|
+
it 'is expected to capture the left comparison as :left' do
|
1291
|
+
expect(parse).to match(
|
1292
|
+
hash_including(
|
1293
|
+
left: {
|
1294
|
+
aggregate_modifier: '#',
|
1295
|
+
attribute: { string_no_quotes: 'variations' },
|
1296
|
+
comparator: '>=',
|
1297
|
+
value: { integer: '5' }
|
1298
|
+
}
|
1299
|
+
)
|
1300
|
+
)
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
it 'is expected to capture the joiner as :joiner' do
|
1304
|
+
expect(parse).to match(hash_including(joiner: '|'))
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
it 'is expected to capture the right comparison as :right' do
|
1308
|
+
expect(parse).to match(
|
1309
|
+
hash_including(
|
1310
|
+
right: {
|
1311
|
+
aggregate_modifier: '#',
|
1312
|
+
attribute: { string_no_quotes: 'variations' },
|
1313
|
+
comparator: '=',
|
1314
|
+
value: { integer: '1' }
|
1315
|
+
}
|
1316
|
+
)
|
1317
|
+
)
|
1318
|
+
end
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
context 'when parsing a join of an aggregate comparison to another join' do
|
1322
|
+
subject(:parse) { aggregate_comparison_join.parse('*views > 100 & #variations >= 5 | #variations = 1') }
|
1323
|
+
|
1324
|
+
it 'is expected to capture the left aggregate comparison as :left' do
|
1325
|
+
expect(parse).to match(
|
1326
|
+
hash_including(
|
1327
|
+
left: {
|
1328
|
+
aggregate_modifier: '*',
|
1329
|
+
attribute: { string_no_quotes: 'views' },
|
1330
|
+
comparator: '>',
|
1331
|
+
value: { integer: '100' }
|
1332
|
+
}
|
1333
|
+
)
|
1334
|
+
)
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
it 'is expected to capture the joiner as :joiner' do
|
1338
|
+
expect(parse).to match(hash_including(joiner: '&'))
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
it 'is expected to capture the right join as :right' do
|
1342
|
+
expect(parse).to match(
|
1343
|
+
hash_including(
|
1344
|
+
right: {
|
1345
|
+
left: {
|
1346
|
+
aggregate_modifier: '#',
|
1347
|
+
attribute: { string_no_quotes: 'variations' },
|
1348
|
+
comparator: '>=',
|
1349
|
+
value: { integer: '5' }
|
1350
|
+
},
|
1351
|
+
joiner: '|',
|
1352
|
+
right: {
|
1353
|
+
aggregate_modifier: '#',
|
1354
|
+
attribute: { string_no_quotes: 'variations' },
|
1355
|
+
comparator: '=',
|
1356
|
+
value: { integer: '1' }
|
1357
|
+
}
|
1358
|
+
}
|
1359
|
+
)
|
1360
|
+
)
|
1361
|
+
end
|
1362
|
+
end
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
describe '#aggregate_comparison_node' do
|
1366
|
+
subject(:aggregate_comparison_node) { parser.aggregate_comparison_node }
|
1367
|
+
|
1368
|
+
context 'when parsing an aggregate comparison' do
|
1369
|
+
subject(:parse) { aggregate_comparison_node.parse('#variations >= 5') }
|
1370
|
+
|
1371
|
+
it 'is expected to eq the aggregate comparison result' do
|
1372
|
+
expect(parse).to match(
|
1373
|
+
hash_including(
|
1374
|
+
aggregate_modifier: '#',
|
1375
|
+
attribute: { string_no_quotes: 'variations' },
|
1376
|
+
comparator: '>=',
|
1377
|
+
value: { integer: '5' }
|
1378
|
+
)
|
1379
|
+
)
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
context 'when parsing a join' do
|
1384
|
+
subject(:parse) { aggregate_comparison_node.parse('#variations >= 5 | #variations = 1') }
|
1385
|
+
|
1386
|
+
it 'is expected to eq the join result' do
|
1387
|
+
expect(parse).to match(
|
1388
|
+
hash_including(
|
1389
|
+
left: {
|
1390
|
+
aggregate_modifier: '#',
|
1391
|
+
attribute: { string_no_quotes: 'variations' },
|
1392
|
+
comparator: '>=',
|
1393
|
+
value: { integer: '5' }
|
1394
|
+
},
|
1395
|
+
joiner: '|',
|
1396
|
+
right: {
|
1397
|
+
aggregate_modifier: '#',
|
1398
|
+
attribute: { string_no_quotes: 'variations' },
|
1399
|
+
comparator: '=',
|
1400
|
+
value: { integer: '1' }
|
1401
|
+
}
|
1402
|
+
)
|
1403
|
+
)
|
1404
|
+
end
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
context 'when parsing a group' do
|
1408
|
+
subject(:parse) { aggregate_comparison_node.parse('(#variations >= 5)') }
|
1409
|
+
|
1410
|
+
it 'is expected to eq the group result' do
|
1411
|
+
expect(parse).to match(
|
1412
|
+
hash_including(
|
1413
|
+
aggregate_modifier: '#',
|
1414
|
+
attribute: { string_no_quotes: 'variations' },
|
1415
|
+
comparator: '>=',
|
1416
|
+
value: { integer: '5' }
|
1417
|
+
)
|
1418
|
+
)
|
1419
|
+
end
|
1420
|
+
end
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
describe '#predicate_where' do
|
1424
|
+
subject(:predicate_where) { parser.predicate_where }
|
1425
|
+
|
1426
|
+
context 'when parsing a comparison' do
|
1427
|
+
subject(:parse) { predicate_where.parse('title = Shirt') }
|
1428
|
+
|
1429
|
+
it 'is expected to capture the comparison as :where' do
|
1430
|
+
expect(parse).to match(
|
1431
|
+
hash_including(
|
1432
|
+
where: {
|
1433
|
+
attribute: { string_no_quotes: 'title' },
|
1434
|
+
comparator: '=',
|
1435
|
+
value: { string_no_quotes: 'Shirt' }
|
1436
|
+
}
|
1437
|
+
)
|
1438
|
+
)
|
1439
|
+
end
|
1440
|
+
end
|
1441
|
+
end
|
1442
|
+
|
1443
|
+
describe '#predicate_having' do
|
1444
|
+
subject(:predicate_having) { parser.predicate_having }
|
1445
|
+
|
1446
|
+
context 'when parsing an aggregate comparison' do
|
1447
|
+
subject(:parse) { predicate_having.parse('#variations >= 5') }
|
1448
|
+
|
1449
|
+
it 'is expected to capture the aggregate comparison as :having' do
|
1450
|
+
expect(parse).to match(
|
1451
|
+
hash_including(
|
1452
|
+
having: {
|
1453
|
+
aggregate_modifier: '#',
|
1454
|
+
attribute: { string_no_quotes: 'variations' },
|
1455
|
+
comparator: '>=',
|
1456
|
+
value: { integer: '5' }
|
1457
|
+
}
|
1458
|
+
)
|
1459
|
+
)
|
1460
|
+
end
|
1461
|
+
end
|
1462
|
+
end
|
1463
|
+
|
1464
|
+
describe '#predicate' do
|
1465
|
+
subject(:predicate) { parser.predicate }
|
1466
|
+
|
1467
|
+
context 'when parsing a comparison' do
|
1468
|
+
subject(:parse) { predicate.parse('title = Shirt') }
|
1469
|
+
|
1470
|
+
it 'is expected to capture the comparison as :where' do
|
1471
|
+
expect(parse).to match(
|
1472
|
+
hash_including(
|
1473
|
+
where: {
|
1474
|
+
attribute: { string_no_quotes: 'title' },
|
1475
|
+
comparator: '=',
|
1476
|
+
value: { string_no_quotes: 'Shirt' }
|
1477
|
+
}
|
1478
|
+
)
|
1479
|
+
)
|
1480
|
+
end
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
context 'when parsing an aggregate comparison' do
|
1484
|
+
subject(:parse) { predicate.parse('#variations >= 5') }
|
1485
|
+
|
1486
|
+
it 'is expected to capture the aggregate comparison as :having' do
|
1487
|
+
expect(parse).to match(
|
1488
|
+
hash_including(
|
1489
|
+
having: {
|
1490
|
+
aggregate_modifier: '#',
|
1491
|
+
attribute: { string_no_quotes: 'variations' },
|
1492
|
+
comparator: '>=',
|
1493
|
+
value: { integer: '5' }
|
1494
|
+
}
|
1495
|
+
)
|
1496
|
+
)
|
1497
|
+
end
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
context 'when parsing a comparison followed by an aggregate comparison' do
|
1501
|
+
subject(:parse) { predicate.parse('title = Shirt & #variations >= 5') }
|
1502
|
+
|
1503
|
+
it 'is expected to capture the comparison as :where' do
|
1504
|
+
expect(parse).to match(
|
1505
|
+
hash_including(
|
1506
|
+
where: {
|
1507
|
+
attribute: { string_no_quotes: 'title' },
|
1508
|
+
comparator: '=',
|
1509
|
+
value: { string_no_quotes: 'Shirt' }
|
1510
|
+
}
|
1511
|
+
)
|
1512
|
+
)
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
it 'is expected to capture the aggregate comparison as :having' do
|
1516
|
+
expect(parse).to match(
|
1517
|
+
hash_including(
|
1518
|
+
having: {
|
1519
|
+
aggregate_modifier: '#',
|
1520
|
+
attribute: { string_no_quotes: 'variations' },
|
1521
|
+
comparator: '>=',
|
1522
|
+
value: { integer: '5' }
|
1523
|
+
}
|
1524
|
+
)
|
1525
|
+
)
|
1526
|
+
end
|
1527
|
+
end
|
1528
|
+
|
1529
|
+
context 'when parsing an aggregate comparison followed by a comparison' do
|
1530
|
+
subject(:parse) { predicate.parse('#variations >= 5 & title = Shirt') }
|
1531
|
+
|
1532
|
+
it 'is expected to capture the comparison as :where' do
|
1533
|
+
expect(parse).to match(
|
1534
|
+
hash_including(
|
1535
|
+
where: {
|
1536
|
+
attribute: { string_no_quotes: 'title' },
|
1537
|
+
comparator: '=',
|
1538
|
+
value: { string_no_quotes: 'Shirt' }
|
1539
|
+
}
|
1540
|
+
)
|
1541
|
+
)
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
it 'is expected to capture the aggregate comparison as :having' do
|
1545
|
+
expect(parse).to match(
|
1546
|
+
hash_including(
|
1547
|
+
having: {
|
1548
|
+
aggregate_modifier: '#',
|
1549
|
+
attribute: { string_no_quotes: 'variations' },
|
1550
|
+
comparator: '>=',
|
1551
|
+
value: { integer: '5' }
|
1552
|
+
}
|
1553
|
+
)
|
1554
|
+
)
|
1555
|
+
end
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
context 'when parsing a complex query' do
|
1559
|
+
subject(:parse) do
|
1560
|
+
predicate.parse(' title ~ "Polo Shirts" & (rating > 2.5 | featured = true) & #variations >= 5 ')
|
1561
|
+
end
|
1562
|
+
|
1563
|
+
it 'is expected to eq the correct tree' do # rubocop:disable RSpec/ExampleLength
|
1564
|
+
expect(parse).to eq(
|
1565
|
+
where: {
|
1566
|
+
left: {
|
1567
|
+
attribute: { string_no_quotes: 'title' },
|
1568
|
+
comparator: '~',
|
1569
|
+
value: { string_double_quotes: 'Polo Shirts' }
|
1570
|
+
},
|
1571
|
+
joiner: '&',
|
1572
|
+
right: {
|
1573
|
+
left: {
|
1574
|
+
attribute: { string_no_quotes: 'rating' },
|
1575
|
+
comparator: '>',
|
1576
|
+
value: { decimal: '2.5' }
|
1577
|
+
},
|
1578
|
+
joiner: '|',
|
1579
|
+
right: {
|
1580
|
+
attribute: { string_no_quotes: 'featured' },
|
1581
|
+
comparator: '=',
|
1582
|
+
value: { truthy: 'true' }
|
1583
|
+
}
|
1584
|
+
}
|
1585
|
+
},
|
1586
|
+
having: {
|
1587
|
+
aggregate_modifier: '#',
|
1588
|
+
attribute: { string_no_quotes: 'variations' },
|
1589
|
+
comparator: '>=',
|
1590
|
+
value: { integer: '5' }
|
1591
|
+
}
|
1592
|
+
)
|
1593
|
+
end
|
1594
|
+
end
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
describe '#root' do
|
1598
|
+
subject(:root) { parser.root }
|
1599
|
+
|
1600
|
+
it 'is expected to eq #predicate' do
|
1601
|
+
expect(root).to eq(parser.predicate)
|
1602
|
+
end
|
1603
|
+
end
|
1604
|
+
end
|