From b79c5da4a184c8b613e49fc09192216aaa30f1cb Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Tue, 1 Sep 2026 01:27:17 +0300 Subject: [PATCH] fix: clear errors while overlay is loading --- overlay/index.js | 37 ++++++++++++++++++++++---- test/overlay/index.test.js | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 test/overlay/index.test.js diff --git a/overlay/index.js b/overlay/index.js index e3f1a04c..cf55f455 100644 --- a/overlay/index.js +++ b/overlay/index.js @@ -82,6 +82,9 @@ function IframeRoot(document, root, props) { iframe.style.width = '100vw'; iframe.style.zIndex = '2147483647'; iframe.addEventListener('load', function onLoad() { + if (iframe !== iframeRoot) { + return; + } // Reset margin of iframe body iframe.contentDocument.body.style.margin = '0'; props.onIframeLoad(); @@ -241,8 +244,11 @@ function render() { */ function cleanup() { // Clean up and reset all internal state. - document.body.removeChild(iframeRoot); + if (iframeRoot && iframeRoot.parentNode) { + iframeRoot.parentNode.removeChild(iframeRoot); + } scheduledRenderFn = null; + rootDocument = null; root = null; iframeRoot = null; } @@ -252,7 +258,14 @@ function cleanup() { * @returns {void} */ function clearCompileError() { - if (!root || currentMode !== 'compileError') { + if (!root) { + currentCompileErrorMessage = ''; + if (iframeRoot && currentRuntimeErrors.length === 0) { + cleanup(); + } + return; + } + if (currentMode !== 'compileError') { return; } @@ -267,7 +280,19 @@ function clearCompileError() { * @returns {void} */ function clearRuntimeErrors(dismissOverlay) { - if (!root || currentMode !== 'runtimeError') { + if (!root) { + currentRuntimeErrorIndex = 0; + currentRuntimeErrors = []; + if ( + (typeof dismissOverlay === 'undefined' || dismissOverlay) && + iframeRoot && + !currentCompileErrorMessage + ) { + cleanup(); + } + return; + } + if (currentMode !== 'runtimeError') { return; } @@ -316,7 +341,9 @@ function showRuntimeErrors(errors) { * @param {Error[]} errors * @returns {void} */ -const debouncedShowRuntimeErrors = utils.debounce(showRuntimeErrors, 30); +const debouncedShowRuntimeErrors = utils.debounce(function () { + showRuntimeErrors(currentRuntimeErrors); +}, 30); /** * Detects if an error is a Webpack compilation error. @@ -337,7 +364,7 @@ function handleRuntimeError(error) { if (error && !isWebpackCompileError(error) && currentRuntimeErrors.indexOf(error) === -1) { currentRuntimeErrors = currentRuntimeErrors.concat(error); } - debouncedShowRuntimeErrors(currentRuntimeErrors); + debouncedShowRuntimeErrors(); } module.exports = Object.freeze({ diff --git a/test/overlay/index.test.js b/test/overlay/index.test.js new file mode 100644 index 00000000..81a04c09 --- /dev/null +++ b/test/overlay/index.test.js @@ -0,0 +1,54 @@ +/** + * @jest-environment jsdom + */ +/* eslint-env browser */ + +describe('error overlay', () => { + let loadHandler; + + beforeEach(() => { + jest.resetModules(); + document.body.innerHTML = ''; + jest + .spyOn(HTMLIFrameElement.prototype, 'addEventListener') + .mockImplementation((eventType, handler) => { + if (eventType === 'load') loadHandler = handler; + }); + }); + + afterEach(() => { + jest.useRealTimers(); + jest.restoreAllMocks(); + }); + + it.each([ + ['compile', (overlay) => overlay.showCompileError('Failed'), 'clearCompileError'], + [ + 'runtime', + (overlay) => overlay.showRuntimeErrors([new Error('Failed')]), + 'clearRuntimeErrors', + ], + ])('clears a pending %s error before the iframe loads', (_, showError, clearError) => { + const overlay = require('../../overlay'); + showError(overlay); + const iframe = document.getElementById('react-refresh-overlay'); + + overlay[clearError](); + + expect(iframe).not.toBeNull(); + expect(document.getElementById('react-refresh-overlay')).toBeNull(); + expect(() => loadHandler.call(iframe)).not.toThrow(); + expect(document.getElementById('react-refresh-overlay')).toBeNull(); + }); + + it('does not render a runtime error cleared during the debounce', () => { + jest.useFakeTimers(); + const overlay = require('../../overlay'); + + overlay.handleRuntimeError(new Error('Failed')); + overlay.clearRuntimeErrors(); + jest.runAllTimers(); + + expect(document.getElementById('react-refresh-overlay')).toBeNull(); + }); +});