test: organize react 18 tests (#36003)

* Organize react 18 test cases, group invalid cases to speed up the regular test cases
* Add `runDevSuite` and `runProdSuite` for group next dev/prod test cases

```js
runDevSuite(name, appDir, {
  runTests: (context, env) => { ... },
  beforeAll,
  afterAll,
})

runProdvSuite(name, appDir, {
  runTests: (context, env) => { ... },
  beforeAll,
  afterAll,
})
```
This commit is contained in:
Jiachi Liu 2022-04-08 17:29:35 +02:00 committed by GitHub
parent 1c92591466
commit de0bc57b6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 181 additions and 210 deletions

View file

@ -1,37 +1,8 @@
import { join } from 'path'
import {
nextBuild,
nextStart,
launchApp,
killApp,
findPort,
renderViaHTTP,
} from 'next-test-utils'
import { renderViaHTTP, runDevSuite, runProdSuite } from 'next-test-utils'
const appDir = join(__dirname, '../')
function runSuite(suiteName, env, runTests) {
const context = { appDir }
describe(`${suiteName} ${env}`, () => {
if (env === 'prod') {
beforeAll(async () => {
context.appPort = await findPort()
await nextBuild(context.appDir)
context.server = await nextStart(context.appDir, context.appPort)
})
}
if (env === 'dev') {
beforeAll(async () => {
context.appPort = await findPort()
context.server = await launchApp(context.appDir, context.appPort)
})
}
afterAll(async () => await killApp(context.server))
runTests(context, env)
})
}
function basic(context) {
it('should handle json assertions', async () => {
const esHtml = await renderViaHTTP(context.appPort, '/es')
@ -41,5 +12,5 @@ function basic(context) {
})
}
runSuite('import-assertion', 'dev', basic)
runSuite('import-assertion', 'prod', basic)
runDevSuite('import-assertion', appDir, { runTests: basic })
runProdSuite('import-assertion', appDir, { runTests: basic })

View file

@ -2,19 +2,47 @@
import fs from 'fs-extra'
import { join } from 'path'
import { File, nextBuild } from 'next-test-utils'
import {
File,
nextBuild,
runDevSuite,
runProdSuite,
fetchViaHTTP,
} from 'next-test-utils'
const appDir = __dirname
const nodeArgs = ['-r', join(appDir, '../../lib/react-17-require-hook.js')]
const nextConfig = new File(join(appDir, 'next.config.js'))
const reactDomPackagePah = join(appDir, 'node_modules/react-dom')
const nextConfig = new File(join(appDir, 'next.config.js'))
const documentPage = new File(join(appDir, 'pages/_document.js'))
const indexPage = join(appDir, 'pages/index.js')
const indexServerPage = join(appDir, 'pages/index.server.js')
function writeNextConfig(config) {
const documentWithGip = `
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
Document.getInitialProps = (ctx) => {
return ctx.defaultGetInitialProps(ctx)
}
`
function writeNextConfig(config, reactVersion = 17) {
const content = `
const path = require('path')
module.exports = require(path.join(__dirname, '../../lib/with-react-17.js'))({ experimental: ${JSON.stringify(
config
)} })
const withReact = ${reactVersion} === 18 ? v => v : require(path.join(__dirname, '../../lib/with-react-17.js'))
module.exports = withReact({ experimental: ${JSON.stringify(config)} })
`
nextConfig.write(content)
}
@ -65,3 +93,36 @@ describe('React 17 with React 18 config', () => {
expect(code).toBe(1)
})
})
const documentSuite = {
runTests: (context, env) => {
if (env === 'dev') {
it('should error when custom _document has getInitialProps method', async () => {
const res = await fetchViaHTTP(context.appPort, '/')
expect(res.status).toBe(500)
})
} else {
it('should failed building', async () => {
expect(context.code).toBe(1)
})
}
},
beforeAll: async () => {
writeNextConfig(
{
serverComponents: true,
},
18
)
documentPage.write(documentWithGip)
await fs.rename(indexPage, indexServerPage)
},
afterAll: async () => {
documentPage.delete()
nextConfig.restore()
await fs.rename(indexServerPage, indexPage)
},
}
runDevSuite('Invalid custom document with gip', appDir, documentSuite)
runProdSuite('Invalid custom document with gip', appDir, documentSuite)

View file

@ -7,27 +7,23 @@ import {
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
hasRedbox,
getRedboxHeader,
runDevSuite,
runProdSuite,
} from 'next-test-utils'
import concurrent from './concurrent'
import basics from './basics'
import strictMode from './strict-mode'
import webdriver from 'next-webdriver'
// overrides react and react-dom to v18
const nodeArgs = []
const appDir = join(__dirname, '../app')
const nextConfig = new File(join(appDir, 'next.config.js'))
const invalidPage = new File(join(appDir, 'pages/invalid.js'))
describe('Basics', () => {
runTests('default setting with react 18', (context, env) =>
basics(context, env)
)
runTests('default setting with react 18', basics)
})
// React 18 with Strict Mode enabled might cause double invocation of lifecycle methods.
@ -37,9 +33,7 @@ describe('Strict mode - dev', () => {
beforeAll(async () => {
nextConfig.replace('// reactStrictMode: true,', 'reactStrictMode: true,')
context.appPort = await findPort()
context.server = await launchApp(context.appDir, context.appPort, {
nodeArgs,
})
context.server = await launchApp(context.appDir, context.appPort)
})
afterAll(() => {
@ -84,42 +78,11 @@ function runTestsAgainstRuntime(runtime) {
)
}
function runTest(env, name, fn, options) {
const context = { appDir }
describe(`${name} (${env})`, () => {
beforeAll(async () => {
context.appPort = await findPort()
context.stderr = ''
options?.beforeAll(env)
if (env === 'dev') {
context.server = await launchApp(context.appDir, context.appPort, {
nodeArgs,
onStderr(msg) {
context.stderr += msg
},
})
} else {
await nextBuild(context.appDir, [], { nodeArgs })
context.server = await nextStart(context.appDir, context.appPort, {
nodeArgs,
onStderr(msg) {
context.stderr += msg
},
})
}
})
afterAll(async () => {
options?.afterAll(env)
await killApp(context.server)
})
fn(context, env)
})
}
runTestsAgainstRuntime('edge')
runTestsAgainstRuntime('nodejs')
function runTests(name, fn, options) {
runTest('dev', name, fn, options)
runTest('prod', name, fn, options)
function runTests(name, fn, opts) {
const suiteOptions = { ...opts, runTests: fn }
runDevSuite(name, appDir, suiteOptions)
runProdSuite(name, appDir, suiteOptions)
}

View file

@ -10,20 +10,23 @@ import { nextBuild } from './utils'
export default function (context) {
it('should not generate functions manifest when filesystem API is not enabled', async () => {
// Make sure there is no existing functions manifest (caused by failed tests etc).
await fs.remove(join(context.appDir, '.next'))
const distDir = join(context.appDir, '.next')
await fs.remove(distDir)
await nextBuild(context.appDir)
const functionsManifestPath = join(
context.distDir,
distDir,
'server',
'functions-manifest.json'
)
expect(fs.existsSync(functionsManifestPath)).toBe(false)
await fs.remove(join(context.appDir, '.next'))
})
it('should contain rsc paths in functions manifest', async () => {
const distDir = join(context.appDir, '.next')
await nextBuild(context.appDir, { env: { ENABLE_FILE_SYSTEM_API: '1' } })
const functionsManifestPath = join(
context.distDir,
distDir,
'server',
'functions-manifest.json'
)

View file

@ -3,16 +3,17 @@
import { join } from 'path'
import fs from 'fs-extra'
import { fetchViaHTTP, findPort, killApp, renderViaHTTP } from 'next-test-utils'
import {
fetchViaHTTP,
renderViaHTTP,
nextBuild,
runDevSuite,
runProdSuite,
} from 'next-test-utils'
import {
nextBuild,
nextStart,
nextDev,
appDir,
nativeModuleTestAppDir,
distDir,
documentPage,
appPage,
appServerPage,
error500Page,
@ -25,26 +26,6 @@ import streaming from './streaming'
import basic from './basic'
import runtime from './runtime'
const documentWithGip = `
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
Document.getInitialProps = (ctx) => {
return ctx.defaultGetInitialProps(ctx)
}
`
const rscAppPage = `
import Container from '../components/container.server'
export default function App({children}) {
@ -72,7 +53,9 @@ describe('Edge runtime - errors', () => {
it('should warn user that native node APIs are not supported', async () => {
const fsImportedErrorMessage =
'Native Node.js APIs are not supported in the Edge Runtime. Found `dns` imported.'
const { stderr } = await nextBuild(nativeModuleTestAppDir)
const { stderr } = await nextBuild(nativeModuleTestAppDir, [], {
stderr: true,
})
expect(stderr).toContain(fsImportedErrorMessage)
})
})
@ -80,6 +63,7 @@ describe('Edge runtime - errors', () => {
const edgeRuntimeBasicSuite = {
runTests: (context, env) => {
const options = { runtime: 'edge', env }
const distDir = join(appDir, '.next')
basic(context, options)
streaming(context, options)
rsc(context, options)
@ -158,6 +142,7 @@ const edgeRuntimeBasicSuite = {
const nodejsRuntimeBasicSuite = {
runTests: (context, env) => {
const options = { runtime: 'nodejs', env }
const distDir = join(appDir, '.next')
basic(context, options)
streaming(context, options)
rsc(context, options)
@ -217,61 +202,13 @@ const cssSuite = {
afterAll: () => appPage.delete(),
}
const documentSuite = {
runTests: (context, env) => {
if (env === 'dev') {
it('should error when custom _document has getInitialProps method', async () => {
const res = await fetchViaHTTP(context.appPort, '/')
expect(res.status).toBe(500)
})
} else {
it('should failed building', async () => {
expect(context.code).toBe(1)
})
}
},
beforeAll: () => documentPage.write(documentWithGip),
afterAll: () => documentPage.delete(),
}
runDevSuite('Node.js runtime', appDir, nodejsRuntimeBasicSuite)
runProdSuite('Node.js runtime', appDir, nodejsRuntimeBasicSuite)
runDevSuite('Edge runtime', appDir, edgeRuntimeBasicSuite)
runProdSuite('Edge runtime', appDir, edgeRuntimeBasicSuite)
runSuite('Node.js runtime', 'dev', nodejsRuntimeBasicSuite)
runSuite('Node.js runtime', 'prod', nodejsRuntimeBasicSuite)
runSuite('Edge runtime', 'dev', edgeRuntimeBasicSuite)
runSuite('Edge runtime', 'prod', edgeRuntimeBasicSuite)
runDevSuite('Custom App', appDir, customAppPageSuite)
runProdSuite('Custom App', appDir, customAppPageSuite)
runSuite('Custom App', 'dev', customAppPageSuite)
runSuite('Custom App', 'prod', customAppPageSuite)
runSuite('CSS', 'dev', cssSuite)
runSuite('CSS', 'prod', cssSuite)
runSuite('Custom Document', 'dev', documentSuite)
runSuite('Custom Document', 'prod', documentSuite)
function runSuite(suiteName, env, options) {
const context = { appDir, distDir }
describe(`${suiteName} ${env}`, () => {
beforeAll(async () => {
options.beforeAll?.()
if (env === 'prod') {
context.appPort = await findPort()
const { stdout, stderr, code } = await nextBuild(context.appDir)
context.stdout = stdout
context.stderr = stderr
context.code = code
context.server = await nextStart(context.appDir, context.appPort)
}
if (env === 'dev') {
context.appPort = await findPort()
context.server = await nextDev(context.appDir, context.appPort)
}
})
afterAll(async () => {
options.afterAll?.()
if (context.server) {
await killApp(context.server)
}
})
options.runTests(context, env)
})
}
runDevSuite('CSS', appDir, cssSuite)
runProdSuite('CSS', appDir, cssSuite)

View file

@ -3,9 +3,10 @@ import webdriver from 'next-webdriver'
import { renderViaHTTP, check } from 'next-test-utils'
import { join } from 'path'
import fs from 'fs-extra'
import { distDir, getNodeBySelector } from './utils'
import { getNodeBySelector } from './utils'
export default function (context, { runtime, env }) {
const distDir = join(context.appDir, '.next')
it('should render server components correctly', async () => {
const homeHTML = await renderViaHTTP(context.appPort, '/', null, {
headers: {

View file

@ -2,9 +2,9 @@ import { renderViaHTTP } from 'next-test-utils'
import { join } from 'path'
import fs from 'fs-extra'
import { distDir } from './utils'
export default async function runtime(context, { runtime, env }) {
const distDir = join(context.appDir, '.next')
if (runtime === 'edge') {
it('should support per-page runtime configuration', async () => {
const html1 = await renderViaHTTP(context.appPort, '/runtime')

View file

@ -5,10 +5,12 @@ import {
check,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import { nextBuild, nextDev, nextStart } from './utils'
const appDir = join(__dirname, '../switchable-runtime')
@ -48,7 +50,10 @@ describe('Switchable runtime (prod)', () => {
beforeAll(async () => {
context.appPort = await findPort()
const { stdout, stderr } = await nextBuild(context.appDir)
const { stdout, stderr } = await nextBuild(context.appDir, [], {
stderr: true,
stdout: true,
})
context.stdout = stdout
context.stderr = stderr
context.server = await nextStart(context.appDir, context.appPort)
@ -252,7 +257,7 @@ describe('Switchable runtime (dev)', () => {
beforeAll(async () => {
context.appPort = await findPort()
context.server = await nextDev(context.appDir, context.appPort)
context.server = await launchApp(context.appDir, context.appPort)
})
afterAll(async () => {
await killApp(context.server)

View file

@ -1,51 +1,17 @@
import { join } from 'path'
import {
File,
launchApp,
nextBuild as _nextBuild,
nextStart as _nextStart,
} from 'next-test-utils'
import { File } from 'next-test-utils'
import cheerio from 'cheerio'
const nodeArgs = []
export const appDir = join(__dirname, '../app')
export const nativeModuleTestAppDir = join(
__dirname,
'../unsupported-native-module'
)
export const distDir = join(__dirname, '../app/.next')
export const documentPage = new File(join(appDir, 'pages/_document.jsx'))
export const appPage = new File(join(appDir, 'pages/_app.js'))
export const appServerPage = new File(join(appDir, 'pages/_app.server.js'))
export const error500Page = new File(join(appDir, 'pages/500.js'))
export const nextConfig = new File(join(appDir, 'next.config.js'))
export async function nextBuild(dir, options) {
return await _nextBuild(dir, [], {
...options,
stdout: true,
stderr: true,
nodeArgs,
})
}
export async function nextStart(dir, port) {
return await _nextStart(dir, port, {
stdout: true,
stderr: true,
nodeArgs,
})
}
export async function nextDev(dir, port) {
return await launchApp(dir, port, {
stdout: true,
stderr: true,
nodeArgs,
})
}
export function getNodeBySelector(html, selector) {
const $ = cheerio.load(html)
return $(selector)

View file

@ -722,3 +722,67 @@ export function readNextBuildServerPageFile(appDir, page) {
const pageFile = getPageFileFromPagesManifest(appDir, page)
return readFileSync(path.join(appDir, '.next', 'server', pageFile), 'utf8')
}
/**
*
* @param {string} suiteName
* @param {{env: 'prod' | 'dev', appDir: string}} context
* @param {{beforeAll?: Function; afterAll?: Function; runTests: Function}} options
*/
function runSuite(suiteName, context, options) {
const { appDir, env } = context
describe(`${suiteName} ${env}`, () => {
beforeAll(async () => {
options.beforeAll?.(env)
context.stderr = ''
const onStderr = (msg) => {
context.stderr += msg
}
if (env === 'prod') {
context.appPort = await findPort()
const { stdout, stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
context.stdout = stdout
context.stderr = stderr
context.code = code
context.server = await nextStart(context.appDir, context.appPort, {
onStderr,
})
} else if (env === 'dev') {
context.appPort = await findPort()
context.server = await launchApp(context.appDir, context.appPort, {
onStderr,
})
}
})
afterAll(async () => {
options.afterAll?.(env)
if (context.server) {
await killApp(context.server)
}
})
options.runTests(context, env)
})
}
/**
*
* @param {string} suiteName
* @param {string} appDir
* @param {{beforeAll?: Function; afterAll?: Function; runTests: Function}} options
*/
export function runDevSuite(suiteName, appDir, options) {
return runSuite(suiteName, { appDir, env: 'dev' }, options)
}
/**
*
* @param {string} suiteName
* @param {string} appDir
* @param {{beforeAll?: Function; afterAll?: Function; runTests: Function}} options
*/
export function runProdSuite(suiteName, appDir, options) {
return runSuite(suiteName, { appDir, env: 'prod' }, options)
}