AT-C001..C019 场景⑤:画布导出 PNG/PDF 从未被重新打开验证,PDF 在测试里根本没被生成过
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1h 7m
- Merged PRs (30d)
- 969
Description
> 来自 #3005(AT 验收 B4+B5)。实测 SHA `94f6dda037ca94da73d1b0a6359763a54db832a3`(= 当时 origin/main HEAD,工作树 clean)。
## 结论
验收方案 §8.1 给 19 个内置模板规定的统一场景第 ⑤ 条是「**导出 PNG/PDF 可打开,边界、标题、便签、连接线不裁切**」,§1 结论规则第 4 条另有一条总纲:「承诺文件产物时,**必须重新打开并验证结构、MIME、字节 hash 和下载权限**」。
本仓当前的导出验收**两条都不满足**,而且 PNG 与 PDF 的缺口不是同一种:
- **PNG**:真的产出了,但只按前缀和尺寸判,从没被解码过。
- **PDF**:在测试里**从来没有被生成过一次**——断言的对象是一个 `vi.fn()`。
## 证据(实测,非推断)
唯一的导出测试是 `apps/web/tests/ui/canvas-stage-export.test.tsx`(3 条用例)。
### ① PNG:只判前缀 + 尺寸 > 0
`tests/ui/canvas-stage-export.test.tsx:100-102`:
```ts
expect(result.dataUrl.startsWith("data:image/png")).toBe(true);
expect(result.width).toBeGreaterThan(0);
expect(result.height).toBeGreaterThan(0);
```
`width`/`height` 是 `exportPNG()` 自己**回报**的包围盒数字,不是从图里量出来的。整条链路没有任何一步把 base64 解码成位图、核对像素宽高、或检查四边有没有把标题/便签/连接线裁掉。一张内容全白但包围盒算对的图会**照样绿**。
### ② PDF:被 mock 掉,没有 PDF 产生
同文件 `:120-124` 断言的是 `exportPngAsPdf` 这个 **mock 被调用过**,以及传进去的第 1 个参数是 PNG data URL、第 4 个参数以 `.pdf` 结尾:
```ts
fireEvent.click(screen.getByTestId("chat-diagram-export-pdf"));
await waitFor(() => expect(exportPngAsPdf).toHaveBeenCalledTimes(1));
expect(exportPngAsPdf.mock.calls[0][0]).toMatch(/^data:image\/png/);
expect(exportPngAsPdf.mock.calls[0][3]).toMatch(/\.pdf$/);
```
真实实现 `apps/web/lib/canvas/export-image.ts:33` 的 `exportPngAsPdf` 里那句 `await import("jspdf")` **在整个测试套件里一次都没执行过**。也就是说:jspdf 的页面尺寸换算、纵横比、下载触发,全部零覆盖;`.pdf` 这个后缀是断言里唯一和 PDF 有关的东西。
### ③ 服务端没有导出面
`apps/api` 侧的 `GET /canvas/instances/:id/render` 是**渲染投影**(JSON),不是图片;grep `png|pdf` 在 `apps/api/src/application/canvas/`、`canvas*.controller.ts`、`packages/contracts/src/canvas.ts` 里只命中设计文档引用(`Design.pdf` §x.y),**没有任何导出端点**。所以「下载权限」这一层在服务端也无从验证。
## 影响
`AT-C001…AT-C019` 全部 19 个模板的场景 ⑤ 目前都只能记 **PARTIAL**——组件层产出了 PNG,但没有任何一层证明产物可打开、不裁切、权限正确;PDF 连产出都没发生。
## 建议修法(未自行动手,等裁决优先级)
1. **PNG 解码断言**:在组件测试里把 data URL 解成 buffer,用 `sharp`/`pngjs` 之类读回真实像素宽高与 PNG 魔数,和 `exportPNG()` 自报的 `width/height` 交叉核对(自报值与实测值不一致就是缺陷)。
2. **PDF 真产出**:至少一条测试**不 mock** `exportPngAsPdf`,让 jspdf 真跑一遍,把产物读回来核 `%PDF-` 魔数、页数、页面尺寸比例。
3. **不裁切**:这条是视觉判据,归 e2e——现有 `apps/web/e2e/canvas-tpl-sticky-not-clipped.spec.ts` 是对的方向,但它判的是**画布上**没裁切,不是**导出的图**没裁切;需要把导出产物本身喂给比对。
4. 若决定 PNG/PDF 导出**只在前端、不做服务端下载权限**,请在 §8.1 场景 ⑤ 里把「下载权限」显式标 N/A 并写明理由,否则这条永远达不到 PASS。
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with apps/web/tests/ui/canvas-stage-export.test.tsx and apps/web/lib/canvas/export-image.ts, then review apps/web/e2e/canvas-tpl-sticky-not-clipped.spec.ts. Run the existing export tests to confirm the PNG assertions and mocked PDF path. Done means the agreed PNG/PDF validation and clipping checks are covered, with the server-side download-permission expectation explicitly resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100