From d7243ed22754cb4351ddd0962087d1cff9004992 Mon Sep 17 00:00:00 2001 From: Katsuya HIDAKA Date: Fri, 17 Oct 2025 01:17:21 +0900 Subject: Add functional and system tests for current Gantt behaviour --- test/functional/gantts_controller_test.rb | 171 ++++++++++++++++++++++ test/system/gantt_test.rb | 113 ++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 test/system/gantt_test.rb diff --git a/test/functional/gantts_controller_test.rb b/test/functional/gantts_controller_test.rb index 73a74ba65..2fe83bdd5 100644 --- a/test/functional/gantts_controller_test.rb +++ b/test/functional/gantts_controller_test.rb @@ -215,4 +215,175 @@ class GanttsControllerTest < Redmine::ControllerTest assert_select 'div.gantt_hdr>a', :text => /^[\d-]+$/, :count => 6 end end + + test 'renders project tree with child issues and bars' do + prepare_stable_gantt_data + + @request.session[:user_id] = 2 + + project = projects(:projects_001) + + get(:show, params: { project_id: project.id }) + assert_response :success + + # eCookbook + assert_subject_row('div.project-name', row: '0', text: project.name) + assert_chart_row('div.task.project.task_todo', row: '0', style_substring: 'left:0px;width:138px') + + assert_issue_row(3, 'Bug #3', row: '1') + assert_chart_row('div.task.leaf.task_todo', row: '1', style_substring: 'left:0px;width:38px') + + assert_issue_row(7, 'Bug #7', row: '2') + assert_chart_row('div.task.leaf.task_todo', row: '2', style_substring: 'left:16px;width:42px') + + assert_issue_row(1, 'Bug #1', row: '3') + assert_chart_row('div.task.leaf.task_todo', row: '3', style_substring: 'left:52px;width:46px') + + # Version 1.0 + assert_subject_row('div#version-2', row: '4', text: '1.0') + assert_chart_row('div.task.version', row: '4', style_substring: 'left:48px;width:90px') + + assert_issue_row(2, 'Feature request #2', row: '5') + assert_chart_row('div.task.leaf.task_todo', row: '5', style_substring: 'left:48px;width:90px') + + # Private child of eCookbook + assert_subject_row( + 'div.project-name[data-collapse-expand*="project-5"]', + row: '6', + text: projects(:projects_005).name + ) + assert_chart_row('div.task.project.task_todo', row: '6', style_substring: 'left:56px;width:6px') + + assert_issue_row(6, 'Bug #6', row: '7') + assert_chart_row('div.task.leaf.task_todo', row: '7', style_substring: 'left:56px;width:6px') + + assert_issue_row(9, 'Bug #9', row: '8') + assert_chart_row('div.task.leaf.task_todo', row: '8', style_substring: 'left:56px;width:6px') + + assert_issue_row(10, 'Bug #10', row: '9') + assert_chart_row('div.task.leaf.task_todo', row: '9', style_substring: 'left:56px;width:6px') + assert_select 'div.task[id=?][data-rels*=9]', 'task-todo-issue-10' + + # eCookbook Subproject1 + assert_subject_row( + 'div.project-name[data-collapse-expand*="project-3"]', + row: '10', + text: projects(:projects_003).name + ) + assert_issue_row(5, 'Bug #5', row: '11') + assert_issue_row(13, 'Bug #13', row: '12') + assert_issue_row(14, 'Bug #14', row: '13') + end + + test 'renders chart with selected start month and year' do + prepare_stable_gantt_data + + @request.session[:user_id] = 2 + + project = projects(:projects_005) + + selected_start = User.current.today.prev_month.beginning_of_month + get( + :show, + params: { + project_id: project.id, + month: selected_start.month, + year: selected_start.year + } + ) + assert_response :success + + assert_select 'select#month option[selected=selected][value=?]', selected_start.month.to_s + assert_select 'select#year option[selected=selected][value=?]', selected_start.year.to_s + + 6.times do |offset| + m = selected_start.since(offset.month) + assert_select 'div.gantt_hdr > a', text: "#{m.year}-#{m.month}" + end + + # eCookbook + assert_subject_row('div.project-name', row: '0', text: projects(:projects_001).name) + assert_chart_row('div.task.project.task_todo', row: '0', style_substring: 'left:0px;width:258px') + + # Private child of eCookbook + assert_subject_row( + 'div.project-name[data-collapse-expand*="project-5"]', + row: '1', + text: project.name + ) + assert_chart_row('div.task.project.task_todo', row: '1', style_substring: 'left:176px;width:6px') + + # Bug #6 + assert_issue_row(6, 'Bug #6', row: '2') + assert_chart_row('div.task.leaf.task_todo', row: '2', style_substring: 'left:176px;width:6px') + + # Bug #9 + assert_issue_row(9, 'Bug #9', row: '3') + assert_chart_row('div.task.leaf.task_todo', row: '3', style_substring: 'left:176px;width:6px') + + # Bug #10 + assert_issue_row(10, 'Bug #10', row: '4') + assert_chart_row('div.task.leaf.task_todo', row: '4', style_substring: 'left:176px;width:6px') + + assert_select 'div.task[id=?][data-rels*=9]', 'task-todo-issue-10' + end + + test 'shows six months starting from current month' do + prepare_stable_gantt_data + + @request.session[:user_id] = 2 + + project = projects(:projects_001) + + get :show, params: { project_id: project.id } + assert_response :success + + start_of_month = User.current.today.beginning_of_month + 6.times do |offset| + m = start_of_month.since(offset.months) + + assert_select 'div.gantt_hdr > a', text: "#{m.year}-#{m.month}" + end + + assert_select 'input#months[value=?]', '6' + assert_select 'select#month option[selected=selected][value=?]', User.current.today.month.to_s + assert_select 'select#year option[selected=selected][value=?]', User.current.today.year.to_s + assert_select 'input#zoom[value=?]', '2' + end + + private + + def assert_subject_row(selector, row:, text:) + assert_select "div.gantt_subjects form #{selector}[data-number-of-rows=?]", row do + assert_select 'a', text: text + end + end + + def assert_issue_row(issue_id, link_text, row:) + selector = "div.gantt_subjects form div#issue-#{issue_id}[data-number-of-rows=\"#{row}\"]" + assert_select selector do + assert_select 'a.issue', text: link_text + end + end + + def assert_chart_row(selector, row:, style_substring:) + matcher = "#gantt_area #{selector}[data-number-of-rows=?][style*=?]" + assert_select matcher, row, style_substring, minimum: 1 + end + + # Freezes today and resets the start and due dates of issues and versions in the eCookbook project and its descendants to fixed values + # so the Gantt layout uses deterministic dates, bar positions stay stable across runs, and the tests remain easy to execute. + def prepare_stable_gantt_data + issues(:issues_003).update!(start_date: Date.new(2025, 9, 30), due_date: Date.new(2025, 10, 10)) + issues(:issues_007).update!(start_date: Date.new(2025, 10, 5), due_date: Date.new(2025, 10, 15)) + issues(:issues_001).update!(start_date: Date.new(2025, 10, 14), due_date: Date.new(2025, 10, 25)) + issues(:issues_002).update!(start_date: Date.new(2025, 10, 13), due_date: nil) + issues(:issues_006).update!(start_date: Date.new(2025, 10, 15), due_date: Date.new(2025, 10, 16)) + issues(:issues_009).update!(start_date: Date.new(2025, 10, 15), due_date: Date.new(2025, 10, 16)) + issues(:issues_010).update!(start_date: Date.new(2025, 10, 15), due_date: Date.new(2025, 10, 16)) + + Version.find(2).update!(effective_date: Date.new(2025, 11, 4)) + + travel_to Date.new(2025, 10, 15) + end end diff --git a/test/system/gantt_test.rb b/test/system/gantt_test.rb new file mode 100644 index 000000000..0f897f65a --- /dev/null +++ b/test/system/gantt_test.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require_relative '../application_system_test_case' + +class GanttSystemTest < ApplicationSystemTestCase + setup do + log_user('jsmith', 'jsmith') + end + + test 'columns display toggle shows status priority assignee updated' do + visit_gantt + expand_options + + assert_no_selector('td#status', visible: :visible) + assert_no_selector('td#priority', visible: :visible) + assert_no_selector('td#assigned_to', visible: :visible) + assert_no_selector('td#updated_on', visible: :visible) + + find('#draw_selected_columns').check + + assert_selector('div.gantt_subjects_container.draw_selected_columns') + assert_selector('td#status', visible: :visible) + assert_selector('td#priority', visible: :visible) + assert_selector('td#assigned_to', visible: :visible) + assert_selector('td#updated_on', visible: :visible) + end + + test 'related issues toggle displays and hides relation arrows' do + visit_gantt + expand_options + + assert_selector('#gantt_draw_area path', minimum: 1) + + find('#draw_relations').uncheck + + assert_no_selector('#gantt_draw_area path') + + find('#draw_relations').check + + assert_selector('#gantt_draw_area path', minimum: 1) + end + + test 'progress line toggle draws zigzag line' do + visit_gantt + expand_options + + find('#draw_relations').uncheck + assert_no_selector('#gantt_draw_area path') + + find('#draw_progress_line').check + + assert_selector('#gantt_draw_area path', minimum: 1) + end + + test 'selected columns can be resized by dragging' do + visit_gantt + expand_options + + find('#draw_selected_columns').check + + width_before = column_width('status') + drag_column_resizer('status', 80) + width_after = column_width('status') + + assert width_after > width_before + end + + test 'context menu and tooltip interactions' do + visit_gantt + + issue_subject = find('div.issue-subject.hascontextmenu', match: :first) + issue_reference = issue_subject.find('a.issue', match: :first).text + task_area = find('div.tooltip.hascontextmenu', match: :first, visible: :all) + + task_area.hover + assert_selector('div.tooltip span.tip', text: issue_reference, visible: :visible) + + issue_subject.right_click + + assert_selector('#context-menu', visible: :visible) + assert_selector('#context-menu a.icon-edit', visible: :visible) + + page.send_keys(:escape) + + task_area = find('div.tooltip.hascontextmenu', match: :first, visible: :all) + task_area.right_click + + assert_selector('#context-menu', visible: :visible) + assert_selector('#context-menu a.icon-edit', visible: :visible) + + page.send_keys(:escape) + end + + private + + def visit_gantt + visit '/projects/ecookbook/issues/gantt' + end + + def expand_options + legend = find('fieldset#options legend') + legend.click if legend[:class].to_s.include?('collapsed') + end + + def column_width(id) + page.evaluate_script("document.querySelector('td##{id}').offsetWidth") + end + + def drag_column_resizer(column_id, distance) + handle = find("td##{column_id} .ui-resizable-e", visible: :visible) + page.driver.browser.action.click_and_hold(handle.native).move_by(distance, 0).release.perform + end +end -- 2.51.0