···294295 expect(res.status).toBe(200);
296 const html = await res.text();
0297 expect(html).not.toContain("--injected");
000298 });
299300 it("drops token values containing '}' (CSS block-escape injection prevention)", async () => {
···317 const html = await res.text();
318 // The injected block-escape value must not appear
319 expect(html).not.toContain("red} body");
00000000000000000000320 });
321});
···294295 expect(res.status).toBe(200);
296 const html = await res.text();
297+ // The injected declaration must not appear
298 expect(html).not.toContain("--injected");
299+ // The entire dirty value must be dropped — not just the injected suffix
300+ // (a partial-strip bug would output '--color-bg: red' which looks safe)
301+ expect(html).not.toContain("--color-bg");
302 });
303304 it("drops token values containing '}' (CSS block-escape injection prevention)", async () => {
···321 const html = await res.text();
322 // The injected block-escape value must not appear
323 expect(html).not.toContain("red} body");
324+ });
325+326+ it("returns a fallback HTML fragment when no tokens are submitted (does not crash)", async () => {
327+ setupAuthenticatedSession([MANAGE_THEMES]);
328+329+ const routes = await loadThemeRoutes();
330+ // POST with no body — parseBody() returns {} which produces an empty token map
331+ const res = await routes.request("/admin/themes/abc123/preview", {
332+ method: "POST",
333+ headers: {
334+ "content-type": "application/x-www-form-urlencoded",
335+ cookie: "atbb_session=token",
336+ },
337+ });
338+339+ // Must not crash — returns a valid HTML fragment
340+ expect(res.status).toBe(200);
341+ const html = await res.text();
342+ expect(html).not.toContain("<html");
343+ expect(html).toContain(".preview-pane-inner");
344 });
345});