Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 20s
Stage 2 final unit for Step 22 (user edit + export). u20 wires the previously
placeholder bottom-action footer to the u18 /api/connect and u19 /api/export
middlewares living in Front/vite.config.ts:
- BottomActions.tsx
• drops the dead `serializeSlidePlan` import (TS2305 blocker since u14;
project-wide `tsc --noEmit` now exits 0)
• exports three pure builders for vitest (no jsdom / RTL devDep needed):
buildConnectRequest(run_id, slug) -> POST /api/connect {run_id, slug}
buildExportRequest(run_id) -> POST /api/export {run_id}
buildDownloadFilename(run_id) -> "<run_id>.html"
• handleExport: POST -> blob -> a[download] click chain; toast on
success / failure / network error.
• handleConnect: derives slug via deriveUserOverridesKey(uploadedFile.name)
and PUTs to u18 cel mirror; reports assets_copied count.
• both buttons disable when runMeta is null so the UI cannot fire
requests with an undefined run_id.
- Home.tsx
• mounts <BottomActions/> in the footer with
{slidePlan, runMeta, uploadedFile, isLoading, onGenerate}.
• removes 2 of 3 placeholder `toast.info('… 준비 중입니다.')` buttons
(LeftMdxPanel MDX-edit placeholder remains — out of u20 scope).
• adds handleTextEdit (u15 wire to text_overrides axis) and
handleStructureEdit (u15 wire to structure_overrides axis) to satisfy
the SlideCanvas props introduced earlier in the u-series.
- imp90_bottom_actions.test.ts (new)
• 11 vitest specs locking the builder URL + JSON shape against u18/u19
middleware contracts. Verified 11/11 pass.
Stage 4 verification (all PASS):
• u20 vitest: 11/11
• u18/u19 endpoint vitest: 31/31
• npx tsc --noEmit: exit 0 (carry-forward TS2305 resolved)
• backend pytest (u1~u9 + u17 print mode, 9 files): 185/185
Out of scope:
• LeftMdxPanel.tsx:333 MDX-edit placeholder toast (separate unit)
• #1 / #72 / #74 / #79 / #80 / #81 / #93 closed dependencies (no re-impl)
• AI-generated HTML structure (Phase Z regression guard)
• frame swap via structure_overrides (locked to slot_order + hidden_slots)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
// IMP-56 (#90) u20 — vitest coverage for the pure request builders exported
|
|
// by `BottomActions`. The React component itself is not rendered (jsdom /
|
|
// @testing-library NOT in Front devDependencies — verified against the prior
|
|
// u14 `imp90_structure_overlay.test.tsx` pattern); we test the deterministic
|
|
// pieces that drive the network payload sent to the u18 / u19 middlewares.
|
|
//
|
|
// Upstream / downstream contracts (verified by prior units):
|
|
// - u18 /api/connect : body shape = { run_id, slug } (Front/vite.config.ts
|
|
// handleConnectMirror — `imp90_connect_endpoint.test.ts`).
|
|
// - u19 /api/export : body shape = { run_id }; response = raw text/html
|
|
// with `Content-Disposition: attachment; filename="<run_id>.html"`
|
|
// (Front/vite.config.ts handleExportStandalone —
|
|
// `imp90_export_endpoint.test.ts`).
|
|
//
|
|
// u20 scope: builders only. Any drift in URL or JSON shape fails here before
|
|
// the request leaves the client. Toast / fetch / blob plumbing is not tested
|
|
// (it would require jsdom + a fetch mock; the existing server-side tests
|
|
// already pin the wire contract).
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
buildConnectRequest,
|
|
buildExportRequest,
|
|
buildDownloadFilename,
|
|
} from "../src/components/BottomActions";
|
|
|
|
describe("buildConnectRequest", () => {
|
|
it("targets /api/connect", () => {
|
|
const { url } = buildConnectRequest("run_42", "mdx_03");
|
|
expect(url).toBe("/api/connect");
|
|
});
|
|
|
|
it("emits { run_id, slug } JSON body — matches u18 middleware shape", () => {
|
|
const { body } = buildConnectRequest("run_42", "mdx_03");
|
|
expect(JSON.parse(body)).toEqual({ run_id: "run_42", slug: "mdx_03" });
|
|
});
|
|
|
|
it("preserves zero-length and unicode run_id verbatim (server validates)", () => {
|
|
const { body } = buildConnectRequest("", "x");
|
|
expect(JSON.parse(body)).toEqual({ run_id: "", slug: "x" });
|
|
const { body: uni } = buildConnectRequest("런", "슬러그");
|
|
expect(JSON.parse(uni)).toEqual({ run_id: "런", slug: "슬러그" });
|
|
});
|
|
|
|
it("does not leak extra keys (frame swap / overrides etc.)", () => {
|
|
const { body } = buildConnectRequest("r", "s");
|
|
expect(Object.keys(JSON.parse(body)).sort()).toEqual(["run_id", "slug"]);
|
|
});
|
|
});
|
|
|
|
describe("buildExportRequest", () => {
|
|
it("targets /api/export", () => {
|
|
const { url } = buildExportRequest("run_42");
|
|
expect(url).toBe("/api/export");
|
|
});
|
|
|
|
it("emits { run_id } JSON body — matches u19 middleware shape", () => {
|
|
const { body } = buildExportRequest("run_42");
|
|
expect(JSON.parse(body)).toEqual({ run_id: "run_42" });
|
|
});
|
|
|
|
it("does not leak extra keys (slug / format etc.)", () => {
|
|
const { body } = buildExportRequest("r");
|
|
expect(Object.keys(JSON.parse(body))).toEqual(["run_id"]);
|
|
});
|
|
|
|
it("preserves zero-length and unicode run_id verbatim (server validates)", () => {
|
|
expect(JSON.parse(buildExportRequest("").body)).toEqual({ run_id: "" });
|
|
expect(JSON.parse(buildExportRequest("런").body)).toEqual({ run_id: "런" });
|
|
});
|
|
});
|
|
|
|
describe("buildDownloadFilename", () => {
|
|
it("returns <run_id>.html for the a[download] click chain", () => {
|
|
expect(buildDownloadFilename("run_42")).toBe("run_42.html");
|
|
});
|
|
|
|
it("appends exactly one .html suffix even when run_id already ends in .html", () => {
|
|
// The server-side `Content-Disposition` already carries the same
|
|
// filename; we mirror it verbatim so browser default behavior wins.
|
|
// We intentionally do NOT strip a trailing `.html` — run_id is the
|
|
// backend's `Path(args.mdx_path).stem`-style key, which never contains
|
|
// a dot suffix (validated by `isValidUserOverridesKey` at u18/u19).
|
|
expect(buildDownloadFilename("foo.html")).toBe("foo.html.html");
|
|
});
|
|
|
|
it("returns just .html for empty run_id (server rejects upstream)", () => {
|
|
expect(buildDownloadFilename("")).toBe(".html");
|
|
});
|
|
});
|