Migrate basic-basepath tests into basic (#44776)
Removes `test/development/basic-basepath` and migrates those tests into `test/development/basic` instead. Currently the tests are duplicated but `/basic-basepath` is just a subset of `/basic`. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
This commit is contained in:
parent
7fe9e035d8
commit
ee0f483159
49 changed files with 1210 additions and 2521 deletions
|
@ -1,704 +0,0 @@
|
|||
import { join } from 'path'
|
||||
import cheerio from 'cheerio'
|
||||
import webdriver from 'next-webdriver'
|
||||
import {
|
||||
check,
|
||||
getBrowserBodyText,
|
||||
getRedboxHeader,
|
||||
getRedboxSource,
|
||||
hasRedbox,
|
||||
renderViaHTTP,
|
||||
waitFor,
|
||||
} from 'next-test-utils'
|
||||
import { createNext, FileRef } from 'e2e-utils'
|
||||
import { NextInstance } from 'test/lib/next-modes/base'
|
||||
|
||||
describe('basic HMR', () => {
|
||||
let next: NextInstance
|
||||
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
pages: new FileRef(join(__dirname, 'hmr/pages')),
|
||||
components: new FileRef(join(__dirname, 'hmr/components')),
|
||||
},
|
||||
nextConfig: {
|
||||
basePath: '/docs',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
describe('Hot Module Reloading', () => {
|
||||
describe('delete a page and add it back', () => {
|
||||
it('should load the page properly', async () => {
|
||||
const contactPagePath = join('pages', 'hmr', 'contact.js')
|
||||
const newContactPagePath = join('pages', 'hmr', '_contact.js')
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/contact')
|
||||
const text = await browser.elementByCss('p').text()
|
||||
expect(text).toBe('This is the contact page.')
|
||||
|
||||
// Rename the file to mimic a deleted page
|
||||
await next.renameFile(contactPagePath, newContactPagePath)
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This page could not be found/
|
||||
)
|
||||
|
||||
// Rename the file back to the original filename
|
||||
await next.renameFile(newContactPagePath, contactPagePath)
|
||||
|
||||
// wait until the page comes back
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the contact page/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await next
|
||||
.renameFile(newContactPagePath, contactPagePath)
|
||||
.catch(() => {})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('editing a page', () => {
|
||||
it('should detect the changes and display it', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about')
|
||||
const text = await browser.elementByCss('p').text()
|
||||
expect(text).toBe('This is the about page.')
|
||||
|
||||
const aboutPagePath = join('pages', 'hmr', 'about.js')
|
||||
|
||||
const originalContent = await next.readFile(aboutPagePath)
|
||||
const editedContent = originalContent.replace(
|
||||
'This is the about page',
|
||||
'COOL page'
|
||||
)
|
||||
|
||||
// change the content
|
||||
try {
|
||||
await next.patchFile(aboutPagePath, editedContent)
|
||||
await check(() => getBrowserBodyText(browser), /COOL page/)
|
||||
} finally {
|
||||
// add the original content
|
||||
await next.patchFile(aboutPagePath, originalContent)
|
||||
}
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should not reload unrelated pages', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/counter')
|
||||
const text = await browser
|
||||
.elementByCss('button')
|
||||
.click()
|
||||
.elementByCss('button')
|
||||
.click()
|
||||
.elementByCss('p')
|
||||
.text()
|
||||
expect(text).toBe('COUNT: 2')
|
||||
|
||||
const aboutPagePath = join('pages', 'hmr', 'about.js')
|
||||
|
||||
const originalContent = await next.readFile(aboutPagePath)
|
||||
const editedContent = originalContent.replace(
|
||||
'This is the about page',
|
||||
'COOL page'
|
||||
)
|
||||
|
||||
try {
|
||||
// Change the about.js page
|
||||
await next.patchFile(aboutPagePath, editedContent)
|
||||
|
||||
// Check whether the this page has reloaded or not.
|
||||
await check(() => browser.elementByCss('p').text(), /COUNT: 2/)
|
||||
} finally {
|
||||
// restore the about page content.
|
||||
await next.patchFile(aboutPagePath, originalContent)
|
||||
}
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Added because of a regression in react-hot-loader, see issues: #4246 #4273
|
||||
// Also: https://github.com/vercel/styled-jsx/issues/425
|
||||
it('should update styles correctly', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/style')
|
||||
const pTag = await browser.elementByCss('.hmr-style-page p')
|
||||
const initialFontSize = await pTag.getComputedCss('font-size')
|
||||
|
||||
expect(initialFontSize).toBe('100px')
|
||||
|
||||
const pagePath = join('pages', 'hmr', 'style.js')
|
||||
|
||||
const originalContent = await next.readFile(pagePath)
|
||||
const editedContent = originalContent.replace('100px', '200px')
|
||||
|
||||
// Change the page
|
||||
await next.patchFile(pagePath, editedContent)
|
||||
|
||||
try {
|
||||
// Check whether the this page has reloaded or not.
|
||||
await check(async () => {
|
||||
const editedPTag = await browser.elementByCss('.hmr-style-page p')
|
||||
return editedPTag.getComputedCss('font-size')
|
||||
}, /200px/)
|
||||
} finally {
|
||||
// Finally is used so that we revert the content back to the original regardless of the test outcome
|
||||
// restore the about page content.
|
||||
await next.patchFile(pagePath, originalContent)
|
||||
}
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Added because of a regression in react-hot-loader, see issues: #4246 #4273
|
||||
// Also: https://github.com/vercel/styled-jsx/issues/425
|
||||
it('should update styles in a stateful component correctly', async () => {
|
||||
let browser
|
||||
const pagePath = join('pages', 'hmr', 'style-stateful-component.js')
|
||||
const originalContent = await next.readFile(pagePath)
|
||||
try {
|
||||
browser = await webdriver(
|
||||
next.url,
|
||||
'/docs/hmr/style-stateful-component'
|
||||
)
|
||||
const pTag = await browser.elementByCss('.hmr-style-page p')
|
||||
const initialFontSize = await pTag.getComputedCss('font-size')
|
||||
|
||||
expect(initialFontSize).toBe('100px')
|
||||
const editedContent = originalContent.replace('100px', '200px')
|
||||
|
||||
// Change the page
|
||||
await next.patchFile(pagePath, editedContent)
|
||||
|
||||
// Check whether the this page has reloaded or not.
|
||||
await check(async () => {
|
||||
const editedPTag = await browser.elementByCss('.hmr-style-page p')
|
||||
return editedPTag.getComputedCss('font-size')
|
||||
}, /200px/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await next.patchFile(pagePath, originalContent)
|
||||
}
|
||||
})
|
||||
|
||||
// Added because of a regression in react-hot-loader, see issues: #4246 #4273
|
||||
// Also: https://github.com/vercel/styled-jsx/issues/425
|
||||
it('should update styles in a dynamic component correctly', async () => {
|
||||
let browser = null
|
||||
let secondBrowser = null
|
||||
const pagePath = join('components', 'hmr', 'dynamic.js')
|
||||
const originalContent = await next.readFile(pagePath)
|
||||
try {
|
||||
browser = await webdriver(
|
||||
next.url,
|
||||
'/docs/hmr/style-dynamic-component'
|
||||
)
|
||||
const div = await browser.elementByCss('#dynamic-component')
|
||||
const initialClientClassName = await div.getAttribute('class')
|
||||
const initialFontSize = await div.getComputedCss('font-size')
|
||||
|
||||
expect(initialFontSize).toBe('100px')
|
||||
|
||||
const initialHtml = await renderViaHTTP(
|
||||
next.url,
|
||||
'/docs/hmr/style-dynamic-component'
|
||||
)
|
||||
expect(initialHtml.includes('100px')).toBeTruthy()
|
||||
|
||||
const $initialHtml = cheerio.load(initialHtml)
|
||||
const initialServerClassName =
|
||||
$initialHtml('#dynamic-component').attr('class')
|
||||
|
||||
expect(initialClientClassName === initialServerClassName).toBeTruthy()
|
||||
|
||||
const editedContent = originalContent.replace('100px', '200px')
|
||||
|
||||
// Change the page
|
||||
await next.patchFile(pagePath, editedContent)
|
||||
|
||||
// wait for 5 seconds
|
||||
await waitFor(5000)
|
||||
|
||||
secondBrowser = await webdriver(
|
||||
next.url,
|
||||
'/docs/hmr/style-dynamic-component'
|
||||
)
|
||||
// Check whether the this page has reloaded or not.
|
||||
const editedDiv = await secondBrowser.elementByCss(
|
||||
'#dynamic-component'
|
||||
)
|
||||
const editedClientClassName = await editedDiv.getAttribute('class')
|
||||
const editedFontSize = await editedDiv.getComputedCss('font-size')
|
||||
const browserHtml = await secondBrowser.eval(
|
||||
'document.documentElement.innerHTML'
|
||||
)
|
||||
|
||||
expect(editedFontSize).toBe('200px')
|
||||
expect(browserHtml.includes('font-size:200px')).toBe(true)
|
||||
expect(browserHtml.includes('font-size:100px')).toBe(false)
|
||||
|
||||
const editedHtml = await renderViaHTTP(
|
||||
next.url,
|
||||
'/docs/hmr/style-dynamic-component'
|
||||
)
|
||||
expect(editedHtml.includes('200px')).toBeTruthy()
|
||||
const $editedHtml = cheerio.load(editedHtml)
|
||||
const editedServerClassName =
|
||||
$editedHtml('#dynamic-component').attr('class')
|
||||
|
||||
expect(editedClientClassName === editedServerClassName).toBe(true)
|
||||
} finally {
|
||||
// Finally is used so that we revert the content back to the original regardless of the test outcome
|
||||
// restore the about page content.
|
||||
await next.patchFile(pagePath, originalContent)
|
||||
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
if (secondBrowser) {
|
||||
secondBrowser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Error Recovery', () => {
|
||||
it('should recover from 404 after a page has been added', async () => {
|
||||
let browser
|
||||
const newPage = join('pages', 'hmr', 'new-page.js')
|
||||
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/new-page')
|
||||
|
||||
expect(await browser.elementByCss('body').text()).toMatch(
|
||||
/This page could not be found/
|
||||
)
|
||||
|
||||
// Add the page
|
||||
await next.patchFile(
|
||||
newPage,
|
||||
'export default () => (<div id="new-page">the-new-page</div>)'
|
||||
)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /the-new-page/)
|
||||
|
||||
await next.deleteFile(newPage)
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This page could not be found/
|
||||
)
|
||||
} catch (err) {
|
||||
await next.deleteFile(newPage)
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should detect syntax errors and recover', async () => {
|
||||
let browser
|
||||
const aboutPage = join('pages', 'hmr', 'about2.js')
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about2')
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent.replace('</div>', 'div'))
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(/Unexpected eof/)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
} catch (err) {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should show the error on all pages', async () => {
|
||||
const aboutPage = join('pages', 'hmr', 'about2.js')
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
let browser
|
||||
try {
|
||||
await renderViaHTTP(next.url, '/docs/hmr/about2')
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent.replace('</div>', 'div'))
|
||||
|
||||
// Ensure dev server has time to break:
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
browser = await webdriver(next.url, '/docs/hmr/contact')
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(/Unexpected eof/)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the contact page/
|
||||
)
|
||||
} catch (err) {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the contact page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should detect runtime errors on the module scope', async () => {
|
||||
let browser
|
||||
const aboutPage = join('pages', 'hmr', 'about3.js')
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about3')
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
|
||||
await next.patchFile(
|
||||
aboutPage,
|
||||
aboutContent.replace('export', 'aa=20;\nexport')
|
||||
)
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatch(/aa is not defined/)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
} finally {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should recover from errors in the render function', async () => {
|
||||
let browser
|
||||
const aboutPage = join('pages', 'hmr', 'about4.js')
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about4')
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
|
||||
await next.patchFile(
|
||||
aboutPage,
|
||||
aboutContent.replace(
|
||||
'return',
|
||||
'throw new Error("an-expected-error");\nreturn'
|
||||
)
|
||||
)
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(/an-expected-error/)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
} catch (err) {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should recover after exporting an invalid page', async () => {
|
||||
let browser
|
||||
const aboutPage = join('pages', 'hmr', 'about5.js')
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about5')
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
|
||||
await next.patchFile(
|
||||
aboutPage,
|
||||
aboutContent.replace(
|
||||
'export default',
|
||||
'export default {};\nexport const fn ='
|
||||
)
|
||||
)
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
|
||||
" 1 of 1 unhandled error
|
||||
Server Error
|
||||
|
||||
Error: The default export is not a React Component in page: \\"/hmr/about5\\"
|
||||
|
||||
This error happened while generating the page. Any console logs will be displayed in the terminal window."
|
||||
`)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
} catch (err) {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should recover after a bad return from the render function', async () => {
|
||||
let browser
|
||||
const aboutPage = join('pages', 'hmr', 'about6.js')
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about6')
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
|
||||
await next.patchFile(
|
||||
aboutPage,
|
||||
aboutContent.replace(
|
||||
'export default',
|
||||
'export default () => /search/;\nexport const fn ='
|
||||
)
|
||||
)
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
// TODO: Replace this when webpack 5 is the default
|
||||
expect(await getRedboxHeader(browser)).toMatch(
|
||||
`Objects are not valid as a React child (found: [object RegExp]). If you meant to render a collection of children, use an array instead.`
|
||||
)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
} catch (err) {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should recover after undefined exported as default', async () => {
|
||||
let browser
|
||||
const aboutPage = join('pages', 'hmr', 'about7.js')
|
||||
|
||||
const aboutContent = await next.readFile(aboutPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/about7')
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
|
||||
await next.patchFile(
|
||||
aboutPage,
|
||||
aboutContent.replace(
|
||||
'export default',
|
||||
'export default undefined;\nexport const fn ='
|
||||
)
|
||||
)
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
|
||||
" 1 of 1 unhandled error
|
||||
Server Error
|
||||
|
||||
Error: The default export is not a React Component in page: \\"/hmr/about7\\"
|
||||
|
||||
This error happened while generating the page. Any console logs will be displayed in the terminal window."
|
||||
`)
|
||||
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
expect(await hasRedbox(browser, false)).toBe(false)
|
||||
} catch (err) {
|
||||
await next.patchFile(aboutPage, aboutContent)
|
||||
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should recover from errors in getInitialProps in client', async () => {
|
||||
let browser
|
||||
const erroredPage = join('pages', 'hmr', 'error-in-gip.js')
|
||||
const errorContent = await next.readFile(erroredPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr')
|
||||
await browser.elementByCss('#error-in-gip-link').click()
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
|
||||
" 1 of 1 unhandled error
|
||||
Unhandled Runtime Error
|
||||
|
||||
Error: an-expected-error-in-gip"
|
||||
`)
|
||||
|
||||
await next.patchFile(
|
||||
erroredPage,
|
||||
errorContent.replace('throw error', 'return {}')
|
||||
)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /Hello/)
|
||||
|
||||
await next.patchFile(erroredPage, errorContent)
|
||||
|
||||
await check(async () => {
|
||||
await browser.refresh()
|
||||
await waitFor(2000)
|
||||
const text = await browser.elementByCss('body').text()
|
||||
if (text.includes('Hello')) {
|
||||
throw new Error('waiting')
|
||||
}
|
||||
return getRedboxSource(browser)
|
||||
}, /an-expected-error-in-gip/)
|
||||
} catch (err) {
|
||||
await next.patchFile(erroredPage, errorContent)
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should recover after an error reported via SSR', async () => {
|
||||
let browser
|
||||
const erroredPage = join('pages', 'hmr', 'error-in-gip.js')
|
||||
const errorContent = await next.readFile(erroredPage)
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/hmr/error-in-gip')
|
||||
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
|
||||
" 1 of 1 unhandled error
|
||||
Server Error
|
||||
|
||||
Error: an-expected-error-in-gip
|
||||
|
||||
This error happened while generating the page. Any console logs will be displayed in the terminal window."
|
||||
`)
|
||||
|
||||
const erroredPage = join('pages', 'hmr', 'error-in-gip.js')
|
||||
|
||||
await next.patchFile(
|
||||
erroredPage,
|
||||
errorContent.replace('throw error', 'return {}')
|
||||
)
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /Hello/)
|
||||
|
||||
await next.patchFile(erroredPage, errorContent)
|
||||
|
||||
await check(async () => {
|
||||
await browser.refresh()
|
||||
await waitFor(2000)
|
||||
const text = await getBrowserBodyText(browser)
|
||||
if (text.includes('Hello')) {
|
||||
throw new Error('waiting')
|
||||
}
|
||||
return getRedboxSource(browser)
|
||||
}, /an-expected-error-in-gip/)
|
||||
} catch (err) {
|
||||
await next.patchFile(erroredPage, errorContent)
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,12 +0,0 @@
|
|||
export default () => {
|
||||
return (
|
||||
<div id="dynamic-component">
|
||||
Dynamic Component
|
||||
<style jsx>{`
|
||||
div {
|
||||
font-size: 100px;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default function About() {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default () => {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default function About2() {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default () => {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default function About4() {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default () => {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default () => {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default () => {
|
||||
return (
|
||||
<div className="hmr-about-page">
|
||||
<p>This is the about page.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export default () => (
|
||||
<div className="hmr-contact-page">
|
||||
<p>This is the contact page.</p>
|
||||
</div>
|
||||
)
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
export default class Counter extends React.Component {
|
||||
state = { count: 0 }
|
||||
|
||||
incr() {
|
||||
const { count } = this.state
|
||||
this.setState({ count: count + 1 })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<p>COUNT: {this.state.count}</p>
|
||||
<button onClick={() => this.incr()}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import React from 'react'
|
||||
export default class extends React.Component {
|
||||
static getInitialProps() {
|
||||
const error = new Error('an-expected-error-in-gip')
|
||||
throw error
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>Hello</div>
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Link href="/hmr/error-in-gip" id="error-in-gip-link">
|
||||
Bad Page
|
||||
</Link>
|
||||
</div>
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
import React from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const HmrDynamic = dynamic(import('../../components/hmr/dynamic'))
|
||||
|
||||
export default () => {
|
||||
return <HmrDynamic />
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
|
||||
export default class StyleStateFul extends Component {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="hmr-style-page">
|
||||
<p>
|
||||
This is the style page.
|
||||
<style jsx>{`
|
||||
p {
|
||||
font-size: 100px;
|
||||
}
|
||||
`}</style>
|
||||
</p>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import React from 'react'
|
||||
export default function Style() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="hmr-style-page">
|
||||
<p>
|
||||
This is the style page.
|
||||
<style jsx>{`
|
||||
p {
|
||||
font-size: 100px;
|
||||
}
|
||||
`}</style>
|
||||
</p>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
import url from 'url'
|
||||
import { join } from 'path'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { createNext, FileRef } from 'e2e-utils'
|
||||
import { NextInstance } from 'test/lib/next-modes/base'
|
||||
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
|
||||
|
||||
describe('misc basic dev tests', () => {
|
||||
let next: NextInstance
|
||||
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
pages: new FileRef(join(__dirname, 'misc/pages')),
|
||||
public: new FileRef(join(__dirname, 'misc/public')),
|
||||
},
|
||||
nextConfig: {
|
||||
basePath: '/docs',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
it('should set process.env.NODE_ENV in development', async () => {
|
||||
const browser = await webdriver(next.url, '/docs/process-env')
|
||||
const nodeEnv = await browser.elementByCss('#node-env').text()
|
||||
expect(nodeEnv).toBe('development')
|
||||
await browser.close()
|
||||
})
|
||||
|
||||
it('should allow access to public files', async () => {
|
||||
const data = await renderViaHTTP(next.url, '/docs/data/data.txt')
|
||||
expect(data).toBe('data')
|
||||
|
||||
const legacy = await renderViaHTTP(next.url, '/docs/static/legacy.txt')
|
||||
expect(legacy).toMatch(`new static folder`)
|
||||
})
|
||||
|
||||
describe('With Security Related Issues', () => {
|
||||
it('should not allow accessing files outside .next/static and .next/server directory', async () => {
|
||||
const pathsToCheck = [
|
||||
`/docs/_next/static/../BUILD_ID`,
|
||||
`/docs/_next/static/../routes-manifest.json`,
|
||||
]
|
||||
for (const path of pathsToCheck) {
|
||||
const res = await fetchViaHTTP(next.url, path)
|
||||
const text = await res.text()
|
||||
try {
|
||||
expect(res.status).toBe(404)
|
||||
expect(text).toMatch(/This page could not be found/)
|
||||
} catch (err) {
|
||||
throw new Error(`Path ${path} accessible from the browser`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle encoded / value for trailing slash correctly', async () => {
|
||||
const res = await fetchViaHTTP(
|
||||
next.url,
|
||||
'/docs/%2fexample.com/',
|
||||
undefined,
|
||||
{ redirect: 'manual' }
|
||||
)
|
||||
|
||||
const { pathname, hostname } = url.parse(
|
||||
res.headers.get('location') || ''
|
||||
)
|
||||
expect(res.status).toBe(308)
|
||||
expect(pathname).toBe('/docs/%2fexample.com')
|
||||
expect(hostname).not.toBe('example.com')
|
||||
const text = await res.text()
|
||||
expect(text).toEqual('/docs/%2fexample.com')
|
||||
})
|
||||
})
|
||||
|
||||
async function getLogs$(path) {
|
||||
let foundLog = false
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, path)
|
||||
const browserLogs = await browser.log('browser')
|
||||
|
||||
browserLogs.forEach((log) => {
|
||||
if (log.message.includes('Next.js auto-prefetches automatically')) {
|
||||
foundLog = true
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
return foundLog
|
||||
}
|
||||
describe('Development Logs', () => {
|
||||
it('should warn when prefetch is true', async () => {
|
||||
const foundLog = await getLogs$('/docs/development-logs')
|
||||
expect(foundLog).toBe(true)
|
||||
})
|
||||
it('should not warn when prefetch is false', async () => {
|
||||
const foundLog = await getLogs$(
|
||||
'/docs/development-logs/link-with-prefetch-false'
|
||||
)
|
||||
expect(foundLog).toBe(false)
|
||||
})
|
||||
it('should not warn when prefetch is not specified', async () => {
|
||||
const foundLog = await getLogs$(
|
||||
'/docs/development-logs/link-with-no-prefetch'
|
||||
)
|
||||
expect(foundLog).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export default () => <div className="about-page">About Page</div>
|
|
@ -1,11 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default function IndexPage() {
|
||||
return (
|
||||
<div>
|
||||
<Link href="/about" prefetch>
|
||||
To About Page
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default function NoPrefetchPage() {
|
||||
return (
|
||||
<div>
|
||||
<Link href="/about">No prefetch</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default function PrefetchFalsePage() {
|
||||
return (
|
||||
<div>
|
||||
<Link href="/about" prefetch={false}>
|
||||
Prefetch set to false
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export default () => <div id="node-env">{process.env.NODE_ENV}</div>
|
|
@ -1 +0,0 @@
|
|||
data
|
|
@ -1 +0,0 @@
|
|||
new static folder
|
|
@ -1,253 +0,0 @@
|
|||
import { join } from 'path'
|
||||
import cheerio from 'cheerio'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { createNext, FileRef } from 'e2e-utils'
|
||||
import { renderViaHTTP, check } from 'next-test-utils'
|
||||
import { NextInstance } from 'test/lib/next-modes/base'
|
||||
|
||||
describe('basic next/dynamic usage', () => {
|
||||
let next: NextInstance
|
||||
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
components: new FileRef(join(__dirname, 'next-dynamic/components')),
|
||||
pages: new FileRef(join(__dirname, 'next-dynamic/pages')),
|
||||
},
|
||||
nextConfig: {
|
||||
basePath: '/docs',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
async function get$(path, query?: any) {
|
||||
const html = await renderViaHTTP(next.url, path, query)
|
||||
return cheerio.load(html)
|
||||
}
|
||||
|
||||
describe('Dynamic import', () => {
|
||||
describe('default behavior', () => {
|
||||
it('should render dynamic import components', async () => {
|
||||
const $ = await get$('/docs/dynamic/ssr')
|
||||
// Make sure the client side knows it has to wait for the bundle
|
||||
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
|
||||
'dynamic/ssr.js -> ../../components/hello1'
|
||||
)
|
||||
expect($('body').text()).toMatch(/Hello World 1/)
|
||||
})
|
||||
|
||||
it('should render dynamic import components using a function as first parameter', async () => {
|
||||
const $ = await get$('/docs/dynamic/function')
|
||||
// Make sure the client side knows it has to wait for the bundle
|
||||
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
|
||||
'dynamic/function.js -> ../../components/hello1'
|
||||
)
|
||||
expect($('body').text()).toMatch(/Hello World 1/)
|
||||
})
|
||||
|
||||
it('should render even there are no physical chunk exists', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/no-chunk')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Welcome, normal/
|
||||
)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Welcome, dynamic/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should hydrate nested chunks', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/nested')
|
||||
await check(() => browser.elementByCss('body').text(), /Nested 1/)
|
||||
await check(() => browser.elementByCss('body').text(), /Nested 2/)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Browser hydrated/
|
||||
)
|
||||
|
||||
if ((global as any).browserName === 'chrome') {
|
||||
const logs = await browser.log('browser')
|
||||
|
||||
logs.forEach((logItem) => {
|
||||
expect(logItem.message).not.toMatch(
|
||||
/Expected server HTML to contain/
|
||||
)
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should render the component Head content', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/head')
|
||||
await check(() => browser.elementByCss('body').text(), /test/)
|
||||
const backgroundColor = await browser
|
||||
.elementByCss('.dynamic-style')
|
||||
.getComputedCss('background-color')
|
||||
const height = await browser
|
||||
.elementByCss('.dynamic-style')
|
||||
.getComputedCss('height')
|
||||
expect(height).toBe('200px')
|
||||
expect(backgroundColor).toMatch(/rgba?\(0, 128, 0/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('ssr:false option', () => {
|
||||
it('should not render loading on the server side', async () => {
|
||||
const $ = await get$('/docs/dynamic/no-ssr')
|
||||
expect($('body').html()).not.toContain('"dynamicIds"')
|
||||
expect($('body').text()).not.toMatch('loading...')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/no-ssr')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!(global as any).isNextDev) {
|
||||
it('should not include ssr:false imports to server trace', async () => {
|
||||
const trace = JSON.parse(
|
||||
await next.readFile('.next/server/pages/dynamic/no-ssr.js.nft.json')
|
||||
) as { files: string[] }
|
||||
expect(trace).not.toContain('hello1')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('ssr:true option', () => {
|
||||
it('Should render the component on the server side', async () => {
|
||||
const $ = await get$('/docs/dynamic/ssr-true')
|
||||
expect($('body').html()).toContain('"dynamicIds"')
|
||||
expect($('p').text()).toBe('Hello World 1')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/ssr-true')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom chunkfilename', () => {
|
||||
it('should render the correct filename', async () => {
|
||||
const $ = await get$('/docs/dynamic/chunkfilename')
|
||||
expect($('body').text()).toMatch(/test chunkfilename/)
|
||||
expect($('html').html()).toMatch(/hello-world\.js/)
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/chunkfilename')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/test chunkfilename/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom loading', () => {
|
||||
it('should render custom loading on the server side when `ssr:false` and `loading` is provided', async () => {
|
||||
const $ = await get$('/docs/dynamic/no-ssr-custom-loading')
|
||||
expect($('p').text()).toBe('LOADING')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(
|
||||
next.url,
|
||||
'/docs/dynamic/no-ssr-custom-loading'
|
||||
)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Multiple modules', () => {
|
||||
it('should only include the rendered module script tag', async () => {
|
||||
const $ = await get$('/docs/dynamic/multiple-modules')
|
||||
const html = $('html').html()
|
||||
expect(html).toMatch(/hello1\.js/)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
})
|
||||
|
||||
it('should only load the rendered module in the browser', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/docs/dynamic/multiple-modules')
|
||||
const html = await browser.eval('document.documentElement.innerHTML')
|
||||
expect(html).toMatch(/hello1\.js/)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should only render one bundle if component is used multiple times', async () => {
|
||||
const $ = await get$('/docs/dynamic/multiple-modules')
|
||||
const html = $('html').html()
|
||||
try {
|
||||
expect(html.match(/chunks[\\/]hello1\.js/g).length).toBe(1)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
} catch (err) {
|
||||
console.error(html)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export default () => <div>test chunkfilename</div>
|
|
@ -1,13 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class extends React.Component {
|
||||
static contextTypes = {
|
||||
data: PropTypes.object,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data } = this.context
|
||||
return <div>{data.title}</div>
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export default () => <p>Hello World 1</p>
|
|
@ -1 +0,0 @@
|
|||
export default () => <p>Hello World 2</p>
|
|
@ -1 +0,0 @@
|
|||
export default () => <p>Hello World 1</p>
|
|
@ -1 +0,0 @@
|
|||
export default () => <p>Hello World 2</p>
|
|
@ -1,10 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Nested2 = dynamic(() => import('./nested2'))
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
Nested 1
|
||||
<Nested2 />
|
||||
</div>
|
||||
)
|
|
@ -1,12 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const BrowserLoaded = dynamic(async () => () => <div>Browser hydrated</div>, {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<div>Nested 2</div>
|
||||
<BrowserLoaded />
|
||||
</div>
|
||||
)
|
|
@ -1,17 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
export default class Welcome extends React.Component {
|
||||
state = { name: null }
|
||||
|
||||
componentDidMount() {
|
||||
const { name } = this.props
|
||||
this.setState({ name })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name } = this.state
|
||||
if (!name) return null
|
||||
|
||||
return <p>Welcome, {name}</p>
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(
|
||||
import(
|
||||
/* webpackChunkName: 'hello-world' */ '../../components/hello-chunkfilename'
|
||||
)
|
||||
)
|
||||
|
||||
export default Hello
|
|
@ -1,5 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(() => import('../../components/hello1'))
|
||||
|
||||
export default Hello
|
|
@ -1,30 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
import Head from 'next/head'
|
||||
|
||||
const Test = dynamic({
|
||||
loader: async () => {
|
||||
// component
|
||||
return () => {
|
||||
return (
|
||||
<div className="dynamic-style">
|
||||
<Head>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
.dynamic-style {
|
||||
background-color: green;
|
||||
height: 200px;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
test
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export default Test
|
|
@ -1,7 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Link href="/dynamic/no-chunk">No Chunk</Link>
|
||||
</div>
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(
|
||||
import(/* webpackChunkName: 'hello1' */ '../../components/hello3')
|
||||
)
|
||||
const Hello2 = dynamic(
|
||||
import(/* webpackChunkName: 'hello2' */ '../../components/hello4')
|
||||
)
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<Hello />
|
||||
<Hello />
|
||||
<Hello />
|
||||
<Hello />
|
||||
<Hello />
|
||||
<Hello />
|
||||
<Hello />
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(() => import('../../components/nested1'))
|
||||
|
||||
export default DynamicComponent
|
|
@ -1,11 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
import Welcome from '../../components/welcome'
|
||||
|
||||
const Welcome2 = dynamic(import('../../components/welcome'))
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Welcome name="normal" />
|
||||
<Welcome2 name="dynamic" />
|
||||
</div>
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(import('../../components/hello1'), {
|
||||
ssr: false,
|
||||
loading: () => <p>LOADING</p>,
|
||||
})
|
||||
|
||||
export default Hello
|
|
@ -1,5 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(import('../../components/hello1'), { ssr: false })
|
||||
|
||||
export default Hello
|
|
@ -1,5 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(import('../../components/hello1'), { ssr: true })
|
||||
|
||||
export default Hello
|
|
@ -1,5 +0,0 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const Hello = dynamic(import('../../components/hello1'))
|
||||
|
||||
export default Hello
|
File diff suppressed because it is too large
Load diff
|
@ -5,101 +5,117 @@ import { createNext, FileRef } from 'e2e-utils'
|
|||
import { NextInstance } from 'test/lib/next-modes/base'
|
||||
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
|
||||
|
||||
describe('misc basic dev tests', () => {
|
||||
let next: NextInstance
|
||||
describe.each([[''], ['/docs']])(
|
||||
'misc basic dev tests, basePath: %p',
|
||||
(basePath: string) => {
|
||||
let next: NextInstance
|
||||
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
pages: new FileRef(join(__dirname, 'misc/pages')),
|
||||
public: new FileRef(join(__dirname, 'misc/public')),
|
||||
},
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
pages: new FileRef(join(__dirname, 'misc/pages')),
|
||||
public: new FileRef(join(__dirname, 'misc/public')),
|
||||
},
|
||||
nextConfig: {
|
||||
basePath,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
it('should set process.env.NODE_ENV in development', async () => {
|
||||
const browser = await webdriver(next.url, '/process-env')
|
||||
const nodeEnv = await browser.elementByCss('#node-env').text()
|
||||
expect(nodeEnv).toBe('development')
|
||||
await browser.close()
|
||||
})
|
||||
it('should set process.env.NODE_ENV in development', async () => {
|
||||
const browser = await webdriver(next.url, basePath + '/process-env')
|
||||
const nodeEnv = await browser.elementByCss('#node-env').text()
|
||||
expect(nodeEnv).toBe('development')
|
||||
await browser.close()
|
||||
})
|
||||
|
||||
it('should allow access to public files', async () => {
|
||||
const data = await renderViaHTTP(next.url, '/data/data.txt')
|
||||
expect(data).toBe('data')
|
||||
it('should allow access to public files', async () => {
|
||||
const data = await renderViaHTTP(next.url, basePath + '/data/data.txt')
|
||||
expect(data).toBe('data')
|
||||
|
||||
const legacy = await renderViaHTTP(next.url, '/static/legacy.txt')
|
||||
expect(legacy).toMatch(`new static folder`)
|
||||
})
|
||||
const legacy = await renderViaHTTP(
|
||||
next.url,
|
||||
basePath + '/static/legacy.txt'
|
||||
)
|
||||
expect(legacy).toMatch(`new static folder`)
|
||||
})
|
||||
|
||||
describe('With Security Related Issues', () => {
|
||||
it('should not allow accessing files outside .next/static and .next/server directory', async () => {
|
||||
const pathsToCheck = [
|
||||
`/_next/static/../BUILD_ID`,
|
||||
`/_next/static/../routes-manifest.json`,
|
||||
]
|
||||
for (const path of pathsToCheck) {
|
||||
const res = await fetchViaHTTP(next.url, path)
|
||||
describe('With Security Related Issues', () => {
|
||||
it('should not allow accessing files outside .next/static and .next/server directory', async () => {
|
||||
const pathsToCheck = [
|
||||
basePath + '/_next/static/../BUILD_ID',
|
||||
basePath + '/_next/static/../routes-manifest.json',
|
||||
]
|
||||
for (const path of pathsToCheck) {
|
||||
const res = await fetchViaHTTP(next.url, path)
|
||||
const text = await res.text()
|
||||
try {
|
||||
expect(res.status).toBe(404)
|
||||
expect(text).toMatch(/This page could not be found/)
|
||||
} catch (err) {
|
||||
throw new Error(`Path ${path} accessible from the browser`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle encoded / value for trailing slash correctly', async () => {
|
||||
const res = await fetchViaHTTP(
|
||||
next.url,
|
||||
basePath + '/%2fexample.com/',
|
||||
undefined,
|
||||
{
|
||||
redirect: 'manual',
|
||||
}
|
||||
)
|
||||
|
||||
const { pathname, hostname } = url.parse(
|
||||
res.headers.get('location') || ''
|
||||
)
|
||||
expect(res.status).toBe(308)
|
||||
expect(pathname).toBe(basePath + '/%2fexample.com')
|
||||
expect(hostname).not.toBe('example.com')
|
||||
const text = await res.text()
|
||||
try {
|
||||
expect(res.status).toBe(404)
|
||||
expect(text).toMatch(/This page could not be found/)
|
||||
} catch (err) {
|
||||
throw new Error(`Path ${path} accessible from the browser`)
|
||||
}
|
||||
}
|
||||
expect(text).toEqual(basePath + '/%2fexample.com')
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle encoded / value for trailing slash correctly', async () => {
|
||||
const res = await fetchViaHTTP(next.url, '/%2fexample.com/', undefined, {
|
||||
redirect: 'manual',
|
||||
})
|
||||
async function getLogs$(path) {
|
||||
let foundLog = false
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, path)
|
||||
const browserLogs = await browser.log('browser')
|
||||
|
||||
const { pathname, hostname } = url.parse(
|
||||
res.headers.get('location') || ''
|
||||
)
|
||||
expect(res.status).toBe(308)
|
||||
expect(pathname).toBe('/%2fexample.com')
|
||||
expect(hostname).not.toBe('example.com')
|
||||
const text = await res.text()
|
||||
expect(text).toEqual('/%2fexample.com')
|
||||
})
|
||||
})
|
||||
|
||||
async function getLogs$(path) {
|
||||
let foundLog = false
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, path)
|
||||
const browserLogs = await browser.log('browser')
|
||||
|
||||
browserLogs.forEach((log) => {
|
||||
if (log.message.includes('Next.js auto-prefetches automatically')) {
|
||||
foundLog = true
|
||||
browserLogs.forEach((log) => {
|
||||
if (log.message.includes('Next.js auto-prefetches automatically')) {
|
||||
foundLog = true
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
return foundLog
|
||||
}
|
||||
return foundLog
|
||||
describe('Development Logs', () => {
|
||||
it('should warn when prefetch is true', async () => {
|
||||
const foundLog = await getLogs$(basePath + '/development-logs')
|
||||
expect(foundLog).toBe(true)
|
||||
})
|
||||
it('should not warn when prefetch is false', async () => {
|
||||
const foundLog = await getLogs$(
|
||||
basePath + '/development-logs/link-with-prefetch-false'
|
||||
)
|
||||
expect(foundLog).toBe(false)
|
||||
})
|
||||
it('should not warn when prefetch is not specified', async () => {
|
||||
const foundLog = await getLogs$(
|
||||
basePath + '/development-logs/link-with-no-prefetch'
|
||||
)
|
||||
expect(foundLog).toBe(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
describe('Development Logs', () => {
|
||||
it('should warn when prefetch is true', async () => {
|
||||
const foundLog = await getLogs$('/development-logs')
|
||||
expect(foundLog).toBe(true)
|
||||
})
|
||||
it('should not warn when prefetch is false', async () => {
|
||||
const foundLog = await getLogs$(
|
||||
'/development-logs/link-with-prefetch-false'
|
||||
)
|
||||
expect(foundLog).toBe(false)
|
||||
})
|
||||
it('should not warn when prefetch is not specified', async () => {
|
||||
const foundLog = await getLogs$('/development-logs/link-with-no-prefetch')
|
||||
expect(foundLog).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
|
|
|
@ -5,244 +5,263 @@ import { createNext, FileRef } from 'e2e-utils'
|
|||
import { renderViaHTTP, check, hasRedbox } from 'next-test-utils'
|
||||
import { NextInstance } from 'test/lib/next-modes/base'
|
||||
|
||||
describe('basic next/dynamic usage', () => {
|
||||
let next: NextInstance
|
||||
describe.each([[''], ['/docs']])(
|
||||
'basic next/dynamic usage, basePath: %p',
|
||||
(basePath: string) => {
|
||||
let next: NextInstance
|
||||
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
components: new FileRef(join(__dirname, 'next-dynamic/components')),
|
||||
pages: new FileRef(join(__dirname, 'next-dynamic/pages')),
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
async function get$(path, query?: any) {
|
||||
const html = await renderViaHTTP(next.url, path, query)
|
||||
return cheerio.load(html)
|
||||
}
|
||||
|
||||
describe('Dynamic import', () => {
|
||||
describe('default behavior', () => {
|
||||
it('should render dynamic import components', async () => {
|
||||
const $ = await get$('/dynamic/ssr')
|
||||
// Make sure the client side knows it has to wait for the bundle
|
||||
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
|
||||
'dynamic/ssr.js -> ../../components/hello1'
|
||||
)
|
||||
expect($('body').text()).toMatch(/Hello World 1/)
|
||||
})
|
||||
|
||||
it('should render dynamic import components using a function as first parameter', async () => {
|
||||
const $ = await get$('/dynamic/function')
|
||||
// Make sure the client side knows it has to wait for the bundle
|
||||
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
|
||||
'dynamic/function.js -> ../../components/hello1'
|
||||
)
|
||||
expect($('body').text()).toMatch(/Hello World 1/)
|
||||
})
|
||||
|
||||
it('should render even there are no physical chunk exists', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/no-chunk')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Welcome, normal/
|
||||
)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Welcome, dynamic/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should hydrate nested chunks', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/nested')
|
||||
await check(() => browser.elementByCss('body').text(), /Nested 1/)
|
||||
await check(() => browser.elementByCss('body').text(), /Nested 2/)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Browser hydrated/
|
||||
)
|
||||
|
||||
if ((global as any).browserName === 'chrome') {
|
||||
const logs = await browser.log('browser')
|
||||
|
||||
logs.forEach((logItem) => {
|
||||
expect(logItem.message).not.toMatch(
|
||||
/Expected server HTML to contain/
|
||||
)
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should render the component Head content', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/head')
|
||||
await check(() => browser.elementByCss('body').text(), /test/)
|
||||
const backgroundColor = await browser
|
||||
.elementByCss('.dynamic-style')
|
||||
.getComputedCss('background-color')
|
||||
const height = await browser
|
||||
.elementByCss('.dynamic-style')
|
||||
.getComputedCss('height')
|
||||
expect(height).toBe('200px')
|
||||
expect(backgroundColor).toMatch(/rgba?\(0, 128, 0/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
components: new FileRef(join(__dirname, 'next-dynamic/components')),
|
||||
pages: new FileRef(join(__dirname, 'next-dynamic/pages')),
|
||||
},
|
||||
nextConfig: {
|
||||
basePath,
|
||||
},
|
||||
})
|
||||
})
|
||||
describe('ssr:false option', () => {
|
||||
it('should not render loading on the server side', async () => {
|
||||
const $ = await get$('/dynamic/no-ssr')
|
||||
expect($('body').html()).not.toContain('"dynamicIds"')
|
||||
expect($('body').text()).not.toMatch('loading...')
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/no-ssr')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
async function get$(path, query?: any) {
|
||||
const html = await renderViaHTTP(next.url, path, query)
|
||||
return cheerio.load(html)
|
||||
}
|
||||
|
||||
describe('Dynamic import', () => {
|
||||
describe('default behavior', () => {
|
||||
it('should render dynamic import components', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/ssr')
|
||||
// Make sure the client side knows it has to wait for the bundle
|
||||
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
|
||||
'dynamic/ssr.js -> ../../components/hello1'
|
||||
)
|
||||
expect(await hasRedbox(browser)).toBe(false)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('ssr:true option', () => {
|
||||
it('Should render the component on the server side', async () => {
|
||||
const $ = await get$('/dynamic/ssr-true')
|
||||
expect($('body').html()).toContain('"dynamicIds"')
|
||||
expect($('p').text()).toBe('Hello World 1')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/ssr-true')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!(global as any).isNextDev) {
|
||||
it('should not include ssr:false imports to server trace', async () => {
|
||||
const trace = JSON.parse(
|
||||
await next.readFile('.next/server/pages/dynamic/no-ssr.js.nft.json')
|
||||
) as { files: string[] }
|
||||
expect(trace).not.toContain('hello1')
|
||||
expect($('body').text()).toMatch(/Hello World 1/)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('custom chunkfilename', () => {
|
||||
it('should render the correct filename', async () => {
|
||||
const $ = await get$('/dynamic/chunkfilename')
|
||||
expect($('body').text()).toMatch(/test chunkfilename/)
|
||||
expect($('html').html()).toMatch(/hello-world\.js/)
|
||||
it('should render dynamic import components using a function as first parameter', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/function')
|
||||
// Make sure the client side knows it has to wait for the bundle
|
||||
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
|
||||
'dynamic/function.js -> ../../components/hello1'
|
||||
)
|
||||
expect($('body').text()).toMatch(/Hello World 1/)
|
||||
})
|
||||
|
||||
it('should render even there are no physical chunk exists', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, basePath + '/dynamic/no-chunk')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Welcome, normal/
|
||||
)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Welcome, dynamic/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should hydrate nested chunks', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, basePath + '/dynamic/nested')
|
||||
await check(() => browser.elementByCss('body').text(), /Nested 1/)
|
||||
await check(() => browser.elementByCss('body').text(), /Nested 2/)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Browser hydrated/
|
||||
)
|
||||
|
||||
if ((global as any).browserName === 'chrome') {
|
||||
const logs = await browser.log('browser')
|
||||
|
||||
logs.forEach((logItem) => {
|
||||
expect(logItem.message).not.toMatch(
|
||||
/Expected server HTML to contain/
|
||||
)
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should render the component Head content', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, basePath + '/dynamic/head')
|
||||
await check(() => browser.elementByCss('body').text(), /test/)
|
||||
const backgroundColor = await browser
|
||||
.elementByCss('.dynamic-style')
|
||||
.getComputedCss('background-color')
|
||||
const height = await browser
|
||||
.elementByCss('.dynamic-style')
|
||||
.getComputedCss('height')
|
||||
expect(height).toBe('200px')
|
||||
expect(backgroundColor).toMatch(/rgba?\(0, 128, 0/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('ssr:false option', () => {
|
||||
it('should not render loading on the server side', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/no-ssr')
|
||||
expect($('body').html()).not.toContain('"dynamicIds"')
|
||||
expect($('body').text()).not.toMatch('loading...')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, basePath + '/dynamic/no-ssr')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
expect(await hasRedbox(browser)).toBe(false)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/chunkfilename')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/test chunkfilename/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
describe('ssr:true option', () => {
|
||||
it('Should render the component on the server side', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/ssr-true')
|
||||
expect($('body').html()).toContain('"dynamicIds"')
|
||||
expect($('p').text()).toBe('Hello World 1')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, basePath + '/dynamic/ssr-true')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!(global as any).isNextDev) {
|
||||
it('should not include ssr:false imports to server trace', async () => {
|
||||
const trace = JSON.parse(
|
||||
await next.readFile(
|
||||
'.next/server/pages/dynamic/no-ssr.js.nft.json'
|
||||
)
|
||||
) as { files: string[] }
|
||||
expect(trace).not.toContain('hello1')
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom loading', () => {
|
||||
it('should render custom loading on the server side when `ssr:false` and `loading` is provided', async () => {
|
||||
const $ = await get$('/dynamic/no-ssr-custom-loading')
|
||||
expect($('p').text()).toBe('LOADING')
|
||||
})
|
||||
describe('custom chunkfilename', () => {
|
||||
it('should render the correct filename', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/chunkfilename')
|
||||
expect($('body').text()).toMatch(/test chunkfilename/)
|
||||
expect($('html').html()).toMatch(/hello-world\.js/)
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/no-ssr-custom-loading')
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(
|
||||
next.url,
|
||||
basePath + '/dynamic/chunkfilename'
|
||||
)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/test chunkfilename/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Multiple modules', () => {
|
||||
it('should only include the rendered module script tag', async () => {
|
||||
const $ = await get$('/dynamic/multiple-modules')
|
||||
const html = $('html').html()
|
||||
expect(html).toMatch(/hello1\.js/)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
})
|
||||
})
|
||||
|
||||
it('should only load the rendered module in the browser', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(next.url, '/dynamic/multiple-modules')
|
||||
const html = await browser.eval('document.documentElement.innerHTML')
|
||||
describe('custom loading', () => {
|
||||
it('should render custom loading on the server side when `ssr:false` and `loading` is provided', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/no-ssr-custom-loading')
|
||||
expect($('p').text()).toBe('LOADING')
|
||||
})
|
||||
|
||||
it('should render the component on client side', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(
|
||||
next.url,
|
||||
basePath + '/dynamic/no-ssr-custom-loading'
|
||||
)
|
||||
await check(
|
||||
() => browser.elementByCss('body').text(),
|
||||
/Hello World 1/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Multiple modules', () => {
|
||||
it('should only include the rendered module script tag', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/multiple-modules')
|
||||
const html = $('html').html()
|
||||
expect(html).toMatch(/hello1\.js/)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should only render one bundle if component is used multiple times', async () => {
|
||||
const $ = await get$('/dynamic/multiple-modules')
|
||||
const html = $('html').html()
|
||||
try {
|
||||
expect(html.match(/chunks[\\/]hello1\.js/g).length).toBe(1)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
} catch (err) {
|
||||
console.error(html)
|
||||
throw err
|
||||
}
|
||||
it('should only load the rendered module in the browser', async () => {
|
||||
let browser
|
||||
try {
|
||||
browser = await webdriver(
|
||||
next.url,
|
||||
basePath + '/dynamic/multiple-modules'
|
||||
)
|
||||
const html = await browser.eval(
|
||||
'document.documentElement.innerHTML'
|
||||
)
|
||||
expect(html).toMatch(/hello1\.js/)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should only render one bundle if component is used multiple times', async () => {
|
||||
const $ = await get$(basePath + '/dynamic/multiple-modules')
|
||||
const html = $('html').html()
|
||||
try {
|
||||
expect(html.match(/chunks[\\/]hello1\.js/g).length).toBe(1)
|
||||
expect(html).not.toMatch(/hello2\.js/)
|
||||
} catch (err) {
|
||||
console.error(html)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue