timetrap 1.1.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.
@@ -0,0 +1,622 @@
1
+ TEST_MODE = true
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'timetrap')
3
+ require 'spec'
4
+
5
+ describe Timetrap do
6
+ def create_entry atts = {}
7
+ Timetrap::Entry.create({
8
+ :sheet => 'default',
9
+ :start => Time.now,
10
+ :end => Time.now,
11
+ :note => 'note'}.merge(atts))
12
+ end
13
+
14
+ before :each do
15
+ Timetrap::Entry.create_table!
16
+ Timetrap::Meta.create_table!
17
+ $stdout = StringIO.new
18
+ $stdin = StringIO.new
19
+ end
20
+
21
+ describe 'CLI' do
22
+ describe "COMMANDS" do
23
+ def invoke command
24
+ Timetrap::CLI.parse command
25
+ Timetrap::CLI.invoke
26
+ end
27
+
28
+ describe 'archive' do
29
+ before do
30
+ 3.times do |i|
31
+ create_entry
32
+ end
33
+ end
34
+
35
+ it "should put the entries in a hidden sheet" do
36
+ $stdin.string = "yes\n"
37
+ invoke 'archive'
38
+ Timetrap::Entry.each do |e|
39
+ e.sheet.should == '_default'
40
+ end
41
+ end
42
+
43
+ it "should leave the running entry alone" do
44
+ invoke "in"
45
+ $stdin.string = "yes\n"
46
+ invoke 'archive'
47
+ Timetrap::Entry.order(:id).last.sheet.should == 'default'
48
+ end
49
+ end
50
+
51
+ describe 'edit' do
52
+ before do
53
+ Timetrap.start "running entry", nil
54
+ end
55
+
56
+ it "should edit the description of the active period" do
57
+ Timetrap.active_entry.note.should == 'running entry'
58
+ invoke 'edit new description'
59
+ Timetrap.active_entry.note.should == 'new description'
60
+ end
61
+
62
+ it "should edit the start time of the active period" do
63
+ invoke 'edit --start "yesterday 10am"'
64
+ Timetrap.active_entry.start.should == Chronic.parse("yesterday 10am")
65
+ Timetrap.active_entry.note.should == 'running entry'
66
+ end
67
+
68
+ it "should edit the end time of the active period" do
69
+ entry = Timetrap.active_entry
70
+ invoke 'edit --end "yesterday 10am"'
71
+ entry.refresh.end.should == Chronic.parse("yesterday 10am")
72
+ entry.refresh.note.should == 'running entry'
73
+ end
74
+
75
+ it "should edit a non running entry based on id" do
76
+ not_running = Timetrap.active_entry
77
+ Timetrap.stop
78
+ Timetrap.start "another entry", nil
79
+ invoke "edit --id #{not_running.id} a new description"
80
+ not_running.refresh.note.should == 'a new description'
81
+ end
82
+ end
83
+
84
+ describe "backend" do
85
+ it "should open an sqlite console to the db" do
86
+ Timetrap::CLI.should_receive(:exec).with("sqlite3 #{DB_NAME}")
87
+ invoke 'backend'
88
+ end
89
+ end
90
+
91
+ describe "display" do
92
+ before do
93
+ Timetrap::Entry.create( :sheet => 'another',
94
+ :note => 'entry 4', :start => '2008-10-05 18:00:00'
95
+ )
96
+ Timetrap::Entry.create( :sheet => 'SpecSheet',
97
+ :note => 'entry 2', :start => '2008-10-03 16:00:00', :end => '2008-10-03 18:00:00'
98
+ )
99
+ Timetrap::Entry.create( :sheet => 'SpecSheet',
100
+ :note => 'entry 1', :start => '2008-10-03 12:00:00', :end => '2008-10-03 14:00:00'
101
+ )
102
+ Timetrap::Entry.create( :sheet => 'SpecSheet',
103
+ :note => 'entry 3', :start => '2008-10-05 16:00:00', :end => '2008-10-05 18:00:00'
104
+ )
105
+ Timetrap::Entry.create( :sheet => 'SpecSheet',
106
+ :note => 'entry 4', :start => '2008-10-05 18:00:00'
107
+ )
108
+
109
+ Time.stub!(:now).and_return Time.at(1223254800 + (60*60*2))
110
+ @desired_output = <<-OUTPUT
111
+ Timesheet: SpecSheet
112
+ Day Start End Duration Notes
113
+ Fri Oct 03, 2008 12:00:00 - 14:00:00 2:00:00 entry 1
114
+ 16:00:00 - 18:00:00 2:00:00 entry 2
115
+ 4:00:00
116
+ Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 entry 3
117
+ 18:00:00 - 2:00:00 entry 4
118
+ 4:00:00
119
+ ---------------------------------------------------------
120
+ Total 8:00:00
121
+ OUTPUT
122
+
123
+ @desired_output_with_ids = <<-OUTPUT
124
+ Timesheet: SpecSheet
125
+ Id Day Start End Duration Notes
126
+ 3 Fri Oct 03, 2008 12:00:00 - 14:00:00 2:00:00 entry 1
127
+ 2 16:00:00 - 18:00:00 2:00:00 entry 2
128
+ 4:00:00
129
+ 4 Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 entry 3
130
+ 5 18:00:00 - 2:00:00 entry 4
131
+ 4:00:00
132
+ ---------------------------------------------------------
133
+ Total 8:00:00
134
+ OUTPUT
135
+
136
+ @desired_output_for_all = <<-OUTPUT
137
+ Timesheet: SpecSheet
138
+ Day Start End Duration Notes
139
+ Fri Oct 03, 2008 12:00:00 - 14:00:00 2:00:00 entry 1
140
+ 16:00:00 - 18:00:00 2:00:00 entry 2
141
+ 4:00:00
142
+ Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 entry 3
143
+ 18:00:00 - 2:00:00 entry 4
144
+ 4:00:00
145
+ ---------------------------------------------------------
146
+ Total 8:00:00
147
+
148
+ Timesheet: another
149
+ Day Start End Duration Notes
150
+ Sun Oct 05, 2008 18:00:00 - 2:00:00 entry 4
151
+ 2:00:00
152
+ ---------------------------------------------------------
153
+ Total 2:00:00
154
+ -------------------------------------------------------------
155
+ Grand Total 10:00:00
156
+ OUTPUT
157
+ end
158
+
159
+ it "should display the current timesheet" do
160
+ Timetrap.current_sheet = 'SpecSheet'
161
+ invoke 'display'
162
+ $stdout.string.should == @desired_output
163
+ end
164
+
165
+ it "should display a non current timesheet" do
166
+ Timetrap.current_sheet = 'another'
167
+ invoke 'display SpecSheet'
168
+ $stdout.string.should == @desired_output
169
+ end
170
+
171
+ it "should display a non current timesheet based on a partial name match" do
172
+ Timetrap.current_sheet = 'another'
173
+ invoke 'display S'
174
+ $stdout.string.should == @desired_output
175
+ end
176
+
177
+ it "should display a timesheet with ids" do
178
+ invoke 'display S --ids'
179
+ $stdout.string.should == @desired_output_with_ids
180
+ end
181
+
182
+ it "should display all timesheets" do
183
+ Timetrap.current_sheet = 'another'
184
+ invoke 'display all'
185
+ $stdout.string.should == @desired_output_for_all
186
+ end
187
+
188
+ it "should not display archived for all timesheets" do
189
+ $stdin.string = "yes\n"
190
+ invoke 'archive SpecSheet'
191
+ $stdout.string = ''
192
+ invoke 'display all'
193
+ $stdout.string.should_not =~ /_SpecSheet/
194
+ end
195
+ end
196
+
197
+ describe "format" do
198
+ before do
199
+ create_entry(:start => '2008-10-03 12:00:00', :end => '2008-10-03 14:00:00')
200
+ create_entry(:start => '2008-10-05 12:00:00', :end => '2008-10-05 14:00:00')
201
+ end
202
+ describe 'csv' do
203
+
204
+ it "should not export running items" do
205
+ invoke 'in'
206
+ invoke 'format --format csv'
207
+ $stdout.string.should == <<-EOF
208
+ start,end,note
209
+ "2008-10-03 12:00:00","2008-10-03 14:00:00","note"
210
+ "2008-10-05 12:00:00","2008-10-05 14:00:00","note"
211
+ EOF
212
+ end
213
+
214
+ it "should filter events by the passed dates" do
215
+ invoke 'format --format csv --start 2008-10-03 --end 2008-10-03'
216
+ $stdout.string.should == <<-EOF
217
+ start,end,note
218
+ "2008-10-03 12:00:00","2008-10-03 14:00:00","note"
219
+ EOF
220
+ end
221
+
222
+ it "should not filter events by date when none are passed" do
223
+ invoke 'format --format csv'
224
+ $stdout.string.should == <<-EOF
225
+ start,end,note
226
+ "2008-10-03 12:00:00","2008-10-03 14:00:00","note"
227
+ "2008-10-05 12:00:00","2008-10-05 14:00:00","note"
228
+ EOF
229
+ end
230
+ end
231
+
232
+ describe 'ical' do
233
+
234
+ it "should not export running items" do
235
+ invoke 'in'
236
+ invoke 'format --format ical'
237
+ $stdout.string.scan(/BEGIN:VEVENT/).should have(2).item
238
+ end
239
+
240
+ it "should filter events by the passed dates" do
241
+ invoke 'format --format ical --start 2008-10-03 --end 2008-10-03'
242
+ $stdout.string.scan(/BEGIN:VEVENT/).should have(1).item
243
+ end
244
+
245
+ it "should not filter events by date when none are passed" do
246
+ invoke 'format --format ical'
247
+ $stdout.string.scan(/BEGIN:VEVENT/).should have(2).item
248
+ end
249
+
250
+ it "should export a sheet to an ical format" do
251
+ invoke 'format --format ical --start 2008-10-03 --end 2008-10-03'
252
+ desired = <<-EOF
253
+ BEGIN:VCALENDAR
254
+ VERSION:2.0
255
+ CALSCALE:GREGORIAN
256
+ METHOD:PUBLISH
257
+ PRODID:iCalendar-Ruby
258
+ BEGIN:VEVENT
259
+ SEQUENCE:0
260
+ DTEND:20081003T140000
261
+ SUMMARY:note
262
+ DTSTART:20081003T120000
263
+ END:VEVENT
264
+ END:VCALENDAR
265
+ EOF
266
+ desired.each_line do |line|
267
+ $stdout.string.should =~ /#{line.chomp}/
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ describe "in" do
274
+ it "should start the time for the current timesheet" do
275
+ lambda do
276
+ invoke 'in'
277
+ end.should change(Timetrap::Entry, :count).by(1)
278
+ end
279
+
280
+ it "should set the note when starting a new entry" do
281
+ invoke 'in working on something'
282
+ Timetrap::Entry.order_by(:id).last.note.should == 'working on something'
283
+ end
284
+
285
+ it "should set the start when starting a new entry" do
286
+ @time = Time.now
287
+ Time.stub!(:now).and_return @time
288
+ invoke 'in working on something'
289
+ Timetrap::Entry.order_by(:id).last.start.to_i.should == @time.to_i
290
+ end
291
+
292
+ it "should not start the time if the timetrap is running" do
293
+ Timetrap.stub!(:running?).and_return true
294
+ lambda do
295
+ invoke 'in'
296
+ end.should_not change(Timetrap::Entry, :count)
297
+ end
298
+
299
+ it "should allow the sheet to be started at a certain time" do
300
+ invoke 'in work --at "10am 2008-10-03"'
301
+ Timetrap::Entry.order_by(:id).last.start.should == Time.parse('2008-10-03 10:00')
302
+ end
303
+ end
304
+
305
+ describe "kill" do
306
+ it "should give me a chance not to fuck up" do
307
+ entry = create_entry
308
+ lambda do
309
+ $stdin.string = ""
310
+ invoke "kill #{entry.sheet}"
311
+ end.should_not change(Timetrap::Entry, :count).by(-1)
312
+ end
313
+
314
+ it "should delete a timesheet" do
315
+ create_entry
316
+ entry = create_entry
317
+ lambda do
318
+ $stdin.string = "yes\n"
319
+ invoke "kill #{entry.sheet}"
320
+ end.should change(Timetrap::Entry, :count).by(-2)
321
+ end
322
+
323
+ it "should delete an entry" do
324
+ create_entry
325
+ entry = create_entry
326
+ lambda do
327
+ $stdin.string = "yes\n"
328
+ invoke "kill --id #{entry.id}"
329
+ end.should change(Timetrap::Entry, :count).by(-1)
330
+ end
331
+ end
332
+
333
+ describe "list" do
334
+ describe "with no sheets defined" do
335
+ it "should say that there are no sheets" do
336
+ invoke 'list'
337
+ $stdout.string.chomp.should == 'No sheets found'
338
+ end
339
+ end
340
+
341
+ describe "with sheets defined" do
342
+ before do
343
+ Time.stub!(:now).and_return Time.parse("Oct 5 18:00:00 -0700 2008")
344
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-03 12:00:00',
345
+ :end => '2008-10-03 14:00:00')
346
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-03 12:00:00',
347
+ :end => '2008-10-03 14:00:00')
348
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-05 12:00:00',
349
+ :end => '2008-10-05 14:00:00')
350
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-05 14:00:00',
351
+ :end => nil)
352
+ create_entry( :sheet => 'Sheet 1', :start => '2008-10-03 16:00:00',
353
+ :end => '2008-10-03 18:00:00')
354
+ Timetrap.current_sheet = 'A Longly Named Sheet 2'
355
+ end
356
+ it "should list available timesheets" do
357
+ invoke 'list'
358
+ $stdout.string.should == <<-OUTPUT
359
+ Timesheet Running Today Total Time
360
+ *A Longly Named Sheet 2 4:00:00 6:00:00 10:00:00
361
+ Sheet 1 0:00:00 0:00:00 2:00:00
362
+ OUTPUT
363
+ end
364
+ end
365
+ end
366
+
367
+ describe "now" do
368
+ before do
369
+ Timetrap.current_sheet = 'current sheet'
370
+ end
371
+
372
+ describe "when the current timesheet isn't running" do
373
+ it "should show that it isn't running" do
374
+ invoke 'now'
375
+ $stdout.string.should == <<-OUTPUT
376
+ current sheet: not running
377
+ OUTPUT
378
+ end
379
+ end
380
+
381
+ describe "when the current timesheet is running" do
382
+ before do
383
+ invoke 'in a timesheet that is running'
384
+ @entry = Timetrap.active_entry
385
+ @entry.stub!(:start).and_return(Time.at(0))
386
+ Time.stub!(:now).and_return Time.at(60)
387
+ Timetrap.stub!(:active_entry).and_return @entry
388
+ end
389
+
390
+ it "should show how long the current item is running for" do
391
+ invoke 'now'
392
+ $stdout.string.should == <<-OUTPUT
393
+ current sheet: 0:01:00 (a timesheet that is running)
394
+ OUTPUT
395
+ end
396
+ end
397
+ end
398
+
399
+ describe "out" do
400
+ before :each do
401
+ invoke 'in'
402
+ @active = Timetrap.active_entry
403
+ @now = Time.now
404
+ Time.stub!(:now).and_return @now
405
+ end
406
+ it "should set the stop for the running entry" do
407
+ @active.refresh.end.should == nil
408
+ invoke 'out'
409
+ @active.refresh.end.to_i.should == @now.to_i
410
+ end
411
+
412
+ it "should not do anything if nothing is running" do
413
+ lambda do
414
+ invoke 'out'
415
+ invoke 'out'
416
+ end.should_not raise_error
417
+ end
418
+
419
+ it "should allow the sheet to be stopped at a certain time" do
420
+ invoke 'out --at "10am 2008-10-03"'
421
+ Timetrap::Entry.order_by(:id).last.end.should == Time.parse('2008-10-03 10:00')
422
+ end
423
+ end
424
+
425
+ describe "running" do
426
+ it "should show all running timesheets" do
427
+ create_entry :sheet => 'one', :end => nil
428
+ create_entry :sheet => 'two', :end => nil
429
+ create_entry :sheet => 'three'
430
+ invoke 'running'
431
+ $stdout.string.should == "Running Timesheets:\n one: note\n two: note\n"
432
+ end
433
+ it "should show no runnig timesheets" do
434
+ invoke 'running'
435
+ $stdout.string.should == "Running Timesheets:\n"
436
+ end
437
+ end
438
+
439
+ describe "switch" do
440
+ it "should switch to a new timesheet" do
441
+ invoke 'switch sheet 1'
442
+ Timetrap.current_sheet.should == 'sheet 1'
443
+ invoke 'switch sheet 2'
444
+ Timetrap.current_sheet.should == 'sheet 2'
445
+ end
446
+
447
+ it "should not switch to an blank timesheet" do
448
+ invoke 'switch sheet 1'
449
+ invoke 'switch'
450
+ Timetrap.current_sheet.should == 'sheet 1'
451
+ end
452
+ end
453
+ end
454
+ end
455
+
456
+ describe "entries" do
457
+ it "should give the entires for a sheet" do
458
+ e = create_entry :sheet => 'sheet'
459
+ Timetrap.entries('sheet').all.should include(e)
460
+ end
461
+
462
+ end
463
+
464
+ describe "start" do
465
+ it "should start an new entry" do
466
+ @time = Time.now
467
+ Timetrap.current_sheet = 'sheet1'
468
+ lambda do
469
+ Timetrap.start 'some work', @time
470
+ end.should change(Timetrap::Entry, :count).by(1)
471
+ Timetrap::Entry.order(:id).last.sheet.should == 'sheet1'
472
+ Timetrap::Entry.order(:id).last.note.should == 'some work'
473
+ Timetrap::Entry.order(:id).last.start.to_i.should == @time.to_i
474
+ Timetrap::Entry.order(:id).last.end.should be_nil
475
+ end
476
+
477
+ it "should be running if it is started" do
478
+ Timetrap.should_not be_running
479
+ Timetrap.start 'some work', @time
480
+ Timetrap.should be_running
481
+ end
482
+
483
+ it "should raise and error if it is already running" do
484
+ lambda do
485
+ Timetrap.start 'some work', @time
486
+ Timetrap.start 'some work', @time
487
+ end.should change(Timetrap::Entry, :count).by(1)
488
+ end
489
+ end
490
+
491
+ describe "stop" do
492
+ it "should stop a new entry" do
493
+ @time = Time.now
494
+ Timetrap.start 'some work', @time
495
+ entry = Timetrap.active_entry
496
+ entry.end.should be_nil
497
+ Timetrap.stop @time
498
+ entry.refresh.end.to_i.should == @time.to_i
499
+ end
500
+
501
+ it "should not be running if it is stopped" do
502
+ Timetrap.should_not be_running
503
+ Timetrap.start 'some work', @time
504
+ Timetrap.stop
505
+ Timetrap.should_not be_running
506
+ end
507
+
508
+ it "should not stop it twice" do
509
+ Timetrap.start 'some work'
510
+ e = Timetrap.active_entry
511
+ Timetrap.stop
512
+ time = e.refresh.end
513
+ Timetrap.stop
514
+ time.to_i.should == e.refresh.end.to_i
515
+ end
516
+
517
+ end
518
+
519
+ describe 'switch' do
520
+ it "should switch to a new sheet" do
521
+ Timetrap.switch 'sheet1'
522
+ Timetrap.current_sheet.should == 'sheet1'
523
+ Timetrap.switch 'sheet2'
524
+ Timetrap.current_sheet.should == 'sheet2'
525
+ end
526
+ end
527
+
528
+ end
529
+
530
+ describe Timetrap::Entry do
531
+ describe "with an instance" do
532
+ before do
533
+ @time = Time.now
534
+ @entry = Timetrap::Entry.new
535
+ end
536
+
537
+ describe 'attributes' do
538
+ it "should have a note" do
539
+ @entry.note = "world takeover"
540
+ @entry.note.should == "world takeover"
541
+ end
542
+
543
+ it "should have a start" do
544
+ @entry.start = @time
545
+ @entry.start.should == @time
546
+ end
547
+
548
+ it "should have a end" do
549
+ @entry.end = @time
550
+ @entry.end.should == @time
551
+ end
552
+
553
+ it "should have a sheet" do
554
+ @entry.sheet= 'name'
555
+ @entry.sheet.should == 'name'
556
+ end
557
+
558
+ def with_global_round_set_to val
559
+ old_val = Timetrap::Entry.round
560
+ begin
561
+ Timetrap::Entry.round = val
562
+ block_return_value = yield
563
+ ensure
564
+ Timetrap::Entry.round = old_val
565
+ end
566
+ end
567
+
568
+ it "should use round start if the global round attribute is set" do
569
+ with_global_round_set_to true do
570
+ @time = Chronic.parse("12:55am")
571
+ @entry.start = @time
572
+ @entry.start.should == Chronic.parse("1am")
573
+ end
574
+ end
575
+
576
+ it "should use round start if the global round attribute is set" do
577
+ with_global_round_set_to true do
578
+ @time = Chronic.parse("12:50am")
579
+ @entry.start = @time
580
+ @entry.start.should == Chronic.parse("12:45am")
581
+ end
582
+ end
583
+
584
+ it "should have a rounded start" do
585
+ @time = Chronic.parse("12:50am")
586
+ @entry.start = @time
587
+ @entry.rounded_start.should == Chronic.parse("12:45am")
588
+ end
589
+
590
+ it "should not round nil times" do
591
+ @entry.start = nil
592
+ @entry.rounded_start.should be_nil
593
+ end
594
+ end
595
+
596
+ describe "parsing natural language times" do
597
+ it "should set start time using english" do
598
+ @entry.start = "yesterday 10am"
599
+ @entry.start.should_not be_nil
600
+ @entry.start.should == Chronic.parse("yesterday 10am")
601
+ end
602
+
603
+ it "should set end time using english" do
604
+ @entry.end = "tomorrow 1pm"
605
+ @entry.end.should_not be_nil
606
+ @entry.end.should == Chronic.parse("tomorrow 1pm")
607
+ end
608
+ end
609
+ end
610
+
611
+ describe '::sheets' do
612
+ it "should output a list of all the available sheets" do
613
+ Timetrap::Entry.create( :sheet => 'another',
614
+ :note => 'entry 4', :start => '2008-10-05 18:00:00'
615
+ )
616
+ Timetrap::Entry.create( :sheet => 'SpecSheet',
617
+ :note => 'entry 2', :start => '2008-10-03 16:00:00', :end => '2008-10-03 18:00:00'
618
+ )
619
+ Timetrap::Entry.sheets.should == %w(another SpecSheet).sort
620
+ end
621
+ end
622
+ end