issues_controller_test.rb 199 KB
Newer Older
1
# Redmine - project management software
jplang's avatar
jplang committed
2
# Copyright (C) 2006-2016  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

20
class IssuesControllerTest < Redmine::ControllerTest
21
  fixtures :projects,
22
           :users, :email_addresses, :user_preferences,
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
    with_settings :default_language => "en" do
      get :index
      assert_response :success
58 59

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

70
  def test_index_should_not_list_issues_when_module_disabled
jplang's avatar
jplang committed
71
    EnabledModule.where("name = 'issue_tracking' AND project_id = 1").delete_all
72 73
    get :index
    assert_response :success
74

75 76
    assert_select 'a[href="/issues/1"]', 0
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
77
  end
jplang's avatar
jplang committed
78 79

  def test_index_should_list_visible_issues_only
80 81 82
    get :index, :params => {
        :per_page => 100
      }
jplang's avatar
jplang committed
83
    assert_response :success
84 85 86 87

    Issue.open.each do |issue|
      assert_select "tr#issue-#{issue.id}", issue.visible? ? 1 : 0
    end
jplang's avatar
jplang committed
88
  end
89

90
  def test_index_with_project
91
    Setting.display_subprojects_issues = 0
92 93 94
    get :index, :params => {
        :project_id => 1
      }
95
    assert_response :success
96

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

101 102
  def test_index_with_project_and_subprojects
    Setting.display_subprojects_issues = 1
103 104 105
    get :index, :params => {
        :project_id => 1
      }
106
    assert_response :success
107

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

113
  def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
114 115
    @request.session[:user_id] = 2
    Setting.display_subprojects_issues = 1
116 117 118
    get :index, :params => {
        :project_id => 1
      }
119
    assert_response :success
120

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

126
  def test_index_with_project_and_default_filter
127 128 129 130
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1
      }
131
    assert_response :success
132

133
    # default filter
134
    assert_query_filters [['status_id', 'o', '']]
135
  end
136

137
  def test_index_with_project_and_filter
138 139 140 141 142 143 144 145 146 147 148
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :f => ['tracker_id'],
        :op => {
          'tracker_id' => '='
        },  
        :v => {
          'tracker_id' => ['1']
        }
      }
149
    assert_response :success
150

151
    assert_query_filters [['tracker_id', '=', '1']]
152
  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
        get :index, :params => {
            :set_filter => 1, field => filter_expression
          }
213 214
        assert_response :success

215 216
        expected_with_default = default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}})
        assert_query_filters expected_with_default.map {|f, v| [f, v[:operator], v[:values]]}
217 218 219 220
      end
    end
  end

221
  def test_index_with_project_and_empty_filters
222 223 224 225 226
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :fields => ['']
      }
227
    assert_response :success
228

229
    # no filter
230
    assert_query_filters []
231
  end
232

233 234 235 236 237 238 239
  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

240 241 242 243 244 245 246 247 248 249 250
    get :index, :params => {
        :set_filter => 1,
        :f => [filter_name],
        :op => {
          filter_name => '='
        },  
        :v => {
          filter_name => ['Foo']
        },  
        :c => ['project']
      }
251
    assert_response :success
252 253

    assert_equal [3, 5], issues_in_list.map(&:project_id).uniq.sort
254 255
  end

jplang's avatar
jplang committed
256
  def test_index_with_query
257 258 259 260
    get :index, :params => {
        :project_id => 1,
        :query_id => 5
      }
jplang's avatar
jplang committed
261 262
    assert_response :success
  end
263

264
  def test_index_with_query_grouped_by_tracker
265 266 267 268
    get :index, :params => {
        :project_id => 1,
        :query_id => 6
      }
jplang's avatar
jplang committed
269
    assert_response :success
270
    assert_select 'tr.group span.count'
271
  end
272

273
  def test_index_with_query_grouped_and_sorted_by_category
274 275 276 277 278 279
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :group_by => "category",
        :sort => "category"
      }
280
    assert_response :success
281
    assert_select 'tr.group span.count'
282 283
  end

284
  def test_index_with_query_grouped_and_sorted_by_fixed_version
285 286 287 288 289 290
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :group_by => "fixed_version",
        :sort => "fixed_version"
      }
291
    assert_response :success
292
    assert_select 'tr.group span.count'
293 294 295
  end

  def test_index_with_query_grouped_and_sorted_by_fixed_version_in_reverse_order
296 297 298 299 300 301
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :group_by => "fixed_version",
        :sort => "fixed_version:desc"
      }
302
    assert_response :success
303
    assert_select 'tr.group span.count'
304 305
  end

306
  def test_index_with_query_grouped_by_list_custom_field
307 308 309 310
    get :index, :params => {
        :project_id => 1,
        :query_id => 9
      }
311
    assert_response :success
312
    assert_select 'tr.group span.count'
jplang's avatar
jplang committed
313
  end
314

315 316 317 318 319 320 321 322 323
  def test_index_with_query_grouped_by_key_value_custom_field
    cf = IssueCustomField.create!(:name => 'Key', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'enumeration')
    cf.enumerations << valueb = CustomFieldEnumeration.new(:name => 'Value B', :position => 1)
    cf.enumerations << valuea = CustomFieldEnumeration.new(:name => 'Value A', :position => 2)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => valueb.id)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => valueb.id)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => valuea.id)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')

324 325 326 327 328
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
      :group_by => "cf_#{cf.id}"
      }
329 330 331 332 333 334 335 336 337 338 339 340 341
    assert_response :success

    assert_select 'tr.group', 3
    assert_select 'tr.group' do
      assert_select 'span.name', :text => 'Value B'
      assert_select 'span.count', :text => '2'
    end
    assert_select 'tr.group' do
      assert_select 'span.name', :text => 'Value A'
      assert_select 'span.count', :text => '1'
    end
  end

342 343 344 345 346 347 348
  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 => '')

349 350 351 352 353
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
      :group_by => "cf_#{cf.id}"
      }
354 355 356 357 358
    assert_response :success

    assert_select 'tr.group', 3
    assert_select 'tr.group' do
      assert_select 'a', :text => 'John Smith'
jplang's avatar
jplang committed
359
      assert_select 'span.count', :text => '1'
360 361 362
    end
    assert_select 'tr.group' do
      assert_select 'a', :text => 'Dave Lopper'
jplang's avatar
jplang committed
363
      assert_select 'span.count', :text => '2'
364 365 366
    end
  end

367 368 369 370 371 372 373
  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
374 375 376 377 378
      get :index, :params => {
          :project_id => 1,
          :set_filter => 1,
        :group_by => "cf_#{cf.id}"
        }
379 380 381
      assert_response :success
    end

382
    assert_select 'tr.group', 3
383 384
    assert_select 'tr.group', :text => /Yes/
    assert_select 'tr.group', :text => /No/
jplang's avatar
jplang committed
385
    assert_select 'tr.group', :text => /blank/
386 387
  end

388 389 390 391 392 393
  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
394 395 396 397 398
      get :index, :params => {
          :project_id => 1,
        :set_filter => 1, "cf_#{cf.id}" => "*",
        :group_by => "cf_#{cf.id}"
        }
399 400 401
      assert_response :success
    end

402
    assert_equal [1, 2], issues_in_list.map(&:id).sort
403 404 405 406
    assert_select 'tr.group', 1
    assert_select 'tr.group', :text => /No/
  end

407
  def test_index_with_query_grouped_by_tracker_in_normal_order
408 409
    3.times {|i| Issue.generate!(:tracker_id => (i + 1))}

410 411 412 413 414
    get :index, :params => {
        :set_filter => 1,
        :group_by => 'tracker',
        :sort => 'id:desc'
      }
415 416
    assert_response :success

417 418
    assert_equal ["Bug", "Feature request", "Support request"],
      css_select("tr.issue td.tracker").map(&:text).uniq
419 420 421 422 423
  end

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

424 425 426 427 428 429
    get :index, :params => {
        :set_filter => 1,
        :group_by => 'tracker',
        :c => ['tracker', 'subject'],
        :sort => 'id:desc,tracker:desc'
      }
430 431
    assert_response :success

432 433
    assert_equal ["Bug", "Feature request", "Support request"].reverse,
      css_select("tr.issue td.tracker").map(&:text).uniq
434 435
  end

436
  def test_index_with_query_id_and_project_id_should_set_session_query
437 438 439 440
    get :index, :params => {
        :project_id => 1,
        :query_id => 4
      }
441
    assert_response :success
442 443 444
    assert_kind_of Hash, session[:issue_query]
    assert_equal 4, session[:issue_query][:id]
    assert_equal 1, session[:issue_query][:project_id]
445 446
  end

447
  def test_index_with_invalid_query_id_should_respond_404
448 449 450 451
    get :index, :params => {
        :project_id => 1,
        :query_id => 999
      }
452 453 454
    assert_response 404
  end

455
  def test_index_with_cross_project_query_in_session_should_show_project_issues
456
    q = IssueQuery.create!(:name => "cross_project_query", :user_id => 2, :project => nil, :column_names => ['project'])
457
    @request.session[:issue_query] = {:id => q.id, :project_id => 1}
458 459

    with_settings :display_subprojects_issues => '0' do
460 461 462
      get :index, :params => {
          :project_id => 1
        }
463 464
    end
    assert_response :success
465 466 467

    assert_select 'h2', :text => q.name
    assert_equal ["eCookbook"], css_select("tr.issue td.project").map(&:text).uniq
468 469
  end

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

474 475 476
    get :index, :params => {
        :query_id => q.id
      }
477 478
    assert_response 403
  end
479

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

484 485 486
    get :index, :params => {
        :query_id => q.id
      }
487 488
    assert_response :success
  end
489

490
  def test_public_query_should_be_available_to_other_users
491
    q = IssueQuery.create!(:name => "public", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
492
    @request.session[:user_id] = 3
493

494 495 496
    get :index, :params => {
        :query_id => q.id
      }
497 498
    assert_response :success
  end
499

500
  def test_index_should_omit_page_param_in_export_links
501 502 503
    get :index, :params => {
        :page => 2
      }
504
    assert_response :success
505 506 507
    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
508
    assert_select 'form#csv-export-form[action="/issues.csv"]'
509 510
  end

511 512 513 514 515 516 517 518 519 520 521 522 523 524
  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

525
  def test_index_should_include_query_params_as_hidden_fields_in_csv_export_form
526 527 528 529 530 531 532
    get :index, :params => {
        :project_id => 1,
        :set_filter => "1",
        :tracker_id => "2",
        :sort => 'status',
        :c => ["status", "priority"]
      }
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548

    assert_select '#csv-export-form[action=?]', '/projects/ecookbook/issues.csv'
    assert_select '#csv-export-form[method=?]', 'get'

    assert_select '#csv-export-form' do
      assert_select 'input[name=?][value=?]', 'set_filter', '1'

      assert_select 'input[name=?][value=?]', 'f[]', 'tracker_id'
      assert_select 'input[name=?][value=?]', 'op[tracker_id]', '='
      assert_select 'input[name=?][value=?]', 'v[tracker_id][]', '2'

      assert_select 'input[name=?][value=?]', 'c[]', 'status'
      assert_select 'input[name=?][value=?]', 'c[]', 'priority'

      assert_select 'input[name=?][value=?]', 'sort', 'status'
    end
549

550 551 552 553 554
    get :index, :params => {
        :project_id => 1,
        :set_filter => "1",
        :f => ['']
      }
555
    assert_select '#csv-export-form input[name=?][value=?]', 'f[]', ''
556 557
  end

558
  def test_index_csv
559 560 561
    get :index, :params => {
        :format => 'csv'
      }
562
    assert_response :success
563

jplang's avatar
jplang committed
564
    assert_equal 'text/csv; header=present', @response.content_type
565 566 567 568
    assert response.body.starts_with?("#,")
    lines = response.body.chomp.split("\n")
    # default columns + id and project
    assert_equal Setting.issue_list_default_columns.size + 2, lines[0].split(',').size
569
  end
570

571
  def test_index_csv_with_project
572 573 574 575
    get :index, :params => {
        :project_id => 1,
        :format => 'csv'
      }
576
    assert_response :success
577

jplang's avatar
jplang committed
578
    assert_equal 'text/csv; header=present', @response.content_type
579
  end
580

581 582 583
  def test_index_csv_without_any_filters
    @request.session[:user_id] = 1
    Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5, :subject => 'Closed issue', :author_id => 1)
584 585 586 587 588
    get :index, :params => {
        :set_filter => 1,
        :f => [''],
        :format => 'csv'
      }
589
    assert_response :success
590 591
    # -1 for headers
    assert_equal Issue.count, response.body.chomp.split("\n").size - 1
592 593
  end

594
  def test_index_csv_with_description
595 596 597
    Issue.generate!(:description => 'test_index_csv_with_description')

    with_settings :default_language => 'en' do
598 599 600 601 602
      get :index, :params => {
          :format => 'csv',
          :c => [:tracker,
          :description]
        }
603 604 605 606 607 608 609
      assert_response :success
    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
610 611
  end

612
  def test_index_csv_with_spent_time_column
jplang's avatar
jplang committed
613 614
    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)
615

616 617 618 619 620
    get :index, :params => {
        :format => 'csv',
        :set_filter => '1',
        :c => %w(subject spent_hours)
      }
621
    assert_response :success
jplang's avatar
jplang committed
622
    assert_equal 'text/csv; header=present', @response.content_type
623 624 625 626
    lines = @response.body.chomp.split("\n")
    assert_include "#{issue.id},#{issue.subject},7.33", lines
  end

627
  def test_index_csv_with_all_columns
628 629 630 631
    get :index, :params => {
        :format => 'csv',
        :c => ['all_inline']
      }
632
    assert_response :success
633

jplang's avatar
jplang committed
634
    assert_equal 'text/csv; header=present', @response.content_type
635 636
    assert_match /\A#,/, response.body
    lines = response.body.chomp.split("\n")
637
    assert_equal IssueQuery.new.available_inline_columns.size, lines[0].split(',').size
638 639
  end

jplang's avatar
jplang committed
640 641 642 643 644 645
  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!

646 647 648 649
    get :index, :params => {
        :format => 'csv',
        :c => ['tracker', "cf_1"]
      }
jplang's avatar
jplang committed
650 651 652 653 654
    assert_response :success
    lines = @response.body.chomp.split("\n")
    assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
  end

655 656 657 658 659
  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
660 661 662 663
      get :index, :params => {
          :format => 'csv',
        :c => ['id', 'tracker', "cf_#{field.id}"]
        }
664 665 666 667 668 669
      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
670 671 672 673
      get :index, :params => {
          :format => 'csv',
        :c => ['id', 'tracker', "cf_#{field.id}"]
        }
674 675 676 677 678
      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
679 680 681 682 683 684 685

  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
686 687 688 689
      get :index, :params => {
          :format => 'csv',
          :c => %w(parent)
        }
690
    end
jplang's avatar
Typo.  
jplang committed
691
    lines = response.body.split("\n")
692 693
    assert_include "#{child.id},#{parent.id}", lines
  end
694

695 696
  def test_index_csv_big_5
    with_settings :default_language => "zh-TW" do
jplang's avatar
jplang committed
697 698
      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
699
      issue = Issue.generate!(:subject => str_utf8)
700

701 702 703 704 705
      get :index, :params => {
          :project_id => 1,
          :subject => str_utf8,
          :format => 'csv'
        }
jplang's avatar
jplang committed
706
      assert_equal 'text/csv; header=present', @response.content_type
707
      lines = @response.body.chomp.split("\n")
708
      header = lines[0]
709
      status = "\xaa\xac\xbaA".force_encoding('Big5')
710
      assert_include status, header
711
      issue_line = lines.find {|l| l =~ /^#{issue.id},/}
712
      assert_include str_big5, issue_line
713 714 715
    end
  end

716 717
  def test_index_csv_cannot_convert_should_be_replaced_big_5
    with_settings :default_language => "zh-TW" do
jplang's avatar
jplang committed
718
      str_utf8  = "\xe4\xbb\xa5\xe5\x86\x85".force_encoding('UTF-8')
jplang's avatar
jplang committed
719
      issue = Issue.generate!(:subject => str_utf8)
720

721 722 723 724 725 726 727
      get :index, :params => {
          :project_id => 1,
          :subject => str_utf8,
          :c => ['status', 'subject'],
          :format => 'csv',
          :set_filter => 1
        }
jplang's avatar
jplang committed
728
      assert_equal 'text/csv; header=present', @response.content_type
729
      lines = @response.body.chomp.split("\n")
730 731
      header = lines[0]
      issue_line = lines.find {|l| l =~ /^#{issue.id},/}
jplang's avatar
jplang committed
732
      s1 = "\xaa\xac\xbaA".force_encoding('Big5') # status
733 734
      assert header.include?(s1)
      s2 = issue_line.split(",")[2]
jplang's avatar
jplang committed
735 736
      s3 = "\xa5H?".force_encoding('Big5') # subject
      assert_equal s3, s2
737 738 739
    end
  end

740 741 742
  def test_index_csv_tw
    with_settings :default_language => "zh-TW" do
      str1  = "test_index_csv_tw"
jplang's avatar
jplang committed
743
      issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
744

745 746 747 748 749 750 751
      get :index, :params => {
          :project_id => 1,
          :subject => str1,
          :c => ['estimated_hours', 'subject'],
          :format => 'csv',
          :set_filter => 1
        }
jplang's avatar
jplang committed
752
      assert_equal 'text/csv; header=present', @response.content_type
753
      lines = @response.body.chomp.split("\n")
754
      assert_include "#{issue.id},1234.50,#{str1}", lines
755 756 757 758 759 760
    end
  end

  def test_index_csv_fr
    with_settings :default_language => "fr" do
      str1  = "test_index_csv_fr"
jplang's avatar
jplang committed
761
      issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
762

763 764 765 766 767 768 769
      get :index, :params => {
          :project_id => 1,
          :subject => str1,
          :c => ['estimated_hours', 'subject'],
          :format => 'csv',
          :set_filter => 1
        }
jplang's avatar
jplang committed
770
      assert_equal 'text/csv; header=present', @response.content_type
771
      lines = @response.body.chomp.split("\n")
772
      assert_include "#{issue.id};1234,50;#{str1}", lines
773 774 775
    end
  end

776
  def test_index_pdf
777 778
    ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
      with_settings :default_language => lang do
779

780 781
        get :index
        assert_response :success
782

783 784 785
        get :index, :params => {
            :format => 'pdf'
          }
786 787 788
        assert_response :success
        assert_equal 'application/pdf', @response.content_type

789 790 791 792
        get :index, :params => {
            :project_id => 1,
            :format => 'pdf'
          }
793 794 795
        assert_response :success
        assert_equal 'application/pdf', @response.content_type

796 797 798 799 800
        get :index, :params => {
            :project_id => 1,
            :query_id => 6,
            :format => 'pdf'
          }
801 802 803 804
        assert_response :success
        assert_equal 'application/pdf', @response.content_type
      end
    end
805
  end
806

807
  def test_index_pdf_with_query_grouped_by_list_custom_field
808 809 810 811 812
    get :index, :params => {
        :project_id => 1,
        :query_id => 9,
        :format => 'pdf'
      }
813 814 815
    assert_response :success
    assert_equal 'application/pdf', @response.content_type
  end
816

817
  def test_index_atom
818 819 820 821
    get :index, :params => {
        :project_id => 'ecookbook',
        :format => 'atom'
      }
822
    assert_response :success
823
    assert_equal 'application/atom+xml', response.content_type
824

825 826 827 828 829
    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
830 831
  end

jplang's avatar
jplang committed
832
  def test_index_should_include_back_url_input
833 834 835 836
    get :index, :params => {
        :project_id => 'ecookbook',
        :foo => 'bar'
      }
jplang's avatar
jplang committed
837 838 839 840
    assert_response :success
    assert_select 'input[name=back_url][value=?]', '/projects/ecookbook/issues?foo=bar'
  end

841
  def test_index_sort
842 843 844
    get :index, :params => {
        :sort => 'tracker,id:desc'
      }
845
    assert_response :success
846

847
    assert_equal issues_in_list.sort_by {|issue| [issue.tracker.position, -issue.id]}, issues_in_list
848
    assert_select 'table.issues.sort-by-tracker.sort-asc'
849
  end
850 851

  def test_index_sort_by_field_not_included_in_columns
jplang's avatar
jplang committed