##// END OF EJS Templates
Removes routes for time entries nested under project/issues....
Jean-Philippe Lang -
r13060:2e9b28906db6
parent child
Show More
@@ -0,0 +1,45
1 # encoding: utf-8
2 #
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 require File.expand_path('../../../test_helper', __FILE__)
21
22 class RoutesHelperTest < ActionView::TestCase
23 include Rails.application.routes.url_helpers
24
25 def test_time_entries_path
26 assert_equal '/projects/ecookbook/time_entries', _time_entries_path(Project.find(1), nil)
27 assert_equal '/issues/1/time_entries', _time_entries_path(Project.find(1), Issue.find(1))
28 assert_equal '/issues/1/time_entries', _time_entries_path(nil, Issue.find(1))
29 assert_equal '/time_entries', _time_entries_path(nil, nil)
30 end
31
32 def test_report_time_entries_path
33 assert_equal '/projects/ecookbook/time_entries/report', _report_time_entries_path(Project.find(1), nil)
34 assert_equal '/issues/1/time_entries/report', _report_time_entries_path(Project.find(1), Issue.find(1))
35 assert_equal '/issues/1/time_entries/report', _report_time_entries_path(nil, Issue.find(1))
36 assert_equal '/time_entries/report', _report_time_entries_path(nil, nil)
37 end
38
39 def test_new_time_entry_path
40 assert_equal '/projects/ecookbook/time_entries/new', _new_time_entry_path(Project.find(1), nil)
41 assert_equal '/issues/1/time_entries/new', _new_time_entry_path(Project.find(1), Issue.find(1))
42 assert_equal '/issues/1/time_entries/new', _new_time_entry_path(nil, Issue.find(1))
43 assert_equal '/time_entries/new', _new_time_entry_path(nil, nil)
44 end
45 end
@@ -1,39 +1,69
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module RoutesHelper
21 21
22 22 # Returns the path to project issues or to the cross-project
23 23 # issue list if project is nil
24 24 def _project_issues_path(project, *args)
25 25 if project
26 26 project_issues_path(project, *args)
27 27 else
28 28 issues_path(*args)
29 29 end
30 30 end
31 31
32 32 def _project_calendar_path(project, *args)
33 33 project ? project_calendar_path(project, *args) : issues_calendar_path(*args)
34 34 end
35 35
36 36 def _project_gantt_path(project, *args)
37 37 project ? project_gantt_path(project, *args) : issues_gantt_path(*args)
38 38 end
39
40 def _time_entries_path(project, issue, *args)
41 if issue
42 issue_time_entries_path(issue, *args)
43 elsif project
44 project_time_entries_path(project, *args)
45 else
46 time_entries_path(*args)
47 end
48 end
49
50 def _report_time_entries_path(project, issue, *args)
51 if issue
52 report_issue_time_entries_path(issue, *args)
53 elsif project
54 report_project_time_entries_path(project, *args)
55 else
56 report_time_entries_path(*args)
57 end
58 end
59
60 def _new_time_entry_path(project, issue, *args)
61 if issue
62 new_issue_time_entry_path(issue, *args)
63 elsif project
64 new_project_time_entry_path(project, *args)
65 else
66 new_time_entry_path(*args)
67 end
68 end
39 69 end
@@ -1,36 +1,36
1 1 <div id="query_form_with_buttons" class="hide-when-print">
2 2 <div id="query_form_content">
3 3 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
4 4 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
5 5 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
6 6 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
7 7 </div>
8 8 </fieldset>
9 9 <fieldset class="collapsible collapsed">
10 10 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
11 11 <div style="display: none;">
12 12 <table>
13 13 <tr>
14 14 <td><%= l(:field_column_names) %></td>
15 15 <td><%= render_query_columns_selection(@query) %></td>
16 16 </tr>
17 17 </table>
18 18 </div>
19 19 </fieldset>
20 20 </div>
21 21
22 22 <p class="buttons">
23 23 <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %>
24 24 <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %>
25 25 </p>
26 26 </div>
27 27
28 28 <div class="tabs hide-when-print">
29 29 <% query_params = params.slice(:f, :op, :v, :sort) %>
30 30 <ul>
31 <li><%= link_to(l(:label_details), query_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }),
31 <li><%= link_to(l(:label_details), _time_entries_path(@project, @issue, query_params),
32 32 :class => (action_name == 'index' ? 'selected' : nil)) %></li>
33 <li><%= link_to(l(:label_report), query_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}),
33 <li><%= link_to(l(:label_report), _report_time_entries_path(@project, @issue, query_params),
34 34 :class => (action_name == 'report' ? 'selected' : nil)) %></li>
35 35 </ul>
36 36 </div>
@@ -1,48 +1,48
1 1 <div class="contextual">
2 2 <%= link_to l(:button_log_time),
3 {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue},
3 _new_time_entry_path(@project, @issue),
4 4 :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project, :global => true) %>
5 5 </div>
6 6
7 7 <%= render_timelog_breadcrumb %>
8 8
9 9 <h2><%= l(:label_spent_time) %></h2>
10 10
11 <%= form_tag({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}, :method => :get, :id => 'query_form') do %>
11 <%= form_tag(params.slice(:project_id, :issue_id), :method => :get, :id => 'query_form') do %>
12 12 <%= render :partial => 'date_range' %>
13 13 <% end %>
14 14
15 15 <div class="total-hours">
16 16 <p><%= l(:label_total_time) %>: <%= html_hours(l_hours(@total_hours)) %></p>
17 17 </div>
18 18
19 19 <% unless @entries.empty? %>
20 20 <%= render :partial => 'list', :locals => { :entries => @entries }%>
21 21 <p class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></p>
22 22
23 23 <% other_formats_links do |f| %>
24 24 <%= f.link_to 'Atom', :url => params.merge({:issue_id => @issue, :key => User.current.rss_key}) %>
25 25 <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
26 26 <% end %>
27 27
28 28 <div id="csv-export-options" style="display:none;">
29 29 <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
30 30 <%= form_tag(params.slice(:project_id, :issue_id).merge(:format => 'csv', :page=>nil), :method => :get, :id => 'csv-export-form') do %>
31 31 <%= query_hidden_tags @query %>
32 32 <p>
33 33 <label><%= radio_button_tag 'columns', '', true %> <%= l(:description_selected_columns) %></label><br />
34 34 <label><%= radio_button_tag 'columns', 'all' %> <%= l(:description_all_columns) %></label>
35 35 </p>
36 36 <p class="buttons">
37 37 <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>
38 38 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
39 39 </p>
40 40 <% end %>
41 41 </div>
42 42 <% end %>
43 43
44 44 <% html_title l(:label_spent_time), l(:label_details) %>
45 45
46 46 <% content_for :header_tags do %>
47 47 <%= auto_discovery_link_tag(:atom, {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :title => l(:label_spent_time)) %>
48 48 <% end %>
@@ -1,74 +1,74
1 1 <div class="contextual">
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %>
2 <%= link_to l(:button_log_time),
3 _new_time_entry_path(@project, @issue),
4 :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project, :global => true) %>
3 5 </div>
4 6
5 7 <%= render_timelog_breadcrumb %>
6 8
7 9 <h2><%= l(:label_spent_time) %></h2>
8 10
9 <%= form_tag({:controller => 'timelog', :action => 'report',
10 :project_id => @project, :issue_id => @issue},
11 :method => :get, :id => 'query_form') do %>
11 <%= form_tag(params.slice(:project_id, :issue_id), :method => :get, :id => 'query_form') do %>
12 12 <% @report.criteria.each do |criterion| %>
13 13 <%= hidden_field_tag 'criteria[]', criterion, :id => nil %>
14 14 <% end %>
15 15 <%= render :partial => 'timelog/date_range' %>
16 16
17 17 <p><label for='columns'><%= l(:label_details) %></label>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
18 18 [l(:label_month), 'month'],
19 19 [l(:label_week), 'week'],
20 20 [l(:label_day_plural).titleize, 'day']], @report.columns),
21 21 :onchange => "this.form.submit();" %>
22 22
23 23 <label for='criterias'><%= l(:button_add) %></label>: <%= select_tag('criteria[]', options_for_select([[]] + (@report.available_criteria.keys - @report.criteria).collect{|k| [l_or_humanize(@report.available_criteria[k][:label]), k]}),
24 24 :onchange => "this.form.submit();",
25 25 :style => 'width: 200px',
26 26 :id => nil,
27 27 :disabled => (@report.criteria.length >= 3), :id => "criterias") %>
28 28 <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @report.columns}, :class => 'icon icon-reload' %></p>
29 29 <% end %>
30 30
31 31 <% unless @report.criteria.empty? %>
32 32 <div class="total-hours">
33 33 <p><%= l(:label_total_time) %>: <%= html_hours(l_hours(@report.total_hours)) %></p>
34 34 </div>
35 35
36 36 <% unless @report.hours.empty? %>
37 37 <div class="autoscroll">
38 38 <table class="list" id="time-report">
39 39 <thead>
40 40 <tr>
41 41 <% @report.criteria.each do |criteria| %>
42 42 <th><%= l_or_humanize(@report.available_criteria[criteria][:label]) %></th>
43 43 <% end %>
44 44 <% columns_width = (40 / (@report.periods.length+1)).to_i %>
45 45 <% @report.periods.each do |period| %>
46 46 <th class="period" style="width:<%= columns_width %>%;"><%= period %></th>
47 47 <% end %>
48 48 <th class="total" style="width:<%= columns_width %>%;"><%= l(:label_total_time) %></th>
49 49 </tr>
50 50 </thead>
51 51 <tbody>
52 52 <%= render :partial => 'report_criteria', :locals => {:criterias => @report.criteria, :hours => @report.hours, :level => 0} %>
53 53 <tr class="total">
54 54 <td><%= l(:label_total_time) %></td>
55 55 <%= ('<td></td>' * (@report.criteria.size - 1)).html_safe %>
56 56 <% total = 0 -%>
57 57 <% @report.periods.each do |period| -%>
58 58 <% sum = sum_hours(select_hours(@report.hours, @report.columns, period.to_s)); total += sum -%>
59 59 <td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td>
60 60 <% end -%>
61 61 <td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td>
62 62 </tr>
63 63 </tbody>
64 64 </table>
65 65 </div>
66 66
67 67 <% other_formats_links do |f| %>
68 68 <%= f.link_to 'CSV', :url => params %>
69 69 <% end %>
70 70 <% end %>
71 71 <% end %>
72 72
73 73 <% html_title l(:label_spent_time), l(:label_report) %>
74 74
@@ -1,356 +1,350
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 RedmineApp::Application.routes.draw do
19 19 root :to => 'welcome#index', :as => 'home'
20 20
21 21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
22 22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
23 23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 25 match 'account/activate', :to => 'account#activate', :via => :get
26 26 get 'account/activation_email', :to => 'account#activation_email', :as => 'activation_email'
27 27
28 28 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put]
29 29 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put]
30 30 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put]
31 31 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put]
32 32
33 33 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
34 34 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
35 35
36 36 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
37 37 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
38 38 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
39 39 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
40 40
41 41 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
42 42 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
43 43 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
44 44 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
45 45
46 46 # Misc issue routes. TODO: move into resources
47 47 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
48 48 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
49 49 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
50 50 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
51 51
52 52 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
53 53 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
54 54
55 55 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
56 56 get '/issues/gantt', :to => 'gantts#show'
57 57
58 58 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
59 59 get '/issues/calendar', :to => 'calendars#show'
60 60
61 61 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
62 62 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
63 63
64 64 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
65 65 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
66 66 match 'my/page', :controller => 'my', :action => 'page', :via => :get
67 67 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
68 68 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
69 69 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
70 70 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
71 71 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
72 72 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
73 73 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
74 74 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
75 75
76 76 resources :users
77 77 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
78 78 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
79 79 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
80 80
81 81 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
82 82 delete 'watchers/watch', :to => 'watchers#unwatch'
83 83 get 'watchers/new', :to => 'watchers#new'
84 84 post 'watchers', :to => 'watchers#create'
85 85 post 'watchers/append', :to => 'watchers#append'
86 86 delete 'watchers', :to => 'watchers#destroy'
87 87 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
88 88 # Specific routes for issue watchers API
89 89 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
90 90 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
91 91
92 92 resources :projects do
93 93 member do
94 94 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
95 95 post 'modules'
96 96 post 'archive'
97 97 post 'unarchive'
98 98 post 'close'
99 99 post 'reopen'
100 100 match 'copy', :via => [:get, :post]
101 101 end
102 102
103 103 shallow do
104 104 resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
105 105 collection do
106 106 get 'autocomplete'
107 107 end
108 108 end
109 109 end
110 110
111 111 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
112 112
113 113 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
114 resources :issues, :only => [:index, :new, :create] do
115 resources :time_entries, :controller => 'timelog' do
116 collection do
117 get 'report'
118 end
119 end
120 end
114 resources :issues, :only => [:index, :new, :create]
121 115 # issue form update
122 116 match 'issues/update_form', :controller => 'issues', :action => 'update_form', :via => [:put, :post], :as => 'issue_form'
123 117
124 118 resources :files, :only => [:index, :new, :create]
125 119
126 120 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
127 121 collection do
128 122 put 'close_completed'
129 123 end
130 124 end
131 125 get 'versions.:format', :to => 'versions#index'
132 126 get 'roadmap', :to => 'versions#index', :format => false
133 127 get 'versions', :to => 'versions#index'
134 128
135 129 resources :news, :except => [:show, :edit, :update, :destroy]
136 130 resources :time_entries, :controller => 'timelog' do
137 131 get 'report', :on => :collection
138 132 end
139 133 resources :queries, :only => [:new, :create]
140 134 shallow do
141 135 resources :issue_categories
142 136 end
143 137 resources :documents, :except => [:show, :edit, :update, :destroy]
144 138 resources :boards
145 139 shallow do
146 140 resources :repositories, :except => [:index, :show] do
147 141 member do
148 142 match 'committers', :via => [:get, :post]
149 143 end
150 144 end
151 145 end
152 146
153 147 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
154 148 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
155 149 member do
156 150 get 'rename'
157 151 post 'rename'
158 152 get 'history'
159 153 get 'diff'
160 154 match 'preview', :via => [:post, :put]
161 155 post 'protect'
162 156 post 'add_attachment'
163 157 end
164 158 collection do
165 159 get 'export'
166 160 get 'date_index'
167 161 end
168 162 end
169 163 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
170 164 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
171 165 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
172 166 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
173 167 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
174 168 end
175 169
176 170 resources :issues do
177 171 collection do
178 172 match 'bulk_edit', :via => [:get, :post]
179 173 post 'bulk_update'
180 174 end
181 175 resources :time_entries, :controller => 'timelog' do
182 176 collection do
183 177 get 'report'
184 178 end
185 179 end
186 180 shallow do
187 181 resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
188 182 end
189 183 end
190 184 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
191 185
192 186 resources :queries, :except => [:show]
193 187
194 188 resources :news, :only => [:index, :show, :edit, :update, :destroy]
195 189 match '/news/:id/comments', :to => 'comments#create', :via => :post
196 190 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
197 191
198 192 resources :versions, :only => [:show, :edit, :update, :destroy] do
199 193 post 'status_by', :on => :member
200 194 end
201 195
202 196 resources :documents, :only => [:show, :edit, :update, :destroy] do
203 197 post 'add_attachment', :on => :member
204 198 end
205 199
206 200 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
207 201
208 202 resources :time_entries, :controller => 'timelog', :except => :destroy do
209 203 collection do
210 204 get 'report'
211 205 get 'bulk_edit'
212 206 post 'bulk_update'
213 207 end
214 208 end
215 209 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
216 210 # TODO: delete /time_entries for bulk deletion
217 211 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
218 212
219 213 get 'projects/:id/activity', :to => 'activities#index'
220 214 get 'projects/:id/activity.:format', :to => 'activities#index'
221 215 get 'activity', :to => 'activities#index'
222 216
223 217 # repositories routes
224 218 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
225 219 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
226 220
227 221 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
228 222 :to => 'repositories#changes'
229 223
230 224 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
231 225 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
232 226 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
233 227 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
234 228 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
235 229 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
236 230 :controller => 'repositories',
237 231 :format => false,
238 232 :constraints => {
239 233 :action => /(browse|show|entry|raw|annotate|diff)/,
240 234 :rev => /[a-z0-9\.\-_]+/
241 235 }
242 236
243 237 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
244 238 get 'projects/:id/repository/graph', :to => 'repositories#graph'
245 239
246 240 get 'projects/:id/repository/changes(/*path(.:ext))',
247 241 :to => 'repositories#changes'
248 242
249 243 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
250 244 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
251 245 get 'projects/:id/repository/revision', :to => 'repositories#revision'
252 246 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
253 247 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
254 248 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
255 249 :controller => 'repositories',
256 250 :format => false,
257 251 :constraints => {
258 252 :action => /(browse|show|entry|raw|annotate|diff)/,
259 253 :rev => /[a-z0-9\.\-_]+/
260 254 }
261 255 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
262 256 :controller => 'repositories',
263 257 :action => /(browse|show|entry|raw|changes|annotate|diff)/
264 258 get 'projects/:id/repository/:action(/*path(.:ext))',
265 259 :controller => 'repositories',
266 260 :action => /(browse|show|entry|raw|changes|annotate|diff)/
267 261
268 262 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
269 263 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
270 264
271 265 # additional routes for having the file name at the end of url
272 266 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
273 267 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
274 268 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
275 269 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
276 270 resources :attachments, :only => [:show, :destroy]
277 271
278 272 resources :groups do
279 273 member do
280 274 get 'autocomplete_for_user'
281 275 end
282 276 end
283 277
284 278 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
285 279 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
286 280 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
287 281 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
288 282
289 283 resources :trackers, :except => :show do
290 284 collection do
291 285 match 'fields', :via => [:get, :post]
292 286 end
293 287 end
294 288 resources :issue_statuses, :except => :show do
295 289 collection do
296 290 post 'update_issue_done_ratio'
297 291 end
298 292 end
299 293 resources :custom_fields, :except => :show
300 294 resources :roles do
301 295 collection do
302 296 match 'permissions', :via => [:get, :post]
303 297 end
304 298 end
305 299 resources :enumerations, :except => :show
306 300 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
307 301
308 302 get 'projects/:id/search', :controller => 'search', :action => 'index'
309 303 get 'search', :controller => 'search', :action => 'index'
310 304
311 305 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
312 306
313 307 match 'admin', :controller => 'admin', :action => 'index', :via => :get
314 308 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
315 309 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
316 310 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
317 311 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
318 312 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
319 313
320 314 resources :auth_sources do
321 315 member do
322 316 get 'test_connection', :as => 'try_connection'
323 317 end
324 318 collection do
325 319 get 'autocomplete_for_new_user'
326 320 end
327 321 end
328 322
329 323 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
330 324 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
331 325 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
332 326 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
333 327 match 'settings', :controller => 'settings', :action => 'index', :via => :get
334 328 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
335 329 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
336 330
337 331 match 'sys/projects', :to => 'sys#projects', :via => :get
338 332 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
339 333 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => [:get, :post]
340 334
341 335 match 'uploads', :to => 'attachments#upload', :via => :post
342 336
343 337 get 'robots.txt', :to => 'welcome#robots'
344 338
345 339 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
346 340 file = File.join(plugin_dir, "config/routes.rb")
347 341 if File.exists?(file)
348 342 begin
349 343 instance_eval File.read(file)
350 344 rescue Exception => e
351 345 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
352 346 exit 1
353 347 end
354 348 end
355 349 end
356 350 end
@@ -1,386 +1,386
1 1 # -*- coding: utf-8 -*-
2 2 # Redmine - project management software
3 3 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require File.expand_path('../../test_helper', __FILE__)
20 20
21 21 class TimeEntryReportsControllerTest < ActionController::TestCase
22 22 tests TimelogController
23 23
24 24 fixtures :projects, :enabled_modules, :roles, :members, :member_roles,
25 25 :issues, :time_entries, :users, :trackers, :enumerations,
26 26 :issue_statuses, :custom_fields, :custom_values,
27 27 :projects_trackers, :custom_fields_trackers,
28 28 :custom_fields_projects
29 29
30 30 include Redmine::I18n
31 31
32 32 def setup
33 33 Setting.default_language = "en"
34 34 end
35 35
36 36 def test_report_at_project_level
37 37 get :report, :project_id => 'ecookbook'
38 38 assert_response :success
39 39 assert_template 'report'
40 40 assert_tag :form,
41 41 :attributes => {:action => "/projects/ecookbook/time_entries/report", :id => 'query_form'}
42 42 end
43 43
44 44 def test_report_all_projects
45 45 get :report
46 46 assert_response :success
47 47 assert_template 'report'
48 48 assert_tag :form,
49 49 :attributes => {:action => "/time_entries/report", :id => 'query_form'}
50 50 end
51 51
52 52 def test_report_all_projects_denied
53 53 r = Role.anonymous
54 54 r.permissions.delete(:view_time_entries)
55 55 r.permissions_will_change!
56 56 r.save
57 57 get :report
58 58 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftime_entries%2Freport'
59 59 end
60 60
61 61 def test_report_all_projects_one_criteria
62 62 get :report, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criteria => ['project']
63 63 assert_response :success
64 64 assert_template 'report'
65 65 assert_not_nil assigns(:report)
66 66 assert_equal "8.65", "%.2f" % assigns(:report).total_hours
67 67 end
68 68
69 69 def test_report_all_time
70 70 get :report, :project_id => 1, :criteria => ['project', 'issue']
71 71 assert_response :success
72 72 assert_template 'report'
73 73 assert_not_nil assigns(:report)
74 74 assert_equal "162.90", "%.2f" % assigns(:report).total_hours
75 75 end
76 76
77 77 def test_report_all_time_by_day
78 78 get :report, :project_id => 1, :criteria => ['project', 'issue'], :columns => 'day'
79 79 assert_response :success
80 80 assert_template 'report'
81 81 assert_not_nil assigns(:report)
82 82 assert_equal "162.90", "%.2f" % assigns(:report).total_hours
83 83 assert_tag :tag => 'th', :content => '2007-03-12'
84 84 end
85 85
86 86 def test_report_one_criteria
87 87 get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criteria => ['project']
88 88 assert_response :success
89 89 assert_template 'report'
90 90 assert_not_nil assigns(:report)
91 91 assert_equal "8.65", "%.2f" % assigns(:report).total_hours
92 92 end
93 93
94 94 def test_report_two_criteria
95 95 get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"]
96 96 assert_response :success
97 97 assert_template 'report'
98 98 assert_not_nil assigns(:report)
99 99 assert_equal "162.90", "%.2f" % assigns(:report).total_hours
100 100 end
101 101
102 102 def test_report_custom_field_criteria_with_multiple_values_on_single_value_custom_field_should_not_fail
103 103 field = TimeEntryCustomField.create!(:name => 'multi', :field_format => 'list', :possible_values => ['value1', 'value2'])
104 104 entry = TimeEntry.create!(:project => Project.find(1), :hours => 1, :activity_id => 10, :user => User.find(2), :spent_on => Date.today)
105 105 CustomValue.create!(:customized => entry, :custom_field => field, :value => 'value1')
106 106 CustomValue.create!(:customized => entry, :custom_field => field, :value => 'value2')
107 107
108 108 get :report, :project_id => 1, :columns => 'day', :criteria => ["cf_#{field.id}"]
109 109 assert_response :success
110 110 end
111 111
112 112 def test_report_multiple_values_custom_fields_should_not_be_proposed
113 113 TimeEntryCustomField.create!(:name => 'Single', :field_format => 'list', :possible_values => ['value1', 'value2'])
114 114 TimeEntryCustomField.create!(:name => 'Multi', :field_format => 'list', :multiple => true, :possible_values => ['value1', 'value2'])
115 115
116 116 get :report, :project_id => 1
117 117 assert_response :success
118 118 assert_select 'select[name=?]', 'criteria[]' do
119 119 assert_select 'option', :text => 'Single'
120 120 assert_select 'option', :text => 'Multi', :count => 0
121 121 end
122 122 end
123 123
124 124 def test_report_one_day
125 125 get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criteria => ["user", "activity"]
126 126 assert_response :success
127 127 assert_template 'report'
128 128 assert_not_nil assigns(:report)
129 129 assert_equal "4.25", "%.2f" % assigns(:report).total_hours
130 130 end
131 131
132 132 def test_report_at_issue_level
133 get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"]
133 get :report, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"]
134 134 assert_response :success
135 135 assert_template 'report'
136 136 assert_not_nil assigns(:report)
137 137 assert_equal "154.25", "%.2f" % assigns(:report).total_hours
138 138 assert_tag :form,
139 :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'}
139 :attributes => {:action => "/issues/1/time_entries/report", :id => 'query_form'}
140 140 end
141 141
142 142 def test_report_by_week_should_use_commercial_year
143 143 TimeEntry.delete_all
144 144 TimeEntry.generate!(:hours => '2', :spent_on => '2009-12-25') # 2009-52
145 145 TimeEntry.generate!(:hours => '4', :spent_on => '2009-12-31') # 2009-53
146 146 TimeEntry.generate!(:hours => '8', :spent_on => '2010-01-01') # 2009-53
147 147 TimeEntry.generate!(:hours => '16', :spent_on => '2010-01-05') # 2010-1
148 148
149 149 get :report, :columns => 'week', :from => "2009-12-25", :to => "2010-01-05", :criteria => ["project"]
150 150 assert_response :success
151 151
152 152 assert_select '#time-report thead tr' do
153 153 assert_select 'th:nth-child(1)', :text => 'Project'
154 154 assert_select 'th:nth-child(2)', :text => '2009-52'
155 155 assert_select 'th:nth-child(3)', :text => '2009-53'
156 156 assert_select 'th:nth-child(4)', :text => '2010-1'
157 157 assert_select 'th:nth-child(5)', :text => 'Total time'
158 158 end
159 159 assert_select '#time-report tbody tr' do
160 160 assert_select 'td:nth-child(1)', :text => 'eCookbook'
161 161 assert_select 'td:nth-child(2)', :text => '2.00'
162 162 assert_select 'td:nth-child(3)', :text => '12.00'
163 163 assert_select 'td:nth-child(4)', :text => '16.00'
164 164 assert_select 'td:nth-child(5)', :text => '30.00' # Total
165 165 end
166 166 end
167 167
168 168 def test_report_should_propose_association_custom_fields
169 169 get :report
170 170 assert_response :success
171 171 assert_template 'report'
172 172
173 173 assert_select 'select[name=?]', 'criteria[]' do
174 174 assert_select 'option[value=cf_1]', {:text => 'Database'}, 'Issue custom field not found'
175 175 assert_select 'option[value=cf_3]', {:text => 'Development status'}, 'Project custom field not found'
176 176 assert_select 'option[value=cf_7]', {:text => 'Billable'}, 'TimeEntryActivity custom field not found'
177 177 end
178 178 end
179 179
180 180 def test_report_with_association_custom_fields
181 181 get :report, :criteria => ['cf_1', 'cf_3', 'cf_7']
182 182 assert_response :success
183 183 assert_template 'report'
184 184 assert_not_nil assigns(:report)
185 185 assert_equal 3, assigns(:report).criteria.size
186 186 assert_equal "162.90", "%.2f" % assigns(:report).total_hours
187 187
188 188 # Custom fields columns
189 189 assert_select 'th', :text => 'Database'
190 190 assert_select 'th', :text => 'Development status'
191 191 assert_select 'th', :text => 'Billable'
192 192
193 193 # Custom field row
194 194 assert_select 'tr' do
195 195 assert_select 'td', :text => 'MySQL'
196 196 assert_select 'td.hours', :text => '1.00'
197 197 end
198 198 end
199 199
200 200 def test_report_one_criteria_no_result
201 201 get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criteria => ['project']
202 202 assert_response :success
203 203 assert_template 'report'
204 204 assert_not_nil assigns(:report)
205 205 assert_equal "0.00", "%.2f" % assigns(:report).total_hours
206 206 end
207 207
208 208 def test_report_status_criterion
209 209 get :report, :project_id => 1, :criteria => ['status']
210 210 assert_response :success
211 211 assert_template 'report'
212 212 assert_tag :tag => 'th', :content => 'Status'
213 213 assert_tag :tag => 'td', :content => 'New'
214 214 end
215 215
216 216 def test_report_all_projects_csv_export
217 217 get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30",
218 218 :criteria => ["project", "user", "activity"], :format => "csv"
219 219 assert_response :success
220 220 assert_equal 'text/csv; header=present', @response.content_type
221 221 lines = @response.body.chomp.split("\n")
222 222 # Headers
223 223 assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first
224 224 # Total row
225 225 assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last
226 226 end
227 227
228 228 def test_report_csv_export
229 229 get :report, :project_id => 1, :columns => 'month',
230 230 :from => "2007-01-01", :to => "2007-06-30",
231 231 :criteria => ["project", "user", "activity"], :format => "csv"
232 232 assert_response :success
233 233 assert_equal 'text/csv; header=present', @response.content_type
234 234 lines = @response.body.chomp.split("\n")
235 235 # Headers
236 236 assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first
237 237 # Total row
238 238 assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last
239 239 end
240 240
241 241 def test_csv_big_5
242 242 Setting.default_language = "zh-TW"
243 243 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
244 244 str_big5 = "\xa4@\xa4\xeb"
245 245 if str_utf8.respond_to?(:force_encoding)
246 246 str_utf8.force_encoding('UTF-8')
247 247 str_big5.force_encoding('Big5')
248 248 end
249 249 user = User.find_by_id(3)
250 250 user.firstname = str_utf8
251 251 user.lastname = "test-lastname"
252 252 assert user.save
253 253 comments = "test_csv_big_5"
254 254 te1 = TimeEntry.create(:spent_on => '2011-11-11',
255 255 :hours => 7.3,
256 256 :project => Project.find(1),
257 257 :user => user,
258 258 :activity => TimeEntryActivity.find_by_name('Design'),
259 259 :comments => comments)
260 260
261 261 te2 = TimeEntry.find_by_comments(comments)
262 262 assert_not_nil te2
263 263 assert_equal 7.3, te2.hours
264 264 assert_equal 3, te2.user_id
265 265
266 266 get :report, :project_id => 1, :columns => 'day',
267 267 :from => "2011-11-11", :to => "2011-11-11",
268 268 :criteria => ["user"], :format => "csv"
269 269 assert_response :success
270 270 assert_equal 'text/csv; header=present', @response.content_type
271 271 lines = @response.body.chomp.split("\n")
272 272 # Headers
273 273 s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xa4u\xae\xc9\xc1`\xadp"
274 274 s2 = "\xa4u\xae\xc9\xc1`\xadp"
275 275 if s1.respond_to?(:force_encoding)
276 276 s1.force_encoding('Big5')
277 277 s2.force_encoding('Big5')
278 278 end
279 279 assert_equal s1, lines.first
280 280 # Total row
281 281 assert_equal "#{str_big5} #{user.lastname},7.30,7.30", lines[1]
282 282 assert_equal "#{s2},7.30,7.30", lines[2]
283 283
284 284 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
285 285 if str_tw.respond_to?(:force_encoding)
286 286 str_tw.force_encoding('UTF-8')
287 287 end
288 288 assert_equal str_tw, l(:general_lang_name)
289 289 assert_equal 'Big5', l(:general_csv_encoding)
290 290 assert_equal ',', l(:general_csv_separator)
291 291 assert_equal '.', l(:general_csv_decimal_separator)
292 292 end
293 293
294 294 def test_csv_cannot_convert_should_be_replaced_big_5
295 295 Setting.default_language = "zh-TW"
296 296 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
297 297 if str_utf8.respond_to?(:force_encoding)
298 298 str_utf8.force_encoding('UTF-8')
299 299 end
300 300 user = User.find_by_id(3)
301 301 user.firstname = str_utf8
302 302 user.lastname = "test-lastname"
303 303 assert user.save
304 304 comments = "test_replaced"
305 305 te1 = TimeEntry.create(:spent_on => '2011-11-11',
306 306 :hours => 7.3,
307 307 :project => Project.find(1),
308 308 :user => user,
309 309 :activity => TimeEntryActivity.find_by_name('Design'),
310 310 :comments => comments)
311 311
312 312 te2 = TimeEntry.find_by_comments(comments)
313 313 assert_not_nil te2
314 314 assert_equal 7.3, te2.hours
315 315 assert_equal 3, te2.user_id
316 316
317 317 get :report, :project_id => 1, :columns => 'day',
318 318 :from => "2011-11-11", :to => "2011-11-11",
319 319 :criteria => ["user"], :format => "csv"
320 320 assert_response :success
321 321 assert_equal 'text/csv; header=present', @response.content_type
322 322 lines = @response.body.chomp.split("\n")
323 323 # Headers
324 324 s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xa4u\xae\xc9\xc1`\xadp"
325 325 if s1.respond_to?(:force_encoding)
326 326 s1.force_encoding('Big5')
327 327 end
328 328 assert_equal s1, lines.first
329 329 # Total row
330 330 s2 = ""
331 331 if s2.respond_to?(:force_encoding)
332 332 s2 = "\xa5H?"
333 333 s2.force_encoding('Big5')
334 334 elsif RUBY_PLATFORM == 'java'
335 335 s2 = "??"
336 336 else
337 337 s2 = "\xa5H???"
338 338 end
339 339 assert_equal "#{s2} #{user.lastname},7.30,7.30", lines[1]
340 340 end
341 341
342 342 def test_csv_fr
343 343 with_settings :default_language => "fr" do
344 344 str1 = "test_csv_fr"
345 345 user = User.find_by_id(3)
346 346 te1 = TimeEntry.create(:spent_on => '2011-11-11',
347 347 :hours => 7.3,
348 348 :project => Project.find(1),
349 349 :user => user,
350 350 :activity => TimeEntryActivity.find_by_name('Design'),
351 351 :comments => str1)
352 352
353 353 te2 = TimeEntry.find_by_comments(str1)
354 354 assert_not_nil te2
355 355 assert_equal 7.3, te2.hours
356 356 assert_equal 3, te2.user_id
357 357
358 358 get :report, :project_id => 1, :columns => 'day',
359 359 :from => "2011-11-11", :to => "2011-11-11",
360 360 :criteria => ["user"], :format => "csv"
361 361 assert_response :success
362 362 assert_equal 'text/csv; header=present', @response.content_type
363 363 lines = @response.body.chomp.split("\n")
364 364 # Headers
365 365 s1 = "Utilisateur;2011-11-11;Temps total"
366 366 s2 = "Temps total"
367 367 if s1.respond_to?(:force_encoding)
368 368 s1.force_encoding('ISO-8859-1')
369 369 s2.force_encoding('ISO-8859-1')
370 370 end
371 371 assert_equal s1, lines.first
372 372 # Total row
373 373 assert_equal "#{user.firstname} #{user.lastname};7,30;7,30", lines[1]
374 374 assert_equal "#{s2};7,30;7,30", lines[2]
375 375
376 376 str_fr = "Fran\xc3\xa7ais"
377 377 if str_fr.respond_to?(:force_encoding)
378 378 str_fr.force_encoding('UTF-8')
379 379 end
380 380 assert_equal str_fr, l(:general_lang_name)
381 381 assert_equal 'ISO-8859-1', l(:general_csv_encoding)
382 382 assert_equal ';', l(:general_csv_separator)
383 383 assert_equal ',', l(:general_csv_decimal_separator)
384 384 end
385 385 end
386 386 end
@@ -1,717 +1,717
1 1 # -*- coding: utf-8 -*-
2 2 # Redmine - project management software
3 3 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require File.expand_path('../../test_helper', __FILE__)
20 20
21 21 class TimelogControllerTest < ActionController::TestCase
22 22 fixtures :projects, :enabled_modules, :roles, :members,
23 23 :member_roles, :issues, :time_entries, :users,
24 24 :trackers, :enumerations, :issue_statuses,
25 25 :custom_fields, :custom_values,
26 26 :projects_trackers, :custom_fields_trackers,
27 27 :custom_fields_projects
28 28
29 29 include Redmine::I18n
30 30
31 31 def test_new
32 32 @request.session[:user_id] = 3
33 33 get :new
34 34 assert_response :success
35 35 assert_template 'new'
36 36 assert_select 'input[name=?][type=hidden]', 'project_id', 0
37 37 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
38 38 assert_select 'select[name=?]', 'time_entry[project_id]' do
39 39 # blank option for project
40 40 assert_select 'option[value=]'
41 41 end
42 42 end
43 43
44 44 def test_new_with_project_id
45 45 @request.session[:user_id] = 3
46 46 get :new, :project_id => 1
47 47 assert_response :success
48 48 assert_template 'new'
49 49 assert_select 'input[name=?][type=hidden]', 'project_id'
50 50 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
51 51 assert_select 'select[name=?]', 'time_entry[project_id]', 0
52 52 end
53 53
54 54 def test_new_with_issue_id
55 55 @request.session[:user_id] = 3
56 56 get :new, :issue_id => 2
57 57 assert_response :success
58 58 assert_template 'new'
59 59 assert_select 'input[name=?][type=hidden]', 'project_id', 0
60 60 assert_select 'input[name=?][type=hidden]', 'issue_id'
61 61 assert_select 'select[name=?]', 'time_entry[project_id]', 0
62 62 end
63 63
64 64 def test_new_without_project_should_prefill_the_form
65 65 @request.session[:user_id] = 3
66 66 get :new, :time_entry => {:project_id => '1'}
67 67 assert_response :success
68 68 assert_template 'new'
69 69 assert_select 'select[name=?]', 'time_entry[project_id]' do
70 70 assert_select 'option[value=1][selected=selected]'
71 71 end
72 72 end
73 73
74 74 def test_new_without_project_should_deny_without_permission
75 75 Role.all.each {|role| role.remove_permission! :log_time}
76 76 @request.session[:user_id] = 3
77 77
78 78 get :new
79 79 assert_response 403
80 80 end
81 81
82 82 def test_new_should_select_default_activity
83 83 @request.session[:user_id] = 3
84 84 get :new, :project_id => 1
85 85 assert_response :success
86 86 assert_select 'select[name=?]', 'time_entry[activity_id]' do
87 87 assert_select 'option[selected=selected]', :text => 'Development'
88 88 end
89 89 end
90 90
91 91 def test_new_should_only_show_active_time_entry_activities
92 92 @request.session[:user_id] = 3
93 93 get :new, :project_id => 1
94 94 assert_response :success
95 95 assert_no_tag 'option', :content => 'Inactive Activity'
96 96 end
97 97
98 98 def test_get_edit_existing_time
99 99 @request.session[:user_id] = 2
100 100 get :edit, :id => 2, :project_id => nil
101 101 assert_response :success
102 102 assert_template 'edit'
103 103 # Default activity selected
104 104 assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/time_entries/2' }
105 105 end
106 106
107 107 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
108 108 te = TimeEntry.find(1)
109 109 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
110 110 te.save!
111 111
112 112 @request.session[:user_id] = 1
113 113 get :edit, :project_id => 1, :id => 1
114 114 assert_response :success
115 115 assert_template 'edit'
116 116 # Blank option since nothing is pre-selected
117 117 assert_tag :tag => 'option', :content => '--- Please select ---'
118 118 end
119 119
120 120 def test_post_create
121 121 @request.session[:user_id] = 3
122 122 assert_difference 'TimeEntry.count' do
123 123 post :create, :project_id => 1,
124 124 :time_entry => {:comments => 'Some work on TimelogControllerTest',
125 125 # Not the default activity
126 126 :activity_id => '11',
127 127 :spent_on => '2008-03-14',
128 128 :issue_id => '1',
129 129 :hours => '7.3'}
130 130 assert_redirected_to '/projects/ecookbook/time_entries'
131 131 end
132 132
133 133 t = TimeEntry.order('id DESC').first
134 134 assert_not_nil t
135 135 assert_equal 'Some work on TimelogControllerTest', t.comments
136 136 assert_equal 1, t.project_id
137 137 assert_equal 1, t.issue_id
138 138 assert_equal 11, t.activity_id
139 139 assert_equal 7.3, t.hours
140 140 assert_equal 3, t.user_id
141 141 end
142 142
143 143 def test_post_create_with_blank_issue
144 144 @request.session[:user_id] = 3
145 145 assert_difference 'TimeEntry.count' do
146 146 post :create, :project_id => 1,
147 147 :time_entry => {:comments => 'Some work on TimelogControllerTest',
148 148 # Not the default activity
149 149 :activity_id => '11',
150 150 :issue_id => '',
151 151 :spent_on => '2008-03-14',
152 152 :hours => '7.3'}
153 153 assert_redirected_to '/projects/ecookbook/time_entries'
154 154 end
155 155
156 156 t = TimeEntry.order('id DESC').first
157 157 assert_not_nil t
158 158 assert_equal 'Some work on TimelogControllerTest', t.comments
159 159 assert_equal 1, t.project_id
160 160 assert_nil t.issue_id
161 161 assert_equal 11, t.activity_id
162 162 assert_equal 7.3, t.hours
163 163 assert_equal 3, t.user_id
164 164 end
165 165
166 166 def test_create_and_continue_at_project_level
167 167 @request.session[:user_id] = 2
168 168 assert_difference 'TimeEntry.count' do
169 169 post :create, :time_entry => {:project_id => '1',
170 170 :activity_id => '11',
171 171 :issue_id => '',
172 172 :spent_on => '2008-03-14',
173 173 :hours => '7.3'},
174 174 :continue => '1'
175 175 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
176 176 end
177 177 end
178 178
179 179 def test_create_and_continue_at_issue_level
180 180 @request.session[:user_id] = 2
181 181 assert_difference 'TimeEntry.count' do
182 182 post :create, :time_entry => {:project_id => '',
183 183 :activity_id => '11',
184 184 :issue_id => '1',
185 185 :spent_on => '2008-03-14',
186 186 :hours => '7.3'},
187 187 :continue => '1'
188 188 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
189 189 end
190 190 end
191 191
192 192 def test_create_and_continue_with_project_id
193 193 @request.session[:user_id] = 2
194 194 assert_difference 'TimeEntry.count' do
195 195 post :create, :project_id => 1,
196 196 :time_entry => {:activity_id => '11',
197 197 :issue_id => '',
198 198 :spent_on => '2008-03-14',
199 199 :hours => '7.3'},
200 200 :continue => '1'
201 201 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D='
202 202 end
203 203 end
204 204
205 205 def test_create_and_continue_with_issue_id
206 206 @request.session[:user_id] = 2
207 207 assert_difference 'TimeEntry.count' do
208 208 post :create, :issue_id => 1,
209 209 :time_entry => {:activity_id => '11',
210 210 :issue_id => '1',
211 211 :spent_on => '2008-03-14',
212 212 :hours => '7.3'},
213 213 :continue => '1'
214 214 assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
215 215 end
216 216 end
217 217
218 218 def test_create_without_log_time_permission_should_be_denied
219 219 @request.session[:user_id] = 2
220 220 Role.find_by_name('Manager').remove_permission! :log_time
221 221 post :create, :project_id => 1,
222 222 :time_entry => {:activity_id => '11',
223 223 :issue_id => '',
224 224 :spent_on => '2008-03-14',
225 225 :hours => '7.3'}
226 226
227 227 assert_response 403
228 228 end
229 229
230 230 def test_create_without_project_and_issue_should_fail
231 231 @request.session[:user_id] = 2
232 232 post :create, :time_entry => {:issue_id => ''}
233 233
234 234 assert_response :success
235 235 assert_template 'new'
236 236 end
237 237
238 238 def test_create_with_failure
239 239 @request.session[:user_id] = 2
240 240 post :create, :project_id => 1,
241 241 :time_entry => {:activity_id => '',
242 242 :issue_id => '',
243 243 :spent_on => '2008-03-14',
244 244 :hours => '7.3'}
245 245
246 246 assert_response :success
247 247 assert_template 'new'
248 248 end
249 249
250 250 def test_create_without_project
251 251 @request.session[:user_id] = 2
252 252 assert_difference 'TimeEntry.count' do
253 253 post :create, :time_entry => {:project_id => '1',
254 254 :activity_id => '11',
255 255 :issue_id => '',
256 256 :spent_on => '2008-03-14',
257 257 :hours => '7.3'}
258 258 end
259 259
260 260 assert_redirected_to '/projects/ecookbook/time_entries'
261 261 time_entry = TimeEntry.order('id DESC').first
262 262 assert_equal 1, time_entry.project_id
263 263 end
264 264
265 265 def test_create_without_project_should_fail_with_issue_not_inside_project
266 266 @request.session[:user_id] = 2
267 267 assert_no_difference 'TimeEntry.count' do
268 268 post :create, :time_entry => {:project_id => '1',
269 269 :activity_id => '11',
270 270 :issue_id => '5',
271 271 :spent_on => '2008-03-14',
272 272 :hours => '7.3'}
273 273 end
274 274
275 275 assert_response :success
276 276 assert assigns(:time_entry).errors[:issue_id].present?
277 277 end
278 278
279 279 def test_create_without_project_should_deny_without_permission
280 280 @request.session[:user_id] = 2
281 281 Project.find(3).disable_module!(:time_tracking)
282 282
283 283 assert_no_difference 'TimeEntry.count' do
284 284 post :create, :time_entry => {:project_id => '3',
285 285 :activity_id => '11',
286 286 :issue_id => '',
287 287 :spent_on => '2008-03-14',
288 288 :hours => '7.3'}
289 289 end
290 290
291 291 assert_response 403
292 292 end
293 293
294 294 def test_create_without_project_with_failure
295 295 @request.session[:user_id] = 2
296 296 assert_no_difference 'TimeEntry.count' do
297 297 post :create, :time_entry => {:project_id => '1',
298 298 :activity_id => '11',
299 299 :issue_id => '',
300 300 :spent_on => '2008-03-14',
301 301 :hours => ''}
302 302 end
303 303
304 304 assert_response :success
305 305 assert_tag 'select', :attributes => {:name => 'time_entry[project_id]'},
306 306 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
307 307 end
308 308
309 309 def test_update
310 310 entry = TimeEntry.find(1)
311 311 assert_equal 1, entry.issue_id
312 312 assert_equal 2, entry.user_id
313 313
314 314 @request.session[:user_id] = 1
315 315 put :update, :id => 1,
316 316 :time_entry => {:issue_id => '2',
317 317 :hours => '8'}
318 318 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
319 319 entry.reload
320 320
321 321 assert_equal 8, entry.hours
322 322 assert_equal 2, entry.issue_id
323 323 assert_equal 2, entry.user_id
324 324 end
325 325
326 326 def test_update_should_allow_to_change_issue_to_another_project
327 327 entry = TimeEntry.generate!(:issue_id => 1)
328 328
329 329 @request.session[:user_id] = 1
330 330 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
331 331 assert_response 302
332 332 entry.reload
333 333
334 334 assert_equal 5, entry.issue_id
335 335 assert_equal 3, entry.project_id
336 336 end
337 337
338 338 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
339 339 entry = TimeEntry.generate!(:issue_id => 1)
340 340 Project.find(3).disable_module!(:time_tracking)
341 341
342 342 @request.session[:user_id] = 1
343 343 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
344 344 assert_response 200
345 345 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
346 346 end
347 347
348 348 def test_get_bulk_edit
349 349 @request.session[:user_id] = 2
350 350 get :bulk_edit, :ids => [1, 2]
351 351 assert_response :success
352 352 assert_template 'bulk_edit'
353 353
354 354 assert_select 'ul#bulk-selection' do
355 355 assert_select 'li', 2
356 356 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
357 357 end
358 358
359 359 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
360 360 # System wide custom field
361 361 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
362 362
363 363 # Activities
364 364 assert_select 'select[name=?]', 'time_entry[activity_id]' do
365 365 assert_select 'option[value=]', :text => '(No change)'
366 366 assert_select 'option[value=9]', :text => 'Design'
367 367 end
368 368 end
369 369 end
370 370
371 371 def test_get_bulk_edit_on_different_projects
372 372 @request.session[:user_id] = 2
373 373 get :bulk_edit, :ids => [1, 2, 6]
374 374 assert_response :success
375 375 assert_template 'bulk_edit'
376 376 end
377 377
378 378 def test_bulk_update
379 379 @request.session[:user_id] = 2
380 380 # update time entry activity
381 381 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
382 382
383 383 assert_response 302
384 384 # check that the issues were updated
385 385 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
386 386 end
387 387
388 388 def test_bulk_update_with_failure
389 389 @request.session[:user_id] = 2
390 390 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
391 391
392 392 assert_response 302
393 393 assert_match /Failed to save 2 time entrie/, flash[:error]
394 394 end
395 395
396 396 def test_bulk_update_on_different_projects
397 397 @request.session[:user_id] = 2
398 398 # makes user a manager on the other project
399 399 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
400 400
401 401 # update time entry activity
402 402 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
403 403
404 404 assert_response 302
405 405 # check that the issues were updated
406 406 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
407 407 end
408 408
409 409 def test_bulk_update_on_different_projects_without_rights
410 410 @request.session[:user_id] = 3
411 411 user = User.find(3)
412 412 action = { :controller => "timelog", :action => "bulk_update" }
413 413 assert user.allowed_to?(action, TimeEntry.find(1).project)
414 414 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
415 415 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
416 416 assert_response 403
417 417 end
418 418
419 419 def test_bulk_update_custom_field
420 420 @request.session[:user_id] = 2
421 421 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
422 422
423 423 assert_response 302
424 424 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
425 425 end
426 426
427 427 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
428 428 @request.session[:user_id] = 2
429 429 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
430 430
431 431 assert_response :redirect
432 432 assert_redirected_to '/time_entries'
433 433 end
434 434
435 435 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
436 436 @request.session[:user_id] = 2
437 437 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
438 438
439 439 assert_response :redirect
440 440 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
441 441 end
442 442
443 443 def test_post_bulk_update_without_edit_permission_should_be_denied
444 444 @request.session[:user_id] = 2
445 445 Role.find_by_name('Manager').remove_permission! :edit_time_entries
446 446 post :bulk_update, :ids => [1,2]
447 447
448 448 assert_response 403
449 449 end
450 450
451 451 def test_destroy
452 452 @request.session[:user_id] = 2
453 453 delete :destroy, :id => 1
454 454 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
455 455 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
456 456 assert_nil TimeEntry.find_by_id(1)
457 457 end
458 458
459 459 def test_destroy_should_fail
460 460 # simulate that this fails (e.g. due to a plugin), see #5700
461 461 TimeEntry.any_instance.expects(:destroy).returns(false)
462 462
463 463 @request.session[:user_id] = 2
464 464 delete :destroy, :id => 1
465 465 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
466 466 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
467 467 assert_not_nil TimeEntry.find_by_id(1)
468 468 end
469 469
470 470 def test_index_all_projects
471 471 get :index
472 472 assert_response :success
473 473 assert_template 'index'
474 474 assert_not_nil assigns(:total_hours)
475 475 assert_equal "162.90", "%.2f" % assigns(:total_hours)
476 476 assert_tag :form,
477 477 :attributes => {:action => "/time_entries", :id => 'query_form'}
478 478 end
479 479
480 480 def test_index_all_projects_should_show_log_time_link
481 481 @request.session[:user_id] = 2
482 482 get :index
483 483 assert_response :success
484 484 assert_template 'index'
485 485 assert_tag 'a', :attributes => {:href => '/time_entries/new'}, :content => /Log time/
486 486 end
487 487
488 488 def test_index_my_spent_time
489 489 @request.session[:user_id] = 2
490 490 get :index, :user_id => 'me'
491 491 assert_response :success
492 492 assert_template 'index'
493 493 assert assigns(:entries).all? {|entry| entry.user_id == 2}
494 494 end
495 495
496 496 def test_index_at_project_level
497 497 get :index, :project_id => 'ecookbook'
498 498 assert_response :success
499 499 assert_template 'index'
500 500 assert_not_nil assigns(:entries)
501 501 assert_equal 4, assigns(:entries).size
502 502 # project and subproject
503 503 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
504 504 assert_not_nil assigns(:total_hours)
505 505 assert_equal "162.90", "%.2f" % assigns(:total_hours)
506 506 assert_tag :form,
507 507 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
508 508 end
509 509
510 510 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
511 511 entry = TimeEntry.generate!(:project => Project.find(3))
512 512
513 513 with_settings :display_subprojects_issues => '0' do
514 514 get :index, :project_id => 'ecookbook'
515 515 assert_response :success
516 516 assert_template 'index'
517 517 assert_not_include entry, assigns(:entries)
518 518 end
519 519 end
520 520
521 521 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
522 522 entry = TimeEntry.generate!(:project => Project.find(3))
523 523
524 524 with_settings :display_subprojects_issues => '0' do
525 525 get :index, :project_id => 'ecookbook', :subproject_id => 3
526 526 assert_response :success
527 527 assert_template 'index'
528 528 assert_include entry, assigns(:entries)
529 529 end
530 530 end
531 531
532 532 def test_index_at_project_level_with_date_range
533 533 get :index, :project_id => 'ecookbook',
534 534 :f => ['spent_on'],
535 535 :op => {'spent_on' => '><'},
536 536 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
537 537 assert_response :success
538 538 assert_template 'index'
539 539 assert_not_nil assigns(:entries)
540 540 assert_equal 3, assigns(:entries).size
541 541 assert_not_nil assigns(:total_hours)
542 542 assert_equal "12.90", "%.2f" % assigns(:total_hours)
543 543 assert_tag :form,
544 544 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
545 545 end
546 546
547 547 def test_index_at_project_level_with_date_range_using_from_and_to_params
548 548 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
549 549 assert_response :success
550 550 assert_template 'index'
551 551 assert_not_nil assigns(:entries)
552 552 assert_equal 3, assigns(:entries).size
553 553 assert_not_nil assigns(:total_hours)
554 554 assert_equal "12.90", "%.2f" % assigns(:total_hours)
555 555 assert_tag :form,
556 556 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
557 557 end
558 558
559 559 def test_index_at_project_level_with_period
560 560 get :index, :project_id => 'ecookbook',
561 561 :f => ['spent_on'],
562 562 :op => {'spent_on' => '>t-'},
563 563 :v => {'spent_on' => ['7']}
564 564 assert_response :success
565 565 assert_template 'index'
566 566 assert_not_nil assigns(:entries)
567 567 assert_not_nil assigns(:total_hours)
568 568 assert_tag :form,
569 569 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
570 570 end
571 571
572 572 def test_index_at_issue_level
573 573 get :index, :issue_id => 1
574 574 assert_response :success
575 575 assert_template 'index'
576 576 assert_not_nil assigns(:entries)
577 577 assert_equal 2, assigns(:entries).size
578 578 assert_not_nil assigns(:total_hours)
579 579 assert_equal 154.25, assigns(:total_hours)
580 580 # display all time
581 581 assert_nil assigns(:from)
582 582 assert_nil assigns(:to)
583 583 assert_tag :form,
584 :attributes => {:action => "/projects/ecookbook/issues/1/time_entries", :id => 'query_form'}
584 :attributes => {:action => "/issues/1/time_entries", :id => 'query_form'}
585 585 end
586 586
587 587 def test_index_should_sort_by_spent_on_and_created_on
588 588 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
589 589 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
590 590 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
591 591
592 592 get :index, :project_id => 1,
593 593 :f => ['spent_on'],
594 594 :op => {'spent_on' => '><'},
595 595 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
596 596 assert_response :success
597 597 assert_equal [t2, t1, t3], assigns(:entries)
598 598
599 599 get :index, :project_id => 1,
600 600 :f => ['spent_on'],
601 601 :op => {'spent_on' => '><'},
602 602 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
603 603 :sort => 'spent_on'
604 604 assert_response :success
605 605 assert_equal [t3, t1, t2], assigns(:entries)
606 606 end
607 607
608 608 def test_index_with_filter_on_issue_custom_field
609 609 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
610 610 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
611 611
612 612 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
613 613 assert_response :success
614 614 assert_equal [entry], assigns(:entries)
615 615 end
616 616
617 617 def test_index_with_issue_custom_field_column
618 618 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
619 619 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
620 620
621 621 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
622 622 assert_response :success
623 623 assert_include :'issue.cf_2', assigns(:query).column_names
624 624 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
625 625 end
626 626
627 627 def test_index_with_time_entry_custom_field_column
628 628 field = TimeEntryCustomField.generate!(:field_format => 'string')
629 629 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
630 630 field_name = "cf_#{field.id}"
631 631
632 632 get :index, :c => ["hours", field_name]
633 633 assert_response :success
634 634 assert_include field_name.to_sym, assigns(:query).column_names
635 635 assert_select "td.#{field_name}", :text => 'CF Value'
636 636 end
637 637
638 638 def test_index_with_time_entry_custom_field_sorting
639 639 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
640 640 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
641 641 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
642 642 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
643 643 field_name = "cf_#{field.id}"
644 644
645 645 get :index, :c => ["hours", field_name], :sort => field_name
646 646 assert_response :success
647 647 assert_include field_name.to_sym, assigns(:query).column_names
648 648 assert_select "th a.sort", :text => 'String Field'
649 649
650 650 # Make sure that values are properly sorted
651 651 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
652 652 assert_equal 3, values.size
653 653 assert_equal values.sort, values
654 654 end
655 655
656 656 def test_index_atom_feed
657 657 get :index, :project_id => 1, :format => 'atom'
658 658 assert_response :success
659 659 assert_equal 'application/atom+xml', @response.content_type
660 660 assert_not_nil assigns(:items)
661 661 assert assigns(:items).first.is_a?(TimeEntry)
662 662 end
663 663
664 664 def test_index_at_project_level_should_include_csv_export_dialog
665 665 get :index, :project_id => 'ecookbook',
666 666 :f => ['spent_on'],
667 667 :op => {'spent_on' => '>='},
668 668 :v => {'spent_on' => ['2007-04-01']},
669 669 :c => ['spent_on', 'user']
670 670 assert_response :success
671 671
672 672 assert_select '#csv-export-options' do
673 673 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
674 674 # filter
675 675 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
676 676 assert_select 'input[name=?][value=?]', 'op[spent_on]', '&gt;='
677 677 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
678 678 # columns
679 679 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
680 680 assert_select 'input[name=?][value=?]', 'c[]', 'user'
681 681 assert_select 'input[name=?]', 'c[]', 2
682 682 end
683 683 end
684 684 end
685 685
686 686 def test_index_cross_project_should_include_csv_export_dialog
687 687 get :index
688 688 assert_response :success
689 689
690 690 assert_select '#csv-export-options' do
691 691 assert_select 'form[action=?][method=get]', '/time_entries.csv'
692 692 end
693 693 end
694 694
695 695 def test_index_at_issue_level_should_include_csv_export_dialog
696 get :index, :project_id => 'ecookbook', :issue_id => 3
696 get :index, :issue_id => 3
697 697 assert_response :success
698 698
699 699 assert_select '#csv-export-options' do
700 assert_select 'form[action=?][method=get]', '/projects/ecookbook/issues/3/time_entries.csv'
700 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
701 701 end
702 702 end
703 703
704 704 def test_index_csv_all_projects
705 705 Setting.date_format = '%m/%d/%Y'
706 706 get :index, :format => 'csv'
707 707 assert_response :success
708 708 assert_equal 'text/csv; header=present', response.content_type
709 709 end
710 710
711 711 def test_index_csv
712 712 Setting.date_format = '%m/%d/%Y'
713 713 get :index, :project_id => 1, :format => 'csv'
714 714 assert_response :success
715 715 assert_equal 'text/csv; header=present', response.content_type
716 716 end
717 717 end
@@ -1,240 +1,189
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class RoutingTimelogsTest < ActionController::IntegrationTest
21 21 def test_timelogs_global
22 22 assert_routing(
23 23 { :method => 'get', :path => "/time_entries" },
24 24 { :controller => 'timelog', :action => 'index' }
25 25 )
26 26 assert_routing(
27 27 { :method => 'get', :path => "/time_entries.csv" },
28 28 { :controller => 'timelog', :action => 'index', :format => 'csv' }
29 29 )
30 30 assert_routing(
31 31 { :method => 'get', :path => "/time_entries.atom" },
32 32 { :controller => 'timelog', :action => 'index', :format => 'atom' }
33 33 )
34 34 assert_routing(
35 35 { :method => 'get', :path => "/time_entries/new" },
36 36 { :controller => 'timelog', :action => 'new' }
37 37 )
38 38 assert_routing(
39 39 { :method => 'get', :path => "/time_entries/22/edit" },
40 40 { :controller => 'timelog', :action => 'edit', :id => '22' }
41 41 )
42 42 assert_routing(
43 43 { :method => 'post', :path => "/time_entries" },
44 44 { :controller => 'timelog', :action => 'create' }
45 45 )
46 46 assert_routing(
47 47 { :method => 'put', :path => "/time_entries/22" },
48 48 { :controller => 'timelog', :action => 'update', :id => '22' }
49 49 )
50 50 assert_routing(
51 51 { :method => 'delete', :path => "/time_entries/55" },
52 52 { :controller => 'timelog', :action => 'destroy', :id => '55' }
53 53 )
54 54 end
55 55
56 56 def test_timelogs_scoped_under_project
57 57 assert_routing(
58 58 { :method => 'get', :path => "/projects/567/time_entries" },
59 59 { :controller => 'timelog', :action => 'index', :project_id => '567' }
60 60 )
61 61 assert_routing(
62 62 { :method => 'get', :path => "/projects/567/time_entries.csv" },
63 63 { :controller => 'timelog', :action => 'index', :project_id => '567',
64 64 :format => 'csv' }
65 65 )
66 66 assert_routing(
67 67 { :method => 'get', :path => "/projects/567/time_entries.atom" },
68 68 { :controller => 'timelog', :action => 'index', :project_id => '567',
69 69 :format => 'atom' }
70 70 )
71 71 assert_routing(
72 72 { :method => 'get', :path => "/projects/567/time_entries/new" },
73 73 { :controller => 'timelog', :action => 'new', :project_id => '567' }
74 74 )
75 75 assert_routing(
76 76 { :method => 'get', :path => "/projects/567/time_entries/22/edit" },
77 77 { :controller => 'timelog', :action => 'edit',
78 78 :id => '22', :project_id => '567' }
79 79 )
80 80 assert_routing(
81 81 { :method => 'post', :path => "/projects/567/time_entries" },
82 82 { :controller => 'timelog', :action => 'create',
83 83 :project_id => '567' }
84 84 )
85 85 assert_routing(
86 86 { :method => 'put', :path => "/projects/567/time_entries/22" },
87 87 { :controller => 'timelog', :action => 'update',
88 88 :id => '22', :project_id => '567' }
89 89 )
90 90 assert_routing(
91 91 { :method => 'delete', :path => "/projects/567/time_entries/55" },
92 92 { :controller => 'timelog', :action => 'destroy',
93 93 :id => '55', :project_id => '567' }
94 94 )
95 95 end
96 96
97 97 def test_timelogs_scoped_under_issues
98 98 assert_routing(
99 99 { :method => 'get', :path => "/issues/234/time_entries" },
100 100 { :controller => 'timelog', :action => 'index', :issue_id => '234' }
101 101 )
102 102 assert_routing(
103 103 { :method => 'get', :path => "/issues/234/time_entries.csv" },
104 104 { :controller => 'timelog', :action => 'index', :issue_id => '234',
105 105 :format => 'csv' }
106 106 )
107 107 assert_routing(
108 108 { :method => 'get', :path => "/issues/234/time_entries.atom" },
109 109 { :controller => 'timelog', :action => 'index', :issue_id => '234',
110 110 :format => 'atom' }
111 111 )
112 112 assert_routing(
113 113 { :method => 'get', :path => "/issues/234/time_entries/new" },
114 114 { :controller => 'timelog', :action => 'new', :issue_id => '234' }
115 115 )
116 116 assert_routing(
117 117 { :method => 'get', :path => "/issues/234/time_entries/22/edit" },
118 118 { :controller => 'timelog', :action => 'edit', :id => '22',
119 119 :issue_id => '234' }
120 120 )
121 121 assert_routing(
122 122 { :method => 'post', :path => "/issues/234/time_entries" },
123 123 { :controller => 'timelog', :action => 'create', :issue_id => '234' }
124 124 )
125 125 assert_routing(
126 126 { :method => 'put', :path => "/issues/234/time_entries/22" },
127 127 { :controller => 'timelog', :action => 'update', :id => '22',
128 128 :issue_id => '234' }
129 129 )
130 130 assert_routing(
131 131 { :method => 'delete', :path => "/issues/234/time_entries/55" },
132 132 { :controller => 'timelog', :action => 'destroy', :id => '55',
133 133 :issue_id => '234' }
134 134 )
135 135 end
136 136
137 def test_timelogs_scoped_under_project_and_issues
138 assert_routing(
139 { :method => 'get',
140 :path => "/projects/ecookbook/issues/234/time_entries" },
141 { :controller => 'timelog', :action => 'index',
142 :issue_id => '234', :project_id => 'ecookbook' }
143 )
144 assert_routing(
145 { :method => 'get',
146 :path => "/projects/ecookbook/issues/234/time_entries.csv" },
147 { :controller => 'timelog', :action => 'index',
148 :issue_id => '234', :project_id => 'ecookbook', :format => 'csv' }
149 )
150 assert_routing(
151 { :method => 'get',
152 :path => "/projects/ecookbook/issues/234/time_entries.atom" },
153 { :controller => 'timelog', :action => 'index',
154 :issue_id => '234', :project_id => 'ecookbook', :format => 'atom' }
155 )
156 assert_routing(
157 { :method => 'get',
158 :path => "/projects/ecookbook/issues/234/time_entries/new" },
159 { :controller => 'timelog', :action => 'new',
160 :issue_id => '234', :project_id => 'ecookbook' }
161 )
162 assert_routing(
163 { :method => 'get',
164 :path => "/projects/ecookbook/issues/234/time_entries/22/edit" },
165 { :controller => 'timelog', :action => 'edit', :id => '22',
166 :issue_id => '234', :project_id => 'ecookbook' }
167 )
168 assert_routing(
169 { :method => 'post',
170 :path => "/projects/ecookbook/issues/234/time_entries" },
171 { :controller => 'timelog', :action => 'create',
172 :issue_id => '234', :project_id => 'ecookbook' }
173 )
174 assert_routing(
175 { :method => 'put',
176 :path => "/projects/ecookbook/issues/234/time_entries/22" },
177 { :controller => 'timelog', :action => 'update', :id => '22',
178 :issue_id => '234', :project_id => 'ecookbook' }
179 )
180 assert_routing(
181 { :method => 'delete',
182 :path => "/projects/ecookbook/issues/234/time_entries/55" },
183 { :controller => 'timelog', :action => 'destroy', :id => '55',
184 :issue_id => '234', :project_id => 'ecookbook' }
185 )
186 end
187
188 137 def test_timelogs_report
189 138 assert_routing(
190 139 { :method => 'get',
191 140 :path => "/time_entries/report" },
192 141 { :controller => 'timelog', :action => 'report' }
193 142 )
194 143 assert_routing(
195 144 { :method => 'get',
196 145 :path => "/time_entries/report.csv" },
197 146 { :controller => 'timelog', :action => 'report', :format => 'csv' }
198 147 )
199 148 assert_routing(
200 149 { :method => 'get',
201 150 :path => "/issues/234/time_entries/report" },
202 151 { :controller => 'timelog', :action => 'report', :issue_id => '234' }
203 152 )
204 153 assert_routing(
205 154 { :method => 'get',
206 155 :path => "/issues/234/time_entries/report.csv" },
207 156 { :controller => 'timelog', :action => 'report', :issue_id => '234',
208 157 :format => 'csv' }
209 158 )
210 159 assert_routing(
211 160 { :method => 'get',
212 161 :path => "/projects/567/time_entries/report" },
213 162 { :controller => 'timelog', :action => 'report', :project_id => '567' }
214 163 )
215 164 assert_routing(
216 165 { :method => 'get',
217 166 :path => "/projects/567/time_entries/report.csv" },
218 167 { :controller => 'timelog', :action => 'report', :project_id => '567',
219 168 :format => 'csv' }
220 169 )
221 170 end
222 171
223 172 def test_timelogs_bulk_edit
224 173 assert_routing(
225 174 { :method => 'delete',
226 175 :path => "/time_entries/destroy" },
227 176 { :controller => 'timelog', :action => 'destroy' }
228 177 )
229 178 assert_routing(
230 179 { :method => 'post',
231 180 :path => "/time_entries/bulk_update" },
232 181 { :controller => 'timelog', :action => 'bulk_update' }
233 182 )
234 183 assert_routing(
235 184 { :method => 'get',
236 185 :path => "/time_entries/bulk_edit" },
237 186 { :controller => 'timelog', :action => 'bulk_edit' }
238 187 )
239 188 end
240 189 end
General Comments 0
You need to be logged in to leave comments. Login now