-
Notifications
You must be signed in to change notification settings - Fork 101
Open
Labels
Description
Environment
- Operating System: Linux
- Node Version: v18.20.3
- Nuxt Version: 3.15.4
- CLI Version: 3.21.1
- Nitro Version: 2.10.4
- Package Manager: [email protected]
- Builder: -
- User Config: compatibilityDate, devtools
- Runtime Modules: -
- Build Modules: -
Reproduction
Minimal Reproduction - https://stackblitz.com/edit/github-remjha3q?file=app.vue
- Open
app.vue
andapp.spec.ts
files - Open a new Terminal and run
npm run rest
Describe the bug
When testing a Vue component using mountSuspended
from @nuxt/test-utils/runtime
, computed properties fail to update reactively when their dependencies change. The same test cases pass when using @vue/test-utils
' mount
or shallowMount
methods.
Current Behavior:
- Data property
foo
updates correctly - Computed property
bar
remains stale with its initial value - Computed property
baz
remains stale with its initial value
Expected Behavior:
- When
foo
changes totrue
:foo
property should updatebar
should update to"(bar = true)"
baz
should update to"baz => (bar = true)"
Workaround:
Using standard @vue/test-utils
mounting methods works as expected:
// These alternatives work correctly
return shallowMount(App, {});
// or
return mount(App, {});
Files
package.json
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"test": "vitest"
},
"dependencies": {
"nuxt": "^3.15.4",
"vue": "latest",
"vue-router": "latest"
},
"devDependencies": {
"@nuxt/test-utils": "^3.15.4",
"@vue/test-utils": "^2.4.6",
"happy-dom": "^16.6.0",
"jsdom": "^26.0.0",
"vitest": "^3.0.5"
}
}
vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config';
export default defineVitestConfig({
test: {
environment: 'nuxt',
environmentOptions: {
nuxt: {
domEnvironment: 'jsdom', // 'happy-dom' (default) or 'jsdom'
},
},
globals: true,
},
});
app.vue
<template>
<div>{{ baz }}</div>
</template>
<script lang="ts">
export default defineNuxtComponent({
name: 'UnitTestsMountSuspendedComputedValuesBug',
data() {
return {
foo: false,
};
},
computed: {
bar() {
return this.foo ? '(bar = true)' : '(bar = false)';
},
baz() {
return 'baz => ' + this.bar;
},
},
});
</script>
app.spec.ts
import { describe, beforeEach, it, expect } from 'vitest';
import { mountSuspended } from '@nuxt/test-utils/runtime';
import { shallowMount, mount } from '@vue/test-utils';
import App from './app.vue';
import { VueWrapper } from '@vue/test-utils';
let wrapper: VueWrapper<InstanceType<typeof App>>;
const mountComponent = async () => {
// return shallowMount(App, { // this works as expected
// return mount(App, { // this works as expected
return await mountSuspended(App, { // this doesn't work as expected!
// data() { // pre-setting data while using moutSuspended doesn't work as well! (it works fine with other methods though)
// return {
// foo: true,
// };
// },
});
};
describe('AppMountSuspendedComputedValuesBug', () => {
beforeEach(async () => {
wrapper = await mountComponent();
});
// Toggling foo (data property) works as expected
describe('foo', () => {
it('should equal false by default', () => {
expect(wrapper.vm.foo).toBe(false);
});
it('should equal true if changed using wrapper.vm.foo = true', async () => {
wrapper.vm.foo = true;
await wrapper.vm.$nextTick();
expect(wrapper.vm.foo).toBe(true);
});
});
// bar (coumputed value) doesn't update when foo (data property) changes
describe('bar', () => {
it('should return "(bar = false)" by default', () => {
expect(wrapper.vm.bar).toBe('(bar = false)');
});
it('should return "(bar = true)" if this.foo === true', async () => {
wrapper.vm.foo = true;
await wrapper.vm.$nextTick();
expect(wrapper.vm.bar).toBe('(bar = true)');
});
});
// Obviously, baz (computed value) won't update becasue bar (computed value) doesn't update
describe('baz', () => {
it('should return "baz => (bar = false)" by default', () => {
expect(wrapper.vm.baz).toBe('baz => (bar = false)');
});
it('should return "baz => (bar = true)" if this.foo === true', async () => {
wrapper.vm.foo = true;
await wrapper.vm.$nextTick();
expect(wrapper.vm.baz).toBe('baz => (bar = true)');
});
});
});
Additional context
Pre-setting initial data values through the mount options also doesn't work with mountSuspended
, while it works fine with other mounting methods.
Logs
RERUN app.spec.ts x1
❯ app.spec.ts (6 tests | 2 failed) 234ms
✓ AppMountSuspendedComputedValuesBug > foo > should equal false by default
✓ AppMountSuspendedComputedValuesBug > foo > should equal true if changed using wrapper.vm.foo = true
✓ AppMountSuspendedComputedValuesBug > bar > should return "(bar = false)" by default
× AppMountSuspendedComputedValuesBug > bar > should return "(bar = true)" if this.foo === true 44ms
→ expected '(bar = false)' to be '(bar = true)' // Object.is equality
✓ AppMountSuspendedComputedValuesBug > baz > should return "baz => (bar = false)" by default
× AppMountSuspendedComputedValuesBug > baz > should return "baz => (bar = true)" if this.foo === true 36ms
→ expected 'baz => (bar = false)' to be 'baz => (bar = true)' // Object.is equality
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
FAIL app.spec.ts > AppMountSuspendedComputedValuesBug > bar > should return "(bar = true)" if this.foo === true
AssertionError: expected '(bar = false)' to be '(bar = true)' // Object.is equality
Expected: "(bar = true)"
Received: "(bar = false)"
❯ eval app.spec.ts:48:30
46| wrapper.vm.foo = true;
47| await wrapper.vm.$nextTick();
48| expect(wrapper.vm.bar).toBe('(bar = true)');
| ^
49| });
50| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯
FAIL app.spec.ts > AppMountSuspendedComputedValuesBug > baz > should return "baz => (bar = true)" if this.foo === true
AssertionError: expected 'baz => (bar = false)' to be 'baz => (bar = true)' // Object.is equality
Expected: "baz => (bar = true)"
Received: "baz => (bar = false)"
❯ eval app.spec.ts:61:30
59| wrapper.vm.foo = true;
60| await wrapper.vm.$nextTick();
61| expect(wrapper.vm.baz).toBe('baz => (bar = true)');
| ^
62| });
63| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯
Test Files 1 failed (1)
Tests 2 failed | 4 passed (6)
Start at 16:28:03
Duration 730ms
FAIL Tests failed. Watching for file changes...
press h to show help, press q to quit
Reiss-Cashmore and valgeirb