-
-
Notifications
You must be signed in to change notification settings - Fork 40
/
add_event_helper.rb
505 lines (399 loc) · 16.1 KB
/
add_event_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
def date_parsing_specs(test_stdout: true)
describe "date parsing" do
let(:description) { "Test." }
describe "when date is in YYYY-MM-DD" do
let(:date) { "2017-01-01" }
it { line_added "- #{date}: #{description}" }
it { stdout_only "#{capitalized_event} added: \"#{date}: #{description}\"" } if test_stdout
end
describe "when date is in MM-DD-YYYY" do
let(:date) { "01-02-2017" }
it { line_added "- 2017-01-02: #{description}" }
it { stdout_only "#{capitalized_event} added: \"2017-01-02: #{description}\"" } if test_stdout
end
describe "when date is invalid" do
let(:date) { "2017-02-30" }
it { line_added "- 2017-03-02: #{description}" }
it { stdout_only "#{capitalized_event} added: \"2017-03-02: #{description}\"" } if test_stdout
end
describe "when date is natural language and in full" do
let(:date) { "February 23rd, 2017" }
it { line_added "- 2017-02-23: #{description}" }
it { stdout_only "#{capitalized_event} added: \"2017-02-23: #{description}\"" } if test_stdout
end
describe "when date is natural language and only month and day" do
# We use two days rather than just one to avoid strange behavior around
# edge cases when the test is being run right around midnight.
let(:two_days_in_seconds) { 2 * 24 * 60 * 60 }
let(:raw_date) { Time.now + two_days_in_seconds }
let(:date) { raw_date.strftime("%B %d") }
let(:expected_year) { raw_date.strftime("%Y").to_i - 1 }
let(:expected_date_str) { "#{expected_year}-#{raw_date.strftime('%m-%d')}" }
it { line_added "- #{expected_date_str}: #{description}" }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{expected_date_str}: #{description}\"" }
end
end
end
end
def description_parsing_specs(test_stdout: true)
describe "description parsing" do
let(:date) { Date.today.strftime }
unless test_stdout
describe "when description is blank" do
let(:description) { " " }
it "prints an error message" do
value(subject[:stderr]).must_equal(
ensure_trailing_newline_unless_empty("Error: Blank #{event} not added")
)
end
end
end
describe "when description includes a friend's full name (case insensitive)" do
let(:description) { "Lunch with grace hopper." }
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace Hopper.\"" }
end
end
describe "when description includes a friend's first name (case insensitive)" do
let(:description) { "Lunch with grace." }
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace Hopper.\"" }
end
end
describe "when description has a friend's first name and last initial (case insensitive)" do
describe "when followed by no period" do
let(:description) { "Lunch with grace h" }
it { line_added "- #{date}: Lunch with **Grace Hopper**" }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace Hopper\"" }
end
end
describe "when followed by a period at the end of a sentence" do
let(:description) { "Met grace h. So fun!" }
it { line_added "- #{date}: Met **Grace Hopper**. So fun!" }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper. So fun!\"" }
end
end
describe "when followed by a period at the end of the description" do
let(:description) { "Lunch with grace h." }
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace Hopper.\"" }
end
end
describe "when followed by a period in the middle of a sentence" do
let(:description) { "Met grace h. at 12." }
it { line_added "- #{date}: Met **Grace Hopper** at 12." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Grace Hopper at 12.\"" }
end
end
end
describe "when description includes a friend's nickname (case insensitive)" do
let(:description) { "Lunch with the admiral." }
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace Hopper.\"" }
end
end
describe "when description includes a friend's nickname which contains a name" do
let(:description) { "Lunch with Amazing Grace." }
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace Hopper.\"" }
end
end
describe "when description includes a friend's name at the beginning of a word" do
# Capitalization reduces chance of a false positive.
let(:description) { "Gracefully strolled." }
it { line_added "- #{date}: Gracefully strolled." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Gracefully strolled.\"" }
end
end
describe "when description includes a friend's name at the end of a word" do
# Capitalization reduces chance of a false positive.
let(:description) { "The service was a disGrace." }
it { line_added "- #{date}: The service was a disGrace." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: The service was a disGrace.\"" }
end
end
describe "when description includes a friend's name in the middle of a word" do
# Capitalization reduces chance of a false positive.
let(:description) { "The service was disGraceful." }
it { line_added "- #{date}: The service was disGraceful." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: The service was disGraceful.\"" }
end
end
describe "when a friend's name is escaped with a backslash" do
# We have to use four backslashes here because of Ruby's backslash escaping; when this
# goes through all of the layers of this test it emerges on the other side as a single one.
let(:description) { "Dinner with \\\\Grace Kelly." }
it { line_added "- #{date}: Dinner with Grace Kelly." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Dinner with Grace Kelly.\"" }
end
end
describe "hyphenated name edge cases" do
describe "when one name precedes another before a hyphen" do
let(:description) { "Shopped w/ Mary-Kate." }
# Make sure "Mary" is a closer friend than "Mary-Kate" so we know our
# test result isn't due to chance.
let(:content) do
<<-FILE
### Activities:
- 2017-01-01: Singing with **Mary Poppins**.
### Notes:
### Friends:
- Mary Poppins
- Mary-Kate Olsen
### Locations:
FILE
end
it { line_added "- #{date}: Shopped w/ **Mary-Kate Olsen**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Shopped w/ Mary-Kate Olsen.\"" }
end
end
describe "when one name follows another after a hyphen" do
let(:description) { "Shopped w/ Mary-Kate." }
# Make sure "Kate" is a closer friend than "Mary-Kate" so we know our
# test result isn't due to chance.
let(:content) do
<<-FILE
### Activities:
- 2017-01-01: Improv with **Kate Winslet**.
### Notes:
### Friends:
- Kate Winslet
- Mary-Kate Olsen
### Locations:
FILE
end
it { line_added "- #{date}: Shopped w/ **Mary-Kate Olsen**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Shopped w/ Mary-Kate Olsen.\"" }
end
end
describe "when one name is contained within another via hyphens" do
let(:description) { "Met Mary-Jo-Kate." }
# Make sure "Jo" is a closer friend than "Mary-Jo-Kate" so we know our
# test result isn't due to chance.
let(:content) do
<<-FILE
### Activities:
- 2017-01-01: Singing with **Jo Stafford**.
### Notes:
### Friends:
- Jo Stafford
- Mary-Jo-Kate Olsen
### Locations:
FILE
end
it { line_added "- #{date}: Met **Mary-Jo-Kate Olsen**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Mary-Jo-Kate Olsen.\"" }
end
end
end
describe "when a friend's first and middle name matches two other friends' first names" do
let(:description) { "Met Martin Luther." }
# Make sure "Martin Jones" and "Luther Jones" are closer friends than
# "Martin Luther King" so we know our test result isn't due to chance.
let(:content) do
<<-FILE
### Activities:
- 2018-01-01: Singing with **Martin Jones**.
- 2018-01-01: Dancing with **Luther Jones**.
### Notes:
### Friends:
- Luther Jones
- Martin Jones
- Martin Luther King
### Locations:
FILE
end
it { line_added "- #{date}: Met **Martin Luther King**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Martin Luther King.\"" }
end
end
describe "when a friend has a first name only" do
# We want to explicitly check that the first-name last-initial behavior
# doesn't kick in here and match "Alejandra A" when the friend has no
# last name.
let(:description) { "Met Alejandra a few times." }
let(:content) do
<<-FILE
### Activities:
### Notes:
### Friends:
- Alejandra
### Locations:
FILE
end
it { line_added "- #{date}: Met **Alejandra** a few times." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Met Alejandra a few times.\"" }
end
end
describe "when description has a friend's name with leading asterisks" do
let(:description) { "Lunch with **Grace Hopper." }
it { line_added "- #{date}: Lunch with **Grace Hopper." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with **Grace Hopper.\"" }
end
end
describe "when description has a friend's name with trailing asterisks" do
# Note: We can't guarantee that "Grace Hopper**" doesn't match because the "Grace" isn't
# surrounded by asterisks.
let(:description) { "Lunch with Grace**." }
it { line_added "- #{date}: Lunch with Grace**." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch with Grace**.\"" }
end
end
describe "when description has a friend's name multiple times" do
let(:description) { "Grace! Grace!!!" }
it { line_added "- #{date}: **Grace Hopper**! **Grace Hopper**!!!" }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Grace Hopper! Grace Hopper!!!\"" }
end
end
describe "when description has a friend's full name which is another friend's first name" do
let(:description) { "Hung out with Elizabeth for most of the day." }
# Make sure "Elizabeth II" is a better friend than just "Elizabeth" to
# ensure our result isn't due to chance.
let(:content) do
<<-FILE
### Activities:
- 2018-08-05: Royal picnic with **Elizabeth II**.
### Notes:
### Friends:
- Elizabeth
- Elizabeth II
### Locations:
FILE
end
it { line_added "- #{date}: Hung out with **Elizabeth** for most of the day." }
if test_stdout
it do
stdout_only "#{capitalized_event} added: \"#{date}: "\
"Hung out with Elizabeth for most of the day.\""
end
end
end
describe "when description has a name with multiple friend matches" do
describe "when there is useful context from past activities" do
let(:description) { "Met John + Elizabeth." }
# Create a past activity in which Elizabeth Cady Stanton did something
# with John Cage. Then, create past activities to make Elizabeth II a
# better friend than Elizabeth Cady Stanton.
let(:content) do
<<-FILE
### Activities:
- 2017-01-05: Picnic with **Elizabeth Cady Stanton** and **John Cage**.
- 2017-01-04: Got lunch with **Elizabeth II**.
- 2017-01-03: Ice skated with **Elizabeth II**.
### Notes:
### Friends:
- Elizabeth Cady Stanton
- Elizabeth II
- John Cage
### Locations:
FILE
end
# Elizabeth II is the better friend, but historical activities have
# had Elizabeth Cady Stanton and John Cage together. Thus, we should
# interpret "Elizabeth" as Elizabeth Cady Stanton.
it { line_added "- #{date}: Met **John Cage** + **Elizabeth Cady Stanton**." }
if test_stdout
it do
stdout_only "#{capitalized_event} added: "\
"\"#{date}: Met John Cage + Elizabeth Cady Stanton.\""
end
end
end
describe "when there is no useful context from past activities" do
let(:description) { "Dinner with John and Elizabeth." }
# Create a past activity in which Elizabeth Cady Stanton did something
# with John Cage. Then, create past activities to make Elizabeth II a
# better friend than Elizabeth Cady Stanton.
let(:content) do
<<-FILE
### Activities:
- 2017-01-03: Ice skated with **Elizabeth II**.
### Notes:
### Friends:
- Elizabeth Cady Stanton
- Elizabeth II
- John Cage
### Locations:
FILE
end
# Pick the "Elizabeth" with more activities.
it { line_added "- #{date}: Dinner with **John Cage** and **Elizabeth II**." }
if test_stdout
it do
stdout_only "#{capitalized_event} added: "\
"\"#{date}: Dinner with John Cage and Elizabeth II.\""
end
end
end
end
describe "when description contains a location name (case insensitive)" do
let(:description) { "Lunch at a cafe in paris." }
it { line_added "- #{date}: Lunch at a cafe in _Paris_." }
if test_stdout
it { stdout_only "#{capitalized_event} added: \"#{date}: Lunch at a cafe in Paris.\"" }
end
end
describe "when description contains both names and locations" do
let(:description) { "Grace and I went to Atlantis and then Paris for lunch with George." }
it do
line_added "- #{date}: **Grace Hopper** and I went to _Atlantis_ and then _Paris_ for "\
"lunch with **George Washington Carver**."
end
if test_stdout
it do
stdout_only "#{capitalized_event} added: \"#{date}: Grace Hopper and I went to "\
"Atlantis and then Paris for lunch with George Washington Carver.\""
end
end
end
end
end
def parsing_specs(event:)
let(:event) { event.to_s }
let(:capitalized_event) { event.to_s.capitalize }
describe "when given a date and a description in the command" do
subject { run_cmd("add #{event} #{date}: #{description}") }
date_parsing_specs
description_parsing_specs
end
describe "when given only a date in the command" do
subject { run_cmd("add #{event} #{date}", stdin_data: description) }
# We don't try to test the STDOUT here because our command prompt produces other STDOUT that's
# hard to test.
date_parsing_specs(test_stdout: false)
description_parsing_specs(test_stdout: false)
end
describe "when given only a description in the command" do
subject { run_cmd("add #{event} #{description}") }
# We don't test date parsing since in this case the date is always inferred to be today.
description_parsing_specs
end
describe "when given neither a date nor a description in the command" do
subject { run_cmd("add #{event}", stdin_data: description) }
# We don't test date parsing since in this case the date is always inferred to be today.
# We don't try to test the STDOUT here because our command prompt produces other STDOUT that's
# hard to test.
description_parsing_specs(test_stdout: false)
end
end