feat(ui): error & state UX — error surface, view states, map selection, sheet gestures (F4)
- lib/error/: classify any caught error into a stable ErrorKind from the transport signal (HTTP status / Connect Code / fetch TypeError / navigator.onLine); map to translated error.* messages via reportError (sticky Retry toast for retryable kinds) or errorMessageKey (inline). Mail compose now surfaces the translated 403/error inline. - lib/ui/view-state.svelte: shared loading/empty/error placeholder with the right live-region role + optional action; entity tables (races/sciences/ship-classes) migrated, rest adopt incrementally. - map/selection-ring.ts: accent ring around the selected planet, fed into the map buildExtras alongside the reach circles. - lib/ui/sheet-dismiss.ts: tap-outside + drag-handle swipe-down dismissal for the planet/ship-group bottom-sheets (hand-rolled pointer events). Tests: error, view-state, selection-ring, sheet-dismiss (761 total). Docs: ui/docs/error-state-ux.md (+ index); F4 marked done. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { fireEvent, render } from "@testing-library/svelte";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import ViewState from "../src/lib/ui/view-state.svelte";
|
||||
|
||||
describe("ViewState", () => {
|
||||
it("announces an error assertively (role=alert)", () => {
|
||||
const ui = render(ViewState, {
|
||||
props: { kind: "error", message: "it broke", testid: "vs" },
|
||||
});
|
||||
const el = ui.getByTestId("vs");
|
||||
expect(el).toHaveAttribute("role", "alert");
|
||||
expect(el).toHaveAttribute("data-kind", "error");
|
||||
expect(el).toHaveTextContent("it broke");
|
||||
});
|
||||
|
||||
it("announces loading politely (role=status)", () => {
|
||||
const ui = render(ViewState, {
|
||||
props: { kind: "loading", message: "loading…" },
|
||||
});
|
||||
expect(ui.getByRole("status")).toHaveTextContent("loading…");
|
||||
});
|
||||
|
||||
it("renders an action button that fires onAction", async () => {
|
||||
const onAction = vi.fn();
|
||||
const ui = render(ViewState, {
|
||||
props: {
|
||||
kind: "error",
|
||||
message: "failed",
|
||||
actionLabel: "retry",
|
||||
onAction,
|
||||
},
|
||||
});
|
||||
await fireEvent.click(ui.getByText("retry"));
|
||||
expect(onAction).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("omits the action button when no handler is given", () => {
|
||||
const ui = render(ViewState, {
|
||||
props: { kind: "empty", message: "nothing here" },
|
||||
});
|
||||
expect(ui.queryByRole("button")).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user