mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
test: refactor test suite and reduce networkidle
dependency (#22596)
This commit is contained in:
parent
2a4867a23f
commit
e93195a317
@ -8,7 +8,7 @@ import { $fetch, createPage, fetch, isDev, setup, startServer, url, useTestConte
|
||||
import { $fetchComponent } from '@nuxt/test-utils/experimental'
|
||||
|
||||
import type { NuxtIslandResponse } from '../packages/nuxt/src/core/runtime/nitro/renderer'
|
||||
import { expectNoClientErrors, expectWithPolling, isRenderingJson, parseData, parsePayload, renderPage, withLogs } from './utils'
|
||||
import { expectNoClientErrors, expectWithPolling, gotoPath, isRenderingJson, parseData, parsePayload, renderPage } from './utils'
|
||||
|
||||
const isWebpack = process.env.TEST_BUILDER === 'webpack'
|
||||
|
||||
@ -17,7 +17,7 @@ await setup({
|
||||
dev: process.env.TEST_ENV === 'dev',
|
||||
server: true,
|
||||
browser: true,
|
||||
setupTimeout: (isWindows ? 240 : 120) * 1000,
|
||||
setupTimeout: (isWindows ? 360 : 120) * 1000,
|
||||
nuxtConfig: {
|
||||
builder: isWebpack ? 'webpack' : 'vite',
|
||||
buildDir: process.env.NITRO_BUILD_DIR,
|
||||
@ -53,9 +53,8 @@ describe('route rules', () => {
|
||||
})
|
||||
|
||||
it('test noScript routeRules', async () => {
|
||||
const page = await createPage('/no-scripts')
|
||||
expect(await page.locator('script').all()).toHaveLength(0)
|
||||
await page.close()
|
||||
const html = await $fetch('/no-scripts')
|
||||
expect(html).not.toContain('<script')
|
||||
})
|
||||
})
|
||||
|
||||
@ -134,13 +133,12 @@ describe('pages', () => {
|
||||
expect(status).toEqual(404)
|
||||
expect(headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/')
|
||||
|
||||
const page = await createPage('/navigate-to-forbidden')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/navigate-to-forbidden')
|
||||
|
||||
await page.getByText('should throw a 404 error').click()
|
||||
expect(await page.getByRole('heading').textContent()).toMatchInlineSnapshot('"Page Not Found: /forbidden"')
|
||||
|
||||
await page.goto(url('/navigate-to-forbidden'))
|
||||
await page.waitForLoadState('networkidle')
|
||||
await gotoPath(page, '/navigate-to-forbidden')
|
||||
await page.getByText('should be caught by catchall').click()
|
||||
expect(await page.getByRole('heading').textContent()).toMatchInlineSnapshot('"[...slug].vue"')
|
||||
|
||||
@ -172,22 +170,21 @@ describe('pages', () => {
|
||||
|
||||
it('expect no loading indicator on middleware abortNavigation', async () => {
|
||||
const { page } = await renderPage('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.locator('#middleware-abort-non-fatal').click()
|
||||
expect(await page.locator('#lodagin-indicator').all()).toHaveLength(0)
|
||||
await page.locator('#middleware-abort-non-fatal-error').click()
|
||||
expect(await page.locator('#lodagin-indicator').all()).toHaveLength(0)
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('should render correctly when loaded on a different path', async () => {
|
||||
const page = await createPage('/proxy')
|
||||
const { page, pageErrors } = await renderPage('/proxy')
|
||||
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.innerText('body')).toContain('Composable | foo: auto imported from ~/composables/foo.ts')
|
||||
|
||||
await page.close()
|
||||
|
||||
await expectNoClientErrors('/proxy')
|
||||
expect(pageErrors).toEqual([])
|
||||
})
|
||||
|
||||
it('preserves query', async () => {
|
||||
@ -265,11 +262,7 @@ describe('pages', () => {
|
||||
// ensure components are not rendered server-side
|
||||
expect(html).not.toContain('Should not be server rendered')
|
||||
|
||||
await expectNoClientErrors('/client-only-components')
|
||||
|
||||
const page = await createPage('/client-only-components')
|
||||
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, pageErrors } = await renderPage('/client-only-components')
|
||||
|
||||
const hiddenSelectors = [
|
||||
'.string-stateful-should-be-hidden',
|
||||
@ -331,25 +324,16 @@ describe('pages', () => {
|
||||
await Promise.all(hiddenSelectors.map(selector => page.locator(selector).isVisible()))
|
||||
.then(results => results.forEach(isVisible => expect(isVisible).toBeTruthy()))
|
||||
|
||||
expect(pageErrors).toEqual([])
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('/wrapper-expose/layout', async () => {
|
||||
await expectNoClientErrors('/wrapper-expose/layout')
|
||||
|
||||
let lastLog: string|undefined
|
||||
const page = await createPage('/wrapper-expose/layout')
|
||||
page.on('console', (log) => {
|
||||
lastLog = log.text()
|
||||
})
|
||||
page.on('pageerror', (log) => {
|
||||
lastLog = log.message
|
||||
})
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, consoleLogs, pageErrors } = await renderPage('/wrapper-expose/layout')
|
||||
await page.locator('.log-foo').first().click()
|
||||
expect(lastLog).toContain('.logFoo is not a function')
|
||||
expect(pageErrors.at(-1)?.toString() || consoleLogs.at(-1)!.text).toContain('.logFoo is not a function')
|
||||
await page.locator('.log-hello').first().click()
|
||||
expect(lastLog).toContain('world')
|
||||
expect(consoleLogs.at(-1)!.text).toContain('world')
|
||||
await page.locator('.add-count').first().click()
|
||||
expect(await page.locator('.count').first().innerText()).toContain('1')
|
||||
|
||||
@ -357,14 +341,15 @@ describe('pages', () => {
|
||||
await page.locator('.swap-layout').click()
|
||||
await page.waitForFunction(() => document.querySelector('.count')?.innerHTML.includes('0'))
|
||||
await page.locator('.log-foo').first().click()
|
||||
expect(lastLog).toContain('bar')
|
||||
expect(consoleLogs.at(-1)!.text).toContain('bar')
|
||||
await page.locator('.log-hello').first().click()
|
||||
expect(lastLog).toContain('.logHello is not a function')
|
||||
expect(pageErrors.at(-1)?.toString() || consoleLogs.at(-1)!.text).toContain('.logHello is not a function')
|
||||
await page.locator('.add-count').first().click()
|
||||
await page.waitForFunction(() => document.querySelector('.count')?.innerHTML.includes('1'))
|
||||
// change layout
|
||||
await page.locator('.swap-layout').click()
|
||||
await page.waitForFunction(() => document.querySelector('.count')?.innerHTML.includes('0'))
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('/client-only-explicit-import', async () => {
|
||||
@ -379,24 +364,16 @@ describe('pages', () => {
|
||||
})
|
||||
|
||||
it('/wrapper-expose/page', async () => {
|
||||
await expectNoClientErrors('/wrapper-expose/page')
|
||||
let lastLog: string|undefined
|
||||
const page = await createPage('/wrapper-expose/page')
|
||||
page.on('console', (log) => {
|
||||
lastLog = log.text()
|
||||
})
|
||||
page.on('pageerror', (log) => {
|
||||
lastLog = log.message
|
||||
})
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, pageErrors, consoleLogs } = await renderPage('/wrapper-expose/page')
|
||||
await page.locator('#log-foo').click()
|
||||
expect(lastLog === 'bar').toBeTruthy()
|
||||
expect(consoleLogs.at(-1)?.text).toBe('bar')
|
||||
// change page
|
||||
await page.locator('#to-hello').click()
|
||||
await page.locator('#log-foo').click()
|
||||
expect(lastLog?.includes('.foo is not a function')).toBeTruthy()
|
||||
expect(pageErrors.at(-1)?.toString() || consoleLogs.at(-1)!.text).toContain('.foo is not a function')
|
||||
await page.locator('#log-hello').click()
|
||||
expect(lastLog === 'world').toBeTruthy()
|
||||
expect(consoleLogs.at(-1)?.text).toBe('world')
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('client-fallback', async () => {
|
||||
@ -421,10 +398,7 @@ describe('pages', () => {
|
||||
expect(html).not.toContain('<p></p>')
|
||||
expect(html).toContain('hi')
|
||||
|
||||
await expectNoClientErrors('/client-fallback')
|
||||
|
||||
const page = await createPage('/client-fallback')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, pageErrors } = await renderPage('/client-fallback')
|
||||
// ensure components reactivity once mounted
|
||||
await page.locator('#increment-count').click()
|
||||
expect(await page.locator('#sugar-counter').innerHTML()).toContain('Sugar Counter 12 x 1 = 12')
|
||||
@ -432,6 +406,7 @@ describe('pages', () => {
|
||||
expect(await page.locator('#keep-fallback').all()).toHaveLength(1)
|
||||
// #20833
|
||||
expect(await page.locator('body').innerHTML()).not.toContain('Hello world !')
|
||||
expect(pageErrors).toEqual([])
|
||||
await page.close()
|
||||
})
|
||||
|
||||
@ -539,16 +514,14 @@ describe('nuxt links', () => {
|
||||
})
|
||||
|
||||
it('preserves route state', async () => {
|
||||
const page = await createPage('/nuxt-link/trailing-slash')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/nuxt-link/trailing-slash')
|
||||
|
||||
for (const selector of ['nuxt-link', 'router-link', 'link-with-trailing-slash', 'link-without-trailing-slash']) {
|
||||
await page.locator(`.${selector}[href*=with-state]`).click()
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.getByTestId('window-state').innerText()).toContain('bar')
|
||||
await page.getByTestId('window-state').getByText('bar').waitFor()
|
||||
|
||||
await page.locator(`.${selector}[href*=without-state]`).click()
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath.includes('without-state'))
|
||||
expect(await page.getByTestId('window-state').innerText()).not.toContain('bar')
|
||||
}
|
||||
|
||||
@ -760,8 +733,8 @@ describe('errors', () => {
|
||||
await page.waitForURL(url('/chunk-error'))
|
||||
expect(consoleLogs.map(c => c.text).join('')).toContain('caught chunk load error')
|
||||
expect(await page.innerText('div')).toContain('Chunk error page')
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.innerText('div')).toContain('State: 3')
|
||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/chunk-error')
|
||||
await page.locator('div').getByText('State: 3').waitFor()
|
||||
|
||||
await page.close()
|
||||
})
|
||||
@ -803,8 +776,7 @@ describe('middlewares', () => {
|
||||
it('should allow aborting navigation fatally on client-side', async () => {
|
||||
const html = await $fetch('/middleware-abort')
|
||||
expect(html).not.toContain('This is the error page')
|
||||
const page = await createPage('/middleware-abort')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/middleware-abort')
|
||||
expect(await page.innerHTML('body')).toContain('This is the error page')
|
||||
await page.close()
|
||||
})
|
||||
@ -901,13 +873,11 @@ describe('composable tree shaking', () => {
|
||||
|
||||
expect(html).toContain('Tree Shake Example')
|
||||
|
||||
const page = await createPage('/tree-shake')
|
||||
// check page doesn't have any errors or warnings in the console
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, pageErrors } = await renderPage('/tree-shake')
|
||||
// ensure scoped classes are correctly assigned between client and server
|
||||
expect(await page.$eval('h1', e => getComputedStyle(e).color)).toBe('rgb(255, 192, 203)')
|
||||
|
||||
await expectNoClientErrors('/tree-shake')
|
||||
expect(pageErrors).toEqual([])
|
||||
|
||||
await page.close()
|
||||
})
|
||||
@ -922,8 +892,8 @@ describe('server tree shaking', () => {
|
||||
expect(html).not.toContain('rendered client-side')
|
||||
expect(html).not.toContain('id="client-side"')
|
||||
|
||||
const page = await createPage('/client')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/client')
|
||||
await page.waitForFunction(() => window.useNuxtApp?.())
|
||||
// ensure scoped classes are correctly assigned between client and server
|
||||
expect(await page.$eval('.red', e => getComputedStyle(e).color)).toBe('rgb(255, 0, 0)')
|
||||
expect(await page.$eval('.blue', e => getComputedStyle(e).color)).toBe('rgb(0, 0, 255)')
|
||||
@ -1017,44 +987,29 @@ describe('extends support', () => {
|
||||
|
||||
// Bug #7337
|
||||
describe('deferred app suspense resolve', () => {
|
||||
async function behaviour (path: string) {
|
||||
await withLogs(async (page, logs) => {
|
||||
await page.goto(url(path))
|
||||
await page.waitForLoadState('networkidle')
|
||||
it.each(['/async-parent/child', '/internal-layout/async-parent/child'])('should wait for all suspense instance on initial hydration', async (path) => {
|
||||
const { page, consoleLogs } = await renderPage(path)
|
||||
|
||||
// Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet.
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
// Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet.
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
const hydrationLogs = logs.filter(log => log.includes('isHydrating'))
|
||||
expect(hydrationLogs.length).toBe(3)
|
||||
expect(hydrationLogs.every(log => log === 'isHydrating: true'))
|
||||
})
|
||||
}
|
||||
it('should wait for all suspense instance on initial hydration', async () => {
|
||||
await behaviour('/async-parent/child')
|
||||
})
|
||||
it('should wait for all suspense instance on initial hydration', async () => {
|
||||
await behaviour('/internal-layout/async-parent/child')
|
||||
const hydrationLogs = consoleLogs.filter(log => log.text.includes('isHydrating'))
|
||||
expect(hydrationLogs.length).toBe(3)
|
||||
expect(hydrationLogs.every(log => log.text === 'isHydrating: true'))
|
||||
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('should wait for suspense in parent layout', async () => {
|
||||
const page = await createPage('/hydration/layout')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet.
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
const html = await page.getByRole('document').innerHTML()
|
||||
expect(html).toContain('Tests whether hydration is properly resolved within an async layout')
|
||||
const { page } = await renderPage('/hydration/layout')
|
||||
await page.getByText('Tests whether hydration is properly resolved within an async layout').waitFor()
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('should fully hydrate even if there is a redirection on a page with `ssr: false`', async () => {
|
||||
const page = await createPage('/hydration/spa-redirection/start')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet.
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
const html = await page.getByRole('document').innerHTML()
|
||||
expect(html).toContain('fully hydrated and ready to go')
|
||||
const { page } = await renderPage('/hydration/spa-redirection/start')
|
||||
await page.getByText('fully hydrated and ready to go').waitFor()
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1071,14 +1026,7 @@ describe('nested suspense', () => {
|
||||
])
|
||||
|
||||
it.each(navigations)('should navigate from %s to %s with no white flash', async (start, nav) => {
|
||||
const page = await createPage(start, {})
|
||||
const logs: string[] = []
|
||||
page.on('console', (msg) => {
|
||||
const text = msg.text()
|
||||
if (text.includes('[vite]') || text.includes('<Suspense> is an experimental feature')) { return }
|
||||
logs.push(msg.text())
|
||||
})
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, consoleLogs } = await renderPage(start)
|
||||
|
||||
const slug = nav.replace(/\?.*$/, '').replace(/[/-]+/g, '-')
|
||||
await page.click(`[href^="${nav}"]`)
|
||||
@ -1098,7 +1046,7 @@ describe('nested suspense', () => {
|
||||
const first = start.match(/\/suspense\/(?<parentType>a?sync)-(?<parentNum>\d)\/(?<childType>a?sync)-(?<childNum>\d)\//)!.groups!
|
||||
const last = nav.match(/\/suspense\/(?<parentType>a?sync)-(?<parentNum>\d)\/(?<childType>a?sync)-(?<childNum>\d)\//)!.groups!
|
||||
|
||||
expect(logs.sort()).toEqual([
|
||||
expect(consoleLogs.map(l => l.text).filter(i => !i.includes('[vite]') && !i.includes('<Suspense> is an experimental feature')).sort()).toEqual([
|
||||
// [first load] from parent
|
||||
`[${first.parentType}]`,
|
||||
...first.parentType === 'async' ? ['[async] running async data'] : [],
|
||||
@ -1122,14 +1070,7 @@ describe('nested suspense', () => {
|
||||
]
|
||||
|
||||
it.each(outwardNavigations)('should navigate from %s to a parent %s with no white flash', async (start, nav) => {
|
||||
const page = await createPage(start, {})
|
||||
const logs: string[] = []
|
||||
page.on('console', (msg) => {
|
||||
const text = msg.text()
|
||||
if (text.includes('[vite]') || text.includes('<Suspense> is an experimental feature')) { return }
|
||||
logs.push(msg.text())
|
||||
})
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, consoleLogs } = await renderPage(start)
|
||||
|
||||
await page.waitForSelector(`main:has(#child${start.replace(/[/-]+/g, '-')})`)
|
||||
|
||||
@ -1146,7 +1087,7 @@ describe('nested suspense', () => {
|
||||
const first = start.match(/\/suspense\/(?<parentType>a?sync)-(?<parentNum>\d)\/(?<childType>a?sync)-(?<childNum>\d)\//)!.groups!
|
||||
const last = nav.match(/\/suspense\/(?<parentType>a?sync)-(?<parentNum>\d)\//)!.groups!
|
||||
|
||||
expect(logs.sort()).toEqual([
|
||||
expect(consoleLogs.map(l => l.text).filter(i => !i.includes('[vite]') && !i.includes('<Suspense> is an experimental feature')).sort()).toEqual([
|
||||
// [first load] from parent
|
||||
`[${first.parentType}]`,
|
||||
...first.parentType === 'async' ? ['[async] running async data'] : [],
|
||||
@ -1167,14 +1108,7 @@ describe('nested suspense', () => {
|
||||
]
|
||||
|
||||
it.each(inwardNavigations)('should navigate from %s to a child %s with no white flash', async (start, nav) => {
|
||||
const page = await createPage(start, {})
|
||||
const logs: string[] = []
|
||||
page.on('console', (msg) => {
|
||||
const text = msg.text()
|
||||
if (text.includes('[vite]') || text.includes('<Suspense> is an experimental feature')) { return }
|
||||
logs.push(msg.text())
|
||||
})
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page, consoleLogs } = await renderPage(start)
|
||||
|
||||
const slug = nav.replace(/[/-]+/g, '-')
|
||||
await page.click(`[href^="${nav}"]`)
|
||||
@ -1190,7 +1124,7 @@ describe('nested suspense', () => {
|
||||
const first = start.match(/\/suspense\/(?<parentType>a?sync)-(?<parentNum>\d)\//)!.groups!
|
||||
const last = nav.match(/\/suspense\/(?<parentType>a?sync)-(?<parentNum>\d)\/(?<childType>a?sync)-(?<childNum>\d)\//)!.groups!
|
||||
|
||||
expect(logs.sort()).toEqual([
|
||||
expect(consoleLogs.map(l => l.text).filter(i => !i.includes('[vite]') && !i.includes('<Suspense> is an experimental feature')).sort()).toEqual([
|
||||
// [first load] from parent
|
||||
`[${first.parentType}]`,
|
||||
...first.parentType === 'async' ? ['[async] running async data'] : [],
|
||||
@ -1208,80 +1142,64 @@ describe('nested suspense', () => {
|
||||
|
||||
// Bug #6592
|
||||
describe('page key', () => {
|
||||
it('should not cause run of setup if navigation not change page key and layout', async () => {
|
||||
async function behaviour (path: string) {
|
||||
await withLogs(async (page, logs) => {
|
||||
await page.goto(url(`${path}/0`))
|
||||
await page.waitForLoadState('networkidle')
|
||||
it.each(['/fixed-keyed-child-parent', '/internal-layout/fixed-keyed-child-parent'])('should not cause run of setup if navigation not change page key and layout', async (path) => {
|
||||
const { page, consoleLogs } = await renderPage(`${path}/0`)
|
||||
|
||||
await page.click(`[href="${path}/1"]`)
|
||||
await page.waitForSelector('#page-1')
|
||||
await page.click(`[href="${path}/1"]`)
|
||||
await page.waitForSelector('#page-1')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
expect(logs.filter(l => l.includes('Child Setup')).length).toBe(1)
|
||||
})
|
||||
}
|
||||
await behaviour('/fixed-keyed-child-parent')
|
||||
await behaviour('/internal-layout/fixed-keyed-child-parent')
|
||||
expect(consoleLogs.filter(l => l.text.includes('Child Setup')).length).toBe(1)
|
||||
await page.close()
|
||||
})
|
||||
it('will cause run of setup if navigation changed page key', async () => {
|
||||
async function behaviour (path: string) {
|
||||
await withLogs(async (page, logs) => {
|
||||
await page.goto(url(`${path}/0`))
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.click(`[href="${path}/1"]`)
|
||||
await page.waitForSelector('#page-1')
|
||||
it.each(['/keyed-child-parent', '/internal-layout/keyed-child-parent'])('will cause run of setup if navigation changed page key', async (path) => {
|
||||
const { page, consoleLogs } = await renderPage(`${path}/0`)
|
||||
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
await page.click(`[href="${path}/1"]`)
|
||||
await page.waitForSelector('#page-1')
|
||||
|
||||
expect(logs.filter(l => l.includes('Child Setup')).length).toBe(2)
|
||||
})
|
||||
}
|
||||
await behaviour('/keyed-child-parent')
|
||||
await behaviour('/internal-layout/keyed-child-parent')
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
expect(consoleLogs.filter(l => l.text.includes('Child Setup')).length).toBe(2)
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
|
||||
// Bug #6592
|
||||
describe('layout change not load page twice', () => {
|
||||
async function behaviour (path1: string, path2: string) {
|
||||
await withLogs(async (page, logs) => {
|
||||
await page.goto(url(path1))
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.click(`[href="${path2}"]`)
|
||||
await page.waitForSelector('#with-layout2')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
expect(logs.filter(l => l.includes('Layout2 Page Setup')).length).toBe(1)
|
||||
})
|
||||
const cases = {
|
||||
'/with-layout': '/with-layout2',
|
||||
'/internal-layout/with-layout': '/internal-layout/with-layout2'
|
||||
}
|
||||
it('should not cause run of page setup to repeat if layout changed', async () => {
|
||||
await behaviour('/with-layout', '/with-layout2')
|
||||
await behaviour('/internal-layout/with-layout', '/internal-layout/with-layout2')
|
||||
|
||||
it.each(Object.entries(cases))('should not cause run of page setup to repeat if layout changed', async (path1, path2) => {
|
||||
const { page, consoleLogs } = await renderPage(path1)
|
||||
await page.click(`[href="${path2}"]`)
|
||||
await page.waitForSelector('#with-layout2')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
|
||||
expect(consoleLogs.filter(l => l.text.includes('Layout2 Page Setup')).length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('layout switching', () => {
|
||||
// #13309
|
||||
it('does not cause TypeError: Cannot read properties of null', async () => {
|
||||
await withLogs(async (page, logs) => {
|
||||
await page.goto(url('/layout-switch/start'))
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.click('[href="/layout-switch/end"]')
|
||||
// Wait for all pending micro ticks to be cleared,
|
||||
// so we are not resolved too early when there are repeated page loading
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
expect(logs.filter(l => l.match(/error/i))).toMatchInlineSnapshot('[]')
|
||||
})
|
||||
const { page, consoleLogs, pageErrors } = await renderPage('/layout-switch/start')
|
||||
await page.click('[href="/layout-switch/end"]')
|
||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/layout-switch/end')
|
||||
expect(consoleLogs.map(i => i.text).filter(l => l.match(/error/i))).toMatchInlineSnapshot('[]')
|
||||
expect(pageErrors).toMatchInlineSnapshot('[]')
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1372,8 +1290,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
||||
})
|
||||
|
||||
it('still downloads client-only styles', async () => {
|
||||
const page = await createPage('/styles')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/styles')
|
||||
expect(await page.$eval('.client-only-css', e => getComputedStyle(e).color)).toBe('rgb(50, 50, 50)')
|
||||
|
||||
await page.close()
|
||||
@ -1387,13 +1304,12 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
||||
|
||||
describe('server components/islands', () => {
|
||||
it('/islands', async () => {
|
||||
const page = await createPage('/islands')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/islands')
|
||||
await page.locator('#increase-pure-component').click()
|
||||
await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200)
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#slot-in-server').first().innerHTML()).toContain('Slot with in .server component')
|
||||
expect(await page.locator('#test-slot').first().innerHTML()).toContain('Slot with name test')
|
||||
|
||||
await page.locator('#slot-in-server').getByText('Slot with in .server component').waitFor()
|
||||
await page.locator('#test-slot').getByText('Slot with name test').waitFor()
|
||||
|
||||
// test fallback slot with v-for
|
||||
expect(await page.locator('.fallback-slot-content').all()).toHaveLength(2)
|
||||
@ -1404,9 +1320,9 @@ describe('server components/islands', () => {
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200),
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200)
|
||||
])
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1'))
|
||||
expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1')
|
||||
|
||||
await page.locator('#async-server-component-count').getByText('1').waitFor()
|
||||
await page.locator('#long-async-component-count').getByText('1').waitFor()
|
||||
|
||||
// test islands slots interactivity
|
||||
await page.locator('#first-sugar-counter button').click()
|
||||
@ -1420,7 +1336,7 @@ describe('server components/islands', () => {
|
||||
})
|
||||
|
||||
it('lazy server components', async () => {
|
||||
const page = await createPage('/server-components/lazy/start')
|
||||
const { page } = await renderPage('/server-components/lazy/start')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.getByText('Go to page with lazy server component').click()
|
||||
|
||||
@ -1439,7 +1355,7 @@ describe('server components/islands', () => {
|
||||
})
|
||||
|
||||
it('non-lazy server components', async () => {
|
||||
const page = await createPage('/server-components/lazy/start')
|
||||
const { page } = await renderPage('/server-components/lazy/start')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.getByText('Go to page without lazy server component').click()
|
||||
|
||||
@ -1477,14 +1393,9 @@ describe.skipIf(isDev() || isWindows || !isRenderingJson)('prefetching', () => {
|
||||
})
|
||||
|
||||
it('should prefetch everything needed when NuxtLink is used', async () => {
|
||||
const page = await createPage()
|
||||
const requests: string[] = []
|
||||
const { page, requests } = await renderPage()
|
||||
|
||||
page.on('request', (req) => {
|
||||
requests.push(req.url().replace(url('/'), '/').replace(/\.[^.]+\./g, '.'))
|
||||
})
|
||||
|
||||
await page.goto(url('/prefetch'))
|
||||
await gotoPath(page, '/prefetch')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
const snapshot = [...requests]
|
||||
@ -1782,15 +1693,15 @@ describe('component islands', () => {
|
||||
})
|
||||
|
||||
it('test client-side navigation', async () => {
|
||||
const page = await createPage('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
const { page } = await renderPage('/')
|
||||
await page.click('#islands')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/islands')
|
||||
|
||||
await page.locator('#increase-pure-component').click()
|
||||
await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200)
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#slot-in-server').first().innerHTML()).toContain('Slot with in .server component')
|
||||
expect(await page.locator('#test-slot').first().innerHTML()).toContain('Slot with name test')
|
||||
|
||||
await page.locator('#slot-in-server').getByText('Slot with in .server component').waitFor()
|
||||
await page.locator('#test-slot').getByText('Slot with name test').waitFor()
|
||||
|
||||
// test islands update
|
||||
expect(await page.locator('.box').innerHTML()).toContain('"number": 101,')
|
||||
@ -1799,9 +1710,8 @@ describe('component islands', () => {
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200),
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200)
|
||||
])
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1'))
|
||||
expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1')
|
||||
|
||||
await page.locator('#long-async-component-count').getByText('1').waitFor()
|
||||
|
||||
// test islands slots interactivity
|
||||
await page.locator('#first-sugar-counter button').click()
|
||||
@ -1848,18 +1758,12 @@ describe.skipIf(isDev() || isWindows || !isRenderingJson)('payload rendering', (
|
||||
})
|
||||
|
||||
it('does not fetch a prefetched payload', async () => {
|
||||
const page = await createPage()
|
||||
const requests = [] as string[]
|
||||
const { page, requests } = await renderPage()
|
||||
|
||||
page.on('request', (req) => {
|
||||
requests.push(req.url().replace(url('/'), '/'))
|
||||
})
|
||||
|
||||
await page.goto(url('/random/a'))
|
||||
await page.waitForLoadState('networkidle')
|
||||
await gotoPath(page, '/random/a')
|
||||
|
||||
// We are manually prefetching other payloads
|
||||
expect(requests).toContain('/random/c/_payload.json')
|
||||
await page.waitForRequest(url('/random/c/_payload.json'))
|
||||
|
||||
// We are not triggering API requests in the payload
|
||||
expect(requests).not.toContain(expect.stringContaining('/api/random'))
|
||||
@ -1935,10 +1839,7 @@ describe.skipIf(isWindows)('useAsyncData', () => {
|
||||
expect(html).not.toContain('false')
|
||||
|
||||
const page = await createPage('/useAsyncData/status')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
expect(await page.locator('#status5-values').textContent()).toContain('idle,pending,success')
|
||||
|
||||
await page.locator('#status5-values').getByText('idle,pending,success').waitFor()
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
|
@ -17,7 +17,7 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) {
|
||||
dev: true,
|
||||
server: true,
|
||||
browser: true,
|
||||
setupTimeout: (isWindows ? 240 : 120) * 1000,
|
||||
setupTimeout: (isWindows ? 360 : 120) * 1000,
|
||||
nuxtConfig: {
|
||||
builder: isWebpack ? 'webpack' : 'vite',
|
||||
buildDir: process.env.NITRO_BUILD_DIR,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { isWindows } from 'std-env'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { $fetch, setup } from '@nuxt/test-utils'
|
||||
import { isWindows } from 'std-env'
|
||||
import { expectNoClientErrors, renderPage } from './utils'
|
||||
const isWebpack = process.env.TEST_BUILDER === 'webpack'
|
||||
|
||||
@ -10,7 +10,7 @@ await setup({
|
||||
dev: process.env.TEST_ENV === 'dev',
|
||||
server: true,
|
||||
browser: true,
|
||||
setupTimeout: (isWindows ? 240 : 120) * 1000,
|
||||
setupTimeout: (isWindows ? 360 : 120) * 1000,
|
||||
nuxtConfig: {
|
||||
builder: isWebpack ? 'webpack' : 'vite'
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import type { Page } from 'playwright-core'
|
||||
import { parse } from 'devalue'
|
||||
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
|
||||
import { createError } from 'h3'
|
||||
import { createPage, getBrowser, url, useTestContext } from '@nuxt/test-utils'
|
||||
import { getBrowser, url, useTestContext } from '@nuxt/test-utils'
|
||||
|
||||
export const isRenderingJson = true
|
||||
|
||||
@ -17,6 +17,7 @@ export async function renderPage (path = '/') {
|
||||
const browser = await getBrowser()
|
||||
const page = await browser.newPage({})
|
||||
const pageErrors: Error[] = []
|
||||
const requests: string[] = []
|
||||
const consoleLogs: { type: string, text: string }[] = []
|
||||
|
||||
page.on('console', (message) => {
|
||||
@ -28,14 +29,19 @@ export async function renderPage (path = '/') {
|
||||
page.on('pageerror', (err) => {
|
||||
pageErrors.push(err)
|
||||
})
|
||||
page.on('request', (req) => {
|
||||
requests.push(req.url().replace(url('/'), '/'))
|
||||
})
|
||||
|
||||
if (path) {
|
||||
await page.goto(url(path), { waitUntil: 'networkidle' })
|
||||
await page.waitForFunction(() => window.useNuxtApp?.())
|
||||
}
|
||||
|
||||
return {
|
||||
page,
|
||||
pageErrors,
|
||||
requests,
|
||||
consoleLogs
|
||||
}
|
||||
}
|
||||
@ -58,6 +64,11 @@ export async function expectNoClientErrors (path: string) {
|
||||
await page.close()
|
||||
}
|
||||
|
||||
export async function gotoPath (page: Page, path: string) {
|
||||
await page.goto(url(path))
|
||||
await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, path)
|
||||
}
|
||||
|
||||
type EqualityVal = string | number | boolean | null | undefined | RegExp
|
||||
export async function expectWithPolling (
|
||||
get: () => Promise<EqualityVal> | EqualityVal,
|
||||
@ -76,26 +87,6 @@ export async function expectWithPolling (
|
||||
expect(result?.toString(), `"${result?.toString()}" did not equal "${expected?.toString()}" in ${retries * delay}ms`).toEqual(expected?.toString())
|
||||
}
|
||||
|
||||
export async function withLogs (callback: (page: Page, logs: string[]) => Promise<void>) {
|
||||
let done = false
|
||||
const page = await createPage()
|
||||
const logs: string[] = []
|
||||
page.on('console', (msg) => {
|
||||
const text = msg.text()
|
||||
if (done && !text.includes('[vite] server connection lost')) {
|
||||
throw new Error(`Test finished prematurely before log: [${msg.type()}] ${text}`)
|
||||
}
|
||||
logs.push(text)
|
||||
})
|
||||
|
||||
try {
|
||||
await callback(page, logs)
|
||||
} finally {
|
||||
done = true
|
||||
await page.close()
|
||||
}
|
||||
}
|
||||
|
||||
const revivers = {
|
||||
NuxtError: (data: any) => createError(data),
|
||||
EmptyShallowRef: (data: any) => shallowRef(JSON.parse(data)),
|
||||
|
Loading…
Reference in New Issue
Block a user