* Fix presentation and frontend ownership invariants * Fix audited backend ownership invariants
274 lines
8.6 KiB
TypeScript
274 lines
8.6 KiB
TypeScript
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { Couch } from "../src/client/components/living/Couch.tsx";
|
|
import { participantColor } from "../src/client/components/living/participant-color.ts";
|
|
import {
|
|
BrandLoader,
|
|
BrandMark,
|
|
} from "../src/client/components/living/BrandMark.tsx";
|
|
import { Comic } from "../src/client/components/living/Comic.tsx";
|
|
import { ComicTooltip } from "../src/client/components/living/ComicTooltip.tsx";
|
|
import { HintComic } from "../src/client/components/living/hints/index.tsx";
|
|
import { StageOverlay } from "../src/client/components/living/Stage.tsx";
|
|
import { ViewerOverview } from "../src/client/components/living/ViewerOverview.tsx";
|
|
import { Btn, NameTag } from "../src/client/components/living/primitives.tsx";
|
|
import { EMPTY_METRICS } from "../src/client/types.ts";
|
|
import { setCopy, t } from "../src/client/ui/copy.ts";
|
|
|
|
describe("living-room presentation", () => {
|
|
afterEach(() => setCopy({ lang: "zh", vis: false }));
|
|
|
|
it("keeps visual-only stage status available to assistive technology", () => {
|
|
setCopy({ lang: "en", vis: true });
|
|
const html = renderToStaticMarkup(
|
|
createElement(StageOverlay, {
|
|
icon: "refresh",
|
|
message: "Media connection recovering",
|
|
}),
|
|
);
|
|
|
|
expect(html).toContain('role="status"');
|
|
expect(html).toContain('aria-label="Media connection recovering"');
|
|
expect(html).not.toContain("lr-tv-msg");
|
|
});
|
|
|
|
it("uses one static brand shape and an aria-hidden animated loading variant", () => {
|
|
const mark = renderToStaticMarkup(createElement(BrandMark, { size: 32 }));
|
|
const loader = renderToStaticMarkup(createElement(BrandLoader));
|
|
|
|
expect(mark).toContain('class="lr-brand-mark"');
|
|
expect(mark).toContain("lr-brand-eye-wink");
|
|
expect(mark).not.toContain("is-animated");
|
|
expect(loader).toContain('class="lr-brand-loader"');
|
|
expect(loader).toContain("lr-brand-mark is-animated");
|
|
expect(loader).toContain('aria-hidden="true"');
|
|
});
|
|
|
|
it("uses the animated brand for transient stage states", () => {
|
|
const html = renderToStaticMarkup(
|
|
createElement(StageOverlay, {
|
|
icon: "loader",
|
|
transition: true,
|
|
message: "Allocating media route",
|
|
}),
|
|
);
|
|
|
|
expect(html).toContain('role="status"');
|
|
expect(html).toContain("lr-brand-loader");
|
|
expect(html).not.toContain("lr-tv-big");
|
|
});
|
|
|
|
it("ends the exhausted-route comic with a failed fallback", () => {
|
|
const html = renderToStaticMarkup(
|
|
createElement(Comic, { kind: "route-failed", size: 320 }),
|
|
);
|
|
|
|
expect(html).toContain("vls-rf-fallback-x");
|
|
expect(html).toContain("vls-rf-fallback");
|
|
expect(html).toContain("vlsRfFailurePulse");
|
|
expect(html).toContain("vlsRfFailureX");
|
|
expect(html).not.toContain("vls-rf-green");
|
|
expect(html).not.toContain("vls-rf-star");
|
|
});
|
|
|
|
it("announces the supplied state for a Viewer without committed media", () => {
|
|
setCopy({ lang: "en", vis: false });
|
|
const html = renderToStaticMarkup(
|
|
createElement(Couch, {
|
|
entries: [
|
|
{
|
|
key: "viewer-1",
|
|
name: "TNT",
|
|
connected: false,
|
|
statusLabel: "Recovering",
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
expect(html).toContain("TNT · Recovering");
|
|
expect(html).toContain("lr-pawn-led is-wait");
|
|
expect(html).toContain('aria-label="Viewers 1"');
|
|
});
|
|
|
|
it("keeps the current display name visible in visual mode", () => {
|
|
setCopy({ lang: "en", vis: true });
|
|
const identity = "viewer-name-tag";
|
|
const html = renderToStaticMarkup(
|
|
createElement(NameTag, { name: "TNT", identity }),
|
|
);
|
|
|
|
expect(html).toContain(">TNT<");
|
|
expect(html).toContain(participantColor(identity));
|
|
expect(html).not.toContain("visually-hidden");
|
|
});
|
|
|
|
it("leaves hidden tooltip artwork out of the initial markup", () => {
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
ComicTooltip,
|
|
{
|
|
kind: "hint-share-start",
|
|
children: createElement("button", { type: "button" }, "Share"),
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(html).toContain("lr-comic-tip");
|
|
expect(html).toContain(">Share</button>");
|
|
expect(html).not.toContain('viewBox="0 0 320 96"');
|
|
});
|
|
|
|
it("keeps a hint-wrapped disabled control explainable by keyboard", () => {
|
|
setCopy({ lang: "en", vis: true });
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
ComicTooltip,
|
|
{
|
|
kind: "hint-join-go",
|
|
children: createElement(Btn, {
|
|
icon: "refresh",
|
|
title: "viewer.reconnect",
|
|
disabled: true,
|
|
}),
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(html).toContain('class="lr-comic-tip-wrap is-disabled-trigger"');
|
|
expect(html).toContain('tabindex="0"');
|
|
expect(html).toContain(`aria-label="${t("en", "viewer.reconnect")}"`);
|
|
});
|
|
|
|
it("animates the private-room hint around its door and lock", () => {
|
|
const html = renderToStaticMarkup(
|
|
createElement(HintComic, { kind: "hint-policy-private", size: 240 }),
|
|
);
|
|
|
|
expect(html).toContain("vls-priv-door");
|
|
expect(html).toContain("vls-priv-lock");
|
|
expect(html).toContain("vls-priv-card");
|
|
expect(html).toContain("vlsPrivDoor");
|
|
expect(html).toContain("vlsPrivLock");
|
|
});
|
|
|
|
it("keeps the Host-offline comic free of the waiting-room moon", () => {
|
|
const html = renderToStaticMarkup(
|
|
createElement(Comic, { kind: "host-offline", theme: "stage" }),
|
|
);
|
|
|
|
expect(html).not.toContain("M229 39.5a8.5");
|
|
});
|
|
|
|
it("keeps every crowded and relay Viewer name visible in visual mode", () => {
|
|
setCopy({ lang: "en", vis: true });
|
|
const html = renderToStaticMarkup(
|
|
createElement(Couch, {
|
|
entries: Array.from({ length: 12 }, (_, index) => ({
|
|
key: `viewer-${index}`,
|
|
name:
|
|
index === 11
|
|
? "Relay Viewer With A Full Name"
|
|
: `Viewer ${index}`,
|
|
connected: true,
|
|
})),
|
|
}),
|
|
);
|
|
|
|
expect(html).toContain("lr-pawns is-crowded");
|
|
expect(html).toContain(
|
|
'<span class="lr-pawn-name">Relay Viewer With A Full Name</span>',
|
|
);
|
|
expect(html).toContain('title="Relay Viewer With A Full Name"');
|
|
expect(html).not.toContain("max-width:72px");
|
|
});
|
|
|
|
it("derives every participant color only from the peer identity", () => {
|
|
const peerId = "viewer-stable-identity";
|
|
const color = participantColor(peerId);
|
|
const html = renderToStaticMarkup(
|
|
createElement(Couch, {
|
|
entries: [
|
|
{
|
|
key: peerId,
|
|
name: "Self",
|
|
connected: true,
|
|
you: true,
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
expect(participantColor(peerId)).toBe(color);
|
|
expect(html).toContain(color);
|
|
expect(html).not.toContain("var(--you)");
|
|
});
|
|
|
|
it("shows all Viewer primary metrics in one compact selectable overview", () => {
|
|
const html = renderToStaticMarkup(
|
|
createElement(ViewerOverview, {
|
|
entries: [
|
|
{
|
|
key: "viewer-a",
|
|
name: "Alice",
|
|
connected: true,
|
|
statusLabel: "Connected",
|
|
route: "p2p",
|
|
metrics: {
|
|
...EMPTY_METRICS,
|
|
resolution: "1920x1080",
|
|
framesPerSecond: 59.8,
|
|
bitrateKbps: 5120,
|
|
packetLossPercent: 0.4,
|
|
rttMs: 18,
|
|
},
|
|
},
|
|
{
|
|
key: "viewer-b",
|
|
name: "Bob",
|
|
connected: false,
|
|
statusLabel: "Routing",
|
|
route: null,
|
|
metrics: null,
|
|
},
|
|
],
|
|
selectedKey: "viewer-a",
|
|
onSelect: () => undefined,
|
|
}),
|
|
);
|
|
|
|
expect(html).toContain("lr-viewer-overview-row is-selected");
|
|
expect(html).toContain("Alice");
|
|
expect(html).toContain("1920x1080");
|
|
expect(html).toContain("59.8");
|
|
expect(html).toContain("5120");
|
|
expect(html).toContain("18 ms");
|
|
expect(html).toContain("Bob");
|
|
});
|
|
|
|
it("uses route glyphs without visible route text in visual mode", () => {
|
|
setCopy({ lang: "en", vis: true });
|
|
const html = renderToStaticMarkup(
|
|
createElement(ViewerOverview, {
|
|
entries: [
|
|
{
|
|
key: "viewer-p2p",
|
|
name: "Alice",
|
|
connected: true,
|
|
statusLabel: "Connected",
|
|
route: "p2p",
|
|
metrics: null,
|
|
},
|
|
],
|
|
selectedKey: null,
|
|
onSelect: () => undefined,
|
|
}),
|
|
);
|
|
|
|
expect(html).not.toContain("<b>P2P</b>");
|
|
expect(html).toContain(t("en", "state.route.p2p"));
|
|
});
|
|
});
|