# Corpus runner

Runs your suite once per .docx in a folder, each document on its own freshly seeded simulator, and prints a documents by tests matrix.

The documents that break an add-in are seldom the clean ones. `officejs-harness corpus` runs your suite once per `.docx` in a folder, each document on its own freshly seeded simulator, all at once. It prints a documents × tests matrix of passes, failures and syncs:

<!-- example: skip network -->
```bash
npx officejs-harness corpus ./fixtures -- npx vitest run
```

```
3 documents × 3 tests: 7 passed, 2 failed

  1  fixtures/01-plain-contract.docx
  2  fixtures/04-text-box.docx
  3  fixtures/05-one-paragraph.docx

test/documents.test.js              1    2    3
  reads every paragraph             ✓ 2  ✓ 2  ✓ 2
  walks to the second paragraph     ✓ 1  ✓ 1  ✗ 1
  search > finds the 30-day clause  ✓ 1  ✗ 1  ✓ 1
syncs                               4    4    4

✗ 2 fixtures/04-text-box.docx · test/documents.test.js · search > finds the 30-day clause
    batch  every batch answered without an error
    error  AssertionError: expected 0 to be greater than 0
    rerun  npx officejs-harness corpus fixtures/04-text-box.docx -- npx vitest run test/documents.test.js -t '^search finds the 30-day clause$'
    log    /tmp/officejs-corpus-k2Jd8Q/2-04-text-box.docx.log
✗ 3 fixtures/05-one-paragraph.docx · test/documents.test.js · walks to the second paragraph
    batch  sync 1 answered ItemNotFound at Paragraph.getNext: We couldn't find the item you requested. …
    error  RichApi.Error: We couldn't find the item you requested. …
    rerun  npx officejs-harness corpus fixtures/05-one-paragraph.docx -- npx vitest run test/documents.test.js -t '^walks to the second paragraph$'
    log    /tmp/officejs-corpus-k2Jd8Q/3-05-one-paragraph.docx.log
```

- **A simulator per document.** The runner starts one simulator per document it runs at once, with `rystic run word-twin -d --port 0`, and stops them when it ends. Before each document it resets its simulator and seeds the document. `--jobs N` sets how many run at once (one per CPU by default), and `--twins http://127.0.0.1:8080,http://127.0.0.1:8081` uses simulators you already run instead. The documents go only to those simulators, so they never leave your machine or runner.
- **Every test starts from the document.** In a corpus run, `office.reset()` resets the simulator and seeds that run's document, so a suite that resets in `beforeEach` runs unchanged. Your command also gets `RYSTIC_TWIN_URL` and `OFFICEJS_HARNESS_DOCX`, the document's path. Your test files share the document's simulator, so run them one file at a time (Vitest `fileParallelism: false`, Jest `--runInBand`).
- **Each test, by name.** For a `vitest` or `jest` command, or an `npm test` script that runs one, the runner adds a JSON reporter and reads each test's outcome from it. Any other command is one row, passed or failed by its exit status.
- **Syncs per cell.** A cell's number is that test's syncs on that document: the simulator's count between one reset and the next. So a test that takes 3 syncs on a clean contract and 40 on a converted PDF shows it. A test that doesn't reset gets no count of its own; the `syncs` row still totals the document.
- **A failure names its batch.** When a batch returned an error, the cell names its sync since the reset, Word's error code and the member that raised it. The `rerun` line runs that test, in its file, on that document alone.
- **Exit status.** The runner exits 0 when every test passed on every document and 1 otherwise, so CI fails on it. Ctrl-C stops the commands and the simulators, then exits 130. Each document's output, report and batch log stay in the folder it prints, or in `--out <dir>`.

### Documents to put in your corpus

Add-ins break on a document's structure. The simulator's document engine is checked against documents of these nine shapes, every one fictional, on Word for the web and on the simulator, so they are good shapes to collect for your own corpus:

| Document | Shape |
|---|---|
| PDF converted by Word's reflow | A framed letterhead, tabs and hard line breaks, a rebuilt table, a text box with a VML fallback, a section break |
| PDF converted by Acrobat's export | Positioned shapes, a text box anchored to the page, exact line spacing, a framed date, words split across runs |
| Template | Content controls at block and inline level (plain text, drop-down, check box), a repeating section of table rows, a control around one cell |
| Fee schedule | A row spanning three columns, a cell merged down two rows, a table nested in a cell, empty cells |
| Exhibit cover sheet | Text boxes as DrawingML with VML fallbacks, anchored, inline and inside a table cell |
| Memo | PAGE, NUMPAGES and DATE fields as `w:fldSimple` and `w:fldChar`, bookmarks, a footnote, external and internal hyperlinks |
| Filing checklist | Wingdings symbols, tab leaders, soft and no-break hyphens, line breaks, a page break, a no-break space, an emoji |
| Pleading paper | Its own styles, a two-level numbered list, line numbers, a caption table, a footnote |
| Redlined schedule | Two authors' tracked changes inside table cells and inside a content control, a comment on a cell's text |

On each document, the checks compare:

- `body.paragraphs` text, in order;
- every paragraph, reached with `getNext`;
- a `search` for a phrase in each place: a cell, a content control, a text box, a field result;
- a range inside a table cell, and its `paragraphs`;
- a tracked edit inside a cell and inside a content control, read back as the document after.

The engine is also held to Word's `getOoxml` of every paragraph, the body and each cell range on the same documents.

---

Source: https://www.rystic.ai/docs/simulators/word/features/corpus-runner · Markdown: https://www.rystic.ai/docs/simulators/word/features/corpus-runner.md
