nuxt / test-utils

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

Testing components that use CSS modules is failing #910

Open Nojusle opened 3 months ago

Nojusle commented 3 months ago

Environment

Reproduction

https://codesandbox.io/p/devbox/nuxt-test-with-css-modules-sz2wlg?file=%2Fsandbox.config.json%3A4%2C25-4%2C37

Describe the bug

When running tests on components that uses CSS Modules, the $style attribute is consistently set to undefined, causing the tests to fail.

Additional context

There are no examples in the Nuxt test examples folder that include CSS modules. I've read the documentation for @nuxt/test-utils, and it doesn't mention how to use it with CSS modules. I tried googling it, but I couldn't find any information on how to test Vue components with CSS modules. I only found an unanswered question regarding the same issue, e.g., https://github.com/nuxt/test-utils/discussions/804. Since CSS modules work with Nuxt 'out of the box,' I believe testing components that use CSS modules should also work seamlessly.

Logs

❯ test.nuxt.test.ts (1) 5010ms
   × my test 5009ms

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

 FAIL  test.nuxt.test.ts > my test
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 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 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
TypeError: Cannot read properties of undefined (reading 'someCssModuleClassName')
 ❯ Proxy._sfc_render app.vue:3:20
      1| <template>
      2|   <div :class="$style.someCssModuleClassName">
      3|     <NuxtWelcome />
       |                    ^
      4|   </div>
      5| </template>
 ❯ Proxy.clonedComponent.render node_modules/.pnpm/@nuxt+test-utils@3.14.0_@vue+test-utils@2.4.6_h3@1.12.0_happy-dom@14.12.3_nitropack@2.9.7_pla_3tccdhrwarphrx6od5vsqeppdq/node_modules/@nuxt/test-utils/dist/runtime-utils/index.mjs:142:37
 ❯ renderComponentRoot node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:6267:16
 ❯ ReactiveEffect.componentUpdateFn [as fn] node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4942:46
 ❯ ReactiveEffect.run node_modules/.pnpm/@vue+reactivity@3.4.37/node_modules/@vue/reactivity/dist/reactivity.cjs.js:181:19
 ❯ instance.update node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5073:16
 ❯ setupRenderEffect node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5083:5
 ❯ mountComponent node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4851:7
 ❯ processComponent node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4805:9
 ❯ patch node_modules/.pnpm/@vue+runtime-core@3.4.37/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4334:11

This error originated in "test.nuxt.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
The latest test that might've caused the error is "my test". It might mean one of the following:
- The error was thrown, while Vitest was running this test.
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 Test Files  1 failed (1)
      Tests  1 failed (1)
     Errors  1 error
   Start at  18:00:20
   Duration  5.24s
dosubot[bot] commented 3 months ago

Hey @Nojusle! I'm a bot that can help you solve bugs, answer questions, and become a contributor. I'm here to help you while waiting for a human maintainer.

To resolve the issue of the $style attribute being undefined when testing components that use CSS Modules in your Nuxt 3 project, you need to ensure that the @nuxt/test-utils/module is properly loaded and configured in your nuxt.config file.

Here are the key steps:

  1. Ensure that @nuxt/test-utils/module is added to the modules array in your nuxt.config file:
// nuxt.config.js
export default {
  modules: [
    '@nuxt/test-utils/module',
    // other modules
  ],
  // other configurations
}
  1. Use the startNuxtAndGetViteConfig function to start Nuxt and get the Vite configuration, which includes the necessary setup for handling CSS Modules:
import { startNuxtAndGetViteConfig } from './path/to/config';

async function setup() {
  const { nuxt, viteConfig } = await startNuxtAndGetViteConfig();
  // additional setup if needed
}

setup();

By ensuring that the @nuxt/test-utils/module is properly loaded and configured, the $style attribute should be correctly defined when testing components that use CSS Modules [1].

Additionally, you can mock the $style attribute in your tests to avoid it being undefined. Here is an example of how to mock a component with CSS Modules:

import { mockComponent } from '@nuxt/test-utils/runtime'

// Mock the component and its styles
mockComponent('MyComponent', {
  props: {
    value: String
  },
  setup(props) {
    return {
      $style: {
        myClass: 'mocked-class'
      }
    }
  }
})

// Now you can write your tests
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { it, expect } from 'vitest'
import MyComponent from '~/components/MyComponent.vue'

it('renders with mocked styles', async () => {
  const component = await mountSuspended(MyComponent)
  expect(component.vm.$style.myClass).toBe('mocked-class')
})

In this example, the $style attribute is mocked to return a predefined class name, ensuring that it is not undefined during the tests [2].

To continue talking to Dosu, mention @dosu.

robm-masabi commented 2 months ago

Well, I'm not convinced it's the proper fix solution, but you can get the correct values passed through by modifying this line from...

setup: setup ? (props: Record<string, unknown>) => wrappedSetup(props, setupContext) : undefined,

....to...

setup: setup ? (props: Record<string, unknown>) => wrappedSetup({ ...props, ...component.__cssModules }, setupContext) : undefined,

I say that I don't think its the correct fix, as this then only solve the issue for components with a setup script, but I couldn't expose this via the render context.

robm-masabi commented 2 months ago

OK, so not a fix, but a temporary workaround for anyone else stumbling on this, a small function which wraps mountSuspended and should work as a drop-in replacement to make for easy removal if/when the core bug is fixed.

import { mountSuspended } from '@nuxt/test-utils/runtime'

export const mountWithNuxt: typeof mountSuspended = async (component, options) => {
  return mountSuspended(component, {
    ...options,
    // @ts-expect-error Vue's TS doesn't like us specifying $style as an external prop
    props: {
      ...options?.props,
      $style: {},
    },
    attrs: {
      ...options?.attrs,
      $style: undefined,
    },
  })
}
Nojusle commented 2 months ago

Great, the workaround works for me. That's all I needed, thank you!

kier-mc commented 1 month ago

Thanks @robm-masabi for the workaround.

I've been playing around with this locally for the last hour or so and I've noticed that this only seems to occur if you use $style to pass the module class in the template. If you declare or pass the style via <script setup> in the component being tested, the tests should run as expected:

<script setup lang="ts">
  // works with no changes in the template
    const $style = useCssModules();

  // ...or use :class="elementStyle" in the template
    const styles = useCssModules();
    const elementStyle = styles.elementClass;
</script>