watch_tower 0.0.0.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.
- data/.gitignore +10 -0
- data/.todo +33 -0
- data/.travis.yml +14 -0
- data/Gemfile +43 -0
- data/Guardfile +25 -0
- data/MIT-LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +8 -0
- data/TODO +17 -0
- data/bin/watchtower +10 -0
- data/ci/adapters/jruby-mysql.yml +8 -0
- data/ci/adapters/jruby-postgresql.yml +6 -0
- data/ci/adapters/jruby-sqlite.yml +6 -0
- data/ci/adapters/ruby-mysql.yml +8 -0
- data/ci/adapters/ruby-postgresql.yml +6 -0
- data/ci/adapters/ruby-sqlite.yml +6 -0
- data/ci/travis.rb +102 -0
- data/lib/watch_tower.rb +60 -0
- data/lib/watch_tower/appscript.rb +22 -0
- data/lib/watch_tower/cli.rb +15 -0
- data/lib/watch_tower/cli/.gitkeep +0 -0
- data/lib/watch_tower/cli/install.rb +63 -0
- data/lib/watch_tower/cli/open.rb +24 -0
- data/lib/watch_tower/cli/start.rb +140 -0
- data/lib/watch_tower/config.rb +38 -0
- data/lib/watch_tower/core_ext.rb +4 -0
- data/lib/watch_tower/core_ext/.gitkeep +0 -0
- data/lib/watch_tower/editor.rb +17 -0
- data/lib/watch_tower/editor/.gitkeep +0 -0
- data/lib/watch_tower/editor/base_appscript.rb +34 -0
- data/lib/watch_tower/editor/base_ps.rb +6 -0
- data/lib/watch_tower/editor/textmate.rb +17 -0
- data/lib/watch_tower/editor/xcode.rb +22 -0
- data/lib/watch_tower/errors.rb +25 -0
- data/lib/watch_tower/eye.rb +79 -0
- data/lib/watch_tower/project.rb +14 -0
- data/lib/watch_tower/project/.gitkeep +0 -0
- data/lib/watch_tower/project/any_based.rb +22 -0
- data/lib/watch_tower/project/git_based.rb +86 -0
- data/lib/watch_tower/project/init.rb +38 -0
- data/lib/watch_tower/project/path_based.rb +144 -0
- data/lib/watch_tower/server.rb +62 -0
- data/lib/watch_tower/server/.gitkeep +0 -0
- data/lib/watch_tower/server/app.rb +37 -0
- data/lib/watch_tower/server/assets/images/WatchTower.jpg +0 -0
- data/lib/watch_tower/server/assets/images/percentage.png +0 -0
- data/lib/watch_tower/server/assets/javascripts/application.js +3 -0
- data/lib/watch_tower/server/assets/javascripts/percentage.coffee +8 -0
- data/lib/watch_tower/server/assets/stylesheets/application.css +7 -0
- data/lib/watch_tower/server/assets/stylesheets/global.sass +71 -0
- data/lib/watch_tower/server/assets/stylesheets/project.sass +59 -0
- data/lib/watch_tower/server/configurations.rb +10 -0
- data/lib/watch_tower/server/configurations/asset.rb +43 -0
- data/lib/watch_tower/server/database.rb +105 -0
- data/lib/watch_tower/server/db/migrate/001_create_projects.rb +15 -0
- data/lib/watch_tower/server/db/migrate/002_create_files.rb +16 -0
- data/lib/watch_tower/server/db/migrate/003_create_time_entries.rb +12 -0
- data/lib/watch_tower/server/db/migrate/004_create_durations.rb +14 -0
- data/lib/watch_tower/server/db/migrate/005_add_hash_to_time_entries.rb +6 -0
- data/lib/watch_tower/server/db/migrate/006_add_hash_to_files.rb +6 -0
- data/lib/watch_tower/server/decorator.rb +21 -0
- data/lib/watch_tower/server/decorator/application_decorator.rb +91 -0
- data/lib/watch_tower/server/decorator/file_decorator.rb +38 -0
- data/lib/watch_tower/server/decorator/project_decorator.rb +51 -0
- data/lib/watch_tower/server/helpers.rb +13 -0
- data/lib/watch_tower/server/helpers/asset.rb +29 -0
- data/lib/watch_tower/server/helpers/improved_partials.rb +41 -0
- data/lib/watch_tower/server/models/duration.rb +11 -0
- data/lib/watch_tower/server/models/file.rb +31 -0
- data/lib/watch_tower/server/models/project.rb +17 -0
- data/lib/watch_tower/server/models/time_entry.rb +64 -0
- data/lib/watch_tower/server/public/assets/WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg +0 -0
- data/lib/watch_tower/server/public/assets/application-7829b53b5ece1a16d22dc3d00f329023.css +107 -0
- data/lib/watch_tower/server/public/assets/application-e0e6b7731aade460f680331e65cf0682.js +9359 -0
- data/lib/watch_tower/server/public/assets/percentage-d8589e21a5fc85d32a445f531ff8ab95.png +0 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery-ui.js +11729 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery.js +8981 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery_ujs.js +363 -0
- data/lib/watch_tower/server/views/.gitkeep +0 -0
- data/lib/watch_tower/server/views/_file.haml +9 -0
- data/lib/watch_tower/server/views/_project.haml +13 -0
- data/lib/watch_tower/server/views/index.haml +7 -0
- data/lib/watch_tower/server/views/layout.haml +32 -0
- data/lib/watch_tower/server/views/project.haml +12 -0
- data/lib/watch_tower/templates/config.yml +146 -0
- data/lib/watch_tower/templates/watchtower.plist +23 -0
- data/lib/watch_tower/version.rb +8 -0
- data/spec/factories.rb +45 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_record.rb +44 -0
- data/spec/support/factory_girl.rb +6 -0
- data/spec/support/launchy.rb +3 -0
- data/spec/support/sinatra.rb +10 -0
- data/spec/support/timecop.rb +7 -0
- data/spec/watch_tower/appscript_spec.rb +6 -0
- data/spec/watch_tower/cli/install_spec.rb +16 -0
- data/spec/watch_tower/cli/open_spec.rb +14 -0
- data/spec/watch_tower/cli/start_spec.rb +17 -0
- data/spec/watch_tower/cli_spec.rb +15 -0
- data/spec/watch_tower/config_spec.rb +25 -0
- data/spec/watch_tower/editor/textmate_spec.rb +43 -0
- data/spec/watch_tower/editor/xcode_spec.rb +43 -0
- data/spec/watch_tower/editor_spec.rb +19 -0
- data/spec/watch_tower/eye_spec.rb +130 -0
- data/spec/watch_tower/project/git_based_spec.rb +131 -0
- data/spec/watch_tower/project/path_based_spec.rb +111 -0
- data/spec/watch_tower/project_spec.rb +82 -0
- data/spec/watch_tower/server/app_spec.rb +186 -0
- data/spec/watch_tower/server/decorator/project_decorator_spec.rb +60 -0
- data/spec/watch_tower/server/models/file_spec.rb +284 -0
- data/spec/watch_tower/server/models/project_spec.rb +165 -0
- data/spec/watch_tower/server/models/time_entry_spec.rb +37 -0
- data/spec/watch_tower/server_spec.rb +4 -0
- data/watch_tower.gemspec +80 -0
- metadata +450 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Server
|
|
4
|
+
module Decorator
|
|
5
|
+
describe ProjectDecorator do
|
|
6
|
+
before(:each) do
|
|
7
|
+
@project = FactoryGirl.create :project
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
subject { ProjectDecorator.find(@project.id) }
|
|
11
|
+
|
|
12
|
+
it "should return a formatted elapsed time" do
|
|
13
|
+
time = 1.day + 2.hours + 3.minutes + 34.seconds
|
|
14
|
+
subject.stubs(:elapsed_time).returns(time)
|
|
15
|
+
subject.elapsed.should == '1 day, 2 hours, 3 minutes and 34 seconds'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "Application Decorator" do
|
|
19
|
+
describe "#humanize_time" do
|
|
20
|
+
it "should return 1 second" do
|
|
21
|
+
subject.send(:humanize_time, 1).should == '1 second'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should return 2 seconds" do
|
|
25
|
+
subject.send(:humanize_time, 2).should == '2 seconds'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should return 1 minute" do
|
|
29
|
+
subject.send(:humanize_time, 1.minute).should == '1 minute'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should return 2 minutes" do
|
|
33
|
+
subject.send(:humanize_time, 2.minutes).should == '2 minutes'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should return 1 hour" do
|
|
37
|
+
subject.send(:humanize_time, 1.hour).should == '1 hour'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should return 2 hours" do
|
|
41
|
+
subject.send(:humanize_time, 2.hours).should == '2 hours'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "should return 1 day" do
|
|
45
|
+
subject.send(:humanize_time, 1.day).should == '1 day'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should return 2 days" do
|
|
49
|
+
subject.send(:humanize_time, 2.days).should == '2 days'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should return 1 day, 2 hours, 3 minutes and 34 seconds" do
|
|
53
|
+
time = 1.day + 2.hours + 3.minutes + 34.seconds
|
|
54
|
+
subject.send(:humanize_time, time).should == '1 day, 2 hours, 3 minutes and 34 seconds'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Server
|
|
4
|
+
describe File do
|
|
5
|
+
describe "Attributes" do
|
|
6
|
+
it { should respond_to :path }
|
|
7
|
+
it { should respond_to :elapsed_time }
|
|
8
|
+
it { should respond_to :time_entries_count }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "Validations" do
|
|
12
|
+
it { should_not be_valid }
|
|
13
|
+
|
|
14
|
+
it "should require an path" do
|
|
15
|
+
m = FactoryGirl.build :file, path: nil
|
|
16
|
+
m.should_not be_valid
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should require a project" do
|
|
20
|
+
m = FactoryGirl.build :file, project: nil
|
|
21
|
+
m.should_not be_valid
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should not require a hash" do
|
|
25
|
+
m = FactoryGirl.build :file, file_hash: nil
|
|
26
|
+
m.should be_valid
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should have a unique path for each project" do
|
|
30
|
+
p = FactoryGirl.create :project
|
|
31
|
+
FactoryGirl.create :file, path: '/path/to/file.rb', project: p
|
|
32
|
+
m = FactoryGirl.build :file, path: '/path/to/file.rb', project: p
|
|
33
|
+
m.should_not be_valid
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should be valid if attributes requirements are met" do
|
|
37
|
+
m = FactoryGirl.build :file
|
|
38
|
+
m.should be_valid
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "Associations" do
|
|
43
|
+
before(:each) do
|
|
44
|
+
@file = FactoryGirl.create :file
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it { should respond_to :project }
|
|
48
|
+
it { should respond_to :time_entries }
|
|
49
|
+
|
|
50
|
+
it "should increment the counter cache" do
|
|
51
|
+
10.times do
|
|
52
|
+
FactoryGirl.create :time_entry, file: @file
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@file.reload.time_entries_count.should == 10
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should belong to a project" do
|
|
59
|
+
project = FactoryGirl.create :project
|
|
60
|
+
file = FactoryGirl.create :file, project: project
|
|
61
|
+
|
|
62
|
+
file.project.should == project
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe "TimeEntries count" do
|
|
67
|
+
before(:each) do
|
|
68
|
+
@file = FactoryGirl.create :file
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should calculate elapsed time" do
|
|
72
|
+
3.times do
|
|
73
|
+
Timecop.freeze(Time.now + 1)
|
|
74
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@file.reload
|
|
78
|
+
@file.elapsed_time.should == 2
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should skip the one with pause time" do
|
|
82
|
+
3.times do
|
|
83
|
+
Timecop.freeze(Time.now + 1)
|
|
84
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Timecop.freeze(Time.now + @file.time_entries.first.send(:pause_time) + 1)
|
|
88
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
89
|
+
|
|
90
|
+
3.times do
|
|
91
|
+
Timecop.freeze(Time.now + 1)
|
|
92
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@file.reload
|
|
96
|
+
@file.elapsed_time.should == 5
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should not count the time entries of another file" do
|
|
100
|
+
3.times do
|
|
101
|
+
Timecop.freeze(Time.now + 1)
|
|
102
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
3.times do
|
|
106
|
+
Timecop.freeze(Time.now + 1)
|
|
107
|
+
FactoryGirl.create :time_entry, mtime: Time.now
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@file.reload
|
|
111
|
+
@file.elapsed_time.should == 2
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "should add duration to a different day (2 days)." do
|
|
115
|
+
# Create initial time entries
|
|
116
|
+
3.times do
|
|
117
|
+
Timecop.freeze(Time.now + 1)
|
|
118
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Go ahead one day
|
|
122
|
+
Timecop.freeze(Time.now + 1.day)
|
|
123
|
+
|
|
124
|
+
# Add 3 more time entries
|
|
125
|
+
3.times do
|
|
126
|
+
Timecop.freeze(Time.now + 1)
|
|
127
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
@file.reload
|
|
131
|
+
@file.elapsed_time.should == 4
|
|
132
|
+
@file.durations.size.should == 2
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "should correctly record dates" do
|
|
136
|
+
# Record the date of the first day
|
|
137
|
+
Timecop.freeze(Time.now)
|
|
138
|
+
current_day = Time.now.to_date
|
|
139
|
+
|
|
140
|
+
# Create initial time entries
|
|
141
|
+
3.times do
|
|
142
|
+
Timecop.freeze(Time.now + 1)
|
|
143
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Go ahead one day
|
|
147
|
+
Timecop.freeze(Time.now + 1.day)
|
|
148
|
+
next_day = Time.now.to_date
|
|
149
|
+
|
|
150
|
+
# Add 3 more time entries
|
|
151
|
+
3.times do
|
|
152
|
+
Timecop.freeze(Time.now + 1)
|
|
153
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
@file.reload
|
|
157
|
+
@file.durations.order('id ASC').first.date.should == current_day
|
|
158
|
+
@file.durations.order('id ASC').last.date.should == next_day
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "should not add elapsed time to the count if the file's has has not changed" do
|
|
162
|
+
# Freeze the time
|
|
163
|
+
Timecop.freeze(Time.now)
|
|
164
|
+
# Create the first time entry
|
|
165
|
+
t1 = FactoryGirl.create :time_entry, file: @file, file_hash: @file.file_hash
|
|
166
|
+
# Go ahead 10 minutes
|
|
167
|
+
Timecop.freeze(Time.now + 10.minutes)
|
|
168
|
+
# Create the second time entry with the same file and hash
|
|
169
|
+
FactoryGirl.create :time_entry, file: @file, file_hash: @file.file_hash
|
|
170
|
+
|
|
171
|
+
@file.elapsed_time.should == 0
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "should update the hash of the file with the last time entry" do
|
|
175
|
+
# Freeze the time
|
|
176
|
+
Timecop.freeze(Time.now)
|
|
177
|
+
# Create the first time entry
|
|
178
|
+
t1 = FactoryGirl.create :time_entry, file: @file
|
|
179
|
+
# Go ahead 10 minutes
|
|
180
|
+
Timecop.freeze(Time.now + 10.minutes)
|
|
181
|
+
# Create the second time entry with the same file and hash
|
|
182
|
+
FactoryGirl.create :time_entry, file: @file, file_hash: t1.file_hash
|
|
183
|
+
|
|
184
|
+
@file.file_hash.should == t1.file_hash
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
describe "scopes" do
|
|
190
|
+
before(:each) do
|
|
191
|
+
@files = 3.times.collect{ FactoryGirl.create :file }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "should have the default scope to" do
|
|
195
|
+
10.times do |n|
|
|
196
|
+
file = @files.first
|
|
197
|
+
Timecop.freeze(Time.now + n * 2)
|
|
198
|
+
FactoryGirl.create :time_entry, file: file, mtime: Time.now
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
10.times do |n|
|
|
202
|
+
file = @files[1]
|
|
203
|
+
Timecop.freeze(Time.now + n * 10)
|
|
204
|
+
FactoryGirl.create :time_entry, file: file, mtime: Time.now
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
10.times do |n|
|
|
208
|
+
file = @files.last
|
|
209
|
+
Timecop.freeze(Time.now + n * 5)
|
|
210
|
+
FactoryGirl.create :time_entry, file: file, mtime: Time.now
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
File.all.first.should == @files[1]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it "should have a scope worked_on" do
|
|
217
|
+
File.should respond_to(:worked_on)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "should have a scope worked_on that returns all projects that do not have empty time_entries" do
|
|
221
|
+
5.times do
|
|
222
|
+
FactoryGirl.create :time_entry, file: @files[0]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
5.times do
|
|
226
|
+
FactoryGirl.create :time_entry, file: @files[1]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
File.worked_on.should_not include(@files.last)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe "Methods" do
|
|
235
|
+
before(:each) do
|
|
236
|
+
@project = FactoryGirl.create :project
|
|
237
|
+
@files = []
|
|
238
|
+
2.times do
|
|
239
|
+
@files << FactoryGirl.create(:file, project: @project)
|
|
240
|
+
2.times do
|
|
241
|
+
FactoryGirl.create :time_entry, file: @files.last
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
describe "one project" do
|
|
247
|
+
describe "#sum_elapsed_time" do
|
|
248
|
+
it "should return the sum of all elapsed times of all the files for the same project" do
|
|
249
|
+
@project.files.sum_elapsed_time.should == @files.inject(0) {|s, f| s += f.elapsed_time }
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
describe "#percent" do
|
|
254
|
+
it "should return 50" do
|
|
255
|
+
@project.files.first.percent.should == 50
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
describe "two projects" do
|
|
261
|
+
before(:each) do
|
|
262
|
+
@other_files = []
|
|
263
|
+
2.times do
|
|
264
|
+
@other_files << FactoryGirl.create(:file)
|
|
265
|
+
2.times do
|
|
266
|
+
FactoryGirl.create :time_entry, file: @other_files.last
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
describe "#sum_elapsed_time" do
|
|
271
|
+
it "should return the sum of all elapsed times of all the files for the same project" do
|
|
272
|
+
@project.files.sum_elapsed_time.should == @files.inject(0) {|s, f| s += f.elapsed_time }
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
describe "#percent" do
|
|
277
|
+
it "should return 50" do
|
|
278
|
+
@project.files.first.percent.should == 50
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Server
|
|
4
|
+
describe Project do
|
|
5
|
+
describe "Attributes" do
|
|
6
|
+
it { should respond_to :name }
|
|
7
|
+
it { should respond_to :path }
|
|
8
|
+
it { should respond_to :files_count }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "Methods" do
|
|
12
|
+
it { should respond_to :elapsed_time }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "Validations" do
|
|
16
|
+
it { should_not be_valid }
|
|
17
|
+
|
|
18
|
+
it "should require a name" do
|
|
19
|
+
m = FactoryGirl.build :project, name: nil
|
|
20
|
+
m.should_not be_valid
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should require a path" do
|
|
24
|
+
m = FactoryGirl.build :project, path: nil
|
|
25
|
+
m.should_not be_valid
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should be valid if attributes requirements are met" do
|
|
29
|
+
m = FactoryGirl.build :project
|
|
30
|
+
m.should be_valid
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "Associations" do
|
|
35
|
+
before(:each) do
|
|
36
|
+
@project = FactoryGirl.create :project
|
|
37
|
+
end
|
|
38
|
+
it { should respond_to :files }
|
|
39
|
+
it { should respond_to :time_entries }
|
|
40
|
+
|
|
41
|
+
it "should increment the counter cache" do
|
|
42
|
+
10.times do
|
|
43
|
+
FactoryGirl.create :file, project: @project
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@project.reload.files_count.should == 10
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should have time_entries through files" # do
|
|
50
|
+
# file1 = FactoryGirl.create :file, project: @project
|
|
51
|
+
# file2 = FactoryGirl.create :file, project: @project
|
|
52
|
+
#
|
|
53
|
+
# 10.times do
|
|
54
|
+
# FactoryGirl.create :time_entry, file: file1
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# 10.times do
|
|
58
|
+
# FactoryGirl.create :time_entry, file: file2
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# @project.reload.time_entries.size.should == 20
|
|
62
|
+
# end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "TimeEntries count" do
|
|
66
|
+
before(:each) do
|
|
67
|
+
@project = FactoryGirl.create :project
|
|
68
|
+
@file = FactoryGirl.create :file, project: @project
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should calculate elapsed time" do
|
|
72
|
+
3.times do
|
|
73
|
+
Timecop.freeze(Time.now + 1)
|
|
74
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@project.reload
|
|
78
|
+
@project.elapsed_time.should == 2
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should skip the one with pause time" do
|
|
82
|
+
3.times do
|
|
83
|
+
Timecop.freeze(Time.now + 1)
|
|
84
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Timecop.freeze(Time.now + @file.time_entries.first.send(:pause_time) + 1)
|
|
88
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
89
|
+
|
|
90
|
+
3.times do
|
|
91
|
+
Timecop.freeze(Time.now + 1)
|
|
92
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@project.reload
|
|
96
|
+
@project.elapsed_time.should == 5
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should not count the time entries of another file" do
|
|
100
|
+
3.times do
|
|
101
|
+
Timecop.freeze(Time.now + 1)
|
|
102
|
+
FactoryGirl.create :time_entry, file: @file, mtime: Time.now
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
3.times do
|
|
106
|
+
Timecop.freeze(Time.now + 1)
|
|
107
|
+
FactoryGirl.create :time_entry, mtime: Time.now
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@project.reload
|
|
111
|
+
@project.elapsed_time.should == 2
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "scopes" do
|
|
116
|
+
before(:each) do
|
|
117
|
+
@projects = 3.times.collect do
|
|
118
|
+
project = FactoryGirl.create :project
|
|
119
|
+
FactoryGirl.create :file, project: project
|
|
120
|
+
project.reload
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "should have the default scope to" do
|
|
125
|
+
file = @projects.first.files.first
|
|
126
|
+
10.times do |n|
|
|
127
|
+
Timecop.freeze(Time.now + n * 2)
|
|
128
|
+
FactoryGirl.create :time_entry, file: file, mtime: Time.now
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
file = @projects[1].files.first
|
|
132
|
+
10.times do |n|
|
|
133
|
+
Timecop.freeze(Time.now + n * 10)
|
|
134
|
+
FactoryGirl.create :time_entry, file: file, mtime: Time.now
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
file = @projects.last.files.first
|
|
138
|
+
10.times do |n|
|
|
139
|
+
Timecop.freeze(Time.now + n * 5)
|
|
140
|
+
FactoryGirl.create :time_entry, file: file, mtime: Time.now
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
Project.all.first.should == @projects[1]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "should have a scope worked_on" do
|
|
147
|
+
Project.should respond_to(:worked_on)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "should have a scope worked_on that returns all projects that do not have empty time_entries" do
|
|
151
|
+
file = @projects[0].files.first
|
|
152
|
+
5.times do
|
|
153
|
+
FactoryGirl.create :time_entry, file: file
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
file = @projects[1].files.first
|
|
157
|
+
5.times do
|
|
158
|
+
FactoryGirl.create :time_entry, file: file
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
Project.worked_on.should_not include(@projects.last)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|