Stop a closing dialog taking focus back from whatever moved on (#584)
This commit is contained in:
parent
f2c9a02756
commit
882b263701
4 changed files with 42 additions and 9 deletions
|
|
@ -448,6 +448,9 @@ playwright's own reporting only cover contexts playwright made itself, and the h
|
||||||
own, so without that attachment a failure carries the DOM snapshot and nothing the app said —
|
own, so without that attachment a failure carries the DOM snapshot and nothing the app said —
|
||||||
which is unreadable when what failed is the app rendering its error page.
|
which is unreadable when what failed is the app rendering its error page.
|
||||||
|
|
||||||
|
It also attaches `relay-transcript`, what each user said to each relay and what came back. The
|
||||||
|
console says what the app did with an event, and the transcript says whether it ever had one.
|
||||||
|
|
||||||
One engine per run. These specs exercise sockets, auth and sync, so running them under three
|
One engine per run. These specs exercise sockets, auth and sync, so running them under three
|
||||||
engines adds little coverage. `E2E_BROWSER=webkit pnpm test` runs the whole
|
engines adds little coverage. `E2E_BROWSER=webkit pnpm test` runs the whole
|
||||||
suite under another one. The container listens on a fixed port and cannot be sharded, so
|
suite under another one. The container listens on a fixed port and cannot be sharded, so
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,12 @@ import {
|
||||||
mockRelayInfo,
|
mockRelayInfo,
|
||||||
} from "./net/http"
|
} from "./net/http"
|
||||||
import type {BlossomOptions, HostingFixtures, RelayInfoOverrides} from "./net/http"
|
import type {BlossomOptions, HostingFixtures, RelayInfoOverrides} from "./net/http"
|
||||||
import {assertNoLeaks, installWebSocketRoutes, silenceRelay} from "./net/websocket"
|
import {
|
||||||
|
assertNoLeaks,
|
||||||
|
formatTranscript,
|
||||||
|
installWebSocketRoutes,
|
||||||
|
silenceRelay,
|
||||||
|
} from "./net/websocket"
|
||||||
import {watchFaults} from "./faults"
|
import {watchFaults} from "./faults"
|
||||||
import {boot} from "./app/boot"
|
import {boot} from "./app/boot"
|
||||||
import {injectNip07} from "./app/nip07"
|
import {injectNip07} from "./app/nip07"
|
||||||
|
|
@ -206,7 +211,7 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
||||||
await zooid.start()
|
await zooid.start()
|
||||||
await zooid.ensure()
|
await zooid.ensure()
|
||||||
|
|
||||||
const contexts: BrowserContext[] = []
|
const contexts: {name: string; context: BrowserContext}[] = []
|
||||||
const faults = watchFaults()
|
const faults = watchFaults()
|
||||||
|
|
||||||
let scenario: Maybe<Scenario>
|
let scenario: Maybe<Scenario>
|
||||||
|
|
@ -233,7 +238,7 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
||||||
...options.context,
|
...options.context,
|
||||||
})
|
})
|
||||||
|
|
||||||
contexts.push(context)
|
contexts.push({name: user?.name ?? "anonymous", context})
|
||||||
|
|
||||||
// `use.trace` and the built-in reporting only cover contexts playwright made itself, so a
|
// `use.trace` and the built-in reporting only cover contexts playwright made itself, so a
|
||||||
// failure in a context opened here arrives with the app's own account of it thrown away —
|
// failure in a context opened here arrives with the app's own account of it thrown away —
|
||||||
|
|
@ -306,7 +311,7 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
||||||
|
|
||||||
// Closed before anything is read off it: an $effect teardown reading a binding svelte has
|
// Closed before anything is read off it: an $effect teardown reading a binding svelte has
|
||||||
// already cleared throws on unmount, and that is the fault the suite was blindest to.
|
// already cleared throws on unmount, and that is the fault the suite was blindest to.
|
||||||
for (const context of contexts) {
|
for (const {context} of contexts) {
|
||||||
await context.close()
|
await context.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,15 +323,29 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
||||||
if (faults.found.length > 0 || testInfo.status !== testInfo.expectedStatus) {
|
if (faults.found.length > 0 || testInfo.status !== testInfo.expectedStatus) {
|
||||||
// A path rather than a body: the list reporter truncates an inline attachment, and the
|
// A path rather than a body: the list reporter truncates an inline attachment, and the
|
||||||
// nightly run on the box keeps test-results and nothing else.
|
// nightly run on the box keeps test-results and nothing else.
|
||||||
const path = testInfo.outputPath("browser-console.log")
|
const consolePath = testInfo.outputPath("browser-console.log")
|
||||||
|
|
||||||
await writeFile(path, faults.log.join("\n"))
|
await writeFile(consolePath, faults.log.join("\n"))
|
||||||
await testInfo.attach("browser-console", {path, contentType: "text/plain"})
|
await testInfo.attach("browser-console", {path: consolePath, contentType: "text/plain"})
|
||||||
|
|
||||||
|
// What each user said to each relay and what came back. The console says what the app did
|
||||||
|
// with an event; this says whether it ever had one, which is the only way to tell a client
|
||||||
|
// that dropped a message from a relay that never sent it.
|
||||||
|
const transcriptPath = testInfo.outputPath("relay-transcript.log")
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
transcriptPath,
|
||||||
|
contexts.map(({name, context}) => `== ${name}\n${formatTranscript(context)}`).join("\n"),
|
||||||
|
)
|
||||||
|
await testInfo.attach("relay-transcript", {
|
||||||
|
path: transcriptPath,
|
||||||
|
contentType: "text/plain",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
faults.assertNone()
|
faults.assertNone()
|
||||||
|
|
||||||
for (const context of contexts) {
|
for (const {context} of contexts) {
|
||||||
assertNoLeaks(context)
|
assertNoLeaks(context)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -630,6 +630,10 @@ test("US-036 receive a new conversation live", async ({seed, as}) => {
|
||||||
|
|
||||||
await sendDm(bob, "starting a chat with you")
|
await sendDm(bob, "starting a chat with you")
|
||||||
|
|
||||||
|
// His own copy first, so a send that lost keystrokes to something else on the page fails here
|
||||||
|
// rather than thirty seconds later as a message that never reached her
|
||||||
|
await expect(bubble(bob, "starting a chat with you")).toBeVisible()
|
||||||
|
|
||||||
// Her list picks the conversation up on its own
|
// Her list picks the conversation up on its own
|
||||||
const fromBob = chatItems(alice).filter({hasText: "Bob Barnacle"})
|
const fromBob = chatItems(alice).filter({hasText: "Bob Barnacle"})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,15 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
const overlay = element
|
||||||
const autofocus = panel.querySelector<HTMLElement>("[autofocus]")
|
const autofocus = panel.querySelector<HTMLElement>("[autofocus]")
|
||||||
|
|
||||||
|
const holdsFocus = () => {
|
||||||
|
const {activeElement} = document
|
||||||
|
|
||||||
|
return !activeElement || activeElement === document.body || overlay.contains(activeElement)
|
||||||
|
}
|
||||||
|
|
||||||
trap = createFocusTrap(element, {
|
trap = createFocusTrap(element, {
|
||||||
allowOutsideClick: true,
|
allowOutsideClick: true,
|
||||||
escapeDeactivates: false,
|
escapeDeactivates: false,
|
||||||
|
|
@ -113,7 +120,7 @@
|
||||||
...(autofocus ? {initialFocus: autofocus} : {}),
|
...(autofocus ? {initialFocus: autofocus} : {}),
|
||||||
isolateSubtrees: false,
|
isolateSubtrees: false,
|
||||||
returnFocusOnDeactivate: false,
|
returnFocusOnDeactivate: false,
|
||||||
setReturnFocus: previous => (previous.isConnected ? previous : false),
|
setReturnFocus: previous => (previous.isConnected && holdsFocus() ? previous : false),
|
||||||
tabbableOptions: {getShadowRoot: true},
|
tabbableOptions: {getShadowRoot: true},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue