Skip to content

feat(testing): publish shared test helpers + fixtures via ./testing#49

Merged
scottlovegrove merged 4 commits into
mainfrom
feat/testing-helpers-subpath
May 24, 2026
Merged

feat(testing): publish shared test helpers + fixtures via ./testing#49
scottlovegrove merged 4 commits into
mainfrom
feat/testing-helpers-subpath

Conversation

@scottlovegrove
Copy link
Copy Markdown
Collaborator

@scottlovegrove scottlovegrove commented May 24, 2026

Summary

Promote the genuinely-shared testing scaffolding into the published @doist/cli-core/testing subpath so the consuming CLIs (outline-cli, todoist-cli, twist-cli) stop reinventing it.

New ./testing exports:

  • createTestProgram(register) — Commander harness with exitOverride(). Was byte-identical in todoist-cli/twist-cli and inline in outline-cli.
  • captureConsole(method?) / captureStream(stream?) — silence + spy on console / process.stdout|stderr.write, auto-restoring via onTestFinished. Returns the spy so .mock.calls assertions keep working.
  • buildTokenStore / buildSingleEntryStore + Ingen account fixtures (alanGrant / ellieSattler / ianMalcolm) + TestAccount / StoreEntry / TokenStoreHarness / MatchAccount types — moved out of the build-excluded src/test-support/.

buildTokenStore gains an optional matchAccount so consumers whose stores resolve refs differently (twist's numeric-id / case-insensitive matcher, outline's) can mirror production semantics. Defaults to the existing id/email/label rule, so cli-core's own suites are unchanged.

Mechanics

  • src/testing.tssrc/testing/ directory (index barrel + program / console / accounts / empty-output). accounts.ts moved from test-support/.
  • package.json ./testing export now points at dist/testing/index.{js,d.ts}.
  • Converged cli-core's own auth suites onto the canonical helpers: deleted installConsoleLogSpy / installStdoutSpy; buildProgram now wraps createTestProgram; the 4 attacher suites moved from the describe-top-level getter pattern to beforeEach (required because onTestFinished can't run at describe top-level).
  • README "What's in it" testing row + a new usage block updated in the same commit (per AGENTS.md).

Safety

The ./testing module imports vitest, but it's only ever imported by test files. All three consumers build with plain tsc (no bundler) and keep vitest as a devDep, so prod code never pulls /testing into its runtime import graph. This extends the pattern describeEmptyMachineOutput already used.

Test plan

  • npm run builddist/testing/ contains index/program/console/accounts/empty-output
  • npm run type-check
  • npm run check (oxlint + oxfmt)
  • npm test — 499 tests pass, incl. the new accounts.test.ts proving a no-email account compiles + matchAccount override works, and the relocated subpath wiring test

🤖 Generated with Claude Code

Promote the genuinely-shared testing scaffolding into the published
`@doist/cli-core/testing` subpath so consuming CLIs stop reinventing it:

- `createTestProgram(register)` — Commander harness (was byte-identical in
  todoist-cli/twist-cli and inline in outline-cli)
- `captureConsole(method?)` / `captureStream(stream?)` — silencing spies with
  onTestFinished auto-restore
- `buildTokenStore` / `buildSingleEntryStore` + Ingen account fixtures +
  TestAccount/StoreEntry/TokenStoreHarness/MatchAccount types, moved out of the
  build-excluded test-support/

buildTokenStore gains an optional `matchAccount` so consumers whose stores match
refs differently (twist's numeric-id/case-insensitive matcher, outline's) can
mirror production semantics; it defaults to the existing id/email/label rule.

Convert src/testing.ts into a src/testing/ directory and point the ./testing
export at dist/testing/index. Converge cli-core's own auth suites onto the
canonical helpers (drop installConsoleLogSpy/installStdoutSpy; buildProgram now
wraps createTestProgram).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR successfully promotes valuable testing scaffolding into a published @doist/cli-core/testing subpath to eliminate duplication across the consuming CLIs. Extracting these shared utilities and converging the internal auth suites significantly standardizes our testing environment. A few adjustments are needed to address an implicit commander dependency in the barrel file, refine the captureStream mock to properly handle trailing callbacks, and fix a minor type error in the README example. Additionally, some test coverage gaps need to be filled for the new subpath exports, the custom account matcher, and the migrated modules themselves, alongside a bit of internal boilerplate deduplication.

Share FeedbackReview Logs

Comment thread src/testing/index.ts
Comment thread README.md Outdated
Comment thread src/auth/account.test.ts Outdated
Comment thread src/testing/accounts.ts Outdated
Comment thread src/test-support/cli-harness.ts
Comment thread src/testing/console.ts Outdated
Comment thread src/testing/console.ts
Comment thread src/testing/console.ts Outdated
Comment thread src/testing/index.ts
Comment thread src/testing/accounts.test.ts
- captureStream now detects a trailing write callback and invokes it, matching
  the WriteStream.write contract so write(chunk, cb) / write(chunk, enc, cb)
  paths don't hang
- dedupe the spy lifecycle behind a private captureSpy helper
- reintroduce internal installCapturedConsole/installCapturedStream wrappers in
  cli-harness (mirroring buildProgram over createTestProgram) so the attacher
  suites declare a spy once per describe instead of repeating let+beforeEach
- setDefault reuses the find(ref) helper instead of duplicating the lookup
- add colocated console.test.ts + program.test.ts (AGENTS.md module layout)
- pin the custom matchAccount on a mutating path (setDefault) in accounts.test
- extend the subpath smoke test to assert every runtime export the barrel adds
- README: type-correct attachStatusCommand example; document that the whole
  ./testing entrypoint links commander (the barrel re-exports createTestProgram)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@scottlovegrove
Copy link
Copy Markdown
Collaborator Author

@doistbot /review

Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR successfully extracts our shared testing scaffolding into the published ./testing subpath, providing a centralized harness, console capturing, and account fixtures for consuming CLIs. Centralizing these canonical utilities is a great move that will significantly reduce boilerplate and duplication across our downstream tooling. A few refinements are noted for the new utilities, including queuing stream callbacks asynchronously, tightening Commander error assertions, narrowing the buildTokenStore override types to preserve contract safety, dynamically verifying exports in the smoke test, and cleaning up a brittle test alongside some redundant comments.

Share FeedbackReview Logs

Comment thread src/testing/console.ts Outdated
Comment thread src/testing/subpath.test.ts Outdated
Comment thread src/testing/accounts.ts
Comment thread src/testing/console.test.ts Outdated
Comment thread src/testing/program.test.ts Outdated
Comment thread src/test-support/cli-harness.ts Outdated
scottlovegrove and others added 2 commits May 24, 2026 11:49
- captureStream queues the trailing write callback on the microtask queue
  instead of invoking it inline, matching the async WriteStream.write contract
- narrow buildTokenStore overrides to a StoreOverrides type covering only the
  optional store members, so callers can't erase required methods and leave the
  returned store an invalid TokenStore
- subpath smoke test compares the source barrel's runtime keys against the dist
  module's instead of a hand-maintained name list
- drop the order-dependent console-restore test (passes trivially in isolation;
  onTestFinished is a vitest primitive)
- tighten the exitOverride test to assert the commander.unknownCommand code
- drop the restating JSDoc on the installCaptured* wrappers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…usable

`ReturnType<typeof vi.spyOn>` erases to a shape whose `.mock.calls` is `any`,
forcing consumers to annotate `.mock.calls.map((c) => ...)` callbacks. Typing
the spies as vitest's `MockInstance` restores `.mock.calls` typing so the
helpers are a true drop-in for hand-rolled console/stream spies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@scottlovegrove scottlovegrove merged commit 44b38e9 into main May 24, 2026
4 checks passed
@scottlovegrove scottlovegrove deleted the feat/testing-helpers-subpath branch May 24, 2026 11:06
doist-release-bot Bot added a commit that referenced this pull request May 24, 2026
## [0.24.0](v0.23.0...v0.24.0) (2026-05-24)

### Features

* **testing:** publish shared test helpers + fixtures via ./testing ([#49](#49)) ([44b38e9](44b38e9))
@doist-release-bot
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.24.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

scottlovegrove added a commit to Doist/outline-cli that referenced this pull request May 24, 2026
## Summary
- Replace bespoke inline test scaffolding with the published
`@doist/cli-core/testing` helpers, mirroring
[todoist-cli#361](Doist/todoist-cli#361) (per
[cli-core#49](Doist/cli-core#49)).
- `createTestProgram(register)` replaces inline `new Command();
exitOverride(); registerX(program)` harness blocks across `commands`,
`changelog-integration`, `empty-output`, and `auth-command` tests.
- `captureConsole(method?)` replaces inline `vi.spyOn(console, …)`
recorders and the local `captureLogs`/`captureStreams` helpers;
assertions now read the spy's `.mock.calls` via a small `lines()`
accessor.
- Bump `@doist/cli-core` 0.23.0 → 0.24.0, which ships these helpers. No
source/runtime files changed.

## Notes / deviations
- No `captureStream` usage — outline-cli has no
`process.stdout/stderr.write` spies (todoist adopted it via
`mockProcessStdout`).
- Skipped the `buildTokenStore`/account fixtures, same as todoist-cli —
outline's auth tests are module-level `vi.mock` interaction mocks, the
wrong shape for cli-core's stateful store fake.
- `captureConsole` takes a single method string
(`captureConsole('error')`), per the published 0.24 `.d.ts`.

## Test plan
- [x] `npm run type-check`
- [x] `npm run lint:check` — 0 warnings, 0 errors
- [x] `npm run format:check`
- [x] `npm test` — 188 tests pass (21 files)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants