77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
// 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>',
|
|
)
|
|
})
|
|
})
|