diff --git a/.changeset/brave-horses-pump.md b/.changeset/brave-horses-pump.md new file mode 100644 index 0000000000..f19f9422b8 --- /dev/null +++ b/.changeset/brave-horses-pump.md @@ -0,0 +1,5 @@ +--- +'@openproject/primer-view-components': patch +--- + +Initiate form state for TreeView diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark.png index 84c81554da..8bb2e74413 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark.png differ diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_colorblind.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_colorblind.png index 84c81554da..8bb2e74413 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_colorblind.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_colorblind.png differ diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_dimmed.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_dimmed.png index ca42e2331f..bcbadd2fc5 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_dimmed.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_dimmed.png differ diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_high_contrast.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_high_contrast.png index 4a82338602..cad7780e36 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_high_contrast.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/dark_high_contrast.png differ diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light.png index e3d671b847..2042591b10 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light.png differ diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_colorblind.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_colorblind.png index e3d671b847..2042591b10 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_colorblind.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_colorblind.png differ diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_high_contrast.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_high_contrast.png index de581df68d..b6be4694f0 100644 Binary files a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_high_contrast.png and b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/action_menu/multiple_selected_items/light_high_contrast.png differ diff --git a/app/components/primer/alpha/tree_view/tree_view.ts b/app/components/primer/alpha/tree_view/tree_view.ts index b2072e3064..056e22dbef 100644 --- a/app/components/primer/alpha/tree_view/tree_view.ts +++ b/app/components/primer/alpha/tree_view/tree_view.ts @@ -45,26 +45,7 @@ export class TreeViewElement extends HTMLElement { if (!somethingChanged) return - const newInputs = [] - - // eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback - for (const node of this.querySelectorAll('[role=treeitem][aria-checked=true]')) { - const newInput = this.formInputPrototype.cloneNode() as HTMLInputElement - newInput.removeAttribute('data-target') - newInput.removeAttribute('form') - - const payload: {path: string[]; value?: string} = { - path: this.getNodePath(node), - } - - const inputValue = this.getFormInputValueForNode(node) - if (inputValue) payload.value = inputValue - - newInput.value = JSON.stringify(payload) - newInputs.push(newInput) - } - - this.formInputContainer.replaceChildren(...newInputs) + this.updateHiddenFormInputs() }) updateInputsObserver.observe(this, { @@ -73,6 +54,11 @@ export class TreeViewElement extends HTMLElement { attributeFilter: ['aria-checked'], }) + // Correctly initialize the form + if (this.formInputContainer) { + this.updateHiddenFormInputs() + } + // eslint-disable-next-line github/no-then -- We don't want to wait for this to resolve, just get on with it customElements.whenDefined('tree-view-sub-tree-node').then(() => { // depends on TreeViewSubTreeNodeElement#eachAncestorSubTreeNode, which may not be defined yet @@ -483,6 +469,29 @@ export class TreeViewElement extends HTMLElement { selectVariant(node: Element): SelectVariant { return (node.getAttribute('data-select-variant') || 'none') as SelectVariant } + + updateHiddenFormInputs() { + const newInputs = [] + + // eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback + for (const node of this.querySelectorAll('[role=treeitem][aria-checked=true]')) { + const newInput = this.formInputPrototype.cloneNode() as HTMLInputElement + newInput.removeAttribute('data-target') + newInput.removeAttribute('form') + + const payload: {path: string[]; value?: string} = { + path: this.getNodePath(node), + } + + const inputValue = this.getFormInputValueForNode(node) + if (inputValue) payload.value = inputValue + + newInput.value = JSON.stringify(payload) + newInputs.push(newInput) + } + + this.formInputContainer.replaceChildren(...newInputs) + } } if (!window.customElements.get('tree-view')) { diff --git a/previews/primer/alpha/tree_view_preview/form_input.html.erb b/previews/primer/alpha/tree_view_preview/form_input.html.erb index 805269649e..37fcb6124c 100644 --- a/previews/primer/alpha/tree_view_preview/form_input.html.erb +++ b/previews/primer/alpha/tree_view_preview/form_input.html.erb @@ -2,7 +2,7 @@ <%= render(Primer::Alpha::Stack.new) do %> <%= render(Primer::Alpha::TreeView.new(form_arguments: { builder: f, name: "folder_structure" })) do |tree| %> <% tree.with_sub_tree(label: "src", expanded: expanded, select_variant: select_variant, value: 0) do |sub_tree| %> - <% sub_tree.with_leaf(label: "button.rb", select_variant: select_variant, value: 1) %> + <% sub_tree.with_leaf(label: "button.rb", select_variant: select_variant, value: 1, checked: true) %> <% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: select_variant, value: 2) %> <% end %> diff --git a/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb b/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb index fb32c4c087..0ebfbe1ba5 100644 --- a/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb +++ b/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb @@ -7,7 +7,7 @@ <% end %> <% hogwarts.with_sub_tree(label: "Slytherin", select_variant: select_variant, expanded: expanded) do |hufflepuff| %> - <% hufflepuff.with_leaf(label: "Draco Malfoy", select_variant: select_variant) %> + <% hufflepuff.with_leaf(label: "Draco Malfoy", select_variant: select_variant, checked: true) %> <% end %> <% hogwarts.with_sub_tree(label: "Hufflepuff", select_variant: select_variant, expanded: expanded) do |hufflepuff| %> diff --git a/test/system/alpha/tree_view_test.rb b/test/system/alpha/tree_view_test.rb index 373eae24fc..924719477f 100644 --- a/test/system/alpha/tree_view_test.rb +++ b/test/system/alpha/tree_view_test.rb @@ -834,6 +834,7 @@ def test_self_select_strategy_checking_sub_tree_does_not_check_children def test_form_submission visit_preview(:form_input, expanded: true, route_format: :json) + assert_path_checked "src", "button.rb" check_at_path("action_menu.rb") find("button[type=submit]").click @@ -841,12 +842,27 @@ def test_form_submission # for some reason the JSON response is wrapped in HTML, I have no idea why response = JSON.parse(find("pre").text) - assert_equal "{\"path\":[\"action_menu.rb\"],\"value\":\"3\"}", response.dig("form_params", "folder_structure", 0) + assert_equal "{\"path\":[\"src\",\"button.rb\"],\"value\":\"1\"}", response.dig("form_params", "folder_structure", 0) + assert_equal "{\"path\":[\"action_menu.rb\"],\"value\":\"3\"}", response.dig("form_params", "folder_structure", 1) + end + + def test_initial_form_state + visit_preview(:form_input, expanded: true, route_format: :json) + + assert_path_checked "src", "button.rb" + + find("button[type=submit]").click + + # for some reason the JSON response is wrapped in HTML, I have no idea why + response = JSON.parse(find("pre").text) + + assert_equal "{\"path\":[\"src\",\"button.rb\"],\"value\":\"1\"}", response.dig("form_params", "folder_structure", 0) end def test_form_submission_with_single_select_variant visit_preview(:form_input, expanded: true, select_variant: :single, route_format: :json) + assert_path_checked "src", "button.rb" activate_at_path("action_menu.rb") find("button[type=submit]").click @@ -856,5 +872,18 @@ def test_form_submission_with_single_select_variant assert_equal "{\"path\":[\"action_menu.rb\"],\"value\":\"3\"}", response.dig("form_params", "folder_structure", 0) end + + def test_initial_form_state_for_single_select + visit_preview(:form_input, expanded: true, route_format: :json) + + assert_path_checked "src", "button.rb" + + find("button[type=submit]").click + + # for some reason the JSON response is wrapped in HTML, I have no idea why + response = JSON.parse(find("pre").text) + + assert_equal "{\"path\":[\"src\",\"button.rb\"],\"value\":\"1\"}", response.dig("form_params", "folder_structure", 0) + end end end diff --git a/test/system/open_project/filterable_tree_view_test.rb b/test/system/open_project/filterable_tree_view_test.rb index 5321d85fb7..59245ce691 100644 --- a/test/system/open_project/filterable_tree_view_test.rb +++ b/test/system/open_project/filterable_tree_view_test.rb @@ -220,6 +220,7 @@ def test_automatically_expands_sub_trees_to_show_matching_items def test_form_submits_checked_nodes_for_single_select_variant visit_preview(:form_input, select_variant: :single) + assert_path_checked("Students", "Slytherin", "Draco Malfoy") activate_at_path("Students", "Gryffindor", "Harry Potter") activate_at_path("Students", "Ravenclaw", "Luna Lovegood") click_on "Submit" @@ -234,28 +235,63 @@ def test_form_submits_checked_nodes_for_single_select_variant assert_equal character["path"], ["Students", "Ravenclaw", "Luna Lovegood"] end + def test_initial_form_state_for_single_select + visit_preview(:form_input, select_variant: :single) + + assert_path_checked("Students", "Slytherin", "Draco Malfoy") + + find("button[type=submit]").click + + response = JSON.parse(find("pre").text) + assert character_list = response.dig("form_params", "characters") + assert_equal 1, character_list.size + + character = JSON.parse(character_list[0]) + assert_equal character["path"], ["Students", "Slytherin", "Draco Malfoy"] + end + def test_form_submits_checked_nodes visit_preview(:form_input) + assert_path_checked("Students", "Slytherin", "Draco Malfoy") check_at_path("Students", "Gryffindor", "Harry Potter") check_at_path("Students", "Ravenclaw", "Luna Lovegood") click_on "Submit" response = JSON.parse(find("pre").text) assert character_list = response.dig("form_params", "characters") - assert_equal 2, character_list.size + assert_equal 3, character_list.size character = JSON.parse(character_list[0]) assert_equal character["path"], ["Students", "Ravenclaw", "Luna Lovegood"] - character = JSON.parse(character_list[1]) + assert_equal character["path"], ["Students", "Slytherin", "Draco Malfoy"] + + character = JSON.parse(character_list[2]) assert_equal character["path"], ["Students", "Gryffindor", "Harry Potter"] end + def test_initial_form_state_for_multi_select + visit_preview(:form_input) + + assert_path_checked("Students", "Slytherin", "Draco Malfoy") + + find("button[type=submit]").click + + response = JSON.parse(find("pre").text) + assert character_list = response.dig("form_params", "characters") + assert_equal 1, character_list.size + + character = JSON.parse(character_list[0]) + assert_equal character["path"], ["Students", "Slytherin", "Draco Malfoy"] + end + def test_form_submits_checked_nodes_when_sub_items_included_checked visit_preview(:form_input) + assert_path_checked("Students", "Slytherin", "Draco Malfoy") + check "Include sub-items" assert_checked_field "Include sub-items" @@ -265,18 +301,23 @@ def test_form_submits_checked_nodes_when_sub_items_included_checked response = JSON.parse(find("pre").text) assert character_list = response.dig("form_params", "characters") - assert_equal 2, character_list.size + assert_equal 3, character_list.size character = JSON.parse(character_list[0]) assert_equal character["path"], ["Students", "Ravenclaw"] character = JSON.parse(character_list[1]) assert_equal character["path"], ["Students", "Ravenclaw", "Luna Lovegood"] + + character = JSON.parse(character_list[2]) + assert_equal character["path"], ["Students", "Slytherin", "Draco Malfoy"] end def test_form_submits_checked_nodes_when_items_filtered_out visit_preview(:form_input) + assert_path_checked("Students", "Slytherin", "Draco Malfoy") + check_at_path("Students", "Ravenclaw") fill_in "Filter", with: "Harry" @@ -287,18 +328,22 @@ def test_form_submits_checked_nodes_when_items_filtered_out response = JSON.parse(find("pre").text) assert character_list = response.dig("form_params", "characters") - assert_equal 2, character_list.size + assert_equal 3, character_list.size character = JSON.parse(character_list[0]) assert_equal character["path"], ["Students", "Ravenclaw"] character = JSON.parse(character_list[1]) + assert_equal character["path"], ["Students", "Slytherin", "Draco Malfoy"] + + character = JSON.parse(character_list[2]) assert_equal character["path"], ["Students", "Gryffindor", "Harry Potter"] end def test_form_submits_checked_nodes_when_filtering_for_selected_only visit_preview(:form_input) + assert_path_checked("Students", "Slytherin", "Draco Malfoy") check_at_path("Students", "Ravenclaw") check_at_path("Students", "Ravenclaw", "Luna Lovegood") @@ -308,6 +353,7 @@ def test_form_submits_checked_nodes_when_filtering_for_selected_only uncheck_at_path("Students", "Ravenclaw", "Luna Lovegood") + assert_path_checked("Students", "Slytherin", "Draco Malfoy") assert_path_checked("Students", "Ravenclaw") refute_path_checked("Students", "Ravenclaw", "Luna Lovegood") @@ -315,10 +361,13 @@ def test_form_submits_checked_nodes_when_filtering_for_selected_only response = JSON.parse(find("pre").text) assert character_list = response.dig("form_params", "characters") - assert_equal 1, character_list.size + assert_equal 2, character_list.size character = JSON.parse(character_list[0]) assert_equal character["path"], ["Students", "Ravenclaw"] + + character = JSON.parse(character_list[1]) + assert_equal character["path"], ["Students", "Slytherin", "Draco Malfoy"] end end end