Enable Fast Refresh by Default (#12640)
This commit is contained in:
parent
0bc0760356
commit
ae1daea355
18 changed files with 207 additions and 148 deletions
|
@ -94,6 +94,9 @@ export default function connect(options) {
|
|||
subscribeToHmrEvent(handler) {
|
||||
customHmrEventHandler = handler
|
||||
},
|
||||
onUnrecoverableError() {
|
||||
hadRuntimeError = true
|
||||
},
|
||||
reportRuntimeError(err) {
|
||||
if (process.env.__NEXT_FAST_REFRESH) {
|
||||
return
|
||||
|
|
|
@ -96,7 +96,7 @@ class Container extends React.Component {
|
|||
(isFallback ||
|
||||
(data.nextExport &&
|
||||
(isDynamicRoute(router.pathname) || location.search)) ||
|
||||
(props.__N_SSG && location.search))
|
||||
(props && props.__N_SSG && location.search))
|
||||
) {
|
||||
// update query on mount for exported pages
|
||||
router.replace(
|
||||
|
@ -287,6 +287,10 @@ export function renderError(props) {
|
|||
// In production we catch runtime errors using componentDidCatch which will trigger renderError
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (process.env.__NEXT_FAST_REFRESH) {
|
||||
// A Next.js rendering runtime error is always unrecoverable
|
||||
// FIXME: let's make this recoverable (error in GIP client-transition)
|
||||
webpackHMR.onUnrecoverableError()
|
||||
|
||||
const { getNodeError } = require('@next/react-dev-overlay/lib/client')
|
||||
// Server-side runtime errors need to be re-thrown on the client-side so
|
||||
// that the overlay is rendered.
|
||||
|
|
|
@ -52,7 +52,7 @@ const defaultConfig: { [key: string]: any } = {
|
|||
basePath: '',
|
||||
sassOptions: {},
|
||||
pageEnv: false,
|
||||
reactRefresh: false,
|
||||
reactRefresh: true,
|
||||
},
|
||||
future: {
|
||||
excludeDefaultMomentLocales: false,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import flush from 'styled-jsx/server'
|
||||
|
||||
import {
|
||||
CLIENT_STATIC_FILES_RUNTIME_AMP,
|
||||
CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
|
||||
AMP_RENDER_TARGET,
|
||||
CLIENT_STATIC_FILES_RUNTIME_AMP,
|
||||
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
|
||||
CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
|
||||
} from '../next-server/lib/constants'
|
||||
import { DocumentContext as DocumentComponentContext } from '../next-server/lib/document-context'
|
||||
import {
|
||||
|
@ -661,6 +661,7 @@ export class NextScript extends Component<OriginProps> {
|
|||
}
|
||||
|
||||
const devFiles = [
|
||||
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
|
||||
CLIENT_STATIC_FILES_RUNTIME_AMP,
|
||||
CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
|
||||
]
|
||||
|
|
|
@ -20,7 +20,11 @@ export default function() {
|
|||
// Legacy CSS implementations will `eval` browser code in a Node.js context
|
||||
// to extract CSS. For backwards compatibility, we need to check we're in a
|
||||
// browser context before continuing.
|
||||
if (typeof self !== 'undefined') {
|
||||
if (
|
||||
typeof self !== 'undefined' &&
|
||||
// AMP / No-JS mode does not inject these helpers:
|
||||
'$RefreshHelpers$' in self
|
||||
) {
|
||||
const currentExports = module.__proto__.exports
|
||||
const prevExports = module.hot.data?.prevExports ?? null
|
||||
|
||||
|
|
|
@ -43,6 +43,10 @@ declare const module: {
|
|||
}
|
||||
}
|
||||
|
||||
function isSafeExport(key: string): boolean {
|
||||
return key === '__esModule' || key === '__N_SSG' || key === '__N_SSP'
|
||||
}
|
||||
|
||||
function registerExportsForReactRefresh(
|
||||
moduleExports: unknown,
|
||||
moduleID: string
|
||||
|
@ -54,6 +58,9 @@ function registerExportsForReactRefresh(
|
|||
return
|
||||
}
|
||||
for (const key in moduleExports) {
|
||||
if (isSafeExport(key)) {
|
||||
continue
|
||||
}
|
||||
const exportValue = moduleExports[key]
|
||||
const typeID = moduleID + ' %exports% ' + key
|
||||
RefreshRuntime.register(exportValue, typeID)
|
||||
|
@ -72,7 +79,7 @@ function isReactRefreshBoundary(moduleExports: unknown): boolean {
|
|||
let areAllExportsComponents = true
|
||||
for (const key in moduleExports) {
|
||||
hasExports = true
|
||||
if (key === '__esModule') {
|
||||
if (isSafeExport(key)) {
|
||||
continue
|
||||
}
|
||||
const exportValue = moduleExports[key]
|
||||
|
@ -109,7 +116,7 @@ function getRefreshBoundarySignature(moduleExports: unknown): Array<unknown> {
|
|||
return signature
|
||||
}
|
||||
for (const key in moduleExports) {
|
||||
if (key === '__esModule') {
|
||||
if (isSafeExport(key)) {
|
||||
continue
|
||||
}
|
||||
const exportValue = moduleExports[key]
|
||||
|
|
|
@ -392,7 +392,7 @@ describe('AMP Usage', () => {
|
|||
const html = await renderViaHTTP(dynamicAppPort, '/only-amp')
|
||||
const $ = cheerio.load(html)
|
||||
expect($('html').attr('data-ampdevmode')).toBe('')
|
||||
expect($('script[data-ampdevmode]').length).toBe(3)
|
||||
expect($('script[data-ampdevmode]').length).toBe(4)
|
||||
})
|
||||
|
||||
it('should detect the changes and display it', async () => {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
/* eslint-env jest */
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
check,
|
||||
File,
|
||||
waitFor,
|
||||
getReactErrorOverlayContent,
|
||||
getBrowserBodyText,
|
||||
getRedboxHeader,
|
||||
getRedboxSource,
|
||||
hasRedbox,
|
||||
waitFor,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
|
||||
export default (context, renderViaHTTP) => {
|
||||
describe('Error Recovery', () => {
|
||||
|
@ -46,40 +48,6 @@ export default (context, renderViaHTTP) => {
|
|||
}
|
||||
})
|
||||
|
||||
it('should have installed the react-overlay-editor editor handler', async () => {
|
||||
let browser
|
||||
const aboutPage = new File(
|
||||
join(__dirname, '../', 'pages', 'hmr', 'about1.js')
|
||||
)
|
||||
try {
|
||||
aboutPage.replace('</div>', 'div')
|
||||
browser = await webdriver(context.appPort, '/hmr/about1')
|
||||
|
||||
// react-error-overlay uses the following inline style if an editorHandler is installed
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
/style="cursor: pointer;"/
|
||||
)
|
||||
|
||||
aboutPage.restore()
|
||||
|
||||
await check(() => getBrowserBodyText(browser), /This is the about page/)
|
||||
} catch (err) {
|
||||
aboutPage.restore()
|
||||
if (browser) {
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
/This is the about page/
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should detect syntax errors and recover', async () => {
|
||||
let browser
|
||||
const aboutPage = new File(
|
||||
|
@ -91,7 +59,8 @@ export default (context, renderViaHTTP) => {
|
|||
|
||||
aboutPage.replace('</div>', 'div')
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(
|
||||
/Unterminated JSX contents/
|
||||
)
|
||||
|
||||
|
@ -127,7 +96,8 @@ export default (context, renderViaHTTP) => {
|
|||
|
||||
browser = await webdriver(context.appPort, '/hmr/contact')
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(
|
||||
/Unterminated JSX contents/
|
||||
)
|
||||
|
||||
|
@ -166,9 +136,8 @@ export default (context, renderViaHTTP) => {
|
|||
|
||||
aboutPage.replace('export', 'aa=20;\nexport')
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
/aa is not defined/
|
||||
)
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatch(/aa is not defined/)
|
||||
|
||||
aboutPage.restore()
|
||||
|
||||
|
@ -195,9 +164,8 @@ export default (context, renderViaHTTP) => {
|
|||
'throw new Error("an-expected-error");\nreturn'
|
||||
)
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
/an-expected-error/
|
||||
)
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(/an-expected-error/)
|
||||
|
||||
aboutPage.restore()
|
||||
|
||||
|
@ -234,7 +202,7 @@ export default (context, renderViaHTTP) => {
|
|||
)
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
() => getRedboxHeader(browser),
|
||||
/The default export is not a React Component/
|
||||
)
|
||||
|
||||
|
@ -274,7 +242,7 @@ export default (context, renderViaHTTP) => {
|
|||
)
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
() => getRedboxHeader(browser),
|
||||
/Objects are not valid as a React child/
|
||||
)
|
||||
|
||||
|
@ -314,8 +282,7 @@ export default (context, renderViaHTTP) => {
|
|||
)
|
||||
|
||||
await check(async () => {
|
||||
const txt = await getBrowserBodyText(browser)
|
||||
console.log(txt)
|
||||
const txt = await getRedboxHeader(browser)
|
||||
return txt
|
||||
}, /The default export is not a React Component/)
|
||||
|
||||
|
@ -349,7 +316,8 @@ export default (context, renderViaHTTP) => {
|
|||
browser = await webdriver(context.appPort, '/hmr')
|
||||
await browser.elementByCss('#error-in-gip-link').click()
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(
|
||||
/an-expected-error-in-gip/
|
||||
)
|
||||
|
||||
|
@ -366,7 +334,7 @@ export default (context, renderViaHTTP) => {
|
|||
await waitFor(2000)
|
||||
throw new Error('waiting')
|
||||
}
|
||||
return getReactErrorOverlayContent(browser)
|
||||
return getRedboxSource(browser)
|
||||
}, /an-expected-error-in-gip/)
|
||||
} catch (err) {
|
||||
erroredPage.restore()
|
||||
|
@ -387,7 +355,8 @@ export default (context, renderViaHTTP) => {
|
|||
try {
|
||||
browser = await webdriver(context.appPort, '/hmr/error-in-gip')
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxSource(browser)).toMatch(
|
||||
/an-expected-error-in-gip/
|
||||
)
|
||||
|
||||
|
@ -407,7 +376,7 @@ export default (context, renderViaHTTP) => {
|
|||
await waitFor(2000)
|
||||
throw new Error('waiting')
|
||||
}
|
||||
return getReactErrorOverlayContent(browser)
|
||||
return getRedboxSource(browser)
|
||||
}, /an-expected-error-in-gip/)
|
||||
} catch (err) {
|
||||
erroredPage.restore()
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
/* eslint-env jest */
|
||||
/* global jasmine */
|
||||
import { join } from 'path'
|
||||
import webdriver from 'next-webdriver'
|
||||
import renderingSuite from './rendering'
|
||||
import {
|
||||
waitFor,
|
||||
findPort,
|
||||
killApp,
|
||||
launchApp,
|
||||
fetchViaHTTP,
|
||||
findPort,
|
||||
getRedboxSource,
|
||||
hasRedbox,
|
||||
killApp,
|
||||
getRedboxHeader,
|
||||
launchApp,
|
||||
renderViaHTTP,
|
||||
getReactErrorOverlayContent,
|
||||
waitFor,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
import renderingSuite from './rendering'
|
||||
|
||||
const context = {}
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
|
||||
|
@ -209,10 +211,8 @@ describe('Client Navigation', () => {
|
|||
try {
|
||||
browser = await webdriver(context.appPort, '/nav')
|
||||
await browser.elementByCss('#empty-props').click()
|
||||
|
||||
await waitFor(3000)
|
||||
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatch(
|
||||
/should resolve to an object\. But found "null" instead\./
|
||||
)
|
||||
} finally {
|
||||
|
@ -953,10 +953,10 @@ describe('Client Navigation', () => {
|
|||
let browser
|
||||
try {
|
||||
browser = await webdriver(context.appPort, '/error-inside-browser-page')
|
||||
await waitFor(3000)
|
||||
const text = await getReactErrorOverlayContent(browser)
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
const text = await getRedboxSource(browser)
|
||||
expect(text).toMatch(/An Expected error occurred/)
|
||||
expect(text).toMatch(/pages\/error-inside-browser-page\.js:5/)
|
||||
expect(text).toMatch(/pages[\\/]error-inside-browser-page\.js \(5:12\)/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
|
@ -971,10 +971,10 @@ describe('Client Navigation', () => {
|
|||
context.appPort,
|
||||
'/error-in-the-browser-global-scope'
|
||||
)
|
||||
await waitFor(3000)
|
||||
const text = await getReactErrorOverlayContent(browser)
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
const text = await getRedboxSource(browser)
|
||||
expect(text).toMatch(/An Expected error occurred/)
|
||||
expect(text).toMatch(/error-in-the-browser-global-scope\.js:2/)
|
||||
expect(text).toMatch(/error-in-the-browser-global-scope\.js \(2:8\)/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
|
|
|
@ -204,9 +204,9 @@ export default function(render, fetch) {
|
|||
test('getInitialProps circular structure', async () => {
|
||||
const $ = await get$('/circular-json-error')
|
||||
const expectedErrorMessage =
|
||||
'Circular structure in "getInitialProps" result of page "/circular-json-error".'
|
||||
'Circular structure in \\"getInitialProps\\" result of page \\"/circular-json-error\\".'
|
||||
expect(
|
||||
$('pre')
|
||||
$('#__NEXT_DATA__')
|
||||
.text()
|
||||
.includes(expectedErrorMessage)
|
||||
).toBeTruthy()
|
||||
|
@ -215,9 +215,9 @@ export default function(render, fetch) {
|
|||
test('getInitialProps should be class method', async () => {
|
||||
const $ = await get$('/instance-get-initial-props')
|
||||
const expectedErrorMessage =
|
||||
'"InstanceInitialPropsPage.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.'
|
||||
'\\"InstanceInitialPropsPage.getInitialProps()\\" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.'
|
||||
expect(
|
||||
$('pre')
|
||||
$('#__NEXT_DATA__')
|
||||
.text()
|
||||
.includes(expectedErrorMessage)
|
||||
).toBeTruthy()
|
||||
|
@ -226,9 +226,9 @@ export default function(render, fetch) {
|
|||
test('getInitialProps resolves to null', async () => {
|
||||
const $ = await get$('/empty-get-initial-props')
|
||||
const expectedErrorMessage =
|
||||
'"EmptyInitialPropsPage.getInitialProps()" should resolve to an object. But found "null" instead.'
|
||||
'\\"EmptyInitialPropsPage.getInitialProps()\\" should resolve to an object. But found \\"null\\" instead.'
|
||||
expect(
|
||||
$('pre')
|
||||
$('#__NEXT_DATA__')
|
||||
.text()
|
||||
.includes(expectedErrorMessage)
|
||||
).toBeTruthy()
|
||||
|
@ -282,19 +282,19 @@ export default function(render, fetch) {
|
|||
|
||||
test('default export is not a React Component', async () => {
|
||||
const $ = await get$('/no-default-export')
|
||||
const pre = $('pre')
|
||||
const pre = $('#__NEXT_DATA__')
|
||||
expect(pre.text()).toMatch(/The default export is not a React Component/)
|
||||
})
|
||||
|
||||
test('error-inside-page', async () => {
|
||||
const $ = await get$('/error-inside-page')
|
||||
expect($('pre').text()).toMatch(/This is an expected error/)
|
||||
expect($('#__NEXT_DATA__').text()).toMatch(/This is an expected error/)
|
||||
// Sourcemaps are applied by react-error-overlay, so we can't check them on SSR.
|
||||
})
|
||||
|
||||
test('error-in-the-global-scope', async () => {
|
||||
const $ = await get$('/error-in-the-global-scope')
|
||||
expect($('pre').text()).toMatch(/aa is not defined/)
|
||||
expect($('#__NEXT_DATA__').text()).toMatch(/aa is not defined/)
|
||||
// Sourcemaps are applied by react-error-overlay, so we can't check them on SSR.
|
||||
})
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
fetchViaHTTP,
|
||||
findPort,
|
||||
getBrowserBodyText,
|
||||
getRedboxHeader,
|
||||
killApp,
|
||||
launchApp,
|
||||
nextBuild,
|
||||
|
@ -471,7 +472,7 @@ const runTests = (dev = false) => {
|
|||
await browser.elementByCss('#non-json').click()
|
||||
|
||||
await check(
|
||||
() => getBrowserBodyText(browser),
|
||||
() => getRedboxHeader(browser),
|
||||
/Error serializing `.time` returned from `getServerSideProps`/
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
/* eslint-env jest */
|
||||
/* global jasmine */
|
||||
import { join } from 'path'
|
||||
import webdriver from 'next-webdriver'
|
||||
import {
|
||||
findPort,
|
||||
launchApp,
|
||||
getRedboxHeader,
|
||||
hasRedbox,
|
||||
killApp,
|
||||
nextStart,
|
||||
launchApp,
|
||||
nextBuild,
|
||||
getReactErrorOverlayContent,
|
||||
nextStart,
|
||||
waitFor,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
|
||||
let app
|
||||
|
@ -53,7 +54,8 @@ const showsError = async (
|
|||
console.log(warnLogs)
|
||||
expect(warnLogs.some(log => log.match(regex))).toBe(true)
|
||||
} else {
|
||||
const errorContent = await getReactErrorOverlayContent(browser)
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
const errorContent = await getRedboxHeader(browser)
|
||||
expect(errorContent).toMatch(regex)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
File,
|
||||
findPort,
|
||||
getBrowserBodyText,
|
||||
getReactErrorOverlayContent,
|
||||
getRedboxHeader,
|
||||
hasRedbox,
|
||||
initNextServerScript,
|
||||
killApp,
|
||||
launchApp,
|
||||
|
@ -569,7 +570,8 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
// we need to reload the page to trigger getStaticProps
|
||||
await browser.refresh()
|
||||
|
||||
const errOverlayContent = await getReactErrorOverlayContent(browser)
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
const errOverlayContent = await getRedboxHeader(browser)
|
||||
|
||||
await fs.writeFile(indexPage, origContent)
|
||||
const errorMsg = /oops from getStaticProps/
|
||||
|
@ -694,12 +696,13 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
const browser = await webdriver(appPort, '/non-json/direct')
|
||||
|
||||
// FIXME: enable this
|
||||
// expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
// expect(await getRedboxHeader(browser)).toMatch(
|
||||
// /Error serializing `.time` returned from `getStaticProps`/
|
||||
// )
|
||||
|
||||
// FIXME: disable this
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatch(
|
||||
/Failed to load static props/
|
||||
)
|
||||
})
|
||||
|
@ -709,12 +712,13 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
await browser.elementByCss('#non-json').click()
|
||||
|
||||
// FIXME: enable this
|
||||
// expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
// expect(await getRedboxHeader(browser)).toMatch(
|
||||
// /Error serializing `.time` returned from `getStaticProps`/
|
||||
// )
|
||||
|
||||
// FIXME: disable this
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatch(
|
||||
/Failed to load static props/
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/* eslint-env jest */
|
||||
/* global jasmine */
|
||||
import fs from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import webdriver from 'next-webdriver'
|
||||
import {
|
||||
findPort,
|
||||
launchApp,
|
||||
killApp,
|
||||
check,
|
||||
findPort,
|
||||
getBrowserBodyText,
|
||||
getReactErrorOverlayContent,
|
||||
getRedboxHeader,
|
||||
killApp,
|
||||
launchApp,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
|
||||
const appDir = join(__dirname, '..')
|
||||
|
@ -57,7 +57,8 @@ describe('TypeScript HMR', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('should recover from a type error', async () => {
|
||||
// old behavior:
|
||||
it.skip('should recover from a type error', async () => {
|
||||
let browser
|
||||
const pagePath = join(appDir, 'pages/type-error-recover.tsx')
|
||||
const origContent = await fs.readFile(pagePath, 'utf8')
|
||||
|
@ -67,7 +68,7 @@ describe('TypeScript HMR', () => {
|
|||
|
||||
await fs.writeFile(pagePath, errContent)
|
||||
await check(
|
||||
() => getReactErrorOverlayContent(browser),
|
||||
() => getRedboxHeader(browser),
|
||||
/Type 'Element' is not assignable to type 'boolean'/
|
||||
)
|
||||
|
||||
|
@ -81,4 +82,33 @@ describe('TypeScript HMR', () => {
|
|||
await fs.writeFile(pagePath, origContent)
|
||||
}
|
||||
})
|
||||
|
||||
it('should ignore type errors in development', async () => {
|
||||
let browser
|
||||
const pagePath = join(appDir, 'pages/type-error-recover.tsx')
|
||||
const origContent = await fs.readFile(pagePath, 'utf8')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/type-error-recover')
|
||||
const errContent = origContent.replace(
|
||||
'() => <p>Hello world</p>',
|
||||
'(): boolean => <p>hello with error</p>'
|
||||
)
|
||||
await fs.writeFile(pagePath, errContent)
|
||||
const res = await check(
|
||||
async () => {
|
||||
const html = await browser.eval(
|
||||
'document.querySelector("p").innerText'
|
||||
)
|
||||
return html.match(/hello with error/) ? 'success' : 'fail'
|
||||
},
|
||||
/success/,
|
||||
false
|
||||
)
|
||||
|
||||
expect(res).toBe(true)
|
||||
} finally {
|
||||
if (browser) browser.close()
|
||||
await fs.writeFile(pagePath, origContent)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,7 +16,9 @@ const appDir = join(__dirname, '..')
|
|||
const nextConfigFile = new File(join(appDir, 'next.config.js'))
|
||||
|
||||
describe('TypeScript with error handling options', () => {
|
||||
for (const ignoreDevErrors of [false, true]) {
|
||||
// Dev can no longer show errors (for now), logbox will cover this in the
|
||||
// future.
|
||||
for (const ignoreDevErrors of [/*false,*/ true]) {
|
||||
for (const ignoreBuildErrors of [false, true]) {
|
||||
describe(`ignoreDevErrors: ${ignoreDevErrors}, ignoreBuildErrors: ${ignoreBuildErrors}`, () => {
|
||||
beforeAll(() => {
|
||||
|
|
|
@ -51,7 +51,8 @@ describe('TypeScript Features', () => {
|
|||
expect($('#imported-value').text()).toBe('OK')
|
||||
})
|
||||
|
||||
it('should report type checking to stdout', async () => {
|
||||
// old behavior:
|
||||
it.skip('should report type checking to stdout', async () => {
|
||||
expect(output).toContain('waiting for typecheck results...')
|
||||
})
|
||||
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
/* eslint-env jest */
|
||||
/* global jasmine */
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
getReactErrorOverlayContent,
|
||||
nextServer,
|
||||
launchApp,
|
||||
findPort,
|
||||
getRedboxHeader,
|
||||
hasRedbox,
|
||||
killApp,
|
||||
launchApp,
|
||||
nextBuild,
|
||||
nextServer,
|
||||
startApp,
|
||||
stopApp,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
|
||||
|
||||
|
@ -104,7 +105,8 @@ describe('withRouter SSR', () => {
|
|||
|
||||
it('should show an error when trying to use router methods during SSR', async () => {
|
||||
const browser = await webdriver(port, '/router-method-ssr')
|
||||
expect(await getReactErrorOverlayContent(browser)).toMatch(
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
expect(await getRedboxHeader(browser)).toMatch(
|
||||
`No router instance found. you should only use "next/router" inside the client side of your app. https://err.sh/`
|
||||
)
|
||||
await browser.close()
|
||||
|
|
|
@ -394,36 +394,65 @@ export class File {
|
|||
}
|
||||
}
|
||||
|
||||
// react-error-overlay uses an iframe so we have to read the contents from the frame
|
||||
export async function getReactErrorOverlayContent(browser) {
|
||||
let found = false
|
||||
setTimeout(() => {
|
||||
if (found) {
|
||||
return
|
||||
}
|
||||
console.error('TIMED OUT CHECK FOR IFRAME')
|
||||
throw new Error('TIMED OUT CHECK FOR IFRAME')
|
||||
}, 1000 * 30)
|
||||
while (!found) {
|
||||
try {
|
||||
await browser.waitForElementByCss('iframe', 10000)
|
||||
|
||||
const hasIframe = await browser.hasElementByCssSelector('iframe')
|
||||
if (!hasIframe) {
|
||||
throw new Error('Waiting for iframe')
|
||||
}
|
||||
|
||||
found = true
|
||||
return browser.eval(
|
||||
`document.querySelector('iframe').contentWindow.document.body.innerHTML`
|
||||
)
|
||||
} catch (ex) {
|
||||
await waitFor(1000)
|
||||
}
|
||||
export async function evaluate(browser, input) {
|
||||
if (typeof input === 'function') {
|
||||
const result = await browser.executeScript(input)
|
||||
await new Promise(resolve => setTimeout(resolve, 30))
|
||||
return result
|
||||
} else {
|
||||
throw new Error(`You must pass a function to be evaluated in the browser.`)
|
||||
}
|
||||
return browser.eval(
|
||||
`document.querySelector('iframe').contentWindow.document.body.innerHTML`
|
||||
)
|
||||
}
|
||||
|
||||
export async function hasRedbox(browser, expected = true) {
|
||||
let attempts = 30
|
||||
do {
|
||||
const has = await evaluate(browser, () => {
|
||||
return Boolean(
|
||||
[].slice
|
||||
.call(document.querySelectorAll('nextjs-portal'))
|
||||
.find(p =>
|
||||
p.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_label, #nextjs__container_build_error_label'
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
if (has) {
|
||||
return true
|
||||
}
|
||||
if (--attempts < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
} while (expected)
|
||||
return false
|
||||
}
|
||||
|
||||
export async function getRedboxHeader(browser) {
|
||||
return evaluate(browser, () => {
|
||||
const portal = [].slice
|
||||
.call(document.querySelectorAll('nextjs-portal'))
|
||||
.find(p => p.shadowRoot.querySelector('[data-nextjs-dialog-header'))
|
||||
const root = portal.shadowRoot
|
||||
return root.querySelector('[data-nextjs-dialog-header]').innerText
|
||||
})
|
||||
}
|
||||
|
||||
export async function getRedboxSource(browser) {
|
||||
return evaluate(browser, () => {
|
||||
const portal = [].slice
|
||||
.call(document.querySelectorAll('nextjs-portal'))
|
||||
.find(p =>
|
||||
p.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_label, #nextjs__container_build_error_label'
|
||||
)
|
||||
)
|
||||
const root = portal.shadowRoot
|
||||
return root.querySelector('[data-nextjs-codeframe], [data-nextjs-terminal]')
|
||||
.innerText
|
||||
})
|
||||
}
|
||||
|
||||
export function getBrowserBodyText(browser) {
|
||||
|
|
Loading…
Reference in a new issue