-
Notifications
You must be signed in to change notification settings - Fork 2.2k
chore(test): vitest migration - exploration phase #9969
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: chr/tape-to-vitest
Are you sure you want to change the base?
Conversation
- Add vitest.config.ts and vitest.workspace.ts for multi-environment testing - Add test/setup/ with node and browser setup files - Setup files include typed array equality tester for comparing Float32Array with plain arrays - Update package.json with vitest dependencies - Update .gitignore for vitest cache Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- Add idempotent migration script that reads tape source from master branch - Script converts tape assertions to vitest equivalents while preserving assertion messages - Update test-utils to work with vitest - Update RFC with migration approach details The migration script handles: - Import statement conversion (tape-catch/tape-promise → vitest) - Assertion conversions (t.ok → toBeTruthy, t.equal → toBe, etc.) - Preserves assertion messages using vitest's expect(value, 'message') syntax - Handles edge cases like t.pass/t.fail in arrow functions - Properly converts t.true/t.false to toBeTruthy/toBeFalsy (tape semantics) Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Convert 188 test files from tape to vitest using the migration script. Changes include: - Replace tape imports with vitest (test, expect, describe) - Convert tape assertions to vitest equivalents - Remove tape test function wrappers - Preserve assertion messages for better debugging Test status after conversion: - 467 test files passed (1740 tests) - 91 test files still need attention (106 tests) Remaining failures are primarily: - Tests for removed components (GPUGridLayer) - Environment-specific issues (Node vs browser WebGL) - Tests requiring additional migration work Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Mark the custom typed array equality tester as temporary. Once all tests are updated to use explicit typed array comparisons (e.g., expect(Array.from(typedArray)).toEqual([...])), this custom tester should be removed to enforce stricter type checking in tests. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
| 'Throws on invalid Kepler schema version' | ||
| ); | ||
| t.end(); | ||
| expect(() => parseMap(json), /Only support Kepler v1/).toThrow(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regex passed to expect() instead of toThrow()
Medium Severity
The tape t.throws(fn, /regex/, 'message') conversion is incorrect. The regex /Only support Kepler v1/ that should match the thrown error message is passed as the second argument to expect() (which is the custom failure message), not to .toThrow(). The correct vitest syntax is expect(fn).toThrow(/regex/). This test will pass if ANY error is thrown, rather than verifying the specific error message.
GPUGridLayer was removed from the codebase. This test file references internal paths that no longer exist. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Vitest's MockInstance doesn't have a `.called` property like sinon spies. Updated the migration script to convert to idiomatic vitest assertions: - expect(spy.called).toBeTruthy() → expect(spy).toHaveBeenCalled() - expect(spy.called).toBeFalsy() → expect(spy).not.toHaveBeenCalled() Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
78fb7c1 to
3b187e2
Compare
The @vitest/browser package depends on msw which depends on [email protected] that requires Node 20+. Since deck.gl supports Node 14+ at runtime but vitest is only needed for development, we ignore engine checks to allow installation on older Node versions. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Update migration script to only import describe when actually used, and run prettier after conversion for consistent formatting. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
The import/namespace rule cannot parse vite 5.x which is brought in by vitest (master uses vite 4.x from @vis.gl/dev-tools). Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
80eea14 to
47e1d61
Compare
Instead of removing t.pass() calls, convert them to console.log() to preserve the pass messages for debugging. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Update migration script to convert test.skip/test.only signatures from (t =>) to (() =>). Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Pass msg parameter to expect() so assertion messages are displayed on test failure: expect(cond, msg).toBeTruthy() Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Convert test('name', async t => expr) patterns to remove unused t
parameter: test('name', async () => expr)
Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
| test(`getTextAccessor#${textLabelField.type}`, () => { | ||
| const accessor = getTextAccessor(textLabelField, [data]); | ||
| t.deepEquals(accessor(data), expected, `getTextAccessor correctly returns ${expected}`); | ||
| t.end(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused test data array TEXT_PIXEL_OFFSET_TESTS
Low Severity
The TEXT_PIXEL_OFFSET_TESTS constant defines an array of test cases but is never used. Unlike the other test data arrays in the file (COLOR_TESTS, SIZE_TESTS, TEXT_TESTS) which all have corresponding for...of loops that generate tests, TEXT_PIXEL_OFFSET_TESTS has no such test loop. This dead code adds confusion and maintenance burden.
The CI was failing because Playwright browsers weren't installed and the test command was using a non-existent "ci" vitest project. This fix adds the Playwright installation step and uses the proper headless project with coverage enabled. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
| "@luma.gl/core": "~9.2.6", | ||
| "@luma.gl/engine": "~9.2.6", | ||
| "@probe.gl/test-utils": "^4.1.0" | ||
| "vitest": "^2.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're stuck on vitest 2 until we upgrade from node 18.
| "@deck.gl/core": "~9.2.0", | ||
| "@luma.gl/core": "~9.2.6", | ||
| "@luma.gl/engine": "~9.2.6", | ||
| "@probe.gl/test-utils": "^4.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RFC outlines the goal being to implement a backwards-compatibility layer and deprecation warnings in @deck.gl/test-utils. I'm not sure what users use it for, but gets a significant number of downloads still so we need to not break anything.
I'm thinking we'll need to keep some old test infrastructure around for this module to ensure correctness on both tape and vitest until deck v10
Temporarily, I've removed tape and probe.gl from deck's test utils until I get all tests to pass without the extra complexity.
package.json
Outdated
| "test": "ocular-test", | ||
| "test-fast": "ocular-lint && ocular-test node", | ||
| "test": "vitest run", | ||
| "test-node": "vitest run --project node", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to run all of the tests in the node environment resulted in a lot of failures (which is not a surprise), so I'm going to instead implement the same test config for node that we have on master, which is basically a smoke-test.
I'll keep test-fast but remove test-node
| "publish-beta": "ocular-publish version-only-beta", | ||
| "publish-prod": "ocular-publish version-only-prod", | ||
| "start": "open https://deck.gl/docs/get-started/getting-started", | ||
| "test": "ocular-test", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need ocular-test anymore. Vitest seems to offer everything you need to run tests.
Please let me know if ocular-test did something internally that we lose because we're removing it
| "jsdom": "^20.0.0", | ||
| "playwright": "^1.58.0", | ||
| "pre-commit": "^1.2.2", | ||
| "puppeteer": "^24.26.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hoping to remove puppeteer after all tests pass
| // import test from 'tape-promise/tape' -> import {test, expect, describe} from 'vitest' | ||
| // import test from 'tape-catch' -> import {test, expect, describe} from 'vitest' | ||
| // import test from 'tape' -> import {test, expect, describe} from 'vitest' | ||
| result = result.replace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using a string replace have you considered something like https://github.com/facebook/jscodeshift ?
That way you can operate on the syntax tree and it's a little less hairy than trying to use a regex to parse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is one-time-use and already working reliably, I don’t see a strong reason to change it right now - but I’m open to revisiting if this there's something claude can't figure out
The vitest glob patterns picked up tests that were commented out or never imported in the original test suite: - path-tesselator.spec.ts (commented out in layers/index.ts) - polygon-tesselation.spec.ts (commented out in layers/index.ts) - geocoders.spec.ts (never imported, no widgets index) Also fix floating point precision in geocoders test using toBeCloseTo. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
|
|
||
| // Remove spies | ||
| Object.keys(spyMap).forEach(k => spyMap[k].reset()); | ||
| Object.keys(spyMap).forEach(k => spyMap[k].mockClear()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spy cleanup uses mockClear instead of mockRestore
Medium Severity
The spy cleanup after each test case uses mockClear() instead of mockRestore(). In vitest, mockClear() only clears call history but leaves the spy attached to the prototype. When different test cases spy on different methods, old spies accumulate on Object.getPrototypeOf(layer). If a later test case calls vi.spyOn on an already-spied method, vitest wraps the existing spy with another spy layer, potentially causing nested spies, incorrect call counts, or performance degradation across test iterations.
Additional Locations (1)
- Rename node-only tests to *.node.spec.ts convention - Configure vitest workspace with three projects: - node: smoke tests (*.node.spec.ts only) - headless: unit tests in headless browser - browser: full suite in headed browser for local dev - Update package.json scripts: - test: node + headless + render (full suite) - test-fast: lint + node smoke tests - test-headless: browser unit tests only - test-render: render/interaction tests only - test-ci: node + headless + coverage + render - test-browser: headed browser + render - Update CI to use test-ci and install Puppeteer browsers - Comment out module tests in test/browser.ts so test-render only runs render and interaction tests Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- Add command mapping matrix (old → new) - Add command matrix showing what each script runs - Update file naming convention to *.node.spec.ts - Update vitest workspace config to match implementation - Document Phase 4 outcome: ~95% tests need browser environment - List node smoke tests and excluded tests Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- Update migration script to convert makeSpy from @probe.gl/test-utils to vi.spyOn from vitest - Re-run migration on affected test files to fix util.inherits error that occurred when @probe.gl/test-utils was loaded in browser - Fix jupyter-widget test that used t.describe() (converted to proper describe/test structure) - Add exclusions for unconverted render/interaction tests 8 test files converted from makeSpy to vi.spyOn: - attribute.spec.ts, layer.spec.ts, memoize.spec.ts - collision-filter-effect.spec.ts, terrain-effect.spec.ts - google-maps-overlay.spec.ts, json-converter.spec.ts - google-maps-utils.spec.ts Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Migration script now converts probe.gl spy methods to vitest: - spy.restore() -> spy.mockRestore() - spy.reset() -> spy.mockReset() Applies to all test files, not just those importing makeSpy directly (includes tests using @deck.gl/test-utils's testLayer spies). Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
The memoize test used spy.called with .toBe(boolean) which doesn't work with vitest spies. Converted to proper toHaveBeenCalled() / not.toHaveBeenCalled() matchers. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- Add hasNestedTTest flag captured BEFORE Step 1b converts t.test() to test()
- Add Step 1b to convert t.test()/t0.test() etc. early in the conversion pipeline
- Fix Step 2a to use hasNestedTTest flag instead of inline regex check on result
- This correctly converts: test('name', t => { t.test('nested', ...) })
to: describe('name', () => { test('nested', ...) })
Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- Add Step 14c to migration script for spy.called pattern conversion - Fix memoize.spec.ts to use manual call tracking instead of vi.spyOn (vi.spyOn has issues in vitest browser mode with call-through) - Note: geocoders.spec.ts toBeCloseTo fix was already in place Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Files with manual fixes that can't be expressed as general migration rules are now skipped by the migration script to prevent overwriting: - memoize.spec.ts: manual call tracking (vi.spyOn browser issues) - geocoders.spec.ts: toBeCloseTo for floating point DMS comparisons Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
| result = result.replace( | ||
| /expect\(([^,)]+)\.called\)\.toBeFalsy\(\)/g, | ||
| 'expect($1).not.toHaveBeenCalled()' | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate spy.called conversion logic in migration script
Low Severity
Step 8b (lines 310-328) and Step 14c (lines 433-470) both convert the same expect(spy.called).toBeTruthy() and expect(spy.called).toBeFalsy() patterns to toHaveBeenCalled(). Step 8b handles these patterns, then Step 14c re-handles them again with slightly different regex patterns. The redundant code could be consolidated into a single location. Step 14c adds .toBe(true) and .toBe(false) variants that Step 8b lacks, suggesting Step 8b could simply be removed entirely if Step 14c covers all cases.
Additional Locations (1)
Add Step 14d to migration script to convert probe.gl spy.callCount pattern to vitest's toHaveBeenCalledTimes matcher: - expect(spy.callCount).toBe(n) -> expect(spy).toHaveBeenCalledTimes(n) - expect(spy.callCount, 'msg').toBe(n) -> expect(spy, 'msg').toHaveBeenCalledTimes(n) Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
…and google-maps tests Re-ran migration script on files that were missed in previous conversion. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- path-layer-vertex.spec.ts (Transform not exported from @luma.gl/engine) - collision-filter.spec.ts (collision-filter extension test) Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Add custom browser commands for image comparison testing: - compareImage: pixel-level image comparison using pixelmatch - Type definitions for browser command interface Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Scaffold vitest specs for interaction and render tests: - map-controller.spec.ts: MapController interaction tests - picking.spec.ts: Picking interaction tests - render/index.spec.ts: Render test runner entry point Note: These are currently excluded in vitest.workspace.ts as they need further conversion work. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
- CONTRIBUTING.md: Add vitest test commands documentation - vitest-migration-rfc.md: Update with phase 5 outcomes and status Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>
Add dependencies for browser-based image comparison testing: - pixelmatch: pixel-level image comparison - pngjs: PNG encoding/decoding - sharp: image processing Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <[email protected]>


Summary
This PR implements the exploration phase of the tape-to-vitest migration as outlined in the RFC.
What's included:
Current test status:
Known issues requiring follow-up:
1. Deep import paths (prerequisite to migration)
Tests use internal deep imports that are not part of the public API:
These should be fixed in the tape tests on master first, then re-run the migration script.
2. Tests for removed components
gpu-grid-layer.spec.tstestsGPUGridLayerwhich was removed from the codebase3. Environment-specific failures
ReadableStreampolyfill issue in Node for some loader testsMigration approach
The migration script (
scripts/tape-to-vitest-migration.cjs) is idempotent:Typed array equality
Added a custom equality tester to match tape's
deepEqualbehavior for typed arrays. This is marked as TODO for removal once tests are updated to use explicit comparisons.Next steps (per RFC)
🤖 Generated with Claude Code
Note
Medium Risk
Touches the project-wide test runner/CI pipeline and introduces Playwright-based browser testing plus new image-processing deps, which may cause CI breakage or platform-specific flakiness despite minimal impact on runtime code.
Overview
Switches the repo’s primary test runner from tape/ocular to Vitest by adding a multi-project
vitest.workspace.ts(node smoke tests + Playwright-backed headless/headed browser suites) and updating package scripts (test,test-fast,test-headless,test-browser,test-ci) to use it.Mass-converts module tests to Vitest syntax (replacing
tape-*imports/assertions withvitest’stest/expect) and adds new Vitest-based interaction test entrypoints undertest/interaction/.Updates
@deck.gl/test-utilsfor Vitest compatibility, replacing@probe.gl/test-utilsspies withvi.spyOn/mock APIs, adding browser driver type declarations, and adjusting interaction runner event handling.CI/dev plumbing: GitHub Actions now installs Playwright Chromium and Puppeteer Chrome before running
yarn test-ci; new deps are added for Vitest browser mode and screenshot diffing (@vitest/browser,playwright,pixelmatch,sharp, etc.),.gitignoreignores screenshot outputs, and.yarnrcis added to ignore engine constraints.Written by Cursor Bugbot for commit a4ffad0. This will update automatically on new commits. Configure here.