Skip to content

chore: migrates tests from Enzyme/Mocha to Vitest and React Testing Library#94

Merged
chinrichs-godaddy merged 8 commits intogodaddy:mainfrom
chrisvogt:migrate-to-vitest
Feb 11, 2026
Merged

chore: migrates tests from Enzyme/Mocha to Vitest and React Testing Library#94
chinrichs-godaddy merged 8 commits intogodaddy:mainfrom
chrisvogt:migrate-to-vitest

Conversation

@chrisvogt
Copy link
Contributor

@chrisvogt chrisvogt commented Feb 11, 2026

Summary

Migrates the test framework from Enzyme/Mocha/Chai/Sinon/NYC to Vitest and React Testing Library, replacing deprecated tooling with modern, actively maintained alternatives.

Changes

Test framework

  • Replaced Mocha (runner), Chai (assertions), Sinon (mocking), and NYC (coverage) with Vitest
  • Replaced Enzyme with React Testing Library
  • Using happy-dom for fast DOM simulation
  • New scripts: test:watch (watch mode) and test:ui (Vitest UI)
Screenshot 2026-02-10 at 9 41 13 PM

Source files

  • Renamed source files containing JSX from .js to .jsx, enabling native esbuild transforms
  • Removed orphaned webpack.config.js (referenced a missing react-svg-loader dependency; Storybook uses its own config)

Dependencies

  • Removed: enzyme, @cfaester/enzyme-adapter-react-18, mocha, chai, sinon, sinon-chai, nyc, @babel/register, eslint-plugin-mocha, jsdom, vite-plugin-babel, cheerio override
  • Added: vitest, @vitest/ui, @vitest/coverage-v8, @testing-library/react, @testing-library/user-event, @testing-library/jest-dom, happy-dom

Housekeeping

  • Added .npmrc with legacy-peer-deps=true for streamlined installation
  • Bumped version to 3.0.1

Performance

Metric Mocha + Enzyme (before) Vitest + Testing Library (after)
Tests 24 passing 24 passing
Test execution 103ms 166ms
Total duration 962ms 437ms
Wall clock time 0.962s 0.646s

33% faster end-to-end, primarily from replacing Babel transforms with native esbuild (5x faster) and using happy-dom over jsdom.

Note

The rename from .js to .jsx is required because Vite (which powers Vitest) uses esbuild for transforms, and esbuild will not parse JSX syntax in .js files by default. Without the rename, we would need to route all files through a Babel plugin (vite-plugin-babel) just to handle JSX — negating the performance benefits of switching to Vitest in the first place. Using .jsx extensions lets esbuild handle the transforms natively, which is what brought the transform step from 264ms down to 53ms. This is also the recommended convention from Vite and aligns with broader ecosystem conventions for distinguishing files that contain JSX.

This rename also lays the groundwork for a potential future migration of the build step from Babel to Vite (vite build), and Storybook from Webpack to the Storybook Vite builder. That would allow us to drop @babel/cli, @babel/core, @babel/preset-env, @babel/preset-react, @babel/plugin-transform-runtime, babel-loader, babel-plugin-inline-react-svg, @storybook/addon-webpack5-compiler-babel, @storybook/react-webpack5, @svgr/webpack, css-loader, less-loader, and style-loader — unifying the entire toolchain on Vite and significantly reducing the dependency footprint.

Motivation

  • Enzyme is deprecated and unmaintained; it required a community adapter just to work with React 18 and will never support React 19
  • React Testing Library is the recommended testing approach from the React team, encouraging tests that verify behavior rather than implementation details
  • Vitest provides a modern all-in-one test runner with built-in coverage, watch mode, and UI
  • Reducing the number of test-related dependencies simplifies maintenance

Notes

  • Coverage output still writes to the coverage/ directory with json-summary format, so external tools that read from there should continue to work
  • No changes to component behavior or public API
  • All 24 tests migrated and passing

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Migrates the project’s test stack from Enzyme/Mocha/Chai/Sinon/NYC to Vitest + React Testing Library (with happy-dom), modernizing tooling and updating related scripts/config.

Changes:

  • Added Vitest configuration (globals, happy-dom, setup file, coverage) and updated npm scripts for run/watch/ui/coverage.
  • Replaced Enzyme-based tests with RTL + Vitest tests (including updated setup).
  • Removed legacy Webpack SVG config and deprecated test dependencies; added a repo .npmrc to ease installs.

Reviewed changes

Copilot reviewed 12 out of 17 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
webpack.config.js Removed orphaned Webpack SVG loader config no longer used.
vitest.config.js Added Vitest config (happy-dom env, setup file, coverage settings).
test/unit/carousel.tests.jsx Migrated Carousel unit tests from Enzyme/Mocha to RTL/Vitest.
test/unit/carousel.tests.js Removed old Enzyme/Mocha test suite.
test/setup.js Simplified test setup for RTL/Vitest (jest-dom + setImmediate polyfill).
src/stories/CustomArrows.jsx Minor syntax/format fix (adds missing semicolon).
src/index.jsx Normalized class-field method terminators to }; for arrow properties.
package.json Updated scripts/deps for Vitest + RTL; bumped version to 3.0.1.
CHANGELOG.md Added 3.0.1 entry documenting testing migration.
.npmrc Added legacy-peer-deps=true to streamline installation.
.npmignore Removed NYC output ignore entry since NYC is removed.
.eslintrc Declared Vitest globals for linting tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 598 to 608
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

waitFor may re-run the callback multiple times; triggering goToSlide(1) inside it can cause repeated state updates and flaky behavior. Move the state-changing action (slidingCarousel.goToSlide(1)) outside waitFor (wrapped in act), and keep waitFor for assertions only.

Copilot uses AI. Check for mistakes.
Comment on lines 624 to 625
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Similar to the prior test: calling noneCarousel.goToSlide(1) inside waitFor can be executed multiple times and make the test flaky. Call noneCarousel.goToSlide(1) once before waitFor, then use waitFor only to assert track.style.transition.

Suggested change
await waitFor(() => {
noneCarousel.goToSlide(1);
act(() => {
noneCarousel.goToSlide(1);
});
await waitFor(() => {

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 18
let imagesFetched;

global.Image = class MyImage {
set src(val) {
imagesFetched.push(val);
this.onload && this.onload();
}
};
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This overrides global.Image for the entire test runtime and isn’t restored, which can leak into other test files and cause surprising failures. Store the original global.Image and restore it in afterAll (or afterEach if needed).

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using real timers (setTimeout) makes the test slower and can be flaky on slower/loaded CI. Prefer vi.useFakeTimers() + vi.advanceTimersByTime(20) (and vi.useRealTimers() cleanup) so the test is deterministic.

Suggested change
await new Promise(resolve => setTimeout(resolve, 20));
vi.useFakeTimers();
await act(async () => {
vi.advanceTimersByTime(20);
});
vi.useRealTimers();

Copilot uses AI. Check for mistakes.
Comment on lines 531 to 533
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Asserting exact outerHTML is brittle across DOM implementations (happy-dom vs jsdom) and React attribute serialization differences. Prefer asserting behavior/attributes (e.g., toBeDisabled(), toHaveClass(...), toHaveAttribute('type','button')) rather than the full serialized HTML string.

Suggested change
expect(topArrow.outerHTML).toBe('<button type="button" disabled="" class="carousel-arrow carousel-top-arrow carousel-arrow-default"></button>');
expect(bottomArrow).toBeTruthy();
expect(bottomArrow.outerHTML).toBe('<button type="button" class="carousel-arrow carousel-bottom-arrow carousel-arrow-default"></button>');
expect(topArrow.tagName).toBe('BUTTON');
expect(topArrow).toHaveAttribute('type', 'button');
expect(topArrow).toHaveClass('carousel-arrow', 'carousel-top-arrow', 'carousel-arrow-default');
expect(topArrow).toBeDisabled();
expect(bottomArrow).toBeTruthy();
expect(bottomArrow.tagName).toBe('BUTTON');
expect(bottomArrow).toHaveAttribute('type', 'button');
expect(bottomArrow).toHaveClass('carousel-arrow', 'carousel-bottom-arrow', 'carousel-arrow-default');
expect(bottomArrow).not.toBeDisabled();

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Correct spelling in the test name from 'carousal' to 'carousel' to match the component name and avoid confusion.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Correct spelling in the test name from 'carousal' to 'carousel' to match the component name and avoid confusion.

Copilot uses AI. Check for mistakes.
.npmrc Outdated
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Enabling legacy-peer-deps repo-wide can mask genuine peer dependency conflicts and make installs diverge from typical consumer environments. Consider removing this file and documenting an opt-in install command (or addressing specific peer dependency issues directly) so CI and local installs fail fast on real peer problems.

Suggested change
legacy-peer-deps=true
# Do not enable legacy-peer-deps repo-wide; use `npm install --legacy-peer-deps` explicitly if absolutely necessary.

Copilot uses AI. Check for mistakes.
@chinrichs-godaddy chinrichs-godaddy merged commit 99f7c39 into godaddy:main Feb 11, 2026
3 checks passed
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

Comments