boardx / boardx/workspacex

AT-C001..C019 场景⑤:画布导出 PNG/PDF 从未被重新打开验证,PDF 在测试里根本没被生成过

Open
#3,009 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.