Pin the chrome's locale carrying in unit tests; keep one carrier list
CI / full-gate (push) Successful in 2m36s
CI / full-gate (push) Successful in 2m36s
This commit is contained in:
@@ -111,7 +111,8 @@ them. Revisit only if the stated reason stops holding.
|
|||||||
locales than the host (its strings fall back to `en-US` per key), never one the host lacks.
|
locales than the host (its strings fall back to `en-US` per key), never one the host lacks.
|
||||||
- **The core building blocks carry the locale; a plugin doesn't have to.** The shell (breadcrumbs),
|
- **The core building blocks carry the locale; a plugin doesn't have to.** The shell (breadcrumbs),
|
||||||
`pagination`, `filter-bar`, `data-table`, `auth-card`, `flow-body`, `field` and `menu` wrap every
|
`pagination`, `filter-bar`, `data-table`, `auth-card`, `flow-body`, `field` and `menu` wrap every
|
||||||
href they render in `localeHref`; the nav is wrapped upstream in `chrome.ts`; and the two GET forms
|
href they render in `localeHref`; the nav and the sign-in link are wrapped upstream in `chrome.ts`;
|
||||||
|
and the two GET forms
|
||||||
(filter bar, rows-per-page) carry it as a hidden `locale` input, since a GET submit replaces the
|
(filter bar, rows-per-page) carry it as a hidden `locale` input, since a GET submit replaces the
|
||||||
whole query string and no href wrapper can reach it. Putting the obligation on each call site was
|
whole query string and no href wrapper can reach it. Putting the obligation on each call site was
|
||||||
tried first and missed five of eight sites inside one commit — including the admin screens.
|
tried first and missed five of eight sites inside one commit — including the admin screens.
|
||||||
|
|||||||
@@ -998,12 +998,11 @@ Three rules worth knowing:
|
|||||||
that deliberately carries markup is rendered with `<%- %>` — and must never interpolate
|
that deliberately carries markup is rendered with `<%- %>` — and must never interpolate
|
||||||
untrusted data, since nothing escapes it there.
|
untrusted data, since nothing escapes it there.
|
||||||
- **Dates and numbers are `Intl`'s job**, not the catalog's: `new Intl.DateTimeFormat(ctx.locale)`.
|
- **Dates and numbers are `Intl`'s job**, not the catalog's: `new Intl.DateTimeFormat(ctx.locale)`.
|
||||||
- **The core building blocks carry the locale for you.** The shell's breadcrumbs, `pagination`,
|
- **The core building blocks carry the locale for you** — every href they render (menu, breadcrumbs,
|
||||||
`filter-bar`, `data-table`, `auth-card`, `field` and the nav wrap every href they render, and the
|
pagination, sort headers, row actions, the auth card's links) goes through `localeHref`, and their
|
||||||
two GET forms (filter bar, rows-per-page) carry it as a hidden `locale` input — a GET submit
|
GET forms carry it as a hidden field, since a GET submit replaces the whole query string.
|
||||||
replaces the whole query string, so no href wrapper can reach it. `ctx.localeHref` is only for
|
`ctx.localeHref` is for hrefs your own markup emits, and `localeParam` (a view local: the tag, or
|
||||||
hrefs your own markup emits, and `localeParam` (a view local: the tag, or null) for your own GET
|
null) for your own GET forms. `locale` is reserved: `parseListQuery` never returns it as a filter.
|
||||||
forms. `locale` is reserved: `parseListQuery` never returns it as a filter.
|
|
||||||
- **Reuse the core words.** Generic UI verbs live in the core catalog — `common.add/cancel/delete/
|
- **Reuse the core words.** Generic UI verbs live in the core catalog — `common.add/cancel/delete/
|
||||||
edit/new/remove/save`, `filter.*`, `pagination.*`, `table.*` — and a plugin's lookup falls through
|
edit/new/remove/save`, `filter.*`, `pagination.*`, `table.*` — and a plugin's lookup falls through
|
||||||
to them. Keep your catalog for your domain words, so N plugins don't re-translate "Cancel" N times.
|
to them. Keep your catalog for your domain words, so N plugins don't re-translate "Cancel" N times.
|
||||||
|
|||||||
@@ -52,3 +52,11 @@ test("field defaults to a bare text input, escapes a string error, and never thr
|
|||||||
assert.match(stringErr, /<span><b>Required<\/b>\.<\/span>/); // string error is escaped
|
assert.match(stringErr, /<span><b>Required<\/b>\.<\/span>/); // string error is escaped
|
||||||
assert.match(stringErr, /aria-describedby="x-err"/);
|
assert.match(stringErr, /aria-describedby="x-err"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("an inline field link carries the visitor's language", async () => {
|
||||||
|
const html = await ejs.renderFile(field, {
|
||||||
|
...ENGLISH_LOCALS, localeHref: (href: string) => `${href}?locale=sv-SE`,
|
||||||
|
id: "password", label: "Password", link: { href: "/recovery", label: "Forgot password?" }, name: "password",
|
||||||
|
});
|
||||||
|
assert.match(html, /href="\/recovery\?locale=sv-SE"/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { fileURLToPath } from "node:url";
|
|||||||
import ejs from "ejs";
|
import ejs from "ejs";
|
||||||
import { ENGLISH_LOCALS } from "../i18n/view-locals.ts";
|
import { ENGLISH_LOCALS } from "../i18n/view-locals.ts";
|
||||||
|
|
||||||
|
// A localeHref that marks what it touches, so a raw href in the chrome is visible to a test.
|
||||||
|
const CARRYING = { ...ENGLISH_LOCALS, localeHref: (href: string) => `${href}?locale=sv-SE` };
|
||||||
|
|
||||||
const shell = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "views", "partials", "shell.ejs");
|
const shell = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "views", "partials", "shell.ejs");
|
||||||
const render = (data: Record<string, unknown> = {}): Promise<string> => ejs.renderFile(shell, { ...ENGLISH_LOCALS, ...data });
|
const render = (data: Record<string, unknown> = {}): Promise<string> => ejs.renderFile(shell, { ...ENGLISH_LOCALS, ...data });
|
||||||
|
|
||||||
@@ -107,3 +110,9 @@ test("app shell escapes text but passes slot HTML through, and renders with defa
|
|||||||
assert.match(bare, /<aside class="sidebar"/);
|
assert.match(bare, /<aside class="sidebar"/);
|
||||||
assert.match(bare, /<main class="content"/);
|
assert.match(bare, /<main class="content"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the chrome carries the visitor's language: breadcrumb links go through localeHref", async () => {
|
||||||
|
const html = await ejs.renderFile(shell, { ...CARRYING, breadcrumbs: [{ href: "/admin/users", label: "Users" }, { label: "Ada" }], title: "Ada" });
|
||||||
|
assert.match(html, /<a href="\/admin\/users\?locale=sv-SE">Users<\/a>/);
|
||||||
|
assert.match(html, /<span>Ada<\/span>/); // the current crumb has no href to carry
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user