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:
parent
ecd94c1a4d
commit
59bda2d818
127 changed files with 5849 additions and 5400 deletions
|
@ -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')
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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:\/\//
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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)"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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/
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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]')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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")')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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, [], {
|
||||
|
|
|
@ -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, [], {
|
||||
|
|
|
@ -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\./
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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('/')
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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/
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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]'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue