timetrap 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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