| 1 | # Plan 022: Test the export pipeline (characterization tests for the flagship path) |
| 2 | |
| 3 | > **Executor instructions**: Follow this plan step by step. Run every |
| 4 | > verification command and confirm the expected result. If anything in "STOP |
| 5 | > conditions" occurs, stop and report. When done, update the status row in |
| 6 | > `plans/README.md`. |
| 7 | > |
| 8 | > **Drift check (run first)**: `git diff --stat c63cb120..HEAD -- packages/slidev/node/commands/export.ts` |
| 9 | > On a mismatch with the excerpts below, treat it as a STOP condition. |
| 10 | |
| 11 | ## Status |
| 12 | |
| 13 | - **Priority**: P1 |
| 14 | - **Effort**: M |
| 15 | - **Risk**: LOW |
| 16 | - **Depends on**: none (unblocks 023; overlaps 009/016/021's helper tests) |
| 17 | - **Category**: tests |
| 18 | - **Planned at**: commit `c63cb120`, 2026-07-10 |
| 19 | |
| 20 | ## Why this matters |
| 21 | |
| 22 | `slidev export` (PDF/PNG/PPTX) is a flagship feature (README) implemented in a |
| 23 | 658-line file with **zero** tests. Regressions in page-range selection, TOC |
| 24 | outline building, or filename handling ship undetected; CI only runs `slidev |
| 25 | build` in smoke, never export. Characterization tests over the *pure* helpers |
| 26 | give a fast safety net now (no Playwright needed) and are the prerequisite for |
| 27 | safely decomposing the module (plan 023). |
| 28 | |
| 29 | ## Current state |
| 30 | |
| 31 | `packages/slidev/node/commands/export.ts` pure/near-pure helpers worth pinning: |
| 32 | - `getExportOptions(args, options, outFilename?)` (`:574-624`) — merges CLI args + |
| 33 | deck config into an `ExportOptions`; exported already. |
| 34 | - `addToTree` (`:51-67`) + `makeOutline` (`:69-77`) — TOC → PDF outline string |
| 35 | (module-internal; **not** currently exported). NB: plan 021 may move `addToTree` |
| 36 | into `@slidev/parser` as `buildTocTree` — if 021 has landed, test the shared |
| 37 | builder instead and only test `makeOutline` here. |
| 38 | - `parseRangeString` (from `@slidev/parser`) drives page selection at `:189` — its |
| 39 | own tests are plan 009; here, assert `getExportOptions`/range interplay. |
| 40 | - The `gen*` render functions need Playwright + a running preview → out of scope |
| 41 | for unit tests; covered by the optional smoke in Step 3. |
| 42 | |
| 43 | Test patterns available: colocated `*.test.ts` with Vitest (see |
| 44 | `packages/slidev/node/syntax/*.test.ts`) using `describe/it/expect` and |
| 45 | `toMatchInlineSnapshot`. |
| 46 | |
| 47 | ## Commands you will need |
| 48 | |
| 49 | | Purpose | Command | Expected | |
| 50 | |---------|---------|----------| |
| 51 | | Install | `pnpm install` | exit 0 | |
| 52 | | Build | `pnpm build` | exit 0 | |
| 53 | | Test | `pnpm test -- export` | new tests pass | |
| 54 | | Typecheck | `pnpm typecheck` | exit 0 | |
| 55 | |
| 56 | ## Scope |
| 57 | |
| 58 | **In scope**: |
| 59 | - `packages/slidev/node/commands/export.ts` — export `addToTree`/`makeOutline` |
| 60 | (or import the shared builder from 021) so they're testable; no behavior change |
| 61 | - `packages/slidev/node/commands/export.test.ts` (create) — unit tests |
| 62 | |
| 63 | **Out of scope**: |
| 64 | - Refactoring the `gen*` closures (plan 023). |
| 65 | - Browser teardown / filename confinement (plans 007/016 — their helpers may add |
| 66 | their own tests; don't duplicate). |
| 67 | - Setting up a Playwright CI job (the optional smoke in Step 3 is local-only unless |
| 68 | the operator wants it wired into CI). |
| 69 | |
| 70 | ## Git workflow |
| 71 | |
| 72 | - Branch: `test/export-pipeline`. |
| 73 | - Conventional commit: `test(export): characterize export helpers`. |
| 74 | - Do NOT push/PR unless instructed. |
| 75 | |
| 76 | ## Steps |
| 77 | |
| 78 | ### Step 1: Make the pure helpers importable |
| 79 | |
| 80 | If `addToTree`/`makeOutline` are still local to `export.ts`, add `export` to them |
| 81 | (or, if plan 021 landed, import `buildTocTree` from `@slidev/parser`). Do not |
| 82 | change their logic. |
| 83 | |
| 84 | ### Step 2: Write characterization tests |
| 85 | |
| 86 | Create `packages/slidev/node/commands/export.test.ts` covering: |
| 87 | - **`getExportOptions`**: given representative `args` + a fake `ResolvedSlidevOptions` |
| 88 | (minimal `data.config` + `data.slides`), assert the resolved `format`, `output` |
| 89 | (default `${basename(entry,'.md')}-export`), `width`/`height` from |
| 90 | `canvasWidth`/`aspectRatio`, `withClicks` default for `pptx`, `range`, and |
| 91 | `scale` defaults. Snapshot the returned object. |
| 92 | - **`makeOutline`** (and `addToTree`/`buildTocTree`): build a small tree from a |
| 93 | handful of titled slides at mixed levels and snapshot the outline string |
| 94 | (`path|--|title` lines), including a nested case. |
| 95 | - **range interplay**: assert `parseRangeString(total, range)` (imported from |
| 96 | `@slidev/parser`) selects the expected pages for a couple of inputs the exporter |
| 97 | relies on (e.g. `'2-3'`, `undefined`). |
| 98 | |
| 99 | Keep fixtures inline/synthetic — no real rendering, no fs writes. |
| 100 | |
| 101 | **Verify**: `pnpm build && pnpm test -- export` → all new tests pass. |
| 102 | |
| 103 | ### Step 3 (optional, local-only): a Playwright smoke |
| 104 | |
| 105 | Only if the operator wants render coverage and `playwright-chromium` is available: |
| 106 | add a slow/opt-in test (or a `cypress`/script harness) that builds the demo, |
| 107 | serves it, runs `exportSlides` for a 2-slide deck to each format, and asserts the |
| 108 | output file exists with a plausible page count/size. Gate it so it does not run |
| 109 | in the default `pnpm test` (e.g. behind an env flag) unless CI is set up for |
| 110 | Playwright. **STOP and ask** before adding a browser dependency to the default CI. |
| 111 | |
| 112 | ## Test plan |
| 113 | |
| 114 | - New `export.test.ts` pins `getExportOptions`, outline building, and range |
| 115 | selection — the regressions most likely to slip through today. |
| 116 | - Optional Playwright smoke (Step 3) is the only true end-to-end; keep it opt-in. |
| 117 | |
| 118 | ## Done criteria |
| 119 | |
| 120 | - [ ] `packages/slidev/node/commands/export.test.ts` exists and passes |
| 121 | - [ ] `getExportOptions`, outline building, and range selection are covered |
| 122 | - [ ] No behavior change to `export.ts` beyond adding `export` keywords |
| 123 | - [ ] Default `pnpm test` does not require Playwright/a browser |
| 124 | - [ ] `pnpm build && pnpm typecheck` exit 0 |
| 125 | - [ ] Only in-scope files modified (`git status`) |
| 126 | - [ ] `plans/README.md` status row updated |
| 127 | |
| 128 | ## STOP conditions |
| 129 | |
| 130 | Stop and report if: |
| 131 | |
| 132 | - Making a helper importable would require broad refactoring (it shouldn't — just |
| 133 | an `export` keyword) — that means the code has drifted; report. |
| 134 | - The operator has not approved adding Playwright to CI (keep Step 3 local/opt-in). |
| 135 | |
| 136 | ## Maintenance notes |
| 137 | |
| 138 | - These are characterization tests: if they fail after an intentional change, |
| 139 | update the snapshot deliberately, not reflexively. |
| 140 | - This suite is the safety net plan 023 relies on before decomposing `exportSlides`. |
| 141 | - Reviewer: confirm the fake `ResolvedSlidevOptions` in tests stays minimal and |
| 142 | doesn't couple tests to unrelated config. |
| 143 |