diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1bdc29d81..3186fc47b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -473,7 +473,7 @@ module ApplicationHelper end classes = (ancestors.empty? ? 'root' : 'child') classes += ' archived' if project.archived? - s << "
  • " + s << "
  • " s << h(block ? capture(project, &block) : project.name) s << "
    \n" ancestors << project diff --git a/app/javascript/controllers/project_selector_controller.js b/app/javascript/controllers/project_selector_controller.js new file mode 100644 index 000000000..2c3158c0a --- /dev/null +++ b/app/javascript/controllers/project_selector_controller.js @@ -0,0 +1,20 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + toggleChildProjects(event) { + const checkbox = event.target; + if (checkbox.type !== 'checkbox') return; + + const listItem = checkbox.closest('li'); + if (!listItem) return; + + const childList = listItem.querySelector('ul.projects'); + if (!childList) return; + + const childCheckboxes = childList.querySelectorAll('input[type="checkbox"]'); + + childCheckboxes.forEach(child => { + child.checked = checkbox.checked; + }); + } +} diff --git a/app/views/custom_fields/_visibility_by_project_selector.html.erb b/app/views/custom_fields/_visibility_by_project_selector.html.erb index 25fd4978b..6f107fe61 100644 --- a/app/views/custom_fields/_visibility_by_project_selector.html.erb +++ b/app/views/custom_fields/_visibility_by_project_selector.html.erb @@ -1,11 +1,11 @@
    <%= toggle_checkboxes_link("#custom_field_project_ids input[type=checkbox]:enabled") %><%= l(:label_project_plural) %>

    <%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %>

    -
    +
    <% project_ids = @custom_field.project_ids.to_a %> <%= render_project_nested_lists(Project.all) do |p| - content_tag('label', check_box_tag('custom_field[project_ids][]', p.id, project_ids.include?(p.id), :id => nil) + ' ' + p.to_s) + content_tag('label', check_box_tag('custom_field[project_ids][]', p.id, project_ids.include?(p.id), :id => nil, :data => {:action => 'change->project-selector#toggleChildProjects'}) + ' ' + p.to_s) end %> <%= hidden_field_tag('custom_field[project_ids][]', '', :id => nil) %>
    -
    + \ No newline at end of file diff --git a/test/system/custom_field_project_selector_test.rb b/test/system/custom_field_project_selector_test.rb new file mode 100644 index 000000000..91180e15f --- /dev/null +++ b/test/system/custom_field_project_selector_test.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require_relative '../application_system_test_case' + +class CustomFieldProjectSelectorTest < ApplicationSystemTestCase + setup do + @parent = Project.generate!(name: 'Parent Project') + @child1 = Project.generate!(name: 'Child Project 1', parent_id: @parent.id) + @child2 = Project.generate!(name: 'Child Project 2', parent_id: @parent.id) + @grandchild = Project.generate!(name: 'Grandchild Project', parent_id: @child1.id) + end + + def test_checking_parent_project_should_check_child_projects + log_user('admin', 'admin') + + custom_field = IssueCustomField.generate!( + field_format: 'string', + name: 'Test Field', + is_for_all: false + ) + + visit "/custom_fields/#{custom_field.id}/edit" + + within '#custom_field_project_ids' do + parent_checkbox = find("input[value='#{@parent.id}']") + parent_checkbox.click + + child1_checkbox = find("input[value='#{@child1.id}']") + child2_checkbox = find("input[value='#{@child2.id}']") + + assert child1_checkbox.checked?, 'Child project 1 should be checked' + assert child2_checkbox.checked?, 'Child project 2 should be checked' + end + end + + def test_unchecking_parent_project_should_uncheck_child_projects + log_user('admin', 'admin') + + custom_field = IssueCustomField.generate!( + field_format: 'string', + name: 'Test Field', + is_for_all: false, + project_ids: [@parent.id, @child1.id, @child2.id] + ) + + visit "/custom_fields/#{custom_field.id}/edit" + + within '#custom_field_project_ids' do + parent_checkbox = find("input[value='#{@parent.id}']") + child1_checkbox = find("input[value='#{@child1.id}']") + child2_checkbox = find("input[value='#{@child2.id}']") + + assert parent_checkbox.checked?, 'Parent should be checked initially' + assert child1_checkbox.checked?, 'Child 1 should be checked initially' + assert child2_checkbox.checked?, 'Child 2 should be checked initially' + + parent_checkbox.click + + assert !child1_checkbox.checked?, 'Child project 1 should be unchecked' + assert !child2_checkbox.checked?, 'Child project 2 should be unchecked' + end + end + + def test_grandchild_projects_are_also_synced + log_user('admin', 'admin') + + custom_field = IssueCustomField.generate!( + field_format: 'string', + name: 'Test Field', + is_for_all: false + ) + + visit "/custom_fields/#{custom_field.id}/edit" + + within '#custom_field_project_ids' do + parent_checkbox = find("input[value='#{@parent.id}']") + parent_checkbox.click + + grandchild_checkbox = find("input[value='#{@grandchild.id}']") + assert grandchild_checkbox.checked?, 'Grandchild project should be checked' + end + end +end \ No newline at end of file