stuff_to_do_plugin 0.4.0

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 (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