nuxt / test-utils

🧪 Test utilities for Nuxt
http://nuxt.com/docs/getting-started/testing
MIT License
284 stars 73 forks source link

Vitest failing when baseURL is defined at nuxt.config.ts #865

Open leopradac opened 2 weeks ago

leopradac commented 2 weeks ago

Environment


Reproduction

https://stackblitz.com/edit/github-ichjfp?file=nuxt.config.ts&view=editor

Describe the bug

Quite simple as the title describes, when app > baseURL is defined at nuxt.config.ts vitest starts failing, at least at those tests using the nuxt environment with the // @vitest-environment nuxt at the beginning of the file.

Additional context

Logs shared below are for "baseURL": "/admin/". Also, for those struggling with this, by the time being a workaround would be modifying the "test" script at package.json to:

{
...
"test": "NUXT_APP_BASE_URL='/' vitest"
...
}

Logs

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
FetchError: [GET] "/admin/_nuxt/builds/meta/test.json": 404 Cannot find any path matching /admin/_nuxt/builds/meta/test.json.
 ❯ $fetch2 node_modules/ofetch/dist/shared/ofetch.37386b05.mjs:285:15

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { request: '/admin/_nuxt/builds/meta/test.json', options: { method: undefined }, response: { _data: { statusCode: 404, statusMessage: 'Cannot find any path matching /admin/_nuxt/builds/meta/test.json.', stack: [] }, constructor: 'Function<Response>', type: 'default', url: '', redirected: false, status: 404, ok: false, statusText: 'Cannot find any path matching /admin/_nuxt/builds/meta/test.json.', headers: { constructor: 'Function<Headers>', append: 'Function<append>', delete: 'Function<delete>', get: 'Function<get>', has: 'Function<has>', set: 'Function<set>', getSetCookie: 'Function<getSetCookie>', keys: 'Function<keys>', values: 'Function<values>', entries: 'Function<entries>', forEach: 'Function<forEach>' }, body: { constructor: 'Function<ReadableStream>', locked: true, cancel: 'Function<cancel>', getReader: 'Function<getReader>', pipeThrough: 'Function<pipeThrough>', pipeTo: 'Function<pipeTo>', tee: 'Function<tee>', values: 'Function<values>' }, bodyUsed: true, clone: 'Function<clone>', blob: 'Function<blob>', arrayBuffer: 'Function<arrayBuffer>', text: 'Function<text>', json: 'Function<json>', formData: 'Function<formData>' }, data: { statusCode: 404, statusMessage: 'Cannot find any path matching /admin/_nuxt/builds/meta/test.json.', stack: [] }, status: 404, statusCode: 404, statusText: 'Cannot find any path matching /admin/_nuxt/builds/meta/test.json.', statusMessage: 'Cannot find any path matching /admin/_nuxt/builds/meta/test.json.' }
This error originated in "__test__/nuxt.spec.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 Test Files  1 passed (1)
      Tests  1 passed (1)
     Errors  1 error
   Start at  04:53:52
   Duration  2.42s (transform 149ms, setup 304ms, collect 11ms, tests 1ms, environment 455ms, prepare 610ms)

 FAIL  Tests failed. Watching for file changes...
       press h to show help, press q to quit
dosubot[bot] commented 2 weeks ago

Hello there, @leopradac! I'm here to help you with any bugs, questions, or contributions. Let's work together to solve the issue you're facing.

To resolve the issue where Vitest fails when the baseURL is defined in the nuxt.config.ts file, causing a 404 error for a specific path, you can modify the Vitest configuration to handle the baseURL correctly.

In the src/environments/vitest/index.ts file, adjust the setup function to ensure that the baseURL is taken into account. Here is a snippet showing how you can modify the configuration:

import { joinURL } from 'ufo';

export default <Environment>{
  name: 'nuxt',
  transformMode: 'web',
  async setup(global, environmentOptions) {
    const url = joinURL('http://localhost:3000', environmentOptions?.nuxtRuntimeConfig.app?.baseURL || '/');

    const environmentName = environmentOptions.nuxt.domEnvironment as NuxtBuiltinEnvironment;
    const environment = environmentMap[environmentName] || environmentMap['happy-dom'];
    const { window: win, teardown } = await environment(global, defu(environmentOptions, {
      happyDom: { url },
      jsdom: { url },
    }));

    win.__NUXT_VITEST_ENVIRONMENT__ = true;

    win.__NUXT__ = {
      serverRendered: false,
      config: {
        public: {},
        app: { baseURL: '/' },
        ...environmentOptions?.nuxtRuntimeConfig,
      },
      data: {},
      state: {},
    };

    const app = win.document.createElement('div');
    app.id = environmentOptions.nuxt.rootId;
    win.document.body.appendChild(app);

    if (environmentOptions?.nuxt?.mock?.intersectionObserver) {
      win.IntersectionObserver = win.IntersectionObserver || class IntersectionObserver {
        observe() {}
        unobserve() {}
        disconnect() {}
      };
    }

    if (environmentOptions?.nuxt?.mock?.indexedDb) {
      win.indexedDB = indexedDB;
    }

    const h3App = createApp();

    if (!win.fetch) {
      await import('node-fetch-native/polyfill');
      win.URLSearchParams = globalThis.URLSearchParams;
    }

    const localCall = createCall(toNodeListener(h3App));
    const localFetch = createLocalFetch(localCall, win.fetch);

    const registry = new Set<string>();

    win.fetch = (init, options) => {
      if (typeof init === 'string') {
        const base = init.split('?')[0];
        if (registry.has(base) || registry.has(init)) {
          init = '/_' + init;
        }
      }
      return localFetch(init.toString(), {
        ...options,
        headers: Array.isArray(options?.headers) ? new Headers(options?.headers) : options?.headers,
      });
    };

    win.$fetch = createFetch({ fetch: win.fetch, Headers: win.Headers });

    win.__registry = registry;
    win.__app = h3App;

    const { keys, originals } = populateGlobal(global, win, {
      bindFunctions: true,
    });

    const timestamp = Date.now();
    const routeRulesMatcher = toRouteMatcher(
      createRadixRouter({ routes: environmentOptions.nuxtRouteRules || {} }),
    );
    const matcher = exportMatcher(routeRulesMatcher);
    const manifestOutputPath = joinURL(
      '/',
      environmentOptions?.nuxtRuntimeConfig.app?.buildAssetsDir || '_nuxt',
      'builds',
    );
    const manifestBaseRoutePath = joinURL('/_', manifestOutputPath);

    h3App.use(
      `${manifestBaseRoutePath}/latest.json`,
      defineEventHandler(() => ({
        id: 'test',
        timestamp,
      })),
    );
    h3App.use(
      `${manifestBaseRoutePath}/meta/test.json`,
      defineEventHandler(() => ({
        id: 'test',
        timestamp,
        matcher,
        prerendered: [],
      })),
    );
    h3App.use(
      `${manifestBaseRoutePath}/meta/dev.json`,
      defineEventHandler(() => ({
        id: 'test',
        timestamp,
        matcher,
        prerendered: [],
      })),
    );

    registry.add(`${manifestOutputPath}/latest.json`);
    registry.add(`${manifestOutputPath}/meta/test.json`);
    registry.add(`${manifestOutputPath}/meta/dev.json`);

    return {
      teardown() {
        keys.forEach(key => delete global[key]);
        originals.forEach((v, k) => (global[k] = v));
        teardown();
      },
    };
  },
};

This modification ensures that the baseURL defined in the nuxt.config.ts file is correctly handled by Vitest, preventing the 404 error for the specific path [1].

To continue talking to Dosu, mention @dosu.