diff --git a/examples/app-vitest-full/tests/nuxt/auto-import.spec.ts b/examples/app-vitest-full/tests/nuxt/auto-import.spec.ts index d916aa368..4ddccdefe 100644 --- a/examples/app-vitest-full/tests/nuxt/auto-import.spec.ts +++ b/examples/app-vitest-full/tests/nuxt/auto-import.spec.ts @@ -1,6 +1,20 @@ -import { expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' -it('should not mock', () => { - expect(useAutoImportedTarget()).toMatchInlineSnapshot('"the original"') - expect(useAutoImportedNonTarget()).toMatchInlineSnapshot('"the original"') +describe('auto-imports', () => { + it('can use core nuxt composables within test file', () => { + expect(useAppConfig().hey).toMatchInlineSnapshot('false') + }) + + it('can access auto-imported composables from within project', () => { + const state = useSingleState() + expect(state.value).toMatchInlineSnapshot('{}') + state.value.field = 'new value' + expect(state.value.field).toMatchInlineSnapshot('"new value"') + expect(useSingleState().value.field).toMatchInlineSnapshot('"new value"') + }) + + it('should not mock imports that are mocked in another test file', () => { + expect(useAutoImportedTarget()).toMatchInlineSnapshot('"the original"') + expect(useAutoImportedNonTarget()).toMatchInlineSnapshot('"the original"') + }) }) diff --git a/examples/app-vitest-full/tests/nuxt/export-define-component.spec.ts b/examples/app-vitest-full/tests/nuxt/export-define-component.spec.ts deleted file mode 100644 index fd9825df1..000000000 --- a/examples/app-vitest-full/tests/nuxt/export-define-component.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { expect, it } from 'vitest' -import { mountSuspended } from '@nuxt/test-utils/runtime-utils' -import ExportDefaultComponent from '~/components/ExportDefaultComponent.vue' -import ExportDefineComponent from '~/components/ExportDefineComponent.vue' -import ExportDefaultWithRenderComponent from '~/components/ExportDefaultWithRenderComponent.vue' -import ExportDefaultReturnsRenderComponent from '~/components/ExportDefaultReturnsRenderComponent.vue' - -it('should support export default defineComponent', async () => { - const component = await mountSuspended(ExportDefineComponent, { - props: { - myProp: 'Hello nuxt-vitest', - }, - }) - expect(component.html()).toMatchInlineSnapshot(` - "
-

ExportDefineComponent

Hello nuxt-vitest
XHello nuxt-vitest
-
" - `) -}) - -it('should support export default without setup script', async () => { - const component = await mountSuspended(ExportDefaultComponent, { - props: { - myProp: 'Hello nuxt-vitest', - }, - }) - expect(component.html()).toMatchInlineSnapshot(` - "
-

ExportDefaultComponent

Hello nuxt-vitest
XHello nuxt-vitest
-
" - `) -}) - -it('should support export default with render function', async () => { - const component = await mountSuspended(ExportDefaultWithRenderComponent, { - props: { - myProp: 'Hello nuxt-vitest', - }, - }) - expect(component.html()).toMatchInlineSnapshot(` - "
-

ExportDefaultWithRenderComponent

Hello nuxt-vitest
XHello nuxt-vitest
-
" - `) -}) - -it('should support export default that returns render function', async () => { - const component = await mountSuspended(ExportDefaultReturnsRenderComponent, { - props: { - myProp: 'Hello nuxt-vitest', - }, - }) - expect(component.html()).toMatchInlineSnapshot(` - "
-

ExportDefaultReturnsRenderComponent

Hello nuxt-vitest
XHello nuxt-vitest
-
" - `) -}) diff --git a/examples/app-vitest-full/tests/nuxt/index.spec.ts b/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts similarity index 50% rename from examples/app-vitest-full/tests/nuxt/index.spec.ts rename to examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts index fc50ad847..20c630f37 100644 --- a/examples/app-vitest-full/tests/nuxt/index.spec.ts +++ b/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts @@ -1,53 +1,26 @@ import { describe, expect, it } from 'vitest' -import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime-utils' - -import { listen } from 'listhen' -import { createApp, eventHandler, toNodeListener } from 'h3' +import { mountSuspended } from '@nuxt/test-utils/runtime-utils' import App from '~/app.vue' -import FetchComponent from '~/components/FetchComponent.vue' import OptionsComponent from '~/components/OptionsComponent.vue' import WrapperTests from '~/components/WrapperTests.vue' import { mount } from '@vue/test-utils' -describe('client-side nuxt features', () => { - it('can use core nuxt composables within test file', () => { - expect(useAppConfig().hey).toMatchInlineSnapshot('false') - }) +import ExportDefaultComponent from '~/components/ExportDefaultComponent.vue' +import ExportDefineComponent from '~/components/ExportDefineComponent.vue' +import ExportDefaultWithRenderComponent from '~/components/ExportDefaultWithRenderComponent.vue' +import ExportDefaultReturnsRenderComponent from '~/components/ExportDefaultReturnsRenderComponent.vue' - it('can access auto-imported composables from within project', () => { - const state = useSingleState() - expect(state.value).toMatchInlineSnapshot('{}') - state.value.field = 'new value' - expect(state.value.field).toMatchInlineSnapshot('"new value"') - expect(useSingleState().value.field).toMatchInlineSnapshot('"new value"') - }) - - it('can access injections from nuxt plugins', () => { - const app = useNuxtApp() - expect(app.$auth.didInject).toMatchInlineSnapshot('true') - expect(app.$router).toBeDefined() - }) +const formats = { + ExportDefaultComponent, + ExportDefineComponent, + ExportDefaultWithRenderComponent, + ExportDefaultReturnsRenderComponent, +} - it('defaults to index page', async () => { - expect(useRoute().matched[0].meta).toMatchInlineSnapshot(` - { - "value": "set in index", - } - `) - }) - - it('allows pushing to other pages', async () => { - await navigateTo('/something') - expect(useNuxtApp().$router.currentRoute.value.path).toEqual('/something') - await nextTick() - expect(useRoute().path).toEqual('/something') - }) -}) - -describe('test utils', () => { +describe('mountSuspended', () => { it('can mount components within nuxt suspense', async () => { const component = await mountSuspended(App) expect(component.html()).toMatchInlineSnapshot(` @@ -114,19 +87,6 @@ describe('test utils', () => { `) }) - it('can use $fetch', async () => { - const app = createApp().use( - '/todos/1', - eventHandler(() => ({ id: 1 })) - ) - const server = await listen(toNodeListener(app)) - const [{ url }] = await server.getURLs() - expect(await $fetch('/todos/1', { baseURL: url })).toMatchObject({ - id: 1, - }) - await server.close() - }) - // This test works (you can delete it later) it('can receive emitted events from components using defineModel', () => { const component = mount(WrapperTests) @@ -147,51 +107,19 @@ describe('test utils', () => { expect(component.vm.testExpose?.()).toBe('thing') expect(component.vm.someRef).toBe('thing') }) +}) - it('can mock fetch requests', async () => { - registerEndpoint('https://jsonplaceholder.typicode.com/todos/1', () => ({ - title: 'title from mocked api', - })) - const component = await mountSuspended(FetchComponent) - expect(component.html()).toMatchInlineSnapshot( - '"
title from mocked api
"' - ) - }) - - it('can mock fetch requests', async () => { - registerEndpoint('/with-query', () => ({ - title: 'mocked', - })) - expect( - await $fetch('/with-query', { query: { test: true } }) - ).toMatchObject({ - title: 'mocked', - }) - }) - - it('can mock fetch requests with explicit methods', async () => { - registerEndpoint('/method', { - method: 'POST', - handler: () => ({ method: 'POST' }), - }) - registerEndpoint('/method', { - method: 'GET', - handler: () => ({ method: 'GET' }), - }) - expect(await $fetch('/method', { method: 'POST' })).toMatchObject({ - method: 'POST', +describe.each(Object.entries(formats))(`%s`, (name, component) => { + it('mounts with props', async () => { + const wrapper = await mountSuspended(component, { + props: { + myProp: 'Hello nuxt-vitest', + }, }) - expect(await $fetch('/method')).toMatchObject({ method: 'GET' }) - }) - - // TODO: reenable when merging Nuxt 3.7 - it.skip('handles nuxt routing', async () => { - const component = await mountSuspended(App, { route: '/test' }) - expect(component.html()).toMatchInlineSnapshot(` - "
This is an auto-imported component
-
I am a global component
-
/test
- Test link " - `) + expect(wrapper.html()).toEqual(` +
+

${name}

Hello nuxt-vitest
XHello nuxt-vitest
+
+ `.trim()) }) }) diff --git a/examples/app-vitest-full/tests/nuxt/plugins.spec.ts b/examples/app-vitest-full/tests/nuxt/plugins.spec.ts new file mode 100644 index 000000000..4439fd485 --- /dev/null +++ b/examples/app-vitest-full/tests/nuxt/plugins.spec.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest' + +describe('plugins', () => { + it('can access injections', () => { + const app = useNuxtApp() + expect(app.$auth.didInject).toMatchInlineSnapshot('true') + expect(app.$router).toBeDefined() + }) +}) diff --git a/examples/app-vitest-full/tests/nuxt/render-suspended.spec.ts b/examples/app-vitest-full/tests/nuxt/render-suspended.spec.ts new file mode 100644 index 000000000..0bae98d42 --- /dev/null +++ b/examples/app-vitest-full/tests/nuxt/render-suspended.spec.ts @@ -0,0 +1,94 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { renderSuspended } from '@nuxt/test-utils/runtime-utils' +import { cleanup, fireEvent, screen } from '@testing-library/vue' + +import App from '~/app.vue' +import OptionsComponent from '~/components/OptionsComponent.vue' +import WrapperTests from '~/components/WrapperTests.vue' + +describe('renderSuspended', () => { + afterEach(() => { + // since we're not running with Vitest globals when running the tests + // from inside the test server. This means testing-library cannot + // auto-attach the cleanup go testing globals, and we have to do + // it here manually. + if (process.env.NUXT_VITEST_DEV_TEST) { + cleanup() + } + }) + + it('can render components within nuxt suspense', async () => { + const { html } = await renderSuspended(App) + expect(html()).toMatchInlineSnapshot(` + "
+
This is an auto-imported component
+
I am a global component
+
Index page
Test link +
" + `) + }) + + it('should render default props within nuxt suspense', async () => { + await renderSuspended(OptionsComponent) + expect(screen.getByRole('heading', { level: 2 })).toMatchInlineSnapshot( + ` +

+ The original +

+ ` + ) + }) + + it('should render passed props within nuxt suspense', async () => { + await renderSuspended(OptionsComponent, { + props: { + title: 'title from mount suspense props', + }, + }) + expect(screen.getByRole('heading', { level: 2 })).toMatchInlineSnapshot( + ` +

+ title from mount suspense props +

+ ` + ) + }) + + it('can pass slots to rendered components within nuxt suspense', async () => { + const text = 'slot from mount suspense' + await renderSuspended(OptionsComponent, { + slots: { + default: () => text, + }, + }) + expect(screen.getByText(text)).toBeDefined() + }) + + it('can receive emitted events from components rendered within nuxt suspense', async () => { + const { emitted } = await renderSuspended(WrapperTests) + const button = screen.getByRole('button', { name: 'Click me!' }) + await fireEvent.click(button) + + const emittedEvents = emitted() + expect(emittedEvents.click).toMatchObject( + expect.arrayContaining([ + expect.arrayContaining([expect.objectContaining({ type: 'click' })]), + ]) + ) + + // since this is a native event it doesn't serialize well + delete emittedEvents.click + expect(emittedEvents).toMatchInlineSnapshot(` + { + "customEvent": [ + [ + "foo", + ], + ], + "otherEvent": [ + [], + ], + } + `) + }) +}) diff --git a/examples/app-vitest-full/tests/nuxt/routing.spec.ts b/examples/app-vitest-full/tests/nuxt/routing.spec.ts new file mode 100644 index 000000000..01be68ab7 --- /dev/null +++ b/examples/app-vitest-full/tests/nuxt/routing.spec.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' + +import { mountSuspended } from '@nuxt/test-utils/runtime-utils' + +import App from '~/app.vue' + +describe('routing', () => { + it('defaults to index page', async () => { + expect(useRoute().matched[0].meta).toMatchInlineSnapshot(` + { + "value": "set in index", + } + `) + }) + + it('allows pushing to other pages', async () => { + await navigateTo('/something') + expect(useNuxtApp().$router.currentRoute.value.path).toEqual('/something') + await nextTick() + expect(useRoute().path).toEqual('/something') + }) + + it('handles nuxt routing', async () => { + const component = await mountSuspended(App, { route: '/test' }) + expect(component.html()).toMatchInlineSnapshot(` + "
This is an auto-imported component
+
I am a global component
+
/test
+ Test link " + `) + }) +}) diff --git a/examples/app-vitest-full/tests/nuxt/server.spec.ts b/examples/app-vitest-full/tests/nuxt/server.spec.ts new file mode 100644 index 000000000..e28f95e72 --- /dev/null +++ b/examples/app-vitest-full/tests/nuxt/server.spec.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' + +import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime-utils' + +import { listen } from 'listhen' +import { createApp, eventHandler, toNodeListener } from 'h3' + +import FetchComponent from '~/components/FetchComponent.vue' + +describe('server mocks and data fetching', () => { + it('can use $fetch', async () => { + const app = createApp().use( + '/todos/1', + eventHandler(() => ({ id: 1 })) + ) + const server = await listen(toNodeListener(app)) + const [{ url }] = await server.getURLs() + expect(await $fetch('/todos/1', { baseURL: url })).toMatchObject({ + id: 1, + }) + await server.close() + }) + + it('can mock fetch requests', async () => { + registerEndpoint('https://jsonplaceholder.typicode.com/todos/1', () => ({ + title: 'title from mocked api', + })) + const component = await mountSuspended(FetchComponent) + expect(component.html()).toMatchInlineSnapshot( + '"
title from mocked api
"' + ) + }) + + it('can mock fetch requests', async () => { + registerEndpoint('/with-query', () => ({ + title: 'mocked', + })) + expect( + await $fetch('/with-query', { query: { test: true } }) + ).toMatchObject({ + title: 'mocked', + }) + }) + + it('can mock fetch requests with explicit methods', async () => { + registerEndpoint('/method', { + method: 'POST', + handler: () => ({ method: 'POST' }), + }) + registerEndpoint('/method', { + method: 'GET', + handler: () => ({ method: 'GET' }), + }) + expect(await $fetch('/method', { method: 'POST' })).toMatchObject({ + method: 'POST', + }) + expect(await $fetch('/method')).toMatchObject({ method: 'GET' }) + }) +}) diff --git a/examples/app-vitest-full/tests/nuxt/utils-render.spec.ts b/examples/app-vitest-full/tests/nuxt/utils-render.spec.ts deleted file mode 100644 index a056c358f..000000000 --- a/examples/app-vitest-full/tests/nuxt/utils-render.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { renderSuspended } from '@nuxt/test-utils/runtime-utils' -import { cleanup, fireEvent, screen } from '@testing-library/vue' - -import App from '~/app.vue' -import OptionsComponent from '~/components/OptionsComponent.vue' -import WrapperTests from '~/components/WrapperTests.vue' - -describe('test utils', () => { - describe('renderSuspended', () => { - afterEach(() => { - // since we're not running with Vitest globals when running the tests - // from inside the test server. This means testing-library cannot - // auto-attach the cleanup go testing globals, and we have to do - // it here manually. - if (process.env.NUXT_VITEST_DEV_TEST) { - cleanup() - } - }) - - it('can render components within nuxt suspense', async () => { - const { html } = await renderSuspended(App) - expect(html()).toMatchInlineSnapshot(` - "
-
This is an auto-imported component
-
I am a global component
-
Index page
Test link -
" - `) - }) - - it('should render default props within nuxt suspense', async () => { - await renderSuspended(OptionsComponent) - expect(screen.getByRole('heading', { level: 2 })).toMatchInlineSnapshot( - ` -

- The original -

- ` - ) - }) - - it('should render passed props within nuxt suspense', async () => { - await renderSuspended(OptionsComponent, { - props: { - title: 'title from mount suspense props', - }, - }) - expect(screen.getByRole('heading', { level: 2 })).toMatchInlineSnapshot( - ` -

- title from mount suspense props -

- ` - ) - }) - - it('can pass slots to rendered components within nuxt suspense', async () => { - const text = 'slot from mount suspense' - await renderSuspended(OptionsComponent, { - slots: { - default: () => text, - }, - }) - expect(screen.getByText(text)).toBeDefined() - }) - - it('can receive emitted events from components rendered within nuxt suspense', async () => { - const { emitted } = await renderSuspended(WrapperTests) - const button = screen.getByRole('button', { name: 'Click me!' }) - await fireEvent.click(button) - - const emittedEvents = emitted() - expect(emittedEvents.click).toMatchObject( - expect.arrayContaining([ - expect.arrayContaining([expect.objectContaining({ type: 'click' })]), - ]) - ) - - // since this is a native event it doesn't serialize well - delete emittedEvents.click - expect(emittedEvents).toMatchInlineSnapshot(` - { - "customEvent": [ - [ - "foo", - ], - ], - "otherEvent": [ - [], - ], - } - `) - }) - }) -})