tracco 0.0.9

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.
Files changed (58) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +3 -0
  3. data/.rvmrc.template +2 -0
  4. data/.travis.yml +20 -0
  5. data/CHANGELOG +32 -0
  6. data/Gemfile +9 -0
  7. data/Gemfile.lock +110 -0
  8. data/LICENSE.txt +15 -0
  9. data/README.md +292 -0
  10. data/Rakefile +8 -0
  11. data/config/config.template.yml +6 -0
  12. data/config/config.yml.trackinguser_for_test +7 -0
  13. data/config/mongoid.template.yml +24 -0
  14. data/lib/patches/trello/card.rb +19 -0
  15. data/lib/patches/trello/member.rb +20 -0
  16. data/lib/startup_trello.rb +2 -0
  17. data/lib/tasks/rspec.rake +17 -0
  18. data/lib/tasks/tasks.rake +55 -0
  19. data/lib/tracco.rb +30 -0
  20. data/lib/tracco/effort.rb +35 -0
  21. data/lib/tracco/estimate.rb +25 -0
  22. data/lib/tracco/google_docs_exporter.rb +67 -0
  23. data/lib/tracco/member.rb +61 -0
  24. data/lib/tracco/mongoid_helper.rb +13 -0
  25. data/lib/tracco/tracked_card.rb +121 -0
  26. data/lib/tracco/tracking/base.rb +82 -0
  27. data/lib/tracco/tracking/card_done_tracking.rb +10 -0
  28. data/lib/tracco/tracking/effort_tracking.rb +45 -0
  29. data/lib/tracco/tracking/estimate_tracking.rb +23 -0
  30. data/lib/tracco/tracking/invalid_tracking.rb +19 -0
  31. data/lib/tracco/tracking_factory.rb +22 -0
  32. data/lib/tracco/trello_authorize.rb +16 -0
  33. data/lib/tracco/trello_configuration.rb +39 -0
  34. data/lib/tracco/trello_tracker.rb +44 -0
  35. data/lib/tracco/version.rb +3 -0
  36. data/script/ci/before_script.sh +1 -0
  37. data/script/ci/run_build.sh +2 -0
  38. data/script/crontab.template +8 -0
  39. data/script/mate.sh +1 -0
  40. data/spec/effort_spec.rb +59 -0
  41. data/spec/estimate_spec.rb +38 -0
  42. data/spec/integration/trello_authorization_spec.rb +12 -0
  43. data/spec/integration/trello_tracker_spec.rb +66 -0
  44. data/spec/member_spec.rb +81 -0
  45. data/spec/patches/trello/card_spec.rb +25 -0
  46. data/spec/spec_helper.rb +66 -0
  47. data/spec/support/database_cleaner.rb +12 -0
  48. data/spec/tracked_card_spec.rb +336 -0
  49. data/spec/tracking/card_done_tracking_spec.rb +18 -0
  50. data/spec/tracking/effort_tracking_spec.rb +114 -0
  51. data/spec/tracking/estimate_tracking_spec.rb +44 -0
  52. data/spec/tracking_factory_spec.rb +42 -0
  53. data/spec/trello_authorize_spec.rb +65 -0
  54. data/spec/trello_configuration_spec.rb +43 -0
  55. data/spec/trello_tracker_spec.rb +26 -0
  56. data/tracco.gemspec +41 -0
  57. data/tracco.sublime-project +10 -0
  58. metadata +316 -0
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe TrelloTracker do
5
+ # This integration test works on a real Trello board created just for this purpose
6
+ # (see https://trello.com/board/testingboard/50ff225c7162123e3600074f)
7
+ # The board has some cards in todo, one card in progress and one done
8
+
9
+ TODO_CARD_ID = "51062b71df2bfec47b0039fb"
10
+ IN_PROGRESS_CARD_ID = "51062b99d81a343121004046"
11
+ DONE_CARD_ID = "51062b936d2deed22100157e"
12
+ ANOTHER_DONE_CARD_ID = "510c46d7c7bc9ac6020007ab"
13
+
14
+ let(:config) {
15
+ # auth params for trackinguser_for_test/testinguser!
16
+ OpenStruct.new(tracker: "trackinguser_for_test",
17
+ dev_key: "ef7c400e711057d7ba5e00be20139a33",
18
+ token: "9047d8fdbfdc960d41910673e300516cc8630dd4967e9b418fc27e410516362e")
19
+ }
20
+
21
+ it "tracks some estimates and efforts", :needs_valid_configuration => true do
22
+ without_logging do
23
+ tracker = TrelloTracker.new(tracker_username: config.tracker, developer_public_key: config.dev_key, access_token_key: config.token)
24
+ tracker.track(DateTime.parse("2013-01-28"))
25
+ end
26
+
27
+ # a card to do
28
+ todo_card = TrackedCard.find_by_trello_id(TODO_CARD_ID)
29
+
30
+ todo_card.estimates.should have(1).estimate
31
+ todo_card.estimates.first.amount.should == 4
32
+ todo_card.should_not be_done
33
+
34
+ # a card in progress
35
+ in_progress_card = TrackedCard.find_by_trello_id(IN_PROGRESS_CARD_ID)
36
+
37
+ in_progress_card.estimates.should have(1).estimate
38
+ in_progress_card.estimates.should have(1).effort
39
+ in_progress_card.estimates.first.amount.should == 5
40
+ in_progress_card.efforts.first.amount.should == 1
41
+ in_progress_card.should_not be_done
42
+
43
+ # a done card
44
+ done_card = TrackedCard.find_by_trello_id(DONE_CARD_ID)
45
+ done_card.total_effort.should == 2
46
+ done_card.should be_done
47
+
48
+ # another done card (this time for the DONE column)
49
+ another_done_card = TrackedCard.find_by_trello_id(ANOTHER_DONE_CARD_ID)
50
+ another_done_card.should be_done
51
+ end
52
+
53
+
54
+ private
55
+
56
+ def without_logging(&block)
57
+ original_error_level = Trello.logger.level
58
+
59
+ begin
60
+ Trello.logger.level = Logger::WARN
61
+ block.call unless block.nil?
62
+ ensure
63
+ Trello.logger.level = original_error_level
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'mongoid-rspec'
3
+
4
+ describe Member do
5
+
6
+ it { should have_fields(:trello_id, :username, :full_name, :avatar_id, :bio, :url) }
7
+ it { should be_embedded_in(:effort) }
8
+
9
+ describe "validation" do
10
+ it { should validate_presence_of(:username) }
11
+ end
12
+
13
+ describe "equality" do
14
+ it "is equal to another member with the same username" do
15
+ member = Member.new(username: "piero")
16
+ same_member = Member.new(username: "piero")
17
+ different_member = Member.new(username: "tommaso")
18
+
19
+ member.should == same_member
20
+ member.should_not == different_member
21
+ end
22
+ end
23
+
24
+ describe ".build_from" do
25
+ it "builds a Member from a Trello Member" do
26
+ member = Member.build_from(Trello::Member.new("username" => "piero"))
27
+
28
+ member.username.should == "piero"
29
+ end
30
+
31
+ it "takes the Trello Member id and set it as trello_id" do
32
+ member = Member.build_from(Trello::Member.new("username" => "piero", "id" => "1234567abc"))
33
+
34
+ member.id.should_not == "1234567abc"
35
+ member.trello_id.should == "1234567abc"
36
+ end
37
+ end
38
+
39
+ describe "#effort_spent" do
40
+ %w{piero tommaso}.each do |username|
41
+ let(username.to_sym) { Member.new(username: username) }
42
+ end
43
+
44
+ let(:card) { TrackedCard.create(name: "any", short_id: 1234, trello_id: "123123") }
45
+ let(:another_card) { TrackedCard.create(name: "any_other", short_id: 1235, trello_id: "123125") }
46
+
47
+ it "is zero when the member did not spent effort at all" do
48
+ piero.effort_spent.should == 0
49
+ end
50
+
51
+ it "counts the effort spent on a card" do
52
+ card.efforts << Effort.new(amount: 4, date: Date.today, members: [piero, tommaso])
53
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
54
+
55
+ piero.effort_spent.should == 4 / 2
56
+ end
57
+
58
+ it "counts the effort spent on several cards" do
59
+ card.efforts << Effort.new(amount: 4, date: Date.today, members: [piero, tommaso])
60
+ another_card.efforts << Effort.new(amount: 5, date: Date.today, members: [piero])
61
+
62
+ piero.effort_spent.should == 2 + 5
63
+ end
64
+
65
+ it "counts the effort spent on a card from a given date" do
66
+ card.efforts << Effort.new(amount: 4, date: Date.yesterday, members: [piero])
67
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [piero])
68
+
69
+ piero.effort_spent(Date.today).should == 5
70
+ piero.effort_spent_since(Date.today).should == 5
71
+ end
72
+
73
+ end
74
+
75
+ describe "#avatar_url" do
76
+ it "points to the avatar thumbnail image" do
77
+ member = Member.new(avatar_id: "123xyz")
78
+ member.avatar_url.should == "https://trello-avatars.s3.amazonaws.com/123xyz/30.png"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Trello::Card do
4
+
5
+ describe "#in_done_column?" do
6
+
7
+ let(:trello_card) { Trello::Card.new("name" => "a name", "desc" => "any description") }
8
+
9
+ let(:todo_column) { Trello::List.new("name" => "ToDo") }
10
+ let(:done_column) { Trello::List.new("name" => "Done yesterday") }
11
+
12
+ it "is done when is in a DONE column" do
13
+ trello_card.stub(:list).and_return(done_column)
14
+
15
+ trello_card.in_done_column?.should be_true
16
+ end
17
+
18
+ it "is still not done when is not in DONE column" do
19
+ trello_card.stub(:list).and_return(todo_column)
20
+
21
+ trello_card.in_done_column?.should be_false
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ require 'rubygems'
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
6
+ # Set up gems listed in the Gemfile.
7
+ begin
8
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__))
9
+ require 'bundler'
10
+ Bundler.setup
11
+ rescue Bundler::GemNotFound => e
12
+ STDERR.puts e.message
13
+ STDERR.puts "Try running `bundle install`."
14
+ exit!
15
+ end
16
+
17
+ Bundler.require(:spec)
18
+
19
+ require 'tracco'
20
+
21
+ # Requires supporting ruby files with custom matchers and macros, etc,
22
+ # in spec/support/ and its subdirectories.
23
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
24
+
25
+ RSpec.configure do |configuration|
26
+ configuration.include Mongoid::Matchers
27
+ end
28
+
29
+ # force test env for the mongodb configuration
30
+ TrelloConfiguration::Database.load_env("test")
31
+
32
+
33
+ ## Spec Helper Methods (TODO: should we move them in a separate file?)
34
+
35
+ def unrecognized_notification
36
+ create_notification(data: { 'text' => '@trackinguser hi there!' })
37
+ end
38
+
39
+ def notification_with_message(message)
40
+ create_notification(data: { 'text' => message })
41
+ end
42
+
43
+ def create_estimate(time_measurement)
44
+ create_notification(data: { 'text' => "@trackinguser [1.5#{TIME_MEASUREMENTS[time_measurement]}]" })
45
+ end
46
+
47
+ def create_effort(time_measurement)
48
+ create_notification(data: { 'text' => "@trackinguser +4.5#{TIME_MEASUREMENTS[time_measurement]}]" })
49
+ end
50
+
51
+ def with(notification)
52
+ tracking = TrackingFactory.build_from(notification)
53
+ yield(tracking)
54
+ end
55
+
56
+ def with_message(notification_message, &block)
57
+ with(notification_with_message(notification_message), &block)
58
+ end
59
+
60
+ def create_notification(custom_params)
61
+ params = { data: { 'text' => "@trackinguser +2h" }, date: "2012-10-28T21:06:14.801Z", member_creator: stub(username: "pietrodibello") }
62
+ params.merge!(custom_params)
63
+
64
+ stub(data: params[:data], date: params[:date], member_creator: params[:member_creator]).as_null_object
65
+ end
66
+
@@ -0,0 +1,12 @@
1
+ require 'database_cleaner'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:suite) do
5
+ DatabaseCleaner.orm = 'mongoid'
6
+ DatabaseCleaner.strategy = :truncation
7
+ end
8
+
9
+ config.before(:each) do
10
+ DatabaseCleaner.clean
11
+ end
12
+ end
@@ -0,0 +1,336 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrackedCard do
4
+
5
+ before(:each) do
6
+ Date.stub(:today).and_return(Date.parse("2012-11-05"))
7
+ end
8
+
9
+ subject(:card) { TrackedCard.new(name: "any", short_id: 1234, trello_id: "123123") }
10
+
11
+ %w{piero tommaso michele}.each do |username|
12
+ let(username.to_sym) { Member.new(username: username) }
13
+ end
14
+
15
+ describe "validations" do
16
+ it "is not valid when a name is not given" do
17
+ TrackedCard.new(trello_id: "123456789", short_id: 1234).should_not be_valid
18
+ end
19
+
20
+ it "is not valid when a short_id is not given" do
21
+ TrackedCard.new(name: "any", trello_id: "123456789").should_not be_valid
22
+ end
23
+
24
+ it "is not valid when a trello_id is not given" do
25
+ TrackedCard.new(name: "any", short_id: 1234).should_not be_valid
26
+ end
27
+
28
+ it "is valid when has a name, a short id and a trello id" do
29
+ TrackedCard.new(name: "any", trello_id: "123456789", short_id: 1234).should be_valid
30
+ end
31
+ end
32
+
33
+ describe ".find_by_trello_id" do
34
+ it "find a card given its Trello id" do
35
+ card = TrackedCard.create(name: "any card", short_id: 1234, trello_id: "1")
36
+ another_card = TrackedCard.create(name: "another card", short_id: 3456, trello_id: "2")
37
+
38
+ TrackedCard.find_by_trello_id("1").should == card
39
+ TrackedCard.find_by_trello_id("3").should == nil
40
+ end
41
+ end
42
+
43
+ describe ".update_or_create_with" do
44
+ let(:trello_card) { Trello::Card.new("id" => "ABC123", "name" => "a name", "idShort" => 1, "desc" => "any description") }
45
+
46
+ before(:each) do
47
+ Trello::Card.any_instance.stub(:in_done_column?)
48
+ end
49
+
50
+ it "creates a tracked card on a given trello card" do
51
+ tracked_card = TrackedCard.update_or_create_with(trello_card)
52
+
53
+ tracked_card.name.should == "a name"
54
+ tracked_card.trello_id == "ABC123"
55
+ tracked_card.short_id == 1
56
+ end
57
+
58
+ it "updates an existing tracked card on a given trello card" do
59
+ existing_card = TrackedCard.create(name: "an old name", short_id: 1234, trello_id: trello_card.id)
60
+
61
+ updated_card = TrackedCard.update_or_create_with(trello_card)
62
+
63
+ updated_card.should == existing_card
64
+ updated_card.name.should == "a name"
65
+ end
66
+
67
+ it "is nil when the trello card is not valid" do
68
+ invalid_trello_card = Trello::Card.new("id" => nil, "name" => nil)
69
+
70
+ TrackedCard.update_or_create_with(invalid_trello_card).should be_nil
71
+ TrackedCard.all.should be_empty
72
+ end
73
+
74
+ it "tracks the card as done when the original trello card is moved in a DONE column" do
75
+ trello_card.stub(:in_done_column?).and_return(true)
76
+
77
+ tracked_card = TrackedCard.update_or_create_with(trello_card)
78
+
79
+ tracked_card.should be_done
80
+ end
81
+
82
+ it "tracks the card as NOT done when the original trello card is moved in a column different from DONE" do
83
+ trello_card.stub(:in_done_column?).and_return(false)
84
+
85
+ tracked_card = TrackedCard.update_or_create_with(trello_card)
86
+
87
+ tracked_card.should_not be_done
88
+ end
89
+
90
+ end
91
+
92
+ describe ".build_from" do
93
+ it "builds a TrackedCard from a Trello Card" do
94
+ tracked_card = TrackedCard.build_from(Trello::Card.new("name" => "a name", "desc" => "any description"))
95
+
96
+ tracked_card.name.should == "a name"
97
+ tracked_card.description.should == "any description"
98
+ end
99
+
100
+ it "takes the Trello Card id and set it as trello_id" do
101
+ tracked_card = TrackedCard.build_from(Trello::Card.new("id" => "abc123", "name" => "a name", "desc" => "any description"))
102
+
103
+ tracked_card.id.should_not == "abc123"
104
+ tracked_card.trello_id.should == "abc123"
105
+ end
106
+
107
+ end
108
+
109
+ it "has no estimates and efforts initially" do
110
+ card.estimates.should be_empty
111
+ card.efforts.should be_empty
112
+ end
113
+
114
+ it "is possible to add estimates" do
115
+ card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
116
+ card.estimates << Estimate.new(amount: 12, date: Date.today)
117
+
118
+ card.estimates.should have(2).estimates
119
+ end
120
+
121
+ it "is possible to add efforts" do
122
+ card.efforts << Effort.new(amount: 3, date: Date.today, members: [piero, tommaso])
123
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
124
+
125
+ card.efforts.should have(2).efforts
126
+ end
127
+
128
+ describe "equality" do
129
+ it "is equal to another TrelloCard when the trello id is the same" do
130
+ card = TrackedCard.new(name: "a name", trello_id: "123456789")
131
+ same_card = TrackedCard.new(name: "a name", trello_id: "123456789")
132
+ another_card = TrackedCard.new(name: "a name", trello_id: "987654321")
133
+
134
+ card.should == same_card
135
+ card.should_not == another_card
136
+ end
137
+ end
138
+
139
+ describe "#add" do
140
+ let(:card) { TrackedCard.new(name: "a name", trello_id: "123456789") }
141
+ let(:estimate_tracking) { Tracking::EstimateTracking.new(create_notification(data: { 'text' => "@trackinguser [1h]" })) }
142
+
143
+ it "adds an estimate from a tracking estimate notification" do
144
+ card.add(estimate_tracking)
145
+ card.estimates.should have(1).estimate
146
+ end
147
+
148
+ it "adds an estimate only once" do
149
+ card.add(estimate_tracking)
150
+ card.add(estimate_tracking)
151
+
152
+ card.estimates.should have(1).estimate
153
+ end
154
+
155
+ it "is done when has a DONE notification" do
156
+ card.should_not be_done
157
+
158
+ card.add(Tracking::CardDoneTracking.new(stub(:notification)))
159
+ card.should be_done
160
+ end
161
+
162
+ end
163
+
164
+ describe "#add!" do
165
+ let(:card) { TrackedCard.new(name: "a name", trello_id: "123456789", short_id: "123") }
166
+
167
+ it "saves the tracked card after adding the tracking" do
168
+ any_tracking = Tracking::EstimateTracking.new(create_notification(data: { 'text' => "@trackinguser [1h]" }))
169
+
170
+ card.add!(any_tracking)
171
+ card.reload.should_not be_nil
172
+ end
173
+ end
174
+
175
+ describe "#total_effort" do
176
+ it "is zero when there's no effort" do
177
+ card.total_effort.should == 0
178
+ end
179
+
180
+ it "computes the total effort on the card" do
181
+ card.efforts << Effort.new(amount: 3, date: Date.today, members: [piero, tommaso])
182
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
183
+
184
+ card.total_effort.should == 3+5
185
+ end
186
+ end
187
+
188
+ describe "#last_estimate_error" do
189
+ it "is nil when the card has no estimate" do
190
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
191
+
192
+ card.last_estimate_error.should be_nil
193
+ end
194
+
195
+ it "is nil when the card has no effort" do
196
+ card.efforts << Estimate.new(amount: 5, date: Date.today)
197
+
198
+ card.last_estimate_error.should be_nil
199
+ end
200
+
201
+ it "is zero when actual effort is equal to estimate" do
202
+ card.estimates << Estimate.new(amount: 5, date: Date.today)
203
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
204
+
205
+ card.last_estimate_error.should == 0.0
206
+ end
207
+
208
+ it "is 100 when the actual effort is twice the given estimate" do
209
+ card.estimates << Estimate.new(amount: 5, date: Date.today)
210
+ card.efforts << Effort.new(amount: 10, date: Date.today, members: [tommaso])
211
+
212
+ card.last_estimate_error.should == 100.0
213
+ end
214
+
215
+ it "is -50 when the actual effort is half of the given estimate" do
216
+ card.estimates << Estimate.new(amount: 10, date: Date.today)
217
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
218
+
219
+ card.last_estimate_error.should == -50.0
220
+ end
221
+
222
+ it "is rounded with two decimal digits" do
223
+ card.estimates << Estimate.new(amount: 3, date: Date.today)
224
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
225
+
226
+ card.last_estimate_error.should == 66.67
227
+ end
228
+
229
+ describe "#estimate_errors" do
230
+
231
+ it "collects all the estimate errors against the actual effort" do
232
+ card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
233
+ card.efforts << Effort.new(amount: 10, date: Date.yesterday, members: [tommaso])
234
+
235
+ card.estimates << Estimate.new(amount: 10, date: Date.today)
236
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
237
+
238
+ card.estimate_errors.should == [200.0, 50.0]
239
+ end
240
+ end
241
+
242
+ end
243
+
244
+ describe "#members" do
245
+ it "lists all the members which spent some effort on the card" do
246
+ card.efforts << Effort.new(amount: 3, date: Date.today, members: [piero, tommaso])
247
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
248
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso, michele])
249
+
250
+ card.members.should == [piero, tommaso, michele]
251
+ end
252
+ end
253
+
254
+ describe "#working_start_date" do
255
+
256
+ it "is the date of the first effort spent on the card" do
257
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
258
+ card.efforts << Effort.new(amount: 3, date: Date.yesterday, members: [tommaso])
259
+ card.efforts << Effort.new(amount: 5, date: Date.tomorrow, members: [tommaso])
260
+
261
+ card.working_start_date.should == Date.yesterday
262
+ end
263
+ end
264
+
265
+ describe "#first_activity_date" do
266
+ it "is the date of the first effort or estimate given on the card" do
267
+ card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
268
+ card.estimates << Estimate.new(amount: 12, date: Date.yesterday.prev_day)
269
+ card.estimates << Estimate.new(amount: 12, date: Date.tomorrow)
270
+
271
+ card.efforts << Effort.new(amount: 3, date: Date.yesterday, members: [tommaso])
272
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
273
+
274
+ card.first_activity_date.should == Date.yesterday.prev_day
275
+ end
276
+ end
277
+
278
+ describe "#first_estimate_date" do
279
+ it "is the date of the first estimate given on the card" do
280
+ card.estimates << Estimate.new(amount: 5, date: Date.tomorrow)
281
+ card.estimates << Estimate.new(amount: 12, date: Date.yesterday.prev_day)
282
+ card.estimates << Estimate.new(amount: 12, date: Date.today)
283
+
284
+ card.first_estimate_date.should == Date.yesterday.prev_day
285
+ end
286
+ end
287
+
288
+ describe "#last_estimate_date" do
289
+ it "is the date of the last estimate given on the card" do
290
+ card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
291
+ card.estimates << Estimate.new(amount: 12, date: Date.tomorrow)
292
+ card.estimates << Estimate.new(amount: 12, date: Date.today)
293
+
294
+ card.last_estimate_date.should == Date.tomorrow
295
+ end
296
+ end
297
+
298
+ describe "#no_tracking?" do
299
+ it "is false when there's no effort or estimate tracked on the card" do
300
+ card.no_tracking?.should be_true
301
+
302
+ card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
303
+ card.no_tracking?.should be_false
304
+ end
305
+ end
306
+
307
+ describe "#to_s" do
308
+ it "describes the card as a string" do
309
+ card = TrackedCard.new(name: "A Story Name")
310
+ card.estimates << Estimate.new(amount: 5, date: Date.today)
311
+ card.efforts << Effort.new(amount: 3, date: Date.today, members: [Member.new(username: "piero"), Member.new(username: "tommaso")])
312
+ card.efforts << Effort.new(amount: 6, date: Date.today, members: [Member.new(username: "piero"), Member.new(username: "tommaso")])
313
+
314
+ card.to_s.should == %Q{[A Story Name]. Total effort: 9.0h. Estimates ["[2012-11-05] estimated 5.0 hours"]. Efforts: ["[2012-11-05] spent 3.0 hours by @piero, @tommaso", "[2012-11-05] spent 6.0 hours by @piero, @tommaso"]}
315
+ end
316
+ end
317
+
318
+ describe "#status" do
319
+ it "is done when is done" do
320
+ done_card = TrackedCard.new(done:true)
321
+ done_card.status.should == :done
322
+
323
+ done_card.efforts << Effort.new(amount: 3, date: Date.today, members: [Member.new(username: "any")])
324
+ done_card.status.should == :done
325
+ end
326
+
327
+ it "is todo when no effort has been spent on the card" do
328
+ card = TrackedCard.new
329
+ card.status.should == :todo
330
+
331
+ card.efforts << Effort.new(amount: 3, date: Date.today, members: [Member.new(username: "any")])
332
+ card.status.should == :in_progress
333
+ end
334
+ end
335
+
336
+ end