Skip to content

Commit 4d8ec0c

Browse files
authoredAug 1, 2024··
[Alpha][SelectPanel] Fix single select remote loading bug (#2986)
1 parent 3cefb38 commit 4d8ec0c

File tree

6 files changed

+165
-15
lines changed

6 files changed

+165
-15
lines changed
 

‎.changeset/dull-knives-leave.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/view-components': patch
3+
---
4+
5+
Fixes Alpha::SelectPanel remote loading bug where items from the server were overriding the users selections. Additionally, update the #removeSelectedItem function to grab the data-value from the correct element.

‎app/components/primer/alpha/select_panel_element.ts

+17-11
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export class SelectPanelElement extends HTMLElement {
8484
#selectedItems: Map<string, SelectedItem> = new Map()
8585
#loadingDelayTimeoutId: number | null = null
8686
#loadingAnnouncementTimeoutId: number | null = null
87+
#hasLoadedData = false
8788

8889
get open(): boolean {
8990
return this.dialog.open
@@ -399,9 +400,11 @@ export class SelectPanelElement extends HTMLElement {
399400
}
400401
}
401402

402-
#removeSelectedItem(item: Element) {
403-
const value = item.getAttribute('data-value')
403+
#removeSelectedItem(item: SelectPanelItem) {
404+
const itemContent = this.#getItemContent(item)
405+
if (!itemContent) return
404406

407+
const value = itemContent.getAttribute('data-value')
405408
if (value) {
406409
this.#selectedItems.delete(value)
407410
}
@@ -700,8 +703,12 @@ export class SelectPanelElement extends HTMLElement {
700703
if (!itemContent) continue
701704

702705
const value = itemContent.getAttribute('data-value')
703-
704-
if (value && !this.#selectedItems.has(value) && this.isItemChecked(item)) {
706+
if (this.#hasLoadedData) {
707+
if (value && !this.#selectedItems.has(value)) {
708+
itemContent.setAttribute(this.ariaSelectionType, 'false')
709+
}
710+
} else if (value && !this.#selectedItems.has(value) && this.isItemChecked(item)) {
711+
this.#hasLoadedData = true
705712
this.#addSelectedItem(item)
706713
}
707714
}
@@ -863,19 +870,18 @@ export class SelectPanelElement extends HTMLElement {
863870
const itemContent = this.#getItemContent(item)
864871

865872
if (this.selectVariant === 'single') {
873+
const element = this.selectedItems[0]?.element
874+
if (element) {
875+
this.#getItemContent(element)?.setAttribute(this.ariaSelectionType, 'false')
876+
}
877+
this.#selectedItems.clear()
878+
866879
// Only check, never uncheck here. Single-select mode does not allow unchecking a checked item.
867880
if (checked) {
868881
this.#addSelectedItem(item)
869882
itemContent?.setAttribute(this.ariaSelectionType, 'true')
870883
}
871884

872-
for (const checkedItem of this.querySelectorAll(`[${this.ariaSelectionType}]`)) {
873-
if (checkedItem !== itemContent) {
874-
this.#removeSelectedItem(checkedItem)
875-
checkedItem.setAttribute(this.ariaSelectionType, 'false')
876-
}
877-
}
878-
879885
this.#setDynamicLabel()
880886
} else {
881887
// multi-select mode allows unchecking a checked item

‎demo/app/controllers/select_panel_items_controller.rb

+14
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ def index
4242
[]
4343
end
4444

45+
if unselect_items?
46+
results.map! do |result|
47+
result.merge(selected: false)
48+
end
49+
end
50+
4551
clean_up_old_uuids(uuid)
4652

4753
respond_to do |format|
@@ -84,4 +90,12 @@ def clean_up_old_uuids(current_uuid)
8490
def key_for(uuid)
8591
"#{COOKIE_PREFIX}#{uuid}"
8692
end
93+
94+
def select_items?
95+
params.fetch(:select_items, "true") == "true"
96+
end
97+
98+
def unselect_items?
99+
!select_items?
100+
end
87101
end

‎previews/primer/alpha/select_panel_preview.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class SelectPanelPreview < ViewComponent::Preview
1818
# @param open_on_load toggle
1919
# @param anchor_align [Symbol] select [start, center, end]
2020
# @param anchor_side [Symbol] select [outside_bottom, outside_top, outside_left, outside_right]
21+
# @param select_items toggle
2122
def playground(
2223
title: "Sci-fi equipment",
2324
subtitle: "Various tools from your favorite shows",
@@ -31,10 +32,12 @@ def playground(
3132
show_filter: true,
3233
open_on_load: false,
3334
anchor_align: :start,
34-
anchor_side: :outside_bottom
35+
anchor_side: :outside_bottom,
36+
select_items: true
3537
)
3638
render_with_template(locals: {
3739
subtitle: subtitle,
40+
select_items: select_items,
3841
system_arguments: {
3942
title: title,
4043
size: size,

‎previews/primer/alpha/select_panel_preview/playground.html.erb

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
src: select_panel_items_path(
99
select_variant: :single,
1010
show_results: !simulate_no_results,
11-
fail: simulate_failure
11+
fail: simulate_failure,
12+
select_items: select_items
1213
),
1314
select_variant: :single,
1415
fetch_strategy: :remote,

‎test/system/alpha/select_panel_test.rb

+123-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
# frozen_string_literal: true
23

34
require "system/test_case"
@@ -182,11 +183,12 @@ def test_remembers_selections_on_filter
182183
end
183184

184185
# Phaser should already be selected
185-
assert_selector "[aria-checked=true]", count: 1
186+
assert_selector "[aria-checked=true]", text: "Phaser"
186187

187188
click_on "Photon torpedo"
188189

189-
assert_selector "[aria-checked=true]", count: 2
190+
assert_selector "[aria-checked=true]", text: "Phaser"
191+
assert_selector "[aria-checked=true]", text: "Photon torpedo"
190192

191193
wait_for_items_to_load do
192194
filter_results(query: "ph")
@@ -382,6 +384,100 @@ def test_single_select_disabled_item_cannot_be_checked
382384
refute_selector "[aria-selected=true]"
383385
end
384386

387+
def test_single_select_does_not_allow_server_to_check_items_on_filter_if_selections_already_made
388+
# playground is single-select
389+
visit_preview(:playground)
390+
391+
wait_for_items_to_load do
392+
click_on_invoker_button
393+
end
394+
395+
# Phaser should already be selected
396+
assert_selector "[aria-selected=true]", text: "Phaser"
397+
398+
click_on "Photon torpedo"
399+
click_on_invoker_button
400+
401+
wait_for_items_to_load do
402+
filter_results(query: "ph")
403+
end
404+
405+
# server will render this item checked, but since the user has already made selections,
406+
# the server-rendered selections should be ignored
407+
refute_selector "[aria-selected=true]", text: "Phaser"
408+
assert_selector "[aria-selected=true]", text: "Photon torpedo"
409+
end
410+
411+
def test_single_select_remembers_only_one_checked_item_and_ignores_checked_items_from_server
412+
# playground is single-select
413+
visit_preview(:playground)
414+
415+
wait_for_items_to_load do
416+
click_on_invoker_button
417+
end
418+
419+
# Phaser should already be selected
420+
assert_selector "[aria-selected=true]", text: "Phaser"
421+
422+
wait_for_items_to_load do
423+
filter_results(query: "light")
424+
end
425+
426+
click_on "Lightsaber"
427+
428+
click_on_invoker_button
429+
430+
wait_for_items_to_load do
431+
filter_results(query: "")
432+
end
433+
434+
refute_selector "[aria-selected=true]", text: "Phaser"
435+
assert_selector "[aria-selected=true]", text: "Lightsaber"
436+
end
437+
438+
def test_single_select_handles_all_options_unselected_by_default
439+
# playground is single-select
440+
visit_preview(:playground, select_items: false)
441+
442+
wait_for_items_to_load do
443+
click_on_invoker_button
444+
end
445+
446+
# Phaser should note already be selected
447+
refute_selector "[aria-selected=true]", text: "Phaser"
448+
449+
wait_for_items_to_load do
450+
filter_results(query: "light")
451+
end
452+
453+
click_on "Lightsaber"
454+
455+
click_on_invoker_button
456+
457+
wait_for_items_to_load do
458+
filter_results(query: "")
459+
end
460+
461+
refute_selector "[aria-selected=true]", text: "Phaser"
462+
assert_selector "[aria-selected=true]", text: "Lightsaber"
463+
464+
465+
wait_for_items_to_load do
466+
filter_results(query: "ph")
467+
end
468+
469+
click_on "Phaser"
470+
471+
click_on_invoker_button
472+
473+
wait_for_items_to_load do
474+
filter_results(query: "")
475+
end
476+
477+
assert_selector "[aria-selected=true]", text: "Phaser"
478+
refute_selector "[aria-selected=true]", text: "Lightsaber"
479+
end
480+
385481
########## MULTISELECT TESTS ############
386482

387483
def test_multi_select_items_checked
@@ -477,6 +573,31 @@ def test_multi_select_disabled_item_cannot_be_checked
477573
refute_selector "[aria-checked=true]"
478574
end
479575

576+
def test_multi_select_does_not_allow_server_to_check_items_on_filter_if_selections_already_made
577+
# remote_fetch is multi-select
578+
visit_preview(:remote_fetch)
579+
580+
wait_for_items_to_load do
581+
click_on_invoker_button
582+
end
583+
584+
# Phaser should already be selected
585+
assert_selector "[aria-checked=true]", text: "Phaser"
586+
587+
# check torpedo, uncheck phaser
588+
click_on "Photon torpedo"
589+
click_on "Phaser"
590+
591+
wait_for_items_to_load do
592+
filter_results(query: "ph")
593+
end
594+
595+
# server will render phaser checked, but since the user has already made selections,
596+
# the server-rendered selections should be ignored
597+
refute_selector "[aria-checked=true]", text: "Phaser"
598+
assert_selector "[aria-checked=true]", text: "Photon torpedo"
599+
end
600+
480601
########## JAVASCRIPT API TESTS ############
481602

482603
def test_disable_item_via_js_api

0 commit comments

Comments
 (0)
Please sign in to comment.