This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// The pages a person sees when there is no page (spec §10.4): the error boundary, the loading state, and
|
||||
// the shared shape of not found and forbidden. Rendered to markup, so what is asserted is what a screen
|
||||
// reader is given: one heading, a sentence, an alert for the failure, and the way out. The browser half
|
||||
// (a 404 and a 403 as statuses, axe on both) is e2e/shell.spec.ts.
|
||||
|
||||
import { createElement } from "react"
|
||||
import { renderToStaticMarkup } from "react-dom/server"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { BoundaryMessages, ErrorView } from "../apps/web/src/ui/boundary.js"
|
||||
import { LoadingView, StateView } from "../apps/web/src/ui/state-view.js"
|
||||
|
||||
const english = {
|
||||
title: "Something went wrong",
|
||||
body: "The page could not be shown. Try again.",
|
||||
code: "Error code: {digest}",
|
||||
retry: "Try again",
|
||||
home: "Back to the start",
|
||||
}
|
||||
|
||||
const render = (error: Error & { digest?: string }, retry = vi.fn()): string =>
|
||||
renderToStaticMarkup(
|
||||
createElement(BoundaryMessages, { strings: english, children: createElement(ErrorView, { error, retry }) }),
|
||||
)
|
||||
|
||||
describe("the error boundary", () => {
|
||||
it("has one heading, the failure as an alert, a way to try again and a way out", () => {
|
||||
const html = render(new Error("hidden detail"))
|
||||
expect(html.match(/<h1/g)).toHaveLength(1)
|
||||
expect(html).toContain("<h1>Something went wrong</h1>")
|
||||
expect(html).toContain('role="alert">The page could not be shown. Try again.')
|
||||
expect(html).toContain("<button")
|
||||
expect(html).toContain("Try again</button>")
|
||||
expect(html).toContain('<a href="/">Back to the start</a>')
|
||||
})
|
||||
|
||||
it("never prints the error's own message, which can carry a tenant's data, and prints the digest an operator searches the log for", () => {
|
||||
const html = render(Object.assign(new Error("password=hunter2 for ada@example.test"), { digest: "3275165072" }))
|
||||
expect(html).not.toContain("hunter2")
|
||||
expect(html).not.toContain("ada@example.test")
|
||||
expect(html).toContain("Error code: 3275165072")
|
||||
})
|
||||
|
||||
it("prints no code when there is no digest", () => {
|
||||
expect(render(new Error("x"))).not.toContain("Error code")
|
||||
})
|
||||
|
||||
it("hands the error up to the boundary above when nothing gave it words", () => {
|
||||
expect(() => renderToStaticMarkup(createElement(ErrorView, { error: new Error("up"), retry: vi.fn() }))).toThrow(
|
||||
"up",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("the loading and the empty states", () => {
|
||||
it("announce once, politely, and hide the spinner from a screen reader", () => {
|
||||
const html = renderToStaticMarkup(createElement(LoadingView, { label: "Loading" }))
|
||||
expect(html).toContain('role="status"')
|
||||
expect(html).toContain('<span class="app-spinner" aria-hidden="true"></span>')
|
||||
expect(html).toContain("<span>Loading</span>")
|
||||
expect(html).not.toContain("<h1")
|
||||
})
|
||||
|
||||
it("give a state page a landmark, a heading, its words and its actions, in that order", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
createElement(StateView, {
|
||||
title: "Page not found",
|
||||
actions: createElement("a", { href: "/" }, "Back"),
|
||||
children: createElement("p", null, "Nothing here."),
|
||||
}),
|
||||
)
|
||||
expect(html).toBe(
|
||||
'<main class="app-state"><h1>Page not found</h1><p>Nothing here.</p><p><a href="/">Back</a></p></main>',
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user