Files
ttstd-infinite-canvas/web/test/appearance-bootstrap.test.ts

133 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from "bun:test";
import { appearanceLogoURL, normalizePublicAppearance } from "../src/stores/use-appearance-store";
import { moduleGroupSource } from "./helpers/module-group-source";
test("initial HTML stays brand neutral until the public appearance is resolved", async () => {
const [html, mainSource] = await Promise.all([Bun.file(new URL("../index.html", import.meta.url)).text(), Bun.file(new URL("../src/main.tsx", import.meta.url)).text()]);
expect(html).not.toContain("影策");
expect(html).toContain("<title>正在加载</title>");
expect(mainSource).toContain("bootstrapAppearance()");
expect(mainSource).toContain('import("./application")');
expect(mainSource.indexOf("bootstrapAppearance()")).toBeLessThan(mainSource.indexOf('import("./application")'));
});
test("a custom login video never falls back to the built-in poster", () => {
const appearance = normalizePublicAppearance({
brandName: "HIMA Studio",
brandSlug: "hima-studio",
authHeroTitle: "把灵感,\n变成可见的故事。",
authHeroDescription: "从同一个创作空间持续推进。",
authVideoConfigured: true,
authVideoUrl: "/api/public/appearance/assets/video?v=next",
authVideoPosterConfigured: false,
authVideoPosterUrl: "",
});
expect(appearance.brandName).toBe("HIMA Studio");
expect(appearance.brandSlug).toBe("hima-studio");
expect(appearance.authHeroTitle).toBe("把灵感,\n变成可见的故事。");
expect(appearance.authHeroDescription).toBe("从同一个创作空间持续推进。");
expect(appearance.authVideoPosterUrl).toBe("");
expect(appearance.authVideoAutoplay).toBe(true);
});
test("login video autoplay defaults on and can be disabled explicitly", () => {
expect(normalizePublicAppearance({}).authVideoAutoplay).toBe(true);
expect(normalizePublicAppearance({ authVideoAutoplay: false }).authVideoAutoplay).toBe(false);
});
test("appearance URLs reject executable and insecure remote schemes", () => {
const appearance = normalizePublicAppearance({
logoConfigured: true,
logoUrl: "javascript:alert(1)",
authVideoConfigured: true,
authVideoUrl: "http://example.com/brand.mp4",
});
expect(appearance.logoUrl).toBe("/logo.svg");
expect(appearance.authVideoUrl).not.toContain("example.com");
});
test("appearance selects theme logos and falls back to the single configured logo", () => {
const dual = normalizePublicAppearance({
logoConfigured: true,
darkLogoConfigured: true,
logoUrl: "/api/public/appearance/assets/logo?v=dual",
darkLogoUrl: "/api/public/appearance/assets/logo-dark?v=dual",
logoFrameEnabled: false,
});
expect(appearanceLogoURL(dual, "light")).toContain("/logo?");
expect(appearanceLogoURL(dual, "dark")).toContain("/logo-dark?");
expect(dual.logoFrameEnabled).toBe(false);
const single = normalizePublicAppearance({ logoConfigured: true, logoUrl: "/api/public/appearance/assets/logo?v=single" });
expect(appearanceLogoURL(single, "light")).toBe(single.logoUrl);
expect(appearanceLogoURL(single, "dark")).toBe(single.logoUrl);
expect(single.logoFrameEnabled).toBe(true);
});
test("auth scene consumes resolved appearance instead of hardcoded media constants", async () => {
const source = await Bun.file(new URL("../src/pages/auth/auth-scene.tsx", import.meta.url)).text();
expect(source).toContain("appearance.authVideoUrl");
expect(source).toContain("appearance.authVideoAutoplay");
expect(source).toContain("appearance.authVideoPosterUrl || undefined");
expect(source).toContain("appearance.brandName");
expect(source).toContain("appearance.authHeroTitle");
expect(source).toContain("appearance.authHeroDescription");
expect(source).toContain("useThemeStore((state) => state.theme)");
expect(source).toContain("theme={theme}");
expect(source).not.toContain("让一个故事,");
expect(source).not.toContain("AUTH_VIDEO_URL");
expect(source).not.toContain("AUTH_VIDEO_POSTER");
});
test("appearance management exposes light and dark logo uploads plus the frame switch", async () => {
const [pageSource, brandSource, adminStyles, globalStyles] = await Promise.all([
Promise.resolve(moduleGroupSource("pages/admin/settings/appearance-settings-page.tsx")),
Bun.file(new URL("../src/components/brand/brand-logo.tsx", import.meta.url)).text(),
Bun.file(new URL("../src/styles/admin-ui.css", import.meta.url)).text(),
Bun.file(new URL("../src/styles/globals.css", import.meta.url)).text(),
]);
expect(pageSource).toContain('slot="logo-dark"');
expect(pageSource).toContain("浅色模式 Logo");
expect(pageSource).toContain("深色模式 Logo(可选)");
expect(pageSource).toContain("禁用 Logo 后面的圆角矩形外框");
expect(pageSource).toContain("<Switch");
expect(pageSource).toContain("checked={!logoFrameEnabled}");
expect(pageSource).toContain("setLogoFrameEnabled(!checked)");
expect(pageSource).not.toContain("<Checkbox");
expect(pageSource).toContain("深浅模式 Logo 预览");
expect(pageSource).toContain("登录页视频自动播放");
expect(pageSource).toContain("authVideoAutoplay");
expect(brandSource).toContain("useActiveTheme");
expect(brandSource).toContain("data-logo-frame-enabled");
expect(brandSource).toContain("failedSource === source");
expect(brandSource).toContain('aria-hidden="true"');
expect(brandSource).toContain('style.visibility = "hidden"');
expect(brandSource).toContain("setFailedSource(source)");
expect(adminStyles).toContain(".admin-appearance-logo-preview-mark.is-unframed img");
expect(globalStyles).toContain('.brand-logo-frame[data-logo-frame-enabled="false"] > :is(img, svg)');
});
test("object storage can adopt the configured English brand identifier without replacing saved prefixes automatically", async () => {
const source = await Promise.resolve(moduleGroupSource("pages/admin/settings/storage-settings-page.tsx"));
expect(source).toContain("state.appearance.brandSlug");
expect(source).toContain("使用品牌标识");
expect(source).toContain('form.setFieldValue("pathPrefix", brandSlug)');
expect(source).toContain("setting.pathPrefix || DEFAULT_OSS_PATH_PREFIX");
});
test("appearance management exposes a server-side reset to the built-in Yingce brand", async () => {
const [pageSource, apiSource] = await Promise.all([Promise.resolve(moduleGroupSource("pages/admin/settings/appearance-settings-page.tsx")), Bun.file(new URL("../src/services/api/appearance.ts", import.meta.url)).text()]);
expect(pageSource).toContain("恢复影策默认");
expect(pageSource).toContain("resetAdminAppearance()");
expect(pageSource).toContain("已上传文件仍保留在存储资源中");
expect(apiSource).toContain('http.delete<{ setting: AdminAppearance }>("/admin/settings/appearance")');
});