Document title follows the configured instance name (#323) #327
@ -33,9 +33,10 @@ test('instance name changed in the general settings form persists', async ({ bro
|
|||||||
// Scoped to the card: the page has several forms with status regions.
|
// Scoped to the card: the page has several forms with status regions.
|
||||||
await expect(generalCard.getByRole('status')).toHaveText(/^(Saved\.|Gespeichert\.)$/);
|
await expect(generalCard.getByRole('status')).toHaveText(/^(Saved\.|Gespeichert\.)$/);
|
||||||
|
|
||||||
// The TopBar shows the new name without a reload — the save invalidates
|
// The TopBar and the document title show the new name without a reload —
|
||||||
// the branding query the TopBar reads from.
|
// the save invalidates the branding query both read from (issue #323).
|
||||||
await expect(page.locator('.topbar__brand')).toHaveText(newName);
|
await expect(page.locator('.topbar__brand')).toHaveText(newName);
|
||||||
|
await expect(page).toHaveTitle(new RegExp(`${newName}$`));
|
||||||
|
|
||||||
// The proof the form really persisted: the value survives a reload and
|
// The proof the form really persisted: the value survives a reload and
|
||||||
// the api returns it.
|
// the api returns it.
|
||||||
|
|||||||
@ -1,22 +1,33 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useBranding } from '../branding/use-branding';
|
||||||
|
|
||||||
const APP_NAME = 'Dorfteich';
|
const APP_NAME = 'Dorfteich';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route-specific document title (issue #163, WCAG 2.4.2): joins the given
|
* Route-specific document title (issue #163, WCAG 2.4.2): joins the given
|
||||||
* parts with the app name ("Page — Pond — Dorfteich"). Empty/undefined
|
* parts with the instance name ("Page — Pond — My Wiki"). Empty/undefined
|
||||||
* parts are skipped, so callers can pass still-loading data directly.
|
* parts are skipped, so callers can pass still-loading data directly.
|
||||||
* Falls back to the bare app name on unmount.
|
* Falls back to the bare instance name on unmount.
|
||||||
|
*
|
||||||
|
* The trailing name is the OPERATOR'S instance name, not the product name
|
||||||
|
* (issue #323) — same reasoning as the TopBar brand (issue #306). Until
|
||||||
|
* the branding query resolves (or when it cannot, e.g. maintenance mode)
|
||||||
|
* the shipped default keeps the title stable, so an untouched instance
|
||||||
|
* reads exactly as before.
|
||||||
*/
|
*/
|
||||||
export function useDocumentTitle(...parts: (string | null | undefined)[]): void {
|
export function useDocumentTitle(...parts: (string | null | undefined)[]): void {
|
||||||
const joined = [...parts.filter(Boolean), APP_NAME].join(' — ');
|
const appName = useBranding()?.instanceName.trim() || APP_NAME;
|
||||||
|
const joined = [...parts.filter(Boolean), appName].join(' — ');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = joined;
|
document.title = joined;
|
||||||
}, [joined]);
|
}, [joined]);
|
||||||
useEffect(
|
useEffect(
|
||||||
|
// On unmount only in effect: `joined` always changes with `appName`,
|
||||||
|
// so the title effect above re-runs right after this cleanup.
|
||||||
() => () => {
|
() => () => {
|
||||||
document.title = APP_NAME;
|
document.title = appName;
|
||||||
},
|
},
|
||||||
[],
|
[appName],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user