Skip to content

Commit 66af3ce

Browse files
authored
Improve tests for streaming and server components (#33740)
Reorganizes the existing tests as they're getting longer. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint`
1 parent c8a0aac commit 66af3ce

File tree

3 files changed

+203
-189
lines changed

3 files changed

+203
-189
lines changed

test/integration/react-streaming-and-server-components/test/index.test.js

Lines changed: 21 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-env jest */
22

3-
import cheerio from 'cheerio'
43
import { join } from 'path'
54
import fs from 'fs-extra'
65
import webdriver from 'next-webdriver'
@@ -14,10 +13,11 @@ import {
1413
nextBuild as _nextBuild,
1514
nextStart as _nextStart,
1615
renderViaHTTP,
17-
check,
1816
} from 'next-test-utils'
1917

2018
import css from './css'
19+
import rsc from './rsc'
20+
import streaming from './streaming'
2121

2222
const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')]
2323
const appDir = join(__dirname, '../app')
@@ -123,20 +123,6 @@ async function nextDev(dir, port) {
123123
})
124124
}
125125

126-
async function resolveStreamResponse(response, onData) {
127-
let result = ''
128-
onData = onData || (() => {})
129-
await new Promise((resolve) => {
130-
response.body.on('data', (chunk) => {
131-
result += chunk.toString()
132-
onData(chunk.toString(), result)
133-
})
134-
135-
response.body.on('end', resolve)
136-
})
137-
return result
138-
}
139-
140126
describe('concurrentFeatures - basic', () => {
141127
it('should warn user for experimental risk with server components', async () => {
142128
const edgeRuntimeWarning =
@@ -308,197 +294,43 @@ runSuite('document', 'dev', documentSuite)
308294
runSuite('document', 'prod', documentSuite)
309295

310296
async function runBasicTests(context, env) {
311-
const isDev = env === 'dev'
312-
it('should render html correctly', async () => {
313-
const homeHTML = await renderViaHTTP(context.appPort, '/', null, {
314-
headers: {
315-
'x-next-test-client': 'test-util',
316-
},
317-
})
318-
319-
// should have only 1 DOCTYPE
320-
expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)
321-
322-
// dynamic routes
323-
const dynamicRouteHTML1 = await renderViaHTTP(
324-
context.appPort,
325-
'/routes/dynamic1'
326-
)
327-
const dynamicRouteHTML2 = await renderViaHTTP(
328-
context.appPort,
329-
'/routes/dynamic2'
330-
)
331-
332-
const path404HTML = await renderViaHTTP(context.appPort, '/404')
297+
it('should render 500 error correctly', async () => {
333298
const path500HTML = await renderViaHTTP(context.appPort, '/err')
334-
const pathNotFoundHTML = await renderViaHTTP(
335-
context.appPort,
336-
'/this-is-not-found'
337-
)
338-
339-
const page404Content = 'custom-404-page'
340-
341-
expect(homeHTML).toContain('component:index.server')
342-
expect(homeHTML).toContain('env:env_var_test')
343-
expect(homeHTML).toContain('header:test-util')
344-
expect(homeHTML).toContain('path:/')
345-
expect(homeHTML).toContain('foo.client')
346-
347-
expect(dynamicRouteHTML1).toContain('query: dynamic1')
348-
expect(dynamicRouteHTML2).toContain('query: dynamic2')
349-
350-
const $404 = cheerio.load(path404HTML)
351-
expect($404('#__next').text()).toBe(page404Content)
352299

353-
// In dev mode: it should show the error popup.
300+
// In dev mode it should show the error popup.
301+
const isDev = env === 'dev'
354302
expect(path500HTML).toContain(isDev ? 'Error: oops' : 'custom-500-page')
355-
expect(pathNotFoundHTML).toContain(page404Content)
356303
})
357304

358-
it('should disable cache for fizz pages', async () => {
359-
const urls = ['/', '/next-api/image', '/next-api/link']
360-
await Promise.all(
361-
urls.map(async (url) => {
362-
const { headers } = await fetchViaHTTP(context.appPort, url)
363-
expect(headers.get('cache-control')).toBe(
364-
'no-cache, no-store, max-age=0, must-revalidate'
365-
)
366-
})
367-
)
368-
})
369-
370-
it('should support next/link', async () => {
371-
const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link')
372-
const $ = cheerio.load(linkHTML)
373-
const linkText = $('div[hidden] > a[href="/"]').text()
374-
375-
expect(linkText).toContain('go home')
376-
377-
const browser = await webdriver(context.appPort, '/next-api/link')
378-
379-
// We need to make sure the app is fully hydrated before clicking, otherwise
380-
// it will be a full redirection instead of being taken over by the next
381-
// router. This timeout prevents it being flaky caused by fast refresh's
382-
// rebuilding event.
383-
await new Promise((res) => setTimeout(res, 1000))
384-
await browser.eval('window.beforeNav = 1')
385-
386-
await browser.waitForElementByCss('#next_id').click()
387-
await check(() => browser.elementByCss('#query').text(), 'query:1')
388-
389-
await browser.waitForElementByCss('#next_id').click()
390-
await check(() => browser.elementByCss('#query').text(), 'query:2')
391-
392-
expect(await browser.eval('window.beforeNav')).toBe(1)
393-
})
394-
395-
it('should suspense next/image on server side', async () => {
396-
const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image')
397-
const $ = cheerio.load(imageHTML)
398-
const imageTag = $('div[hidden] > span > span > img')
305+
it('should render 404 error correctly', async () => {
306+
const path404HTML = await renderViaHTTP(context.appPort, '/404')
307+
const pathNotFoundHTML = await renderViaHTTP(context.appPort, '/not-found')
399308

400-
expect(imageTag.attr('src')).toContain('data:image')
309+
expect(path404HTML).toContain('custom-404-page')
310+
expect(pathNotFoundHTML).toContain('custom-404-page')
401311
})
402312

403-
it('should handle multiple named exports correctly', async () => {
404-
const clientExportsHTML = await renderViaHTTP(
313+
it('should render dynamic routes correctly', async () => {
314+
const dynamicRoute1HTML = await renderViaHTTP(
405315
context.appPort,
406-
'/client-exports'
407-
)
408-
const $clientExports = cheerio.load(clientExportsHTML)
409-
expect($clientExports('div[hidden] > div').text()).toBe('abcde')
410-
411-
const browser = await webdriver(context.appPort, '/client-exports')
412-
const text = await browser.waitForElementByCss('#__next').text()
413-
expect(text).toBe('abcde')
414-
})
415-
416-
it('should support multi-level server component imports', async () => {
417-
const html = await renderViaHTTP(context.appPort, '/multi')
418-
expect(html).toContain('bar.server.js:')
419-
expect(html).toContain('foo.client')
420-
})
421-
422-
it('should support streaming', async () => {
423-
await fetchViaHTTP(context.appPort, '/streaming', null, {}).then(
424-
async (response) => {
425-
let gotFallback = false
426-
let gotData = false
427-
428-
await resolveStreamResponse(response, (_, result) => {
429-
gotData = result.includes('next_streaming_data')
430-
if (!gotFallback) {
431-
gotFallback = result.includes('next_streaming_fallback')
432-
if (gotFallback) {
433-
expect(gotData).toBe(false)
434-
}
435-
}
436-
})
437-
438-
expect(gotFallback).toBe(true)
439-
expect(gotData).toBe(true)
440-
}
441-
)
442-
443-
// Should end up with "next_streaming_data".
444-
const browser = await webdriver(context.appPort, '/streaming')
445-
const content = await browser.eval(`window.document.body.innerText`)
446-
expect(content).toMatchInlineSnapshot('"next_streaming_data"')
447-
})
448-
449-
it('should support streaming flight request', async () => {
450-
await fetchViaHTTP(context.appPort, '/?__flight__=1').then(
451-
async (response) => {
452-
const result = await resolveStreamResponse(response)
453-
expect(result).toContain('component:index.server')
454-
}
316+
'/routes/dynamic1'
455317
)
456-
})
457-
458-
it('should support partial hydration with inlined server data', async () => {
459-
await fetchViaHTTP(context.appPort, '/partial-hydration', null, {}).then(
460-
async (response) => {
461-
let gotFallback = false
462-
let gotData = false
463-
let gotInlinedData = false
464-
465-
await resolveStreamResponse(response, (_, result) => {
466-
gotInlinedData = result.includes('self.__next_s=')
467-
gotData = result.includes('next_streaming_data')
468-
if (!gotFallback) {
469-
gotFallback = result.includes('next_streaming_fallback')
470-
if (gotFallback) {
471-
expect(gotData).toBe(false)
472-
expect(gotInlinedData).toBe(false)
473-
}
474-
}
475-
})
476-
477-
expect(gotFallback).toBe(true)
478-
expect(gotData).toBe(true)
479-
expect(gotInlinedData).toBe(true)
480-
}
318+
const dynamicRoute2HTML = await renderViaHTTP(
319+
context.appPort,
320+
'/routes/dynamic2'
481321
)
482322

483-
// Should end up with "next_streaming_data".
484-
const browser = await webdriver(context.appPort, '/partial-hydration')
485-
const content = await browser.eval(`window.document.body.innerText`)
486-
expect(content).toContain('next_streaming_data')
487-
488-
// Should support partial hydration: the boundary should still be pending
489-
// while another part is hydrated already.
490-
expect(await browser.eval(`window.partial_hydration_suspense_result`)).toBe(
491-
'next_streaming_fallback'
492-
)
493-
expect(await browser.eval(`window.partial_hydration_counter_result`)).toBe(
494-
'count: 1'
495-
)
323+
expect(dynamicRoute1HTML).toContain('query: dynamic1')
324+
expect(dynamicRoute2HTML).toContain('query: dynamic2')
496325
})
497326

498327
it('should support api routes', async () => {
499328
const res = await renderViaHTTP(context.appPort, '/api/ping')
500329
expect(res).toContain('pong')
501330
})
331+
332+
rsc(context)
333+
streaming(context)
502334
}
503335

504336
function runSuite(suiteName, env, { runTests, before, after }) {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* eslint-env jest */
2+
import webdriver from 'next-webdriver'
3+
import cheerio from 'cheerio'
4+
import { renderViaHTTP, check } from 'next-test-utils'
5+
6+
export default function (context) {
7+
it('should render server components correctly', async () => {
8+
const homeHTML = await renderViaHTTP(context.appPort, '/', null, {
9+
headers: {
10+
'x-next-test-client': 'test-util',
11+
},
12+
})
13+
14+
// should have only 1 DOCTYPE
15+
expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)
16+
17+
expect(homeHTML).toContain('component:index.server')
18+
expect(homeHTML).toContain('env:env_var_test')
19+
expect(homeHTML).toContain('header:test-util')
20+
expect(homeHTML).toContain('path:/')
21+
expect(homeHTML).toContain('foo.client')
22+
})
23+
24+
it('should support multi-level server component imports', async () => {
25+
const html = await renderViaHTTP(context.appPort, '/multi')
26+
expect(html).toContain('bar.server.js:')
27+
expect(html).toContain('foo.client')
28+
})
29+
30+
it('should support next/link in server components', async () => {
31+
const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link')
32+
const $ = cheerio.load(linkHTML)
33+
const linkText = $('div[hidden] > a[href="/"]').text()
34+
35+
expect(linkText).toContain('go home')
36+
37+
const browser = await webdriver(context.appPort, '/next-api/link')
38+
39+
// We need to make sure the app is fully hydrated before clicking, otherwise
40+
// it will be a full redirection instead of being taken over by the next
41+
// router. This timeout prevents it being flaky caused by fast refresh's
42+
// rebuilding event.
43+
await new Promise((res) => setTimeout(res, 1000))
44+
await browser.eval('window.beforeNav = 1')
45+
46+
await browser.waitForElementByCss('#next_id').click()
47+
await check(() => browser.elementByCss('#query').text(), 'query:1')
48+
49+
await browser.waitForElementByCss('#next_id').click()
50+
await check(() => browser.elementByCss('#query').text(), 'query:2')
51+
52+
expect(await browser.eval('window.beforeNav')).toBe(1)
53+
})
54+
55+
it('should suspense next/image in server components', async () => {
56+
const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image')
57+
const $ = cheerio.load(imageHTML)
58+
const imageTag = $('div[hidden] > span > span > img')
59+
60+
expect(imageTag.attr('src')).toContain('data:image')
61+
})
62+
63+
it('should handle multiple named exports correctly', async () => {
64+
const clientExportsHTML = await renderViaHTTP(
65+
context.appPort,
66+
'/client-exports'
67+
)
68+
const $clientExports = cheerio.load(clientExportsHTML)
69+
expect($clientExports('div[hidden] > div').text()).toBe('abcde')
70+
71+
const browser = await webdriver(context.appPort, '/client-exports')
72+
const text = await browser.waitForElementByCss('#__next').text()
73+
expect(text).toBe('abcde')
74+
})
75+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy