issues_controller_test.rb 157 KB
Newer Older
1
# Redmine - project management software
jplang's avatar
jplang committed
2
# Copyright (C) 2006-2015  Jean-Philippe Lang
3 4 5 6 7
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
8
#
9 10 11 12
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
13
#
14 15 16 17
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

18
require File.expand_path('../../test_helper', __FILE__)
19

edavis10's avatar
edavis10 committed
20
class IssuesControllerTest < ActionController::TestCase
21
  fixtures :projects,
22
           :users, :email_addresses,
23 24
           :roles,
           :members,
25
           :member_roles,
26 27
           :issues,
           :issue_statuses,
28
           :issue_relations,
29
           :versions,
30
           :trackers,
31
           :projects_trackers,
32 33 34
           :issue_categories,
           :enabled_modules,
           :enumerations,
35
           :attachments,
36 37 38
           :workflows,
           :custom_fields,
           :custom_values,
jplang's avatar
jplang committed
39
           :custom_fields_projects,
40
           :custom_fields_trackers,
41 42
           :time_entries,
           :journals,
edavis10's avatar
edavis10 committed
43
           :journal_details,
44 45 46
           :queries,
           :repositories,
           :changesets
47

48 49
  include Redmine::I18n

50 51 52
  def setup
    User.current = nil
  end
53

54
  def test_index
55 56 57 58 59 60
    with_settings :default_language => "en" do
      get :index
      assert_response :success
      assert_template 'index'
      assert_not_nil assigns(:issues)
      assert_nil assigns(:project)
61 62

      # links to visible issues
63
      assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
64
      assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
65
      # private projects hidden
66 67
      assert_select 'a[href="/issues/6"]', 0
      assert_select 'a[href="/issues/4"]', 0
68
      # project column
69
      assert_select 'th', :text => /Project/
70
    end
71
  end
72

73 74
  def test_index_should_not_list_issues_when_module_disabled
    EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 76
    get :index
    assert_response :success
tmaruyama's avatar
tmaruyama committed
77
    assert_template 'index'
78 79
    assert_not_nil assigns(:issues)
    assert_nil assigns(:project)
80

81 82
    assert_select 'a[href="/issues/1"]', 0
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
83
  end
jplang's avatar
jplang committed
84 85 86 87 88 89 90

  def test_index_should_list_visible_issues_only
    get :index, :per_page => 100
    assert_response :success
    assert_not_nil assigns(:issues)
    assert_nil assigns(:issues).detect {|issue| !issue.visible?}
  end
91

92
  def test_index_with_project
93
    Setting.display_subprojects_issues = 0
94 95
    get :index, :project_id => 1
    assert_response :success
96
    assert_template 'index'
97
    assert_not_nil assigns(:issues)
98

99
    assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
100
    assert_select 'a[href="/issues/5"]', 0
101
  end
102

103 104 105 106
  def test_index_with_project_and_subprojects
    Setting.display_subprojects_issues = 1
    get :index, :project_id => 1
    assert_response :success
107
    assert_template 'index'
108
    assert_not_nil assigns(:issues)
109

110
    assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
111 112
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
    assert_select 'a[href="/issues/6"]', 0
113
  end
114

115
  def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
116 117 118 119
    @request.session[:user_id] = 2
    Setting.display_subprojects_issues = 1
    get :index, :project_id => 1
    assert_response :success
tmaruyama's avatar
tmaruyama committed
120
    assert_template 'index'
121
    assert_not_nil assigns(:issues)
122

123
    assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
124 125
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
    assert_select 'a[href="/issues/6"]', :text => /Issue of a private subproject/
126
  end
127

128
  def test_index_with_project_and_default_filter
129 130
    get :index, :project_id => 1, :set_filter => 1
    assert_response :success
131
    assert_template 'index'
132
    assert_not_nil assigns(:issues)
133

134 135 136 137 138
    query = assigns(:query)
    assert_not_nil query
    # default filter
    assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
  end
139

140
  def test_index_with_project_and_filter
141
    get :index, :project_id => 1, :set_filter => 1,
jplang's avatar
jplang committed
142 143
      :f => ['tracker_id'],
      :op => {'tracker_id' => '='},
144
      :v => {'tracker_id' => ['1']}
145
    assert_response :success
146
    assert_template 'index'
147
    assert_not_nil assigns(:issues)
148

149 150 151 152
    query = assigns(:query)
    assert_not_nil query
    assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
  end
153

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
  def test_index_with_short_filters
    to_test = {
      'status_id' => {
        'o' => { :op => 'o', :values => [''] },
        'c' => { :op => 'c', :values => [''] },
        '7' => { :op => '=', :values => ['7'] },
        '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
        '=7' => { :op => '=', :values => ['7'] },
        '!3' => { :op => '!', :values => ['3'] },
        '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
      'subject' => {
        'This is a subject' => { :op => '=', :values => ['This is a subject'] },
        'o' => { :op => '=', :values => ['o'] },
        '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
        '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
169 170
      'tracker_id' => {
        '3' => { :op => '=', :values => ['3'] },
emassip's avatar
emassip committed
171
        '=3' => { :op => '=', :values => ['3'] }},
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
      'start_date' => {
        '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
        '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
        '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
        '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
        '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
        '<t+2' => { :op => '<t+', :values => ['2'] },
        '>t+2' => { :op => '>t+', :values => ['2'] },
        't+2' => { :op => 't+', :values => ['2'] },
        't' => { :op => 't', :values => [''] },
        'w' => { :op => 'w', :values => [''] },
        '>t-2' => { :op => '>t-', :values => ['2'] },
        '<t-2' => { :op => '<t-', :values => ['2'] },
        't-2' => { :op => 't-', :values => ['2'] }},
      'created_on' => {
        '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
188 189 190
        '<t-2' => { :op => '<t-', :values => ['2'] },
        '>t-2' => { :op => '>t-', :values => ['2'] },
        't-2' => { :op => 't-', :values => ['2'] }},
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
      'cf_1' => {
        'c' => { :op => '=', :values => ['c'] },
        '!c' => { :op => '!', :values => ['c'] },
        '!*' => { :op => '!*', :values => [''] },
        '*' => { :op => '*', :values => [''] }},
      'estimated_hours' => {
        '=13.4' => { :op => '=', :values => ['13.4'] },
        '>=45' => { :op => '>=', :values => ['45'] },
        '<=125' => { :op => '<=', :values => ['125'] },
        '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
        '!*' => { :op => '!*', :values => [''] },
        '*' => { :op => '*', :values => [''] }}
    }

    default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}

    to_test.each do |field, expression_and_expected|
      expression_and_expected.each do |filter_expression, expected|
emassip's avatar
emassip committed
209

210 211 212 213 214 215 216 217 218 219 220 221 222 223
        get :index, :set_filter => 1, field => filter_expression

        assert_response :success
        assert_template 'index'
        assert_not_nil assigns(:issues)

        query = assigns(:query)
        assert_not_nil query
        assert query.has_filter?(field)
        assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
      end
    end
  end

224 225 226
  def test_index_with_project_and_empty_filters
    get :index, :project_id => 1, :set_filter => 1, :fields => ['']
    assert_response :success
227
    assert_template 'index'
228
    assert_not_nil assigns(:issues)
229

230 231 232 233
    query = assigns(:query)
    assert_not_nil query
    # no filter
    assert_equal({}, query.filters)
234
  end
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
  def test_index_with_project_custom_field_filter
    field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
    CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
    CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
    filter_name = "project.cf_#{field.id}"
    @request.session[:user_id] = 1

    get :index, :set_filter => 1,
      :f => [filter_name],
      :op => {filter_name => '='},
      :v => {filter_name => ['Foo']}
    assert_response :success
    assert_template 'index'
    assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
  end

jplang's avatar
jplang committed
252 253 254
  def test_index_with_query
    get :index, :project_id => 1, :query_id => 5
    assert_response :success
255
    assert_template 'index'
jplang's avatar
jplang committed
256 257 258
    assert_not_nil assigns(:issues)
    assert_nil assigns(:issue_count_by_group)
  end
259

260
  def test_index_with_query_grouped_by_tracker
jplang's avatar
jplang committed
261 262
    get :index, :project_id => 1, :query_id => 6
    assert_response :success
263
    assert_template 'index'
jplang's avatar
jplang committed
264
    assert_not_nil assigns(:issues)
265
    assert_not_nil assigns(:issue_count_by_group)
266
  end
267

268 269 270 271 272 273 274 275
  def test_index_with_query_grouped_and_sorted_by_category
    get :index, :project_id => 1, :set_filter => 1, :group_by => "category", :sort => "category"
    assert_response :success
    assert_template 'index'
    assert_not_nil assigns(:issues)
    assert_not_nil assigns(:issue_count_by_group)
  end

276 277 278
  def test_index_with_query_grouped_by_list_custom_field
    get :index, :project_id => 1, :query_id => 9
    assert_response :success
tmaruyama's avatar
tmaruyama committed
279
    assert_template 'index'
280
    assert_not_nil assigns(:issues)
281
    assert_not_nil assigns(:issue_count_by_group)
jplang's avatar
jplang committed
282
  end
283

284 285 286 287 288 289 290 291 292 293 294 295 296
  def test_index_with_query_grouped_by_user_custom_field
    cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')

    get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
    assert_response :success

    assert_select 'tr.group', 3
    assert_select 'tr.group' do
      assert_select 'a', :text => 'John Smith'
jplang's avatar
jplang committed
297
      assert_select 'span.count', :text => '1'
298 299 300
    end
    assert_select 'tr.group' do
      assert_select 'a', :text => 'Dave Lopper'
jplang's avatar
jplang committed
301
      assert_select 'span.count', :text => '2'
302 303 304
    end
  end

305 306 307 308 309 310 311 312 313 314 315
  def test_index_grouped_by_boolean_custom_field_should_distinguish_blank_and_false_values
    cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '1')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '')

    with_settings :default_language => 'en' do
      get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
      assert_response :success
    end

316
    assert_select 'tr.group', 3
317 318
    assert_select 'tr.group', :text => /Yes/
    assert_select 'tr.group', :text => /No/
jplang's avatar
jplang committed
319
    assert_select 'tr.group', :text => /blank/
320 321
  end

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
  def test_index_grouped_by_boolean_custom_field_with_false_group_in_first_position_should_show_the_group
    cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool', :is_filter => true)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '0')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')

    with_settings :default_language => 'en' do
      get :index, :project_id => 1, :set_filter => 1, "cf_#{cf.id}" => "*", :group_by => "cf_#{cf.id}"
      assert_response :success
      assert_equal [1, 2], assigns(:issues).map(&:id).sort
    end

    assert_select 'tr.group', 1
    assert_select 'tr.group', :text => /No/
  end

337
  def test_index_with_query_grouped_by_tracker_in_normal_order
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    3.times {|i| Issue.generate!(:tracker_id => (i + 1))}

    get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
    assert_response :success

    trackers = assigns(:issues).map(&:tracker).uniq
    assert_equal [1, 2, 3], trackers.map(&:id)
  end

  def test_index_with_query_grouped_by_tracker_in_reverse_order
    3.times {|i| Issue.generate!(:tracker_id => (i + 1))}

    get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
    assert_response :success

    trackers = assigns(:issues).map(&:tracker).uniq
    assert_equal [3, 2, 1], trackers.map(&:id)
  end

357 358 359 360 361 362 363 364
  def test_index_with_query_id_and_project_id_should_set_session_query
    get :index, :project_id => 1, :query_id => 4
    assert_response :success
    assert_kind_of Hash, session[:query]
    assert_equal 4, session[:query][:id]
    assert_equal 1, session[:query][:project_id]
  end

365 366 367 368 369
  def test_index_with_invalid_query_id_should_respond_404
    get :index, :project_id => 1, :query_id => 999
    assert_response 404
  end

370
  def test_index_with_cross_project_query_in_session_should_show_project_issues
jplang's avatar
jplang committed
371
    q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
372 373 374 375 376 377 378 379 380 381 382 383
    @request.session[:query] = {:id => q.id, :project_id => 1}

    with_settings :display_subprojects_issues => '0' do
      get :index, :project_id => 1
    end
    assert_response :success
    assert_not_nil assigns(:query)
    assert_equal q.id, assigns(:query).id
    assert_equal 1, assigns(:query).project_id
    assert_equal [1], assigns(:issues).map(&:project_id).uniq
  end

384
  def test_private_query_should_not_be_available_to_other_users
jplang's avatar
jplang committed
385
    q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
386
    @request.session[:user_id] = 3
387

388 389 390
    get :index, :query_id => q.id
    assert_response 403
  end
391

392
  def test_private_query_should_be_available_to_its_user
jplang's avatar
jplang committed
393
    q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
394
    @request.session[:user_id] = 2
395

396 397 398
    get :index, :query_id => q.id
    assert_response :success
  end
399

400
  def test_public_query_should_be_available_to_other_users
jplang's avatar
jplang committed
401
    q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
402
    @request.session[:user_id] = 3
403

404 405 406
    get :index, :query_id => q.id
    assert_response :success
  end
407

408 409 410
  def test_index_should_omit_page_param_in_export_links
    get :index, :page => 2
    assert_response :success
411 412 413
    assert_select 'a.atom[href="/issues.atom"]'
    assert_select 'a.csv[href="/issues.csv"]'
    assert_select 'a.pdf[href="/issues.pdf"]'
jplang's avatar
jplang committed
414
    assert_select 'form#csv-export-form[action="/issues.csv"]'
415 416
  end

417 418 419 420 421 422 423 424 425 426 427 428 429 430
  def test_index_should_not_warn_when_not_exceeding_export_limit
    with_settings :issues_export_limit => 200 do
      get :index
      assert_select '#csv-export-options p.icon-warning', 0
    end
  end

  def test_index_should_warn_when_exceeding_export_limit
    with_settings :issues_export_limit => 2 do
      get :index
      assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
    end
  end

431
  def test_index_csv
432 433 434
    get :index, :format => 'csv'
    assert_response :success
    assert_not_nil assigns(:issues)
jplang's avatar
jplang committed
435
    assert_equal 'text/csv; header=present', @response.content_type
436
    assert @response.body.starts_with?("#,")
437
    lines = @response.body.chomp.split("\n")
438
    assert_equal assigns(:query).columns.size, lines[0].split(',').size
439
  end
440

441
  def test_index_csv_with_project
442 443 444
    get :index, :project_id => 1, :format => 'csv'
    assert_response :success
    assert_not_nil assigns(:issues)
jplang's avatar
jplang committed
445
    assert_equal 'text/csv; header=present', @response.content_type
446
  end
447

448
  def test_index_csv_with_description
449 450 451 452 453 454 455 456 457 458 459 460
    Issue.generate!(:description => 'test_index_csv_with_description')

    with_settings :default_language => 'en' do
      get :index, :format => 'csv', :description => '1'
      assert_response :success
      assert_not_nil assigns(:issues)
    end

    assert_equal 'text/csv; header=present', response.content_type
    headers = response.body.chomp.split("\n").first.split(',')
    assert_include 'Description', headers
    assert_include 'test_index_csv_with_description', response.body
461 462
  end

463
  def test_index_csv_with_spent_time_column
jplang's avatar
jplang committed
464 465
    issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
    TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
466 467 468

    get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
    assert_response :success
jplang's avatar
jplang committed
469
    assert_equal 'text/csv; header=present', @response.content_type
470 471 472 473
    lines = @response.body.chomp.split("\n")
    assert_include "#{issue.id},#{issue.subject},7.33", lines
  end

474 475 476 477
  def test_index_csv_with_all_columns
    get :index, :format => 'csv', :columns => 'all'
    assert_response :success
    assert_not_nil assigns(:issues)
jplang's avatar
jplang committed
478
    assert_equal 'text/csv; header=present', @response.content_type
479 480 481
    assert_match /\A#,/, response.body
    lines = response.body.chomp.split("\n")
    assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size
482 483
  end

jplang's avatar
jplang committed
484 485 486 487 488 489 490 491 492 493 494 495
  def test_index_csv_with_multi_column_field
    CustomField.find(1).update_attribute :multiple, true
    issue = Issue.find(1)
    issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
    issue.save!

    get :index, :format => 'csv', :columns => 'all'
    assert_response :success
    lines = @response.body.chomp.split("\n")
    assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
  end

496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
  def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
    field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
    issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})

    with_settings :default_language => 'fr' do
      get :index, :format => 'csv', :columns => 'all'
      assert_response :success
      issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
      assert_include '185,60', issue_line
    end

    with_settings :default_language => 'en' do
      get :index, :format => 'csv', :columns => 'all'
      assert_response :success
      issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
      assert_include '185.60', issue_line
    end
  end
514 515 516 517 518 519 520 521 522

  def test_index_csv_should_fill_parent_column_with_parent_id
    Issue.delete_all
    parent = Issue.generate!
    child = Issue.generate!(:parent_issue_id => parent.id)

    with_settings :default_language => 'en' do
      get :index, :format => 'csv', :c => %w(parent)
    end
jplang's avatar
Typo.  
jplang committed
523
    lines = response.body.split("\n")
524 525
    assert_include "#{child.id},#{parent.id}", lines
  end
526

527 528
  def test_index_csv_big_5
    with_settings :default_language => "zh-TW" do
jplang's avatar
jplang committed
529 530
      str_utf8  = "\xe4\xb8\x80\xe6\x9c\x88".force_encoding('UTF-8')
      str_big5  = "\xa4@\xa4\xeb".force_encoding('Big5')
jplang's avatar
jplang committed
531
      issue = Issue.generate!(:subject => str_utf8)
532 533 534 535 536

      get :index, :project_id => 1, 
                  :f => ['subject'], 
                  :op => '=', :values => [str_utf8],
                  :format => 'csv'
jplang's avatar
jplang committed
537
      assert_equal 'text/csv; header=present', @response.content_type
538
      lines = @response.body.chomp.split("\n")
539
      header = lines[0]
540 541
      status = "\xaa\xac\xbaA".force_encoding('Big5')
      assert header.include?(status)
542
      issue_line = lines.find {|l| l =~ /^#{issue.id},/}
543
      assert issue_line.include?(str_big5)
544 545 546
    end
  end

547 548
  def test_index_csv_cannot_convert_should_be_replaced_big_5
    with_settings :default_language => "zh-TW" do
jplang's avatar
jplang committed
549
      str_utf8  = "\xe4\xbb\xa5\xe5\x86\x85".force_encoding('UTF-8')
jplang's avatar
jplang committed
550
      issue = Issue.generate!(:subject => str_utf8)
551 552 553 554

      get :index, :project_id => 1, 
                  :f => ['subject'], 
                  :op => '=', :values => [str_utf8],
555 556 557
                  :c => ['status', 'subject'],
                  :format => 'csv',
                  :set_filter => 1
jplang's avatar
jplang committed
558
      assert_equal 'text/csv; header=present', @response.content_type
559
      lines = @response.body.chomp.split("\n")
560 561
      header = lines[0]
      issue_line = lines.find {|l| l =~ /^#{issue.id},/}
jplang's avatar
jplang committed
562
      s1 = "\xaa\xac\xbaA".force_encoding('Big5') # status
563 564
      assert header.include?(s1)
      s2 = issue_line.split(",")[2]
jplang's avatar
jplang committed
565 566
      s3 = "\xa5H?".force_encoding('Big5') # subject
      assert_equal s3, s2
567 568 569
    end
  end

570 571 572
  def test_index_csv_tw
    with_settings :default_language => "zh-TW" do
      str1  = "test_index_csv_tw"
jplang's avatar
jplang committed
573
      issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
574 575 576 577 578 579 580

      get :index, :project_id => 1, 
                  :f => ['subject'], 
                  :op => '=', :values => [str1],
                  :c => ['estimated_hours', 'subject'],
                  :format => 'csv',
                  :set_filter => 1
jplang's avatar
jplang committed
581
      assert_equal 'text/csv; header=present', @response.content_type
582
      lines = @response.body.chomp.split("\n")
583
      assert_include "#{issue.id},1234.50,#{str1}", lines
584 585 586 587 588 589
    end
  end

  def test_index_csv_fr
    with_settings :default_language => "fr" do
      str1  = "test_index_csv_fr"
jplang's avatar
jplang committed
590
      issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
591 592 593 594 595 596 597

      get :index, :project_id => 1, 
                  :f => ['subject'], 
                  :op => '=', :values => [str1],
                  :c => ['estimated_hours', 'subject'],
                  :format => 'csv',
                  :set_filter => 1
jplang's avatar
jplang committed
598
      assert_equal 'text/csv; header=present', @response.content_type
599
      lines = @response.body.chomp.split("\n")
600
      assert_include "#{issue.id};1234,50;#{str1}", lines
601 602 603
    end
  end

604
  def test_index_pdf
605 606
    ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
      with_settings :default_language => lang do
607

608 609 610
        get :index
        assert_response :success
        assert_template 'index'
611

612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
        get :index, :format => 'pdf'
        assert_response :success
        assert_not_nil assigns(:issues)
        assert_equal 'application/pdf', @response.content_type

        get :index, :project_id => 1, :format => 'pdf'
        assert_response :success
        assert_not_nil assigns(:issues)
        assert_equal 'application/pdf', @response.content_type

        get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
        assert_response :success
        assert_not_nil assigns(:issues)
        assert_equal 'application/pdf', @response.content_type
      end
    end
628
  end
629

630 631 632 633 634 635 636
  def test_index_pdf_with_query_grouped_by_list_custom_field
    get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
    assert_response :success
    assert_not_nil assigns(:issues)
    assert_not_nil assigns(:issue_count_by_group)
    assert_equal 'application/pdf', @response.content_type
  end
637

638 639 640 641
  def test_index_atom
    get :index, :project_id => 'ecookbook', :format => 'atom'
    assert_response :success
    assert_template 'common/feed'
642
    assert_equal 'application/atom+xml', response.content_type
643

644 645 646 647 648
    assert_select 'feed' do
      assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
      assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
      assert_select 'entry link[href=?]', 'http://test.host/issues/1'
    end
649 650
  end

651
  def test_index_sort
jplang's avatar
jplang committed
652
    get :index, :sort => 'tracker,id:desc'
653
    assert_response :success
654

jplang's avatar
jplang committed
655 656 657
    sort_params = @request.session['issues_index_sort']
    assert sort_params.is_a?(String)
    assert_equal 'tracker,id:desc', sort_params
658

jplang's avatar
jplang committed
659 660 661 662
    issues = assigns(:issues)
    assert_not_nil issues
    assert !issues.empty?
    assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
663
    assert_select 'table.issues.sort-by-tracker.sort-asc'
664
  end
665 666 667 668 669 670 671 672 673 674 675

  def test_index_sort_by_field_not_included_in_columns
    Setting.issue_list_default_columns = %w(subject author)
    get :index, :sort => 'tracker'
  end
  
  def test_index_sort_by_assigned_to
    get :index, :sort => 'assigned_to'
    assert_response :success
    assignees = assigns(:issues).collect(&:assigned_to).compact
    assert_equal assignees.sort, assignees
676
    assert_select 'table.issues.sort-by-assigned-to.sort-asc'
677 678 679 680 681 682 683
  end
  
  def test_index_sort_by_assigned_to_desc
    get :index, :sort => 'assigned_to:desc'
    assert_response :success
    assignees = assigns(:issues).collect(&:assigned_to).compact
    assert_equal assignees.sort.reverse, assignees
684
    assert_select 'table.issues.sort-by-assigned-to.sort-desc'
685 686 687 688 689 690
  end
  
  def test_index_group_by_assigned_to
    get :index, :group_by => 'assigned_to', :sort => 'priority'
    assert_response :success
  end
691 692 693 694
  
  def test_index_sort_by_author
    get :index, :sort => 'author'
    assert_response :success
695 696 697 698 699 700 701 702 703
    authors = assigns(:issues).collect(&:author)
    assert_equal authors.sort, authors
  end
  
  def test_index_sort_by_author_desc
    get :index, :sort => 'author:desc'
    assert_response :success
    authors = assigns(:issues).collect(&:author)
    assert_equal authors.sort.reverse, authors
704 705 706 707 708 709
  end
  
  def test_index_group_by_author
    get :index, :group_by => 'author', :sort => 'priority'
    assert_response :success
  end
710
  
jplang's avatar
jplang committed
711 712
  def test_index_sort_by_spent_hours
    get :index, :sort => 'spent_hours:desc'
713 714 715 716
    assert_response :success
    hours = assigns(:issues).collect(&:spent_hours)
    assert_equal hours.sort.reverse, hours
  end
717 718 719 720 721 722 723
  
  def test_index_sort_by_total_spent_hours
    get :index, :sort => 'total_spent_hours:desc'
    assert_response :success
    hours = assigns(:issues).collect(&:total_spent_hours)
    assert_equal hours.sort.reverse, hours
  end
724 725 726 727 728 729 730
  
  def test_index_sort_by_total_estimated_hours
    get :index, :sort => 'total_estimated_hours:desc'
    assert_response :success
    hours = assigns(:issues).collect(&:total_estimated_hours)
    assert_equal hours.sort.reverse, hours
  end
731

732 733 734 735 736 737 738 739 740 741 742 743 744
  def test_index_sort_by_user_custom_field
    cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')

    get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
    assert_response :success

    assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
  end

745 746
  def test_index_with_columns
    columns = ['tracker', 'subject', 'assigned_to']
747
    get :index, :set_filter => 1, :c => columns
748
    assert_response :success
749

750 751
    # query should use specified columns
    query = assigns(:query)
752
    assert_kind_of IssueQuery, query
753
    assert_equal columns, query.column_names.map(&:to_s)
754

755 756 757 758
    # columns should be stored in session
    assert_kind_of Hash, session[:query]
    assert_kind_of Array, session[:query][:column_names]
    assert_equal columns, session[:query][:column_names].map(&:to_s)
759 760

    # ensure only these columns are kept in the selected columns list
761 762 763 764 765
    assert_select 'select#selected_columns option' do
      assert_select 'option', 3
      assert_select 'option[value=tracker]'
      assert_select 'option[value=project]', 0
    end
766
  end
767

768 769 770 771 772 773
  def test_index_without_project_should_implicitly_add_project_column_to_default_columns
    Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
    get :index, :set_filter => 1

    # query should use specified columns
    query = assigns(:query)
774
    assert_kind_of IssueQuery, query
775
    assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
776 777 778 779
  end

  def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
    Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
780
    columns = ['id', 'tracker', 'subject', 'assigned_to']
781 782 783 784
    get :index, :set_filter => 1, :c => columns

    # query should use specified columns
    query = assigns(:query)
785
    assert_kind_of IssueQuery, query
786 787 788
    assert_equal columns.map(&:to_sym), query.columns.map(&:name)
  end

789 790 791 792 793 794 795 796 797 798
  def test_index_with_default_columns_should_respect_default_columns_order
    columns = ['assigned_to', 'subject', 'status', 'tracker']
    with_settings :issue_list_default_columns => columns do
      get :index, :project_id => 1, :set_filter => 1

      query = assigns(:query)
      assert_equal (['id'] + columns).map(&:to_sym), query.columns.map(&:name)
    end
  end

799 800 801 802
  def test_index_with_custom_field_column
    columns = %w(tracker subject cf_2)
    get :index, :set_filter => 1, :c => columns
    assert_response :success
803

804 805
    # query should use specified columns
    query = assigns(:query)
806
    assert_kind_of IssueQuery, query
807
    assert_equal columns, query.column_names.map(&:to_s)
808

809
    assert_select 'table.issues td.cf_2.string'
810
  end
811

812 813 814 815 816 817 818 819 820 821
  def test_index_with_multi_custom_field_column
    field = CustomField.find(1)
    field.update_attribute :multiple, true
    issue = Issue.find(1)
    issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
    issue.save!

    get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
    assert_response :success

822
    assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
823 824 825 826 827 828 829 830 831 832 833 834
  end

  def test_index_with_multi_user_custom_field_column
    field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
      :tracker_ids => [1], :is_for_all => true)
    issue = Issue.find(1)
    issue.custom_field_values = {field.id => ['2', '3']}
    issue.save!

    get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
    assert_response :success

835 836 837 838 839
    assert_select "table.issues td.cf_#{field.id}" do
      assert_select 'a', 2
      assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
      assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
    end
840 841
  end

842 843
  def test_index_with_date_column
    with_settings :date_format => '%d/%m/%Y' do
844
      Issue.find(1).update_attribute :start_date, '1987-08-24'
845
      get :index, :set_filter => 1, :c => %w(start_date)
846
      assert_select "table.issues td.start_date", :text => '24/08/1987'
847 848 849
    end
  end

850
  def test_index_with_done_ratio_column
851 852
    Issue.find(1).update_attribute :done_ratio, 40
    get :index, :set_filter => 1, :c => %w(done_ratio)
853 854 855 856 857
    assert_select 'table.issues td.done_ratio' do
      assert_select 'table.progress' do
        assert_select 'td.closed[style=?]', 'width: 40%;'
      end
    end
858 859
  end

860
  def test_index_with_spent_hours_column
jplang's avatar
jplang committed
861
    Issue.expects(:load_visible_spent_hours).once
862
    get :index, :set_filter => 1, :c => %w(subject spent_hours)
863
    assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
864 865
  end

866
  def test_index_with_total_spent_hours_column
jplang's avatar
jplang committed
867
    Issue.expects(:load_visible_total_spent_hours).once
868 869 870 871
    get :index, :set_filter => 1, :c => %w(subject total_spent_hours)
    assert_select 'table.issues tr#issue-3 td.total_spent_hours', :text => '1.00'
  end

872 873 874 875 876
  def test_index_with_total_estimated_hours_column
    get :index, :set_filter => 1, :c => %w(subject total_estimated_hours)
    assert_select 'table.issues td.total_estimated_hours'
  end

877 878 879
  def test_index_should_not_show_spent_hours_column_without_permission
    Role.anonymous.remove_permission! :view_time_entries
    get :index, :set_filter => 1, :c => %w(subject spent_hours)
880
    assert_select 'td.spent_hours', 0
881 882
  end

883
  def test_index_with_fixed_version_column
884
    get :index, :set_filter => 1, :c => %w(fixed_version)
885
    assert_select 'table.issues td.fixed_version' do
886
      assert_select 'a[href=?]', '/versions/2', :text => 'eCookbook - 1.0'
887
    end
888 889
  end