import { expect, test } from "bun:test"; import { appearanceLogoURL, normalizePublicAppearance } from "../src/stores/use-appearance-store"; 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("正在加载"); 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('theme="dark"'); 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([ Bun.file(new URL("../src/pages/admin/settings/appearance-settings-page.tsx", import.meta.url)).text(), 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(" :is(img, svg)'); }); test("object storage can adopt the configured English brand identifier without replacing saved prefixes automatically", async () => { const source = await Bun.file(new URL("../src/pages/admin/settings/storage-settings-page.tsx", import.meta.url)).text(); 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([Bun.file(new URL("../src/pages/admin/settings/appearance-settings-page.tsx", import.meta.url)).text(), 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")'); });