vitest-dev / vitest

Next generation testing framework powered by Vite.
https://vitest.dev
MIT License
12.43k stars 1.11k forks source link

Fixture do not work when esbuild target is set to ES2015, ES2016 or esbuild.supported.['async-await'] = false #6042

Open kasperpeulen opened 4 weeks ago

kasperpeulen commented 4 weeks ago

Describe the bug

I'm not 100% sure if this is a bug or by design.

If you set the vite config to target ES2015, async/await will be transpiled and Vitest can not analyse the fn.toString() call correctly to determine where the fixture depends on.

import { defineConfig } from 'vite';

export default defineConfig({
  esbuild: {
    target: 'ES2015',
    // OR
    target: 'ES2016',
    // OR
    supported: {
      'async-await': false,
    }
  },
});

With the following test:

import { test as base, expect } from 'vitest';

// Edit an assertion and save to see HMR in action

const test = base.extend({
  todos: async ({}, use) => {
    await use(['todo']);
  },
});

test('fixture', ({ todos }) => {
  expect(todos).toContain('todo');
});

Gives the following error:

 ❯ test/basic.test.ts (0)

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  test/basic.test.ts [ test/basic.test.ts ]
Error: The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "_0".
 ❯ eval test/basic.test.ts:5:19
      3| // Edit an assertion and save to see HMR in action
      4| 
      5| const test = base.extend({
       |                   ^
      6|   todos: async ({}, use) => {
      7|     await use(['todo']);

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

 Test Files  1 failed (1)
      Tests  no tests
   Start at  20:36:33
   Duration  1.02s (transform 36ms, setup 0ms, collect 0ms, tests 0ms, environment 1ms, prepare 233ms)

 FAIL  Tests failed. Watching for file changes...
       press h to show help, press q to quit

See the reproduction.

In storybook we are considering also adopting fixture, and we started to use some similar as vitest to implement the mount RFC: https://github.com/storybookjs/storybook/discussions/27389

Which you can see as the first fixture.

However, we found that our fixture code breaks in Angular, which always transpiles away async/await when Zone.js is used.

When looking at the output code of esbuild, the destructure gets kind of preserved:

const Story = {
    async play ({mount}) {
        console.log(mount)
    },
}

gets transpiled to:

const Story = {
  play(_0) {
    return __async(this, arguments, function* ({ mount }) {
      console.log(mount);
    });
  }
};

So, it might be possible to improve the regex to cover to extract the fixture dependency information reliably.

My general feeling is that it might be just to fragile, but I am wondering what you here think about this.

Reproduction

https://stackblitz.com/edit/vitest-dev-vitest-tdu2jo?file=test%2Fbasic.test.ts

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.20.3 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  npmPackages:
    @vitest/ui: latest => 1.6.0 
    vite: latest => 5.3.3 
    vitest: latest => 1.6.0

Used Package Manager

npm

Validations

sheremet-va commented 4 weeks ago

Interesting point, but I wonder what is the point of setting esbuild to es5 in Vitest since we don't even support any runtimes with that target (the lowest we support is Node18 which is way ahead of es5). Maybe in our case we can forcefully set it higher 🤔

kasperpeulen commented 4 weeks ago

@sheremet-va Note that this is about using esbuild target ES2015, not ES5.

But a valid case would be that the user uses Angular with vitest.

Angular can not use async/await natively if zone.js is used https://angular.dev/guide/experimental/zoneless#why-use-zoneless

ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs simply cannot be patched effectively, such as async/await, and have to be downleveled to work with ZoneJS.

In this case the user must set esbuild to ES2015, ES2016 (or esbuild.supported.['async-await'] = false), but for any of those settings, fixture don't work anymore as reproduced above, as the following regex won't match: https://github.com/vitest-dev/vitest/blob/200a4349a2f85686bc7005dce686d9d1b48b84d2/packages/runner/src/fixture.ts#L212-L216

Edit the following config also won't work with fixtures:

    target: 'ES2015',
    // OR
    target: 'ES2016',
    // OR
    supported: {
      'async-await': false,
    }

That being said, angular is moving away from Zone.js for those kind of reasons. And you can already disable zoneless.js: https://angular.dev/guide/experimental/zoneless

image
sheremet-va commented 4 weeks ago

Then I think we can just support esbuild's __async output in the regexp since we know esbuild is the compiler.