diff --git a/test/development/basic/hmr.test.ts b/test/development/basic/hmr.test.ts
index 4ab1fa7d645aa..e83a768b19025 100644
--- a/test/development/basic/hmr.test.ts
+++ b/test/development/basic/hmr.test.ts
@@ -1,10 +1,8 @@
import { join } from 'path'
-import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
import {
assertHasRedbox,
getBrowserBodyText,
- renderViaHTTP,
retry,
waitFor,
} from 'next-test-utils'
@@ -65,310 +63,6 @@ describe.each([
})
})
- 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, basePath + '/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 retry(async () => {
- expect(await getBrowserBodyText(browser)).toMatch(
- /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 retry(async () => {
- expect(await getBrowserBodyText(browser)).toMatch(
- /This is the contact page/
- )
- })
-
- expect(next.cliOutput).toContain('Compiled /_error')
- } 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, basePath + '/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 retry(async () => {
- expect(await getBrowserBodyText(browser)).toMatch(/COOL page/)
- })
- } finally {
- // add the original content
- await next.patchFile(aboutPagePath, originalContent)
- }
-
- await retry(async () => {
- expect(await getBrowserBodyText(browser)).toMatch(
- /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, basePath + '/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 retry(async () => {
- expect(await browser.elementByCss('p').text()).toMatch(/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, basePath + '/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 retry(async () => {
- const editedPTag = await browser.elementByCss('.hmr-style-page p')
- expect(await editedPTag.getComputedCss('font-size')).toBe('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,
- basePath + '/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 retry(async () => {
- const editedPTag = await browser.elementByCss('.hmr-style-page p')
- expect(await editedPTag.getComputedCss('font-size')).toBe('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,
- basePath + '/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,
- basePath + '/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,
- basePath + '/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,
- basePath + '/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()
- }
- }
- })
-
- it('should not full reload when nonlatin characters are used', async () => {
- let browser = null
- const pagePath = join('pages', 'hmr', 'nonlatin.js')
- const originalContent = await next.readFile(pagePath)
- try {
- browser = await webdriver(next.url, basePath + '/hmr/nonlatin')
- const timeOrigin = await browser.eval('performance.timeOrigin')
- const editedContent = originalContent.replace(
- '
テスト
',
- 'テスト
'
- )
-
- // Change the page
- await next.patchFile(pagePath, editedContent)
-
- await browser.waitForElementByCss('.updated')
-
- expect(await browser.eval('performance.timeOrigin')).toEqual(
- timeOrigin
- )
- } 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()
- }
- }
- })
- })
- })
-
it('should have correct compile timing after fixing error', async () => {
const pageName = 'pages/auto-export-is-ready.js'
const originalContent = await next.readFile(pageName)
diff --git a/test/development/basic/hmr/hot-module-reload.test.ts b/test/development/basic/hmr/hot-module-reload.test.ts
new file mode 100644
index 0000000000000..1b284ea0d5c1e
--- /dev/null
+++ b/test/development/basic/hmr/hot-module-reload.test.ts
@@ -0,0 +1,282 @@
+import { join } from 'path'
+import cheerio from 'cheerio'
+import {
+ getBrowserBodyText,
+ renderViaHTTP,
+ retry,
+ waitFor,
+} from 'next-test-utils'
+import { nextTestSetup } from 'e2e-utils'
+import type { NextConfig } from 'next'
+
+describe.each([
+ { basePath: '', assetPrefix: '' },
+ { basePath: '', assetPrefix: '/asset-prefix' },
+ { basePath: '/docs', assetPrefix: '' },
+ { basePath: '/docs', assetPrefix: '/asset-prefix' },
+])(
+ 'HMR - Hot Module Reload, nextConfig: %o',
+ (nextConfig: Partial) => {
+ const { next } = nextTestSetup({
+ files: __dirname,
+ nextConfig,
+ patchFileDelay: 500,
+ })
+ const { basePath } = nextConfig
+
+ 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')
+ const browser = await next.browser(basePath + '/hmr/contact')
+ try {
+ 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 retry(async () => {
+ expect(await getBrowserBodyText(browser)).toMatch(
+ /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 retry(async () => {
+ expect(await getBrowserBodyText(browser)).toMatch(
+ /This is the contact page/
+ )
+ })
+
+ expect(next.cliOutput).toContain('Compiled /_error')
+ } finally {
+ await next
+ .renameFile(newContactPagePath, contactPagePath)
+ .catch(() => {})
+ }
+ })
+ })
+
+ describe('editing a page', () => {
+ it('should detect the changes and display it', async () => {
+ const browser = await next.browser(basePath + '/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 retry(async () => {
+ expect(await getBrowserBodyText(browser)).toMatch(/COOL page/)
+ })
+ } finally {
+ // add the original content
+ await next.patchFile(aboutPagePath, originalContent)
+ }
+
+ await retry(async () => {
+ expect(await getBrowserBodyText(browser)).toMatch(
+ /This is the about page/
+ )
+ })
+ })
+
+ it('should not reload unrelated pages', async () => {
+ const browser = await next.browser(basePath + '/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 retry(async () => {
+ expect(await browser.elementByCss('p').text()).toMatch(/COUNT: 2/)
+ })
+ } finally {
+ // restore the about page content.
+ await next.patchFile(aboutPagePath, 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 correctly', async () => {
+ const browser = await next.browser(basePath + '/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 retry(async () => {
+ const editedPTag = await browser.elementByCss('.hmr-style-page p')
+ expect(await editedPTag.getComputedCss('font-size')).toBe('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)
+ }
+ })
+
+ // 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 () => {
+ const browser = await next.browser(
+ basePath + '/hmr/style-stateful-component'
+ )
+ const pagePath = join('pages', 'hmr', 'style-stateful-component.js')
+ const originalContent = await next.readFile(pagePath)
+ try {
+ 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 retry(async () => {
+ const editedPTag = await browser.elementByCss('.hmr-style-page p')
+ expect(await editedPTag.getComputedCss('font-size')).toBe('200px')
+ })
+ } finally {
+ 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 () => {
+ const browser = await next.browser(
+ basePath + '/hmr/style-dynamic-component'
+ )
+ const secondBrowser = await next.browser(
+ basePath + '/hmr/style-dynamic-component'
+ )
+ const pagePath = join('components', 'hmr', 'dynamic.js')
+ const originalContent = await next.readFile(pagePath)
+ try {
+ 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,
+ basePath + '/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)
+
+ // 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,
+ basePath + '/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)
+ }
+ })
+
+ it('should not full reload when nonlatin characters are used', async () => {
+ const browser = await next.browser(basePath + '/hmr/nonlatin')
+ const pagePath = join('pages', 'hmr', 'nonlatin.js')
+ const originalContent = await next.readFile(pagePath)
+ try {
+ const timeOrigin = await browser.eval('performance.timeOrigin')
+ const editedContent = originalContent.replace(
+ 'テスト
',
+ 'テスト
'
+ )
+
+ // Change the page
+ await next.patchFile(pagePath, editedContent)
+
+ await browser.waitForElementByCss('.updated')
+
+ expect(await browser.eval('performance.timeOrigin')).toEqual(
+ timeOrigin
+ )
+ } 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)
+ }
+ })
+ })
+ }
+)