diff --git a/web/src/main.tsx b/web/src/main.tsx index 115869a6d..bd10f2abe 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -7,8 +7,10 @@ import { isIsolatedPrevisRepro } from "@/lib/dev-repro"; installChunkRecovery(); // The public film entry checks its availability independently of workspace bootstrap. -if (/^\/welcome\/?$/.test(window.location.pathname)) void import("./welcome-application"); -else { +if (/^\/welcome\/?$/.test(window.location.pathname)) { + void bootstrapAppearance().catch(() => undefined); + void import("./welcome-application"); +} else { // The backend-free DEV lab must not make requests before AppProviders isolates it. const appearanceReady = isIsolatedPrevisRepro(import.meta.env.DEV, window.location.pathname) ? Promise.resolve() : bootstrapAppearance(); void import("./application"); diff --git a/web/src/pages/welcome/index.tsx b/web/src/pages/welcome/index.tsx index 07b22ec78..15e1247a7 100644 --- a/web/src/pages/welcome/index.tsx +++ b/web/src/pages/welcome/index.tsx @@ -59,7 +59,6 @@ function WelcomeExperience({ look, brandName, onLookChange }: { look: WelcomeLoo const videoRef = useRef(null); useEffect(() => { - document.title = "影策 · 让一个故事从文字走向银幕"; const media = window.matchMedia("(prefers-reduced-motion: reduce)"); const onMotion = () => setReduced(media.matches); media.addEventListener("change", onMotion); diff --git a/web/test/welcome-title.test.mjs b/web/test/welcome-title.test.mjs new file mode 100644 index 000000000..69b6363f0 --- /dev/null +++ b/web/test/welcome-title.test.mjs @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; +import vm from "node:vm"; + +const source = (path) => readFileSync(new URL(path, import.meta.url), "utf8"); + +test("welcome entry loads configured appearance", async () => { + let calls = 0; + const imports = []; + const main = source("../src/main.tsx") + .replace(/^import .*;\n/gm, "") + .replace(/import\.meta\.env\.DEV/g, "false") + .replace(/import\(/g, "loadModule("); + vm.runInNewContext(main, { + window: { location: { pathname: "/welcome" } }, + installChunkRecovery() {}, + isIsolatedDirectorRepro: () => false, + bootstrapAppearance: async () => { calls++; }, + loadModule: async (name) => { imports.push(name); }, + }); + await Promise.resolve(); + assert.equal(calls, 1, "welcome entry must bootstrap appearance"); + assert.deepEqual(imports, ["./welcome-application"]); +}); + +test("welcome effects preserve configured SEO title", () => { + const page = source("../src/pages/welcome/index.tsx"); + // Run the actual mount effects with inert motion/scroll listeners. + const effects = [...page.matchAll(/useEffect\(\(\) => \{([\s\S]*?)\n \}, \[([^\]]*)\]\);/g)].filter((match) => match[2] === ""); + assert.ok(effects.length > 0); + for (const title of ["Custom SEO Title", "Custom Site Name"]) { + const document = { title }; + for (const [, body] of effects) { + vm.runInNewContext(`(() => {${body}\n})()`, { + document, + window: { matchMedia: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }), addEventListener() {}, removeEventListener() {} }, + storyRef: { current: null }, restorePickerFocus: { current: false }, + setReduced() {}, setLook() {}, getWelcomeLook() {}, + }); + } + assert.equal(document.title, title, "welcome must preserve appearance metadata title"); + } +});