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.
@@ -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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast.operands[0]
79
- assert_equal "from", ast.operands[0].name
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
- assert_instance_of Operator, ast.operands[0]
93
- assert_equal "from", ast.operands[0].name
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
- assert_instance_of Operator, ast.operands[0]
107
- assert_equal "from", ast.operands[0].name
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
207
- assert_equal "dinner", ast.value.operands[0].value
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
- assert_instance_of And, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
475
- assert_equal "project", ast.value.operands[0].value
476
- assert_instance_of StringToken, ast.value.operands[1]
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
509
- assert_equal "mischa@", ast.value.operands[0].value
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
522
- assert_equal "amy@example.com", ast.value.operands[0].value
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
- assert_instance_of Operator, ast
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
547
- assert_equal "urgent", ast.value.operands[0].value
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
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
- assert_instance_of Operator, ast
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
- assert_instance_of StringToken, ast.value.operands[0]
619
- assert_equal "mischa@", ast.value.operands[0].value
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