diff --git a/package.json b/package.json index 86769bebc4..06f8856687 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "play:build": "nuxi build playground", "play:preview": "nuxi preview playground", "test": "pnpm test:fixtures && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm test:runtime && pnpm test:types && pnpm typecheck", - "test:fixtures": "nuxi prepare test/fixtures/basic && nuxi prepare test/fixtures/runtime-compiler && vitest run --dir test", + "test:prepare": "jiti ./test/prepare.ts", + "test:fixtures": "pnpm test:prepare && vitest run --dir test", "test:fixtures:dev": "TEST_ENV=dev pnpm test:fixtures", "test:fixtures:webpack": "TEST_BUILDER=webpack pnpm test:fixtures", "test:runtime": "vitest -c vitest.nuxt.config.ts --coverage", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82853c3877..b9fa1b2453 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -879,6 +879,19 @@ importers: specifier: workspace:* version: link:../../../packages/nuxt + test/fixtures/suspense: + dependencies: + nuxt: + specifier: workspace:* + version: link:../../../packages/nuxt + vue: + specifier: 3.4.14 + version: 3.4.14(typescript@5.3.3) + devDependencies: + typescript: + specifier: latest + version: 5.3.3 + packages: /@aashutoshrathi/word-wrap@1.2.6: diff --git a/test/fixtures/suspense/app.vue b/test/fixtures/suspense/app.vue new file mode 100644 index 0000000000..8f62b8bf92 --- /dev/null +++ b/test/fixtures/suspense/app.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/suspense/nuxt.config.ts b/test/fixtures/suspense/nuxt.config.ts new file mode 100644 index 0000000000..c7e627e35e --- /dev/null +++ b/test/fixtures/suspense/nuxt.config.ts @@ -0,0 +1,14 @@ +import { fileURLToPath } from 'node:url' + +const testWithInlineVue = process.env.EXTERNAL_VUE === 'false' + +export default defineNuxtConfig({ + experimental: { + externalVue: !testWithInlineVue + }, + buildDir: testWithInlineVue ? '.nuxt-inline' : '.nuxt', + nitro: { + output: { dir: fileURLToPath(new URL(testWithInlineVue ? './.output-inline' : './.output', import.meta.url)) } + }, + sourcemap: false +}) diff --git a/test/fixtures/suspense/package.json b/test/fixtures/suspense/package.json new file mode 100644 index 0000000000..2f5e19ed19 --- /dev/null +++ b/test/fixtures/suspense/package.json @@ -0,0 +1,14 @@ +{ + "private": true, + "name": "fixture-minimal", + "scripts": { + "build": "nuxi build" + }, + "dependencies": { + "nuxt": "workspace:*", + "vue": "latest" + }, + "devDependencies": { + "typescript": "latest" + } +} diff --git a/test/fixtures/suspense/pages/index.vue b/test/fixtures/suspense/pages/index.vue new file mode 100644 index 0000000000..bc377b646d --- /dev/null +++ b/test/fixtures/suspense/pages/index.vue @@ -0,0 +1,29 @@ + + + diff --git a/test/fixtures/suspense/pages/target.vue b/test/fixtures/suspense/pages/target.vue new file mode 100644 index 0000000000..4a0e8fa21b --- /dev/null +++ b/test/fixtures/suspense/pages/target.vue @@ -0,0 +1,11 @@ + + + diff --git a/test/fixtures/suspense/tsconfig.json b/test/fixtures/suspense/tsconfig.json new file mode 100644 index 0000000000..4b34df1571 --- /dev/null +++ b/test/fixtures/suspense/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.nuxt/tsconfig.json" +} diff --git a/test/prepare.ts b/test/prepare.ts new file mode 100644 index 0000000000..845ec57c5f --- /dev/null +++ b/test/prepare.ts @@ -0,0 +1,25 @@ +import { fileURLToPath } from 'node:url' +import { globby } from 'globby' +import fs from 'fs-extra' +import { execa } from 'execa' + +async function initTesting () { + const dirs = await globby('*', { + onlyDirectories: true, + cwd: fileURLToPath(new URL('./fixtures', import.meta.url)), + absolute: true + }) + + await Promise.all([ + // clear nuxt build files + ...dirs.map(dir => fs.remove(`${dir}/.nuxt`)), + // clear vite cache + ...dirs.map(dir => fs.remove(`${dir}/node_modules/.cache`), { force: true }) + ]) + + await Promise.all( + dirs.map(dir => execa('pnpm', ['nuxi', 'prepare'], { cwd: dir })) + ) +} + +initTesting() diff --git a/test/suspense.test.ts b/test/suspense.test.ts new file mode 100644 index 0000000000..70589e806e --- /dev/null +++ b/test/suspense.test.ts @@ -0,0 +1,53 @@ +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' +import { isWindows } from 'std-env' +import { setup } from '@nuxt/test-utils' +import { renderPage } from './utils' + +const isWebpack = process.env.TEST_BUILDER === 'webpack' + +await setup({ + rootDir: fileURLToPath(new URL('./fixtures/suspense', 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('suspense multiple nav', () => { + it('should not throw error', async () => { + const { page, consoleLogs, pageErrors } = await renderPage('/') + await page.waitForLoadState('networkidle') + + expect(await page.locator('#btn-a').textContent()).toMatchInlineSnapshot('" Target A "') + // Make sure it navigates to the correct page + await page.locator('#btn-a').click() + console.log(page.url()) + expect(await page.locator('#content').textContent()).toContain('Hello a') + await page.goBack() + + // When back + expect(await page.locator('body').textContent()).toContain('Index Page') + + // So we click two navigations quickly, before the first one is resolved + await Promise.all([ + page.locator('#btn-a').click(), + page.locator('#btn-b').click() + ]) + + expect.soft(await page.locator('#content').textContent()).toContain('Hello b') + + const consoleLogErrors = consoleLogs.filter(i => i.type === 'error') + const consoleLogWarnings = consoleLogs.filter(i => i.type === 'warning') + expect.soft(pageErrors).toEqual([]) + expect.soft(consoleLogErrors).toEqual([]) + expect.soft(consoleLogWarnings).toEqual([]) + + await page.close() + }, 60_000) +})