fix: Beute-Editor fängt den Fokus, SVG-Icon im Kopf
- Auf /colonies/[id]/hive bekommen Sprunglink, Seitenleiste, Kopf, Offline-Band, untere Navigation und „+ Neu“ das Attribut inert; der erste Tab landet auf „Zurück“ im Editor (vorher Stopp 34). - metadata.icons verlinkt /icon.svg neben favicon.ico und apple-icon. - Tests: alle THEME_COLORS gleich dem jeweiligen --background, ausdrücklich gewähltes Dunkel färbt die theme-color. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
ce68729233
commit
6ac9eef93b
11 files changed
+221
-9
No files matched your search
@@ -103,6 +103,28 @@ describe("appearanceScript", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("an explicit dark choice paints both media theme-color metas dark, on a light device too", () => {
|
||||
stubMedia(false, false);
|
||||
const metas = (["light", "dark"] as const).map((scheme) => {
|
||||
const meta = document.createElement("meta");
|
||||
meta.name = "theme-color";
|
||||
meta.media = `(prefers-color-scheme: ${scheme})`;
|
||||
meta.content = scheme === "light" ? "#f9f4ea" : "#1a120d";
|
||||
document.head.append(meta);
|
||||
return meta;
|
||||
});
|
||||
|
||||
window.localStorage.setItem(APPEARANCE_KEYS.theme, "dark");
|
||||
run(appearanceScript());
|
||||
expect(metas.map((meta) => meta.content)).toEqual(["#1a120d", "#1a120d"]);
|
||||
|
||||
// Light is the default (dark only on request): a dark device without a choice stays light.
|
||||
window.localStorage.clear();
|
||||
stubMedia(true, false);
|
||||
run(appearanceScript());
|
||||
expect(metas.map((meta) => meta.content)).toEqual(["#f9f4ea", "#f9f4ea"]);
|
||||
});
|
||||
|
||||
it("has no build flag for dark any more (task A4)", () => {
|
||||
expect(appearanceScript()).not.toMatch(/DARK=/);
|
||||
});
|
||||
|
||||
@@ -34,11 +34,11 @@ function setOnLine(value: boolean) {
|
||||
}
|
||||
|
||||
/** The band as the layout places it: above the page content wrapper. */
|
||||
function BandOnPage({ syncEnabled }: { syncEnabled?: boolean }) {
|
||||
function BandOnPage({ syncEnabled, inert }: { syncEnabled?: boolean; inert?: boolean }) {
|
||||
const pageContentRef = useRef<HTMLDivElement>(null);
|
||||
return (
|
||||
<>
|
||||
<OfflineBand userId="user-1" pageContentRef={pageContentRef} syncEnabled={syncEnabled} />
|
||||
<OfflineBand userId="user-1" pageContentRef={pageContentRef} syncEnabled={syncEnabled} inert={inert} />
|
||||
<div ref={pageContentRef} tabIndex={-1} data-testid="page-content">
|
||||
Seiteninhalt
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@ function BandOnPage({ syncEnabled }: { syncEnabled?: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderBand(props: { syncEnabled?: boolean } = {}) {
|
||||
function renderBand(props: { syncEnabled?: boolean; inert?: boolean } = {}) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(
|
||||
<QueryClientProvider client={client}>
|
||||
@@ -85,6 +85,19 @@ describe("OfflineBand", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("leaves the tab order and the accessibility tree while a full-screen page covers it", async () => {
|
||||
setOnLine(false);
|
||||
vi.mocked(summarizeQueue).mockResolvedValue({ own: 2, failed: 0, foreign: 0 });
|
||||
|
||||
const { unmount } = renderBand({ inert: true });
|
||||
await waitFor(() => expect(summarizeQueue).toHaveBeenCalled());
|
||||
expect(document.querySelector('[aria-live="polite"]')).toHaveAttribute("inert");
|
||||
unmount();
|
||||
|
||||
renderBand();
|
||||
expect(screen.getByRole("status")).not.toHaveAttribute("inert");
|
||||
});
|
||||
|
||||
it("announces „Offline – 2 Einträge warten\" in a status region", async () => {
|
||||
setOnLine(false);
|
||||
vi.mocked(summarizeQueue).mockResolvedValue({ own: 2, failed: 0, foreign: 0 });
|
||||
|
||||
@@ -38,6 +38,8 @@ interface OfflineBandProps {
|
||||
* nor sends - the server would only answer 401.
|
||||
*/
|
||||
syncEnabled?: boolean;
|
||||
/** True while a full-screen page covers the shell: out of the tab order and the accessibility tree. */
|
||||
inert?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +47,7 @@ interface OfflineBandProps {
|
||||
* device, how much still waits to be sent and what the server rejected.
|
||||
* Sends by itself as soon as the connection is back.
|
||||
*/
|
||||
export function OfflineBand({ userId, pageContentRef, syncEnabled = true }: OfflineBandProps) {
|
||||
export function OfflineBand({ userId, pageContentRef, syncEnabled = true, inert }: OfflineBandProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const connectivity = useConnectivity();
|
||||
const statusRef = useRef<HTMLDivElement>(null);
|
||||
@@ -136,6 +138,7 @@ export function OfflineBand({ userId, pageContentRef, syncEnabled = true }: Offl
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
tabIndex={-1}
|
||||
inert={inert}
|
||||
className={cn(
|
||||
hasContent &&
|
||||
"flex flex-wrap items-center gap-x-3 gap-y-2 border-b border-border/60 px-4 py-2 text-sm sm:px-6",
|
||||
|
||||
Reference in new issue
Block a user