Skip to content

chore(test): vitest migration#9969

Open
chrisgervang wants to merge 135 commits intomasterfrom
chr/vitest-setup
Open

chore(test): vitest migration#9969
chrisgervang wants to merge 135 commits intomasterfrom
chr/vitest-setup

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Jan 28, 2026

Summary

This PR implements the exploration phase of the tape-to-vitest migration as outlined in the RFC.

What's included:

  • Vitest configuration with multi-environment support (node, headless browser, render)
  • Idempotent migration script that converts tape tests to vitest
  • Test-utils updates for vitest compatibility
  • 188 test files converted from tape to vitest
  • Render tests converted to native vitest browser mode

Render Test Status

Current results: 162 passed, 2 failed, 9 skipped

Test Status Notes
arc-lnglat ❌ ~95-99% match Flaky subtle rendering diff
gridcell-lnglat ❌ ~98-99% match Flaky subtle rendering diff
icon-lnglat-rectangle passed Golden image regenerated
mvt-layer passed
mvt-layer-binary passed
post-process-effects passed Golden image regenerated
scenegraph-layer-frame-1/2/3 passed Fixed with shared Deck instance
scatterplot-transition-1/2/3/4 passed Fixed with shared Deck instance, tests 3/4 unskipped
terrain-extension-drape/offset skipped TerrainExtension loading timeout

Timeline/Transition Test Fix

Tests that manipulate timeline.setTime() in onAfterRender now use a shared Deck instance pattern via updateDeckForTest(). This keeps the animation loop running between callbacks, allowing timeline changes to trigger re-renders.

Test output configuration

  • All environments use TAP reporter - Consistent output format across CI and local development

Excluded/Skipped tests

Render tests (flaky):

  • arc-lnglat, gridcell-lnglat - Subtle rendering differences, needs investigation

Render tests (timeout):

  • terrain-extension-drape, terrain-extension-offset - TerrainExtension loading timeout

Interaction tests:

  • 6 keyboard controller tests, 1 picking test - Need investigation

luma.gl v9 API changes:

  • mask/*.spec.ts - uniforms API changed
  • path-layer-vertex.spec.ts - Transform not exported from @luma.gl/engine

Pre-existing issues (fix on master first):

  • attribute.spec.ts - data-column.ts overwrites user stride/offset

Needs investigation (async/timing issues):

  • tile-3d-layer.spec.ts, layer-extension.spec.ts, pick-layers.spec.ts
  • terrain-layer.spec.ts, mvt-layer.spec.ts

Migration approach

The migration script (scripts/tape-to-vitest-migration.cjs) is idempotent:

  • Reads original tape source from master branch
  • Converts to vitest syntax while preserving assertion messages
  • Can be re-run after fixing issues on master

Typed array equality

Added a custom equality tester to match tape's deepEqual behavior for typed arrays. This is marked as TODO for removal once tests are updated to use explicit comparisons.

Next steps (per RFC)

  1. Fix deep import paths in tape tests on master
  2. Delete obsolete test files (GPUGridLayer)
  3. Re-run migration script
  4. Address remaining environment-specific failures
  5. Move to implementation phase
  6. Investigate flaky arc-lnglat and gridcell-lnglat tests

🤖 Generated with Claude Code


Note

Medium Risk
Moderate risk because it rewires core CI/test commands to Vitest/Playwright and changes @deck.gl/test-utils exports/peer deps, which can break contributor workflows and downstream test consumers if assumptions differ.

Overview
Migrates the repo’s test workflow from ocular-test/tape to Vitest projects (node/headless/browser/render) and updates CI to run yarn test-ci, upload render diff artifacts on failure, and keep a tape-compat smoke test for backwards compatibility.

Updates @deck.gl/test-utils to support both runners by introducing @deck.gl/test-utils/vitest, making spy creation/reset injectable, and adding async cleanup to avoid luma.gl shader-error race unhandled rejections; also adds a new export map entry and optional vitest peer dependency.

Adds/updates migration tracking docs (new docs/TEST-STATUS.md, expanded RFC notes, CONTRIBUTING test commands), adjusts lint/ignore lists and gitignore for new configs/screenshots, and removes the old index.html browser test entry.

Written by Cursor Bugbot for commit 79bc238. This will update automatically on new commits. Configure here.

chrisgervang and others added 7 commits January 27, 2026 09:01
Proposes replacing tape (assertions) + ocular-test (runner) with vitest.
See dev-docs/RFCs/proposals/vitest-migration-rfc.md for full details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add vitest workspace configuration (node/browser/headless projects)
- Clarify entry points: browser as source of truth, node as smoke test
- Add Playwright rationale (required by vitest browser mode)
- Expand Phase 4 with discovery mechanism for browser-only tests
- Add detailed Phase 5 for snapshot/interaction test migration
- Add Verification section with test commands
- Update risks table with new considerations

Co-Authored-By: Claude <noreply@anthropic.com>
- Add 3-phase deprecation plan (9.3.x → 9.4.x → 10.0.0)
- Include migration guide for external consumers
- Resolve open question #3 about tape deprecation timeline

Co-Authored-By: Claude <noreply@anthropic.com>
- 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) <noreply@anthropic.com>
- 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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
The @vitest/browser package depends on msw which depends on type-fest@5.x
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
chrisgervang and others added 3 commits January 28, 2026 21:05
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
test(`getTextAccessor#${textLabelField.type}`, () => {
const accessor = getTextAccessor(textLabelField, [data]);
t.deepEquals(accessor(data), expected, `getTextAccessor correctly returns ${expected}`);
t.end();
Copy link

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.

Fix in Cursor Fix in Web

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) <noreply@anthropic.com>
"@luma.gl/core": "~9.2.6",
"@luma.gl/engine": "~9.2.6",
"@probe.gl/test-utils": "^4.1.0"
"vitest": "^2.1.0"
Copy link
Collaborator Author

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"
Copy link
Collaborator Author

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",
Copy link
Collaborator Author

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",
Copy link
Collaborator Author

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",
Copy link
Collaborator Author

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(
Copy link
Collaborator

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

Copy link
Collaborator Author

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) <noreply@anthropic.com>
- Add TEST-STATUS.md documenting all excluded, skipped, and failed tests
- Document codemod cosmetic issues and architecture concerns
- Document render test headed vs headless mode issues
- Update test and test-ci scripts to run all projects in single vitest command:
  node, headless, and render projects now run together

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

"test-render": "vitest run --project render --silent",
"test-ci": "vitest run --project node --project headless --project render --coverage --silent",
"test-browser": "vitest run --project browser --silent",
"test-tape-compat": "DECK_TEST_UTILS_USE_PROBE_GL=1 ocular-test tape-compat",
Copy link

Choose a reason for hiding this comment

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

Root devDependencies missing required build-time dependency for test-utils

Medium Severity

@probe.gl/test-utils was removed from root devDependencies, but tape.ts (which is compiled as part of @deck.gl/test-utils) still has a hard static import of makeSpy from it. The package relies on @probe.gl/test-utils being available as a transitive dependency (likely through @vis.gl/dev-tools), making the build fragile — if that transitive path ever changes, the @deck.gl/test-utils module will fail to compile.

Additional Locations (1)

Fix in Cursor Fix in Web

chrisgervang and others added 3 commits March 3, 2026 16:52
The jscodeshift transform now detects when t.ok/t.true/t.assert is called
with 3 arguments (value, expected, message) which is a common bug where
t.equal should have been used instead.

Before: t.ok(value, expected, 'message')
After:  expect(value, 'message').toBe(expected)

This fixes several test files that had this pattern:
- json-converter.spec.ts
- h3-layers.spec.ts (5 instances)
- point-cloud-layer.spec.ts

Also includes:
- Remove unreachable return statements after throw (label-utils.spec.ts)
- Restore original non-awaited testLayerAsync call (mvt-layer.spec.ts)
- Add scripts project to vitest.config.ts for codemod tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The mvt-layer.spec.ts tests were excluded due to timeout issues.
The previous commit fixed a bug where the regex script incorrectly
added `await` to a testLayerAsync call that was intentionally
non-awaited in the original tape tests.

With the fix, all 12 MVTLayer tests pass (~20s).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove mvt-layer.spec.ts from excluded tests (now passes)
- Add table showing actually skipped render test cases (2 terrain tests)
- Update summary statistics (17 excluded files, not 18)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

chrisgervang and others added 2 commits March 3, 2026 17:31
The default entry point imports from tape.ts which has a top-level
import of @probe.gl/test-utils. Marking it as optional would cause
runtime crashes for users importing from @deck.gl/test-utils.

Only vitest should be optional since it's only needed for the
./vitest sub-entry point.

This preserves backwards compatibility with 9.2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default entry point of @deck.gl/test-utils has a static import
from tape.ts that requires @probe.gl/test-utils. Previously this was
only available as a transitive dependency through @vis.gl/dev-tools,
making the build fragile.

Also updates the RFC to reflect the actual implementation:
- @probe.gl/test-utils is REQUIRED (not optional) because the default
  entry point needs it
- Only vitest is optional (for the ./vitest sub-entry)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

"peerDependenciesMeta": {
"vitest": {
"optional": true
}
Copy link

Choose a reason for hiding this comment

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

Missing @probe.gl/test-utils optional marker for vitest-only users

Low Severity

The @probe.gl/test-utils peer dependency is required (non-optional) even though the ./vitest sub-entry (vitest.ts) never imports from it. Users who exclusively import from @deck.gl/test-utils/vitest will receive unnecessary peer dependency warnings about @probe.gl/test-utils. While documented in the RFC, marking it as optional in peerDependenciesMeta alongside vitest would be more accurate — users of the main entry would still get a runtime error (with a clear stack trace) if it's missing, which is arguably better than a generic peer dep warning.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not an issue, dont bring it up again

chrisgervang and others added 2 commits March 3, 2026 18:24
runRenderTest now calls the extracted captureAndDiffScreenshot helper
instead of duplicating the screenshot capture and diff logic inline.

Both runRenderTest and updateDeckForTest now use the same helper,
ensuring consistent behavior and easier maintenance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The TODO about refactoring to use vitest's test.each() is no longer
needed - each test case is now a separate vitest test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Base automatically changed from chr/tape-to-vitest to master March 4, 2026 22:51
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.


// Remove spies
Object.keys(spyMap).forEach(k => spyMap[k].reset());
Object.keys(spyMap).forEach(k => restoreSpy(spyMap[k]));
Copy link

Choose a reason for hiding this comment

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

Spy cleanup changes from reset to restore

Medium Severity

The spy cleanup changed from calling spy.reset() (which clears call tracking but keeps the spy active) to restoreSpy() (which calls mockRestore()/restore(), fully removing the spy and restoring the original method). For probe.gl users going through the backward-compatible tape.ts path, this changes the contract: spies are now fully torn down between test cases instead of just having their tracking cleared. While spies are re-created via injectSpies each iteration, any test that relied on the old reset-only semantics (e.g., checking cumulative spy state across the cleanup boundary, or relying on the spy wrapper remaining on the prototype) will behave differently.

Additional Locations (1)

Fix in Cursor Fix in Web

- Add resetSpy parameter to testLayer/testLayerAsync for proper spy cleanup
  - vitest: defaults to mockRestore()
  - tape: defaults to reset() with deprecation warning
- Extract typed-array-equality.ts as shared module (DRY)
- Update vitest-browser-commands.d.ts to augment @vitest/browser/context
- Fix imports to use @vitest/browser/context instead of vitest/browser
- Update tape-compat smoke test to actually exercise testLayer
- Add vitest-entry.spec.ts smoke test for vitest entry point

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

/* global setTimeout */
import {expect} from 'vitest';
import {Deck, Layer} from '@deck.gl/core';
import {device} from '@deck.gl/test-utils';
Copy link

Choose a reason for hiding this comment

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

Utility file imports from wrong test-utils entry point

Medium Severity

The codemod fixture output for utility files imports device from @deck.gl/test-utils (the main entry point backed by tape.ts), while all test file fixtures correctly import from @deck.gl/test-utils/vitest. The main entry point unconditionally imports makeSpy from @probe.gl/test-utils, meaning any vitest-only user who hasn't installed @probe.gl/test-utils as a peer dependency will get a runtime import failure when the utility file loads. Since these fixtures define the expected codemod output, the codemod will propagate this inconsistency to all converted utility files.

Additional Locations (1)

Fix in Cursor Fix in Web

Revert accidental addition of unused CanvasContextProps and WebGLDevice imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

const defaultSpyFactory: SpyFactory = (obj, method) => vi.spyOn(obj, method as never);

/** Default reset for vitest spies - restores original implementation */
const defaultResetSpy: ResetSpy = spy => spy.mockRestore?.();
Copy link

Choose a reason for hiding this comment

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

Vitest mockRestore destroys spy before later test cases reuse it

Medium Severity

The defaultResetSpy calls mockRestore() which completely restores the original method and removes the spy. In lifecycle-test.ts, spies are injected per test case via injectSpies on the layer's prototype, so calling mockRestore() between test cases restores the prototype method. When the next test case re-spies the same prototype method, vi.spyOn wraps the original again — but if a test case's onAfterUpdate calls spy.mockRestore() directly (as seen in the converted HeatmapLayer test fixture output), the spy from injectSpies is destroyed mid-test-case. The probe.gl equivalent reset() only cleared call tracking. Using mockClear instead of mockRestore would preserve the spy while clearing call counts, matching the original reset() semantics.

Fix in Cursor Fix in Web

…tions

Add cleanupAfterLayerTestsAsync() that yields to the event loop before
destroying WebGL resources. This prevents "getProgramInfoLog" errors from
luma.gl's async shader error reporting trying to access already-destroyed
WebGLProgram handles.

The fix addresses 18 unhandled promise rejections that vitest catches but
tape silently ignored. See RFC for detailed analysis and follow-up task
to make testLayer fully async.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

/* global setTimeout */
import {expect} from 'vitest';
import {Deck, Layer} from '@deck.gl/core';
import {device} from '@deck.gl/test-utils';
Copy link

Choose a reason for hiding this comment

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

Utility file codemod output imports from wrong entry point

Low Severity

The codemod fixture 06-utility-file.ts imports device from @deck.gl/test-utils (the default/tape entry point) instead of @deck.gl/test-utils/vitest. This causes the converted test to transitively import @probe.gl/test-utils via tape.ts, which defeats the purpose of the vitest-specific entry point. Meanwhile, 05-layer-callbacks.ts correctly uses @deck.gl/test-utils/vitest. This inconsistency means the codemod would produce output that unnecessarily pulls in @probe.gl/test-utils for utility files. All vitest-converted files that only need device, gl, or other non-spy utilities from test-utils should import from @deck.gl/test-utils/vitest to avoid the probe.gl dependency.

Additional Locations (1)

Fix in Cursor Fix in Web

- Fix TileReader.compression state leakage in carto-raster-tile.spec.ts
- Fix test assertions checking wrong variable (tile vs tile2)
- Re-enable carto-raster-tile.spec.ts and carto-raster-tile-loader.spec.ts
- Document all excluded headless tests in RFC with categorization:
  - Tests never imported on master (8 tests - keep excluded)
  - Tests that were passing on master (12 tests - need investigation)
  - Key patterns: GPU state leakage, global state mutations, async timing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

}
}
return null;
}
Copy link

Choose a reason for hiding this comment

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

Duplicated cleanup logic risks inconsistent maintenance

Low Severity

cleanupAfterLayerTestsAsync is a near-exact copy of cleanupAfterLayerTests, differing only by a single await line before finalize(). The finalize-and-check-resource-counts logic (~8 lines) is duplicated. If the resource checking logic changes (e.g., adding new resource types to track), both functions must be updated in lockstep or they'll diverge silently. Extracting the shared finalize/check logic into a small helper would eliminate this maintenance risk.

Additional Locations (1)

Fix in Cursor Fix in Web

chrisgervang and others added 2 commits March 6, 2026 10:05
…eprecation

- Remove test/render/**/*.spec.ts from browser project to prevent duplicate
  test runs with mismatched viewport causing golden image failures
- Update @vitest/browser/context imports to vitest/browser (deprecated)
- Document browser project render test exclusion in RFC with future fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The codemod fixture output for utility files was importing from
@deck.gl/test-utils instead of @deck.gl/test-utils/vitest. This
would cause vitest-only users to get a runtime import failure
when the utility file loads (due to transitive @probe.gl/test-utils
dependency).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

export {generateLayerTests} from './generate-layer-tests';

// Types
export type {LayerTestCase, ResetSpy, SpyFactory, TestLayerOptions} from './lifecycle-test';
Copy link

Choose a reason for hiding this comment

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

Exported type has required fields wrapper makes optional

Low Severity

The vitest.ts entry point re-exports the TestLayerOptions type from lifecycle-test.ts, which has createSpy and resetSpy as required fields. However, the testLayer and testLayerAsync wrapper functions in vitest.ts accept these as optional (with defaults). Consumers who import TestLayerOptions to type their options objects would be forced to provide createSpy/resetSpy even though the functions don't require them. Exporting a separate type (e.g., Omit<TestLayerOptions, 'createSpy' | 'resetSpy'> & { createSpy?: SpyFactory; resetSpy?: ResetSpy }) or making these fields optional in TestLayerOptions would align the type with the actual API.

Additional Locations (1)

Fix in Cursor Fix in Web

headless: false,
screenshotFailures: false,
commands: browserCommands
},
Copy link

Choose a reason for hiding this comment

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

Browser project uses headed renderPlaywright instead of default

Low Severity

The browser project (for local debugging with visible browser) uses renderPlaywright as its provider, which has a fixed viewport of {width: 1024, height: 768}. However, the browser project is headed mode intended for general debugging of unit tests, not for render/golden-image comparison. Using the render-specific viewport configuration in the non-render debugging project is unintentional — the headless project uses the default playwright() provider without viewport constraints, which is the expected configuration here.

Fix in Cursor Fix in Web

Replace fileURLToPath + dirname pattern with import.meta.dirname
which is available in Node.js 20.11+ and is cleaner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a 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 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

}
}
return null;
}
Copy link

Choose a reason for hiding this comment

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

Cleanup functions have duplicated resource-checking logic

Low Severity

The new cleanupAfterLayerTestsAsync function duplicates the entire body of cleanupAfterLayerTests, differing only by setLayers([]) plus an await setTimeout(0) before finalization. The resource-counting loop and error construction are copy-pasted. If a bug is fixed in one cleanup path, the other could easily be missed. The async version could call the sync cleanup's shared logic after its yield, or a shared helper could extract the resource-check code.

Additional Locations (1)

Fix in Cursor Fix in Web

Resolve conflicts:
- modules/test-utils/package.json: Keep vitest optional peer dep with luma.gl 9.3
- yarn.lock: Regenerated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ibgreen ibgreen mentioned this pull request Mar 22, 2026
34 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants