diff --git a/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts b/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts index 926e38159..260fda2c7 100644 --- a/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts +++ b/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts @@ -592,17 +592,17 @@ it('element should be changed', async () => { expect(component.element.tagName).toBe('SPAN') }) -describe('composable state isolation', () => { - const { useCounterMock } = vi.hoisted(() => { - return { - useCounterMock: vi.fn(() => { - return { - isPositive: (): boolean => false, - } - }), - } - }) +const { useCounterMock } = vi.hoisted(() => { + return { + useCounterMock: vi.fn(() => { + return { + isPositive: (): boolean => false, + } + }), + } +}) +describe('composable state isolation', () => { mockNuxtImport('useCounter', () => { return useCounterMock }) diff --git a/examples/app-vitest-workspace/app3/app.vue b/examples/app-vitest-workspace/app3/app.vue new file mode 100644 index 000000000..f8eacfa73 --- /dev/null +++ b/examples/app-vitest-workspace/app3/app.vue @@ -0,0 +1,5 @@ + diff --git a/examples/app-vitest-workspace/app3/app/components/Counter.vue b/examples/app-vitest-workspace/app3/app/components/Counter.vue new file mode 100644 index 000000000..aa788e0a2 --- /dev/null +++ b/examples/app-vitest-workspace/app3/app/components/Counter.vue @@ -0,0 +1,28 @@ + + + diff --git a/examples/app-vitest-workspace/app3/app/composables/useCounter.ts b/examples/app-vitest-workspace/app3/app/composables/useCounter.ts new file mode 100644 index 000000000..e9f598981 --- /dev/null +++ b/examples/app-vitest-workspace/app3/app/composables/useCounter.ts @@ -0,0 +1,11 @@ +import { ref } from '#imports' + +export function useCounter() { + const count = ref(0) + + function increment() { + count.value++ + } + + return { count, increment } +} diff --git a/examples/app-vitest-workspace/app3/app/pages/counter.vue b/examples/app-vitest-workspace/app3/app/pages/counter.vue new file mode 100644 index 000000000..77907e924 --- /dev/null +++ b/examples/app-vitest-workspace/app3/app/pages/counter.vue @@ -0,0 +1,3 @@ + diff --git a/examples/app-vitest-workspace/app3/app/pages/index.vue b/examples/app-vitest-workspace/app3/app/pages/index.vue new file mode 100644 index 000000000..94bb0e7c5 --- /dev/null +++ b/examples/app-vitest-workspace/app3/app/pages/index.vue @@ -0,0 +1,6 @@ + diff --git a/examples/app-vitest-workspace/app3/nuxt.config.ts b/examples/app-vitest-workspace/app3/nuxt.config.ts new file mode 100644 index 000000000..b377d542b --- /dev/null +++ b/examples/app-vitest-workspace/app3/nuxt.config.ts @@ -0,0 +1,8 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + imports: { + autoImport: false, + }, + devtools: { enabled: true }, + compatibilityDate: '2024-04-03', +}) diff --git a/examples/app-vitest-workspace/app3/test/nuxt/mockNuxtImport.spec.ts b/examples/app-vitest-workspace/app3/test/nuxt/mockNuxtImport.spec.ts new file mode 100644 index 000000000..2c77d3e31 --- /dev/null +++ b/examples/app-vitest-workspace/app3/test/nuxt/mockNuxtImport.spec.ts @@ -0,0 +1,51 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { enableAutoUnmount } from '@vue/test-utils' +import { mountSuspended, mockNuxtImport } from '@nuxt/test-utils/runtime' + +import { ref, useCounter } from '#imports' +import { Counter } from '#components' + +mockNuxtImport(useCounter, original => vi.fn(original)) + +describe('mockNuxtImport', () => { + enableAutoUnmount(afterEach) + + beforeEach(() => { + vi.restoreAllMocks() + vi.clearAllMocks() + }) + + it('should mock composable', () => { + vi.mocked(useCounter).mockImplementationOnce(() => ({ + count: ref(100), + increment: vi.fn(), + })) + + const { count, increment } = useCounter() + expect(count.value).toBe(100) + + increment() + expect(vi.mocked(increment)).toHaveBeenCalled() + }) + + it('sould mock composable used by component', async () => { + const increment = vi.fn() + + vi.mocked(useCounter).mockImplementationOnce(() => ({ + count: ref(100), + increment, + })) + + const wrapper = await mountSuspended(Counter) + const input = wrapper.find('input') + expect(input.exists()).toBe(true) + expect(input.element.value).toBe('100') + + const button = wrapper.find('button') + expect(button.exists()).toBe(true) + + await button.trigger('click') + expect(input.element.value).toBe('100') + expect(increment).toHaveBeenCalled() + }) +}) diff --git a/examples/app-vitest-workspace/app3/test/nuxt/mountSuspended.spec.ts b/examples/app-vitest-workspace/app3/test/nuxt/mountSuspended.spec.ts new file mode 100644 index 000000000..fce094e2c --- /dev/null +++ b/examples/app-vitest-workspace/app3/test/nuxt/mountSuspended.spec.ts @@ -0,0 +1,59 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { enableAutoUnmount } from '@vue/test-utils' +import { mountSuspended } from '@nuxt/test-utils/runtime' + +import App from '~~/app.vue' +import { Counter } from '#components' + +describe('mountSuspended', () => { + enableAutoUnmount(afterEach) + + it('should mount page', async () => { + const wrapper = await mountSuspended(App, { + route: '/', + }) + + const title = wrapper.find('h1') + expect(title.text()).toBe('Index') + + const link = wrapper.find('a[href="/counter"]') + expect(link.exists()).toBe(true) + }) + + it('should mount component', async () => { + const wrapper = await mountSuspended(Counter) + + const title = wrapper.find('h2') + expect(title.exists()).toBe(true) + expect(title.text()).toBe('Counter Component') + + const input = wrapper.find('input') + expect(input.exists()).toBe(true) + expect(input.element.value).toBe('0') + }) + + it('should handle event', async () => { + const wrapper = await mountSuspended(Counter) + const input = wrapper.find('input') + + const button = wrapper.find('button') + expect(button.exists()).toBe(true) + + await button.trigger('click') + expect(input.element.value).toBe('1') + }) + + it('should update props', async () => { + const wrapper = await mountSuspended(Counter, { + props: { + title: 'Title', + }, + }) + + const title = wrapper.find('h2') + expect(title.text()).toBe('Title') + + await wrapper.setProps({ title: 'Title(Updated)' }) + expect(title.text()).toBe('Title(Updated)') + }) +}) diff --git a/examples/app-vitest-workspace/app3/test/nuxt/registerEndpoint.spec.ts b/examples/app-vitest-workspace/app3/test/nuxt/registerEndpoint.spec.ts new file mode 100644 index 000000000..9e1beb3fb --- /dev/null +++ b/examples/app-vitest-workspace/app3/test/nuxt/registerEndpoint.spec.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { registerEndpoint } from '@nuxt/test-utils/runtime' + +describe('registerEndpoint', () => { + it('should mock GET endpoint', async () => { + registerEndpoint('/api/test', () => 'test1') + await expect($fetch('/api/test')).resolves.toBe('test1') + }) + + it('should mock POST endpoint', async () => { + registerEndpoint('/api/test', { + method: 'POST', + handler: () => 'test2', + }) + await expect($fetch('/api/test', { method: 'post' })).resolves.toBe('test2') + }) +}) diff --git a/examples/app-vitest-workspace/app3/test/nuxt/renderSuspended.spec.ts b/examples/app-vitest-workspace/app3/test/nuxt/renderSuspended.spec.ts new file mode 100644 index 000000000..77806254d --- /dev/null +++ b/examples/app-vitest-workspace/app3/test/nuxt/renderSuspended.spec.ts @@ -0,0 +1,58 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { fireEvent, cleanup } from '@testing-library/vue' +import { renderSuspended } from '@nuxt/test-utils/runtime' + +import App from '~~/app.vue' +import { Counter } from '#components' + +describe('renderSuspended', () => { + afterEach(() => { + cleanup() + }) + + it('should render page', async () => { + const wrapper = await renderSuspended(App, { + route: '/', + }) + + const title = await wrapper.findByRole('heading', { level: 1 }) + expect(title.textContent).toBe('Index') + + const link = wrapper.findByRole('link', { name: 'Counter' }) + expect((await link).getAttribute('href')).toBe('/counter') + }) + + it('should render component', async () => { + const wrapper = await renderSuspended(Counter) + + const title = await wrapper.findByRole('heading', { level: 2 }) + expect(title.textContent).toBe('Counter Component') + + const input = await wrapper.findByLabelText('Count') + expect(input.value).toBe('0') + }) + + it('should handle event', async () => { + const wrapper = await renderSuspended(Counter) + const input = await wrapper.findByLabelText('Count') + + const button = await wrapper.findByRole('button') + await fireEvent.click(button) + + expect(input.value).toBe('1') + }) + + it('should update props', async () => { + const wrapper = await renderSuspended(Counter, { + props: { + title: 'Title', + }, + }) + + const title = await wrapper.findByRole('heading', { level: 2 }) + expect(title.textContent).toBe('Title') + + await wrapper.rerender({ title: 'Title(Updated)' }) + expect(title.textContent).toBe('Title(Updated)') + }) +}) diff --git a/examples/app-vitest-workspace/app3/tsconfig.json b/examples/app-vitest-workspace/app3/tsconfig.json new file mode 100644 index 000000000..6ae5970c7 --- /dev/null +++ b/examples/app-vitest-workspace/app3/tsconfig.json @@ -0,0 +1,17 @@ +{ + "files": [], + "references": [ + { + "path": "./.nuxt/tsconfig.app.json" + }, + { + "path": "./.nuxt/tsconfig.server.json" + }, + { + "path": "./.nuxt/tsconfig.shared.json" + }, + { + "path": "./.nuxt/tsconfig.node.json" + } + ] +} diff --git a/examples/app-vitest-workspace/app3/vitest.config.ts b/examples/app-vitest-workspace/app3/vitest.config.ts new file mode 100644 index 000000000..e11c7d6d8 --- /dev/null +++ b/examples/app-vitest-workspace/app3/vitest.config.ts @@ -0,0 +1,13 @@ +import { fileURLToPath } from 'node:url' +import { defineVitestProject } from '@nuxt/test-utils/config' + +export default defineVitestProject({ + test: { + name: 'nuxt-app3', + environmentOptions: { + nuxt: { + rootDir: fileURLToPath(new URL('.', import.meta.url)), + }, + }, + }, +}) diff --git a/examples/app-vitest-workspace/package.json b/examples/app-vitest-workspace/package.json index 7363dbc90..551de69f2 100644 --- a/examples/app-vitest-workspace/package.json +++ b/examples/app-vitest-workspace/package.json @@ -7,7 +7,7 @@ "dev": "nuxt dev", "generate": "nuxt generate", "preview": "nuxt preview", - "dev:prepare": "nuxt prepare app1 && nuxt prepare app2", + "dev:prepare": "nuxt prepare app1 && nuxt prepare app2 && nuxt prepare app3", "postinstall": "pnpm dev:prepare", "test": "vitest run" }, diff --git a/examples/app-vitest-workspace/vitest.config.ts b/examples/app-vitest-workspace/vitest.config.ts index c6031905d..7b64d3b1d 100644 --- a/examples/app-vitest-workspace/vitest.config.ts +++ b/examples/app-vitest-workspace/vitest.config.ts @@ -3,5 +3,10 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { projects: ['*/vitest*.config.ts'], + onConsoleLog(log) { + if (log.includes(' is an experimental feature')) { + return false + } + }, }, })