gmail_search_syntax 0.1.1 → 0.1.2
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/GMAIL_BEHAVIOR_COMPARISON.md +166 -0
- data/GMAIL_COMPATIBILITY_COMPLETE.md +236 -0
- data/examples/gmail_comparison_demo.rb +82 -0
- data/lib/gmail_search_syntax/parser.rb +42 -24
- data/lib/gmail_search_syntax/tokenizer.rb +3 -3
- data/lib/gmail_search_syntax/version.rb +1 -1
- data/test/gmail_search_syntax_test.rb +185 -171
- data/test/tokenizer_test.rb +176 -144
- metadata +4 -1
@@ -3,71 +3,91 @@ require "test_helper"
|
|
3
3
|
class GmailSearchSyntaxTest < Minitest::Test
|
4
4
|
include GmailSearchSyntax::AST
|
5
5
|
|
6
|
+
def assert_operator(expected_properties, actual_operator)
|
7
|
+
assert_instance_of Operator, actual_operator, "Expected Operator, got #{actual_operator.class}"
|
8
|
+
expected_properties.each do |property, expected_value|
|
9
|
+
actual_value = actual_operator.public_send(property)
|
10
|
+
if property == :operands && expected_value.is_a?(Array)
|
11
|
+
assert_equal expected_value.length, actual_value.length, "Expected #{expected_value.length} operands, got #{actual_value.length}"
|
12
|
+
expected_value.each_with_index do |expected_operand, index|
|
13
|
+
if expected_operand.is_a?(Hash)
|
14
|
+
if expected_operand.key?(:name) && expected_operand.key?(:value)
|
15
|
+
# This is an Operator specification
|
16
|
+
assert_operator(expected_operand, actual_value[index])
|
17
|
+
elsif expected_operand.key?(:value)
|
18
|
+
# This is a StringToken specification
|
19
|
+
assert_string_token(expected_operand, actual_value[index])
|
20
|
+
else
|
21
|
+
# Generic node assertion
|
22
|
+
assert_equal expected_operand, actual_value[index], "Operand #{index}: expected #{expected_operand.inspect}, got #{actual_value[index].inspect}"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
assert_equal expected_operand, actual_value[index], "Operand #{index}: expected #{expected_operand.inspect}, got #{actual_value[index].inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
elsif expected_value.is_a?(Class)
|
29
|
+
assert_instance_of expected_value, actual_value, "Operator: expected #{property} to be instance of #{expected_value}, got #{actual_value.class}"
|
30
|
+
else
|
31
|
+
assert_equal expected_value, actual_value, "Operator: expected #{property} to be #{expected_value.inspect}, got #{actual_value.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_string_token(expected_properties, actual_string_token)
|
37
|
+
assert_instance_of StringToken, actual_string_token, "Expected StringToken, got #{actual_string_token.class}"
|
38
|
+
expected_properties.each do |property, expected_value|
|
39
|
+
actual_value = actual_string_token.public_send(property)
|
40
|
+
assert_equal expected_value, actual_value, "StringToken: expected #{property} to be #{expected_value.inspect}, got #{actual_value.inspect}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
6
44
|
def test_version
|
7
45
|
assert GmailSearchSyntax::VERSION
|
8
46
|
end
|
9
47
|
|
10
48
|
def test_simple_from_operator
|
11
49
|
ast = GmailSearchSyntax.parse!("from:amy@example.com")
|
12
|
-
|
13
|
-
assert_equal "from", ast.name
|
14
|
-
assert_equal "amy@example.com", ast.value
|
50
|
+
assert_operator({name: "from", value: "amy@example.com"}, ast)
|
15
51
|
end
|
16
52
|
|
17
53
|
def test_from_me
|
18
54
|
ast = GmailSearchSyntax.parse!("from:me")
|
19
|
-
|
20
|
-
assert_equal "from", ast.name
|
21
|
-
assert_equal "me", ast.value
|
55
|
+
assert_operator({name: "from", value: "me"}, ast)
|
22
56
|
end
|
23
57
|
|
24
58
|
def test_to_operator
|
25
59
|
ast = GmailSearchSyntax.parse!("to:john@example.com")
|
26
|
-
|
27
|
-
assert_equal "to", ast.name
|
28
|
-
assert_equal "john@example.com", ast.value
|
60
|
+
assert_operator({name: "to", value: "john@example.com"}, ast)
|
29
61
|
end
|
30
62
|
|
31
63
|
def test_subject_with_single_word
|
32
64
|
ast = GmailSearchSyntax.parse!("subject:dinner")
|
33
|
-
|
34
|
-
assert_equal "subject", ast.name
|
35
|
-
assert_equal "dinner", ast.value
|
65
|
+
assert_operator({name: "subject", value: "dinner"}, ast)
|
36
66
|
end
|
37
67
|
|
38
68
|
def test_subject_with_quoted_phrase
|
39
69
|
ast = GmailSearchSyntax.parse!('subject:"anniversary party"')
|
40
|
-
|
41
|
-
assert_equal "subject", ast.name
|
42
|
-
assert_equal "anniversary party", ast.value
|
70
|
+
assert_operator({name: "subject", value: "anniversary party"}, ast)
|
43
71
|
end
|
44
72
|
|
45
73
|
def test_after_date
|
46
74
|
ast = GmailSearchSyntax.parse!("after:2004/04/16")
|
47
|
-
|
48
|
-
assert_equal "after", ast.name
|
49
|
-
assert_equal "2004/04/16", ast.value
|
75
|
+
assert_operator({name: "after", value: "2004/04/16"}, ast)
|
50
76
|
end
|
51
77
|
|
52
78
|
def test_before_date
|
53
79
|
ast = GmailSearchSyntax.parse!("before:04/18/2004")
|
54
|
-
|
55
|
-
assert_equal "before", ast.name
|
56
|
-
assert_equal "04/18/2004", ast.value
|
80
|
+
assert_operator({name: "before", value: "04/18/2004"}, ast)
|
57
81
|
end
|
58
82
|
|
59
83
|
def test_older_than_relative
|
60
84
|
ast = GmailSearchSyntax.parse!("older_than:1y")
|
61
|
-
|
62
|
-
assert_equal "older_than", ast.name
|
63
|
-
assert_equal "1y", ast.value
|
85
|
+
assert_operator({name: "older_than", value: "1y"}, ast)
|
64
86
|
end
|
65
87
|
|
66
88
|
def test_newer_than_relative
|
67
89
|
ast = GmailSearchSyntax.parse!("newer_than:2d")
|
68
|
-
|
69
|
-
assert_equal "newer_than", ast.name
|
70
|
-
assert_equal "2d", ast.value
|
90
|
+
assert_operator({name: "newer_than", value: "2d"}, ast)
|
71
91
|
end
|
72
92
|
|
73
93
|
def test_or_operator_with_from
|
@@ -75,13 +95,8 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
75
95
|
assert_instance_of Or, ast
|
76
96
|
|
77
97
|
assert_equal 2, ast.operands.length
|
78
|
-
|
79
|
-
|
80
|
-
assert_equal "amy", ast.operands[0].value
|
81
|
-
|
82
|
-
assert_instance_of Operator, ast.operands[1]
|
83
|
-
assert_equal "from", ast.operands[1].name
|
84
|
-
assert_equal "david", ast.operands[1].value
|
98
|
+
assert_operator({name: "from", value: "amy"}, ast.operands[0])
|
99
|
+
assert_operator({name: "from", value: "david"}, ast.operands[1])
|
85
100
|
end
|
86
101
|
|
87
102
|
def test_braces_as_or
|
@@ -89,13 +104,8 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
89
104
|
assert_instance_of Or, ast
|
90
105
|
|
91
106
|
assert_equal 2, ast.operands.length
|
92
|
-
|
93
|
-
|
94
|
-
assert_equal "amy", ast.operands[0].value
|
95
|
-
|
96
|
-
assert_instance_of Operator, ast.operands[1]
|
97
|
-
assert_equal "from", ast.operands[1].name
|
98
|
-
assert_equal "david", ast.operands[1].value
|
107
|
+
assert_operator({name: "from", value: "amy"}, ast.operands[0])
|
108
|
+
assert_operator({name: "from", value: "david"}, ast.operands[1])
|
99
109
|
end
|
100
110
|
|
101
111
|
def test_and_operator
|
@@ -103,13 +113,8 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
103
113
|
assert_instance_of And, ast
|
104
114
|
|
105
115
|
assert_equal 2, ast.operands.length
|
106
|
-
|
107
|
-
|
108
|
-
assert_equal "amy", ast.operands[0].value
|
109
|
-
|
110
|
-
assert_instance_of Operator, ast.operands[1]
|
111
|
-
assert_equal "to", ast.operands[1].name
|
112
|
-
assert_equal "david", ast.operands[1].value
|
116
|
+
assert_operator({name: "from", value: "amy"}, ast.operands[0])
|
117
|
+
assert_operator({name: "to", value: "david"}, ast.operands[1])
|
113
118
|
end
|
114
119
|
|
115
120
|
def test_implicit_and
|
@@ -157,37 +162,27 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
157
162
|
|
158
163
|
def test_label_operator
|
159
164
|
ast = GmailSearchSyntax.parse!("label:friends")
|
160
|
-
|
161
|
-
assert_equal "label", ast.name
|
162
|
-
assert_equal "friends", ast.value
|
165
|
+
assert_operator({name: "label", value: "friends"}, ast)
|
163
166
|
end
|
164
167
|
|
165
168
|
def test_category_operator
|
166
169
|
ast = GmailSearchSyntax.parse!("category:primary")
|
167
|
-
|
168
|
-
assert_equal "category", ast.name
|
169
|
-
assert_equal "primary", ast.value
|
170
|
+
assert_operator({name: "category", value: "primary"}, ast)
|
170
171
|
end
|
171
172
|
|
172
173
|
def test_has_attachment
|
173
174
|
ast = GmailSearchSyntax.parse!("has:attachment")
|
174
|
-
|
175
|
-
assert_equal "has", ast.name
|
176
|
-
assert_equal "attachment", ast.value
|
175
|
+
assert_operator({name: "has", value: "attachment"}, ast)
|
177
176
|
end
|
178
177
|
|
179
178
|
def test_filename_operator
|
180
179
|
ast = GmailSearchSyntax.parse!("filename:pdf")
|
181
|
-
|
182
|
-
assert_equal "filename", ast.name
|
183
|
-
assert_equal "pdf", ast.value
|
180
|
+
assert_operator({name: "filename", value: "pdf"}, ast)
|
184
181
|
end
|
185
182
|
|
186
183
|
def test_filename_with_extension
|
187
184
|
ast = GmailSearchSyntax.parse!("filename:homework.txt")
|
188
|
-
|
189
|
-
assert_equal "filename", ast.name
|
190
|
-
assert_equal "homework.txt", ast.value
|
185
|
+
assert_operator({name: "filename", value: "homework.txt"}, ast)
|
191
186
|
end
|
192
187
|
|
193
188
|
def test_quoted_exact_phrase
|
@@ -198,56 +193,37 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
198
193
|
|
199
194
|
def test_parentheses_grouping
|
200
195
|
ast = GmailSearchSyntax.parse!("subject:(dinner movie)")
|
201
|
-
|
202
|
-
assert_equal "subject", ast.name
|
203
|
-
|
204
|
-
assert_instance_of And, ast.value
|
196
|
+
assert_operator({name: "subject", value: And}, ast)
|
205
197
|
assert_equal 2, ast.value.operands.length
|
206
|
-
|
207
|
-
|
208
|
-
assert_instance_of StringToken, ast.value.operands[1]
|
209
|
-
assert_equal "movie", ast.value.operands[1].value
|
198
|
+
assert_string_token({value: "dinner"}, ast.value.operands[0])
|
199
|
+
assert_string_token({value: "movie"}, ast.value.operands[1])
|
210
200
|
end
|
211
201
|
|
212
202
|
def test_in_anywhere
|
203
|
+
# With Gmail-compatible bareword consumption, "movie" gets consumed into operator value
|
204
|
+
# To search for "movie" as text, use: in:anywhere "movie" or use a different operator after
|
213
205
|
ast = GmailSearchSyntax.parse!("in:anywhere movie")
|
214
|
-
|
215
|
-
|
216
|
-
assert_equal 2, ast.operands.length
|
217
|
-
assert_instance_of Operator, ast.operands[0]
|
218
|
-
assert_equal "in", ast.operands[0].name
|
219
|
-
assert_equal "anywhere", ast.operands[0].value
|
220
|
-
|
221
|
-
assert_instance_of StringToken, ast.operands[1]
|
222
|
-
assert_equal "movie", ast.operands[1].value
|
206
|
+
assert_operator({name: "in", value: "anywhere movie"}, ast)
|
223
207
|
end
|
224
208
|
|
225
209
|
def test_is_starred
|
226
210
|
ast = GmailSearchSyntax.parse!("is:starred")
|
227
|
-
|
228
|
-
assert_equal "is", ast.name
|
229
|
-
assert_equal "starred", ast.value
|
211
|
+
assert_operator({name: "is", value: "starred"}, ast)
|
230
212
|
end
|
231
213
|
|
232
214
|
def test_is_unread
|
233
215
|
ast = GmailSearchSyntax.parse!("is:unread")
|
234
|
-
|
235
|
-
assert_equal "is", ast.name
|
236
|
-
assert_equal "unread", ast.value
|
216
|
+
assert_operator({name: "is", value: "unread"}, ast)
|
237
217
|
end
|
238
218
|
|
239
219
|
def test_size_operator
|
240
220
|
ast = GmailSearchSyntax.parse!("size:1000000")
|
241
|
-
|
242
|
-
assert_equal "size", ast.name
|
243
|
-
assert_equal 1000000, ast.value
|
221
|
+
assert_operator({name: "size", value: 1000000}, ast)
|
244
222
|
end
|
245
223
|
|
246
224
|
def test_larger_operator
|
247
225
|
ast = GmailSearchSyntax.parse!("larger:10M")
|
248
|
-
|
249
|
-
assert_equal "larger", ast.name
|
250
|
-
assert_equal "10M", ast.value
|
226
|
+
assert_operator({name: "larger", value: "10M"}, ast)
|
251
227
|
end
|
252
228
|
|
253
229
|
def test_complex_query_with_multiple_operators
|
@@ -292,37 +268,27 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
292
268
|
|
293
269
|
def test_list_operator
|
294
270
|
ast = GmailSearchSyntax.parse!("list:info@example.com")
|
295
|
-
|
296
|
-
assert_equal "list", ast.name
|
297
|
-
assert_equal "info@example.com", ast.value
|
271
|
+
assert_operator({name: "list", value: "info@example.com"}, ast)
|
298
272
|
end
|
299
273
|
|
300
274
|
def test_deliveredto_operator
|
301
275
|
ast = GmailSearchSyntax.parse!("deliveredto:username@example.com")
|
302
|
-
|
303
|
-
assert_equal "deliveredto", ast.name
|
304
|
-
assert_equal "username@example.com", ast.value
|
276
|
+
assert_operator({name: "deliveredto", value: "username@example.com"}, ast)
|
305
277
|
end
|
306
278
|
|
307
279
|
def test_rfc822msgid_operator
|
308
280
|
ast = GmailSearchSyntax.parse!("rfc822msgid:200503292@example.com")
|
309
|
-
|
310
|
-
assert_equal "rfc822msgid", ast.name
|
311
|
-
assert_equal "200503292@example.com", ast.value
|
281
|
+
assert_operator({name: "rfc822msgid", value: "200503292@example.com"}, ast)
|
312
282
|
end
|
313
283
|
|
314
284
|
def test_cc_operator
|
315
285
|
ast = GmailSearchSyntax.parse!("cc:john@example.com")
|
316
|
-
|
317
|
-
assert_equal "cc", ast.name
|
318
|
-
assert_equal "john@example.com", ast.value
|
286
|
+
assert_operator({name: "cc", value: "john@example.com"}, ast)
|
319
287
|
end
|
320
288
|
|
321
289
|
def test_bcc_operator
|
322
290
|
ast = GmailSearchSyntax.parse!("bcc:david@example.com")
|
323
|
-
|
324
|
-
assert_equal "bcc", ast.name
|
325
|
-
assert_equal "david@example.com", ast.value
|
291
|
+
assert_operator({name: "bcc", value: "david@example.com"}, ast)
|
326
292
|
end
|
327
293
|
|
328
294
|
def test_plain_text_search
|
@@ -423,9 +389,7 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
423
389
|
|
424
390
|
def test_email_with_plus_sign
|
425
391
|
ast = GmailSearchSyntax.parse!("to:user+tag@example.com")
|
426
|
-
|
427
|
-
assert_equal "to", ast.name
|
428
|
-
assert_equal "user+tag@example.com", ast.value
|
392
|
+
assert_operator({name: "to", value: "user+tag@example.com"}, ast)
|
429
393
|
end
|
430
394
|
|
431
395
|
def test_in_operator_with_location
|
@@ -440,16 +404,12 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
440
404
|
|
441
405
|
def test_has_drive_operator
|
442
406
|
ast = GmailSearchSyntax.parse!("has:drive")
|
443
|
-
|
444
|
-
assert_equal "has", ast.name
|
445
|
-
assert_equal "drive", ast.value
|
407
|
+
assert_operator({name: "has", value: "drive"}, ast)
|
446
408
|
end
|
447
409
|
|
448
410
|
def test_category_updates
|
449
411
|
ast = GmailSearchSyntax.parse!("category:updates")
|
450
|
-
|
451
|
-
assert_equal "category", ast.name
|
452
|
-
assert_equal "updates", ast.value
|
412
|
+
assert_operator({name: "category", value: "updates"}, ast)
|
453
413
|
end
|
454
414
|
|
455
415
|
def test_around_default_distance
|
@@ -466,17 +426,11 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
466
426
|
|
467
427
|
def test_subject_with_parentheses_multiple_words
|
468
428
|
ast = GmailSearchSyntax.parse!("subject:(project status update)")
|
469
|
-
|
470
|
-
assert_equal "subject", ast.name
|
471
|
-
|
472
|
-
assert_instance_of And, ast.value
|
429
|
+
assert_operator({name: "subject", value: And}, ast)
|
473
430
|
assert_equal 3, ast.value.operands.length
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
assert_equal "status", ast.value.operands[1].value
|
478
|
-
assert_instance_of StringToken, ast.value.operands[2]
|
479
|
-
assert_equal "update", ast.value.operands[2].value
|
431
|
+
assert_string_token({value: "project"}, ast.value.operands[0])
|
432
|
+
assert_string_token({value: "status"}, ast.value.operands[1])
|
433
|
+
assert_string_token({value: "update"}, ast.value.operands[2])
|
480
434
|
end
|
481
435
|
|
482
436
|
def test_and_explicit_with_text
|
@@ -493,43 +447,28 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
493
447
|
|
494
448
|
def test_smaller_operator
|
495
449
|
ast = GmailSearchSyntax.parse!("smaller:1M")
|
496
|
-
|
497
|
-
assert_equal "smaller", ast.name
|
498
|
-
assert_equal "1M", ast.value
|
450
|
+
assert_operator({name: "smaller", value: "1M"}, ast)
|
499
451
|
end
|
500
452
|
|
501
453
|
def test_or_inside_operator_value
|
502
454
|
ast = GmailSearchSyntax.parse!("from:(mischa@ OR julik@)")
|
503
|
-
|
504
|
-
assert_equal "from", ast.name
|
505
|
-
|
506
|
-
assert_instance_of Or, ast.value
|
455
|
+
assert_operator({name: "from", value: Or}, ast)
|
507
456
|
assert_equal 2, ast.value.operands.length
|
508
|
-
|
509
|
-
|
510
|
-
assert_instance_of StringToken, ast.value.operands[1]
|
511
|
-
assert_equal "julik@", ast.value.operands[1].value
|
457
|
+
assert_string_token({value: "mischa@"}, ast.value.operands[0])
|
458
|
+
assert_string_token({value: "julik@"}, ast.value.operands[1])
|
512
459
|
end
|
513
460
|
|
514
461
|
def test_or_with_emails_inside_operator
|
515
462
|
ast = GmailSearchSyntax.parse!("from:(amy@example.com OR bob@example.com)")
|
516
|
-
|
517
|
-
assert_equal "from", ast.name
|
518
|
-
|
519
|
-
assert_instance_of Or, ast.value
|
463
|
+
assert_operator({name: "from", value: Or}, ast)
|
520
464
|
assert_equal 2, ast.value.operands.length
|
521
|
-
|
522
|
-
|
523
|
-
assert_instance_of StringToken, ast.value.operands[1]
|
524
|
-
assert_equal "bob@example.com", ast.value.operands[1].value
|
465
|
+
assert_string_token({value: "amy@example.com"}, ast.value.operands[0])
|
466
|
+
assert_string_token({value: "bob@example.com"}, ast.value.operands[1])
|
525
467
|
end
|
526
468
|
|
527
469
|
def test_multiple_or_inside_operator
|
528
470
|
ast = GmailSearchSyntax.parse!("from:(a@ OR b@ OR c@)")
|
529
|
-
|
530
|
-
assert_equal "from", ast.name
|
531
|
-
|
532
|
-
assert_instance_of Or, ast.value
|
471
|
+
assert_operator({name: "from", value: Or}, ast)
|
533
472
|
assert_equal 3, ast.value.operands.length
|
534
473
|
assert_equal "a@", ast.value.operands[0].value
|
535
474
|
assert_equal "b@", ast.value.operands[1].value
|
@@ -538,15 +477,10 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
538
477
|
|
539
478
|
def test_and_inside_operator_value
|
540
479
|
ast = GmailSearchSyntax.parse!("subject:(urgent AND meeting)")
|
541
|
-
|
542
|
-
assert_equal "subject", ast.name
|
543
|
-
|
544
|
-
assert_instance_of And, ast.value
|
480
|
+
assert_operator({name: "subject", value: And}, ast)
|
545
481
|
assert_equal 2, ast.value.operands.length
|
546
|
-
|
547
|
-
|
548
|
-
assert_instance_of StringToken, ast.value.operands[1]
|
549
|
-
assert_equal "meeting", ast.value.operands[1].value
|
482
|
+
assert_string_token({value: "urgent"}, ast.value.operands[0])
|
483
|
+
assert_string_token({value: "meeting"}, ast.value.operands[1])
|
550
484
|
end
|
551
485
|
|
552
486
|
def test_operator_with_or_combined_with_other_conditions
|
@@ -565,13 +499,9 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
565
499
|
|
566
500
|
def test_negation_inside_operator_value
|
567
501
|
ast = GmailSearchSyntax.parse!("subject:(meeting -cancelled)")
|
568
|
-
|
569
|
-
assert_equal "subject", ast.name
|
570
|
-
|
571
|
-
assert_instance_of And, ast.value
|
502
|
+
assert_operator({name: "subject", value: And}, ast)
|
572
503
|
assert_equal 2, ast.value.operands.length
|
573
|
-
|
574
|
-
assert_equal "meeting", ast.value.operands[0].value
|
504
|
+
assert_string_token({value: "meeting"}, ast.value.operands[0])
|
575
505
|
assert_instance_of Not, ast.value.operands[1]
|
576
506
|
assert_equal "cancelled", ast.value.operands[1].child.value
|
577
507
|
end
|
@@ -610,15 +540,10 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
610
540
|
|
611
541
|
def test_curly_braces_inside_operator_value
|
612
542
|
ast = GmailSearchSyntax.parse!("from:{mischa@ marc@}")
|
613
|
-
|
614
|
-
assert_equal "from", ast.name
|
615
|
-
|
616
|
-
assert_instance_of Or, ast.value
|
543
|
+
assert_operator({name: "from", value: Or}, ast)
|
617
544
|
assert_equal 2, ast.value.operands.length
|
618
|
-
|
619
|
-
|
620
|
-
assert_instance_of StringToken, ast.value.operands[1]
|
621
|
-
assert_equal "marc@", ast.value.operands[1].value
|
545
|
+
assert_string_token({value: "mischa@"}, ast.value.operands[0])
|
546
|
+
assert_string_token({value: "marc@"}, ast.value.operands[1])
|
622
547
|
end
|
623
548
|
|
624
549
|
def test_curly_braces_with_emails_inside_operator
|
@@ -734,4 +659,93 @@ class GmailSearchSyntaxTest < Minitest::Test
|
|
734
659
|
assert_equal 'meeting"room', ast.operands[0].value
|
735
660
|
assert_equal 'project\\plan', ast.operands[1].value
|
736
661
|
end
|
662
|
+
|
663
|
+
# Gmail behavior: barewords after operator values get consumed into the operator value
|
664
|
+
# until the next operator is encountered.
|
665
|
+
# We now implement this Gmail-compatible behavior.
|
666
|
+
|
667
|
+
def test_label_with_space_separated_value_gmail_behavior
|
668
|
+
# Gmail parses this as: label:"Cora/Google Drive", label:"Notes"
|
669
|
+
# We now match this behavior
|
670
|
+
ast = GmailSearchSyntax.parse!("label:Cora/Google Drive label:Notes")
|
671
|
+
assert_instance_of And, ast
|
672
|
+
assert_equal 2, ast.operands.length
|
673
|
+
|
674
|
+
# Gmail-compatible: barewords consumed into operator value
|
675
|
+
assert_instance_of Operator, ast.operands[0]
|
676
|
+
assert_equal "label", ast.operands[0].name
|
677
|
+
assert_equal "Cora/Google Drive", ast.operands[0].value
|
678
|
+
|
679
|
+
# Second operator parsed correctly
|
680
|
+
assert_instance_of Operator, ast.operands[1]
|
681
|
+
assert_equal "label", ast.operands[1].name
|
682
|
+
assert_equal "Notes", ast.operands[1].value
|
683
|
+
end
|
684
|
+
|
685
|
+
def test_subject_with_barewords_gmail_behavior
|
686
|
+
# Gmail parses: subject:"urgent meeting important"
|
687
|
+
# We now match this behavior
|
688
|
+
ast = GmailSearchSyntax.parse!("subject:urgent meeting important")
|
689
|
+
assert_instance_of Operator, ast
|
690
|
+
|
691
|
+
assert_equal "subject", ast.name
|
692
|
+
assert_equal "urgent meeting important", ast.value
|
693
|
+
end
|
694
|
+
|
695
|
+
def test_multiple_barewords_between_operators_gmail_behavior
|
696
|
+
# Gmail parses: label:"test one two three", label:"another"
|
697
|
+
# We now match this behavior
|
698
|
+
ast = GmailSearchSyntax.parse!("label:test one two three label:another")
|
699
|
+
assert_instance_of And, ast
|
700
|
+
assert_equal 2, ast.operands.length
|
701
|
+
|
702
|
+
assert_instance_of Operator, ast.operands[0]
|
703
|
+
assert_equal "label", ast.operands[0].name
|
704
|
+
assert_equal "test one two three", ast.operands[0].value
|
705
|
+
|
706
|
+
assert_instance_of Operator, ast.operands[1]
|
707
|
+
assert_equal "label", ast.operands[1].name
|
708
|
+
assert_equal "another", ast.operands[1].value
|
709
|
+
end
|
710
|
+
|
711
|
+
def test_barewords_stop_at_special_operators
|
712
|
+
# Bareword collection should stop at OR, AND, AROUND
|
713
|
+
ast = GmailSearchSyntax.parse!("subject:urgent meeting OR subject:important call")
|
714
|
+
assert_instance_of Or, ast
|
715
|
+
assert_equal 2, ast.operands.length
|
716
|
+
|
717
|
+
assert_instance_of Operator, ast.operands[0]
|
718
|
+
assert_equal "subject", ast.operands[0].name
|
719
|
+
assert_equal "urgent meeting", ast.operands[0].value
|
720
|
+
|
721
|
+
assert_instance_of Operator, ast.operands[1]
|
722
|
+
assert_equal "subject", ast.operands[1].name
|
723
|
+
assert_equal "important call", ast.operands[1].value
|
724
|
+
end
|
725
|
+
|
726
|
+
def test_barewords_with_mixed_tokens
|
727
|
+
# Numbers, dates, emails should all be collected as barewords
|
728
|
+
ast = GmailSearchSyntax.parse!("subject:meeting 2024 Q1 review")
|
729
|
+
assert_instance_of Operator, ast
|
730
|
+
assert_equal "subject", ast.name
|
731
|
+
assert_equal "meeting 2024 Q1 review", ast.value
|
732
|
+
end
|
733
|
+
|
734
|
+
def test_specific_gmail_example_cora_google_drive
|
735
|
+
# The specific example from the user: label:Cora/Google Drive label:Notes
|
736
|
+
# This should parse as two separate label operators with multi-word values
|
737
|
+
ast = GmailSearchSyntax.parse!("label:Cora/Google Drive label:Notes")
|
738
|
+
assert_instance_of And, ast
|
739
|
+
assert_equal 2, ast.operands.length
|
740
|
+
|
741
|
+
# First operator: label with "Cora/Google Drive"
|
742
|
+
assert_instance_of Operator, ast.operands[0]
|
743
|
+
assert_equal "label", ast.operands[0].name
|
744
|
+
assert_equal "Cora/Google Drive", ast.operands[0].value
|
745
|
+
|
746
|
+
# Second operator: label with "Notes"
|
747
|
+
assert_instance_of Operator, ast.operands[1]
|
748
|
+
assert_equal "label", ast.operands[1].name
|
749
|
+
assert_equal "Notes", ast.operands[1].value
|
750
|
+
end
|
737
751
|
end
|