Skip to content

Commit ac5ca89

Browse files
authored
Merge branch 'dev' into fix/3661-compute-graphs2
2 parents ffff73d + 550a4d6 commit ac5ca89

File tree

7 files changed

+132
-6
lines changed

7 files changed

+132
-6
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# These owners will be the default owners for everything in
22
# the repo. Unless a later match takes precedence
3-
* @T4rk1n @ndrezn @camdecoster
3+
* @T4rk1n @ndrezn @camdecoster @KoolADE85

components/dash-core-components/src/fragments/Dropdown.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ const Dropdown = (props: DropdownProps) => {
275275
return;
276276
}
277277

278+
// Don't steal focus from the search input during search-driven
279+
// re-renders (displayOptions changes while the user is typing).
280+
if (document.activeElement === searchInputRef.current) {
281+
return;
282+
}
283+
278284
requestAnimationFrame(() => {
279285
if (!multi) {
280286
const selectedValue = sanitizedValues[0];

components/dash-core-components/src/utils/optionRendering.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const OptionLabel: React.FC<OptionLabelProps> = ({
4949
<ExternalWrapper
5050
key={i}
5151
component={label}
52-
componentPath={[...ctx.componentPath, index, i]}
52+
componentPath={[...ctx.componentPath, String(value), i]}
5353
/>
5454
))}
5555
</>

components/dash-core-components/tests/integration/dropdown/test_a11y.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from dash import Dash, Input, Output
33
from dash.dcc import Dropdown
4-
from dash.html import Div, Label, P
4+
from dash.html import Div, Label, P, Span
55
from selenium.common.exceptions import TimeoutException
66
from selenium.webdriver.common.keys import Keys
77
from selenium.webdriver.common.action_chains import ActionChains
@@ -730,3 +730,40 @@ def is_visible(el):
730730
)
731731

732732
return all([is_visible(el) for el in elements])
733+
734+
735+
def test_a11y009_dropdown_component_labels_render_correctly(dash_duo):
736+
app = Dash(__name__)
737+
app.layout = Div(
738+
[
739+
Dropdown(
740+
options=[
741+
{"label": Span("red"), "value": "red"},
742+
{"label": Span("yellow"), "value": "yellow"},
743+
{"label": Span("blue"), "value": "blue"},
744+
],
745+
value=["red", "yellow", "blue"],
746+
id="components-label-dropdown",
747+
multi=True,
748+
),
749+
]
750+
)
751+
752+
dash_duo.start_server(app)
753+
754+
dash_duo.find_element("#components-label-dropdown").click()
755+
dash_duo.wait_for_element(".dash-dropdown-options")
756+
757+
# Click on the "yellow" option
758+
yellow_option = dash_duo.find_element(
759+
'.dash-dropdown-option:has(input[value="yellow"])'
760+
)
761+
yellow_option.click()
762+
763+
# After interaction, verify the options render correctly
764+
option_elements = dash_duo.find_elements(".dash-dropdown-option")
765+
rendered_labels = [el.text.strip() for el in option_elements]
766+
767+
assert rendered_labels == ["red", "yellow", "blue"]
768+
769+
assert dash_duo.get_logs() == []

components/dash-core-components/tests/integration/dropdown/test_clearable_false.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,13 @@ def update_value(val):
190190

191191
# Attempt to deselect all items. Everything should deselect until we get to
192192
# the last item which cannot be cleared.
193-
selected = dash_duo.find_elements(".dash-dropdown-options input[checked]")
194-
[el.click() for el in selected]
193+
# Click MTL option container
194+
mtl_option = dash_duo.find_element('.dash-dropdown-option:has(input[value="MTL"])')
195+
mtl_option.click()
196+
197+
# Click SF option container
198+
sf_option = dash_duo.find_element('.dash-dropdown-option:has(input[value="SF"])')
199+
sf_option.click()
195200

196201
assert dash_duo.find_element("#dropdown-value").text == "SF"
197202

components/dash-core-components/tests/integration/dropdown/test_search_value.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
from time import sleep
2+
3+
from selenium.webdriver.common.action_chains import ActionChains
4+
from selenium.webdriver.common.keys import Keys
5+
16
from dash import Dash, Input, Output, dcc, html
27

38

@@ -29,3 +34,76 @@ def update_output(search_value):
2934
dash_duo.wait_for_text_to_equal("#output", 'search_value="x"')
3035

3136
assert dash_duo.get_logs() == []
37+
38+
39+
def test_ddsv002_search_filter_and_scroll(dash_duo):
40+
"""Search filters a virtualized dropdown, backspace restores all options,
41+
then scroll to the bottom and select the last item."""
42+
app = Dash(__name__)
43+
options = [
44+
{"label": f"Option {i + 1}", "value": f"opt_{i + 1}"} for i in range(100)
45+
]
46+
app.layout = html.Div(
47+
[
48+
dcc.Dropdown(id="dropdown", options=options, value="opt_1"),
49+
html.Div(id="output"),
50+
]
51+
)
52+
53+
@app.callback(Output("output", "children"), Input("dropdown", "value"))
54+
def update_output(value):
55+
return f"value={value}"
56+
57+
dash_duo.start_server(app)
58+
dash_duo.wait_for_text_to_equal("#output", "value=opt_1")
59+
60+
# Open the dropdown by clicking it
61+
dash_duo.find_element("#dropdown").click()
62+
dash_duo.wait_for_element(".dash-dropdown-options")
63+
64+
# Click the search field to focus it
65+
search = dash_duo.find_element(".dash-dropdown-search")
66+
search.click()
67+
68+
# Use ActionChains to type into the currently focused element,
69+
# which will fail if focus is stolen from the search field.
70+
def send_key(key):
71+
ActionChains(dash_duo.driver).send_keys(key).perform()
72+
73+
# Type "100" one character at a time to filter down to "Option 100"
74+
send_key("1")
75+
sleep(0.2)
76+
send_key("0")
77+
sleep(0.2)
78+
send_key("0")
79+
sleep(0.2)
80+
81+
# Should have exactly one option visible: "Option 100"
82+
visible_options = dash_duo.find_elements(".dash-dropdown-option")
83+
assert len(visible_options) == 1
84+
assert "Option 100" in visible_options[0].text
85+
86+
# Backspace three times to clear the search and restore all options
87+
send_key(Keys.BACKSPACE)
88+
sleep(0.2)
89+
send_key(Keys.BACKSPACE)
90+
sleep(0.2)
91+
send_key(Keys.BACKSPACE)
92+
sleep(0.2)
93+
94+
# Scroll to the bottom of the options list
95+
options_container = dash_duo.find_element(".dash-dropdown-options")
96+
dash_duo.driver.execute_script(
97+
"arguments[0].querySelector('.dash-options-list-virtualized').scrollTop = "
98+
"arguments[0].querySelector('.dash-options-list-virtualized').scrollHeight",
99+
options_container,
100+
)
101+
sleep(0.3)
102+
103+
# Find and click the last option (Option 100)
104+
all_options = dash_duo.find_elements(".dash-dropdown-option")
105+
last_option = all_options[-1]
106+
assert "Option 100" in last_option.text
107+
last_option.click()
108+
109+
dash_duo.wait_for_text_to_equal("#output", "value=opt_100")

components/dash-html-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@
5858
"/dash_html_components/*{.js,.map}"
5959
],
6060
"browserslist": [
61-
"last 10 years and not dead"
61+
"last 11 years and not dead"
6262
]
6363
}

0 commit comments

Comments
 (0)