stuff_to_do_plugin 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/COPYRIGHT.txt +18 -0
  2. data/CREDITS.txt +6 -0
  3. data/GPL.txt +339 -0
  4. data/README.rdoc +61 -0
  5. data/Rakefile +38 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/stuff_to_do_controller.rb +161 -0
  8. data/app/helpers/stuff_to_do_helper.rb +88 -0
  9. data/app/models/stuff_to_do.rb +208 -0
  10. data/app/models/stuff_to_do_filter.rb +32 -0
  11. data/app/models/stuff_to_do_mailer.rb +16 -0
  12. data/app/views/settings/_stuff_to_do_settings.html.erb +27 -0
  13. data/app/views/stuff_to_do/_issue.html.erb +16 -0
  14. data/app/views/stuff_to_do/_item.html.erb +5 -0
  15. data/app/views/stuff_to_do/_left_panes.html.erb +51 -0
  16. data/app/views/stuff_to_do/_panes.html.erb +11 -0
  17. data/app/views/stuff_to_do/_project.html.erb +6 -0
  18. data/app/views/stuff_to_do/_right_panes.html.erb +25 -0
  19. data/app/views/stuff_to_do/_time_grid.html.erb +113 -0
  20. data/app/views/stuff_to_do/_time_grid_form.html.erb +32 -0
  21. data/app/views/stuff_to_do/index.html.erb +44 -0
  22. data/app/views/stuff_to_do_mailer/recommended_below_threshold.erb +3 -0
  23. data/app/views/stuff_to_do_mailer/recommended_below_threshold.text.html.rhtml +1 -0
  24. data/assets/images/b.png +0 -0
  25. data/assets/images/bl.png +0 -0
  26. data/assets/images/br.png +0 -0
  27. data/assets/images/closelabel.gif +0 -0
  28. data/assets/images/loading.gif +0 -0
  29. data/assets/images/tl.png +0 -0
  30. data/assets/images/tr.png +0 -0
  31. data/assets/javascripts/facebox.js +319 -0
  32. data/assets/javascripts/jquery-1.2.6.min.js +32 -0
  33. data/assets/javascripts/jquery-ui.js +2839 -0
  34. data/assets/javascripts/jquery.contextMenu.js +212 -0
  35. data/assets/javascripts/semantic.cache +15 -0
  36. data/assets/javascripts/stuff-to-do.js +270 -0
  37. data/assets/javascripts/ui/build.xml +24 -0
  38. data/assets/javascripts/ui/effects.blind.js +49 -0
  39. data/assets/javascripts/ui/effects.bounce.js +78 -0
  40. data/assets/javascripts/ui/effects.clip.js +54 -0
  41. data/assets/javascripts/ui/effects.core.js +510 -0
  42. data/assets/javascripts/ui/effects.drop.js +50 -0
  43. data/assets/javascripts/ui/effects.explode.js +79 -0
  44. data/assets/javascripts/ui/effects.fold.js +55 -0
  45. data/assets/javascripts/ui/effects.highlight.js +48 -0
  46. data/assets/javascripts/ui/effects.pulsate.js +55 -0
  47. data/assets/javascripts/ui/effects.scale.js +180 -0
  48. data/assets/javascripts/ui/effects.shake.js +57 -0
  49. data/assets/javascripts/ui/effects.slide.js +50 -0
  50. data/assets/javascripts/ui/effects.transfer.js +59 -0
  51. data/assets/javascripts/ui/i18n/ui.datepicker-ar.js +26 -0
  52. data/assets/javascripts/ui/i18n/ui.datepicker-bg.js +25 -0
  53. data/assets/javascripts/ui/i18n/ui.datepicker-ca.js +25 -0
  54. data/assets/javascripts/ui/i18n/ui.datepicker-cs.js +25 -0
  55. data/assets/javascripts/ui/i18n/ui.datepicker-da.js +25 -0
  56. data/assets/javascripts/ui/i18n/ui.datepicker-de.js +25 -0
  57. data/assets/javascripts/ui/i18n/ui.datepicker-eo.js +25 -0
  58. data/assets/javascripts/ui/i18n/ui.datepicker-es.js +25 -0
  59. data/assets/javascripts/ui/i18n/ui.datepicker-fa.js +25 -0
  60. data/assets/javascripts/ui/i18n/ui.datepicker-fi.js +25 -0
  61. data/assets/javascripts/ui/i18n/ui.datepicker-fr.js +25 -0
  62. data/assets/javascripts/ui/i18n/ui.datepicker-he.js +25 -0
  63. data/assets/javascripts/ui/i18n/ui.datepicker-hr.js +25 -0
  64. data/assets/javascripts/ui/i18n/ui.datepicker-hu.js +25 -0
  65. data/assets/javascripts/ui/i18n/ui.datepicker-hy.js +25 -0
  66. data/assets/javascripts/ui/i18n/ui.datepicker-id.js +25 -0
  67. data/assets/javascripts/ui/i18n/ui.datepicker-is.js +25 -0
  68. data/assets/javascripts/ui/i18n/ui.datepicker-it.js +25 -0
  69. data/assets/javascripts/ui/i18n/ui.datepicker-ja.js +26 -0
  70. data/assets/javascripts/ui/i18n/ui.datepicker-ko.js +25 -0
  71. data/assets/javascripts/ui/i18n/ui.datepicker-lt.js +25 -0
  72. data/assets/javascripts/ui/i18n/ui.datepicker-lv.js +25 -0
  73. data/assets/javascripts/ui/i18n/ui.datepicker-nl.js +25 -0
  74. data/assets/javascripts/ui/i18n/ui.datepicker-no.js +25 -0
  75. data/assets/javascripts/ui/i18n/ui.datepicker-pl.js +25 -0
  76. data/assets/javascripts/ui/i18n/ui.datepicker-pt-BR.js +25 -0
  77. data/assets/javascripts/ui/i18n/ui.datepicker-ro.js +25 -0
  78. data/assets/javascripts/ui/i18n/ui.datepicker-ru.js +25 -0
  79. data/assets/javascripts/ui/i18n/ui.datepicker-sk.js +25 -0
  80. data/assets/javascripts/ui/i18n/ui.datepicker-sl.js +26 -0
  81. data/assets/javascripts/ui/i18n/ui.datepicker-sq.js +25 -0
  82. data/assets/javascripts/ui/i18n/ui.datepicker-sv.js +25 -0
  83. data/assets/javascripts/ui/i18n/ui.datepicker-th.js +25 -0
  84. data/assets/javascripts/ui/i18n/ui.datepicker-tr.js +25 -0
  85. data/assets/javascripts/ui/i18n/ui.datepicker-uk.js +25 -0
  86. data/assets/javascripts/ui/i18n/ui.datepicker-zh-CN.js +25 -0
  87. data/assets/javascripts/ui/i18n/ui.datepicker-zh-TW.js +25 -0
  88. data/assets/javascripts/ui/svn.log +11 -0
  89. data/assets/javascripts/ui/ui.accordion.js +400 -0
  90. data/assets/javascripts/ui/ui.core.js +533 -0
  91. data/assets/javascripts/ui/ui.datepicker.js +1754 -0
  92. data/assets/javascripts/ui/ui.dialog.js +630 -0
  93. data/assets/javascripts/ui/ui.draggable.js +696 -0
  94. data/assets/javascripts/ui/ui.droppable.js +314 -0
  95. data/assets/javascripts/ui/ui.progressbar.js +114 -0
  96. data/assets/javascripts/ui/ui.resizable.js +805 -0
  97. data/assets/javascripts/ui/ui.selectable.js +266 -0
  98. data/assets/javascripts/ui/ui.slider.js +552 -0
  99. data/assets/javascripts/ui/ui.sortable.js +1012 -0
  100. data/assets/javascripts/ui/ui.tabs.js +572 -0
  101. data/assets/stylesheets/stuff_to_do.css +216 -0
  102. data/config/locales/bg.yml +18 -0
  103. data/config/locales/ca-fr.yml +18 -0
  104. data/config/locales/cs.yml +16 -0
  105. data/config/locales/da.yml +16 -0
  106. data/config/locales/de.yml +18 -0
  107. data/config/locales/en.yml +24 -0
  108. data/config/locales/es.yml +19 -0
  109. data/config/locales/fr.yml +17 -0
  110. data/config/locales/hu.yml +16 -0
  111. data/config/locales/it.yml +16 -0
  112. data/config/locales/ja.yml +18 -0
  113. data/config/locales/ko.yml +18 -0
  114. data/config/locales/lt.yml +18 -0
  115. data/config/locales/nl.yml +20 -0
  116. data/config/locales/pt-BR.yml +18 -0
  117. data/config/locales/ru.yml +19 -0
  118. data/config/locales/sv.yml +19 -0
  119. data/config/locales/tr.yml +18 -0
  120. data/config/routes.rb +3 -0
  121. data/init.rb +54 -0
  122. data/lang/bg.yml +17 -0
  123. data/lang/ca-fr.yml +17 -0
  124. data/lang/cs.yml +15 -0
  125. data/lang/da.yml +15 -0
  126. data/lang/de.yml +17 -0
  127. data/lang/en.yml +21 -0
  128. data/lang/es.yml +18 -0
  129. data/lang/fr.yml +16 -0
  130. data/lang/hu.yml +15 -0
  131. data/lang/it.yml +15 -0
  132. data/lang/ja.yml +17 -0
  133. data/lang/ko.yml +17 -0
  134. data/lang/lt.yml +17 -0
  135. data/lang/pt-br.yml +17 -0
  136. data/lang/ru.yml +15 -0
  137. data/lang/sv.yml +18 -0
  138. data/lang/tr.yml +17 -0
  139. data/lib/redmine_stuff_to_do/stuff_to_do_compatibility.rb +15 -0
  140. data/lib/stuff_to_do_array_patch.rb +8 -0
  141. data/lib/stuff_to_do_issue_patch.rb +57 -0
  142. data/lib/stuff_to_do_project_patch.rb +31 -0
  143. data/lib/stuff_to_do_user_patch.rb +10 -0
  144. data/rails/init.rb +1 -0
  145. data/spec/controllers/stuff_to_do_controller_add_to_time_grid_spec.rb +58 -0
  146. data/spec/controllers/stuff_to_do_controller_index_spec.rb +155 -0
  147. data/spec/controllers/stuff_to_do_controller_remove_from_time_grid_spec.rb +56 -0
  148. data/spec/controllers/stuff_to_do_controller_reorder_spec.rb +179 -0
  149. data/spec/controllers/stuff_to_do_controller_save_time_entries_spec.rb +56 -0
  150. data/spec/controllers/stuff_to_do_private_methods_spec.rb +82 -0
  151. data/spec/lib/stuff_to_do_issue_patch_spec.rb +60 -0
  152. data/spec/lib/stuff_to_do_project_patch_spec.rb +50 -0
  153. data/spec/lib/stuff_to_do_user_patch_spec.rb +8 -0
  154. data/spec/models/stuff_to_do_filter_spec.rb +3 -0
  155. data/spec/models/stuff_to_do_mailer_spec.rb +42 -0
  156. data/spec/models/stuff_to_do_spec.rb +426 -0
  157. data/spec/sanity_spec.rb +7 -0
  158. data/spec/spec_helper.rb +130 -0
  159. metadata +211 -0
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe StuffToDoController, '#save_time_entries' do
4
+ include Redmine::I18n
5
+ integrate_views
6
+
7
+ before(:each) do
8
+ @current_user = mock_model(User, :admin? => false, :logged? => true, :language => :en, :memberships => [], :anonymous? => false, :name => "A Test User", :projects => Project, :allowed_to? => true)
9
+ @current_user.stub!(:time_grid_issues).and_return(Issue)
10
+ User.stub!(:current).and_return(@current_user)
11
+
12
+ @project = mock_model(Project)
13
+ @issue = mock('issue', :project => @project)
14
+ end
15
+
16
+ def do_request(params={})
17
+ post :save_time_entry, {:format => 'js', :time_entry => []}.merge(params)
18
+ end
19
+
20
+ def make_time_entry_hash
21
+ {:comments => 'Test comment', :issue_id => '100', :activity_id => '1', :spent_on => '2009-08-05', :hours => '1'}
22
+ end
23
+
24
+ def make_time_entry_mock(entry)
25
+ TimeEntry.should_receive(:new).with(entry.stringify_keys).and_return do
26
+ te = mock_model(TimeEntry)
27
+ te.stub!(:issue).and_return(@issue) # So it can get the project
28
+ te.should_receive(:project=).with(@project)
29
+ te.stub!(:project).and_return(@project)
30
+ te.should_receive(:user=).with(User.current)
31
+ te.stub!(:comments).and_return(entry[:comments])
32
+ yield te if block_given?
33
+ te
34
+ end
35
+ end
36
+
37
+ describe 'with a successful save' do
38
+ before(:each) do
39
+ controller.stub!(:save_time_entry_from_time_grid).and_return(true)
40
+ end
41
+
42
+ it_should_behave_like 'get_time_grid_data'
43
+ end
44
+
45
+ describe 'with a failed save' do
46
+ it 'should render the error messages as a string' do
47
+ do_request
48
+ response.should_not be_success
49
+ response.body.should match(/project/i)
50
+ response.body.should match(/activity/i)
51
+ response.body.should match(/comment/i)
52
+ response.body.should match(/date/i)
53
+ response.body.should match(/hours/i)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,82 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe StuffToDoController, '#filters_for_view (private)' do
4
+ it 'should return a StuffToDoFilter' do
5
+ controller.send(:filters_for_view).should be_an_instance_of(StuffToDoFilter)
6
+ end
7
+
8
+ it 'should include all the Users in users' do
9
+ @user1 = mock_model(User)
10
+ @user2 = mock_model(User)
11
+ users = [@user1, @user2]
12
+ User.should_receive(:active).and_return(users)
13
+
14
+ filters = controller.send(:filters_for_view)
15
+ filters.users.should include(@user1)
16
+ filters.users.should include(@user2)
17
+ end
18
+
19
+ it 'should include all the IssuePriorities in priorities' do
20
+ @priority1 = mock_model(IssuePriority)
21
+ @priority2 = mock_model(IssuePriority)
22
+ priorities = [@priority1, @priority2]
23
+ IssuePriority.should_receive(:all).and_return(priorities)
24
+
25
+ filters = controller.send(:filters_for_view)
26
+ filters.priorities.should include(@priority1)
27
+ filters.priorities.should include(@priority2)
28
+ end
29
+
30
+ it 'should include all the IssueStatuses in statuses' do
31
+ @status1 = mock_model(IssueStatus)
32
+ @status2 = mock_model(IssueStatus)
33
+ statuses = [@status1, @status2]
34
+ IssueStatus.should_receive(:find).with(:all).and_return(statuses)
35
+
36
+ filters = controller.send(:filters_for_view)
37
+ filters.statuses.should include(@status1)
38
+ filters.statuses.should include(@status2)
39
+ end
40
+
41
+ end
42
+
43
+ describe StuffToDoController, '#save_time_entry_from_time_grid (private)' do
44
+ before(:each) do
45
+ @user = mock_model(User)
46
+ User.stub!(:current).and_return(@user)
47
+ @project = mock_model(Project)
48
+ User.current.stub!(:allowed_to?).and_return(true)
49
+
50
+ @time_entry = TimeEntry.new(:comments => 'A comment for validation')
51
+ @time_entry.stub!(:project).and_return(@project)
52
+ @time_entry.stub!(:valid?).and_return(true)
53
+ @time_entry.stub!(:save).and_return(true)
54
+ end
55
+
56
+ it 'should check if a TimeEntry is valid' do
57
+ @time_entry.should_receive(:valid?).and_return(true)
58
+ controller.send(:save_time_entry_from_time_grid, @time_entry)
59
+ end
60
+
61
+ it 'should add an error if comments are blank' do
62
+ @time_entry.stub!(:valid?).and_return(true)
63
+ @time_entry.should_receive(:comments).at_least(:once).and_return('')
64
+ controller.send(:save_time_entry_from_time_grid, @time_entry)
65
+
66
+ @time_entry.errors.should have(1).error_on(:comments)
67
+ end
68
+
69
+ it 'should check if the user is allowed to log_time to the project' do
70
+ User.current.should_receive(:allowed_to?).with(:log_time, @project).and_return(true)
71
+ @time_entry.should_receive(:project).and_return(@project)
72
+ @time_entry.should_receive(:save).and_return(true)
73
+
74
+ controller.send(:save_time_entry_from_time_grid, @time_entry)
75
+ end
76
+
77
+ it 'should save a valid TimeEntry' do
78
+ @time_entry.should_receive(:save).and_return(true)
79
+ controller.send(:save_time_entry_from_time_grid, @time_entry)
80
+ end
81
+
82
+ end
@@ -0,0 +1,60 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Issue, "associations" do
4
+ it 'should have a habtm time_grid_users' do
5
+ Issue.should have_association(:time_grid_users, :has_and_belongs_to_many)
6
+ end
7
+ end
8
+
9
+ describe Issue, 'after_save' do
10
+ it 'should include update_next_issues' do
11
+ callbacks = Issue.after_save
12
+ callbacks.should_not be_nil
13
+
14
+ callbacks.should satisfy do |callbacks|
15
+ found = false
16
+ callbacks.each do |callback|
17
+ found = true if callback.method == :update_next_issues
18
+ end
19
+ found
20
+ end
21
+ end
22
+ end
23
+
24
+ describe Issue, 'update_next_issues' do
25
+ before(:each) do
26
+ @issue = Issue.new
27
+ @issue.stub!(:reload)
28
+ @issue.stub!(:closed?).and_return(false)
29
+ StuffToDo.stub!(:remove_stale_assignments)
30
+ end
31
+
32
+ it 'should reload the issue to clear the cache holding its status' do
33
+ @issue.should_receive(:reload)
34
+ @issue.stub!(:closed?).and_return(true)
35
+ @issue.update_next_issues
36
+ end
37
+
38
+ it 'should call StuffToDo#remove_associations_to if the issue is closed' do
39
+ @issue.should_receive(:closed?).and_return(true)
40
+ StuffToDo.should_receive(:remove_associations_to).with(@issue)
41
+ @issue.update_next_issues
42
+ end
43
+
44
+ it 'should not call StuffToDo#remove_associations_to if the issue is open' do
45
+ @issue.should_receive(:closed?).and_return(false)
46
+ StuffToDo.should_not_receive(:remove_associations_to)
47
+ @issue.update_next_issues
48
+ end
49
+
50
+ it 'should return true for the callbacks' do
51
+ StuffToDo.stub!(:remove_associations_to)
52
+
53
+ @issue.update_next_issues.should be_true
54
+ end
55
+
56
+ it 'should call StuffToDo#remove_stale_assignments' do
57
+ StuffToDo.should_receive(:remove_stale_assignments).with(@issue)
58
+ @issue.update_next_issues
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Project, 'associations' do
4
+ it 'should have many stuff_to_dos as "stuff"' do
5
+ Project.should have_association(:stuff_to_dos, :has_many)
6
+ end
7
+ end
8
+
9
+ describe Project, 'after_save' do
10
+ it 'should include update_stuff_to_do' do
11
+ callbacks = Project.after_save
12
+ callbacks.should_not be_nil
13
+
14
+ callbacks.should satisfy do |callbacks|
15
+ found = false
16
+ callbacks.each do |callback|
17
+ found = true if callback.method == :update_stuff_to_do
18
+ end
19
+ found
20
+ end
21
+ end
22
+ end
23
+
24
+ describe Project, 'update_stuff_to_do' do
25
+ before(:each) do
26
+ # Can't use a mock here due to a Rails/RSpec conflict
27
+ # https://rails.lighthouseapp.com/projects/8994/tickets/404-named_scope-bashes-critical-methods
28
+ #
29
+ @project = Project.new
30
+ @project.status = Project::STATUS_ACTIVE
31
+ @project.is_public = true
32
+ end
33
+
34
+ it 'should call StuffToDo#remove_associations_to if the project is not active' do
35
+ @project.status = Project::STATUS_ARCHIVED
36
+ StuffToDo.should_receive(:remove_associations_to).with(@project)
37
+ @project.update_stuff_to_do
38
+ end
39
+
40
+ it 'should not call StuffToDo#remove_associations_to if the project is active' do
41
+ StuffToDo.should_not_receive(:remove_associations_to)
42
+ @project.update_stuff_to_do
43
+ end
44
+
45
+ it 'should return true for the callbacks' do
46
+ StuffToDo.stub!(:remove_associations_to)
47
+
48
+ @project.update_stuff_to_do.should be_true
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe User, "associations" do
4
+ it 'should have a habtm time_grid_issues' do
5
+ User.should have_association(:time_grid_issues, :has_and_belongs_to_many)
6
+ end
7
+ end
8
+
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ # TODO
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe StuffToDoMailer, 'recommended_below_threshold' do
4
+
5
+ before(:each) do
6
+ @settings = {'email_to' => 'user1@example.com,user2@example.com', 'threshold' => '5'}
7
+ Setting.stub!(:plugin_stuff_to_do_plugin).and_return(@settings)
8
+ Setting.stub!(:host_name).and_return('example.com')
9
+ Setting.stub!(:protocol).and_return('https')
10
+ @user = mock_model(User, :name => "Example User", :id => 100)
11
+ @next_item_count = 2
12
+
13
+ @mail = StuffToDoMailer.create_recommended_below_threshold(@user, @next_item_count)
14
+ end
15
+
16
+ it 'should send to the users specified in the Settings' do
17
+ @mail.bcc.should have(2).things
18
+ @mail.bcc.should include("user1@example.com")
19
+ @mail.bcc.should include("user2@example.com")
20
+ end
21
+
22
+ it 'should use the subject of "Whats Recommended is below the threshold"' do
23
+ @mail.subject.should match(/What's Recommended is below the threshold/i)
24
+ end
25
+
26
+ it 'should have the user name in the body' do
27
+ @mail.encoded.should match(/#{ @user.name }/)
28
+ end
29
+
30
+ it 'should say the threshold amount in the body' do
31
+ @mail.encoded.should match(/threshold of 5/)
32
+ end
33
+
34
+ it 'should say the number of StuffToDos for the user in the body' do
35
+ @mail.encoded.should match(/only 2 recommended items left/)
36
+ end
37
+
38
+ it 'should have a link to the users stuff_to_do page in the body' do
39
+ # '=3D' is the encoded version of '='
40
+ @mail.encoded.should include("https://example.com/stuff_to_do?user_id=3D#{@user.id}")
41
+ end
42
+ end
@@ -0,0 +1,426 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module StuffToDoSpecHelper
4
+ def issue_factory(number, fields = { })
5
+ issues = []
6
+ number.times do |issue_number|
7
+ issues << mock_model(Issue, { :id => issue_number }.merge(fields))
8
+ end
9
+
10
+ return issues
11
+ end
12
+
13
+ def project_factory(number, fields = {})
14
+ projects = []
15
+ number.times do |project_number|
16
+ projects << mock_model(Project, { :id => project_number }.merge(fields))
17
+ end
18
+
19
+ return projects
20
+ end
21
+
22
+ def next_issues_from_issues(issues, number_of_next_issues = nil)
23
+ number_of_next_issues ||= issues.size
24
+
25
+ next_issues = []
26
+ issues.each do |issue|
27
+ next if next_issues.size >= number_of_next_issues
28
+ next_issues << mock_model(StuffToDo, :stuff => issue)
29
+ end
30
+
31
+ return next_issues
32
+ end
33
+ end
34
+
35
+ describe StuffToDo, 'associations' do
36
+ it 'should belong to a polymorphic "stuff"' do
37
+ StuffToDo.should have_association(:stuff, :belongs_to)
38
+ end
39
+
40
+ it 'should belong to a user' do
41
+ StuffToDo.should have_association(:user, :belongs_to)
42
+ end
43
+ end
44
+
45
+ describe StuffToDo, '#available with no filter' do
46
+
47
+ before(:each) do
48
+ @user = mock_model(User)
49
+ end
50
+
51
+ it 'should find nothing' do
52
+ StuffToDo.available(@user).should be_empty
53
+ end
54
+ end
55
+
56
+ describe StuffToDo, '#available for user' do
57
+ include StuffToDoSpecHelper
58
+
59
+ before(:each) do
60
+ @user = mock_model(User)
61
+ @find_options = { :conditions => ['1=1 AND (issue_statuses.is_closed = ?) AND (projects.status = ?) AND (assigned_to_id = ?)',false, 1, @user.id ], :include => [:status, :priority, :project], :order => 'issues.created_on DESC'}
62
+ end
63
+
64
+ it 'should find all assigned issues for the user' do
65
+ issues = issue_factory(10, { :assigned_to => @user })
66
+
67
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
68
+ StuffToDo.available(@user, @user).should eql(issues)
69
+ end
70
+
71
+ it 'should not include issues that are StuffToDos' do
72
+ issues = issue_factory(10, { :assigned_to => @user })
73
+ # Add in half the issues as StuffToDos
74
+ next_issues = next_issues_from_issues(issues, issues.size / 2)
75
+
76
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
77
+ StuffToDo.should_receive(:find).with(:all, { :conditions => { :user_id => @user.id }}).and_return(next_issues)
78
+ StuffToDo.available(@user, @user).should eql(issues - next_issues.collect(&:stuff))
79
+ end
80
+
81
+ it 'should only include open issues' do
82
+ issues = issue_factory(10, { :assigned_to => @user })
83
+
84
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
85
+ available = StuffToDo.available(@user, @user)
86
+ available.should have(10).items
87
+ available.should eql(issues)
88
+ end
89
+ end
90
+
91
+ describe StuffToDo, '#available for status' do
92
+ include StuffToDoSpecHelper
93
+
94
+ before(:each) do
95
+ @user = mock_model(User)
96
+ @status = mock_model(IssueStatus)
97
+ @find_options = { :conditions => ['1=1 AND (issue_statuses.is_closed = ?) AND (projects.status = ?) AND (issue_statuses.id = (?))', false, 1, @status.id ], :include => [:status, :priority, :project], :order => 'issues.created_on DESC'}
98
+ end
99
+
100
+ it 'should find all issues with the status' do
101
+ issues = issue_factory(10, { :status => @status })
102
+
103
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
104
+ StuffToDo.available(@user, @status).should eql(issues)
105
+ end
106
+
107
+ it 'should not include issues that are StuffToDos' do
108
+ issues = issue_factory(10, { :status => @status })
109
+ # Add in half the issues as StuffToDos
110
+ next_issues = next_issues_from_issues(issues, issues.size / 2)
111
+
112
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
113
+ StuffToDo.should_receive(:find).with(:all, { :conditions => { :user_id => @user.id }}).and_return(next_issues)
114
+ StuffToDo.available(@user, @status).should eql(issues - next_issues.collect(&:stuff))
115
+ end
116
+
117
+ it 'should only include open issues' do
118
+ issues = issue_factory(10, { :status => @status })
119
+
120
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
121
+ available = StuffToDo.available(@user, @status)
122
+ available.should have(10).items
123
+ available.should eql(issues)
124
+ end
125
+ end
126
+
127
+ describe StuffToDo, '#available for priority' do
128
+ include StuffToDoSpecHelper
129
+
130
+ before(:each) do
131
+ @user = mock_model(User)
132
+ @priority = mock_model(Enumeration, :opt => 'IPRI')
133
+ @find_options = { :conditions => ['1=1 AND (issue_statuses.is_closed = ?) AND (projects.status = ?) AND (enumerations.id = (?))', false, 1, @priority.id ], :include => [:status, :priority, :project], :order => 'issues.created_on DESC'}
134
+ end
135
+
136
+ it 'should find all issues with the priority' do
137
+ issues = issue_factory(10, { :priority => @priority })
138
+
139
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
140
+ StuffToDo.available(@user, @priority).should eql(issues)
141
+ end
142
+
143
+ it 'should not include issues that are StuffToDos' do
144
+ issues = issue_factory(10, { :priority => @priority })
145
+ # Add in half the issues as StuffToDos
146
+ next_issues = next_issues_from_issues(issues, issues.size / 2)
147
+
148
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
149
+ StuffToDo.should_receive(:find).with(:all, { :conditions => { :user_id => @user.id }}).and_return(next_issues)
150
+ StuffToDo.available(@user, @priority).should eql(issues - next_issues.collect(&:stuff))
151
+ end
152
+
153
+ it 'should only include open issues' do
154
+ issues = issue_factory(10, { :priority => @priority })
155
+
156
+ Issue.should_receive(:find).with(:all, @find_options).and_return(issues)
157
+ available = StuffToDo.available(@user, @priority)
158
+ available.should have(10).items
159
+ available.should eql(issues)
160
+ end
161
+ end
162
+
163
+ describe StuffToDo, '#available for project' do
164
+ include StuffToDoSpecHelper
165
+
166
+ before(:each) do
167
+ @user = mock_model(User)
168
+ @priority = mock_model(Enumeration, :opt => 'IPRI')
169
+ end
170
+
171
+ it 'should find all active projects visible to the user' do
172
+ issues = issue_factory(10, { :priority => @priority })
173
+ projects = project_factory(10)
174
+ projects.should_receive(:sort).and_return(projects) # No need to test the sort order
175
+
176
+ Project.should_receive(:active).and_return(Project)
177
+ Project.should_receive(:visible).and_return(projects)
178
+
179
+ StuffToDo.available(@user, Project.new).should eql(projects)
180
+ end
181
+
182
+ it 'should not include projects that are StuffToDos already' do
183
+ projects = project_factory(10)
184
+ projects.stub!(:sort).and_return(projects) # No need to test the sort order
185
+ # Add in half the issues as StuffToDos
186
+ stuff_to_dos = next_issues_from_issues(projects, projects.size / 2)
187
+
188
+ Project.should_receive(:active).and_return(Project)
189
+ Project.should_receive(:visible).and_return(projects)
190
+ StuffToDo.should_receive(:find).with(:all, { :conditions => { :user_id => @user.id }}).and_return(stuff_to_dos)
191
+
192
+ StuffToDo.available(@user, Project.new).should eql(projects - stuff_to_dos.collect(&:stuff))
193
+ end
194
+ end
195
+
196
+ describe StuffToDo, '#remove_associations_to' do
197
+ before(:each) do
198
+ @issue = mock_model(Issue)
199
+ @issue.stub!(:closed?).and_return(true)
200
+ @project = mock_model(Project)
201
+ @project.stub!(:active).and_return(false)
202
+ end
203
+
204
+ it 'should delete all StuffToDos for a closed issue' do
205
+ next_issue_one = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => nil)
206
+ next_issue_one.should_receive(:destroy).and_return(true)
207
+ next_issue_two = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => nil)
208
+ next_issue_two.should_receive(:destroy).and_return(true)
209
+ next_issues = [next_issue_one, next_issue_two]
210
+ @issue.should_receive(:stuff_to_dos).and_return(next_issues)
211
+
212
+ StuffToDo.remove_associations_to(@issue)
213
+ end
214
+
215
+ it 'should delete all StuffToDos for a archived project' do
216
+ next_issue_one = mock_model(StuffToDo, :stuff_id => @project.id, :user_id => nil)
217
+ next_issue_one.should_receive(:destroy).and_return(true)
218
+ next_issue_two = mock_model(StuffToDo, :stuff_id => @project.id, :user_id => nil)
219
+ next_issue_two.should_receive(:destroy).and_return(true)
220
+ next_issues = [next_issue_one, next_issue_two]
221
+ @project.should_receive(:stuff_to_dos).and_return(next_issues)
222
+
223
+ StuffToDo.remove_associations_to(@project)
224
+ end
225
+ end
226
+
227
+ describe StuffToDo, '#remove_associations_to' do
228
+ before(:each) do
229
+ @issue = mock_model(Issue)
230
+ @issue.stub!(:closed?).and_return(true)
231
+
232
+ @user = mock_model(User)
233
+ @next_issue_one = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => @user.id)
234
+ @next_issue_one.should_receive(:destroy).and_return(true)
235
+ @next_issue_two = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => @user.id)
236
+ @next_issue_two.should_receive(:destroy).and_return(true)
237
+ @next_issues = [@next_issue_one, @next_issue_two]
238
+ @issue.stub!(:stuff_to_dos).and_return(@next_issues)
239
+ @number_of_next_issues = 4
240
+ StuffToDo.stub!(:count).with(:conditions => { :user_id => @user.id }).and_return(@number_of_next_issues)
241
+ end
242
+
243
+ it 'should deliver a StuffToDoMailer notification if the StuffToDos are below the threshold' do
244
+ Setting.should_receive(:plugin_stuff_to_do_plugin).and_return({'threshold' => @number_of_next_issues + 1 })
245
+ User.should_receive(:find_by_id).with(@user.id).and_return(@user)
246
+ StuffToDoMailer.should_receive(:deliver_recommended_below_threshold).with(@user, 4)
247
+ StuffToDo.remove_associations_to(@issue)
248
+ end
249
+
250
+ it 'should deliver a StuffToDoMailer notification if the StuffToDos are at the threshold' do
251
+ Setting.should_receive(:plugin_stuff_to_do_plugin).and_return({'threshold' => @number_of_next_issues })
252
+ User.should_receive(:find_by_id).with(@user.id).and_return(@user)
253
+ StuffToDoMailer.should_receive(:deliver_recommended_below_threshold).with(@user, 4)
254
+ StuffToDo.remove_associations_to(@issue)
255
+ end
256
+
257
+ it 'should not deliver any StuffToDoMailer notification if the StuffToDos are above the threshold' do
258
+ Setting.should_receive(:plugin_stuff_to_do_plugin).and_return({'threshold' => @number_of_next_issues - 1 })
259
+ StuffToDoMailer.should_not_receive(:deliver_recommended_below_threshold)
260
+ StuffToDo.remove_associations_to(@issue)
261
+ end
262
+
263
+ end
264
+
265
+ describe StuffToDo, '#remove_stale_assignments' do
266
+ it 'should destroy all StuffToDos for an issue except for the currently assigned user' do
267
+ @user = mock_model(User)
268
+ @user2 = mock_model(User)
269
+ @user3 = mock_model(User)
270
+
271
+ @issue = mock_model(Issue, :assigned_to_id => @user.id)
272
+
273
+ @next_issue_one = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => @user2.id)
274
+ @next_issue_two = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => @user3.id)
275
+ @next_issues = [@next_issue_one, @next_issue_two]
276
+
277
+ StuffToDo.should_receive(:destroy_all).with(['stuff_id = (?) AND user_id NOT IN (?)', @issue.id, @issue.assigned_to_id]).and_return(@next_issues)
278
+
279
+ StuffToDo.remove_stale_assignments(@issue)
280
+ end
281
+
282
+ it 'should destroy all StuffToDos for an issue if the currently assigned user is blank' do
283
+ @user = mock_model(User)
284
+ @user2 = mock_model(User)
285
+ @user3 = mock_model(User)
286
+
287
+ @issue = mock_model(Issue, :assigned_to_id => nil)
288
+
289
+ @next_issue_one = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => @user2.id)
290
+ @next_issue_two = mock_model(StuffToDo, :stuff_id => @issue.id, :user_id => @user3.id)
291
+ @next_issues = [@next_issue_one, @next_issue_two]
292
+
293
+ StuffToDo.should_receive(:destroy_all).with(['stuff_id = (?)', @issue.id]).and_return(@next_issues)
294
+
295
+ StuffToDo.remove_stale_assignments(@issue)
296
+ end
297
+
298
+ end
299
+
300
+
301
+ describe StuffToDo, '#reorder_list' do
302
+ it 'should require a user_id' do
303
+ lambda {
304
+ StuffToDo.reorder_list
305
+ }.should raise_error
306
+
307
+ end
308
+
309
+ it 'should require an array of ids' do
310
+ user = mock_model(User)
311
+ lambda {
312
+ StuffToDo.reorder_list(user)
313
+ }.should raise_error
314
+ end
315
+
316
+ it 'should find all the Stuff To Do items' do
317
+ user = mock_model(User)
318
+ ids = ["598", "709", "746", "1492", "1491", "820", "1094", "1095"]
319
+ stuff_to_dos = []
320
+ ids.each do |id|
321
+ stuff_to_dos << mock_model(StuffToDo, :stuff_id => id, :insert_at => true)
322
+ end
323
+
324
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Issue').and_return(stuff_to_dos)
325
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Project').and_return([])
326
+ StuffToDo.reorder_list(user, ids)
327
+ end
328
+
329
+
330
+ it 'should save the positions of the stuff to do items to the database' do
331
+ user = mock_model(User)
332
+ ids = ["598", "709", "746", "1492", "1491", "820", "1094", "1095"]
333
+ stuff_to_dos = []
334
+ ids.each_with_index do |id, array_position|
335
+ stuff_to_do = mock_model(StuffToDo, :stuff_id => id, :id => id)
336
+ stuff_to_do.should_receive(:insert_at).with(array_position + 1)
337
+ stuff_to_dos << stuff_to_do
338
+ end
339
+
340
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Issue').and_return(stuff_to_dos)
341
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Project').and_return([])
342
+ StuffToDo.reorder_list(user, ids)
343
+
344
+ end
345
+
346
+ it 'should add new StuffToDo that are in the list but not in the database' do
347
+ user = mock_model(User)
348
+ ids = ["598", "709", "746", "1492", "1491", "820", "1094", "1095"]
349
+ stuff_to_dos = []
350
+ ids.each_with_index do |id, array_position|
351
+ stuff_to_dos << mock_model(StuffToDo, :stuff_id => id, :insert_at => true) unless id == "820"
352
+ end
353
+
354
+ stuff_to_do_for_820 = StuffToDo.new
355
+ stuff_to_do_for_820.should_receive(:stuff_id=).with(820)
356
+ stuff_to_do_for_820.should_receive(:user_id=).with(user.id)
357
+ stuff_to_do_for_820.should_receive(:save).and_return(true)
358
+ position = ids.index("820") + 1
359
+ stuff_to_do_for_820.should_receive(:insert_at).with(position)
360
+ StuffToDo.should_receive(:new).and_return(stuff_to_do_for_820)
361
+
362
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Issue').and_return(stuff_to_dos)
363
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Project').and_return([])
364
+ StuffToDo.reorder_list(user, ids)
365
+ end
366
+
367
+ it 'should support adding new Project StuffToDo items' do
368
+ user = mock_model(User)
369
+ ids = ["598", "709", "746", "1492", "1491", "820", "1094", "1095"]
370
+ stuff_to_dos = []
371
+ ids.each_with_index do |id, array_position|
372
+ stuff_to_do = mock_model(StuffToDo, :stuff_id => id, :id => id)
373
+ stuff_to_do.should_receive(:insert_at).with(array_position + 1)
374
+ stuff_to_dos << stuff_to_do
375
+ end
376
+
377
+ # Project 42
378
+ ids << "project42"
379
+ new_project_stuff_to_do = mock_model(StuffToDo, :user_id => user.id, :insert_at => ids.size)
380
+ new_project_stuff_to_do.should_receive(:stuff_id=).with(42)
381
+ new_project_stuff_to_do.should_receive(:stuff_type=).with('Project')
382
+ new_project_stuff_to_do.should_receive(:user_id=).with(user.id)
383
+ new_project_stuff_to_do.should_receive(:save).and_return(true)
384
+ StuffToDo.should_receive(:new).and_return(new_project_stuff_to_do)
385
+
386
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Issue').and_return(stuff_to_dos)
387
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Project').and_return([])
388
+ StuffToDo.reorder_list(user, ids)
389
+
390
+ end
391
+
392
+ it 'should destroy any StuffToDos that are not in the list' do
393
+ user = mock_model(User)
394
+ ids = ["598", "709", "746", "1492", "1491", "820", "1094", "1095"]
395
+ stuff_to_dos = []
396
+ ids.each_with_index do |id, array_position|
397
+ stuff_to_dos << mock_model(StuffToDo, :stuff_id => id, :insert_at => true)
398
+ end
399
+
400
+ extra_stuff_to_do = mock_model(StuffToDo, :stuff_id => 999)
401
+ extra_stuff_to_do.should_receive(:destroy).and_return(true)
402
+ StuffToDo.should_receive(:find_by_user_id_and_stuff_id).with(user.id, extra_stuff_to_do.stuff_id).and_return(extra_stuff_to_do)
403
+ stuff_to_dos << extra_stuff_to_do
404
+
405
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Issue').and_return(stuff_to_dos)
406
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Project').and_return([])
407
+ StuffToDo.reorder_list(user, ids)
408
+
409
+ end
410
+
411
+ it 'should destroy all StuffToDos if the list is empty' do
412
+ user = mock_model(User)
413
+ ids = nil
414
+ stuff_to_dos = []
415
+
416
+ extra_stuff_to_do = mock_model(StuffToDo, :stuff_id => 999)
417
+ extra_stuff_to_do.should_receive(:destroy).and_return(true)
418
+ StuffToDo.should_receive(:find_by_user_id_and_stuff_id).with(user.id, extra_stuff_to_do.stuff_id).and_return(extra_stuff_to_do)
419
+ stuff_to_dos << extra_stuff_to_do
420
+
421
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Issue').and_return(stuff_to_dos)
422
+ StuffToDo.should_receive(:find_all_by_user_id_and_stuff_type).with(user.id, 'Project').and_return([])
423
+ StuffToDo.reorder_list(user, ids)
424
+
425
+ end
426
+ end