nuxt / test-utils

πŸ§ͺ Test utilities for Nuxt
http://nuxt.com/docs/getting-started/testing
MIT License
286 stars 74 forks source link

Vitest doesn't work in programmatic way if the root path isn't the current working dir #860

Closed Jinjiang closed 3 weeks ago

Jinjiang commented 1 month ago

Environment

The background is I'm trying to make the Nuxt apps work in @teambit.

I tried to run Vitest via Node APIs and have found the app doesn't render properly in the tests if the current working dir is not the root path of the project.

Reproduction

https://github.com/Jinjiang/reproductions/tree/nuxtjs-vitest-20240514

pnpm install

# it works
pnpm test
# it works
pnpm test:debug:1
# it doesn't
pnpm test:debug:2

Describe the bug

First, prepare a js file ./debug/run.mjs like:

import { startVitest } from 'vitest/node';

startVitest('test', ...);

then:

Additional context

No response

Logs

$ pnpm test:debug:2

> nuxt-app@ test:debug:2 /Volumes/jinjiang-portable-disk/Developer/teambit/temp/repro-nuxtjs-app-20240513
> cd debug && node test.mjs && cd ..

{
  configPath: '/Volumes/jinjiang-portable-disk/Developer/teambit/temp/repro-nuxtjs-app-20240513/vitest.config.ts',
  rootPath: '/Volumes/jinjiang-portable-disk/Developer/teambit/temp/repro-nuxtjs-app-20240513',
  include: [
    '/Volumes/jinjiang-portable-disk/Developer/teambit/temp/repro-nuxtjs-app-20240513/**/*.spec.ts'
  ]
}

 RUN  v1.6.0 /Volumes/jinjiang-portable-disk/Developer/teambit/temp/repro-nuxtjs-app-20240513/debug

stdout | createSuspenseBoundary (/Volumes/jinjiang-portable-disk/Developer/teambit/temp/repro-nuxtjs-app-20240513/node_modules/.pnpm/@vue+runtime-core@3.4.27/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1451:43)
<Suspense> is an experimental feature and its API will likely change.

stderr | ../tests/foo.nuxt.spec.ts > can also render an app
[Vue warn]: Failed to resolve component: HelloWorld
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 
  at <App > 
  at <Anonymous> 
  at <Anonymous ref="VTU_COMPONENT" > 
  at <VTUROOT>

 ❯ ../tests/foo.nuxt.spec.ts (1)
   Γ— can also render an app

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  ../tests/foo.nuxt.spec.ts > can also render an app
Error: Snapshot `can also render an app 1` mismatched

- Expected
+ Received

  "<div id="test-wrapper">
    <div>
-     <div> 123 <div>
-         <h1>Hello World</h1>
-       </div><!-- <NuxtWelcome /> -->
-       <div>
-         <h1>Hello World</h1>
-       </div>
-       <div>
-         <h1>About</h1>
-         <div>
-           <h1>Hello World</h1>
-         </div><img src="/favicon.ico"><img src="/assets/zhlint.svg"><img src="/vue.svg" style="width: 200px;">
-         <div> Counter: 3000 <button> + </button><button> - </button></div>
-         <p></p>
-       </div>
-     </div>
+     <!-- <NuxtWelcome /> -->
+     <helloworld></helloworld>
+     <!---->
    </div>
  </div>"

 ❯ ../tests/foo.nuxt.spec.ts:8:28
      6| it('can also render an app', async () => {
      7|   const component = await renderSuspended(App, { route: '/about' })
      8|   expect(component.html()).toMatchInlineSnapshot(`
       |                            ^
      9|     "<div id="test-wrapper">
     10|       <div>

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

  Snapshots  1 failed
 Test Files  1 failed (1)
      Tests  1 failed (1)
   Start at  16:44:56
   Duration  733ms (transform 268ms, setup 144ms, collect 193ms, tests 77ms, environment 71ms, prepare 131ms)

 ELIFECYCLE  Command failed with exit code 1.
dosubot[bot] commented 1 month ago

@Jinjiang Hello there! I'm here to help you with any issues you may have. Let me know how I can assist you!

To make Nuxt apps work with @teambit and Vitest when running tests programmatically, ensuring they render properly even if the current working directory is not the root path of the project, you can use the defineVitestConfig from @nuxt/test-utils/config and set the test environment to 'nuxt'. Additionally, you can use the getVitestConfigFromNuxt function to dynamically load and configure the Nuxt environment. Here is an example configuration:

import { defineVitestConfig, getVitestConfigFromNuxt } from '@nuxt/test-utils/config'

export default defineVitestConfig(async () => {
  const nuxtConfig = await getVitestConfigFromNuxt()
  return {
    ...nuxtConfig,
    test: {
      ...nuxtConfig.test,
      environment: 'nuxt',
    },
  }
})

This configuration ensures that the Nuxt environment is properly set up for your tests, which helps in rendering the Nuxt app correctly regardless of the current working directory. The getVitestConfigFromNuxt function dynamically loads the Nuxt configuration, making it adaptable to different project structures [1][2].

#### Details **About Dosu** This response is meant to be useful and save you time. It is **not meant to be a precise solution**, but rather a starting point for your own research. Help me be more useful! Please leave a πŸ‘ if this is helpful and πŸ‘Ž if it is irrelevant.

To continue the conversation, mention @dosu.

Jinjiang commented 3 weeks ago

@Jinjiang Hello there! I'm here to help you with any issues you may have. Let me know how I can assist you!

To make Nuxt apps work with @teambit and Vitest when running tests programmatically, ensuring they render properly even if the current working directory is not the root path of the project, you can use the defineVitestConfig from @nuxt/test-utils/config and set the test environment to 'nuxt'. Additionally, you can use the getVitestConfigFromNuxt function to dynamically load and configure the Nuxt environment. Here is an example configuration:

import { defineVitestConfig, getVitestConfigFromNuxt } from '@nuxt/test-utils/config'

export default defineVitestConfig(async () => {
  const nuxtConfig = await getVitestConfigFromNuxt()
  return {
    ...nuxtConfig,
    test: {
      ...nuxtConfig.test,
      environment: 'nuxt',
    },
  }
})

This configuration ensures that the Nuxt environment is properly set up for your tests, which helps in rendering the Nuxt app correctly regardless of the current working directory. The getVitestConfigFromNuxt function dynamically loads the Nuxt configuration, making it adaptable to different project structures [1][2].

Details

About Dosu This response is meant to be useful and save you time. It is not meant to be a precise solution, but rather a starting point for your own research.

Help me be more useful! Please leave a πŸ‘ if this is helpful and πŸ‘Ž if it is irrelevant.

To continue the conversation, mention @dosu.

It seems doesn't help πŸ˜“

dosubot[bot] commented 3 weeks ago

To address the issue of running Vitest programmatically with Nuxt apps when the current working directory is not the root path, you need to ensure that the root option is correctly set in the Vitest configuration. Here is a more detailed approach:

  1. Set the Root Directory Explicitly in Vitest Configuration:

    Ensure that the root option in the Vitest configuration is set to the root directory of your project. This can be done by passing the rootDir option when starting Vitest programmatically.

    import { startVitest } from 'vitest/node';
    import { resolve } from 'path';
    
    const rootDir = resolve(__dirname, '../'); // Adjust the path to your project's root directory
    
    startVitest('test', [], {
     root: rootDir,
     run: true,
     deps: {
       inline: [/@nuxt\/test-utils/],
     },
    }, {
     esbuild: {
       tsconfigRaw: '{}',
     },
     test: {
       dir: rootDir,
       deps: {
         inline: [
           '@nuxt/test-utils',
           '@nuxt/test-utils-nightly',
           '@nuxt/test-utils-edge',
         ],
       },
       globals: true,
     },
    });
  2. Ensure Nuxt Configuration Uses the Correct Root Directory:

    When starting Nuxt and getting the Vite configuration, make sure the rootDir is set correctly.

    import { loadNuxt, buildNuxt } from '@nuxt/kit';
    import { resolve } from 'path';
    
    async function startNuxtAndGetViteConfig(rootDir = process.cwd(), options = {}) {
     const nuxt = await loadNuxt({
       cwd: rootDir,
       dev: false,
       dotenv: {
         cwd: rootDir,
         fileName: '.env.test',
       },
       overrides: {
         appId: 'nuxt-app',
         buildId: 'test',
         ssr: false,
         test: true,
         modules: ['@nuxt/test-utils/module'],
       },
     });
     // ...
    }
    
    const rootDir = resolve(__dirname, '../'); // Adjust the path to your project's root directory
    startNuxtAndGetViteConfig(rootDir);
  3. Use defineVitestConfig to Set the Root Directory:

    If you are using defineVitestConfig, ensure the rootDir is set explicitly.

    import { fileURLToPath } from 'node:url';
    import { defineVitestConfig } from '@nuxt/test-utils/config';
    
    export default defineVitestConfig({
     test: {
       dir: 'tests',
       coverage: {
         reportsDirectory: 'coverage',
       },
       includeSource: ['../pages/index.vue'],
       environmentOptions: {
         nuxt: {
           rootDir: fileURLToPath(new URL('./', import.meta.url)),
           domEnvironment: (process.env.VITEST_DOM_ENV as 'happy-dom' | 'jsdom') ?? 'happy-dom',
           mock: {
             indexedDb: true,
           },
         },
       },
       setupFiles: './tests/setup/mocks.ts',
       globals: true,
     },
    });

These steps should help ensure that the Nuxt app renders correctly in your tests, regardless of the current working directory [1][2][3].

To continue talking to Dosu, mention @dosu.

danielroe commented 3 weeks ago

Looking, we guess that the Nuxt rootDir will be process.cwd() unless you set it explicitly. When setting it explicitly your reproduction seems to work:

  // vitest.config.ts
+ import { fileURLToPath } from 'node:url'
  import { defineVitestConfig } from '@nuxt/test-utils/config'

  export default defineVitestConfig({
    test: {
      environment: 'nuxt',
+     environmentOptions: {
+       nuxt: {
+         rootDir: fileURLToPath(new URL('.', import.meta.url)),
+       }
+     }
    }
  })

Do you have a better suggestion for default behaviour - for example, I guess we could look for a nuxt.config file... or default to the same directory as vitest.config.ts?

Jinjiang commented 3 weeks ago

It works. Thanks. Will close this issue.

For the default behavior I don't have a strong preference. However, for programmatic usage, this is the only doc that I have found https://nuxt.com/docs/api/kit/programmatic maybe it's better to have one more guide page and some examples about how to run dev server, prepare, build, lint, test a Nuxt app, and etc.

I actually created another discussion about this: https://github.com/nuxt/nuxt/discussions/26398 So far I have figured out most of them by reading source code πŸ˜…. And the vitest config is the last piece left.