feat: contain mobile Device Setup focus (Closes #1134)
CI / lint (pull_request) Successful in 2m54s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 3m24s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-19 14:31:38 +00:00
1 parent f3c9e89920
commit 4b3e53f4d0
3 files changed
+142 -4

No files matched your search

@@ -148,12 +148,24 @@ def test_release_artifact_bootstraps_mobile_home_and_returns_from_insights(
expect(page.locator("#device-setup-sheet")).to_be_visible()
expect(page.locator("#device-storage-heading")).to_have_text("Private device storage")
expect(page.locator("#device-storage-detail")).to_contain_text("private work records")
expect(page.locator("body > header")).to_have_attribute("inert", "")
expect(page.locator("main")).to_have_attribute("inert", "")
expect(dock).to_have_attribute("inert", "")
expect(page.locator("#close-device-setup")).to_be_focused()
page.keyboard.press("Shift+Tab")
expect(page.locator("#clear-private-device-data")).to_be_focused()
page.keyboard.press("Tab")
expect(page.locator("#close-device-setup")).to_be_focused()
for selector in ("#clear-device-caches", "#clear-private-device-data"):
bounds = page.locator(selector).bounding_box()
assert bounds and bounds["height"] >= 44
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
page.locator("#close-device-setup").click()
page.go_back()
expect(page.locator("#device-setup-sheet")).to_be_hidden()
expect(page.locator("#open-device-setup")).to_be_focused()
expect(page.locator("body > header")).not_to_have_attribute("inert", "")
expect(page.locator("main")).not_to_have_attribute("inert", "")
expect(dock).not_to_have_attribute("inert", "")
page.locator("#active-devices").click()
expect(page.locator("#active-devices-sheet")).to_be_visible()
+66 -1
View File
@@ -10,7 +10,7 @@ def run_scenario(script: str) -> dict:
harness = r"""
const createMobileDeviceSetup = require(__MODULE__);
class FakeTarget {
constructor() { this.listeners = {}; this.hidden = false; this.disabled = false; this.textContent = ''; }
constructor() { this.listeners = {}; this.hidden = false; this.disabled = false; this.textContent = ''; this.inert = false; }
addEventListener(name, callback) { (this.listeners[name] ||= []).push(callback); }
async dispatch(name, event = {}) { for (const callback of this.listeners[name] || []) await callback(event); }
focus() { state.focused = this; }
@@ -39,6 +39,15 @@ const promptSummary = new FakeTarget();
const promptLauncher = new FakeTarget();
const promptDismiss = new FakeTarget();
const returnButton = new FakeTarget();
const backgroundHeader = new FakeTarget();
const backgroundMain = new FakeTarget(); backgroundMain.inert = true;
sheet.querySelectorAll = () => [closeButton, installButton, offlineButton, returnButton];
const historyTarget = new FakeTarget();
const history = {
state:{section:'home'},
pushState(state) { this.state = state; },
back() { this.state = {section:'home'}; historyTarget.dispatch('popstate', {state:this.state}); },
};
let now = 1000;
const promptStorage = {
values:{},
@@ -57,6 +66,8 @@ const setup = createMobileDeviceSetup({
installStatus, offlineStatus, protectionStatus, pushStatus, deadlineStatus, readyStatus, escapeTarget,
promptCard, promptSummary, promptLauncher, promptDismiss,
returnButton, isMobile:() => true,
backgroundElements:[backgroundHeader, backgroundMain],
history, historyTarget,
timerView:{
beginDetour:reason => { state.events.push('pause'); state.beginCalls.push(reason); return {reason}; },
finishDetour:() => { state.events.push('resume'); state.finishCalls += 1; },
@@ -206,6 +217,60 @@ process.stdout.write(JSON.stringify({
assert result == {"hidden": True, "launcherFocused": True}
def test_open_setup_contains_focus_and_restores_background_inert_state():
result = run_scenario("""
await launcher.dispatch('click', {currentTarget:launcher});
state.focused = returnButton;
let preventedForward = false;
await escapeTarget.dispatch('keydown', {key:'Tab', target:returnButton, preventDefault(){preventedForward=true;}});
const forwardFocus = state.focused === closeButton;
state.focused = closeButton;
let preventedReverse = false;
await escapeTarget.dispatch('keydown', {key:'Tab', shiftKey:true, target:closeButton, preventDefault(){preventedReverse=true;}});
const reverseFocus = state.focused === returnButton;
const inertWhileOpen = [backgroundHeader.inert, backgroundMain.inert];
await closeButton.dispatch('click');
process.stdout.write(JSON.stringify({
preventedForward, forwardFocus, preventedReverse, reverseFocus,
inertWhileOpen,
inertAfterClose:[backgroundHeader.inert, backgroundMain.inert],
launcherFocused:state.focused === launcher,
}));
""")
assert result == {
"preventedForward": True,
"forwardFocus": True,
"preventedReverse": True,
"reverseFocus": True,
"inertWhileOpen": [True, True],
"inertAfterClose": [False, True],
"launcherFocused": True,
}
def test_browser_back_closes_setup_and_restores_launcher_focus():
result = run_scenario("""
await launcher.dispatch('click', {currentTarget:launcher});
const marked = history.state.deviceSetup === true;
history.back();
await new Promise(resolve => setImmediate(resolve));
process.stdout.write(JSON.stringify({
marked,
hidden:sheet.hidden,
backgroundReleased:backgroundHeader.inert === false,
launcherFocused:state.focused === launcher,
}));
""")
assert result == {
"marked": True,
"hidden": True,
"backgroundReleased": True,
"launcherFocused": True,
}
def test_mobile_setup_pauses_before_readiness_and_each_exit_resumes_once():
result = run_scenario("""
state.events = [];