import { readdir } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' import { joinURL, withQuery } from 'ufo' import { isCI, isWindows } from 'std-env' import { join, normalize } from 'pathe' import { $fetch, createPage, fetch, isDev, setup, startServer, url, useTestContext } from '@nuxt/test-utils' import { $fetchComponent } from '@nuxt/test-utils/experimental' import type { NuxtIslandResponse } from '../packages/nuxt/src/core/runtime/nitro/renderer' import { expectNoClientErrors, expectWithPolling, gotoPath, isRenderingJson, parseData, parsePayload, renderPage } from './utils' const isWebpack = process.env.TEST_BUILDER === 'webpack' const isTestingAppManifest = process.env.TEST_MANIFEST !== 'manifest-off' await setup({ rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)), dev: process.env.TEST_ENV === 'dev', server: true, browser: true, setupTimeout: (isWindows ? 360 : 120) * 1000, nuxtConfig: { builder: isWebpack ? 'webpack' : 'vite', buildDir: process.env.NITRO_BUILD_DIR, nitro: { output: { dir: process.env.NITRO_OUTPUT_DIR } } } }) describe('server api', () => { it('should serialize', async () => { expect(await $fetch('/api/hello')).toBe('Hello API') expect(await $fetch('/api/hey')).toEqual({ foo: 'bar', baz: 'qux' }) }) it('should preserve states', async () => { expect(await $fetch('/api/counter')).toEqual({ count: 0 }) expect(await $fetch('/api/counter')).toEqual({ count: 1 }) expect(await $fetch('/api/counter')).toEqual({ count: 2 }) expect(await $fetch('/api/counter')).toEqual({ count: 3 }) }) it('should auto-import', async () => { const res = await $fetch('/api/auto-imports') expect(res).toMatchInlineSnapshot(` { "autoImported": "utils", "fromServerDir": "test-utils", "thisIs": "serverAutoImported", } `) }) }) describe('route rules', () => { it('should enable spa mode', async () => { const { script, attrs } = parseData(await $fetch('/route-rules/spa')) expect(script.serverRendered).toEqual(false) if (isRenderingJson) { expect(attrs['data-ssr']).toEqual('false') } await expectNoClientErrors('/route-rules/spa') }) it('should allow defining route rules inline', async () => { const res = await fetch('/route-rules/inline') expect(res.status).toEqual(200) expect(res.headers.get('x-extend')).toEqual('added in routeRules') }) it('test noScript routeRules', async () => { const html = await $fetch('/no-scripts') expect(html).not.toContain('
') const indexHtml = await $fetch('/') // should render charset by default expect(indexHtml).toContain('') // should render
components expect(indexHtml).toContain('
') }) it('SSR script setup should render tags', async () => { const headHtml = await $fetch('/head-script-setup') // useHead - title & titleTemplate are working expect(headHtml).toContain('
') // useSeoMeta - template params expect(headHtml).toContain('') // useSeoMeta - refs expect(headHtml).toContain('') // useServerHead - shorthands expect(headHtml).toContain('>/* Custom styles */') // useHeadSafe - removes dangerous content expect(headHtml).toContain('') expect(headHtml).toContain('') }) it('SPA should render appHead tags', async () => { const headHtml = await $fetch('/head', { headers: { 'x-nuxt-no-ssr': '1' } }) expect(headHtml).toContain('') expect(headHtml).toContain('') expect(headHtml).toContain('') }) it('legacy vueuse/head works', async () => { const headHtml = await $fetch('/vueuse-head') expect(headHtml).toContain('
') }) it('should render http-equiv correctly', async () => { const html = await $fetch('/head') // http-equiv should be rendered kebab case expect(html).toContain('') }) // TODO: Doesn't adds header in test environment // it.todo('should render stylesheet link tag (SPA mode)', async () => { // const html = await $fetch('/head', { headers: { 'x-nuxt-no-ssr': '1' } }) // expect(html).toMatch(/ { it('should work with defineNuxtComponent', async () => { const html = await $fetch('/legacy/async-data') expect(html).toContain('
') expect(html).toContain('
') expect(html).toContain('
') const { script } = parseData(html) expect(script.data['options:asyncdata:hello'].hello).toBe('Hello API') expect(Object.values(script.data)).toMatchInlineSnapshot(` [ { "baz": "qux", "foo": "bar", }, { "hello": "Hello API", }, { "fooParent": "fooParent", }, { "fooChild": "fooChild", }, ] `) }) }) describe('navigate', () => { it('should redirect to index with navigateTo', async () => { const { headers, status } = await fetch('/navigate-to/', { redirect: 'manual' }) expect(headers.get('location')).toEqual('/') expect(status).toEqual(301) }) it('respects redirects + headers in middleware', async () => { const res = await fetch('/navigate-some-path/', { redirect: 'manual', headers: { 'trailing-slash': 'true' } }) expect(res.headers.get('location')).toEqual('/navigate-some-path') expect(res.status).toEqual(307) expect(await res.text()).toMatchInlineSnapshot('"