Skip to content

feat(test-utils): add chaiEnabled flag to setupVitest to make chai optional#1274

Draft
Copilot wants to merge 2 commits intomasterfrom
copilot/add-chai-enabled-flag
Draft

feat(test-utils): add chaiEnabled flag to setupVitest to make chai optional#1274
Copilot wants to merge 2 commits intomasterfrom
copilot/add-chai-enabled-flag

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

setupVitest unconditionally imported chai, chai-dom, ./chaiPlugin, and ./chaiTypes at module evaluation time, making chai a hard dependency even for setups that don't use it.

Changes

  • Removed all static chai imports from the top of setupVitest.ts
  • Added chaiEnabled?: boolean option (default: true) to the setupVitest parameter type
  • Replaced chai setup with dynamic import() inside an async IIFE in the one-time init block, gated on chaiEnabled; chai, chai-dom, ./chaiPlugin, and ./chaiTypes are loaded in parallel via Promise.all
  • Error handling: wraps dynamic imports in try/catch with actionable error messages pointing to the missing package and the chaiEnabled: false escape hatch
  • jsdom Touch polyfill and IS_REACT_ACT_ENVIRONMENT remain unconditional — only chai-specific setup is gated

Usage

// Opt out of chai entirely (chai need not be installed)
setupVitest({ chaiEnabled: false });

// Default behavior unchanged
setupVitest();
Original prompt

Summary

Add a chaiEnabled flag to the setupVitest function in packages/test-utils/src/setupVitest.ts that allows consumers to opt out of chai entirely. When chaiEnabled is false, no chai-related packages (chai, chai-dom, ./chaiPlugin, ./chaiTypes) should be imported — not even as side effects — so that chai is not a required dependency for setups that don't use it.

Implement this using dynamic imports (import(...)) with proper error handling so the chai dependency is never loaded when it's disabled.

Current file

packages/test-utils/src/setupVitest.ts (blob SHA: 0eb6a70b6f30f054b2a0acb9641d5ca90981dded):

import failOnConsole from 'vitest-fail-on-console';
import * as chai from 'chai';
import './chaiTypes';
// eslint-disable-next-line import/extensions
import { cleanup, act } from '@testing-library/react/pure.js';
import { afterEach, vi } from 'vitest';
import chaiDom from 'chai-dom';
import chaiPlugin from './chaiPlugin';
import { Configuration, configure } from './configure';

let isInitialized = false;

export default function setupVitest({
  failOnConsoleEnabled = true,
  ...config
}: Partial<Configuration> & { failOnConsoleEnabled?: boolean } = {}): void {
  // When run in vitest with --no-isolate, the test hooks are cleared between each suite,
  // but modules are only evaluated once, so calling it top-level would only register the
  // hooks for the first suite only.
  // Instead call `setupVitest` in one of the `setupFiles`, which are not cached and executed
  // per suite.

  afterEach(async () => {
    if (vi.isFakeTimers()) {
      await act(async () => {
        vi.runOnlyPendingTimers();
      });
    }

    vi.useRealTimers();

    cleanup();
  });

  if (failOnConsoleEnabled) {
    failOnConsole({
      silenceMessage: (message: string) => {
        if (process.env.NODE_ENV === 'production') {
          // TODO: mock scheduler
          if (message.includes('act(...) is not supported in production builds of React')) {
            return true;
          }
        }

        if (message.includes('Warning: useLayoutEffect does nothing on the server')) {
          return true;
        }

        if (
          message.includes(
            'Detected multiple renderers concurrently rendering the same context provider.',
          )
        ) {
          return true;
        }

        return false;
      },
    });
  }

  // Don't call test lifecycle hooks (afterEach/afterAll/beforeEach/beforeAll/...) after this point
  // Make sure none of (transitive) dependencies call lifecycle hooks either, otherwise they won't be
  // registered and thus won't run when using `--no-isolate --no-file-parallelism`.

  if (isInitialized) {
    return;
  }

  isInitialized = true;

  configure(config);

  chai.use(chaiPlugin);

  if (typeof window !== 'undefined') {
    chai.use(chaiDom);

    // Enable missing act warnings: https://github.com/reactwg/react-18/discussions/102
    (globalThis as any).jest = null;
    (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;

    if (window.navigator.userAgent.includes('jsdom')) {
      // Not yet supported: https://github.com/jsdom/jsdom/issues/2152
      (globalThis as any).window.Touch ??= class Touch {
        instance: any;

        constructor(instance: any) {
          this.instance = instance;
        }

        get identifier() {
          return this.instance.identifier;
        }

        get pageX() {
          return this.instance.pageX;
        }

        get pageY() {
          return this.instance.pageY;
        }

        get clientX() {
          return this.instance.clientX;
        }

        get clientY() {
          return this.instance.clientY;
        }
      };
    }
  }
}

Required changes

1. Add chaiEnabled flag to the options parameter

Add chaiEnabled?: boolean to the parameter type, defaulting to true:

export default function setupVitest({
  failOnConsoleEnabled = true,
  chaiEnabled = true,
  ...config
}: Partial<Configuration> & { failOnConsoleEnabled?: boolean; chaiEnabled?: boolean } = {}): void {

2. Remove all static chai imports

Remove the following static imports from the top of the file (they must not be imported unconditionally):

import * as chai from 'chai';
import './chaiTypes';
import chaiDom from 'chai-dom';
import chaiPlugin from './chaiPlugin';

3. Replace the static chai usage with dynamic imports inside the isInitialized block

The existing chai setup code (which only runs once, inside the if (isInitialized) guard) must become async and use dynamic import(), gated on chaiEnabled. The function signature itself can remain synchronous — make the setupVitest function return void still. The one-time initialization section should launch an async IIFE or be handled in a way that doesn't break the existing structure.

The dynamic imports should use try/catch and throw a helpful error if a chai package fails to load (e...

This pull request was created from Copilot chat.

…ports

Agent-Logs-Url: https://github.com/mui/mui-public/sessions/e3a43c6b-ef61-4376-a5eb-e4c96ba9329c

Co-authored-by: bernardobelchior <12778398+bernardobelchior@users.noreply.github.com>
@mui-bot
Copy link
Copy Markdown

mui-bot commented Apr 1, 2026

Bundle size report

Bundle Parsed size Gzip size
@base-ui/react 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

Copilot AI changed the title [WIP] Add chaiEnabled flag to setupVitest function feat(test-utils): add chaiEnabled flag to setupVitest to make chai optional Apr 1, 2026
Copilot AI requested a review from bernardobelchior April 1, 2026 10:31
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