More Turbopack fixes (#56299)

Skips additional production-only tests.

Follow-up to #56089.

In this PR I went through all of `test/integration` looking for `nextBuild(` and added the skipping logic.
This commit is contained in:
Tim Neutkens 2023-10-02 15:55:23 +02:00 committed by GitHub
parent ecd94c1a4d
commit 59bda2d818
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
127 changed files with 5849 additions and 5400 deletions

View file

@ -22,217 +22,222 @@ const gip500Err =
let appPort
let app
it('does not show error with getStaticProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
describe('gsp-gssp', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('does not show error with getStaticProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(code).toBe(0)
})
expect(stderr).not.toMatch(gip500Err)
expect(code).toBe(0)
})
it('shows error with getServerSideProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
it('does not show error with getStaticProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
expect(stderr).toMatch(gip500Err)
expect(code).toBe(1)
})
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/abc')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
})
it('shows error with getServerSideProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
expect(code).toBe(1)
})
it('shows error with getServerSideProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/500')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
})
it('does build 500 statically with getInitialProps in _app and getStaticProps in pages/500', async () => {
await fs.writeFile(
pagesApp,
`
import App from 'next/app'
const page = ({ Component, pageProps }) => <Component {...pageProps} />
page.getInitialProps = (ctx) => App.getInitialProps(ctx)
export default page
`
)
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => {
console.log('rendered 500')
return 'custom 500 page'
}
export default page
export const getStaticProps = () => {
return {
props: {}
it('does build 500 statically with getInitialProps in _app and getStaticProps in pages/500', async () => {
await fs.writeFile(
pagesApp,
`
import App from 'next/app'
const page = ({ Component, pageProps }) => <Component {...pageProps} />
page.getInitialProps = (ctx) => App.getInitialProps(ctx)
export default page
`
)
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => {
console.log('rendered 500')
return 'custom 500 page'
}
}
`
)
await fs.remove(join(appDir, '.next'))
const {
stderr,
stdout: buildStdout,
code,
} = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
await fs.remove(pagesApp)
await fs.remove(pages500)
await fs.rename(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(buildStdout).toContain('rendered 500')
expect(code).toBe(0)
expect(await fs.pathExists(join(appDir, '.next/server/pages/500.html'))).toBe(
true
)
let appStdout = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout(msg) {
appStdout += msg || ''
},
onStderr(msg) {
appStdout += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStdout).not.toContain('rendered 500')
})
it('does not build 500 statically with no pages/500 and getServerSideProps in _error', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
export const getServerSideProps = ({ req, res, err }) => {
console.error('called _error getServerSideProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
export default page
export const getStaticProps = () => {
return {
props: {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
props: {}
}
}
`
)
await fs.remove(join(appDir, '.next'))
const {
stderr,
stdout: buildStdout,
code,
} = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
await fs.remove(pagesApp)
await fs.remove(pages500)
await fs.rename(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(buildStdout).toContain('rendered 500')
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(true)
let appStdout = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout(msg) {
appStdout += msg || ''
},
onStderr(msg) {
appStdout += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStdout).not.toContain('rendered 500')
})
it('does not build 500 statically with no pages/500 and getServerSideProps in _error', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
export const getServerSideProps = ({ req, res, err }) => {
console.error('called _error getServerSideProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
props: {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
}
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
export default Error
let appStderr = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStderr(msg) {
appStderr += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStderr).toContain('called _error getServerSideProps')
})
})
describe('development mode', () => {
it('does not show error with getStaticProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/abc')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
})
it('shows error with getServerSideProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/500')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
})
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(await fs.pathExists(join(appDir, '.next/server/pages/500.html'))).toBe(
false
)
let appStderr = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStderr(msg) {
appStderr += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStderr).toContain('called _error getServerSideProps')
})

View file

@ -70,6 +70,36 @@ describe('500 Page Support', () => {
runTests('dev')
})
describe('development mode 2', () => {
it('shows error with getInitialProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
page.getInitialProps = () => ({ a: 'b' })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/500')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
})
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
@ -81,274 +111,249 @@ describe('500 Page Support', () => {
runTests('server')
})
it('does not build 500 statically with getInitialProps in _app', async () => {
await fs.writeFile(
pagesApp,
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode 2',
() => {
it('does not build 500 statically with getInitialProps in _app', async () => {
await fs.writeFile(
pagesApp,
`
import App from 'next/app'
const page = ({ Component, pageProps }) => <Component {...pageProps} />
page.getInitialProps = (ctx) => App.getInitialProps(ctx)
export default page
`
import App from 'next/app'
)
await fs.remove(join(appDir, '.next'))
const {
stderr,
stdout: buildStdout,
code,
} = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
const page = ({ Component, pageProps }) => <Component {...pageProps} />
page.getInitialProps = (ctx) => App.getInitialProps(ctx)
export default page
`
)
await fs.remove(join(appDir, '.next'))
const {
stderr,
stdout: buildStdout,
code,
} = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
await fs.remove(pagesApp)
await fs.remove(pagesApp)
expect(stderr).not.toMatch(gip500Err)
expect(buildStdout).not.toContain('rendered 500')
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
expect(stderr).not.toMatch(gip500Err)
expect(buildStdout).not.toContain('rendered 500')
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
let appStdout = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout(msg) {
appStdout += msg || ''
},
onStderr(msg) {
appStdout += msg || ''
},
})
let appStdout = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout(msg) {
appStdout += msg || ''
},
onStderr(msg) {
appStdout += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStdout).toContain('rendered 500')
})
it('builds 500 statically by default with no pages/500', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.rename(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(true)
const pagesManifest = await getPagesManifest(appDir)
await updatePagesManifest(
appDir,
JSON.stringify({
...pagesManifest,
'/500': pagesManifest['/404'].replace('/404', '/500'),
expect(appStdout).toContain('rendered 500')
})
)
// ensure static 500 hydrates correctly
const appPort = await findPort()
const app = await nextStart(appDir, appPort)
it('builds 500 statically by default with no pages/500', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.rename(`${pages500}.bak`, pages500)
try {
const browser = await webdriver(appPort, '/err?hello=world')
const initialTitle = await browser.eval('document.title')
expect(stderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(true)
const currentTitle = await browser.eval('document.title')
const pagesManifest = await getPagesManifest(appDir)
await updatePagesManifest(
appDir,
JSON.stringify({
...pagesManifest,
'/500': pagesManifest['/404'].replace('/404', '/500'),
})
)
expect(initialTitle).toBe(currentTitle)
expect(initialTitle).toBe('500: Internal Server Error')
} finally {
await killApp(app)
// ensure static 500 hydrates correctly
const appPort = await findPort()
const app = await nextStart(appDir, appPort)
try {
const browser = await webdriver(appPort, '/err?hello=world')
const initialTitle = await browser.eval('document.title')
const currentTitle = await browser.eval('document.title')
expect(initialTitle).toBe(currentTitle)
expect(initialTitle).toBe('500: Internal Server Error')
} finally {
await killApp(app)
}
})
it('builds 500 statically by default with no pages/500 and custom _error without getInitialProps', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(true)
})
it('does not build 500 statically with no pages/500 and custom getInitialProps in _error', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
Error.getInitialProps = ({ req, res, err }) => {
console.error('called _error.getInitialProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
let appStderr = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStderr(msg) {
appStderr += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStderr).toContain('called _error.getInitialProps')
})
it('does not build 500 statically with no pages/500 and custom getInitialProps in _error and _app', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
Error.getInitialProps = ({ req, res, err }) => {
console.error('called _error.getInitialProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
export default Error
`
)
await fs.writeFile(
pagesApp,
`
function App({ pageProps, Component }) {
return <Component {...pageProps} />
}
App.getInitialProps = async ({ Component, ctx }) => {
// throw _app GIP err here
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
export default App
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
await fs.remove(pagesApp)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
})
it('shows error with getInitialProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
page.getInitialProps = () => ({ a: 'b' })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
expect(code).toBe(1)
})
}
})
it('builds 500 statically by default with no pages/500 and custom _error without getInitialProps', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(true)
})
it('does not build 500 statically with no pages/500 and custom getInitialProps in _error', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
Error.getInitialProps = ({ req, res, err }) => {
console.error('called _error.getInitialProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
let appStderr = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStderr(msg) {
appStderr += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStderr).toContain('called _error.getInitialProps')
})
it('does not build 500 statically with no pages/500 and custom getInitialProps in _error and _app', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
Error.getInitialProps = ({ req, res, err }) => {
console.error('called _error.getInitialProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
export default Error
`
)
await fs.writeFile(
pagesApp,
`
function App({ pageProps, Component }) {
return <Component {...pageProps} />
}
App.getInitialProps = async ({ Component, ctx }) => {
// throw _app GIP err here
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
export default App
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
await fs.remove(pagesApp)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
})
it('shows error with getInitialProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
page.getInitialProps = () => ({ a: 'b' })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
expect(code).toBe(1)
})
it('shows error with getInitialProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
page.getInitialProps = () => ({ a: 'b' })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/500')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
})
)
})

View file

@ -19,103 +19,105 @@ let cdnAccessLog = []
const nextConfig = new File(path.resolve(__dirname, '../next.config.js'))
describe('absolute assetPrefix with path prefix', () => {
beforeAll(async () => {
cdnPort = await findPort()
// lightweight http proxy
cdn = http.createServer((clientReq, clientRes) => {
const proxyPath = clientReq.url.slice('/path-prefix'.length)
cdnAccessLog.push(proxyPath)
const proxyReq = http.request(
{
hostname: 'localhost',
port: appPort,
path: proxyPath,
method: clientReq.method,
headers: clientReq.headers,
},
(proxyRes) => {
// cdn must be configured to allow requests from this origin
proxyRes.headers[
'Access-Control-Allow-Origin'
] = `http://localhost:${appPort}`
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers)
proxyRes.pipe(clientRes, { end: true })
}
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
cdnPort = await findPort()
// lightweight http proxy
cdn = http.createServer((clientReq, clientRes) => {
const proxyPath = clientReq.url.slice('/path-prefix'.length)
cdnAccessLog.push(proxyPath)
const proxyReq = http.request(
{
hostname: 'localhost',
port: appPort,
path: proxyPath,
method: clientReq.method,
headers: clientReq.headers,
},
(proxyRes) => {
// cdn must be configured to allow requests from this origin
proxyRes.headers[
'Access-Control-Allow-Origin'
] = `http://localhost:${appPort}`
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers)
proxyRes.pipe(clientRes, { end: true })
}
)
clientReq.pipe(proxyReq, { end: true })
clientReq.pipe(proxyReq, { end: true })
})
await new Promise((resolve) => cdn.listen(cdnPort, resolve))
nextConfig.replace('__CDN_PORT__', cdnPort)
await nextBuild(appDir)
buildId = await fs.readFile(
path.resolve(__dirname, '../.next/BUILD_ID'),
'utf8'
)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
await new Promise((resolve) => cdn.listen(cdnPort, resolve))
nextConfig.replace('__CDN_PORT__', cdnPort)
await nextBuild(appDir)
buildId = await fs.readFile(
path.resolve(__dirname, '../.next/BUILD_ID'),
'utf8'
)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterEach(() => {
cdnAccessLog = []
})
afterEach(() => {
cdnAccessLog = []
})
afterAll(() => killApp(app))
afterAll(() => cdn.close())
afterAll(() => nextConfig.restore())
afterAll(() => killApp(app))
afterAll(() => cdn.close())
afterAll(() => nextConfig.restore())
it('should not fetch static data from a CDN', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#about-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('hello')
expect(cdnAccessLog).not.toContain(`/_next/data/${buildId}/about.json`)
})
it('should not fetch static data from a CDN', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#about-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('hello')
expect(cdnAccessLog).not.toContain(`/_next/data/${buildId}/about.json`)
})
it('should fetch from cache correctly', async () => {
const browser = await webdriver(appPort, '/')
await browser.eval('window.clientSideNavigated = true')
await browser.waitForElementByCss('#about-link').click()
await browser.waitForElementByCss('#prop')
await browser.back()
await browser.waitForElementByCss('#about-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('hello')
expect(await browser.eval('window.clientSideNavigated')).toBe(true)
expect(
cdnAccessLog.filter(
(path) => path === `/_next/data/${buildId}/about.json`
it('should fetch from cache correctly', async () => {
const browser = await webdriver(appPort, '/')
await browser.eval('window.clientSideNavigated = true')
await browser.waitForElementByCss('#about-link').click()
await browser.waitForElementByCss('#prop')
await browser.back()
await browser.waitForElementByCss('#about-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('hello')
expect(await browser.eval('window.clientSideNavigated')).toBe(true)
expect(
cdnAccessLog.filter(
(path) => path === `/_next/data/${buildId}/about.json`
)
).toHaveLength(0)
})
it('should work with getStaticPaths prerendered', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#gsp-prerender-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('prerendered')
expect(cdnAccessLog).not.toContain(
`/_next/data/${buildId}/gsp-fallback/prerendered.json`
)
).toHaveLength(0)
})
})
it('should work with getStaticPaths prerendered', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#gsp-prerender-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('prerendered')
expect(cdnAccessLog).not.toContain(
`/_next/data/${buildId}/gsp-fallback/prerendered.json`
)
})
it('should work with getStaticPaths fallback', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#gsp-fallback-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('fallback')
expect(cdnAccessLog).not.toContain(
`/_next/data/${buildId}/gsp-fallback/fallback.json`
)
})
it('should work with getStaticPaths fallback', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#gsp-fallback-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('fallback')
expect(cdnAccessLog).not.toContain(
`/_next/data/${buildId}/gsp-fallback/fallback.json`
)
})
it('should work with getServerSideProps', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#gssp-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('foo')
expect(cdnAccessLog).not.toContain(
`/_next/data/${buildId}/gssp.json?prop=foo`
)
it('should work with getServerSideProps', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#gssp-link').click()
const prop = await browser.waitForElementByCss('#prop').text()
expect(prop).toBe('foo')
expect(cdnAccessLog).not.toContain(
`/_next/data/${buildId}/gssp.json?prop=foo`
)
})
})
})

View file

@ -13,114 +13,116 @@ const nextConfig = new File(join(appDir, 'next.config.js'))
let buildOutput
describe('AMP Validation on Export', () => {
beforeAll(async () => {
const { stdout = '', stderr = '' } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
await nextExport(appDir, { outdir: outDir }, { ignoreFail: true })
buildOutput = stdout + stderr
})
it('should have shown errors during build', async () => {
expect(buildOutput).toMatch(
/error.*The mandatory attribute 'height' is missing in tag 'amp-video'\./
)
})
it('should export AMP pages', async () => {
const toCheck = ['first', 'second', 'third.amp']
await Promise.all(
toCheck.map(async (page) => {
const content = await readFile(join(outDir, `${page}.html`))
await validateAMP(content.toString())
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
const { stdout = '', stderr = '' } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
)
})
await nextExport(appDir, { outdir: outDir }, { ignoreFail: true })
buildOutput = stdout + stderr
})
// this is now an error instead of a warning
it.skip('shows AMP warning without throwing error', async () => {
nextConfig.replace(
'// exportPathMap',
`exportPathMap: function(defaultMap) {
it('should have shown errors during build', async () => {
expect(buildOutput).toMatch(
/error.*The mandatory attribute 'height' is missing in tag 'amp-video'\./
)
})
it('should export AMP pages', async () => {
const toCheck = ['first', 'second', 'third.amp']
await Promise.all(
toCheck.map(async (page) => {
const content = await readFile(join(outDir, `${page}.html`))
await validateAMP(content.toString())
})
)
})
// this is now an error instead of a warning
it.skip('shows AMP warning without throwing error', async () => {
nextConfig.replace(
'// exportPathMap',
`exportPathMap: function(defaultMap) {
return {
'/cat': { page: '/cat' },
}
},`
)
try {
const { stdout, stderr } = await runNextCommand(['export', appDir], {
stdout: true,
stderr: true,
})
expect(stdout).toMatch(
/error.*The mandatory attribute 'height' is missing in tag 'amp-video'\./
)
await expect(access(join(outDir, 'cat.html'))).resolves.toBe(undefined)
await expect(stderr).not.toMatch(
/Found conflicting amp tag "meta" with conflicting prop name="viewport"/
)
} finally {
nextConfig.restore()
}
})
// img instead of amp-img no longer shows a warning
it.skip('throws error on AMP error', async () => {
nextConfig.replace(
'// exportPathMap',
`exportPathMap: function(defaultMap) {
try {
const { stdout, stderr } = await runNextCommand(['export', appDir], {
stdout: true,
stderr: true,
})
expect(stdout).toMatch(
/error.*The mandatory attribute 'height' is missing in tag 'amp-video'\./
)
await expect(access(join(outDir, 'cat.html'))).resolves.toBe(undefined)
await expect(stderr).not.toMatch(
/Found conflicting amp tag "meta" with conflicting prop name="viewport"/
)
} finally {
nextConfig.restore()
}
})
// img instead of amp-img no longer shows a warning
it.skip('throws error on AMP error', async () => {
nextConfig.replace(
'// exportPathMap',
`exportPathMap: function(defaultMap) {
return {
'/dog': { page: '/dog' },
}
},`
)
try {
const { stdout, stderr } = await runNextCommand(['export', appDir], {
stdout: true,
stderr: true,
})
expect(stdout).toMatch(
/error.*The parent tag of tag 'img' is 'div', but it can only be 'i-amphtml-sizer-intrinsic'\./
)
await expect(access(join(outDir, 'dog.html'))).resolves.toBe(undefined)
await expect(stderr).not.toMatch(
/Found conflicting amp tag "meta" with conflicting prop name="viewport"/
)
} finally {
nextConfig.restore()
}
})
// img instead of amp-img no longer shows a warning
it.skip('shows warning and error when throwing error', async () => {
nextConfig.replace(
'// exportPathMap',
`exportPathMap: function(defaultMap) {
try {
const { stdout, stderr } = await runNextCommand(['export', appDir], {
stdout: true,
stderr: true,
})
expect(stdout).toMatch(
/error.*The parent tag of tag 'img' is 'div', but it can only be 'i-amphtml-sizer-intrinsic'\./
)
await expect(access(join(outDir, 'dog.html'))).resolves.toBe(undefined)
await expect(stderr).not.toMatch(
/Found conflicting amp tag "meta" with conflicting prop name="viewport"/
)
} finally {
nextConfig.restore()
}
})
// img instead of amp-img no longer shows a warning
it.skip('shows warning and error when throwing error', async () => {
nextConfig.replace(
'// exportPathMap',
`exportPathMap: function(defaultMap) {
return {
'/dog-cat': { page: '/dog-cat' },
}
},`
)
)
try {
const { stdout, stderr } = await runNextCommand(['export', appDir], {
stdout: true,
stderr: true,
})
expect(stdout).toMatch(
/error.*The parent tag of tag 'img' is 'div', but it can only be 'i-amphtml-sizer-intrinsic'\./
)
await expect(access(join(outDir, 'dog-cat.html'))).resolves.toBe(
undefined
)
await expect(stderr).not.toMatch(
/Found conflicting amp tag "meta" with conflicting prop name="viewport"/
)
} finally {
nextConfig.restore()
}
try {
const { stdout, stderr } = await runNextCommand(['export', appDir], {
stdout: true,
stderr: true,
})
expect(stdout).toMatch(
/error.*The parent tag of tag 'img' is 'div', but it can only be 'i-amphtml-sizer-intrinsic'\./
)
await expect(access(join(outDir, 'dog-cat.html'))).resolves.toBe(
undefined
)
await expect(stderr).not.toMatch(
/Found conflicting amp tag "meta" with conflicting prop name="viewport"/
)
} finally {
nextConfig.restore()
}
})
})
})

View file

@ -14,41 +14,43 @@ let appPort
const appDir = join(__dirname, '../')
describe('AMP Custom Optimizer', () => {
it('should build and start for static page', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build and start for static page', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
appPort = await findPort()
app = await nextStart(appDir, appPort)
appPort = await findPort()
app = await nextStart(appDir, appPort)
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
expect(html).toContain(
'amp-twitter width="500" height="500" layout="responsive" data-tweetid="1159145442896166912"'
)
expect(html).toContain('i-amphtml-version="001515617716922"')
expect(html).toContain(
'script async src="https://cdn.ampproject.org/rtv/001515617716922/v0.mjs"'
)
})
expect(html).toContain(
'amp-twitter width="500" height="500" layout="responsive" data-tweetid="1159145442896166912"'
)
expect(html).toContain('i-amphtml-version="001515617716922"')
expect(html).toContain(
'script async src="https://cdn.ampproject.org/rtv/001515617716922/v0.mjs"'
)
})
it('should build and start for dynamic page', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
it('should build and start for dynamic page', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
appPort = await findPort()
app = await nextStart(appDir, appPort)
appPort = await findPort()
app = await nextStart(appDir, appPort)
const html = await renderViaHTTP(appPort, '/dynamic')
await killApp(app)
const html = await renderViaHTTP(appPort, '/dynamic')
await killApp(app)
expect(html).toContain(
'amp-img width="500" height="500" layout="responsive" src="https://amp.dev/static/samples/img/story_dog2_portrait.jpg"'
)
expect(html).toContain('i-amphtml-version="001515617716922"')
expect(html).toContain(
'script async src="https://cdn.ampproject.org/rtv/001515617716922/v0.mjs"'
)
expect(html).toContain(
'amp-img width="500" height="500" layout="responsive" src="https://amp.dev/static/samples/img/story_dog2_portrait.jpg"'
)
expect(html).toContain('i-amphtml-version="001515617716922"')
expect(html).toContain(
'script async src="https://cdn.ampproject.org/rtv/001515617716922/v0.mjs"'
)
})
})
})

View file

@ -15,33 +15,37 @@ let appPort
const appDir = join(__dirname, '../')
describe('AMP Custom Validator', () => {
it('should build and start successfully', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build and start successfully', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
appPort = await findPort()
app = await nextStart(appDir, appPort)
appPort = await findPort()
app = await nextStart(appDir, appPort)
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
expect(html).toContain('Hello from AMP')
expect(html).toContain('Hello from AMP')
})
})
it('should run in dev mode successfully', async () => {
let stderr = ''
describe('development mode', () => {
it('should run in dev mode successfully', async () => {
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
expect(stderr).not.toContain('error')
expect(html).toContain('Hello from AMP')
})
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
expect(stderr).not.toContain('error')
expect(html).toContain('Hello from AMP')
})
})

View file

@ -16,19 +16,21 @@ let appPort
let app
describe('AMP Fragment Styles', () => {
beforeAll(async () => {
await nextBuild(appDir, [])
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir, [])
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('adds styles from fragment in AMP mode correctly', async () => {
const html = await renderViaHTTP(appPort, '/', { amp: 1 })
await validateAMP(html)
const $ = cheerio.load(html)
const styles = $('style[amp-custom]').text()
expect(styles).toMatch(/background:(.*|)hotpink/)
expect(styles).toMatch(/font-size:(.*|)16\.4px/)
it('adds styles from fragment in AMP mode correctly', async () => {
const html = await renderViaHTTP(appPort, '/', { amp: 1 })
await validateAMP(html)
const $ = cheerio.load(html)
const styles = $('style[amp-custom]').text()
expect(styles).toMatch(/background:(.*|)hotpink/)
expect(styles).toMatch(/font-size:(.*|)16\.4px/)
})
})
})

View file

@ -118,32 +118,37 @@ describe('AMP SSG Support', () => {
runTests(true)
})
describe('export mode', () => {
let buildId
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
let buildId
beforeAll(async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir: join(appDir, 'out') })
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
beforeAll(async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir: join(appDir, 'out') })
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
it('should have copied SSG files correctly', async () => {
const outFile = (file) => join(appDir, 'out', file)
it('should have copied SSG files correctly', async () => {
const outFile = (file) => join(appDir, 'out', file)
expect(await fsExists(outFile('amp.html'))).toBe(true)
expect(await fsExists(outFile('index.html'))).toBe(true)
expect(await fsExists(outFile('hybrid.html'))).toBe(true)
expect(await fsExists(outFile('amp.amp.html'))).toBe(false)
expect(await fsExists(outFile('hybrid.amp.html'))).toBe(true)
expect(await fsExists(outFile('blog/post-1.html'))).toBe(true)
expect(await fsExists(outFile('blog/post-1.amp.html'))).toBe(true)
expect(await fsExists(outFile('amp.html'))).toBe(true)
expect(await fsExists(outFile('index.html'))).toBe(true)
expect(await fsExists(outFile('hybrid.html'))).toBe(true)
expect(await fsExists(outFile('amp.amp.html'))).toBe(false)
expect(await fsExists(outFile('hybrid.amp.html'))).toBe(true)
expect(await fsExists(outFile('blog/post-1.html'))).toBe(true)
expect(await fsExists(outFile('blog/post-1.amp.html'))).toBe(true)
expect(
await fsExists(outFile(join('_next/data', buildId, 'amp.json')))
).toBe(true)
expect(
await fsExists(outFile(join('_next/data', buildId, 'amp.json')))
).toBe(true)
expect(
await fsExists(outFile(join('_next/data', buildId, 'hybrid.json')))
).toBe(true)
})
expect(
await fsExists(outFile(join('_next/data', buildId, 'hybrid.json')))
).toBe(true)
})
}
)
})
})

View file

@ -59,8 +59,7 @@ describe('API routes', () => {
runTests()
})
describe('Server support', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()

View file

@ -643,8 +643,7 @@ describe('API routes', () => {
runTests(true)
})
describe('Server support', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
mode = 'server'

View file

@ -11,85 +11,54 @@ import {
} from './utils'
describe('app dir with output export (next dev / next build)', () => {
it('should throw when exportPathMap configured', async () => {
nextConfig.replace(
'trailingSlash: true,',
`trailingSlash: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should throw when exportPathMap configured', async () => {
nextConfig.replace(
'trailingSlash: true,',
`trailingSlash: true,
exportPathMap: async function (map) {
return map
},`
)
await fs.remove(distDir)
await fs.remove(exportDir)
let result = { code: 0, stderr: '' }
try {
result = await nextBuild(appDir, [], { stderr: true })
} finally {
nextConfig.restore()
}
expect(result.code).toBe(1)
expect(result.stderr).toContain(
'The "exportPathMap" configuration cannot be used with the "app" directory. Please use generateStaticParams() instead.'
)
})
it('should warn about "next export" is no longer needed with config', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
await nextBuild(appDir)
expect(await getFiles()).toEqual(expectedWhenTrailingSlashTrue)
let stdout = ''
let stderr = ''
await nextExportDefault(appDir, {
onStdout(msg) {
stdout += msg
},
onStderr(msg) {
stderr += msg
},
})
expect(stderr).toContain(
'"next export" is no longer needed when "output: export" is configured in next.config.js'
)
expect(stdout).toContain('Export successful. Files written to')
expect(await getFiles()).toEqual(expectedWhenTrailingSlashTrue)
})
it('should error when "next export -o <dir>" is used with config', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
await nextBuild(appDir)
expect(await getFiles()).toEqual(expectedWhenTrailingSlashTrue)
let stdout = ''
let stderr = ''
let error = undefined
try {
await nextExport(
appDir,
{ outdir: exportDir },
{
onStdout(msg) {
stdout += msg
},
onStderr(msg) {
stderr += msg
},
}
)
} catch (e) {
error = e
}
expect(error).toBeDefined()
expect(stderr).toContain(
'"next export -o <dir>" cannot be used when "output: export" is configured in next.config.js. Instead add "distDir" in next.config.js'
)
expect(stdout).not.toContain('Export successful. Files written to')
})
it('should error when no config.output detected for next export', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
nextConfig.replace(`output: 'export',`, '')
try {
await fs.remove(distDir)
await fs.remove(exportDir)
let result = { code: 0, stderr: '' }
try {
result = await nextBuild(appDir, [], { stderr: true })
} finally {
nextConfig.restore()
}
expect(result.code).toBe(1)
expect(result.stderr).toContain(
'The "exportPathMap" configuration cannot be used with the "app" directory. Please use generateStaticParams() instead.'
)
})
it('should warn about "next export" is no longer needed with config', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
await nextBuild(appDir)
expect(await getFiles()).toEqual([])
expect(await getFiles()).toEqual(expectedWhenTrailingSlashTrue)
let stdout = ''
let stderr = ''
await nextExportDefault(appDir, {
onStdout(msg) {
stdout += msg
},
onStderr(msg) {
stderr += msg
},
})
expect(stderr).toContain(
'"next export" is no longer needed when "output: export" is configured in next.config.js'
)
expect(stdout).toContain('Export successful. Files written to')
expect(await getFiles()).toEqual(expectedWhenTrailingSlashTrue)
})
it('should error when "next export -o <dir>" is used with config', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
await nextBuild(appDir)
expect(await getFiles()).toEqual(expectedWhenTrailingSlashTrue)
let stdout = ''
let stderr = ''
let error = undefined
@ -111,32 +80,65 @@ describe('app dir with output export (next dev / next build)', () => {
}
expect(error).toBeDefined()
expect(stderr).toContain(
'"next export" does not work with App Router. Please use "output: export" in next.config.js'
'"next export -o <dir>" cannot be used when "output: export" is configured in next.config.js. Instead add "distDir" in next.config.js'
)
expect(stdout).not.toContain('Export successful. Files written to')
expect(await getFiles()).toEqual([])
} finally {
nextConfig.restore()
})
it('should error when no config.output detected for next export', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
}
})
it('should correctly emit exported assets to config.distDir', async () => {
const outputDir = join(appDir, 'output')
await fs.remove(distDir)
await fs.remove(outputDir)
nextConfig.replace(
'trailingSlash: true,',
`trailingSlash: true,
distDir: 'output',`
)
try {
await nextBuild(appDir)
expect(await getFiles(outputDir)).toEqual(expectedWhenTrailingSlashTrue)
} finally {
nextConfig.restore()
nextConfig.replace(`output: 'export',`, '')
try {
await nextBuild(appDir)
expect(await getFiles()).toEqual([])
let stdout = ''
let stderr = ''
let error = undefined
try {
await nextExport(
appDir,
{ outdir: exportDir },
{
onStdout(msg) {
stdout += msg
},
onStderr(msg) {
stderr += msg
},
}
)
} catch (e) {
error = e
}
expect(error).toBeDefined()
expect(stderr).toContain(
'"next export" does not work with App Router. Please use "output: export" in next.config.js'
)
expect(stdout).not.toContain('Export successful. Files written to')
expect(await getFiles()).toEqual([])
} finally {
nextConfig.restore()
await fs.remove(distDir)
await fs.remove(exportDir)
}
})
it('should correctly emit exported assets to config.distDir', async () => {
const outputDir = join(appDir, 'output')
await fs.remove(distDir)
await fs.remove(outputDir)
}
nextConfig.replace(
'trailingSlash: true,',
`trailingSlash: true,
distDir: 'output',`
)
try {
await nextBuild(appDir)
expect(await getFiles(outputDir)).toEqual(expectedWhenTrailingSlashTrue)
} finally {
nextConfig.restore()
await fs.remove(distDir)
await fs.remove(outputDir)
}
})
})
})

View file

@ -18,43 +18,45 @@ const nextConfig = new File(join(appDir, 'next.config.js'))
let app
describe('app dir with output export (next start)', () => {
afterEach(async () => {
await killApp(app)
nextConfig.restore()
await fs.remove(distDir)
await fs.remove(exportDir)
})
it('should error during next start with output export', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
const port = await findPort()
let stderr = ''
app = await nextStart(appDir, port, {
onStderr(msg: string) {
stderr += msg || ''
},
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
afterEach(async () => {
await killApp(app)
nextConfig.restore()
await fs.remove(distDir)
await fs.remove(exportDir)
})
await check(() => stderr, /error/i)
expect(stderr).toContain(
'"next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.'
)
})
it('should warn during next start with output standalone', async () => {
nextConfig.replace(`output: 'export'`, `output: 'standalone'`)
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
const port = await findPort()
let stderr = ''
app = await nextStart(appDir, port, {
onStderr(msg: string) {
stderr += msg || ''
},
it('should error during next start with output export', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
const port = await findPort()
let stderr = ''
app = await nextStart(appDir, port, {
onStderr(msg: string) {
stderr += msg || ''
},
})
await check(() => stderr, /error/i)
expect(stderr).toContain(
'"next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.'
)
})
it('should warn during next start with output standalone', async () => {
nextConfig.replace(`output: 'export'`, `output: 'standalone'`)
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
const port = await findPort()
let stderr = ''
app = await nextStart(appDir, port, {
onStderr(msg: string) {
stderr += msg || ''
},
})
await check(() => stderr, /⚠/i)
expect(stderr).toContain(
`"next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.`
)
})
await check(() => stderr, /⚠/i)
expect(stderr).toContain(
`"next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.`
)
})
})

View file

@ -51,24 +51,26 @@ const respectsChunkAttachmentOrder = async () => {
}
describe('Root components import order', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it(
'_app chunks should be attached to de dom before page chunks',
respectsChunkAttachmentOrder
)
it(
'root components should be imported in this order _document > _app > page in order to respect side effects',
respectsSideEffects
)
it(
'_app chunks should be attached to de dom before page chunks',
respectsChunkAttachmentOrder
)
it(
'root components should be imported in this order _document > _app > page in order to respect side effects',
respectsSideEffects
)
})
})
describe('on dev server', () => {
describe('development mode', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(join(__dirname, '../'), appPort)

View file

@ -16,25 +16,27 @@ let server
let app
describe('Custom Document Fragment Styles', () => {
beforeAll(async () => {
await nextBuild(appDir)
app = nextServer({
dir: join(__dirname, '../'),
dev: false,
quiet: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
app = nextServer({
dir: join(__dirname, '../'),
dev: false,
quiet: true,
})
server = await startApp(app)
appPort = server.address().port
})
afterAll(() => stopApp(server))
server = await startApp(app)
appPort = server.address().port
})
afterAll(() => stopApp(server))
it('correctly adds styles from fragment styles key', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
it('correctly adds styles from fragment styles key', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
const styles = $('style').text()
expect(styles).toMatch(/background:(.*|)hotpink/)
expect(styles).toMatch(/font-size:(.*|)16\.4px/)
const styles = $('style').text()
expect(styles).toMatch(/background:(.*|)hotpink/)
expect(styles).toMatch(/font-size:(.*|)16\.4px/)
})
})
})

View file

@ -1,12 +1,17 @@
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
it('throws an error when prerendering a page with config dynamic error', async () => {
const appDir = join(__dirname, '../../app-dynamic-error')
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
describe('app-dynamic-error', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('throws an error when prerendering a page with config dynamic error', async () => {
const appDir = join(__dirname, '../../app-dynamic-error')
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
expect(stderr).toContain(
'Error occurred prerendering page "/dynamic-error"'
)
expect(code).toBe(1)
})
})
expect(stderr).toContain('Error occurred prerendering page "/dynamic-error"')
expect(code).toBe(1)
})

View file

@ -27,8 +27,10 @@ const runTests = () => {
})
}
describe('dev mode', () => {
// Skip as it runs `next build`, seems that is a bug.
;(process.env.TURBOPACK ? describe.skip : describe)('dev mode', () => {
beforeAll(async () => {
// TODO: This look like a bug, `nextBuild` shouldn't be required here.
await nextBuild(appDir)
appPort = await findPort()
buildId = 'development'

View file

@ -11,39 +11,258 @@ const fixturesDir = join(__dirname, '..', 'fixtures')
const nextConfig = new File(join(fixturesDir, 'basic-app/next.config.js'))
describe('Build Output', () => {
const configs = [{}, { gzipSize: false }]
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
const configs = [{}, { gzipSize: false }]
for (const experimental of configs) {
describe(`Basic Application Output (experimental: ${JSON.stringify(
experimental
)})`, () => {
let stdout
const appDir = join(fixturesDir, 'basic-app')
for (const experimental of configs) {
describe(`Basic Application Output (experimental: ${JSON.stringify(
experimental
)})`, () => {
let stdout
const appDir = join(fixturesDir, 'basic-app')
const hasExperimentalConfig = Object.keys(experimental).length > 0
const hasExperimentalConfig = Object.keys(experimental).length > 0
beforeAll(async () => {
await remove(join(appDir, '.next'))
if (hasExperimentalConfig) {
nextConfig.write(
`module.exports = { experimental: ${JSON.stringify(
experimental
)} };`
)
}
;({ stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
if (hasExperimentalConfig) {
afterAll(async () => {
nextConfig.delete()
})
}
it('should not include internal pages', async () => {
expect(stdout).toMatch(/\/ (.* )?\d{1,} B/)
expect(stdout).toMatch(/\+ First Load JS shared by all [ 0-9.]* kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js [ 0-9.]* kB/)
expect(stdout).toMatch(
/ chunks\/framework-[0-9a-z]{16}\.js [ 0-9. ]* kB/
)
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_app')
expect(stdout).not.toContain(' /_error')
expect(stdout).not.toContain('<buildId>')
expect(stdout).toContain('○ /')
})
// TODO: change format of this test to be more reliable
it.skip('should not deviate from snapshot', async () => {
console.log(stdout)
if (process.env.NEXT_PRIVATE_SKIP_SIZE_TESTS) {
return
}
const parsePageSize = (page) =>
stdout.match(
new RegExp(` ${page} .*?((?:\\d|\\.){1,} (?:\\w{1,})) `)
)[1]
const parsePageFirstLoad = (page) =>
stdout.match(
new RegExp(
` ${page} .*?(?:(?:\\d|\\.){1,}) .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`
)
)[1]
const parseSharedSize = (sharedPartName) => {
const matches = stdout.match(
new RegExp(`${sharedPartName} .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`)
)
if (!matches) {
throw new Error(`Could not match ${sharedPartName}`)
}
return matches[1]
}
const indexSize = parsePageSize('/')
const indexFirstLoad = parsePageFirstLoad('/')
const err404Size = parsePageSize('/404')
const err404FirstLoad = parsePageFirstLoad('/404')
const sharedByAll = parseSharedSize('shared by all')
const _appSize = parseSharedSize('_app-.*?\\.js')
const webpackSize = parseSharedSize('webpack-.*?\\.js')
const mainSize = parseSharedSize('main-.*?\\.js')
const frameworkSize = parseSharedSize('framework-.*?\\.js')
for (const size of [
indexSize,
indexFirstLoad,
err404Size,
err404FirstLoad,
sharedByAll,
_appSize,
webpackSize,
mainSize,
frameworkSize,
]) {
expect(parseFloat(size)).toBeGreaterThan(0)
}
// const gz = experimental.gzipSize !== false
// expect(parseFloat(indexSize) / 1000).toBeCloseTo(
// gz ? 0.251 : 0.394,
// 2
// )
expect(indexSize.endsWith('B')).toBe(true)
// expect(parseFloat(indexFirstLoad)).toBeCloseTo(gz ? 64 : 196, 1)
expect(indexFirstLoad.endsWith('kB')).toBe(true)
// expect(parseFloat(err404Size)).toBeCloseTo(gz ? 3.17 : 8.51, 1)
expect(err404Size.endsWith('B')).toBe(true)
// expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 204, 1)
expect(err404FirstLoad.endsWith('kB')).toBe(true)
// expect(parseFloat(sharedByAll)).toBeCloseTo(gz ? 63.7 : 196, 1)
expect(sharedByAll.endsWith('kB')).toBe(true)
// const appSizeValue = _appSize.endsWith('kB')
// ? parseFloat(_appSize)
// : parseFloat(_appSize) / 1000
// expect(appSizeValue).toBeCloseTo(gz ? 0.799 : 1.63, 1)
expect(_appSize.endsWith('kB') || _appSize.endsWith(' B')).toBe(true)
// const webpackSizeValue = webpackSize.endsWith('kB')
// ? parseFloat(webpackSize)
// : parseFloat(webpackSize) / 1000
// expect(webpackSizeValue).toBeCloseTo(gz ? 0.766 : 1.46, 2)
expect(webpackSize.endsWith('kB') || webpackSize.endsWith(' B')).toBe(
true
)
// expect(parseFloat(mainSize)).toBeCloseTo(gz ? 20.1 : 62.7, 1)
expect(mainSize.endsWith('kB')).toBe(true)
// expect(parseFloat(frameworkSize)).toBeCloseTo(gz ? 42.0 : 130, 1)
expect(frameworkSize.endsWith('kB')).toBe(true)
})
it('should print duration when rendering or get static props takes long', () => {
const matches = stdout.match(
/ \/slow-static\/.+\/.+(?: \(\d+ ms\))?| \[\+\d+ more paths\]/g
)
for (const check of [
// summary
expect.stringMatching(
/\/\[propsDuration\]\/\[renderDuration\] \(\d+ ms\)/
),
// ordered by duration, includes duration
expect.stringMatching(/\/2000\/10 \(\d+ ms\)$/),
expect.stringMatching(/\/10\/1000 \(\d+ ms\)$/),
expect.stringMatching(/\/300\/10 \(\d+ ms\)$/),
// max of 7 preview paths
' [+2 more paths]',
]) {
// the order isn't guaranteed on the timing tests as while() is being
// used in the render so can block the thread of other renders sharing
// the same worker
expect(matches).toContainEqual(check)
}
})
it('should not emit extracted comments', async () => {
const files = await recursiveReadDir(join(appDir, '.next'), {
pathnameFilter: (f) => /\.txt|\.LICENSE\./.test(f),
})
expect(files).toEqual([])
})
})
}
describe('Custom App Output', () => {
const appDir = join(fixturesDir, 'with-app')
beforeAll(async () => {
await remove(join(appDir, '.next'))
if (hasExperimentalConfig) {
nextConfig.write(
`module.exports = { experimental: ${JSON.stringify(
experimental
)} };`
)
}
;({ stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
if (hasExperimentalConfig) {
afterAll(async () => {
nextConfig.delete()
it('should not include custom error', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
}
it('should not include internal pages', async () => {
expect(stdout).toMatch(/\/ (.* )?\d{1,} B/)
expect(stdout).toMatch(/\/_app (.* )?\d{1,} B/)
expect(stdout).toMatch(/\+ First Load JS shared by all \s*[0-9.]+ kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js \s*[0-9.]+ kB/)
expect(stdout).toMatch(
/ chunks\/framework-[0-9a-z]{16}\.js \s*[0-9.]+ kB/
)
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_error')
expect(stdout).not.toContain('<buildId>')
expect(stdout).toContain(' /_app')
expect(stdout).toContain('○ /')
})
})
describe('With AMP Output', () => {
const appDir = join(fixturesDir, 'with-amp')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not include custom error', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(stdout).toMatch(/\/ (.* )?[0-9.]+ B \s*[0-9.]+ kB/)
expect(stdout).toMatch(/\/amp (.* )?AMP/)
expect(stdout).toMatch(/\/hybrid (.* )?[0-9.]+ B/)
expect(stdout).toMatch(/\+ First Load JS shared by all \s*[0-9.]+ kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js \s*[0-9.]+ kB/)
expect(stdout).toMatch(
/ chunks\/framework-[0-9a-z]{16}\.js \s*[0-9.]+ kB/
)
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_error')
expect(stdout).not.toContain('<buildId>')
expect(stdout).toContain('○ /')
})
})
describe('Custom Error Output', () => {
const appDir = join(fixturesDir, 'with-error')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not include custom app', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(stdout).toMatch(/\/ (.* )?\d{1,} B/)
expect(stdout).toMatch(/λ \/404 (.* )?\d{1,} B/)
expect(stdout).toMatch(/\+ First Load JS shared by all [ 0-9.]* kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js [ 0-9.]* kB/)
expect(stdout).toMatch(
@ -52,243 +271,28 @@ describe('Build Output', () => {
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_app')
expect(stdout).not.toContain(' /_error')
expect(stdout).not.toContain('<buildId>')
expect(stdout).not.toContain(' /_error')
expect(stdout).toContain('○ /')
})
})
// TODO: change format of this test to be more reliable
it.skip('should not deviate from snapshot', async () => {
console.log(stdout)
describe('Custom Static Error Output', () => {
const appDir = join(fixturesDir, 'with-error-static')
if (process.env.NEXT_PRIVATE_SKIP_SIZE_TESTS) {
return
}
const parsePageSize = (page) =>
stdout.match(
new RegExp(` ${page} .*?((?:\\d|\\.){1,} (?:\\w{1,})) `)
)[1]
const parsePageFirstLoad = (page) =>
stdout.match(
new RegExp(
` ${page} .*?(?:(?:\\d|\\.){1,}) .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`
)
)[1]
const parseSharedSize = (sharedPartName) => {
const matches = stdout.match(
new RegExp(`${sharedPartName} .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`)
)
if (!matches) {
throw new Error(`Could not match ${sharedPartName}`)
}
return matches[1]
}
const indexSize = parsePageSize('/')
const indexFirstLoad = parsePageFirstLoad('/')
const err404Size = parsePageSize('/404')
const err404FirstLoad = parsePageFirstLoad('/404')
const sharedByAll = parseSharedSize('shared by all')
const _appSize = parseSharedSize('_app-.*?\\.js')
const webpackSize = parseSharedSize('webpack-.*?\\.js')
const mainSize = parseSharedSize('main-.*?\\.js')
const frameworkSize = parseSharedSize('framework-.*?\\.js')
for (const size of [
indexSize,
indexFirstLoad,
err404Size,
err404FirstLoad,
sharedByAll,
_appSize,
webpackSize,
mainSize,
frameworkSize,
]) {
expect(parseFloat(size)).toBeGreaterThan(0)
}
// const gz = experimental.gzipSize !== false
// expect(parseFloat(indexSize) / 1000).toBeCloseTo(
// gz ? 0.251 : 0.394,
// 2
// )
expect(indexSize.endsWith('B')).toBe(true)
// expect(parseFloat(indexFirstLoad)).toBeCloseTo(gz ? 64 : 196, 1)
expect(indexFirstLoad.endsWith('kB')).toBe(true)
// expect(parseFloat(err404Size)).toBeCloseTo(gz ? 3.17 : 8.51, 1)
expect(err404Size.endsWith('B')).toBe(true)
// expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 204, 1)
expect(err404FirstLoad.endsWith('kB')).toBe(true)
// expect(parseFloat(sharedByAll)).toBeCloseTo(gz ? 63.7 : 196, 1)
expect(sharedByAll.endsWith('kB')).toBe(true)
// const appSizeValue = _appSize.endsWith('kB')
// ? parseFloat(_appSize)
// : parseFloat(_appSize) / 1000
// expect(appSizeValue).toBeCloseTo(gz ? 0.799 : 1.63, 1)
expect(_appSize.endsWith('kB') || _appSize.endsWith(' B')).toBe(true)
// const webpackSizeValue = webpackSize.endsWith('kB')
// ? parseFloat(webpackSize)
// : parseFloat(webpackSize) / 1000
// expect(webpackSizeValue).toBeCloseTo(gz ? 0.766 : 1.46, 2)
expect(webpackSize.endsWith('kB') || webpackSize.endsWith(' B')).toBe(
true
)
// expect(parseFloat(mainSize)).toBeCloseTo(gz ? 20.1 : 62.7, 1)
expect(mainSize.endsWith('kB')).toBe(true)
// expect(parseFloat(frameworkSize)).toBeCloseTo(gz ? 42.0 : 130, 1)
expect(frameworkSize.endsWith('kB')).toBe(true)
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should print duration when rendering or get static props takes long', () => {
const matches = stdout.match(
/ \/slow-static\/.+\/.+(?: \(\d+ ms\))?| \[\+\d+ more paths\]/g
)
for (const check of [
// summary
expect.stringMatching(
/\/\[propsDuration\]\/\[renderDuration\] \(\d+ ms\)/
),
// ordered by duration, includes duration
expect.stringMatching(/\/2000\/10 \(\d+ ms\)$/),
expect.stringMatching(/\/10\/1000 \(\d+ ms\)$/),
expect.stringMatching(/\/300\/10 \(\d+ ms\)$/),
// max of 7 preview paths
' [+2 more paths]',
]) {
// the order isn't guaranteed on the timing tests as while() is being
// used in the render so can block the thread of other renders sharing
// the same worker
expect(matches).toContainEqual(check)
}
})
it('should not emit extracted comments', async () => {
const files = await recursiveReadDir(join(appDir, '.next'), {
pathnameFilter: (f) => /\.txt|\.LICENSE\./.test(f),
it('should not specify /404 as lambda when static', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(files).toEqual([])
expect(stdout).toContain('○ /404')
expect(stdout).not.toContain('λ /_error')
expect(stdout).not.toContain('<buildId>')
})
})
}
describe('Custom App Output', () => {
const appDir = join(fixturesDir, 'with-app')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not include custom error', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(stdout).toMatch(/\/ (.* )?\d{1,} B/)
expect(stdout).toMatch(/\/_app (.* )?\d{1,} B/)
expect(stdout).toMatch(/\+ First Load JS shared by all \s*[0-9.]+ kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js \s*[0-9.]+ kB/)
expect(stdout).toMatch(
/ chunks\/framework-[0-9a-z]{16}\.js \s*[0-9.]+ kB/
)
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_error')
expect(stdout).not.toContain('<buildId>')
expect(stdout).toContain(' /_app')
expect(stdout).toContain('○ /')
})
})
describe('With AMP Output', () => {
const appDir = join(fixturesDir, 'with-amp')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not include custom error', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(stdout).toMatch(/\/ (.* )?[0-9.]+ B \s*[0-9.]+ kB/)
expect(stdout).toMatch(/\/amp (.* )?AMP/)
expect(stdout).toMatch(/\/hybrid (.* )?[0-9.]+ B/)
expect(stdout).toMatch(/\+ First Load JS shared by all \s*[0-9.]+ kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js \s*[0-9.]+ kB/)
expect(stdout).toMatch(
/ chunks\/framework-[0-9a-z]{16}\.js \s*[0-9.]+ kB/
)
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_error')
expect(stdout).not.toContain('<buildId>')
expect(stdout).toContain('○ /')
})
})
describe('Custom Error Output', () => {
const appDir = join(fixturesDir, 'with-error')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not include custom app', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(stdout).toMatch(/\/ (.* )?\d{1,} B/)
expect(stdout).toMatch(/λ \/404 (.* )?\d{1,} B/)
expect(stdout).toMatch(/\+ First Load JS shared by all [ 0-9.]* kB/)
expect(stdout).toMatch(/ chunks\/main-[0-9a-z]{16}\.js [ 0-9.]* kB/)
expect(stdout).toMatch(/ chunks\/framework-[0-9a-z]{16}\.js [ 0-9. ]* kB/)
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_app')
expect(stdout).not.toContain('<buildId>')
expect(stdout).not.toContain(' /_error')
expect(stdout).toContain('○ /')
})
})
describe('Custom Static Error Output', () => {
const appDir = join(fixturesDir, 'with-error-static')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not specify /404 as lambda when static', async () => {
const { stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(stdout).toContain('○ /404')
expect(stdout).not.toContain('λ /_error')
expect(stdout).not.toContain('<buildId>')
})
})
})

View file

@ -7,67 +7,71 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '../app')
describe('build trace with extra entries', () => {
it('should build and trace correctly', async () => {
const result = await nextBuild(appDir, undefined, {
cwd: appDir,
stderr: true,
stdout: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build and trace correctly', async () => {
const result = await nextBuild(appDir, undefined, {
cwd: appDir,
stderr: true,
stdout: true,
})
console.log(result)
expect(result.code).toBe(0)
const appTrace = await fs.readJSON(
join(appDir, '.next/server/pages/_app.js.nft.json')
)
const indexTrace = await fs.readJSON(
join(appDir, '.next/server/pages/index.js.nft.json')
)
const anotherTrace = await fs.readJSON(
join(appDir, '.next/server/pages/another.js.nft.json')
)
const imageTrace = await fs.readJSON(
join(appDir, '.next/server/pages/image-import.js.nft.json')
)
const tracedFiles = [
...appTrace.files,
...indexTrace.files,
...anotherTrace.files,
...imageTrace.files,
]
expect(tracedFiles.some((file) => file.endsWith('hello.json'))).toBe(true)
expect(
tracedFiles.some((file) => file.includes('some-cms/index.js'))
).toBe(true)
expect(
tracedFiles.some((file) => file === '../../../include-me/hello.txt')
).toBe(true)
expect(
tracedFiles.some((file) => file === '../../../include-me/second.txt')
).toBe(true)
expect(indexTrace.files.some((file) => file.includes('exclude-me'))).toBe(
false
)
expect(
tracedFiles.some((file) =>
file.includes('nested-structure/constants/package.json')
)
).toBe(true)
expect(
tracedFiles.some((file) =>
file.includes('nested-structure/package.json')
)
).toBe(true)
expect(
tracedFiles.some((file) =>
file.includes('nested-structure/dist/constants.js')
)
).toBe(true)
expect(
tracedFiles.some((file) => file.includes('public/another.jpg'))
).toBe(true)
expect(tracedFiles.some((file) => file.includes('public/test.jpg'))).toBe(
false
)
})
console.log(result)
expect(result.code).toBe(0)
const appTrace = await fs.readJSON(
join(appDir, '.next/server/pages/_app.js.nft.json')
)
const indexTrace = await fs.readJSON(
join(appDir, '.next/server/pages/index.js.nft.json')
)
const anotherTrace = await fs.readJSON(
join(appDir, '.next/server/pages/another.js.nft.json')
)
const imageTrace = await fs.readJSON(
join(appDir, '.next/server/pages/image-import.js.nft.json')
)
const tracedFiles = [
...appTrace.files,
...indexTrace.files,
...anotherTrace.files,
...imageTrace.files,
]
expect(tracedFiles.some((file) => file.endsWith('hello.json'))).toBe(true)
expect(tracedFiles.some((file) => file.includes('some-cms/index.js'))).toBe(
true
)
expect(
tracedFiles.some((file) => file === '../../../include-me/hello.txt')
).toBe(true)
expect(
tracedFiles.some((file) => file === '../../../include-me/second.txt')
).toBe(true)
expect(indexTrace.files.some((file) => file.includes('exclude-me'))).toBe(
false
)
expect(
tracedFiles.some((file) =>
file.includes('nested-structure/constants/package.json')
)
).toBe(true)
expect(
tracedFiles.some((file) => file.includes('nested-structure/package.json'))
).toBe(true)
expect(
tracedFiles.some((file) =>
file.includes('nested-structure/dist/constants.js')
)
).toBe(true)
expect(
tracedFiles.some((file) => file.includes('public/another.jpg'))
).toBe(true)
expect(tracedFiles.some((file) => file.includes('public/test.jpg'))).toBe(
false
)
})
})

View file

@ -7,81 +7,87 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '../app')
describe('build trace with extra entries', () => {
it('should build and trace correctly', async () => {
const result = await nextBuild(appDir, undefined, {
cwd: appDir,
stderr: true,
stdout: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build and trace correctly', async () => {
const result = await nextBuild(appDir, undefined, {
cwd: appDir,
stderr: true,
stdout: true,
})
console.log(result)
expect(result.code).toBe(0)
const appTrace = await fs.readJSON(
join(appDir, '.next/server/pages/_app.js.nft.json')
)
const indexTrace = await fs.readJSON(
join(appDir, '.next/server/pages/index.js.nft.json')
)
const anotherTrace = await fs.readJSON(
join(appDir, '.next/server/pages/another.js.nft.json')
)
const imageTrace = await fs.readJSON(
join(appDir, '.next/server/pages/image-import.js.nft.json')
)
expect(appTrace.files.some((file) => file.endsWith('hello.json'))).toBe(
true
)
expect(
appTrace.files.some((file) => file.endsWith('lib/get-data.js'))
).toBe(true)
expect(
indexTrace.files.some((file) => file.endsWith('hello.json'))
).toBeFalsy()
expect(
indexTrace.files.some((file) => file.endsWith('some-dir'))
).toBeFalsy()
expect(
indexTrace.files.some((file) =>
file.endsWith('.dot-folder/another-file.txt')
)
).toBe(true)
expect(
indexTrace.files.some((file) => file.endsWith('some-dir/file.txt'))
).toBe(true)
expect(
indexTrace.files.some((file) => file.includes('some-cms/index.js'))
).toBe(true)
expect(
indexTrace.files.some(
(file) => file === '../../../include-me/hello.txt'
)
).toBe(true)
expect(
indexTrace.files.some(
(file) => file === '../../../include-me/second.txt'
)
).toBe(true)
expect(indexTrace.files.some((file) => file.includes('exclude-me'))).toBe(
false
)
expect(
anotherTrace.files.some((file) =>
file.includes('nested-structure/constants/package.json')
)
).toBe(true)
expect(
anotherTrace.files.some((file) =>
file.includes('nested-structure/package.json')
)
).toBe(true)
expect(
anotherTrace.files.some((file) =>
file.includes('nested-structure/dist/constants.js')
)
).toBe(true)
expect(
imageTrace.files.some((file) => file.includes('public/another.jpg'))
).toBe(true)
expect(
imageTrace.files.some((file) => file.includes('public/test.jpg'))
).toBe(false)
})
console.log(result)
expect(result.code).toBe(0)
const appTrace = await fs.readJSON(
join(appDir, '.next/server/pages/_app.js.nft.json')
)
const indexTrace = await fs.readJSON(
join(appDir, '.next/server/pages/index.js.nft.json')
)
const anotherTrace = await fs.readJSON(
join(appDir, '.next/server/pages/another.js.nft.json')
)
const imageTrace = await fs.readJSON(
join(appDir, '.next/server/pages/image-import.js.nft.json')
)
expect(appTrace.files.some((file) => file.endsWith('hello.json'))).toBe(
true
)
expect(
appTrace.files.some((file) => file.endsWith('lib/get-data.js'))
).toBe(true)
expect(
indexTrace.files.some((file) => file.endsWith('hello.json'))
).toBeFalsy()
expect(
indexTrace.files.some((file) => file.endsWith('some-dir'))
).toBeFalsy()
expect(
indexTrace.files.some((file) =>
file.endsWith('.dot-folder/another-file.txt')
)
).toBe(true)
expect(
indexTrace.files.some((file) => file.endsWith('some-dir/file.txt'))
).toBe(true)
expect(
indexTrace.files.some((file) => file.includes('some-cms/index.js'))
).toBe(true)
expect(
indexTrace.files.some((file) => file === '../../../include-me/hello.txt')
).toBe(true)
expect(
indexTrace.files.some((file) => file === '../../../include-me/second.txt')
).toBe(true)
expect(indexTrace.files.some((file) => file.includes('exclude-me'))).toBe(
false
)
expect(
anotherTrace.files.some((file) =>
file.includes('nested-structure/constants/package.json')
)
).toBe(true)
expect(
anotherTrace.files.some((file) =>
file.includes('nested-structure/package.json')
)
).toBe(true)
expect(
anotherTrace.files.some((file) =>
file.includes('nested-structure/dist/constants.js')
)
).toBe(true)
expect(
imageTrace.files.some((file) => file.includes('public/another.jpg'))
).toBe(true)
expect(
imageTrace.files.some((file) => file.includes('public/test.jpg'))
).toBe(false)
})
})

View file

@ -7,83 +7,85 @@ import { join } from 'path'
const appDir = join(__dirname, '../')
describe('Build warnings', () => {
it('should not shown warning about minification withou any modification', async () => {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toContain('optimization has been disabled')
})
it('should shown warning about minification for minimize', async () => {
const nextConfig = new File(join(appDir, 'next.config.js'))
await waitFor(500)
nextConfig.replace('true', 'false')
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toContain('optimization has been disabled')
nextConfig.restore()
})
it('should shown warning about minification for minimizer', async () => {
const nextConfig = new File(join(appDir, 'next.config.js'))
await waitFor(500)
nextConfig.replace(
'config.optimization.minimize = true',
'config.optimization.minimizer = []'
)
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toContain('optimization has been disabled')
nextConfig.restore()
})
it('should not warn about missing cache in non-CI', async () => {
await remove(join(appDir, '.next'))
const { stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: {
CI: '',
CIRCLECI: '',
TRAVIS: '',
SYSTEM_TEAMFOUNDATIONCOLLECTIONURI: '',
GITHUB_ACTIONS: '',
GITHUB_EVENT_NAME: '',
},
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should not shown warning about minification without any modification', async () => {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toContain('optimization has been disabled')
})
expect(stdout).not.toContain('no-cache')
})
it('should not warn about missing cache on supported platforms', async () => {
await remove(join(appDir, '.next'))
it('should shown warning about minification for minimize', async () => {
const nextConfig = new File(join(appDir, 'next.config.js'))
const { stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: { CI: '1', NOW_BUILDER: '1' },
await waitFor(500)
nextConfig.replace('true', 'false')
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toContain('optimization has been disabled')
nextConfig.restore()
})
expect(stdout).not.toContain('no-cache')
})
it('should warn about missing cache in CI', async () => {
await remove(join(appDir, '.next'))
it('should shown warning about minification for minimizer', async () => {
const nextConfig = new File(join(appDir, 'next.config.js'))
let { stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: { CI: '1' },
await waitFor(500)
nextConfig.replace(
'config.optimization.minimize = true',
'config.optimization.minimizer = []'
)
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toContain('optimization has been disabled')
nextConfig.restore()
})
expect(stdout).toContain('no-cache')
// Do not warn after cache is present
;({ stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: { CI: '1' },
}))
expect(stdout).not.toContain('no-cache')
it('should not warn about missing cache in non-CI', async () => {
await remove(join(appDir, '.next'))
const { stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: {
CI: '',
CIRCLECI: '',
TRAVIS: '',
SYSTEM_TEAMFOUNDATIONCOLLECTIONURI: '',
GITHUB_ACTIONS: '',
GITHUB_EVENT_NAME: '',
},
})
expect(stdout).not.toContain('no-cache')
})
it('should not warn about missing cache on supported platforms', async () => {
await remove(join(appDir, '.next'))
const { stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: { CI: '1', NOW_BUILDER: '1' },
})
expect(stdout).not.toContain('no-cache')
})
it('should warn about missing cache in CI', async () => {
await remove(join(appDir, '.next'))
let { stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: { CI: '1' },
})
expect(stdout).toContain('no-cache')
// Do not warn after cache is present
;({ stdout } = await nextBuild(appDir, undefined, {
stdout: true,
env: { CI: '1' },
}))
expect(stdout).not.toContain('no-cache')
})
})
})

View file

@ -13,19 +13,22 @@ const appDir = join(__dirname, '../')
const errorRegex = /getStaticPaths was added without a getStaticProps in/
describe('Catches Missing getStaticProps', () => {
it('should catch it in dev mode', async () => {
const appPort = await findPort()
const app = await launchApp(appDir, appPort)
const html = await renderViaHTTP(appPort, '/hello')
await killApp(app)
describe('development mode', () => {
it('should catch it in dev mode', async () => {
const appPort = await findPort()
const app = await launchApp(appDir, appPort)
const html = await renderViaHTTP(appPort, '/hello')
await killApp(app)
expect(html).toMatch(errorRegex)
})
it('should catch it in server build mode', async () => {
const { stderr } = await nextBuild(appDir, [], {
stderr: true,
expect(html).toMatch(errorRegex)
})
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should catch it in server build mode', async () => {
const { stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(stderr).toMatch(errorRegex)
})
expect(stderr).toMatch(errorRegex)
})
})

View file

@ -80,200 +80,235 @@ const testExitSignal = async (
}
describe('CLI Usage', () => {
describe('start', () => {
test('should exit when SIGINT is signalled', async () => {
require('console').log('before build')
await fs.remove(join(dirBasic, '.next'))
await nextBuild(dirBasic, undefined, {
onStdout(msg) {
console.log(msg)
},
onStderr(msg) {
console.log(msg)
},
})
require('console').log('build finished')
const port = await findPort()
await testExitSignal(
'SIGINT',
['start', dirBasic, '-p', port],
/- Local:/
)
})
test('should exit when SIGTERM is signalled', async () => {
await fs.remove(join(dirBasic, '.next'))
await nextBuild(dirBasic, undefined, {
onStdout(msg) {
console.log(msg)
},
onStderr(msg) {
console.log(msg)
},
})
const port = await findPort()
await testExitSignal(
'SIGTERM',
['start', dirBasic, '-p', port],
/- Local:/
)
})
test('--help', async () => {
const help = await runNextCommand(['start', '--help'], {
stdout: true,
})
expect(help.stdout).toMatch(/Starts the application in production mode/)
})
test('-h', async () => {
const help = await runNextCommand(['start', '-h'], {
stdout: true,
})
expect(help.stdout).toMatch(/Starts the application in production mode/)
})
test('should format IPv6 addresses correctly', async () => {
await nextBuild(dirBasic)
const port = await findPort()
let stdout = ''
const app = await runNextCommandDev(
['start', dirBasic, '--hostname', '::', '--port', port],
undefined,
{
nextStart: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
describe('start', () => {
test('should exit when SIGINT is signalled', async () => {
require('console').log('before build')
await fs.remove(join(dirBasic, '.next'))
await nextBuild(dirBasic, undefined, {
onStdout(msg) {
stdout += msg
console.log(msg)
},
onStderr(msg) {
console.log(msg)
},
}
)
try {
await check(() => {
// Only display when hostname is provided
expect(stdout).toMatch(
new RegExp(`Network:\\s*http://\\[::\\]:${port}`)
)
expect(stdout).toMatch(new RegExp(`http://\\[::1\\]:${port}`))
})
} finally {
await killApp(app)
}
})
require('console').log('build finished')
test('should warn when unknown argument provided', async () => {
const { stderr } = await runNextCommand(['start', '--random'], {
stderr: true,
const port = await findPort()
await testExitSignal(
'SIGINT',
['start', dirBasic, '-p', port],
/- Local:/
)
})
expect(stderr).toEqual('Unknown or unexpected option: --random\n')
})
test('should not throw UnhandledPromiseRejectionWarning', async () => {
const { stderr } = await runNextCommand(['start', '--random'], {
stderr: true,
})
expect(stderr).not.toContain('UnhandledPromiseRejectionWarning')
})
test('duplicate sass deps', async () => {
const port = await findPort()
let stderr = ''
let instance = await launchApp(dirDuplicateSass, port, {
stderr: true,
onStderr(msg) {
stderr += msg
},
test('should exit when SIGTERM is signalled', async () => {
await fs.remove(join(dirBasic, '.next'))
await nextBuild(dirBasic, undefined, {
onStdout(msg) {
console.log(msg)
},
onStderr(msg) {
console.log(msg)
},
})
const port = await findPort()
await testExitSignal(
'SIGTERM',
['start', dirBasic, '-p', port],
/- Local:/
)
})
try {
await check(() => stderr, /both `sass` and `node-sass` installed/)
} finally {
await killApp(instance).catch(() => {})
}
})
test('invalid directory', async () => {
const output = await runNextCommand(['start', 'non-existent'], {
stderr: true,
test('--help', async () => {
const help = await runNextCommand(['start', '--help'], {
stdout: true,
})
expect(help.stdout).toMatch(/Starts the application in production mode/)
})
test('-h', async () => {
const help = await runNextCommand(['start', '-h'], {
stdout: true,
})
expect(help.stdout).toMatch(/Starts the application in production mode/)
})
test('should format IPv6 addresses correctly', async () => {
await nextBuild(dirBasic)
const port = await findPort()
let stdout = ''
const app = await runNextCommandDev(
['start', dirBasic, '--hostname', '::', '--port', port],
undefined,
{
nextStart: true,
onStdout(msg) {
stdout += msg
},
}
)
try {
await check(() => {
// Only display when hostname is provided
expect(stdout).toMatch(
new RegExp(`Network:\\s*http://\\[::\\]:${port}`)
)
expect(stdout).toMatch(new RegExp(`http://\\[::1\\]:${port}`))
})
} finally {
await killApp(app)
}
})
test('should warn when unknown argument provided', async () => {
const { stderr } = await runNextCommand(['start', '--random'], {
stderr: true,
})
expect(stderr).toEqual('Unknown or unexpected option: --random\n')
})
test('should not throw UnhandledPromiseRejectionWarning', async () => {
const { stderr } = await runNextCommand(['start', '--random'], {
stderr: true,
})
expect(stderr).not.toContain('UnhandledPromiseRejectionWarning')
})
test('duplicate sass deps', async () => {
const port = await findPort()
let stderr = ''
let instance = await launchApp(dirDuplicateSass, port, {
stderr: true,
onStderr(msg) {
stderr += msg
},
})
try {
await check(() => stderr, /both `sass` and `node-sass` installed/)
} finally {
await killApp(instance).catch(() => {})
}
})
test('invalid directory', async () => {
const output = await runNextCommand(['start', 'non-existent'], {
stderr: true,
})
expect(output.stderr).toContain(
'Invalid project directory provided, no such directory'
)
})
test('--keepAliveTimeout string arg', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout', 'string'],
{
stderr: true,
}
)
expect(stderr).toContain(
'Invalid --keepAliveTimeout, expected a non negative number but received "NaN"'
)
})
test('--keepAliveTimeout negative number', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout=-100'],
{
stderr: true,
}
)
expect(stderr).toContain(
'Invalid --keepAliveTimeout, expected a non negative number but received "-100"'
)
})
test('--keepAliveTimeout Infinity', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout', 'Infinity'],
{
stderr: true,
}
)
expect(stderr).toContain(
'Invalid --keepAliveTimeout, expected a non negative number but received "Infinity"'
)
})
test('--keepAliveTimeout happy path', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout', '100'],
{
stderr: true,
}
)
expect(stderr).not.toContain(
'Invalid keep alive timeout provided, expected a non negative number'
)
})
test('should not start on a port out of range', async () => {
const invalidPort = '300001'
const { stderr } = await runNextCommand(
['start', '--port', invalidPort],
{
stderr: true,
}
)
expect(stderr).toContain(`options.port should be >= 0 and < 65536.`)
})
test('should not start on a reserved port', async () => {
const reservedPort = '4045'
const { stderr } = await runNextCommand(
['start', '--port', reservedPort],
{
stderr: true,
}
)
expect(stderr).toContain(
`Bad port: "${reservedPort}" is reserved for npp`
)
})
expect(output.stderr).toContain(
'Invalid project directory provided, no such directory'
)
})
test('--keepAliveTimeout string arg', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout', 'string'],
{
describe('telemetry', () => {
test('--help', async () => {
const help = await runNextCommand(['telemetry', '--help'], {
stdout: true,
})
expect(help.stdout).toMatch(
/Allows you to control Next\.js' telemetry collection/
)
})
test('-h', async () => {
const help = await runNextCommand(['telemetry', '-h'], {
stdout: true,
})
expect(help.stdout).toMatch(
/Allows you to control Next\.js' telemetry collection/
)
})
test('should warn when unknown argument provided', async () => {
const { stderr } = await runNextCommand(['telemetry', '--random'], {
stderr: true,
}
)
expect(stderr).toContain(
'Invalid --keepAliveTimeout, expected a non negative number but received "NaN"'
)
})
test('--keepAliveTimeout negative number', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout=-100'],
{
})
expect(stderr).toEqual('Unknown or unexpected option: --random\n')
})
test('should not throw UnhandledPromiseRejectionWarning', async () => {
const { stderr } = await runNextCommand(['telemetry', '--random'], {
stderr: true,
}
)
expect(stderr).toContain(
'Invalid --keepAliveTimeout, expected a non negative number but received "-100"'
)
})
test('--keepAliveTimeout Infinity', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout', 'Infinity'],
{
stderr: true,
}
)
expect(stderr).toContain(
'Invalid --keepAliveTimeout, expected a non negative number but received "Infinity"'
)
})
test('--keepAliveTimeout happy path', async () => {
const { stderr } = await runNextCommand(
['start', '--keepAliveTimeout', '100'],
{
stderr: true,
}
)
expect(stderr).not.toContain(
'Invalid keep alive timeout provided, expected a non negative number'
)
})
test('should not start on a port out of range', async () => {
const invalidPort = '300001'
const { stderr } = await runNextCommand(
['start', '--port', invalidPort],
{
stderr: true,
}
)
expect(stderr).toContain(`options.port should be >= 0 and < 65536.`)
})
test('should not start on a reserved port', async () => {
const reservedPort = '4045'
const { stderr } = await runNextCommand(
['start', '--port', reservedPort],
{
stderr: true,
}
)
expect(stderr).toContain(
`Bad port: "${reservedPort}" is reserved for npp`
)
})
expect(stderr).not.toContain('UnhandledPromiseRejectionWarning')
})
})
})
@ -755,39 +790,6 @@ describe('CLI Usage', () => {
})
})
describe('telemetry', () => {
test('--help', async () => {
const help = await runNextCommand(['telemetry', '--help'], {
stdout: true,
})
expect(help.stdout).toMatch(
/Allows you to control Next\.js' telemetry collection/
)
})
test('-h', async () => {
const help = await runNextCommand(['telemetry', '-h'], {
stdout: true,
})
expect(help.stdout).toMatch(
/Allows you to control Next\.js' telemetry collection/
)
})
test('should warn when unknown argument provided', async () => {
const { stderr } = await runNextCommand(['telemetry', '--random'], {
stderr: true,
})
expect(stderr).toEqual('Unknown or unexpected option: --random\n')
})
test('should not throw UnhandledPromiseRejectionWarning', async () => {
const { stderr } = await runNextCommand(['telemetry', '--random'], {
stderr: true,
})
expect(stderr).not.toContain('UnhandledPromiseRejectionWarning')
})
})
describe('info', () => {
function matchInfoOutput(stdout, { nextConfigOutput = '.*' } = {}) {
expect(stdout).toMatch(

View file

@ -7,12 +7,13 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '..')
describe('Promise in next config', () => {
afterEach(() => fs.remove(join(appDir, 'next.config.js')))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
afterEach(() => fs.remove(join(appDir, 'next.config.js')))
it('should warn when a promise is returned on webpack', async () => {
fs.writeFile(
join(appDir, 'next.config.js'),
`
it('should warn when a promise is returned on webpack', async () => {
fs.writeFile(
join(appDir, 'next.config.js'),
`
module.exports = (phase, { isServer }) => {
return {
webpack: async (config) => {
@ -21,14 +22,15 @@ describe('Promise in next config', () => {
}
}
`
)
)
const { stderr, stdout } = await nextBuild(appDir, undefined, {
stderr: true,
stdout: true,
const { stderr, stdout } = await nextBuild(appDir, undefined, {
stderr: true,
stdout: true,
})
expect(stderr + stdout).toMatch(
/> Promise returned in next config\. https:\/\//
)
})
expect(stderr + stdout).toMatch(
/> Promise returned in next config\. https:\/\//
)
})
})

View file

@ -8,44 +8,46 @@ const nextConfigJS = join(appDir, 'next.config.js')
const nextConfigMJS = join(appDir, 'next.config.mjs')
describe('Invalid config syntax', () => {
it('should error when next.config.js contains syntax error', async () => {
await fs.writeFile(
nextConfigJS,
`
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should error when next.config.js contains syntax error', async () => {
await fs.writeFile(
nextConfigJS,
`
module.exports = {
reactStrictMode: true,,
}
`
)
const { stderr } = await nextBuild(appDir, undefined, {
stderr: true,
)
const { stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
await fs.remove(nextConfigJS)
expect(stderr).toContain(
'Failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error'
)
expect(stderr).toContain('SyntaxError')
})
await fs.remove(nextConfigJS)
expect(stderr).toContain(
'Failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error'
)
expect(stderr).toContain('SyntaxError')
})
it('should error when next.config.mjs contains syntax error', async () => {
await fs.writeFile(
nextConfigMJS,
`
it('should error when next.config.mjs contains syntax error', async () => {
await fs.writeFile(
nextConfigMJS,
`
const config = {
reactStrictMode: true,,
}
export default config
`
)
const { stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
await fs.remove(nextConfigMJS)
)
const { stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
await fs.remove(nextConfigMJS)
expect(stderr).toContain(
'Failed to load next.config.mjs, see more info here https://nextjs.org/docs/messages/next-config-error'
)
expect(stderr).toContain('SyntaxError')
expect(stderr).toContain(
'Failed to load next.config.mjs, see more info here https://nextjs.org/docs/messages/next-config-error'
)
expect(stderr).toContain('SyntaxError')
})
})
})

View file

@ -5,10 +5,11 @@ import fs from 'fs-extra'
const nextConfigPath = path.join(__dirname, '../next.config.js')
describe('next.config.js validation', () => {
it.each([
{
name: 'invalid config types',
configContent: `
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it.each([
{
name: 'invalid config types',
configContent: `
module.exports = {
swcMinify: 'hello',
rewrites: true,
@ -17,15 +18,15 @@ describe('next.config.js validation', () => {
}
}
`,
outputs: [
'The value at .images.loader must be one of',
'The value at .rewrites must be a function that returns a Promise',
'The value at .swcMinify must be a boolean but it was a string',
],
},
{
name: 'unexpected config fields',
configContent: `
outputs: [
'The value at .images.loader must be one of',
'The value at .rewrites must be a function that returns a Promise',
'The value at .swcMinify must be a boolean but it was a string',
],
},
{
name: 'unexpected config fields',
configContent: `
module.exports = {
nonExistent: true,
experimental: {
@ -33,24 +34,25 @@ describe('next.config.js validation', () => {
}
}
`,
outputs: [
'The root value has an unexpected property, nonExistent,',
'The value at .experimental has an unexpected property, anotherNonExistent',
],
},
])(
'it should validate correctly for $name',
async ({ outputs, configContent }) => {
await fs.writeFile(nextConfigPath, configContent)
const result = await nextBuild(path.join(__dirname, '../'), undefined, {
stderr: true,
stdout: true,
})
await fs.remove(nextConfigPath)
outputs: [
'The root value has an unexpected property, nonExistent,',
'The value at .experimental has an unexpected property, anotherNonExistent',
],
},
])(
'it should validate correctly for $name',
async ({ outputs, configContent }) => {
await fs.writeFile(nextConfigPath, configContent)
const result = await nextBuild(path.join(__dirname, '../'), undefined, {
stderr: true,
stdout: true,
})
await fs.remove(nextConfigPath)
for (const output of outputs) {
expect(result.stdout + result.stderr).toContain(output)
for (const output of outputs) {
expect(result.stdout + result.stderr).toContain(output)
}
}
}
)
)
})
})

View file

@ -12,28 +12,34 @@ import {
const appDir = path.join(__dirname, '..')
describe('Errors on conflict between public file and page file', () => {
it('Throws error during development', async () => {
const appPort = await findPort()
const app = await launchApp(appDir, appPort)
const conflicts = ['/another/conflict', '/hello']
describe('development mode', () => {
it('Throws error during development', async () => {
const appPort = await findPort()
const app = await launchApp(appDir, appPort)
const conflicts = ['/another/conflict', '/hello']
for (const conflict of conflicts) {
const html = await renderViaHTTP(appPort, conflict)
expect(html).toMatch(
/A conflicting public file and page file was found for path/
)
}
await killApp(app)
for (const conflict of conflicts) {
const html = await renderViaHTTP(appPort, conflict)
expect(html).toMatch(
/A conflicting public file and page file was found for path/
)
}
await killApp(app)
})
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('Throws error during build', async () => {
const conflicts = ['/another/conflict', '/another', '/hello']
const results = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
const output = results.stdout + results.stderr
expect(output).toMatch(/Conflicting public and page files were found/)
it('Throws error during build', async () => {
const conflicts = ['/another/conflict', '/another', '/hello']
const results = await nextBuild(appDir, [], { stdout: true, stderr: true })
const output = results.stdout + results.stderr
expect(output).toMatch(/Conflicting public and page files were found/)
for (const conflict of conflicts) {
expect(output.indexOf(conflict) > 0).toBe(true)
}
for (const conflict of conflicts) {
expect(output.indexOf(conflict) > 0).toBe(true)
}
})
})
})

View file

@ -8,13 +8,14 @@ const appDir = join(__dirname, '../')
const pagesDir = join(appDir, 'pages')
describe('Conflicting SSG paths', () => {
afterEach(() => fs.remove(pagesDir))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
afterEach(() => fs.remove(pagesDir))
it('should show proper error when two dynamic SSG routes have conflicting paths', async () => {
await fs.ensureDir(join(pagesDir, 'blog'))
await fs.writeFile(
join(pagesDir, 'blog/[slug].js'),
`
it('should show proper error when two dynamic SSG routes have conflicting paths', async () => {
await fs.ensureDir(join(pagesDir, 'blog'))
await fs.writeFile(
join(pagesDir, 'blog/[slug].js'),
`
export const getStaticProps = () => {
return {
props: {}
@ -35,11 +36,11 @@ describe('Conflicting SSG paths', () => {
return '/blog/[slug]'
}
`
)
)
await fs.writeFile(
join(pagesDir, '[...catchAll].js'),
`
await fs.writeFile(
join(pagesDir, '[...catchAll].js'),
`
export const getStaticProps = () => {
return {
props: {}
@ -60,39 +61,39 @@ describe('Conflicting SSG paths', () => {
return '/[catchAll]'
}
`
)
)
const result = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
const result = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = result.stdout + result.stderr
expect(output).toContain(
'Conflicting paths returned from getStaticPaths, paths must be unique per page'
)
expect(output).toContain(
'https://nextjs.org/docs/messages/conflicting-ssg-paths'
)
expect(output).toContain(
`path: "/blog/conflicting" from page: "/[...catchAll]"`
)
expect(output).toContain(`conflicts with path: "/blog/conflicting"`)
})
const output = result.stdout + result.stderr
expect(output).toContain(
'Conflicting paths returned from getStaticPaths, paths must be unique per page'
)
expect(output).toContain(
'https://nextjs.org/docs/messages/conflicting-ssg-paths'
)
expect(output).toContain(
`path: "/blog/conflicting" from page: "/[...catchAll]"`
)
expect(output).toContain(`conflicts with path: "/blog/conflicting"`)
})
it('should show proper error when a dynamic SSG route conflicts with normal route', async () => {
await fs.ensureDir(join(pagesDir, 'hello'))
await fs.writeFile(
join(pagesDir, 'hello/world.js'),
`
it('should show proper error when a dynamic SSG route conflicts with normal route', async () => {
await fs.ensureDir(join(pagesDir, 'hello'))
await fs.writeFile(
join(pagesDir, 'hello/world.js'),
`
export default function Page() {
return '/hello/world'
}
`
)
)
await fs.writeFile(
join(pagesDir, '[...catchAll].js'),
`
await fs.writeFile(
join(pagesDir, '[...catchAll].js'),
`
export const getStaticProps = () => {
return {
props: {}
@ -113,40 +114,40 @@ describe('Conflicting SSG paths', () => {
return '/[catchAll]'
}
`
)
)
const result = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
const result = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = result.stdout + result.stderr
expect(output).toContain(
'Conflicting paths returned from getStaticPaths, paths must be unique per page'
)
expect(output).toContain(
'https://nextjs.org/docs/messages/conflicting-ssg-paths'
)
expect(output).toContain(
`path: "/hellO/world" from page: "/[...catchAll]" conflicts with path: "/hello/world"`
)
})
const output = result.stdout + result.stderr
expect(output).toContain(
'Conflicting paths returned from getStaticPaths, paths must be unique per page'
)
expect(output).toContain(
'https://nextjs.org/docs/messages/conflicting-ssg-paths'
)
expect(output).toContain(
`path: "/hellO/world" from page: "/[...catchAll]" conflicts with path: "/hello/world"`
)
})
it('should show proper error when a dynamic SSG route conflicts with SSR route', async () => {
await fs.ensureDir(join(pagesDir, 'hello'))
await fs.writeFile(
join(pagesDir, 'hello/world.js'),
`
it('should show proper error when a dynamic SSG route conflicts with SSR route', async () => {
await fs.ensureDir(join(pagesDir, 'hello'))
await fs.writeFile(
join(pagesDir, 'hello/world.js'),
`
export const getServerSideProps = () => ({ props: {} })
export default function Page() {
return '/hello/world'
}
`
)
)
await fs.writeFile(
join(pagesDir, '[...catchAll].js'),
`
await fs.writeFile(
join(pagesDir, '[...catchAll].js'),
`
export const getStaticProps = () => {
return {
props: {}
@ -167,21 +168,22 @@ describe('Conflicting SSG paths', () => {
return '/[catchAll]'
}
`
)
)
const result = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
const result = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = result.stdout + result.stderr
expect(output).toContain(
'Conflicting paths returned from getStaticPaths, paths must be unique per page'
)
expect(output).toContain(
'https://nextjs.org/docs/messages/conflicting-ssg-paths'
)
expect(output).toContain(
`path: "/hellO/world" from page: "/[...catchAll]" conflicts with path: "/hello/world"`
)
})
const output = result.stdout + result.stderr
expect(output).toContain(
'Conflicting paths returned from getStaticPaths, paths must be unique per page'
)
expect(output).toContain(
'https://nextjs.org/docs/messages/conflicting-ssg-paths'
)
expect(output).toContain(
`path: "/hellO/world" from page: "/[...catchAll]" conflicts with path: "/hello/world"`
)
})
})

View file

@ -57,23 +57,25 @@ function runTests() {
}
describe('CSS optimization for SSR apps', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`module.exports = { experimental: {optimizeCss: true} }`,
'utf8'
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`module.exports = { experimental: {optimizeCss: true} }`,
'utf8'
)
if (fs.pathExistsSync(join(appDir, '.next'))) {
await fs.remove(join(appDir, '.next'))
}
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
if (fs.pathExistsSync(join(appDir, '.next'))) {
await fs.remove(join(appDir, '.next'))
}
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
await fs.remove(nextConfig)
})
runTests()
})
afterAll(async () => {
await killApp(app)
await fs.remove(nextConfig)
})
runTests()
})

View file

@ -28,13 +28,15 @@ function runTests() {
}
describe('css-minify', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
runTests()
})
afterAll(async () => {
await killApp(app)
})
runTests()
})

View file

@ -243,44 +243,52 @@ describe('Can hot reload CSS Module without losing state', () => {
})
describe.skip('Invalid CSS Module Usage in node_modules', () => {
const appDir = join(fixturesDir, 'invalid-module')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
const appDir = join(fixturesDir, 'invalid-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.module.css')
expect(stderr).toMatch(
/CSS Modules.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(
/Location:.*node_modules[\\/]example[\\/]index\.mjs/
)
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.module.css')
expect(stderr).toMatch(
/CSS Modules.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
})
})
describe.skip('Invalid Global CSS Module Usage in node_modules', () => {
const appDir = join(fixturesDir, 'invalid-global-module')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
const appDir = join(fixturesDir, 'invalid-global-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.css')
expect(stderr).toMatch(
/Global CSS.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(
/Location:.*node_modules[\\/]example[\\/]index\.mjs/
)
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.css')
expect(stderr).toMatch(
/Global CSS.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
})
})

View file

@ -38,35 +38,37 @@ describe('Ordering with styled-jsx (dev)', () => {
})
describe('Ordering with styled-jsx (prod)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
})

View file

@ -15,52 +15,53 @@ import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('CSS Support', () => {
describe('CSS Compilation and Prefixing', () => {
const appDir = join(fixturesDir, 'compilation-and-prefixing')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
describe('CSS Compilation and Prefixing', () => {
const appDir = join(fixturesDir, 'compilation-and-prefixing')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've compiled and prefixed`, async () => {
const cssFolder = join(appDir, '.next/static/css')
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
it(`should've compiled and prefixed`, async () => {
const cssFolder = join(appDir, '.next/static/css')
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`"@media (min-width:480px) and (max-width:767px){::placeholder{color:green}}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}.transform-parsing{transform:translate3d(0,0)}.css-grid-shorthand{grid-column:span 2}.g-docs-sidenav .filter::-webkit-input-placeholder{opacity:80%}"`
)
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
// Contains a source map
expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
})
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`"@media (min-width:480px) and (max-width:767px){::placeholder{color:green}}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}.transform-parsing{transform:translate3d(0,0)}.css-grid-shorthand{grid-column:span 2}.g-docs-sidenav .filter::-webkit-input-placeholder{opacity:80%}"`
)
it(`should've emitted a source map`, async () => {
const cssFolder = join(appDir, '.next/static/css')
// Contains a source map
expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
})
const files = await readdir(cssFolder)
const cssMapFiles = files.filter((f) => /\.css\.map$/.test(f))
it(`should've emitted a source map`, async () => {
const cssFolder = join(appDir, '.next/static/css')
expect(cssMapFiles.length).toBe(1)
const cssMapContent = (
await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
).trim()
const files = await readdir(cssFolder)
const cssMapFiles = files.filter((f) => /\.css\.map$/.test(f))
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
expect(cssMapFiles.length).toBe(1)
const cssMapContent = (
await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
).trim()
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
Object {
"mappings": "AAAA,+CACE,cACE,WACF,CACF,CAEA,cACE,2CACF,CAEA,mBACE,0BACF,CAEA,oBACE,kBACF,CAEA,mDACE,WACF",
"sourcesContent": Array [
@ -90,199 +91,206 @@ describe('CSS Support', () => {
"version": 3,
}
`)
})
})
describe('React Lifecyce Order (production)', () => {
const appDir = join(fixturesDir, 'transition-react')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
})
let appPort
let app
let code
let stdout
beforeAll(async () => {
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
describe('React Lifecyce Order (production)', () => {
const appDir = join(fixturesDir, 'transition-react')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
let appPort
let app
let code
let stdout
beforeAll(async () => {
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color on mount after navigation', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
const text = await browser.waitForElementByCss('#red-title').text()
expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
} finally {
if (browser) {
await browser.close()
it('should have the correct color on mount after navigation', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
const text = await browser.waitForElementByCss('#red-title').text()
expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
} finally {
if (browser) {
await browser.close()
}
}
}
})
})
describe('Has CSS in computed styles in Production', () => {
const appDir = join(fixturesDir, 'multi-page')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have CSS for page', async () => {
const browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it(`should've preloaded the CSS file and injected it in <head>`, async () => {
const content = await renderViaHTTP(appPort, '/page2')
const $ = cheerio.load(content)
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
/* ensure CSS preloaded first */
const allPreloads = [].slice.call($('link[rel="preload"]'))
const styleIndexes = allPreloads.flatMap((p, i) =>
p.attribs.as === 'style' ? i : []
)
expect(styleIndexes).toEqual([0])
})
})
describe('Good CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
describe('Has CSS in computed styles in Production', () => {
const appDir = join(fixturesDir, 'multi-page')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(/nprogress/)
})
})
describe('Good Nested CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have CSS for page', async () => {
const browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it(`should've preloaded the CSS file and injected it in <head>`, async () => {
const content = await renderViaHTTP(appPort, '/page2')
const $ = cheerio.load(content)
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(
/^\/_next\/static\/css\/.*\.css$/
)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
/* ensure CSS preloaded first */
const allPreloads = [].slice.call($('link[rel="preload"]'))
const styleIndexes = allPreloads.flatMap((p, i) =>
p.attribs.as === 'style' ? i : []
)
expect(styleIndexes).toEqual([0])
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
describe('Good CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`)
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/nprogress/
)
})
})
describe('Good Nested CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`)
})
})
})
})
// https://github.com/vercel/next.js/issues/15468
describe('CSS Property Ordering', () => {
const appDir = join(fixturesDir, 'next-issue-15468')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
const appDir = join(fixturesDir, 'next-issue-15468')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the border width (property ordering)', async () => {
const browser = await webdriver(appPort, '/')
it('should have the border width (property ordering)', async () => {
const browser = await webdriver(appPort, '/')
const width1 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test1')).borderWidth`
)
expect(width1).toMatchInlineSnapshot(`"0px"`)
const width1 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test1')).borderWidth`
)
expect(width1).toMatchInlineSnapshot(`"0px"`)
const width2 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test2')).borderWidth`
)
expect(width2).toMatchInlineSnapshot(`"5px"`)
const width2 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test2')).borderWidth`
)
expect(width2).toMatchInlineSnapshot(`"5px"`)
})
})
})

View file

@ -15,19 +15,21 @@ import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('CSS Support', () => {
describe('CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-bad')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
describe('CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-bad')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail the build', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
it('should fail the build', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(0)
expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
expect(stderr).not.toMatch(/Build error occurred/)
expect(code).toBe(0)
expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
expect(stderr).not.toMatch(/Build error occurred/)
})
})
})

View file

@ -11,24 +11,26 @@ let app
// TODO: re-enable with React 18
describe.skip('Custom error page exception', () => {
beforeAll(async () => {
await nextBuild(appDir, undefined, {
nodeArgs,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir, undefined, {
nodeArgs,
})
appPort = await findPort()
app = await nextStart(appDir, appPort, {
nodeArgs,
})
})
appPort = await findPort()
app = await nextStart(appDir, appPort, {
nodeArgs,
})
})
afterAll(() => killApp(app))
it('should handle errors from _error render', async () => {
const navSel = '#nav'
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss(navSel).elementByCss(navSel).click()
afterAll(() => killApp(app))
it('should handle errors from _error render', async () => {
const navSel = '#nav'
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss(navSel).elementByCss(navSel).click()
await check(
() => browser.eval('document.documentElement.innerHTML'),
/Application error: a client-side exception has occurred/
)
await check(
() => browser.eval('document.documentElement.innerHTML'),
/Application error: a client-side exception has occurred/
)
})
})
})

View file

@ -143,16 +143,23 @@ describe.skip.each([
})
describe('with generateEtags enabled', () => {
beforeAll(async () => {
await nextBuild(appDir)
await startServer({ GENERATE_ETAGS: 'true', NODE_ENV: 'production' })
})
afterAll(() => killApp(server))
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await nextBuild(appDir)
await startServer({ GENERATE_ETAGS: 'true', NODE_ENV: 'production' })
})
afterAll(() => killApp(server))
it('response includes etag header', async () => {
const response = await fetchViaHTTP(nextUrl, '/', undefined, { agent })
expect(response.headers.get('etag')).toBeTruthy()
})
it('response includes etag header', async () => {
const response = await fetchViaHTTP(nextUrl, '/', undefined, {
agent,
})
expect(response.headers.get('etag')).toBeTruthy()
})
}
)
})
describe('with generateEtags disabled', () => {
@ -222,28 +229,32 @@ describe.skip.each([
expect(html).toContain('made it to dashboard')
expect(stderr).toContain('Cannot render page with path "dashboard"')
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('should warn in production mode', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
it('should warn in production mode', async () => {
const { code } = await nextBuild(appDir)
expect(code).toBe(0)
let stderr = ''
let stderr = ''
await startServer(
{ NODE_ENV: 'production' },
{
onStderr(msg) {
stderr += msg || ''
},
}
)
await startServer(
{ NODE_ENV: 'production' },
{
onStderr(msg) {
stderr += msg || ''
},
}
)
const html = await renderViaHTTP(nextUrl, '/no-slash', undefined, {
agent,
})
expect(html).toContain('made it to dashboard')
expect(stderr).toContain('Cannot render page with path "dashboard"')
})
const html = await renderViaHTTP(nextUrl, '/no-slash', undefined, {
agent,
})
expect(html).toContain('made it to dashboard')
expect(stderr).toContain('Cannot render page with path "dashboard"')
})
}
)
})
describe('compression handling', function () {

View file

@ -132,12 +132,9 @@ describe('GS(S)P Page Errors', () => {
describe('dev mode', () => {
runTests(true)
})
describe('build mode', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
runTests()
})
describe('start mode', () => {
it('Error stack printed to stderr', async () => {
try {
await fs.writeFile(

View file

@ -15,28 +15,30 @@ let appPort
let app
describe('De-dedupes scripts in _document', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir)
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir)
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('Does not have duplicate script references', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
let foundDuplicate = false
const srcs = new Set()
it('Does not have duplicate script references', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
let foundDuplicate = false
const srcs = new Set()
for (const script of $('script').toArray()) {
const { src } = script.attribs
if (!src || !src.startsWith('/_next/static')) continue
if (srcs.has(src)) {
console.error(`Found duplicate script ${src}`)
foundDuplicate = true
for (const script of $('script').toArray()) {
const { src } = script.attribs
if (!src || !src.startsWith('/_next/static')) continue
if (srcs.has(src)) {
console.error(`Found duplicate script ${src}`)
foundDuplicate = true
}
srcs.add(src)
}
srcs.add(src)
}
expect(foundDuplicate).toBe(false)
expect(foundDuplicate).toBe(false)
})
})
})

View file

@ -19,28 +19,33 @@ let app
describe('distDir', () => {
describe('With basic usage', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
await fs.remove(join(appDir, 'dist'))
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
await fs.remove(join(appDir, 'dist'))
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should render the page', async () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/Hello World/)
})
it('should render the page', async () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/Hello World/)
})
it('should build the app within the given `dist` directory', async () => {
expect(
await fs.exists(join(__dirname, `/../dist/${BUILD_ID_FILE}`))
).toBeTruthy()
})
it('should not build the app within the default `.next` directory', async () => {
expect(await fs.exists(join(__dirname, '/../.next'))).toBeFalsy()
})
it('should build the app within the given `dist` directory', async () => {
expect(
await fs.exists(join(__dirname, `/../dist/${BUILD_ID_FILE}`))
).toBeTruthy()
})
it('should not build the app within the default `.next` directory', async () => {
expect(await fs.exists(join(__dirname, '/../.next'))).toBeFalsy()
})
}
)
})
describe('dev mode', () => {
@ -66,27 +71,28 @@ describe('distDir', () => {
expect(await fs.exists(join(__dirname, '/../.next'))).toBeFalsy()
})
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should throw error with invalid distDir', async () => {
const origNextConfig = await fs.readFile(nextConfig, 'utf8')
await fs.writeFile(nextConfig, `module.exports = { distDir: '' }`)
const { stderr } = await nextBuild(appDir, [], { stderr: true })
await fs.writeFile(nextConfig, origNextConfig)
it('should throw error with invalid distDir', async () => {
const origNextConfig = await fs.readFile(nextConfig, 'utf8')
await fs.writeFile(nextConfig, `module.exports = { distDir: '' }`)
const { stderr } = await nextBuild(appDir, [], { stderr: true })
await fs.writeFile(nextConfig, origNextConfig)
expect(stderr).toContain(
'Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined'
)
})
expect(stderr).toContain(
'Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined'
)
})
it('should handle null/undefined distDir', async () => {
const origNextConfig = await fs.readFile(nextConfig, 'utf8')
await fs.writeFile(
nextConfig,
`module.exports = { distDir: null, eslint: { ignoreDuringBuilds: true } }`
)
const { stderr } = await nextBuild(appDir, [], { stderr: true })
await fs.writeFile(nextConfig, origNextConfig)
it('should handle null/undefined distDir', async () => {
const origNextConfig = await fs.readFile(nextConfig, 'utf8')
await fs.writeFile(
nextConfig,
`module.exports = { distDir: null, eslint: { ignoreDuringBuilds: true } }`
)
const { stderr } = await nextBuild(appDir, [], { stderr: true })
await fs.writeFile(nextConfig, origNextConfig)
expect(stderr.length).toBe(0)
expect(stderr.length).toBe(0)
})
})
})

View file

@ -82,30 +82,44 @@ describe('Edge runtime code with imports', () => {
beforeEach(() => init(importStatement))
it('throws unsupported module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectUnsupportedModuleDevError(
moduleName,
importStatement,
await res.text()
describe('development mode', () => {
it('throws unsupported module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(
context.appDir,
context.appPort,
appOption
)
return 'success'
}, 'success')
})
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectUnsupportedModuleDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
}
)
})
describe.each([
@ -154,38 +168,44 @@ describe('Edge runtime code with imports', () => {
`)
})
it('throws unsupported module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectUnsupportedModuleDevError(
moduleName,
importStatement,
await res.text()
describe('development mode', () => {
it('throws unsupported module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(
context.appDir,
context.appPort,
appOption
)
return 'success'
}, 'success')
})
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectUnsupportedModuleDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
}
)
}
)
@ -241,16 +261,20 @@ describe('Edge runtime code with imports', () => {
return 'success'
}, 'success')
})
it('does not build and reports', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('does not build and reports', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
}
)
})
describe.each([
@ -299,18 +323,26 @@ describe('Edge runtime code with imports', () => {
expect(res.headers.get('x-from-runtime')).toBeDefined()
expectNoError(moduleName)
})
it('does not throw in production at runtime', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).not.toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expect(res.headers.get('x-from-runtime')).toBeDefined()
expectNoError(moduleName)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('does not throw in production at runtime', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).not.toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expect(res.headers.get('x-from-runtime')).toBeDefined()
expectNoError(moduleName)
})
}
)
})
describe.each([
@ -360,14 +392,22 @@ describe('Edge runtime code with imports', () => {
expect(res.headers.get('x-from-runtime')).toBe('false')
expectNoError(moduleName)
})
it('does not throw in production at runtime', async () => {
await nextBuild(context.appDir, undefined, { stderr: true })
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expect(res.headers.get('x-from-runtime')).toBe('false')
expectNoError(moduleName)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('does not throw in production at runtime', async () => {
await nextBuild(context.appDir, undefined, { stderr: true })
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expect(res.headers.get('x-from-runtime')).toBe('false')
expectNoError(moduleName)
})
}
)
})
})

View file

@ -95,17 +95,25 @@ describe('Edge runtime code with imports', () => {
return 'success'
}, 'success')
})
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
}
)
})
describe.each([
@ -157,16 +165,20 @@ describe('Edge runtime code with imports', () => {
return 'success'
}, 'success')
})
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
}
)
})
describe.each([
@ -221,17 +233,21 @@ describe('Edge runtime code with imports', () => {
return 'success'
}, 'success')
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
expectModuleNotFoundProdError(moduleName, stderr)
})
}
)
})
describe.each([
@ -279,16 +295,24 @@ describe('Edge runtime code with imports', () => {
expect(res.status).toBe(200)
expectNoError(moduleName)
})
it('does not throw in production at runtime', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expectNoError(moduleName)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it('does not throw in production at runtime', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expectNoError(moduleName)
})
}
)
})
})

View file

@ -72,17 +72,25 @@ describe('Edge runtime code with imports', () => {
)
expect(res.status).toBe(500)
})
it(`${title} build test Response`, async () => {
await nextBuild(context.appDir, undefined, {
stderr: true,
})
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(context.logs.stderr).toContain(
'Expected an instance of Response to be returned'
)
expect(res.status).toBe(500)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
it(`${title} build test Response`, async () => {
await nextBuild(context.appDir, undefined, {
stderr: true,
})
context.app = await nextStart(
context.appDir,
context.appPort,
appOption
)
const res = await fetchViaHTTP(context.appPort, url)
expect(context.logs.stderr).toContain(
'Expected an instance of Response to be returned'
)
expect(res.status).toBe(500)
})
}
)
})
})

View file

@ -16,23 +16,25 @@ let app
let port
describe('Handles an Error in _error', () => {
beforeAll(async () => {
await nextBuild(appDir)
port = await findPort()
app = await nextStart(appDir, port)
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
port = await findPort()
app = await nextStart(appDir, port)
})
afterAll(() => killApp(app))
it('Handles error during SSR', async () => {
const html = await renderViaHTTP(port, '/some-404-page')
expect(html).toMatch(/Internal Server Error/i)
})
it('Handles error during SSR', async () => {
const html = await renderViaHTTP(port, '/some-404-page')
expect(html).toMatch(/Internal Server Error/i)
})
it('Handles error during client transition', async () => {
const browser = await webdriver(port, '/')
await browser.waitForElementByCss('a').click()
await waitFor(1000)
const html = await browser.eval('document.body.innerHTML')
expect(html).toMatch(/Internal Server Error/i)
it('Handles error during client transition', async () => {
const browser = await webdriver(port, '/')
await browser.waitForElementByCss('a').click()
await waitFor(1000)
const html = await browser.eval('document.body.innerHTML')
expect(html).toMatch(/Internal Server Error/i)
})
})
})

View file

@ -16,48 +16,50 @@ const appDir = join(__dirname, '..')
let app
describe('Failing to load _error', () => {
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
afterAll(() => killApp(app))
it('handles failing to load _error correctly', async () => {
await nextBuild(appDir)
const appPort = await findPort()
app = await nextStart(appDir, appPort)
it('handles failing to load _error correctly', async () => {
await nextBuild(appDir)
const appPort = await findPort()
app = await nextStart(appDir, appPort)
const browser = await webdriver(appPort, '/')
await browser.eval(`window.beforeNavigate = true`)
const browser = await webdriver(appPort, '/')
await browser.eval(`window.beforeNavigate = true`)
await browser.elementByCss('#to-broken').moveTo()
await check(
async () => {
const scripts = await browser.elementsByCss('script')
let found = false
await browser.elementByCss('#to-broken').moveTo()
await check(
async () => {
const scripts = await browser.elementsByCss('script')
let found = false
for (const script of scripts) {
const src = await script.getAttribute('src')
if (src.includes('broken-')) {
found = true
break
for (const script of scripts) {
const src = await script.getAttribute('src')
if (src.includes('broken-')) {
found = true
break
}
}
}
return found
},
{
test(content) {
return content === true
return found
},
}
)
{
test(content) {
return content === true
},
}
)
const errorPageFilePath = getPageFileFromBuildManifest(appDir, '/_error')
// remove _error client bundle so that it can't be loaded
await fs.remove(join(appDir, '.next', errorPageFilePath))
const errorPageFilePath = getPageFileFromBuildManifest(appDir, '/_error')
// remove _error client bundle so that it can't be loaded
await fs.remove(join(appDir, '.next', errorPageFilePath))
await browser.elementByCss('#to-broken').click()
await browser.elementByCss('#to-broken').click()
await check(async () => {
return !(await browser.eval('window.beforeNavigate'))
? 'reloaded'
: 'fail'
}, /reloaded/)
await check(async () => {
return !(await browser.eval('window.beforeNavigate'))
? 'reloaded'
: 'fail'
}, /reloaded/)
})
})
})

View file

@ -8,29 +8,34 @@ const appDir = path.join(__dirname, '..')
const nextConfig = path.join(appDir, 'next.config.js')
describe('Errors on output to public', () => {
it('Throws error when `distDir` is set to public', async () => {
await fs.writeFile(nextConfig, `module.exports = { distDir: 'public' }`)
const results = await nextBuild(appDir, [], { stdout: true, stderr: true })
expect(results.stdout + results.stderr).toMatch(
/The 'public' directory is reserved in Next\.js and can not be set as/
)
await fs.remove(nextConfig)
})
it('Throws error when export out dir is public', async () => {
await fs.remove(nextConfig)
await nextBuild(appDir)
const outdir = path.join(appDir, 'public')
const results = await nextExport(
appDir,
{ outdir },
{
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('Throws error when `distDir` is set to public', async () => {
await fs.writeFile(nextConfig, `module.exports = { distDir: 'public' }`)
const results = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
}
)
expect(results.stdout + results.stderr).toMatch(
/The 'public' directory is reserved in Next\.js and can not be used as/
)
})
expect(results.stdout + results.stderr).toMatch(
/The 'public' directory is reserved in Next\.js and can not be set as/
)
await fs.remove(nextConfig)
})
it('Throws error when export out dir is public', async () => {
await fs.remove(nextConfig)
await nextBuild(appDir)
const outdir = path.join(appDir, 'public')
const results = await nextExport(
appDir,
{ outdir },
{
stdout: true,
stderr: true,
}
)
expect(results.stdout + results.stderr).toMatch(
/The 'public' directory is reserved in Next\.js and can not be used as/
)
})
})
})

View file

@ -8,20 +8,22 @@ const appDir = path.join(__dirname, '..')
const nextConfig = path.join(appDir, 'next.config.js')
describe('Errors on output to static', () => {
it('Throws error when export out dir is static', async () => {
await fs.remove(nextConfig)
await nextBuild(appDir)
const outdir = path.join(appDir, 'static')
const results = await nextExport(
appDir,
{ outdir },
{
stdout: true,
stderr: true,
}
)
expect(results.stdout + results.stderr).toMatch(
/The 'static' directory is reserved in Next\.js and can not be used as/
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('Throws error when export out dir is static', async () => {
await fs.remove(nextConfig)
await nextBuild(appDir)
const outdir = path.join(appDir, 'static')
const results = await nextExport(
appDir,
{ outdir },
{
stdout: true,
stderr: true,
}
)
expect(results.stdout + results.stderr).toMatch(
/The 'static' directory is reserved in Next\.js and can not be used as/
)
})
})
})

View file

@ -40,170 +40,182 @@ const mjsCjsLinting = join(__dirname, '../mjs-cjs-linting')
const dirTypescript = join(__dirname, '../with-typescript')
describe('Next Build', () => {
test('first time setup', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
test('first time setup', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '')
const { stdout, stderr } = await nextBuild(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'No ESLint configuration detected. Run next lint to begin setup'
)
})
test('shows warnings and errors', async () => {
const { stdout, stderr } = await nextBuild(dirCustomConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
test('ignore during builds', async () => {
const { stdout, stderr } = await nextBuild(dirIgnoreDuringBuilds, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Failed to compile')
expect(output).not.toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
test('base directories are linted by default during builds', async () => {
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain('Warning: Synchronous scripts should not be used')
expect(output).toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in app, pages, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).toContain('app/layout.js')
})
test('custom directories', async () => {
const { stdout, stderr } = await nextBuild(dirCustomDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain('Warning: Synchronous scripts should not be used.')
})
test('invalid older eslint version', async () => {
const { stdout, stderr } = await nextBuild(
dirInvalidOlderEslintVersion,
[],
{
const { stdout, stderr } = await nextBuild(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
}
)
})
const output = stdout + stderr
const output = stdout + stderr
expect(output).toContain(
'Your project has an older version of ESLint installed'
)
})
test('empty directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEmptyDirectory, [], {
stdout: true,
stderr: true,
expect(output).toContain(
'No ESLint configuration detected. Run next lint to begin setup'
)
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('NoFilesFoundError')
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain('Compiled successfully')
})
test('shows warnings and errors', async () => {
const { stdout, stderr } = await nextBuild(dirCustomConfig, [], {
stdout: true,
stderr: true,
})
test('eslint ignored directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEslintIgnore, [], {
stdout: true,
stderr: true,
const output = stdout + stderr
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('AllFilesIgnoredError')
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain('Compiled successfully')
})
test('ignore during builds', async () => {
const { stdout, stderr } = await nextBuild(dirIgnoreDuringBuilds, [], {
stdout: true,
stderr: true,
})
test('missing Next.js plugin', async () => {
const { stdout, stderr } = await nextBuild(dirNoEslintPlugin, [], {
stdout: true,
stderr: true,
const output = stdout + stderr
expect(output).not.toContain('Failed to compile')
expect(output).not.toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
const output = stdout + stderr
expect(output).toContain(
'The Next.js plugin was not detected in your ESLint configuration'
)
})
test('base directories are linted by default during builds', async () => {
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
test('eslint caching is enabled', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
const output = stdout + stderr
await fs.remove(cacheDir)
await nextBuild(dirEslintCache, [])
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain(
'Warning: Synchronous scripts should not be used'
)
expect(output).toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
// Files in app, pages, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).toContain('app/layout.js')
})
expect(cacheExists).toBe(true)
})
test('custom directories', async () => {
const { stdout, stderr } = await nextBuild(dirCustomDirectories, [], {
stdout: true,
stderr: true,
})
test('eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
})
await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)
test('invalid older eslint version', async () => {
const { stdout, stderr } = await nextBuild(
dirInvalidOlderEslintVersion,
[],
{
stdout: true,
stderr: true,
}
)
await nextBuild(dirEslintCacheCustomDir, [])
const output = stdout + stderr
expect(output).toContain(
'Your project has an older version of ESLint installed'
)
})
expect(fs.existsSync(oldCacheDir)).toBe(false)
test('empty directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEmptyDirectory, [], {
stdout: true,
stderr: true,
})
const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('NoFilesFoundError')
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain('Compiled successfully')
})
expect(cacheExists).toBe(true)
test('eslint ignored directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEslintIgnore, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('AllFilesIgnoredError')
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain('Compiled successfully')
})
test('missing Next.js plugin', async () => {
const { stdout, stderr } = await nextBuild(dirNoEslintPlugin, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'The Next.js plugin was not detected in your ESLint configuration'
)
})
test('eslint caching is enabled', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextBuild(dirEslintCache, [])
const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')
await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)
await nextBuild(dirEslintCacheCustomDir, [])
expect(fs.existsSync(oldCacheDir)).toBe(false)
const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
})
})

View file

@ -19,33 +19,37 @@ const fileExist = (path) =>
// Issue #36855
// https://github.com/vercel/next.js/issues/36855
describe('Static 404 Export', () => {
it('only export 404.html when trailingSlash: false', async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('only export 404.html when trailingSlash: false', async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
expect(await fileExist(join(outdir, '404.html'))).toBe(true)
expect(await fileExist(join(outdir, '404.html.html'))).toBe(false)
expect(await fileExist(join(outdir, '404/index.html'))).toBe(false)
})
expect(await fileExist(join(outdir, '404.html'))).toBe(true)
expect(await fileExist(join(outdir, '404.html.html'))).toBe(false)
expect(await fileExist(join(outdir, '404/index.html'))).toBe(false)
})
it('export 404.html and 404/index.html when trailingSlash: true', async () => {
nextConfig.replace(`trailingSlash: false`, `trailingSlash: true`)
await nextBuild(appDir)
await nextExport(appDir, { outdir })
nextConfig.restore()
it('export 404.html and 404/index.html when trailingSlash: true', async () => {
nextConfig.replace(`trailingSlash: false`, `trailingSlash: true`)
await nextBuild(appDir)
await nextExport(appDir, { outdir })
nextConfig.restore()
expect(await fileExist(join(outdir, '404/index.html'))).toBe(true)
expect(await fileExist(join(outdir, '404.html.html'))).toBe(false)
expect(await fileExist(join(outdir, '404.html'))).toBe(true)
expect(await fileExist(join(outdir, '404/index.html'))).toBe(true)
expect(await fileExist(join(outdir, '404.html.html'))).toBe(false)
expect(await fileExist(join(outdir, '404.html'))).toBe(true)
})
})
})
describe('Export with a page named 404.js', () => {
it('should export a custom 404.html instead of default 404.html', async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should export a custom 404.html instead of default 404.html', async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
const html = await readFile(join(outdir, '404.html'), 'utf8')
expect(html).toMatch(/this is a 404 page override the default 404\.html/)
const html = await readFile(join(outdir, '404.html'), 'utf8')
expect(html).toMatch(/this is a 404 page override the default 404\.html/)
})
})
})

View file

@ -10,56 +10,64 @@ const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
describe('Export with default map', () => {
beforeAll(async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
})
it('should export with folder that has dot in name', async () => {
expect.assertions(1)
await expect(access(join(outdir, 'v1.12.html'))).resolves.toBe(undefined)
})
it('should export with folder that has dot in name', async () => {
expect.assertions(1)
await expect(access(join(outdir, 'v1.12.html'))).resolves.toBe(undefined)
})
it('should export an amp only page to clean path', async () => {
expect.assertions(1)
await expect(access(join(outdir, 'docs.html'))).resolves.toBe(undefined)
})
it('should export an amp only page to clean path', async () => {
expect.assertions(1)
await expect(access(join(outdir, 'docs.html'))).resolves.toBe(undefined)
})
it('should export hybrid amp page correctly', async () => {
expect.assertions(2)
await expect(access(join(outdir, 'some.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'some.amp.html'))).resolves.toBe(undefined)
})
it('should export hybrid amp page correctly', async () => {
expect.assertions(2)
await expect(access(join(outdir, 'some.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'some.amp.html'))).resolves.toBe(
undefined
)
})
it('should export nested hybrid amp page correctly', async () => {
expect.assertions(3)
await expect(access(join(outdir, 'docs.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'docs.amp.html'))).resolves.toBe(undefined)
it('should export nested hybrid amp page correctly', async () => {
expect.assertions(3)
await expect(access(join(outdir, 'docs.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'docs.amp.html'))).resolves.toBe(
undefined
)
const html = await readFile(join(outdir, 'docs.html'))
const $ = cheerio.load(html)
expect($('link[rel=amphtml]').attr('href')).toBe('/docs.amp')
})
const html = await readFile(join(outdir, 'docs.html'))
const $ = cheerio.load(html)
expect($('link[rel=amphtml]').attr('href')).toBe('/docs.amp')
})
it('should export nested hybrid amp page correctly with folder', async () => {
expect.assertions(3)
await expect(access(join(outdir, 'info.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'info.amp.html'))).resolves.toBe(undefined)
it('should export nested hybrid amp page correctly with folder', async () => {
expect.assertions(3)
await expect(access(join(outdir, 'info.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'info.amp.html'))).resolves.toBe(
undefined
)
const html = await readFile(join(outdir, 'info.html'))
const $ = cheerio.load(html)
expect($('link[rel=amphtml]').attr('href')).toBe('/info.amp')
})
const html = await readFile(join(outdir, 'info.html'))
const $ = cheerio.load(html)
expect($('link[rel=amphtml]').attr('href')).toBe('/info.amp')
})
it('should export hybrid index amp page correctly', async () => {
expect.assertions(3)
await expect(access(join(outdir, 'index.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'index.amp.html'))).resolves.toBe(
undefined
)
it('should export hybrid index amp page correctly', async () => {
expect.assertions(3)
await expect(access(join(outdir, 'index.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'index.amp.html'))).resolves.toBe(
undefined
)
const html = await readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('link[rel=amphtml]').attr('href')).toBe('/index.amp')
const html = await readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('link[rel=amphtml]').attr('href')).toBe('/index.amp')
})
})
})

View file

@ -14,9 +14,8 @@ import {
const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
;(process.env.TURBOPACK ? describe.skip : describe)(
'Export Dynamic Pages',
() => {
describe('Export Dynamic Pages', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
let server
let port
beforeAll(async () => {
@ -48,5 +47,5 @@ const outdir = join(appDir, 'out')
await browser.close()
}
})
}
)
})
})

View file

@ -8,18 +8,20 @@ const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
describe('Export error for fallback: true', () => {
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
it('should have error during next export', async () => {
const { stderr } = await nextExport(appDir, { outdir }, { stderr: true })
expect(stderr).toContain('Found pages with `fallback` enabled')
expect(stderr).toContain(
'Pages with `fallback` enabled in `getStaticPaths` can not be exported'
)
expect(stderr).toContain('/[slug]')
it('should have error during next export', async () => {
const { stderr } = await nextExport(appDir, { outdir }, { stderr: true })
expect(stderr).toContain('Found pages with `fallback` enabled')
expect(stderr).toContain(
'Pages with `fallback` enabled in `getStaticPaths` can not be exported'
)
expect(stderr).toContain('/[slug]')
})
})
})

View file

@ -7,11 +7,13 @@ const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
describe('Export with getInitialProps', () => {
it('should show warning with next export', async () => {
await nextBuild(appDir)
const { stderr } = await nextExport(appDir, { outdir }, { stderr: true })
expect(stderr).toContain(
'https://nextjs.org/docs/messages/get-initial-props-export'
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should show warning with next export', async () => {
await nextBuild(appDir)
const { stderr } = await nextExport(appDir, { outdir }, { stderr: true })
expect(stderr).toContain(
'https://nextjs.org/docs/messages/get-initial-props-export'
)
})
})
})

View file

@ -8,16 +8,18 @@ const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
describe('Export with default loader next/image component', () => {
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
it('should have error during next export', async () => {
const { stderr } = await nextExport(appDir, { outdir }, { stderr: true })
expect(stderr).toContain(
'Image Optimization using the default loader is not compatible with export.'
)
it('should have error during next export', async () => {
const { stderr } = await nextExport(appDir, { outdir }, { stderr: true })
expect(stderr).toContain(
'Image Optimization using the default loader is not compatible with export.'
)
})
})
})

View file

@ -11,132 +11,140 @@ const nextConfig = new File(join(appDir, 'next.config.js'))
const pagesIndexJs = new File(join(appDir, 'pages', 'index.js'))
describe('Export with cloudinary loader next/legacy/image component', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
loader: 'cloudinary',
path: 'https://example.com/',
},
})
)
})
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
loader: 'cloudinary',
path: 'https://example.com/',
},
})
)
})
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
it('should contain img element in html output', async () => {
const html = await fs.readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('img[alt="icon"]').attr('alt')).toBe('icon')
})
it('should contain img element in html output', async () => {
const html = await fs.readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('img[alt="icon"]').attr('alt')).toBe('icon')
})
afterAll(async () => {
await nextConfig.restore()
afterAll(async () => {
await nextConfig.restore()
})
})
})
describe('Export with custom loader next/legacy/image component', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
loader: 'custom',
},
})
)
await pagesIndexJs.replace(
'loader = undefined',
'loader = ({src}) => "/custom" + src'
)
})
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
loader: 'custom',
},
})
)
await pagesIndexJs.replace(
'loader = undefined',
'loader = ({src}) => "/custom" + src'
)
})
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
it('should contain img element with same src in html output', async () => {
const html = await fs.readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('img[src="/custom/o.png"]')).toBeDefined()
})
it('should contain img element with same src in html output', async () => {
const html = await fs.readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('img[src="/custom/o.png"]')).toBeDefined()
})
afterAll(async () => {
await nextConfig.restore()
await pagesIndexJs.restore()
afterAll(async () => {
await nextConfig.restore()
await pagesIndexJs.restore()
})
})
})
describe('Export with custom loader config but no loader prop on next/legacy/image', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
loader: 'custom',
},
})
)
})
it('should fail build', async () => {
await fs.remove(join(appDir, '.next'))
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(1)
expect(stderr).toContain(
'Error: Image with src "/i.png" is missing "loader" prop'
)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
loader: 'custom',
},
})
)
})
it('should fail build', async () => {
await fs.remove(join(appDir, '.next'))
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(1)
expect(stderr).toContain(
'Error: Image with src "/i.png" is missing "loader" prop'
)
})
afterAll(async () => {
await nextConfig.restore()
await pagesIndexJs.restore()
afterAll(async () => {
await nextConfig.restore()
await pagesIndexJs.restore()
})
})
})
describe('Export with unoptimized next/legacy/image component', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
unoptimized: true,
},
})
)
})
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextConfig.replace(
'{ /* replaceme */ }',
JSON.stringify({
images: {
unoptimized: true,
},
})
)
})
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
it('should contain img element with same src in html output', async () => {
const html = await fs.readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('img[src="/o.png"]')).toBeDefined()
})
it('should contain img element with same src in html output', async () => {
const html = await fs.readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('img[src="/o.png"]')).toBeDefined()
})
afterAll(async () => {
await nextConfig.restore()
afterAll(async () => {
await nextConfig.restore()
})
})
})

View file

@ -8,14 +8,16 @@ const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
describe('Export index page with `notFound: true` in `getStaticProps`', () => {
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build successfully', async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with status ${code}`)
})
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
it('should export successfully', async () => {
const { code } = await nextExport(appDir, { outdir })
if (code !== 0) throw new Error(`export failed with status ${code}`)
})
})
})

View file

@ -6,30 +6,32 @@ import { nextBuild, nextExportDefault } from 'next-test-utils'
const appDir = join(__dirname, '../')
describe('Export cli prints progress info', () => {
let buildStdout
let exportStdout
beforeAll(async () => {
const buildResult = await nextBuild(appDir, [], { stdout: true })
buildStdout = buildResult.stdout
const exportResult = await nextExportDefault(appDir, { stdout: true })
exportStdout = exportResult.stdout
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
let buildStdout
let exportStdout
beforeAll(async () => {
const buildResult = await nextBuild(appDir, [], { stdout: true })
buildStdout = buildResult.stdout
const exportResult = await nextExportDefault(appDir, { stdout: true })
exportStdout = exportResult.stdout
})
it('build: should log with internally passed statusMessage', async () => {
const lines = buildStdout.split('\n')
// Search `info - Generating static pages (n/m)` line
const found = lines.some((line) =>
/Generating static pages \(\d+\/\d+\)/.test(line)
)
it('build: should log with internally passed statusMessage', async () => {
const lines = buildStdout.split('\n')
// Search `info - Generating static pages (n/m)` line
const found = lines.some((line) =>
/Generating static pages \(\d+\/\d+\)/.test(line)
)
expect(found).toBeTruthy()
})
expect(found).toBeTruthy()
})
it('export: should log with default label', async () => {
const lines = exportStdout.split('\n')
// Search `info - Exporting (n/m)` line
const found = lines.some((line) => /Exporting \(\d+\/\d+\)/.test(line))
it('export: should log with default label', async () => {
const lines = exportStdout.split('\n')
// Search `info - Exporting (n/m)` line
const found = lines.some((line) => /Exporting \(\d+\/\d+\)/.test(line))
expect(found).toBeTruthy()
expect(found).toBeTruthy()
})
})
})

View file

@ -10,27 +10,29 @@ const appDir = join(__dirname, '../')
const outdir = join(appDir, 'out')
describe('Export config#exportTrailingSlash set to false', () => {
beforeAll(async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
await nextExport(appDir, { outdir })
})
it('should export pages as [filename].html instead of [filename]/index.html', async () => {
expect.assertions(6)
it('should export pages as [filename].html instead of [filename]/index.html', async () => {
expect.assertions(6)
await expect(access(join(outdir, 'index.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'about.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'posts.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'posts', 'single.html'))).resolves.toBe(
undefined
)
await expect(access(join(outdir, 'index.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'about.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'posts.html'))).resolves.toBe(undefined)
await expect(access(join(outdir, 'posts', 'single.html'))).resolves.toBe(
undefined
)
const html = await readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('p').text()).toBe('I am a home page')
const html = await readFile(join(outdir, 'index.html'))
const $ = cheerio.load(html)
expect($('p').text()).toBe('I am a home page')
const htmlSingle = await readFile(join(outdir, 'posts', 'single.html'))
const $single = cheerio.load(htmlSingle)
expect($single('p').text()).toBe('I am a single post')
const htmlSingle = await readFile(join(outdir, 'posts', 'single.html'))
const $single = cheerio.load(htmlSingle)
expect($single('p').text()).toBe('I am a single post')
})
})
})

View file

@ -7,12 +7,14 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '../')
describe('bundle pages externals with config.experimental.bundlePagesExternals', () => {
it('should have no externals with the config set', async () => {
await nextBuild(appDir, [], { stdout: true })
const output = await fs.readFile(
join(appDir, '.next/server/pages/index.js'),
'utf8'
)
expect(output).not.toContain('require("external-package")')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should have no externals with the config set', async () => {
await nextBuild(appDir, [], { stdout: true })
const output = await fs.readFile(
join(appDir, '.next/server/pages/index.js'),
'utf8'
)
expect(output).not.toContain('require("external-package")')
})
})
})

View file

@ -7,48 +7,50 @@ import { join } from 'path'
const fixturesDir = join(__dirname, '..', 'fixtures')
describe('Build Output', () => {
describe('Crypto Application', () => {
let stdout
const appDir = join(fixturesDir, 'with-crypto')
describe('Fallback Modules', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
describe('Crypto Application', () => {
let stdout
const appDir = join(fixturesDir, 'with-crypto')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should not include crypto', async () => {
if (process.env.NEXT_PRIVATE_SKIP_SIZE_TESTS) {
return
}
it('should not include crypto', async () => {
if (process.env.NEXT_PRIVATE_SKIP_SIZE_TESTS) {
return
}
;({ stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
;({ stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
console.log(stdout)
console.log(stdout)
const parsePageSize = (page) =>
stdout.match(
new RegExp(` ${page} .*?((?:\\d|\\.){1,} (?:\\w{1,})) `)
)[1]
const parsePageSize = (page) =>
stdout.match(
new RegExp(` ${page} .*?((?:\\d|\\.){1,} (?:\\w{1,})) `)
)[1]
const parsePageFirstLoad = (page) =>
stdout.match(
new RegExp(
` ${page} .*?(?:(?:\\d|\\.){1,}) .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`
)
)[1]
const parsePageFirstLoad = (page) =>
stdout.match(
new RegExp(
` ${page} .*?(?:(?:\\d|\\.){1,}) .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`
)
)[1]
const indexSize = parsePageSize('/')
const indexFirstLoad = parsePageFirstLoad('/')
const indexSize = parsePageSize('/')
const indexFirstLoad = parsePageFirstLoad('/')
// expect(parseFloat(indexSize)).toBeLessThanOrEqual(3.1)
// expect(parseFloat(indexSize)).toBeGreaterThanOrEqual(2)
expect(indexSize.endsWith('kB')).toBe(true)
// expect(parseFloat(indexSize)).toBeLessThanOrEqual(3.1)
// expect(parseFloat(indexSize)).toBeGreaterThanOrEqual(2)
expect(indexSize.endsWith('kB')).toBe(true)
// expect(parseFloat(indexFirstLoad)).toBeLessThanOrEqual(67.9)
// expect(parseFloat(indexFirstLoad)).toBeGreaterThanOrEqual(60)
expect(indexFirstLoad.endsWith('kB')).toBe(true)
// expect(parseFloat(indexFirstLoad)).toBeLessThanOrEqual(67.9)
// expect(parseFloat(indexFirstLoad)).toBeGreaterThanOrEqual(60)
expect(indexFirstLoad.endsWith('kB')).toBe(true)
})
})
})
})

View file

@ -51,7 +51,7 @@ function runTests() {
}
describe('Fetch polyfill with ky-universal', () => {
describe('dev support', () => {
describe('development mode', () => {
beforeAll(async () => {
appPort = await findPort()
await startApiServer()
@ -68,8 +68,7 @@ describe('Fetch polyfill with ky-universal', () => {
runTests()
})
describe('Server support', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await startApiServer()
await nextBuild(appDir, [], {

View file

@ -70,7 +70,7 @@ function runTests() {
}
describe('Fetch polyfill', () => {
describe('dev support', () => {
describe('development mode', () => {
beforeAll(async () => {
appPort = await findPort()
await startApiServer()
@ -87,8 +87,7 @@ describe('Fetch polyfill', () => {
runTests()
})
describe('Server support', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await startApiServer()
await nextBuild(appDir, [], {

View file

@ -8,25 +8,35 @@ const appDir = path.join(__dirname, '..')
const nextConfig = path.join(appDir, 'next.config.js')
describe('Building Firebase', () => {
// TODO: investigate re-enabling this test in node 12 environment
it.skip('Throws an error when building with firebase dependency with worker_threads', async () => {
await fs.writeFile(
nextConfig,
`module.exports = { experimental: { workerThreads: true } }`
)
const results = await nextBuild(appDir, [], { stdout: true, stderr: true })
expect(results.stdout + results.stderr).toMatch(/Build error occurred/)
expect(results.stdout + results.stderr).toMatch(
/grpc_node\.node\. Module did not self-register\./
)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
// TODO: investigate re-enabling this test in node 12 environment
it.skip('Throws an error when building with firebase dependency with worker_threads', async () => {
await fs.writeFile(
nextConfig,
`module.exports = { experimental: { workerThreads: true } }`
)
const results = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
expect(results.stdout + results.stderr).toMatch(/Build error occurred/)
expect(results.stdout + results.stderr).toMatch(
/grpc_node\.node\. Module did not self-register\./
)
})
it('Throws no error when building with firebase dependency without worker_threads', async () => {
await fs.remove(nextConfig)
const results = await nextBuild(appDir, [], { stdout: true, stderr: true })
expect(results.stdout + results.stderr).not.toMatch(/Build error occurred/)
expect(results.stdout + results.stderr).not.toMatch(
/grpc_node\.node\. Module did not self-register\./
)
it('Throws no error when building with firebase dependency without worker_threads', async () => {
await fs.remove(nextConfig)
const results = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
expect(results.stdout + results.stderr).not.toMatch(
/Build error occurred/
)
expect(results.stdout + results.stderr).not.toMatch(
/grpc_node\.node\. Module did not self-register\./
)
})
})
})

View file

@ -15,20 +15,22 @@ let appPort
let app
describe('excludeDefaultMomentLocales', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir)
app = await nextStart(appDir, appPort)
await renderViaHTTP(appPort, '/')
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir)
app = await nextStart(appDir, appPort)
await renderViaHTTP(appPort, '/')
})
afterAll(() => killApp(app))
it('should load momentjs', async () => {
const browser = await webdriver(appPort, '/')
expect(await browser.elementByCss('h1').text()).toMatch(/current time/i)
const locales = await browser.eval('moment.locales()')
expect(locales).toEqual(['en'])
expect(locales.length).toBe(1)
await browser.close()
it('should load momentjs', async () => {
const browser = await webdriver(appPort, '/')
expect(await browser.elementByCss('h1').text()).toMatch(/current time/i)
const locales = await browser.eval('moment.locales()')
expect(locales).toEqual(['en'])
expect(locales.length).toBe(1)
await browser.close()
})
})
})

View file

@ -18,41 +18,43 @@ let app
const fileNames = ['1', '2.ext', '3.html']
describe('GS(S)P with file extension', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with code ${code}`)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
const { code } = await nextBuild(appDir)
if (code !== 0) throw new Error(`build failed with code ${code}`)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should support slug with different extensions', async () => {
const baseDir = join(appDir, '.next/server/pages')
fileNames.forEach((name) => {
const filePath = join(baseDir, name)
expect(fs.existsSync(filePath + '.html')).toBe(true)
expect(fs.existsSync(filePath + '.json')).toBe(true)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
})
afterAll(() => killApp(app))
it('should render properly for routes with extension', async () => {
const paths = fileNames.map((name) => `/${name}`)
const contentPromises = paths.map((path) => renderViaHTTP(appPort, path))
const contents = await Promise.all(contentPromises)
contents.forEach((content, i) => expect(content).toContain(fileNames[i]))
})
it('should contain extension in name of json files in _next/data', async () => {
const buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
const requests = fileNames.map((name) => {
const pathname = `/_next/data/${buildId}/${name}.json`
return fetchViaHTTP(appPort, pathname).then((r) => r.json())
it('should support slug with different extensions', async () => {
const baseDir = join(appDir, '.next/server/pages')
fileNames.forEach((name) => {
const filePath = join(baseDir, name)
expect(fs.existsSync(filePath + '.html')).toBe(true)
expect(fs.existsSync(filePath + '.json')).toBe(true)
})
})
it('should render properly for routes with extension', async () => {
const paths = fileNames.map((name) => `/${name}`)
const contentPromises = paths.map((path) => renderViaHTTP(appPort, path))
const contents = await Promise.all(contentPromises)
contents.forEach((content, i) => expect(content).toContain(fileNames[i]))
})
it('should contain extension in name of json files in _next/data', async () => {
const buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
const requests = fileNames.map((name) => {
const pathname = `/_next/data/${buildId}/${name}.json`
return fetchViaHTTP(appPort, pathname).then((r) => r.json())
})
const results = await Promise.all(requests)
results.forEach((result, i) =>
expect(result.pageProps.value).toBe(fileNames[i])
)
})
const results = await Promise.all(requests)
results.forEach((result, i) =>
expect(result.pageProps.value).toBe(fileNames[i])
)
})
})

View file

@ -499,13 +499,12 @@ describe('GS(S)P Redirect Support', () => {
afterAll(() => killApp(app))
runTests()
})
it('should error for redirect during prerendering', async () => {
await fs.mkdirp(join(appDir, 'pages/invalid'))
await fs.writeFile(
join(appDir, 'pages', 'invalid', '[slug].js'),
`
it('should error for redirect during prerendering', async () => {
await fs.mkdirp(join(appDir, 'pages/invalid'))
await fs.writeFile(
join(appDir, 'pages', 'invalid', '[slug].js'),
`
export default function Post(props) {
return "hi"
}
@ -526,16 +525,17 @@ describe('GS(S)P Redirect Support', () => {
}
}
`
)
const { stdout, stderr } = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = stdout + stderr
await fs.remove(join(appDir, 'pages/invalid'))
)
const { stdout, stderr } = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = stdout + stderr
await fs.remove(join(appDir, 'pages/invalid'))
expect(output).toContain(
'`redirect` can not be returned from getStaticProps during prerendering'
)
expect(output).toContain(
'`redirect` can not be returned from getStaticProps during prerendering'
)
})
})
})

View file

@ -537,13 +537,12 @@ describe('GS(S)P Redirect Support', () => {
it('should not have errors in output', async () => {
expect(output).not.toContain('Failed to update prerender files')
})
})
it('should error for redirect during prerendering', async () => {
await fs.mkdirp(join(appDir, 'pages/invalid'))
await fs.writeFile(
join(appDir, 'pages', 'invalid', '[slug].js'),
`
it('should error for redirect during prerendering', async () => {
await fs.mkdirp(join(appDir, 'pages/invalid'))
await fs.writeFile(
join(appDir, 'pages', 'invalid', '[slug].js'),
`
export default function Post(props) {
return "hi"
}
@ -564,16 +563,17 @@ describe('GS(S)P Redirect Support', () => {
}
}
`
)
const { stdout, stderr } = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = stdout + stderr
await fs.remove(join(appDir, 'pages/invalid'))
)
const { stdout, stderr } = await nextBuild(appDir, undefined, {
stdout: true,
stderr: true,
})
const output = stdout + stderr
await fs.remove(join(appDir, 'pages/invalid'))
expect(output).toContain(
'`redirect` can not be returned from getStaticProps during prerendering'
)
expect(output).toContain(
'`redirect` can not be returned from getStaticProps during prerendering'
)
})
})
})

View file

@ -6,22 +6,24 @@ import { nextBuild } from 'next-test-utils'
const appDir = path.join(__dirname, '..')
describe('Handles Errors During Export', () => {
it('Does not crash workers', async () => {
const { stdout, stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('Does not crash workers', async () => {
const { stdout, stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
expect(stdout + stderr).not.toMatch(/ERR_IPC_CHANNEL_CLOSED/)
expect(stderr).toContain('Export encountered errors on following paths')
expect(stderr).toContain('/page')
expect(stderr).toContain('/page-1')
expect(stderr).toContain('/page-2')
expect(stderr).toContain('/page-3')
expect(stderr).toContain('/page-13')
expect(stderr).toContain('/blog/[slug]: /blog/first')
expect(stderr).toContain('/blog/[slug]: /blog/second')
expect(stderr).toContain('/custom-error')
expect(stderr).toContain('custom error message')
expect(stdout + stderr).not.toMatch(/ERR_IPC_CHANNEL_CLOSED/)
expect(stderr).toContain('Export encountered errors on following paths')
expect(stderr).toContain('/page')
expect(stderr).toContain('/page-1')
expect(stderr).toContain('/page-2')
expect(stderr).toContain('/page-3')
expect(stderr).toContain('/page-13')
expect(stderr).toContain('/blog/[slug]: /blog/first')
expect(stderr).toContain('/blog/[slug]: /blog/second')
expect(stderr).toContain('/custom-error')
expect(stderr).toContain('custom error message')
})
})
})

View file

@ -9,28 +9,30 @@ let appPort
let server
describe('hydrate/render ordering', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir, [], {})
server = await nextStart(appDir, appPort)
})
afterAll(() => killApp(server))
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir, [], {})
server = await nextStart(appDir, appPort)
})
afterAll(() => killApp(server))
it('correctly measures hydrate followed by render', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#to-other')
await browser.elementByCss('#to-other').click()
await browser.waitForElementByCss('#on-other')
it('correctly measures hydrate followed by render', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#to-other')
await browser.elementByCss('#to-other').click()
await browser.waitForElementByCss('#on-other')
const beacons = (await browser.eval('window.__BEACONS'))
.map(([, value]) => Object.fromEntries(new URLSearchParams(value)))
.filter((p) => p.label === 'custom')
expect(beacons).toMatchObject([
{ name: 'Next.js-hydration' },
{ name: 'Next.js-render' },
{ name: 'Next.js-route-change-to-render' },
])
const beacons = (await browser.eval('window.__BEACONS'))
.map(([, value]) => Object.fromEntries(new URLSearchParams(value)))
.filter((p) => p.label === 'custom')
expect(beacons).toMatchObject([
{ name: 'Next.js-hydration' },
{ name: 'Next.js-render' },
{ name: 'Next.js-route-change-to-render' },
])
await browser.close()
await browser.close()
})
})
})

View file

@ -78,100 +78,105 @@ describe('i18n Support basePath', () => {
})
describe('with localeDetection disabled', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
nextConfig.replace('// localeDetection', 'localeDetection')
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
nextConfig.replace('// localeDetection', 'localeDetection')
await nextBuild(appDir)
ctx.appPort = await findPort()
ctx.app = await nextStart(appDir, ctx.appPort)
})
afterAll(async () => {
nextConfig.restore()
await killApp(ctx.app)
})
await nextBuild(appDir)
ctx.appPort = await findPort()
ctx.app = await nextStart(appDir, ctx.appPort)
})
afterAll(async () => {
nextConfig.restore()
await killApp(ctx.app)
})
it('should have localeDetection in routes-manifest', async () => {
const routesManifest = await fs.readJSON(
join(appDir, '.next/routes-manifest.json')
)
it('should have localeDetection in routes-manifest', async () => {
const routesManifest = await fs.readJSON(
join(appDir, '.next/routes-manifest.json')
)
expect(routesManifest.i18n).toEqual({
localeDetection: false,
locales: [
'en-US',
'nl-NL',
'nl-BE',
'nl',
'fr-BE',
'fr',
'en',
'go',
'go-BE',
'do',
'do-BE',
],
defaultLocale: 'en-US',
domains: [
{
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{
domain: 'example.com',
defaultLocale: 'go',
locales: ['go-BE'],
},
],
})
})
expect(routesManifest.i18n).toEqual({
localeDetection: false,
locales: [
'en-US',
'nl-NL',
'nl-BE',
'nl',
'fr-BE',
'fr',
'en',
'go',
'go-BE',
'do',
'do-BE',
],
defaultLocale: 'en-US',
domains: [
{
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{
domain: 'example.com',
defaultLocale: 'go',
locales: ['go-BE'],
},
],
})
})
it('should not detect locale from accept-language', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
{},
{
redirect: 'manual',
headers: {
'accept-language': 'fr',
},
}
)
it('should not detect locale from accept-language', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
{},
{
redirect: 'manual',
headers: {
'accept-language': 'fr',
},
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
it('should set locale from detected path', async () => {
for (const locale of locales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}`,
{},
{
redirect: 'manual',
headers: {
'accept-language': 'en-US,en;q=0.9',
},
it('should set locale from detected path', async () => {
for (const locale of locales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}`,
{},
{
redirect: 'manual',
headers: {
'accept-language': 'en-US,en;q=0.9',
},
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
}
})
)
})
})

View file

@ -88,123 +88,128 @@ describe('i18n Support', () => {
})
describe('with localeDetection disabled', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
nextConfig.replace('// localeDetection', 'localeDetection')
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
nextConfig.replace('// localeDetection', 'localeDetection')
await nextBuild(appDir)
ctx.appPort = await findPort()
ctx.app = await nextStart(appDir, ctx.appPort)
})
afterAll(async () => {
nextConfig.restore()
await killApp(ctx.app)
})
await nextBuild(appDir)
ctx.appPort = await findPort()
ctx.app = await nextStart(appDir, ctx.appPort)
})
afterAll(async () => {
nextConfig.restore()
await killApp(ctx.app)
})
it('should have localeDetection in routes-manifest', async () => {
const routesManifest = await fs.readJSON(
join(appDir, '.next/routes-manifest.json')
)
it('should have localeDetection in routes-manifest', async () => {
const routesManifest = await fs.readJSON(
join(appDir, '.next/routes-manifest.json')
)
expect(routesManifest.i18n).toEqual({
localeDetection: false,
locales: [
'en-US',
'nl-NL',
'nl-BE',
'nl',
'fr-BE',
'fr',
'en',
'go',
'go-BE',
'do',
'do-BE',
],
defaultLocale: 'en-US',
domains: [
{
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{
domain: 'example.com',
defaultLocale: 'go',
locales: ['go-BE'],
},
],
})
})
expect(routesManifest.i18n).toEqual({
localeDetection: false,
locales: [
'en-US',
'nl-NL',
'nl-BE',
'nl',
'fr-BE',
'fr',
'en',
'go',
'go-BE',
'do',
'do-BE',
],
defaultLocale: 'en-US',
domains: [
{
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{
domain: 'example.com',
defaultLocale: 'go',
locales: ['go-BE'],
},
],
})
})
it('should not detect locale from accept-language', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
'/',
{},
{
redirect: 'manual',
headers: {
'accept-language': 'fr',
},
}
)
it('should not detect locale from accept-language', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
'/',
{},
{
redirect: 'manual',
headers: {
'accept-language': 'fr',
},
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
it('should ignore the invalid accept-language header', async () => {
nextConfig.replace('localeDetection: false', 'localeDetection: true')
const res = await fetchViaHTTP(
ctx.appPort,
'/',
{},
{
headers: {
'accept-language': 'ldfir;',
},
}
)
it('should ignore the invalid accept-language header', async () => {
nextConfig.replace('localeDetection: false', 'localeDetection: true')
const res = await fetchViaHTTP(
ctx.appPort,
'/',
{},
{
headers: {
'accept-language': 'ldfir;',
},
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
it('should set locale from detected path', async () => {
for (const locale of nonDomainLocales) {
const res = await fetchViaHTTP(
ctx.appPort,
`/${locale}`,
{},
{
redirect: 'manual',
headers: {
'accept-language': 'en-US,en;q=0.9',
},
it('should set locale from detected path', async () => {
for (const locale of nonDomainLocales) {
const res = await fetchViaHTTP(
ctx.appPort,
`/${locale}`,
{},
{
redirect: 'manual',
headers: {
'accept-language': 'en-US,en;q=0.9',
},
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
}
})
)
})
describe('with trailingSlash: true', () => {
@ -520,9 +525,9 @@ describe('i18n Support', () => {
}
)
})
it('should show proper error for duplicate defaultLocales', async () => {
nextConfig.write(`
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should show proper error for duplicate defaultLocales', async () => {
nextConfig.write(`
module.exports = {
i18n: {
locales: ['en', 'fr', 'nl'],
@ -545,18 +550,18 @@ describe('i18n Support', () => {
}
`)
const { code, stderr } = await nextBuild(appDir, undefined, {
stderr: true,
const { code, stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
nextConfig.restore()
expect(code).toBe(1)
expect(stderr).toContain(
'Both fr.example.com and french.example.com configured the defaultLocale fr but only one can'
)
})
nextConfig.restore()
expect(code).toBe(1)
expect(stderr).toContain(
'Both fr.example.com and french.example.com configured the defaultLocale fr but only one can'
)
})
it('should show proper error for duplicate locales', async () => {
nextConfig.write(`
it('should show proper error for duplicate locales', async () => {
nextConfig.write(`
module.exports = {
i18n: {
locales: ['en', 'fr', 'nl', 'eN', 'fr'],
@ -565,19 +570,19 @@ describe('i18n Support', () => {
}
`)
const { code, stderr } = await nextBuild(appDir, undefined, {
stderr: true,
const { code, stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
nextConfig.restore()
expect(code).toBe(1)
expect(stderr).toContain(
'Specified i18n.locales contains the following duplicate locales:'
)
expect(stderr).toContain(`eN, fr`)
})
nextConfig.restore()
expect(code).toBe(1)
expect(stderr).toContain(
'Specified i18n.locales contains the following duplicate locales:'
)
expect(stderr).toContain(`eN, fr`)
})
it('should show proper error for invalid locale domain', async () => {
nextConfig.write(`
it('should show proper error for invalid locale domain', async () => {
nextConfig.write(`
module.exports = {
i18n: {
locales: ['en', 'fr', 'nl', 'eN', 'fr'],
@ -592,13 +597,14 @@ describe('i18n Support', () => {
}
`)
const { code, stderr } = await nextBuild(appDir, undefined, {
stderr: true,
const { code, stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
nextConfig.restore()
expect(code).toBe(1)
expect(stderr).toContain(
`i18n domain: "hello:3000" is invalid it should be a valid domain without protocol (https://) or port (:3000) e.g. example.vercel.sh`
)
})
nextConfig.restore()
expect(code).toBe(1)
expect(stderr).toContain(
`i18n domain: "hello:3000" is invalid it should be a valid domain without protocol (https://) or port (:3000) e.g. example.vercel.sh`
)
})
})

View file

@ -483,14 +483,17 @@ describe('Image Optimizer', () => {
})
describe('Server support for headers in next.config.js', () => {
const size = 96 // defaults defined in server/config.ts
let app
let appPort
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
const size = 96 // defaults defined in server/config.ts
let app
let appPort
beforeAll(async () => {
nextConfig.replace(
'{ /* replaceme */ }',
`{
beforeAll(async () => {
nextConfig.replace(
'{ /* replaceme */ }',
`{
async headers() {
return [
{
@ -505,58 +508,62 @@ describe('Image Optimizer', () => {
]
},
}`
)
await nextBuild(appDir)
await cleanImagesDir({ imagesDir })
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
nextConfig.restore()
})
it('should set max-age header', async () => {
const query = { url: '/test.png', w: size, q: 75 }
const opts = { headers: { accept: 'image/webp' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=86400, must-revalidate`
)
expect(res.headers.get('Content-Disposition')).toBe(
`inline; filename="test.webp"`
)
await check(async () => {
const files = await fsToJson(imagesDir)
let found = false
const maxAge = '86400'
Object.keys(files).forEach((dir) => {
if (
Object.keys(files[dir]).some((file) => file.includes(`${maxAge}.`))
) {
found = true
}
)
await nextBuild(appDir)
await cleanImagesDir({ imagesDir })
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
nextConfig.restore()
})
return found ? 'success' : 'failed'
}, 'success')
})
it('should not set max-age header when not matching next.config.js', async () => {
const query = { url: '/test.jpg', w: size, q: 75 }
const opts = { headers: { accept: 'image/webp' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=60, must-revalidate`
)
expect(res.headers.get('Content-Disposition')).toBe(
`inline; filename="test.webp"`
)
})
it('should set max-age header', async () => {
const query = { url: '/test.png', w: size, q: 75 }
const opts = { headers: { accept: 'image/webp' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=86400, must-revalidate`
)
expect(res.headers.get('Content-Disposition')).toBe(
`inline; filename="test.webp"`
)
await check(async () => {
const files = await fsToJson(imagesDir)
let found = false
const maxAge = '86400'
Object.keys(files).forEach((dir) => {
if (
Object.keys(files[dir]).some((file) =>
file.includes(`${maxAge}.`)
)
) {
found = true
}
})
return found ? 'success' : 'failed'
}, 'success')
})
it('should not set max-age header when not matching next.config.js', async () => {
const query = { url: '/test.jpg', w: size, q: 75 }
const opts = { headers: { accept: 'image/webp' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=60, must-revalidate`
)
expect(res.headers.get('Content-Disposition')).toBe(
`inline; filename="test.webp"`
)
})
}
)
})
describe('dev support next.config.js cloudinary loader', () => {
@ -619,11 +626,14 @@ describe('Image Optimizer', () => {
})
describe('External rewrite support with for serving static content in images', () => {
let app
let appPort
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
let app
let appPort
beforeAll(async () => {
const newConfig = `{
beforeAll(async () => {
const newConfig = `{
async rewrites() {
return [
{
@ -633,50 +643,54 @@ describe('Image Optimizer', () => {
]
},
}`
nextConfig.replace('{ /* replaceme */ }', newConfig)
await nextBuild(appDir)
await cleanImagesDir({ imagesDir })
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
nextConfig.restore()
})
it('should return response when image is served from an external rewrite', async () => {
await cleanImagesDir({ imagesDir })
const query = { url: '/next-js/next-js-bg.png', w: 64, q: 75 }
const opts = { headers: { accept: 'image/webp' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/webp')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=31536000, must-revalidate`
)
expect(res.headers.get('Vary')).toBe('Accept')
expect(res.headers.get('Content-Disposition')).toBe(
`inline; filename="next-js-bg.webp"`
)
await check(async () => {
const files = await fsToJson(imagesDir)
let found = false
const maxAge = '31536000'
Object.keys(files).forEach((dir) => {
if (
Object.keys(files[dir]).some((file) => file.includes(`${maxAge}.`))
) {
found = true
}
nextConfig.replace('{ /* replaceme */ }', newConfig)
await nextBuild(appDir)
await cleanImagesDir({ imagesDir })
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
return found ? 'success' : 'failed'
}, 'success')
await expectWidth(res, 64)
})
afterAll(async () => {
await killApp(app)
nextConfig.restore()
})
it('should return response when image is served from an external rewrite', async () => {
await cleanImagesDir({ imagesDir })
const query = { url: '/next-js/next-js-bg.png', w: 64, q: 75 }
const opts = { headers: { accept: 'image/webp' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/webp')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=31536000, must-revalidate`
)
expect(res.headers.get('Vary')).toBe('Accept')
expect(res.headers.get('Content-Disposition')).toBe(
`inline; filename="next-js-bg.webp"`
)
await check(async () => {
const files = await fsToJson(imagesDir)
let found = false
const maxAge = '31536000'
Object.keys(files).forEach((dir) => {
if (
Object.keys(files[dir]).some((file) =>
file.includes(`${maxAge}.`)
)
) {
found = true
}
})
return found ? 'success' : 'failed'
}, 'success')
await expectWidth(res, 64)
})
}
)
})
describe('dev support for dynamic blur placeholder', () => {

View file

@ -10,29 +10,31 @@ const nextConfigPath = join(appDir, 'next.config.js')
const cleanUp = () => fs.remove(nextConfigPath)
describe('Handles valid/invalid assetPrefix', () => {
beforeAll(() => cleanUp())
afterAll(() => cleanUp())
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(() => cleanUp())
afterAll(() => cleanUp())
it('should not error without usage of assetPrefix', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
it('should not error without usage of assetPrefix', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
}`
)
)
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toMatch(/Specified assetPrefix is not a string/)
})
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toMatch(/Specified assetPrefix is not a string/)
})
it('should not error when assetPrefix is a string', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
it('should not error when assetPrefix is a string', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
assetPrefix: '/hello'
}`
)
)
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toMatch(/Specified assetPrefix is not a string/)
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toMatch(/Specified assetPrefix is not a string/)
})
})
})

View file

@ -8,37 +8,39 @@ const appDir = join(__dirname, '../')
const nextConfig = new File(join(appDir, 'next.config.js'))
describe('Invalid static image import in _document', () => {
afterAll(() => nextConfig.restore())
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
afterAll(() => nextConfig.restore())
it('Should fail to build when no next.config.js', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
it('Should fail to build when no next.config.js', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toMatch(
/Images.*cannot.*be imported within.*pages[\\/]_document\.js/
)
expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toMatch(
/Images.*cannot.*be imported within.*pages[\\/]_document\.js/
)
expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
})
it('Should fail to build when disableStaticImages in next.config.js', async () => {
nextConfig.write(`
it('Should fail to build when disableStaticImages in next.config.js', async () => {
nextConfig.write(`
module.exports = {
images: {
disableStaticImages: true
}
}
`)
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toMatch(
/You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file/
)
expect(stderr).not.toMatch(
/Images.*cannot.*be imported within.*pages[\\/]_document\.js/
)
})
expect(code).not.toBe(0)
expect(stderr).toMatch(
/You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file/
)
expect(stderr).not.toMatch(
/Images.*cannot.*be imported within.*pages[\\/]_document\.js/
)
})
})

View file

@ -7,24 +7,28 @@ import { nextBuild } from 'next-test-utils'
const appDir = path.join(__dirname, '..')
describe('Invalid Page automatic static optimization', () => {
it('Fails softly with descriptive error', async () => {
const { stderr } = await nextBuild(appDir, [], { stderr: true })
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('Fails softly with descriptive error', async () => {
const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toMatch(
/Build optimization failed: found pages without a React Component as default export in/
)
expect(stderr).toMatch(/pages\/invalid/)
expect(stderr).toMatch(/pages\/also-invalid/)
})
expect(stderr).toMatch(
/Build optimization failed: found pages without a React Component as default export in/
)
expect(stderr).toMatch(/pages\/invalid/)
expect(stderr).toMatch(/pages\/also-invalid/)
})
it('handles non-error correctly', async () => {
const testPage = path.join(appDir, 'pages/[slug].js')
await fs.rename(path.join(appDir, 'pages'), path.join(appDir, 'pages-bak'))
it('handles non-error correctly', async () => {
const testPage = path.join(appDir, 'pages/[slug].js')
await fs.rename(
path.join(appDir, 'pages'),
path.join(appDir, 'pages-bak')
)
await fs.ensureDir(path.join(appDir, 'pages'))
await fs.writeFile(
testPage,
`
await fs.ensureDir(path.join(appDir, 'pages'))
await fs.writeFile(
testPage,
`
export default function Page() {
return <p>hello world</p>
}
@ -41,18 +45,19 @@ describe('Invalid Page automatic static optimization', () => {
}
}
`
)
try {
const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toMatch(/invalid API token/)
expect(stderr).not.toMatch(/without a React Component/)
} finally {
await fs.remove(path.join(appDir, 'pages'))
await fs.rename(
path.join(appDir, 'pages-bak'),
path.join(appDir, 'pages')
)
}
try {
const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toMatch(/invalid API token/)
expect(stderr).not.toMatch(/without a React Component/)
} finally {
await fs.remove(path.join(appDir, 'pages'))
await fs.rename(
path.join(appDir, 'pages-bak'),
path.join(appDir, 'pages')
)
}
})
})
})

View file

@ -65,19 +65,26 @@ describe('TypeScript Features', () => {
})
describe('should build', () => {
beforeAll(async () => {
await nextBuild(appDir)
})
it('should trace correctly', async () => {
const helloTrace = await fs.readJSON(
join(appDir, '.next/server/pages/hello.js.nft.json')
)
expect(
helloTrace.files.some((file) => file.includes('components/world.js'))
).toBe(false)
expect(
helloTrace.files.some((file) => file.includes('react/index.js'))
).toBe(true)
})
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await nextBuild(appDir)
})
it('should trace correctly', async () => {
const helloTrace = await fs.readJSON(
join(appDir, '.next/server/pages/hello.js.nft.json')
)
expect(
helloTrace.files.some((file) =>
file.includes('components/world.js')
)
).toBe(false)
expect(
helloTrace.files.some((file) => file.includes('react/index.js'))
).toBe(true)
})
}
)
})
})

View file

@ -5,11 +5,13 @@ import { join } from 'path'
const appDir = join(__dirname, '..')
describe('Empty JSConfig Support', () => {
test('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
test('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
})

View file

@ -85,60 +85,67 @@ function runTests() {
})
describe('should build', () => {
beforeAll(async () => {
await nextBuild(appDir)
})
it('should trace correctly', async () => {
const singleAliasTrace = await fs.readJSON(
join(appDir, '.next/server/pages/single-alias.js.nft.json')
)
const wildcardAliasTrace = await fs.readJSON(
join(appDir, '.next/server/pages/wildcard-alias.js.nft.json')
)
const resolveOrderTrace = await fs.readJSON(
join(appDir, '.next/server/pages/resolve-order.js.nft.json')
)
const resolveFallbackTrace = await fs.readJSON(
join(appDir, '.next/server/pages/resolve-fallback.js.nft.json')
)
const basicAliasTrace = await fs.readJSON(
join(appDir, '.next/server/pages/basic-alias.js.nft.json')
)
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await nextBuild(appDir)
})
it('should trace correctly', async () => {
const singleAliasTrace = await fs.readJSON(
join(appDir, '.next/server/pages/single-alias.js.nft.json')
)
const wildcardAliasTrace = await fs.readJSON(
join(appDir, '.next/server/pages/wildcard-alias.js.nft.json')
)
const resolveOrderTrace = await fs.readJSON(
join(appDir, '.next/server/pages/resolve-order.js.nft.json')
)
const resolveFallbackTrace = await fs.readJSON(
join(appDir, '.next/server/pages/resolve-fallback.js.nft.json')
)
const basicAliasTrace = await fs.readJSON(
join(appDir, '.next/server/pages/basic-alias.js.nft.json')
)
expect(
singleAliasTrace.files.some((file) =>
file.includes('components/hello.js')
)
).toBe(false)
expect(
wildcardAliasTrace.files.some((file) =>
file.includes('mypackage/myfile.js')
)
).toBe(true)
expect(
wildcardAliasTrace.files.some((file) =>
file.includes('mypackage/data.js')
)
).toBe(false)
expect(
resolveOrderTrace.files.some((file) => file.includes('lib/a/api.js'))
).toBe(false)
expect(
resolveOrderTrace.files.some((file) =>
file.includes('mypackage/data.js')
)
).toBe(true)
expect(
resolveFallbackTrace.files.some((file) =>
file.includes('lib/b/b-only.js')
)
).toBe(false)
expect(
basicAliasTrace.files.some((file) =>
file.includes('components/world.js')
)
).toBe(false)
})
expect(
singleAliasTrace.files.some((file) =>
file.includes('components/hello.js')
)
).toBe(false)
expect(
wildcardAliasTrace.files.some((file) =>
file.includes('mypackage/myfile.js')
)
).toBe(true)
expect(
wildcardAliasTrace.files.some((file) =>
file.includes('mypackage/data.js')
)
).toBe(false)
expect(
resolveOrderTrace.files.some((file) =>
file.includes('lib/a/api.js')
)
).toBe(false)
expect(
resolveOrderTrace.files.some((file) =>
file.includes('mypackage/data.js')
)
).toBe(true)
expect(
resolveFallbackTrace.files.some((file) =>
file.includes('lib/b/b-only.js')
)
).toBe(false)
expect(
basicAliasTrace.files.some((file) =>
file.includes('components/world.js')
)
).toBe(false)
})
}
)
})
}

View file

@ -7,27 +7,29 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '..')
describe('jsconfig.json', () => {
it('should build normally', async () => {
const res = await await nextBuild(appDir, [], { stdout: true })
expect(res.stdout).toMatch(/Compiled successfully/)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should build normally', async () => {
const res = await await nextBuild(appDir, [], { stdout: true })
expect(res.stdout).toMatch(/Compiled successfully/)
})
it('should fail on invalid jsconfig.json', async () => {
const jsconfigPath = join(appDir, 'jsconfig.json')
const originalJsconfig = await fs.readFile(jsconfigPath, {
encoding: 'utf-8',
})
await fs.writeFile(jsconfigPath, '{', {
encoding: 'utf-8',
})
try {
const res = await nextBuild(appDir, [], { stderr: true })
expect(res.stderr).toMatch(/Error: Failed to parse "/)
expect(res.stderr).toMatch(/JSON5: invalid end of input at 1:2/)
} finally {
await fs.writeFile(jsconfigPath, originalJsconfig, {
it('should fail on invalid jsconfig.json', async () => {
const jsconfigPath = join(appDir, 'jsconfig.json')
const originalJsconfig = await fs.readFile(jsconfigPath, {
encoding: 'utf-8',
})
}
await fs.writeFile(jsconfigPath, '{', {
encoding: 'utf-8',
})
try {
const res = await nextBuild(appDir, [], { stderr: true })
expect(res.stderr).toMatch(/Error: Failed to parse "/)
expect(res.stderr).toMatch(/JSON5: invalid end of input at 1:2/)
} finally {
await fs.writeFile(jsconfigPath, originalJsconfig, {
encoding: 'utf-8',
})
}
})
})
})

View file

@ -7,9 +7,11 @@ jest.setTimeout(1000 * 60 * 2)
const appDir = join(__dirname, '..')
describe('JSON Serialization', () => {
test('should fail with original error', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(1)
expect(stderr).toContain('Do not know how to serialize a BigInt')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
test('should fail with original error', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(1)
expect(stderr).toContain('Do not know how to serialize a BigInt')
})
})
})

View file

@ -34,7 +34,7 @@ describe('dev mode', () => {
})
// TODO enable that once turbopack supports middleware in dev mode
describe.skip('production mode', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)

View file

@ -3,89 +3,91 @@ import { nextBuild } from 'next-test-utils'
import { join } from 'path'
describe('Middleware validation during build', () => {
const appDir = join(__dirname, '..')
const middlewareFile = join(appDir, 'middleware.js')
const middlewareError = 'Middleware is returning a response body'
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
const appDir = join(__dirname, '..')
const middlewareFile = join(appDir, 'middleware.js')
const middlewareError = 'Middleware is returning a response body'
beforeEach(() => remove(join(appDir, '.next')))
beforeEach(() => remove(join(appDir, '.next')))
afterEach(() =>
writeFile(middlewareFile, '// this will be populated by each test\n')
)
afterEach(() =>
writeFile(middlewareFile, '// this will be populated by each test\n')
)
describe.each([
{
title: 'returning a text body',
code: `export default function () {
describe.each([
{
title: 'returning a text body',
code: `export default function () {
return new Response('this is not allowed')
}`,
},
{
title: 'building body with JSON.stringify',
code: `export default function () {
},
{
title: 'building body with JSON.stringify',
code: `export default function () {
return new Response(JSON.stringify({ error: 'this is not allowed' }))
}`,
},
{
title: 'building response body with a variable',
code: `export default function () {
},
{
title: 'building response body with a variable',
code: `export default function () {
const body = 'this is not allowed, but hard to detect with AST'
return new Response(body)
}`,
},
{
title: 'building response body with custom code',
code: `function buildResponse() {
},
{
title: 'building response body with custom code',
code: `function buildResponse() {
return JSON.stringify({ message: 'this is not allowed, but hard to detect with AST' })
}
export default function () {
return new Response(buildResponse())
}`,
},
{
title: 'returning a text body with NextResponse',
code: `import { NextResponse } from 'next/server'
},
{
title: 'returning a text body with NextResponse',
code: `import { NextResponse } from 'next/server'
export default function () {
return new NextResponse('this is not allowed')
}`,
},
])('given a middleware $title', ({ code }) => {
beforeAll(() => writeFile(middlewareFile, code))
},
])('given a middleware $title', ({ code }) => {
beforeAll(() => writeFile(middlewareFile, code))
it('does not throw an error', async () => {
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
it('does not throw an error', async () => {
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
expect(stderr).not.toMatch(middlewareError)
expect(code).toBe(0)
})
expect(stderr).not.toMatch(middlewareError)
expect(code).toBe(0)
})
})
describe.each([
{
title: 'returning a null body',
code: `export default function () {
describe.each([
{
title: 'returning a null body',
code: `export default function () {
return new Response(null)
}`,
},
{
title: 'returning an undefined body',
code: `export default function () {
},
{
title: 'returning an undefined body',
code: `export default function () {
return new Response(undefined)
}`,
},
])('given a middleware $title', ({ code }) => {
beforeAll(() => writeFile(middlewareFile, code))
},
])('given a middleware $title', ({ code }) => {
beforeAll(() => writeFile(middlewareFile, code))
it('builds successfully', async () => {
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
it('builds successfully', async () => {
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
expect(stderr).not.toMatch(middlewareError)
expect(code).toBe(0)
})
expect(stderr).not.toMatch(middlewareError)
expect(code).toBe(0)
})
})
})

View file

@ -14,62 +14,64 @@ const context = {
}
describe('Middleware Production Prefetch', () => {
afterAll(() => killApp(context.app))
beforeAll(async () => {
const build = await nextBuild(context.appDir, undefined, {
stderr: true,
stdout: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
afterAll(() => killApp(context.app))
beforeAll(async () => {
const build = await nextBuild(context.appDir, undefined, {
stderr: true,
stdout: true,
})
context.buildId = await fs.readFile(
join(context.appDir, '.next/BUILD_ID'),
'utf8'
)
context.buildLogs = {
output: build.stdout + build.stderr,
stderr: build.stderr,
stdout: build.stdout,
}
context.dev = false
context.appPort = await findPort()
context.app = await nextStart(context.appDir, context.appPort, {
env: {
MIDDLEWARE_TEST: 'asdf',
},
onStdout(msg) {
context.logs.output += msg
context.logs.stdout += msg
},
onStderr(msg) {
context.logs.output += msg
context.logs.stderr += msg
},
})
})
context.buildId = await fs.readFile(
join(context.appDir, '.next/BUILD_ID'),
'utf8'
)
context.buildLogs = {
output: build.stdout + build.stderr,
stderr: build.stderr,
stdout: build.stdout,
}
context.dev = false
context.appPort = await findPort()
context.app = await nextStart(context.appDir, context.appPort, {
env: {
MIDDLEWARE_TEST: 'asdf',
},
onStdout(msg) {
context.logs.output += msg
context.logs.stdout += msg
},
onStderr(msg) {
context.logs.output += msg
context.logs.stderr += msg
},
it(`prefetch correctly for unexistent routes`, async () => {
const browser = await webdriver(context.appPort, `/`)
await browser.elementByCss('#made-up-link').moveTo()
await check(async () => {
const scripts = await browser.elementsByCss('script')
const attrs = await Promise.all(
scripts.map((script) => script.getAttribute('src'))
)
return attrs.find((src) => src.includes('/ssg-page')) ? 'yes' : 'nope'
}, 'yes')
})
})
it(`prefetch correctly for unexistent routes`, async () => {
const browser = await webdriver(context.appPort, `/`)
await browser.elementByCss('#made-up-link').moveTo()
await check(async () => {
const scripts = await browser.elementsByCss('script')
const attrs = await Promise.all(
scripts.map((script) => script.getAttribute('src'))
)
return attrs.find((src) => src.includes('/ssg-page')) ? 'yes' : 'nope'
}, 'yes')
})
it(`does not prefetch provided path if it will be rewritten`, async () => {
const browser = await webdriver(context.appPort, `/`)
await browser.elementByCss('#ssg-page-2').moveTo()
await check(async () => {
const scripts = await browser.elementsByCss('script')
const attrs = await Promise.all(
scripts.map((script) => script.getAttribute('src'))
)
return attrs.find((src) => src.includes('/ssg-page-2')) ? 'nope' : 'yes'
}, 'yes')
it(`does not prefetch provided path if it will be rewritten`, async () => {
const browser = await webdriver(context.appPort, `/`)
await browser.elementByCss('#ssg-page-2').moveTo()
await check(async () => {
const scripts = await browser.elementsByCss('script')
const attrs = await Promise.all(
scripts.map((script) => script.getAttribute('src'))
)
return attrs.find((src) => src.includes('/ssg-page-2')) ? 'nope' : 'yes'
}, 'yes')
})
})
})

View file

@ -264,101 +264,104 @@ async function hasImagePreloadBeforeCSSPreload() {
}
describe('Image Component Tests', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
describe('SSR Image Component Tests', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
browser = null
})
runTests()
it('should add a preload tag for a priority image', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriority.png?auto=format&fit=max&w=1024&q=60'
afterAll(() => killApp(app))
describe('SSR Image Component Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
})
afterAll(async () => {
browser = null
})
runTests()
it('should add a preload tag for a priority image', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriority.png?auto=format&fit=max&w=1024&q=60'
)
).toBe(true)
})
it('should add a preload tag for a priority image with preceding slash', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/fooslash.jpg?auto=format&fit=max&w=1024'
)
).toBe(true)
})
it('should add a preload tag for a priority image, with arbitrary host', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://arbitraryurl.com/withpriority3.png'
)
).toBe(true)
})
it('should add a preload tag for a priority image, with quality', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriority.png?auto=format&fit=max&w=1024&q=60'
)
).toBe(true)
})
it('should not create any preload tags higher up the page than CSS preload tags', async () => {
expect(await hasImagePreloadBeforeCSSPreload()).toBe(false)
})
it('should add data-nimg data attribute based on layout', async () => {
expect(
await browser
.elementById('image-with-sizes')
.getAttribute('data-nimg')
).toBe('responsive')
expect(
await browser.elementById('basic-image').getAttribute('data-nimg')
).toBe('intrinsic')
})
it('should not pass config to custom loader prop', async () => {
browser = await webdriver(appPort, '/loader-prop')
expect(
await browser.elementById('loader-prop-img').getAttribute('src')
).toBe('https://example.vercel.sh/success/foo.jpg?width=1024')
expect(
await browser.elementById('loader-prop-img').getAttribute('srcset')
).toBe(
'https://example.vercel.sh/success/foo.jpg?width=480 1x, https://example.vercel.sh/success/foo.jpg?width=1024 2x'
)
).toBe(true)
})
})
it('should add a preload tag for a priority image with preceding slash', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/fooslash.jpg?auto=format&fit=max&w=1024'
)
).toBe(true)
})
it('should add a preload tag for a priority image, with arbitrary host', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://arbitraryurl.com/withpriority3.png'
)
).toBe(true)
})
it('should add a preload tag for a priority image, with quality', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriority.png?auto=format&fit=max&w=1024&q=60'
)
).toBe(true)
})
it('should not create any preload tags higher up the page than CSS preload tags', async () => {
expect(await hasImagePreloadBeforeCSSPreload()).toBe(false)
})
it('should add data-nimg data attribute based on layout', async () => {
expect(
await browser.elementById('image-with-sizes').getAttribute('data-nimg')
).toBe('responsive')
expect(
await browser.elementById('basic-image').getAttribute('data-nimg')
).toBe('intrinsic')
})
it('should not pass config to custom loader prop', async () => {
browser = await webdriver(appPort, '/loader-prop')
expect(
await browser.elementById('loader-prop-img').getAttribute('src')
).toBe('https://example.vercel.sh/success/foo.jpg?width=1024')
expect(
await browser.elementById('loader-prop-img').getAttribute('srcset')
).toBe(
'https://example.vercel.sh/success/foo.jpg?width=480 1x, https://example.vercel.sh/success/foo.jpg?width=1024 2x'
)
})
})
describe('Client-side Image Component Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#clientlink').click()
})
afterAll(async () => {
browser = null
})
runTests()
// FIXME: this test
it.skip('should NOT add a preload tag for a priority image', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriorityclient.png?auto=format&fit=max'
)
).toBe(false)
})
it('should only be loaded once if `sizes` is set', async () => {
const numRequests = await browser.eval(`(function() {
describe('Client-side Image Component Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#clientlink').click()
})
afterAll(async () => {
browser = null
})
runTests()
// FIXME: this test
it.skip('should NOT add a preload tag for a priority image', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriorityclient.png?auto=format&fit=max'
)
).toBe(false)
})
it('should only be loaded once if `sizes` is set', async () => {
const numRequests = await browser.eval(`(function() {
const entries = window.performance.getEntries()
return entries.filter(function(entry) {
return entry.name.includes('test-sizes.jpg')
}).length
})()`)
expect(numRequests).toBe(1)
})
describe('Client-side Errors', () => {
beforeAll(async () => {
await browser.eval(`(function() {
expect(numRequests).toBe(1)
})
describe('Client-side Errors', () => {
beforeAll(async () => {
await browser.eval(`(function() {
window.gotHostError = false
const origError = console.error
window.console.error = function () {
@ -368,32 +371,33 @@ describe('Image Component Tests', () => {
origError.apply(this, arguments)
}
})()`)
await browser.waitForElementByCss('#errorslink').click()
})
it('Should not log an error when an unregistered host is used in production', async () => {
const foundError = await browser.eval('window.gotHostError')
expect(foundError).toBe(false)
await browser.waitForElementByCss('#errorslink').click()
})
it('Should not log an error when an unregistered host is used in production', async () => {
const foundError = await browser.eval('window.gotHostError')
expect(foundError).toBe(false)
})
})
})
})
describe('SSR Lazy Loading Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/lazy')
describe('SSR Lazy Loading Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/lazy')
})
afterAll(async () => {
browser = null
})
lazyLoadingTests()
})
afterAll(async () => {
browser = null
describe('Client-side Lazy Loading Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#lazylink').click()
await waitFor(500)
})
afterAll(async () => {
browser = null
})
lazyLoadingTests()
})
lazyLoadingTests()
})
describe('Client-side Lazy Loading Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#lazylink').click()
await waitFor(500)
})
afterAll(async () => {
browser = null
})
lazyLoadingTests()
})
})

View file

@ -30,32 +30,34 @@ function runTests() {
}
describe('Custom Resolver Tests', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
describe('SSR Custom Loader Tests', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
browser = null
afterAll(() => killApp(app))
describe('SSR Custom Loader Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
})
afterAll(async () => {
browser = null
})
runTests()
})
runTests()
})
describe('Client-side Custom Loader Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser
.elementByCss('#clientlink')
.click()
.waitForElementByCss('#client-side')
describe('Client-side Custom Loader Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser
.elementByCss('#clientlink')
.click()
.waitForElementByCss('#client-side')
})
afterAll(async () => {
browser = null
})
runTests()
})
afterAll(async () => {
browser = null
})
runTests()
})
})

View file

@ -25,16 +25,18 @@ function runTests() {
}
describe('Image Component Tests In Prod Mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
runTests()
runTests()
})
})
describe('Image Component Tests In Dev Mode', () => {

View file

@ -15,57 +15,59 @@ let app
let browser
describe('Image Component No IntersectionObserver test', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
describe('SSR Lazy Loading Tests', () => {
it('should automatically load images if observer does not exist', async () => {
browser = await webdriver(appPort, '/no-observer')
// Make sure the IntersectionObserver is mocked to null during the test
await check(() => {
return browser.eval('IntersectionObserver')
}, /null/)
expect(
await browser.elementById('lazy-no-observer').getAttribute('src')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000'
)
expect(
await browser.elementById('lazy-no-observer').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000 2x'
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
})
afterAll(() => killApp(app))
describe('Client-side Lazy Loading Tests', () => {
it('should automatically load images if observer does not exist', async () => {
browser = await webdriver(appPort, '/')
describe('SSR Lazy Loading Tests', () => {
it('should automatically load images if observer does not exist', async () => {
browser = await webdriver(appPort, '/no-observer')
// Make sure the IntersectionObserver is mocked to null during the test
await check(() => {
return browser.eval('IntersectionObserver')
}, /null/)
// Make sure the IntersectionObserver is mocked to null during the test
await check(() => {
return browser.eval('IntersectionObserver')
}, /null/)
await browser.waitForElementByCss('#link-no-observer').click()
expect(
await browser.elementById('lazy-no-observer').getAttribute('src')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000'
)
expect(
await browser.elementById('lazy-no-observer').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000 2x'
)
})
})
await waitFor(1000)
expect(
await browser.elementById('lazy-no-observer').getAttribute('src')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000'
)
expect(
await browser.elementById('lazy-no-observer').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000 2x'
)
describe('Client-side Lazy Loading Tests', () => {
it('should automatically load images if observer does not exist', async () => {
browser = await webdriver(appPort, '/')
// Make sure the IntersectionObserver is mocked to null during the test
await check(() => {
return browser.eval('IntersectionObserver')
}, /null/)
await browser.waitForElementByCss('#link-no-observer').click()
await waitFor(1000)
expect(
await browser.elementById('lazy-no-observer').getAttribute('src')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000'
)
expect(
await browser.elementById('lazy-no-observer').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/foox.jpg?auto=format&fit=max&w=2000 2x'
)
})
})
})
})

View file

@ -15,28 +15,30 @@ let appPort
let app
describe('Noscript Tests', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
describe('Noscript page source tests', () => {
it('should use local API for noscript img#basic-image src attribute', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
expect($('noscript > img#basic-image').attr('src')).toMatch(
/^\/_next\/image/
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
it('should use loader url for noscript img#image-with-loader src attribute', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
afterAll(() => killApp(app))
describe('Noscript page source tests', () => {
it('should use local API for noscript img#basic-image src attribute', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
expect($('noscript > img#image-with-loader').attr('src')).toMatch(
/^https:\/\/customresolver.com/
)
expect($('noscript > img#basic-image').attr('src')).toMatch(
/^\/_next\/image/
)
})
it('should use loader url for noscript img#image-with-loader src attribute', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
expect($('noscript > img#image-with-loader').attr('src')).toMatch(
/^https:\/\/customresolver.com/
)
})
})
})
})

View file

@ -15,7 +15,7 @@ let app
let devOutput
describe('svgo-webpack with Image Component', () => {
describe('next build', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should not fail to build invalid usage of the Image component', async () => {
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toBeFalsy()
@ -23,7 +23,7 @@ describe('svgo-webpack with Image Component', () => {
})
})
describe('next dev', () => {
describe('development mode', () => {
beforeAll(async () => {
devOutput = { stdout: '', stderr: '' }
appPort = await findPort()

View file

@ -21,7 +21,7 @@ const handleOutput = (msg) => {
}
describe('TypeScript Image Component', () => {
describe('next build', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should fail to build invalid usage of the Image component', async () => {
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toMatch(/Failed to compile/)
@ -47,7 +47,7 @@ describe('TypeScript Image Component', () => {
})
})
describe('next dev', () => {
describe('development mode', () => {
beforeAll(async () => {
output = ''
appPort = await findPort()
@ -75,16 +75,18 @@ describe('TypeScript Image Component', () => {
})
})
it('should remove global image types when disabled (dev)', async () => {
const content = await fs.readFile(nextConfig, 'utf8')
await fs.writeFile(
nextConfig,
content.replace('// disableStaticImages', 'disableStaticImages')
)
const app = await launchApp(appDir, await findPort())
await killApp(app)
await fs.writeFile(nextConfig, content)
const envTypes = await fs.readFile(join(appDir, 'next-env.d.ts'), 'utf8')
expect(envTypes).not.toContain('image-types/global')
describe('development mode 2', () => {
it('should remove global image types when disabled (dev)', async () => {
const content = await fs.readFile(nextConfig, 'utf8')
await fs.writeFile(
nextConfig,
content.replace('// disableStaticImages', 'disableStaticImages')
)
const app = await launchApp(appDir, await findPort())
await killApp(app)
await fs.writeFile(nextConfig, content)
const envTypes = await fs.readFile(join(appDir, 'next-env.d.ts'), 'utf8')
expect(envTypes).not.toContain('image-types/global')
})
})
})

View file

@ -15,7 +15,7 @@ let app
let devOutput
describe('svgo-webpack with Image Component', () => {
describe('next build', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should not fail to build invalid usage of the Image component', async () => {
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
const errors = stderr
@ -26,7 +26,7 @@ describe('svgo-webpack with Image Component', () => {
})
})
describe('next dev', () => {
describe('development mode', () => {
beforeAll(async () => {
devOutput = { stdout: '', stderr: '' }
appPort = await findPort()

View file

@ -21,7 +21,7 @@ const handleOutput = (msg) => {
}
describe('TypeScript Image Component', () => {
describe('next build', () => {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should fail to build invalid usage of the Image component', async () => {
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
expect(stderr).toMatch(/Failed to compile/)
@ -47,7 +47,7 @@ describe('TypeScript Image Component', () => {
})
})
describe('next dev', () => {
describe('development mode', () => {
beforeAll(async () => {
output = ''
appPort = await findPort()

View file

@ -5,12 +5,14 @@ import { nextBuild, readNextBuildServerPageFile } from 'next-test-utils'
const appDir = path.join(__dirname, '../app')
describe('Non-Next externalization', () => {
beforeAll(async () => {
await nextBuild(appDir)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
})
it('Externalized non-Next dist-using package', async () => {
const content = readNextBuildServerPageFile(appDir, '/')
expect(content).not.toContain('BrokenExternalMarker')
it('Externalized non-Next dist-using package', async () => {
const content = readNextBuildServerPageFile(appDir, '/')
expect(content).not.toContain('BrokenExternalMarker')
})
})
})

View file

@ -79,28 +79,56 @@ describe('Non-Standard NODE_ENV', () => {
await killApp(app)
expect(output).not.toContain(warningText)
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should still DCE NODE_ENV specific code', async () => {
await nextBuild(appDir, undefined, {
env: {
NODE_ENV: 'test',
},
})
it('should still DCE NODE_ENV specific code', async () => {
await nextBuild(appDir, undefined, {
env: {
NODE_ENV: 'test',
},
})
const staticFiles = glob.sync('**/*.js', {
cwd: join(appDir, '.next/static'),
})
expect(staticFiles.length).toBeGreaterThan(0)
const staticFiles = glob.sync('**/*.js', {
cwd: join(appDir, '.next/static'),
})
expect(staticFiles.length).toBeGreaterThan(0)
for (const file of staticFiles) {
const content = await fs.readFile(
join(appDir, '.next/static', file),
'utf8'
)
if (content.match(/cannot find module(?! for page)/i)) {
throw new Error(`${file} contains module not found error`)
for (const file of staticFiles) {
const content = await fs.readFile(
join(appDir, '.next/static', file),
'utf8'
)
if (content.match(/cannot find module(?! for page)/i)) {
throw new Error(`${file} contains module not found error`)
}
}
}
})
it('should show the warning with NODE_ENV set to development with next build', async () => {
const { stderr } = await nextBuild(appDir, [], {
env: {
NODE_ENV: 'development',
},
stderr: true,
})
expect(stderr).toContain(warningText)
})
it('should show the warning with NODE_ENV set to development with next start', async () => {
let output = ''
await nextBuild(appDir)
app = await nextStart(appDir, await findPort(), {
env: {
NODE_ENV: 'development',
},
onStderr(msg) {
output += msg || ''
},
})
await waitFor(2000)
await killApp(app)
expect(output).toContain(warningText)
})
})
it('should show the warning with NODE_ENV set to invalid value', async () => {
@ -152,31 +180,4 @@ describe('Non-Standard NODE_ENV', () => {
await killApp(app)
expect(output).toContain(warningText)
})
it('should show the warning with NODE_ENV set to development with next build', async () => {
const { stderr } = await nextBuild(appDir, [], {
env: {
NODE_ENV: 'development',
},
stderr: true,
})
expect(stderr).toContain(warningText)
})
it('should show the warning with NODE_ENV set to development with next start', async () => {
let output = ''
await nextBuild(appDir)
app = await nextStart(appDir, await findPort(), {
env: {
NODE_ENV: 'development',
},
onStderr(msg) {
output += msg || ''
},
})
await waitFor(2000)
await killApp(app)
expect(output).toContain(warningText)
})
})

View file

@ -6,15 +6,17 @@ import { join } from 'path'
const appDir = join(__dirname, '../')
describe('Numeric Separator Support', () => {
it('should successfully build for a JavaScript file', async () => {
const { code, stdout, stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should successfully build for a JavaScript file', async () => {
const { code, stdout, stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
expect(code).toBe(0)
expect(stdout).toContain('Compiled successfully')
expect(stderr).not.toContain('Failed to compile')
})
expect(code).toBe(0)
expect(stdout).toContain('Compiled successfully')
expect(stderr).not.toContain('Failed to compile')
})
})

View file

@ -17,85 +17,87 @@ async function uncommentExport(page) {
}
describe('Page Config', () => {
it('builds without error when export const config is used outside page', async () => {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).not.toMatch(/Failed to compile\./)
})
it('shows valid error when page config is a string', async () => {
const reset = await uncommentExport('invalid/string-config.js')
try {
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('builds without error when export const config is used outside page', async () => {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
expect(stderr).not.toMatch(/Failed to compile\./)
})
it('shows valid error when page config has no init', async () => {
const reset = await uncommentExport('invalid/no-init.js')
it('shows valid error when page config is a string', async () => {
const reset = await uncommentExport('invalid/string-config.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
it('shows error when page config has spread properties', async () => {
const reset = await uncommentExport('invalid/spread-config.js')
it('shows valid error when page config has no init', async () => {
const reset = await uncommentExport('invalid/no-init.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
it('shows error when page config has invalid properties', async () => {
const reset = await uncommentExport('invalid/invalid-property.js')
it('shows error when page config has spread properties', async () => {
const reset = await uncommentExport('invalid/spread-config.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
it('shows error when page config has invalid property value', async () => {
const reset = await uncommentExport('invalid/invalid-value.js')
it('shows error when page config has invalid properties', async () => {
const reset = await uncommentExport('invalid/invalid-property.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
it('shows error when page config is export from', async () => {
const reset = await uncommentExport('invalid/export-from.js')
it('shows error when page config has invalid property value', async () => {
const reset = await uncommentExport('invalid/invalid-value.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
it('shows error when page config is imported and exported', async () => {
const reset = await uncommentExport('invalid/import-export.js')
it('shows error when page config is export from', async () => {
const reset = await uncommentExport('invalid/export-from.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
it('shows error when page config is imported and exported', async () => {
const reset = await uncommentExport('invalid/import-export.js')
try {
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(/\/invalid-page-config/)
} finally {
await reset()
}
})
})
})

View file

@ -5,7 +5,7 @@ import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils'
const context = {}
describe('Configuration', () => {
describe('MDX-rs Configuration', () => {
beforeAll(async () => {
context.appPort = await findPort()
context.server = await launchApp(join(__dirname, '../'), context.appPort)

View file

@ -22,79 +22,435 @@ let proxyServer
let nextDataRequests = []
describe('Prefetching Links in viewport', () => {
beforeAll(async () => {
await nextBuild(appDir)
const port = await findPort()
app = await nextStart(appDir, port)
appPort = await findPort()
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
const port = await findPort()
app = await nextStart(appDir, port)
appPort = await findPort()
const proxy = httpProxy.createProxyServer({
target: `http://localhost:${port}`,
const proxy = httpProxy.createProxyServer({
target: `http://localhost:${port}`,
})
proxyServer = http.createServer(async (req, res) => {
if (stallJs && req.url.includes('chunks/pages/another')) {
console.log('stalling request for', req.url)
await new Promise((resolve) => setTimeout(resolve, 5 * 1000))
}
if (req.url.startsWith('/_next/data')) {
nextDataRequests.push(req.url)
}
proxy.web(req, res)
})
proxy.on('error', (err) => {
console.warn('Failed to proxy', err)
})
await new Promise((resolve) => {
proxyServer.listen(appPort, () => resolve())
})
})
afterAll(async () => {
await killApp(app)
proxyServer.close()
})
proxyServer = http.createServer(async (req, res) => {
if (stallJs && req.url.includes('chunks/pages/another')) {
console.log('stalling request for', req.url)
await new Promise((resolve) => setTimeout(resolve, 5 * 1000))
it('should de-dupe inflight SSG requests', async () => {
nextDataRequests = []
const browser = await webdriver(appPort, '/')
await browser.eval(function navigate() {
window.next.router.push('/ssg/slow')
window.next.router.push('/ssg/slow')
window.next.router.push('/ssg/slow')
})
await browser.waitForElementByCss('#content')
expect(
nextDataRequests.filter((reqUrl) => reqUrl.includes('/ssg/slow.json'))
.length
).toBe(2)
})
it('should handle timed out prefetch correctly', async () => {
try {
stallJs = true
const browser = await webdriver(appPort, '/')
await browser.elementByCss('#scroll-to-another').click()
// wait for preload to timeout
await waitFor(6 * 1000)
await browser
.elementByCss('#link-another')
.click()
.waitForElementByCss('#another')
expect(await browser.elementByCss('#another').text()).toBe(
'Hello world'
)
} finally {
stallJs = false
}
if (req.url.startsWith('/_next/data')) {
nextDataRequests.push(req.url)
})
it('should prefetch with link in viewport onload', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await check(async () => {
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('first')) {
found = true
break
}
}
expect(found).toBe(true)
return 'success'
}, 'success')
} finally {
if (browser) await browser.close()
}
proxy.web(req, res)
})
proxy.on('error', (err) => {
console.warn('Failed to proxy', err)
it('should prefetch with non-bot UA', async () => {
let browser
try {
browser = await webdriver(
appPort,
`/bot-user-agent?useragent=${encodeURIComponent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
)}`
)
const links = await browser.elementsByCss('link[rel=prefetch]')
expect(links).toHaveLength(1)
} finally {
if (browser) await browser.close()
}
})
await new Promise((resolve) => {
proxyServer.listen(appPort, () => resolve())
it('should not prefetch with bot UA', async () => {
let browser
try {
browser = await webdriver(
appPort,
`/bot-user-agent?useragent=${encodeURIComponent(
'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
)}`
)
const links = await browser.elementsByCss('link[rel=prefetch]')
expect(links).toHaveLength(0)
} finally {
if (browser) await browser.close()
}
})
})
afterAll(async () => {
await killApp(app)
proxyServer.close()
})
it('should de-dupe inflight SSG requests', async () => {
nextDataRequests = []
const browser = await webdriver(appPort, '/')
await browser.eval(function navigate() {
window.next.router.push('/ssg/slow')
window.next.router.push('/ssg/slow')
window.next.router.push('/ssg/slow')
it('should prefetch rewritten href with link in viewport onload', async () => {
let browser
try {
browser = await webdriver(appPort, '/rewrite-prefetch')
await check(async () => {
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('%5Bslug%5D')) {
found = true
break
}
}
expect(found).toBe(true)
return 'success'
}, 'success')
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/dynamic/one.json',
])
} finally {
if (browser) await browser.close()
}
})
await browser.waitForElementByCss('#content')
expect(
nextDataRequests.filter((reqUrl) => reqUrl.includes('/ssg/slow.json'))
.length
).toBe(2)
})
it('should handle timed out prefetch correctly', async () => {
try {
stallJs = true
it('should prefetch with link in viewport when href changes', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await browser.elementByCss('button').click()
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let foundFirst = false
let foundAnother = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) foundAnother = true
if (href.includes('first')) foundFirst = true
}
expect(foundFirst).toBe(true)
expect(foundAnother).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should prefetch with link in viewport on scroll', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await browser.elementByCss('#scroll-to-another').click()
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
found = true
break
}
}
expect(found).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should prefetch with link in viewport and inject script on hover', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await browser.elementByCss('#scroll-to-another').click()
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let foundLink = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
foundLink = true
break
}
}
expect(foundLink).toBe(true)
await browser.elementByCss('#link-another').moveTo()
await waitFor(2 * 1000)
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('another')) {
scriptFound = true
break
}
}
expect(scriptFound).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should inject script on hover with prefetching disabled', async () => {
let browser
try {
browser = await webdriver(appPort, '/prefetch-disabled')
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let foundLink = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
foundLink = true
break
}
}
expect(foundLink).toBe(false)
async function hasAnotherScript() {
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('another')) {
scriptFound = true
break
}
}
return scriptFound
}
expect(await hasAnotherScript()).toBe(false)
await browser.elementByCss('#link-another').moveTo()
await waitFor(2 * 1000)
expect(await hasAnotherScript()).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should inject script on hover with prefetching disabled and fetch data', async () => {
let browser
try {
browser = await webdriver(appPort, '/prefetch-disabled-ssg')
async function hasSsgScript() {
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('basic')) {
scriptFound = true
break
}
}
return scriptFound
}
await waitFor(2 * 1000)
expect(await hasSsgScript()).toBe(false)
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([])
await browser.elementByCss('#link-ssg').moveTo()
await waitFor(2 * 1000)
expect(await hasSsgScript()).toBe(true)
const hrefs2 = await browser.eval(`Object.keys(window.next.router.sdc)`)
expect(hrefs2.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/basic.json',
])
} finally {
if (browser) await browser.close()
}
})
it('should inject a <script> tag when onMouseEnter (even with invalid ref)', async () => {
let browser
try {
browser = await webdriver(appPort, '/invalid-ref')
await browser.elementByCss('#btn-link').moveTo()
await waitFor(2 * 1000)
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('another')) {
scriptFound = true
break
}
}
expect(scriptFound).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should not have unhandledRejection when failing to prefetch on link', async () => {
const browser = await webdriver(appPort, '/')
await browser.eval(`(function() {
window.addEventListener('unhandledrejection', function (err) {
window.hadUnhandledReject = true;
})
window.next.router.push('/invalid-prefetch');
})()`)
expect(await browser.eval('window.hadUnhandledReject')).toBeFalsy()
await browser.waitForElementByCss('#invalid-link')
await browser.elementByCss('#invalid-link').moveTo()
expect(await browser.eval('window.hadUnhandledReject')).toBeFalsy()
})
it('should not prefetch when prefetch is explicitly set to false', async () => {
const browser = await webdriver(appPort, '/opt-out')
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
found = true
break
}
}
expect(found).toBe(false)
})
it('should not prefetch already loaded scripts', async () => {
const browser = await webdriver(appPort, '/')
await browser.elementByCss('#scroll-to-another').click()
// wait for preload to timeout
await waitFor(6 * 1000)
const scriptSrcs = await browser.eval(`(function() {
return Array.from(document.querySelectorAll('script'))
.map(function(el) {
return el.src && new URL(el.src).pathname
}).filter(Boolean)
})()`)
await browser
.elementByCss('#link-another')
.click()
.waitForElementByCss('#another')
await browser.eval('next.router.prefetch("/")')
expect(await browser.elementByCss('#another').text()).toBe('Hello world')
} finally {
stallJs = false
}
})
const linkHrefs = await browser.eval(`(function() {
return Array.from(document.querySelectorAll('link'))
.map(function(el) {
return el.href && new URL(el.href).pathname
}).filter(Boolean)
})()`)
it('should prefetch with link in viewport onload', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
console.log({ linkHrefs, scriptSrcs })
expect(scriptSrcs.some((src) => src.includes('pages/index-'))).toBe(true)
expect(linkHrefs.some((href) => href.includes('pages/index-'))).toBe(
false
)
})
it('should not duplicate prefetches', async () => {
const browser = await webdriver(appPort, '/multi-prefetch')
const links = await browser.elementsByCss('link[rel=prefetch]')
const hrefs = []
for (const link of links) {
const href = await link.getAttribute('href')
hrefs.push(href)
}
hrefs.sort()
// Ensure no duplicates
expect(hrefs).toEqual([...new Set(hrefs)])
// Verify encoding
expect(hrefs.some((e) => e.includes(`%5Bhello%5D-`))).toBe(true)
})
it('should not re-prefetch for an already prefetched page', async () => {
// info: both `/` and `/de-duped` ref the `/first` page, which we don't
// want to be re-fetched/re-observed.
const browser = await webdriver(appPort, '/')
await check(async () => {
const links = await browser.elementsByCss('link[rel=prefetch]')
@ -110,359 +466,8 @@ describe('Prefetching Links in viewport', () => {
expect(found).toBe(true)
return 'success'
}, 'success')
} finally {
if (browser) await browser.close()
}
})
it('should prefetch with non-bot UA', async () => {
let browser
try {
browser = await webdriver(
appPort,
`/bot-user-agent?useragent=${encodeURIComponent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
)}`
)
const links = await browser.elementsByCss('link[rel=prefetch]')
expect(links).toHaveLength(1)
} finally {
if (browser) await browser.close()
}
})
it('should not prefetch with bot UA', async () => {
let browser
try {
browser = await webdriver(
appPort,
`/bot-user-agent?useragent=${encodeURIComponent(
'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
)}`
)
const links = await browser.elementsByCss('link[rel=prefetch]')
expect(links).toHaveLength(0)
} finally {
if (browser) await browser.close()
}
})
it('should prefetch rewritten href with link in viewport onload', async () => {
let browser
try {
browser = await webdriver(appPort, '/rewrite-prefetch')
await check(async () => {
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('%5Bslug%5D')) {
found = true
break
}
}
expect(found).toBe(true)
return 'success'
}, 'success')
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/dynamic/one.json',
])
} finally {
if (browser) await browser.close()
}
})
it('should prefetch with link in viewport when href changes', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await browser.elementByCss('button').click()
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let foundFirst = false
let foundAnother = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) foundAnother = true
if (href.includes('first')) foundFirst = true
}
expect(foundFirst).toBe(true)
expect(foundAnother).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should prefetch with link in viewport on scroll', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await browser.elementByCss('#scroll-to-another').click()
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
found = true
break
}
}
expect(found).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should prefetch with link in viewport and inject script on hover', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await browser.elementByCss('#scroll-to-another').click()
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let foundLink = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
foundLink = true
break
}
}
expect(foundLink).toBe(true)
await browser.elementByCss('#link-another').moveTo()
await waitFor(2 * 1000)
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('another')) {
scriptFound = true
break
}
}
expect(scriptFound).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should inject script on hover with prefetching disabled', async () => {
let browser
try {
browser = await webdriver(appPort, '/prefetch-disabled')
await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let foundLink = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
foundLink = true
break
}
}
expect(foundLink).toBe(false)
async function hasAnotherScript() {
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('another')) {
scriptFound = true
break
}
}
return scriptFound
}
expect(await hasAnotherScript()).toBe(false)
await browser.elementByCss('#link-another').moveTo()
await waitFor(2 * 1000)
expect(await hasAnotherScript()).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should inject script on hover with prefetching disabled and fetch data', async () => {
let browser
try {
browser = await webdriver(appPort, '/prefetch-disabled-ssg')
async function hasSsgScript() {
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('basic')) {
scriptFound = true
break
}
}
return scriptFound
}
await waitFor(2 * 1000)
expect(await hasSsgScript()).toBe(false)
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([])
await browser.elementByCss('#link-ssg').moveTo()
await waitFor(2 * 1000)
expect(await hasSsgScript()).toBe(true)
const hrefs2 = await browser.eval(`Object.keys(window.next.router.sdc)`)
expect(hrefs2.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/basic.json',
])
} finally {
if (browser) await browser.close()
}
})
it('should inject a <script> tag when onMouseEnter (even with invalid ref)', async () => {
let browser
try {
browser = await webdriver(appPort, '/invalid-ref')
await browser.elementByCss('#btn-link').moveTo()
await waitFor(2 * 1000)
const scripts = await browser.elementsByCss(
// Mouse hover is a high-priority fetch
'script:not([async])'
)
let scriptFound = false
for (const aScript of scripts) {
const href = await aScript.getAttribute('src')
if (href.includes('another')) {
scriptFound = true
break
}
}
expect(scriptFound).toBe(true)
} finally {
if (browser) await browser.close()
}
})
it('should not have unhandledRejection when failing to prefetch on link', async () => {
const browser = await webdriver(appPort, '/')
await browser.eval(`(function() {
window.addEventListener('unhandledrejection', function (err) {
window.hadUnhandledReject = true;
})
window.next.router.push('/invalid-prefetch');
})()`)
expect(await browser.eval('window.hadUnhandledReject')).toBeFalsy()
await browser.waitForElementByCss('#invalid-link')
await browser.elementByCss('#invalid-link').moveTo()
expect(await browser.eval('window.hadUnhandledReject')).toBeFalsy()
})
it('should not prefetch when prefetch is explicitly set to false', async () => {
const browser = await webdriver(appPort, '/opt-out')
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('another')) {
found = true
break
}
}
expect(found).toBe(false)
})
it('should not prefetch already loaded scripts', async () => {
const browser = await webdriver(appPort, '/')
const scriptSrcs = await browser.eval(`(function() {
return Array.from(document.querySelectorAll('script'))
.map(function(el) {
return el.src && new URL(el.src).pathname
}).filter(Boolean)
})()`)
await browser.eval('next.router.prefetch("/")')
const linkHrefs = await browser.eval(`(function() {
return Array.from(document.querySelectorAll('link'))
.map(function(el) {
return el.href && new URL(el.href).pathname
}).filter(Boolean)
})()`)
console.log({ linkHrefs, scriptSrcs })
expect(scriptSrcs.some((src) => src.includes('pages/index-'))).toBe(true)
expect(linkHrefs.some((href) => href.includes('pages/index-'))).toBe(false)
})
it('should not duplicate prefetches', async () => {
const browser = await webdriver(appPort, '/multi-prefetch')
const links = await browser.elementsByCss('link[rel=prefetch]')
const hrefs = []
for (const link of links) {
const href = await link.getAttribute('href')
hrefs.push(href)
}
hrefs.sort()
// Ensure no duplicates
expect(hrefs).toEqual([...new Set(hrefs)])
// Verify encoding
expect(hrefs.some((e) => e.includes(`%5Bhello%5D-`))).toBe(true)
})
it('should not re-prefetch for an already prefetched page', async () => {
// info: both `/` and `/de-duped` ref the `/first` page, which we don't
// want to be re-fetched/re-observed.
const browser = await webdriver(appPort, '/')
await check(async () => {
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
for (const link of links) {
const href = await link.getAttribute('href')
if (href.includes('first')) {
found = true
break
}
}
expect(found).toBe(true)
return 'success'
}, 'success')
await browser.eval(`(function() {
await browser.eval(`(function() {
window.calledPrefetch = false
window.next.router.prefetch = function() {
window.calledPrefetch = true
@ -470,19 +475,19 @@ describe('Prefetching Links in viewport', () => {
}
window.next.router.push('/de-duped')
})()`)
await check(
() => browser.eval('document.documentElement.innerHTML'),
/to \/first/
)
const calledPrefetch = await browser.eval(`window.calledPrefetch`)
expect(calledPrefetch).toBe(false)
})
await check(
() => browser.eval('document.documentElement.innerHTML'),
/to \/first/
)
const calledPrefetch = await browser.eval(`window.calledPrefetch`)
expect(calledPrefetch).toBe(false)
})
it('should prefetch with a different asPath for a prefetched page', async () => {
// info: both `/` and `/not-de-duped` ref the `/first` page, which we want
// to see prefetched twice.
const browser = await webdriver(appPort, '/')
await browser.eval(`(function() {
it('should prefetch with a different asPath for a prefetched page', async () => {
// info: both `/` and `/not-de-duped` ref the `/first` page, which we want
// to see prefetched twice.
const browser = await webdriver(appPort, '/')
await browser.eval(`(function() {
window.calledPrefetch = false
window.next.router.prefetch = function() {
window.calledPrefetch = true
@ -490,21 +495,21 @@ describe('Prefetching Links in viewport', () => {
}
window.next.router.push('/not-de-duped')
})()`)
await waitFor(2 * 1000)
const calledPrefetch = await browser.eval(`window.calledPrefetch`)
expect(calledPrefetch).toBe(true)
})
await waitFor(2 * 1000)
const calledPrefetch = await browser.eval(`window.calledPrefetch`)
expect(calledPrefetch).toBe(true)
})
it('should correctly omit pre-generated dynamic pages from SSG manifest', async () => {
const content = await readFile(
join(appDir, '.next', 'static', 'test-build', '_ssgManifest.js'),
'utf8'
)
it('should correctly omit pre-generated dynamic pages from SSG manifest', async () => {
const content = await readFile(
join(appDir, '.next', 'static', 'test-build', '_ssgManifest.js'),
'utf8'
)
let self = {}
// eslint-disable-next-line no-eval
eval(content)
expect([...self.__SSG_MANIFEST].sort()).toMatchInlineSnapshot(`
let self = {}
// eslint-disable-next-line no-eval
eval(content)
expect([...self.__SSG_MANIFEST].sort()).toMatchInlineSnapshot(`
Array [
"/[...rest]",
"/ssg/basic",
@ -514,44 +519,45 @@ describe('Prefetching Links in viewport', () => {
"/ssg/slow",
]
`)
})
})
it('should prefetch data files', async () => {
const browser = await webdriver(appPort, '/ssg/fixture')
await waitFor(2 * 1000) // wait for prefetching to occur
it('should prefetch data files', async () => {
const browser = await webdriver(appPort, '/ssg/fixture')
await waitFor(2 * 1000) // wait for prefetching to occur
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/basic.json',
'/_next/data/test-build/ssg/catch-all/foo.json',
'/_next/data/test-build/ssg/catch-all/foo/bar.json',
'/_next/data/test-build/ssg/catch-all/one.json',
'/_next/data/test-build/ssg/catch-all/one/two.json',
'/_next/data/test-build/ssg/dynamic-nested/foo/bar.json',
'/_next/data/test-build/ssg/dynamic-nested/one/two.json',
'/_next/data/test-build/ssg/dynamic/one.json',
'/_next/data/test-build/ssg/dynamic/two.json',
])
})
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/basic.json',
'/_next/data/test-build/ssg/catch-all/foo.json',
'/_next/data/test-build/ssg/catch-all/foo/bar.json',
'/_next/data/test-build/ssg/catch-all/one.json',
'/_next/data/test-build/ssg/catch-all/one/two.json',
'/_next/data/test-build/ssg/dynamic-nested/foo/bar.json',
'/_next/data/test-build/ssg/dynamic-nested/one/two.json',
'/_next/data/test-build/ssg/dynamic/one.json',
'/_next/data/test-build/ssg/dynamic/two.json',
])
})
it('should prefetch data files when mismatched', async () => {
const browser = await webdriver(appPort, '/ssg/fixture/mismatch')
await waitFor(2 * 1000) // wait for prefetching to occur
it('should prefetch data files when mismatched', async () => {
const browser = await webdriver(appPort, '/ssg/fixture/mismatch')
await waitFor(2 * 1000) // wait for prefetching to occur
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/catch-all/foo.json',
'/_next/data/test-build/ssg/catch-all/foo/bar.json',
'/_next/data/test-build/ssg/catch-all/one.json',
'/_next/data/test-build/ssg/catch-all/one/two.json',
'/_next/data/test-build/ssg/dynamic-nested/foo/bar.json',
'/_next/data/test-build/ssg/dynamic-nested/one/two.json',
'/_next/data/test-build/ssg/dynamic/one.json',
'/_next/data/test-build/ssg/dynamic/two.json',
])
expect(hrefs.map((href) => new URL(href).pathname)).toEqual([
'/_next/data/test-build/ssg/catch-all/foo.json',
'/_next/data/test-build/ssg/catch-all/foo/bar.json',
'/_next/data/test-build/ssg/catch-all/one.json',
'/_next/data/test-build/ssg/catch-all/one/two.json',
'/_next/data/test-build/ssg/dynamic-nested/foo/bar.json',
'/_next/data/test-build/ssg/dynamic-nested/one/two.json',
'/_next/data/test-build/ssg/dynamic/one.json',
'/_next/data/test-build/ssg/dynamic/two.json',
])
})
})
})

View file

@ -6,11 +6,13 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '..')
describe('Invalid Prerender Catchall Params', () => {
it('should fail the build', async () => {
const out = await nextBuild(appDir, [], { stderr: true })
expect(out.stderr).toMatch(`Build error occurred`)
expect(out.stderr).toMatch(
'A required parameter (slug) was not provided as an array received string in getStaticPaths for /[...slug]'
)
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
it('should fail the build', async () => {
const out = await nextBuild(appDir, [], { stderr: true })
expect(out.stderr).toMatch(`Build error occurred`)
expect(out.stderr).toMatch(
'A required parameter (slug) was not provided as an array received string in getStaticPaths for /[...slug]'
)
})
})
})

View file

@ -6,13 +6,15 @@ import { nextBuild } from 'next-test-utils'
const appDir = join(__dirname, '..')
describe('Legacy Prerender', () => {
describe('handles old getStaticParams', () => {
it('should fail the build', async () => {
const out = await nextBuild(appDir, [], { stderr: true })
expect(out.stderr).toMatch(`Build error occurred`)
expect(out.stderr).toMatch('Additional keys were returned from')
expect(out.stderr).toMatch('return { params: { foo: ..., post: ... } }')
expect(out.stderr).toMatch('Keys that need to be moved: foo, baz.')
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
describe('handles old getStaticParams', () => {
it('should fail the build', async () => {
const out = await nextBuild(appDir, [], { stderr: true })
expect(out.stderr).toMatch(`Build error occurred`)
expect(out.stderr).toMatch('Additional keys were returned from')
expect(out.stderr).toMatch('return { params: { foo: ..., post: ... } }')
expect(out.stderr).toMatch('Keys that need to be moved: foo, baz.')
})
})
})
})

View file

@ -96,22 +96,27 @@ describe('SSG Prerender Revalidate', () => {
// Regression test for https://github.com/vercel/next.js/issues/24806
describe('[regression] production mode and incremental cache size exceeded', () => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
await nextBuild(appDir, [])
appPort = await findPort()
app = await nextStart(appDir, appPort, {
// The lowest size of the LRU cache that can be set is "1"
// this will cause the cache size to always be exceeded
env: { __NEXT_TEST_MAX_ISR_CACHE: 1 },
})
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
afterAll(() => killApp(app))
;(process.env.TURBOPACK ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await fs.remove(join(appDir, '.next'))
await nextBuild(appDir, [])
appPort = await findPort()
app = await nextStart(appDir, appPort, {
// The lowest size of the LRU cache that can be set is "1"
// this will cause the cache size to always be exceeded
env: { __NEXT_TEST_MAX_ISR_CACHE: 1 },
})
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
afterAll(() => killApp(app))
runTests('/', '/')
runTests('/named', '/named')
runTests('/nested', '/nested')
runTests('/nested/named', '/nested/named')
runTests('/', '/')
runTests('/named', '/named')
runTests('/nested', '/nested')
runTests('/nested/named', '/nested/named')
}
)
})
})

View file

@ -265,35 +265,35 @@ describe('SSG Prerender', () => {
}
})
})
;(process.env.TURBOPACK ? describe.skip : describe)('production mode', () => {
describe('export mode', () => {
// disable fallback: true since this is an error during `next export`
const fallbackTruePages = [
'/blog/[post]/[comment].js',
'/user/[user]/profile.js',
'/catchall/[...slug].js',
'/non-json/[p].js',
'/blog/[post]/index.js',
'/fallback-only/[slug].js',
'/api-docs/[...slug].js',
]
const fallbackBlockingPages = [
'/blocking-fallback/[slug].js',
'/blocking-fallback-once/[slug].js',
'/blocking-fallback-some/[slug].js',
'/non-json-blocking/[p].js',
]
describe('export mode', () => {
// disable fallback: true since this is an error during `next export`
const fallbackTruePages = [
'/blog/[post]/[comment].js',
'/user/[user]/profile.js',
'/catchall/[...slug].js',
'/non-json/[p].js',
'/blog/[post]/index.js',
'/fallback-only/[slug].js',
'/api-docs/[...slug].js',
]
const fallbackBlockingPages = [
'/blocking-fallback/[slug].js',
'/blocking-fallback-once/[slug].js',
'/blocking-fallback-some/[slug].js',
'/non-json-blocking/[p].js',
]
const brokenPages = ['/bad-gssp.js', '/bad-ssr.js']
const brokenPages = ['/bad-gssp.js', '/bad-ssr.js']
const fallbackTruePageContents = {}
const fallbackBlockingPageContents = {}
const fallbackTruePageContents = {}
const fallbackBlockingPageContents = {}
beforeAll(async () => {
exportDir = join(appDir, 'out')
await fs.writeFile(
nextConfigPath,
`module.exports = {
beforeAll(async () => {
exportDir = join(appDir, 'out')
await fs.writeFile(
nextConfigPath,
`module.exports = {
exportTrailingSlash: true,
exportPathMap: function(defaultPathMap) {
if (defaultPathMap['/blog/[post]']) {
@ -302,82 +302,88 @@ describe('SSG Prerender', () => {
return defaultPathMap
},
}`
)
await fs.remove(join(appDir, '.next'))
for (const page of fallbackTruePages) {
const pagePath = join(appDir, 'pages', page)
fallbackTruePageContents[page] = await fs.readFile(pagePath, 'utf8')
await fs.writeFile(
pagePath,
fallbackTruePageContents[page].replace(
'fallback: true',
'fallback: false'
)
)
}
for (const page of fallbackBlockingPages) {
const pagePath = join(appDir, 'pages', page)
fallbackBlockingPageContents[page] = await fs.readFile(pagePath, 'utf8')
await fs.writeFile(
pagePath,
fallbackBlockingPageContents[page].replace(
"fallback: 'blocking'",
'fallback: false'
)
)
}
for (const page of brokenPages) {
const pagePath = join(appDir, 'pages', page)
await fs.rename(pagePath, `${pagePath}.bak`)
}
await nextBuild(appDir, undefined, { cwd: appDir })
await nextExport(appDir, { outdir: exportDir, cwd: appDir })
app = await startStaticServer(exportDir)
appPort = app.address().port
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
afterAll(async () => {
try {
stopApp(app)
await fs.remove(nextConfigPath)
await fs.remove(join(appDir, '.next'))
for (const page of fallbackTruePages) {
const pagePath = join(appDir, 'pages', page)
await fs.writeFile(pagePath, fallbackTruePageContents[page])
fallbackTruePageContents[page] = await fs.readFile(pagePath, 'utf8')
await fs.writeFile(
pagePath,
fallbackTruePageContents[page].replace(
'fallback: true',
'fallback: false'
)
)
}
for (const page of fallbackBlockingPages) {
const pagePath = join(appDir, 'pages', page)
await fs.writeFile(pagePath, fallbackBlockingPageContents[page])
fallbackBlockingPageContents[page] = await fs.readFile(
pagePath,
'utf8'
)
await fs.writeFile(
pagePath,
fallbackBlockingPageContents[page].replace(
"fallback: 'blocking'",
'fallback: false'
)
)
}
for (const page of brokenPages) {
const pagePath = join(appDir, 'pages', page)
await fs.rename(`${pagePath}.bak`, pagePath)
await fs.rename(pagePath, `${pagePath}.bak`)
}
} catch (err) {
console.error(err)
}
await nextBuild(appDir, undefined, { cwd: appDir })
await nextExport(appDir, { outdir: exportDir, cwd: appDir })
app = await startStaticServer(exportDir)
appPort = app.address().port
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
afterAll(async () => {
try {
stopApp(app)
await fs.remove(nextConfigPath)
for (const page of fallbackTruePages) {
const pagePath = join(appDir, 'pages', page)
await fs.writeFile(pagePath, fallbackTruePageContents[page])
}
for (const page of fallbackBlockingPages) {
const pagePath = join(appDir, 'pages', page)
await fs.writeFile(pagePath, fallbackBlockingPageContents[page])
}
for (const page of brokenPages) {
const pagePath = join(appDir, 'pages', page)
await fs.rename(`${pagePath}.bak`, pagePath)
}
} catch (err) {
console.error(err)
}
})
it('should copy prerender files and honor exportTrailingSlash', async () => {
const routes = [
'/another',
'/something',
'/blog/post-1',
'/blog/post-2/comment-2',
]
for (const route of routes) {
await fs.access(join(exportDir, `${route}/index.html`))
await fs.access(
join(exportDir, '_next/data', buildId, `${route}.json`)
)
}
})
navigateTest()
})
it('should copy prerender files and honor exportTrailingSlash', async () => {
const routes = [
'/another',
'/something',
'/blog/post-1',
'/blog/post-2/comment-2',
]
for (const route of routes) {
await fs.access(join(exportDir, `${route}/index.html`))
await fs.access(join(exportDir, '_next/data', buildId, `${route}.json`))
}
})
navigateTest()
})
})

Some files were not shown because too many files have changed in this diff Show more