Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ function createEventSourceWrapper() {
closed = false;

const handleDisconnect = () => {
// Reached only by an `error` event the EventSource had already queued
// when `close()` ran — a race the browser will not stage on demand.
/* istanbul ignore next -- @preserve */
if (closed) {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions client-src/indicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ export function show(text, percent, source = "") {
state.building[source] = true;
ensureIndicator();

// `ensureIndicator` above builds the label, so it is missing only in the
// document-less case that function already guards.
/* istanbul ignore next -- @preserve */
if (!state.label) {
return;
}
Expand Down
6 changes: 6 additions & 0 deletions client-src/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ let render = () => {};
* @param {number} index requested page index
*/
function goToPage(index) {
// Paging is only reachable from a rendered overlay, which implies problems.
/* istanbul ignore next -- @preserve */
if (!state.currentProblems) {
return;
}
Expand Down Expand Up @@ -557,12 +559,16 @@ function ensureOverlay() {
* Render the current problem set into the card.
*/
function renderProblems() {
// Both guards cover callers that cannot occur in a browser: rendering is
// driven by a problem set, and `ensureOverlay` only fails without a body.
/* istanbul ignore next -- @preserve */
if (!state.currentProblems) {
return;
}

const card = ensureOverlay();

/* istanbul ignore next -- @preserve */
if (!card) {
return;
}
Expand Down
6 changes: 6 additions & 0 deletions client-src/process-update.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ export default function applyUpdate(hash, options, name) {
* @param {Error} err error
*/
const handleError = (err) => {
// `hot.check()` rejecting while the runtime is already in `abort`/`fail`
// is a webpack-internal state the browser suite cannot stage.
/* istanbul ignore next -- @preserve */
if (hot.status() in failureStatuses) {
log.warn("Cannot check for update (Full reload needed)");
log.warn(err.stack || err.message);
Expand Down Expand Up @@ -140,6 +143,9 @@ export default function applyUpdate(hash, options, name) {
return;
}

// An applied update that renews nothing: webpack does not produce one
// from an edit, so there is no build to drive this from.
/* istanbul ignore next -- @preserve */
if (!renewedModules || renewedModules.length === 0) {
log.info("Nothing hot updated.");
} else {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/__snapshots__/overlay.test.js.snap.webpack5
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`error overlay (browser) keeps warnings out of the payload with hot.statsOptions 1`] = `
exports[`error overlay (browser) keeps warnings out of the payload when the stats option does 1`] = `
[
"[webpack-dev-middleware] connected",
]
Expand Down
72 changes: 68 additions & 4 deletions test/e2e/overlay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,11 +492,11 @@ describe("error overlay (browser)", () => {
expect(normalizeConsole(console_.messages)).toMatchSnapshot();
});

it("keeps warnings out of the payload with hot.statsOptions", async () => {
it("keeps warnings out of the payload when the stats option does", async () => {
hotApp = await createHotApp({
// The README's documented recipe: keep warnings in the build output
// but out of the SSE payload entirely.
hot: { statsOptions: { warnings: false } },
// One setting governs what a build reports in the terminal and in the
// browser; `hot.statsOptions` is deprecated and must not be used here.
stats: "errors-only",
code: warningApp("v1"),
});
({ page, browser } = await runBrowser());
Expand Down Expand Up @@ -1092,4 +1092,68 @@ describe("overlay shared state across bundled copies (browser)", () => {
await page.evaluate(() => document.querySelectorAll("iframe").length),
).toBe(1);
});

it("restyles a card that is already open", async () => {
await start();
await page.goto(hotApp.url);

await page.evaluate(() => {
globalThis.overlayA.showProblems("errors", ["styled"], "a");
});
await overlayFrame();

// Configuring while a card exists has to reach that card, not only the
// next one rendered.
await page.evaluate(() => {
globalThis.overlayA.default({
overlayStyles: { background: "rgb(1, 2, 3)" },
});
});

const frame = await overlayFrame();

expect(
await frame.evaluate(
(id) => document.getElementById(id).style.background,
CARD_ID,
),
).toBe("rgb(1, 2, 3)");
});

it("renders an empty problem without markup", async () => {
await start();
await page.goto(hotApp.url);

// An empty message still opens the overlay; the encoder has nothing to
// escape and must not fall over on it.
await page.evaluate(() => {
globalThis.overlayA.showProblems("errors", [""], "a");
});

const frame = await overlayFrame();

expect(await frame.evaluate(() => document.body.innerHTML)).toContain(
"ERROR",
);
});

it("dismisses on Escape pressed inside the overlay frame", async () => {
await start();
await page.goto(hotApp.url);

await page.evaluate(() => {
globalThis.overlayA.showProblems("errors", ["dismiss me"], "a");
});

const frame = await overlayFrame();

// Clicking inside the card moves focus into the frame, so the keydown
// lands on the frame's own listener rather than the host page's.
await clickInFrame(page, frame, `#${CARD_ID}`);
await page.keyboard.press("Escape");

await waitForNoOverlay(page);

expect(await page.$(`#${OVERLAY_ID}`)).toBeNull();
});
});
5 changes: 3 additions & 2 deletions test/helpers/hot-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function makeConfig(
* app becomes a named compilation whose client connects with `?name=<name>`
* and renders from `<name>.js`. `pageHeaders` are sent with the HTML page
* (e.g. a Content-Security-Policy).
* @param {{ query?: string, code?: string, files?: Record<string, string>, apps?: { name: string, code: string }[], hot?: EXPECTED_ANY, pageHeaders?: Record<string, string>, publicPath?: string, setup?: (server: EXPECTED_ANY) => void, hmrPlugin?: boolean }} options options
* @param {{ query?: string, code?: string, files?: Record<string, string>, apps?: { name: string, code: string }[], hot?: EXPECTED_ANY, stats?: EXPECTED_ANY, pageHeaders?: Record<string, string>, publicPath?: string, setup?: (server: EXPECTED_ANY) => void, hmrPlugin?: boolean }} options options
* @returns {Promise<EXPECTED_ANY>} handles for the running app
*/
async function createHotApp({
Expand All @@ -121,6 +121,7 @@ async function createHotApp({
files = {},
apps,
hot = true,
stats,
pageHeaders = {},
publicPath = "/",
setup,
Expand Down Expand Up @@ -188,7 +189,7 @@ async function createHotApp({
}
});

instance = middleware(compiler, { hot });
instance = middleware(compiler, { hot, stats });

const app = express();

Expand Down