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 { join } from 'path'
import { import { renderViaHTTP, runDevSuite, runProdSuite } from 'next-test-utils'
nextBuild,
nextStart,
launchApp,
killApp,
findPort,
renderViaHTTP,
} from 'next-test-utils'
const appDir = join(__dirname, '../') 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) { function basic(context) {
it('should handle json assertions', async () => { it('should handle json assertions', async () => {
const esHtml = await renderViaHTTP(context.appPort, '/es') const esHtml = await renderViaHTTP(context.appPort, '/es')
@ -41,5 +12,5 @@ function basic(context) {
}) })
} }
runSuite('import-assertion', 'dev', basic) runDevSuite('import-assertion', appDir, { runTests: basic })
runSuite('import-assertion', 'prod', basic) runProdSuite('import-assertion', appDir, { runTests: basic })

View file

@ -2,19 +2,47 @@
import fs from 'fs-extra' import fs from 'fs-extra'
import { join } from 'path' 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 appDir = __dirname
const nodeArgs = ['-r', join(appDir, '../../lib/react-17-require-hook.js')] 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 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 content = `
const path = require('path') const path = require('path')
module.exports = require(path.join(__dirname, '../../lib/with-react-17.js'))({ experimental: ${JSON.stringify( const withReact = ${reactVersion} === 18 ? v => v : require(path.join(__dirname, '../../lib/with-react-17.js'))
config module.exports = withReact({ experimental: ${JSON.stringify(config)} })
)} })
` `
nextConfig.write(content) nextConfig.write(content)
} }
@ -65,3 +93,36 @@ describe('React 17 with React 18 config', () => {
expect(code).toBe(1) 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, findPort,
killApp, killApp,
launchApp, launchApp,
nextBuild,
nextStart,
renderViaHTTP, renderViaHTTP,
hasRedbox, hasRedbox,
getRedboxHeader, getRedboxHeader,
runDevSuite,
runProdSuite,
} from 'next-test-utils' } from 'next-test-utils'
import concurrent from './concurrent' import concurrent from './concurrent'
import basics from './basics' import basics from './basics'
import strictMode from './strict-mode' import strictMode from './strict-mode'
import webdriver from 'next-webdriver' import webdriver from 'next-webdriver'
// overrides react and react-dom to v18
const nodeArgs = []
const appDir = join(__dirname, '../app') const appDir = join(__dirname, '../app')
const nextConfig = new File(join(appDir, 'next.config.js')) const nextConfig = new File(join(appDir, 'next.config.js'))
const invalidPage = new File(join(appDir, 'pages/invalid.js')) const invalidPage = new File(join(appDir, 'pages/invalid.js'))
describe('Basics', () => { describe('Basics', () => {
runTests('default setting with react 18', (context, env) => runTests('default setting with react 18', basics)
basics(context, env)
)
}) })
// React 18 with Strict Mode enabled might cause double invocation of lifecycle methods. // React 18 with Strict Mode enabled might cause double invocation of lifecycle methods.
@ -37,9 +33,7 @@ describe('Strict mode - dev', () => {
beforeAll(async () => { beforeAll(async () => {
nextConfig.replace('// reactStrictMode: true,', 'reactStrictMode: true,') nextConfig.replace('// reactStrictMode: true,', 'reactStrictMode: true,')
context.appPort = await findPort() context.appPort = await findPort()
context.server = await launchApp(context.appDir, context.appPort, { context.server = await launchApp(context.appDir, context.appPort)
nodeArgs,
})
}) })
afterAll(() => { 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('edge')
runTestsAgainstRuntime('nodejs') runTestsAgainstRuntime('nodejs')
function runTests(name, fn, options) { function runTests(name, fn, opts) {
runTest('dev', name, fn, options) const suiteOptions = { ...opts, runTests: fn }
runTest('prod', name, fn, options) runDevSuite(name, appDir, suiteOptions)
runProdSuite(name, appDir, suiteOptions)
} }

View file

@ -10,20 +10,23 @@ import { nextBuild } from './utils'
export default function (context) { export default function (context) {
it('should not generate functions manifest when filesystem API is not enabled', async () => { 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). // 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) await nextBuild(context.appDir)
const functionsManifestPath = join( const functionsManifestPath = join(
context.distDir, distDir,
'server', 'server',
'functions-manifest.json' 'functions-manifest.json'
) )
expect(fs.existsSync(functionsManifestPath)).toBe(false) expect(fs.existsSync(functionsManifestPath)).toBe(false)
await fs.remove(join(context.appDir, '.next')) await fs.remove(join(context.appDir, '.next'))
}) })
it('should contain rsc paths in functions manifest', async () => { 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' } }) await nextBuild(context.appDir, { env: { ENABLE_FILE_SYSTEM_API: '1' } })
const functionsManifestPath = join( const functionsManifestPath = join(
context.distDir, distDir,
'server', 'server',
'functions-manifest.json' 'functions-manifest.json'
) )

View file

@ -3,16 +3,17 @@
import { join } from 'path' import { join } from 'path'
import fs from 'fs-extra' 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 { import {
nextBuild,
nextStart,
nextDev,
appDir, appDir,
nativeModuleTestAppDir, nativeModuleTestAppDir,
distDir,
documentPage,
appPage, appPage,
appServerPage, appServerPage,
error500Page, error500Page,
@ -25,26 +26,6 @@ import streaming from './streaming'
import basic from './basic' import basic from './basic'
import runtime from './runtime' 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 = ` const rscAppPage = `
import Container from '../components/container.server' import Container from '../components/container.server'
export default function App({children}) { 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 () => { it('should warn user that native node APIs are not supported', async () => {
const fsImportedErrorMessage = const fsImportedErrorMessage =
'Native Node.js APIs are not supported in the Edge Runtime. Found `dns` imported.' '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) expect(stderr).toContain(fsImportedErrorMessage)
}) })
}) })
@ -80,6 +63,7 @@ describe('Edge runtime - errors', () => {
const edgeRuntimeBasicSuite = { const edgeRuntimeBasicSuite = {
runTests: (context, env) => { runTests: (context, env) => {
const options = { runtime: 'edge', env } const options = { runtime: 'edge', env }
const distDir = join(appDir, '.next')
basic(context, options) basic(context, options)
streaming(context, options) streaming(context, options)
rsc(context, options) rsc(context, options)
@ -158,6 +142,7 @@ const edgeRuntimeBasicSuite = {
const nodejsRuntimeBasicSuite = { const nodejsRuntimeBasicSuite = {
runTests: (context, env) => { runTests: (context, env) => {
const options = { runtime: 'nodejs', env } const options = { runtime: 'nodejs', env }
const distDir = join(appDir, '.next')
basic(context, options) basic(context, options)
streaming(context, options) streaming(context, options)
rsc(context, options) rsc(context, options)
@ -217,61 +202,13 @@ const cssSuite = {
afterAll: () => appPage.delete(), afterAll: () => appPage.delete(),
} }
const documentSuite = { runDevSuite('Node.js runtime', appDir, nodejsRuntimeBasicSuite)
runTests: (context, env) => { runProdSuite('Node.js runtime', appDir, nodejsRuntimeBasicSuite)
if (env === 'dev') { runDevSuite('Edge runtime', appDir, edgeRuntimeBasicSuite)
it('should error when custom _document has getInitialProps method', async () => { runProdSuite('Edge runtime', appDir, edgeRuntimeBasicSuite)
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(),
}
runSuite('Node.js runtime', 'dev', nodejsRuntimeBasicSuite) runDevSuite('Custom App', appDir, customAppPageSuite)
runSuite('Node.js runtime', 'prod', nodejsRuntimeBasicSuite) runProdSuite('Custom App', appDir, customAppPageSuite)
runSuite('Edge runtime', 'dev', edgeRuntimeBasicSuite)
runSuite('Edge runtime', 'prod', edgeRuntimeBasicSuite)
runSuite('Custom App', 'dev', customAppPageSuite) runDevSuite('CSS', appDir, cssSuite)
runSuite('Custom App', 'prod', customAppPageSuite) runProdSuite('CSS', appDir, cssSuite)
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)
})
}

View file

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

View file

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

View file

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

View file

@ -1,51 +1,17 @@
import { join } from 'path' import { join } from 'path'
import { import { File } from 'next-test-utils'
File,
launchApp,
nextBuild as _nextBuild,
nextStart as _nextStart,
} from 'next-test-utils'
import cheerio from 'cheerio' import cheerio from 'cheerio'
const nodeArgs = []
export const appDir = join(__dirname, '../app') export const appDir = join(__dirname, '../app')
export const nativeModuleTestAppDir = join( export const nativeModuleTestAppDir = join(
__dirname, __dirname,
'../unsupported-native-module' '../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 appPage = new File(join(appDir, 'pages/_app.js'))
export const appServerPage = new File(join(appDir, 'pages/_app.server.js')) export const appServerPage = new File(join(appDir, 'pages/_app.server.js'))
export const error500Page = new File(join(appDir, 'pages/500.js')) export const error500Page = new File(join(appDir, 'pages/500.js'))
export const nextConfig = new File(join(appDir, 'next.config.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) { export function getNodeBySelector(html, selector) {
const $ = cheerio.load(html) const $ = cheerio.load(html)
return $(selector) return $(selector)

View file

@ -722,3 +722,67 @@ export function readNextBuildServerPageFile(appDir, page) {
const pageFile = getPageFileFromPagesManifest(appDir, page) const pageFile = getPageFileFromPagesManifest(appDir, page)
return readFileSync(path.join(appDir, '.next', 'server', pageFile), 'utf8') 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)
}