Skip to content
Closed

wip #12835

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
a410426
chore: add vitest and testing-library dependencies
midleman Apr 3, 2026
e3d8125
chore: add vitest and tsconfig configuration
midleman Apr 3, 2026
3dc349a
feat: add Vitest test infrastructure (setup, builder, script)
midleman Apr 3, 2026
2ba205e
test: migrate positronVersions to Vitest (Tier 0 POC)
midleman Apr 3, 2026
ad8e7db
test: migrate Tier 0 Positron tests to Vitest (19 files)
midleman Apr 3, 2026
9e51322
test: migrate Tier 0 Positron tests to Vitest (19 files)
midleman Apr 3, 2026
59defb2
test: migrate Tier 1 Positron tests to Vitest
midleman Apr 3, 2026
16e80d5
test: remove original .test.ts files migrated to Vitest
midleman Apr 3, 2026
a3135f4
test: migrate Tier 2 and Tier 3 Positron tests to Vitest (20 files)
midleman Apr 4, 2026
b827779
test: migrate React/DOM Positron tests to Vitest
midleman Apr 4, 2026
94c0d0d
ci: add Vitest step to unit test workflow and update docs
midleman Apr 4, 2026
75204e6
fix: resolve unhandled errors in activeRuntimeNotebookContextManager …
midleman Apr 4, 2026
ff45d1a
docs: add Vitest migration proposal, design spec, and implementation …
midleman Apr 4, 2026
ce0209e
docs: add Vitest tier guide to CLAUDE.md
midleman Apr 4, 2026
1198de0
docs: add test discovery gap finding to proposal and spec
midleman Apr 4, 2026
999cd11
docs: soften test discovery framing -- script is fixable, not a blocker
midleman Apr 4, 2026
793825a
docs: add upstream test divergence data to design spec
midleman Apr 4, 2026
fb9f8b4
docs: remove vitest-proposal.md in favor of design spec
midleman Apr 4, 2026
fa753d4
docs: remove completed implementation plan
midleman Apr 4, 2026
03883c5
fix: exclude .vitest.ts files from main tsconfig compilation
midleman Apr 4, 2026
3fae477
docs: add testing pyramid strategy to design spec
midleman Apr 4, 2026
2ab055b
docs: add next steps to spec and decision tree to CLAUDE.md
midleman Apr 4, 2026
1c8fe06
docs: mark CLAUDE.md decision tree as done in spec next steps
midleman Apr 4, 2026
8c798f9
docs: update success criteria to 10 minutes (was 30)
midleman Apr 4, 2026
cccb9e4
docs: simplify success criteria wording
midleman Apr 4, 2026
c9b207b
chore: extend vitest config to discover extension tests
midleman Apr 4, 2026
168dd5c
test: extract positron-assistant pure logic tests to Vitest (3 files)
midleman Apr 4, 2026
e27c728
test: extract positron-r pure logic tests to Vitest (2 files)
midleman Apr 4, 2026
01b44d8
docs: update spec with extension test extraction results
midleman Apr 4, 2026
8ce02d5
test: extract remaining positron-assistant tests to Vitest (3 files)
midleman Apr 4, 2026
4261dba
docs: update spec -- all 8 extension tests migrated, audit complete
midleman Apr 4, 2026
40c678e
docs: add CLAUDE.md testing guides for 3 Positron extensions
midleman Apr 4, 2026
ee2bf64
docs: mark extension test documentation as done in spec
midleman Apr 4, 2026
3eef145
fix: exclude .vitest.ts from extension tsconfigs
midleman Apr 4, 2026
a26858d
fix: reduce layoutManager test size to avoid CI timeout
midleman Apr 4, 2026
a1a0e66
fix: increase layoutManager test timeout to 180s instead of removing …
midleman Apr 4, 2026
919d4c7
fix: exclude .vitest.ts from gulp compile, transpile, and watch tasks
midleman Apr 4, 2026
cbbf957
fix: use 'any' instead of 'unknown' for service stub impl type
midleman Apr 4, 2026
a2fc8d8
docs: add before/after developer workflow comparison to spec
midleman Apr 4, 2026
099aaf1
docs: fix inaccurate CI timing claims in spec
midleman Apr 4, 2026
0659df7
docs: add 'when you need both' section to developer workflow
midleman Apr 4, 2026
65076d6
docs: add coverage gaps analysis to spec
midleman Apr 4, 2026
2c06631
docs: complete coverage gap audit for all 10 untested modules
midleman Apr 4, 2026
ccb1d46
docs: add incremental mocking guide to CLAUDE.md
midleman Apr 4, 2026
17a6682
docs: reorganize CLAUDE.md testing section
midleman Apr 4, 2026
d28f374
docs: use accurate boilerplate numbers in spec
midleman Apr 6, 2026
206a850
docs: add /write-tests slash command design spec
midleman Apr 6, 2026
a925e59
docs: allow /write-tests to extend existing test files
midleman Apr 6, 2026
c56c9c5
feat: add /write-tests slash command
midleman Apr 6, 2026
ef78324
feat: support PR numbers and URLs in /write-tests command
midleman Apr 6, 2026
5c7a6de
Merge remote-tracking branch 'origin/main' into mi/all-things-test
midleman Apr 6, 2026
fe08b30
fix: add vitest/globals type reference to all .vitest.ts files
midleman Apr 6, 2026
5b1a35b
test: add tests for PR #12593 session tracking changes
midleman Apr 6, 2026
7e26acc
chore: remove .superset/ from tracking and add to gitignore
midleman Apr 6, 2026
23438fe
docs: remove per-extension CLAUDE.md files
midleman Apr 6, 2026
0f0098e
fix: restore original copyright years in 20 migrated vitest files
midleman Apr 6, 2026
26253f2
fix: include .vitest.tsx in extension patterns (roborev findings)
midleman Apr 6, 2026
739ed9c
ci: rename unit test steps for clarity
midleman Apr 6, 2026
b5aeb17
docs: be honest about build daemon friction in workflow comparison
midleman Apr 6, 2026
806abe9
chore: rename /write-tests to /unit-tests-author
midleman Apr 6, 2026
8ecd40b
docs: update references from /write-tests to /unit-tests-author
midleman Apr 6, 2026
bdecaa8
docs: simplify testing pyramid -- frame by runtime, not taxonomy
midleman Apr 6, 2026
196acde
docs: align CLAUDE.md with simplified pyramid framing
midleman Apr 6, 2026
fe46102
fix: increase ANSI 256 matrix test timeout for CI
midleman Apr 6, 2026
1d1141d
fix: add timeout to ANSI RGB matrix test (also 256x256 iterations)
midleman Apr 6, 2026
1fa9838
docs: simplify tiers from 4 to 3 (Bare, Runtime, Workbench)
midleman Apr 6, 2026
f6616eb
feat: add .withNotebookServices() tier to test builder
midleman Apr 6, 2026
66e8d74
fix: make /unit-tests-author read presets from builder file
midleman Apr 6, 2026
0feb76b
docs: standardize on 'presets' terminology in CLAUDE.md
midleman Apr 6, 2026
115ec5b
docs: remove preset definitions from CLAUDE.md, point to source
midleman Apr 6, 2026
cd536d2
docs: replace all tier references with preset in /unit-tests-author
midleman Apr 6, 2026
34bb01c
fix: deduplicate vitest/globals references across 33 files
midleman Apr 6, 2026
e5c0edb
chore: remove docs/superpowers from tracking, keep locally
midleman Apr 6, 2026
8a2dd46
docs: remove stale reference to untracked spec in CLAUDE.md
midleman Apr 6, 2026
3786d27
docs: restore build daemon instructions for upstream tests
midleman Apr 6, 2026
cb96780
docs: clarify positron-python test exception in CLAUDE.md
midleman Apr 6, 2026
8514453
Add snapshot and React Testing Library tests for ColumnSummaryCell
dhruvisompura Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions .claude/commands/unit-tests-author.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Analyze branch changes and write Vitest tests for Positron code

You are a testing assistant for the Positron IDE (a VS Code fork). Your job is to analyze the dev's branch, recommend which tests to write, and then write them after confirmation.

## Arguments

$ARGUMENTS may contain:
- `--branch <branch-name>` to analyze a specific branch instead of the current one
- A PR number (e.g., `#12242` or `12242`) to analyze a pull request
- A PR URL (e.g., `https://github.com/posit-dev/positron/pull/12242`)

## Phase 1: Analysis

### Step 1: Get the diff

If a PR number or URL was provided:
```bash
gh pr diff <number> --name-only
```
Use `gh pr diff <number>` (not `--patch`) for the full diff content when analyzing changes.

If `--branch` was provided:
```bash
git fetch origin <branch-name> 2>/dev/null
git diff main...origin/<branch-name> --name-only --diff-filter=ACMR
```
If fetch fails (branch not found), try without the `origin/` prefix for local branches.

Otherwise (current branch):
```bash
git diff main...HEAD --name-only --diff-filter=ACMR
```

### Step 2: Read the testing guide

Read the Testing section of `CLAUDE.md` for the decision tree and mocking guide. Read `src/vs/workbench/test/browser/positronTestContainer.ts` for available presets. These are your references for all recommendations.

### Step 3: Classify each changed file

For each file in the diff, determine:

**Skip these entirely (don't mention them):**
- Test files (`*.test.ts`, `*.vitest.ts`, `*.test.tsx`, `*.vitest.tsx`)
- Type definitions (`*.d.ts`, `interfaces/*.ts` that only contain interfaces)
- Generated files, configs, docs, build scripts
- Files with no testable logic (pure re-exports, registration wiring like `*.contribution.ts` that only calls `registerAction`)

**For Positron source files** (Posit Software copyright in `src/vs/` or `extensions/positron-*/`):

1. Check if a `.vitest.ts` test already exists for this file
2. Read the file's imports and constructor to determine the preset:
- Read `src/vs/workbench/test/browser/positronTestContainer.ts` to see the available presets (e.g., `withRuntimeServices()`, `withNotebookServices()`, `withWorkbenchServices()`). Match the source file's dependencies to the lowest preset that covers them.
- If no preset is needed (pure functions, no `@IServiceId` decorators), use a bare `createTestContainer().build()`.
- If dependencies don't fit a preset cleanly, use the closest preset + `.stub()` for the extras.
3. For `.tsx` files: check if it's a presentational component (bare), a component with service context (use a preset), or tightly coupled to VS Code editor lifecycle (recommend E2E instead).
4. For extension files that import `vscode` or `positron`: check if the import is for types/enums only (can still be Vitest with stubs) or genuinely needs extension host APIs (recommend `npm run test-extension` instead).

**For upstream VS Code files** (Microsoft copyright):
- Flag with a warning and provide the command to run existing Mocha tests:
```
./scripts/test.sh --run <path-to-existing-test>
```

### Step 4: Present the test plan

Show the dev a clear summary grouped by action:

**Tests to write** -- files with no existing test or where changes need new test cases. Include:
- The file path
- The recommended preset and WHY (which dependencies led to this choice)
- What to test (public methods, events, state changes visible from the diff)

**Tests to extend** -- files that already have a `.vitest.ts` but the diff introduces new behavior not covered. Include:
- The existing test file path
- What new test cases are needed based on the diff

**Already covered** -- files with existing tests that cover the changes. Just list them briefly.

**Upstream warnings** -- any modified upstream files with the Mocha test command.

Then ask: **"Want me to write/extend these tests?"**

Wait for the dev's response. They may approve all, approve some, or ask questions. Do not proceed to Phase 2 until they confirm.

## Phase 2: Writing

For each approved item:

1. **Read the source file** to understand the public API, events, and behavior.

2. **Read existing tests in the same directory** for pattern consistency.

3. **Write the test** following the preset pattern from `positronTestContainer.ts`:
- If bare (no services): just import and assert. No builder needed.
- Otherwise: read `src/vs/workbench/test/browser/positronTestContainer.ts` for available presets, use the lowest one that fits.
- Use incremental mocking: start with the preset, add `.stub()` only if the test fails.
- Use `// @vitest-environment node` if the test uses sinon's fetch stubs.
- Use tabs for indentation.
- Add the Posit Software copyright header.
- File name: `<source-name>.vitest.ts` (or `.vitest.tsx` if it contains JSX).

4. **Run the test:**
```bash
npx vitest run <path-to-test-file>
```

5. **Show the dev the results.** If tests fail, diagnose and fix. If they pass, show the output and ask: "Looks good? Any adjustments?"

6. **Move to the next file** after the dev confirms.

7. **After all tests are written**, run the full suite:
```bash
npx vitest run
```
Report the total: "X files, Y tests passing, no regressions."

## Key Rules

- **Show your reasoning.** Don't just say "Runtime" -- say "Runtime because this service depends on IRuntimeSessionService, which is covered by `.withRuntimeServices()`." This teaches the dev the preset system.
- **Don't over-test.** Focus on public behavior, not implementation details. Test what the code DOES, not how it does it.
- **Don't over-mock.** Start with the preset. Add stubs incrementally only when tests fail.
- **Don't write E2E tests.** This command is for Vitest unit/service tests only. If something needs E2E coverage, say so but don't write it.
- **Don't modify upstream VS Code tests.** Warn about upstream changes and provide the Mocha test command.
- **Don't auto-commit.** The dev reviews and commits when ready.
6 changes: 3 additions & 3 deletions .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
- name: Compile Integration Tests
run: npm run --prefix test/integration/browser compile

- name: 🧪 Run Integration Tests (Electron)
- name: 🧪 Run Extension Host Tests (Electron)
id: electron-integration-tests
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
Expand All @@ -170,14 +170,14 @@ jobs:
DISPLAY=:10 ./scripts/test-integration.sh
fi

- name: 🧪 Run Integration Tests (Remote)
- name: 🧪 Run Extension Host Tests (Remote)
if: ${{ job.status != 'cancelled' && (success() || failure()) }}
id: electron-remote-integration-tests
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
run: DISPLAY=:10 ./scripts/test-remote-integration.sh

- name: 🧪 Run Integration Tests (Browser, Chromium)
- name: 🧪 Run Extension Host Tests (Chromium)
if: ${{ job.status != 'cancelled' && (success() || failure()) }}
id: browser-integration-tests
env:
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ jobs:
find . -type f -o -type l | grep -v "node_modules/" | sort > /tmp/files-after-npm-install.txt
.github/cache-scripts/check-uncached-artifacts.sh /tmp/files-before-npm-install.txt /tmp/files-after-npm-install.txt

- name: 🧪 Run Positron Tests (Vitest)
id: vitest-tests
run: npm run test-vitest:run

- name: Compile Positron and Download Electron
run: npm exec -- npm-run-all --max-old-space-size=8192 -p compile "electron x64"

Expand Down Expand Up @@ -197,17 +201,17 @@ jobs:
/opt/R
key: ${{ runner.os }}-r-4.5.2-${{ steps.download-r-desc.outputs.description-hash }}

- name: 🧪 Run Unit Tests (Electron)
- name: 🧪 Run VS Code Tests (Electron)
id: electron-unit-tests
run: DISPLAY=:10 ./scripts/test.sh

- name: 🧪 Run Unit Tests (node.js)
- name: 🧪 Run VS Code Tests (Node.js)
id: nodejs-unit-tests
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
run: npm run test-node

- name: 🧪 Run Unit Tests (Browser, Chromium)
- name: 🧪 Run VS Code Tests (Chromium)
id: browser-unit-tests
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ product.json.backup
*.lic
# --- End Positron ---
test-output.json
.superset/
docs/superpowers/
91 changes: 85 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,94 @@ Positron forks VSCode. Minimize merge conflicts by isolating Positron code.

## Testing

- Ensure build daemons are running before testing
- Core tests (`src/**/*.ts`):
- `./scripts/test.sh`: run all tests
### Where should I put my test?

The deciding question: **does it need Electron?**

1. **Vitest** (`*.vitest.ts`) -- DEFAULT for Positron code. No Electron needed. Covers everything from pure functions to 124-service integration. If your code doesn't genuinely need `vscode`/`positron` APIs at runtime, it belongs here.
2. **Extension host** (`npm run test-extension`) -- Needs Electron. Only when your test requires activated extensions, workspace APIs, or editor document manipulation.
3. **E2E** (Playwright) -- Needs the full app. Only for user-visible workflows across multiple systems.

### Running tests

- **Positron Vitest tests** (`*.vitest.ts`, no build daemon needed):
- `npm run test-vitest`: watch mode (re-runs on save)
- `npm run test-vitest:run`: single run
- `npx vitest run src/path/to/<file>.vitest.ts`: run a specific file
- `npx vitest run --grep '<pattern>'`: run tests matching a pattern
- New Positron tests should use `.vitest.ts` extension -- see the preset guide below
- **Upstream VS Code tests** (`*.test.ts`, requires build daemons):
- Ensure build daemons are running first: `npm run build-start && npm run build-check`
- `./scripts/test.sh`: run all upstream tests
- `./scripts/test.sh --run src/path/to/<file>.test.ts`: run a specific file
- `./scripts/test.sh --run src/path/to/<file>.test.ts --grep '<pattern>'`: run specific tests in a file
- `./scripts/test.sh --runGlob <glob>.test.js`: run files matching a glob (use `.js` extension with `--runGlob`)
- Extension tests (`extensions/<extension-name>/*.test.ts`, preferred for extension development except positron-python): `npm run test-extension -- -l <extension-name> --grep <pattern>`
- For positron-python, see that extension's CLAUDE.md
- E2E tests (for UI integration testing): `npx playwright test test/e2e/tests/<test-name>.test.ts --project e2e-electron --grep '<pattern>'`
- **Extension tests** (`extensions/<extension-name>/*.test.ts`): `npm run test-extension -- -l <extension-name> --grep <pattern>`
- positron-python has its own test setup -- see `extensions/positron-python/CLAUDE.md`
- **E2E tests** (full app, real browser): `npx playwright test test/e2e/tests/<test-name>.test.ts --project e2e-electron --grep '<pattern>'`

### Positron Vitest: The Builder

All Positron-specific tests use Vitest (`.vitest.ts`). The builder (`createTestContainer()`) provides presets for common service groupings. Pick the lowest one that works, and use `.stub()` to add or override individual services.

For pure logic tests (no services), skip the builder entirely -- just import and assert.

For available presets and examples, see `src/vs/workbench/test/browser/positronTestContainer.ts`.

**Key rules:**
- Any preset supports `.stub(IService, mock)` for adding or overriding individual services
- The builder result (`ctx`) uses lazy getters -- access `ctx.instantiationService` inside `beforeEach`/`it`, not at describe-level via destructuring
- `ctx.disposables` is auto-cleaned after each test -- pass it to helpers like `startTestLanguageRuntimeSession()` that need it
- Upstream VS Code tests stay on Mocha (`.test.ts`) -- only Positron tests use Vitest

### How to mock (the incremental approach)

You don't need to understand all 124 services. You build mocks incrementally -- start with nothing and let the test tell you what's missing.

**Step 1: Start with a preset and run the test.**

```typescript
const ctx = createTestContainer().withRuntimeServices().build();
```

If the test passes, you're done. Most dependencies are already handled.

**Step 2: If it fails with "X is not a function" or "Cannot read properties of undefined", a service is missing.**

Add an empty stub for just that service:

```typescript
const ctx = createTestContainer()
.withRuntimeServices()
.stub(IMissingService, {} as IMissingService)
.build();
```

Run again. If it passes, you're done. The empty stub works when the code has the dependency but your test path doesn't call it.

**Step 3: If it still fails because the code calls a specific method, add just that method.**

```typescript
.stub(IMissingService, {
getDoc: () => undefined,
} as IMissingService)
```

**Step 4: If the code listens to an event, add an Emitter.**

```typescript
import { Emitter } from '<path>/base/common/event.js';

const onDidChange = new Emitter<void>();
.stub(IMissingService, {
getDoc: () => undefined,
onDidChange: onDidChange.event,
} as IMissingService)
```

Now your test can trigger the event with `onDidChange.fire()` to test reactive behavior.

**The pattern: empty -> add methods -> add events.** You never mock more than what the test actually needs. If you're writing 20 lines of mock setup, you're probably over-mocking -- step back and check if a higher preset already covers it.

## Directory Structure

Expand Down
19 changes: 15 additions & 4 deletions build/lib/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ export function transpileTask(src: string, out: string, esbuild?: boolean): task
const task = () => {

const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild: !!esbuild }, preserveEnglish: false });
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
// --- Start Positron ---
// Exclude .vitest.ts/.vitest.tsx files -- they use Vitest globals (describe/it/expect)
// and have their own tsconfig (tsconfig.vitest.json). See docs/superpowers/specs/.
const srcPipe = gulp.src([`${src}/**`, `!${src}/**/*.vitest.ts`, `!${src}/**/*.vitest.tsx`], { base: `${src}` });
// --- End Positron ---

return srcPipe
.pipe(transpile())
Expand All @@ -126,7 +130,11 @@ export function compileTask(src: string, out: string, build: boolean, options: {
}

const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish });
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
// --- Start Positron ---
// Exclude .vitest.ts/.vitest.tsx files -- they use Vitest globals (describe/it/expect)
// and have their own tsconfig (tsconfig.vitest.json). See docs/superpowers/specs/.
const srcPipe = gulp.src([`${src}/**`, `!${src}/**/*.vitest.ts`, `!${src}/**/*.vitest.tsx`], { base: `${src}` });
// --- End Positron ---
const generator = new MonacoGenerator(false);
if (src === 'src') {
generator.execute();
Expand Down Expand Up @@ -171,8 +179,11 @@ export function watchTask(out: string, build: boolean, srcPath: string = 'src'):
const task = () => {
const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false });

const src = gulp.src(`${srcPath}/**`, { base: srcPath });
const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 });
// --- Start Positron ---
// Exclude .vitest.ts/.vitest.tsx files from compilation and watch.
const src = gulp.src([`${srcPath}/**`, `!${srcPath}/**/*.vitest.ts`, `!${srcPath}/**/*.vitest.tsx`], { base: srcPath });
const watchSrc = watch([`${srcPath}/**`, `!${srcPath}/**/*.vitest.ts`, `!${srcPath}/**/*.vitest.tsx`], { base: srcPath, readDelay: 200 });
// --- End Positron ---

const generator = new MonacoGenerator(true);
generator.execute();
Expand Down
Loading
Loading