Skip to content

[code-infra] Replace mocha with vitest for browser & jsdom tests #14508

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

Merged
merged 625 commits into from
May 8, 2025

Conversation

JCQuintas
Copy link
Member

@JCQuintas JCQuintas commented Sep 5, 2024

Related mui/material-ui#43625

Help toward mui/mui-public#170.

Running tests

# jsdom
pnpm test
pnpm text:jsdom
pnpm test:unit:jsdom

# browser
pnpm test:browser
pnpm test:unit:browser

## Filter projects

### starting with `x-charts`
... --project "x-charts*"

### exactly `x-data-grid`
... --project "x-data-grid"

### ending with `-pro`
... --project "*-pro"

Work

  • Moved --coverage to V8/C8, and moved it to browser instead of jsdom as it was faster
  • Removed usage of clock='fake' and uses the vitest provided versions throughout the application.
    • The goal is to eventually create a proper handler for it to replicate our most used pattern
    • Issue with previous implementation is that it required faking the clock/timers for ALL tests in a suit, rather than just the ones that needed it.
  • Vitest config at
    • packages/**/vitest.config.browser.mts
    • packages/**/vitest.config.jsdom.mts
  • skipLibCheck=true added to some packages due to chai issues with @mui/internal-test-utils

Times

JSDOM: 11-13m
BROWSER: 5-6m

Table is a bit outdated, times are faster now (above). Left it as a comparison.

class runner time browser time jsdom coverage pool
medium mocha 8m 14m JSDOM *
medium vitest 9m 19m JSDOM fork
medium vitest 13m 15m BROWSER fork
medium+ vitest 7m 19m JSDOM fork
large vitest 6m 17m JSDOM fork

@mui-bot
Copy link

mui-bot commented Sep 5, 2024

Deploy preview: https://deploy-preview-14508--material-ui-x.netlify.app/

Generated by 🚫 dangerJS against 73c6a59

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Sep 9, 2024
Copy link

github-actions bot commented Sep 9, 2024

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Sep 10, 2024
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot added PR: out-of-date The pull request has merge conflicts and can't be merged and removed PR: out-of-date The pull request has merge conflicts and can't be merged labels Sep 11, 2024
@JCQuintas JCQuintas changed the title [code-infra] vitest browser mode working - charts [code-infra] vitest browser mode Sep 11, 2024
@JCQuintas JCQuintas changed the title [code-infra] vitest browser mode [code-infra] vitest browser & jsdom modes Sep 11, 2024
@JCQuintas
Copy link
Member Author

@LukasTy @flaviendelangle @noraleonte @arthurbalduini I think I've fixed most of the issues with date-pickers in vitest that I could without going super deep on how everything works 😅

I now need some help with understanding the current errors, and possible ideas on how to fix them.

You can check the current failing tests here:
Vitest Tests (jsdom)
Vitest Tests (browser)

Comment on lines 14 to 22
browser: {
enabled: true,
name: 'chromium',
provider: 'playwright',
headless: true,
// https://playwright.dev
providerOptions: {},
screenshotFailures: false,
},
Copy link
Member

Choose a reason for hiding this comment

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

Interesting

Copy link
Member

@oliviertassinari oliviertassinari left a comment

Choose a reason for hiding this comment

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

The speed of the tests in the CI looks encouraging 👍 . A bit early to see if there is a real difference with the current CI though (test coverage vs. none, CircleCI vs. GitHub Action).

.eslintrc.js Outdated
@@ -37,7 +37,7 @@ const addReactCompilerRule = (packagesNames, isEnabled) =>
!isEnabled
? []
: packagesNames.map((packageName) => ({
files: [`packages/${packageName}/src/**/*{.ts,.tsx,.js}`],
files: [`packages/${packageName}/src/**/*.?(c|m)[jt]s?(x)`],
Copy link
Member

@oliviertassinari oliviertassinari Sep 18, 2024

Choose a reason for hiding this comment

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

With this regex, it seems hard to search in the codebase for, e.g. .tsx, I suspect that listing all the permutations would be clearer.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see why you would need to search for that though. As you will have to sweep over all configuration files when doing a change that would require updating that anyways 😅

Replacing it to list all the permutations is quite unnecessary in my view: {.ts,.tsx,.js,.cjs,.cjsx,.mjs,.mjsx,.cts,.ctsx,.mts,.mtsx}

We could cleanup some unlikely exts like {.ts,.tsx,.js,.cjs,.mjs,.mts} which would be simpler, but less complete.

Copy link
Member

Choose a reason for hiding this comment

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

but less complete.

Could it actually be great? I assume we will never need those other extensions, so if someone create them, he might be more likely to realize those are wrong?

Copy link
Member

@Janpot Janpot Oct 17, 2024

Choose a reason for hiding this comment

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

Could it actually be great? I assume we will never need those other extensions, so if someone create them, he might be more likely to realize those are wrong?

Quite the contrary, we sometimes need those extensions:

  • When running scripts natively on node (or through tsx), we need a way to signal node which module system is being used.
  • We need .cjs in edge-cases for upcoming ESM support in places where we want to bridge between ESM and CJS, e.g. to deal with importing next/document in ESM.
  • Tooling like esbuild, tsx and vitest rely on the extension to determine whether JSX needs to be transformed or not. This is currently giving problems as we have a bunch of jsx in .js files. See Enable JSX in .js files privatenumber/tsx#644 and Allow JSX in .js files vitest-dev/vitest#1564. For the latter I have a workaround but it also forces our .ts files to adhere to the JSX rules, which required me to refactor some code.
  • If we want our output to be interpreted correctly as ESM in node, it will need the correct extension. (granted, this code is not authored and thus not linted)
  • ...

We shouldn't restrict extensions, they're not for cosmetic reasons. We should just prefer .ts/.tsx unless specific runtime requirements demand otherwise.
Besides that, I think the logic of "so if someone create them, he might be more likely to realize those are wrong?" is flawed. They likely won't realize anything, starting with not realizing eslint is not running on their code 😄. Better is to apply the rules to any file that could contain javascript/typescript and if we need to enforce a specific extension we should use a specific rule for that.

Copy link
Member

@oliviertassinari oliviertassinari Oct 17, 2024

Choose a reason for hiding this comment

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

They likely won't realize anything

@Janpot If a developer can't run the tests he/she try to write in dev mode, I think he/she will notice that something is off.

For eslint specifically, yeah, agree, the ideal is to lint the file and have an eslint rule that error because the extension is wrong.

Copy link
Member

@Janpot Janpot Oct 17, 2024

Choose a reason for hiding this comment

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

If a developer can't run the tests he/she try to write in dev mode, I think he/she will notice that something is off.

In any case, why have them waste time reverse engineering the tests to find out they're using the wrong extension if the eslint plugin can tell them in context in the editor? 🤔 Not that there would be anything wrong with using .jsx instead of .js for JSX files, contrary, it would kind of make things easier.

Copy link
Member

@oliviertassinari oliviertassinari Oct 17, 2024

Choose a reason for hiding this comment

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

if the eslint plugin can tell them in context in the editor?

👍 https://www.npmjs.com/package/eslint-plugin-filename-rules sounds great.

Not that there would be anything wrong with using .jsx instead of .js for JSX files

I think that the value of only having .js is about not having to think about extensions. Having to rename a file after refactoring its content and moving the code around is friction to change.

Copy link
Member

Choose a reason for hiding this comment

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

But we can't get around this friction in typescript. Finishing the typescript migration means imposing this friction across the codebase anyway.

Copy link
Member

@Janpot Janpot Oct 17, 2024

Choose a reason for hiding this comment

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

If a developer can't run the tests he/she try to write in dev mode, I think he/she will notice that something is off.

🙂 Coincidentally, I'm just running into a problem that illustrates why this is harmful. Just trying to figure out why I have a failing test locally in vitest that doesn't seem to break in mocha. The following produces a failing test:

pnpm --filter @mui/icons-material test -- -g "\"should produce the expected output\""

But when you try running the global command:

pnpm test:coverage -- -g "\"should produce the expected output\""

Whoops, no test is running. What happened? Somewhere along the way we started running the build script natively in node. The file was renamed to .mjs. The test runner only looks for .js, .ts, and .tsx. The author didn't see any tests break after running them and assumed they were good. They didn't realize the test stopped running altogether. If the test framework was configured to run on any encountered javascript file, nothing would have been broken.

It took me a while to realize the test wasn't running, my first instinct was "something fails in vitest that breaks in mocha".

Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Sep 19, 2024
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 8, 2024
@JCQuintas
Copy link
Member Author

@LukasTy had to move away from global overrides in favour of vi.mock because the later works correctly and with minimal required changes in both jsdom and browser. We just have to mock the mock function when running in mocha 😅

Changes are here:
2d5d395

Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot added PR: out-of-date The pull request has merge conflicts and can't be merged and removed PR: out-of-date The pull request has merge conflicts and can't be merged labels Oct 11, 2024
Copy link
Member

@Janpot Janpot left a comment

Choose a reason for hiding this comment

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

Looks good overall, a few reflections

  • The project naming and filtering logic feel quite complicated to me. Are there no ways to simplify. i.e. is it not possible with wildcards? e.g. --project=jsdom/* or --project=*/@mui/x-charts. I could see it making harder to build more tooling around this if projects don't have a static name.
  • Either I misunderstand it or the date-fns resolution plugin could use resolution apis and not code transformation.
  • I wouldn't be against creating a vitest config factory e.g.
    export default createConfig({ browser: true, hideScrollbars: true })

Copy link
Member

@LukasTy LukasTy left a comment

Choose a reason for hiding this comment

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

A couple of additional comments/issues:

  • I think we should replace mocha in tsconfig types with vitest (in package tests), since now all describe and it calls still point to mocha.
  • It looks like adding .only to a block (i.e. describe) does not limit test running only to that particular block.
    IMHO, this is a blocker. 🙈

@JCQuintas
Copy link
Member Author

JCQuintas commented May 5, 2025

  • It looks like adding .only to a block (i.e. describe) does not limit test running only to that particular block.

This is a behaviour of vitest 🫠

Jest has the same issue: jestjs/jest#4414

The docs recommend using npm vitest FileName to filter for a single file
https://vitest.dev/api/#describe-only

Or run it in watch-mode which will run on changed files only

@LukasTy
Copy link
Member

LukasTy commented May 5, 2025

This is a behaviour of vitest 🫠

Jest has the same issue: jestjs/jest#4414

The docs recommend using npm vitest FileName to filter for a single file https://vitest.dev/api/#describe-only

That's "slightly" annoying. 🙈
What if I want to run certain only blocks in multiple files? 🤔
I will need to include all the files? 🙈 🤷
In any case, it seems that we got used to comfortable single-threaded features... 🙈

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label May 6, 2025
@JCQuintas
Copy link
Member Author

  • I wouldn't be against creating a vitest config factory e.g.
    export default createConfig({ browser: true, hideScrollbars: true })

I'm not against it, but I would rather do it in a future step if necessary.

I think the current way works, although it is a bit wordy. We already have a place to store shared config in vitest.shared.mts, so the changes would to have a createConfig to mostly automate this:

  test: {
    name: getTestName(import.meta.url),
    environment: 'browser',
    browser: {
      enabled: true,
      instances: [
        {
          browser: 'chromium',
        },
      ],
    },
  },

Copy link
Member

@LukasTy LukasTy left a comment

Choose a reason for hiding this comment

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

LGTM. 👍
Thank you for all the massive effort working on this! 🙏 💯 💙

@JCQuintas JCQuintas merged commit e2dceb2 into mui:master May 8, 2025
23 checks passed
@JCQuintas JCQuintas deleted the vitest-x-browser-working branch May 8, 2025 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants