Skip to content

Commit bfddcd2

Browse files
bjohansebasclaude
andcommitted
test(e2e): cover the client option surface in the browser and slim the jsdom suite
The subscribe/subscribeAll contract, the ?name= filter, per-source badge counting, the shared SSE connection between bundled clients (asserted explicitly in the multi-compiler console snapshot: one "connected" for two clients), the dev-server-shaped overlay option, and the warnings filter function — parsed from the entry query and applied against two real warning-producing modules — now all run in Chrome. The warnings overlay test also covers clearing on a recovered build, and the pagination test snapshots the two-error console output that the deleted jsdom snapshot used to document. client.test.js keeps only what the browser cannot pin deterministically: problem-type transitions, the multi-bundle problem union, option forwarding to the overlay factory, dynamicPublicPath URL building, and protocol edges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fcd7150 commit bfddcd2

5 files changed

Lines changed: 105 additions & 258 deletions

File tree

test/__snapshots__/client.test.js.snap.webpack5

Lines changed: 0 additions & 13 deletions
This file was deleted.

test/client.test.js

Lines changed: 1 addition & 244 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ describe("client", () => {
9999

100100
describe("with default options", () => {
101101
let EventSourceStub;
102-
let client;
103102

104103
beforeEach(() => {
105104
EventSourceStub = makeEventSourceStub();
@@ -108,53 +107,13 @@ describe("client", () => {
108107
jest.spyOn(console, "log").mockImplementation(() => {});
109108
jest.spyOn(console, "warn").mockImplementation(() => {});
110109
jest.spyOn(console, "error").mockImplementation(() => {});
111-
client = loadClient();
110+
loadClient();
112111
});
113112

114113
afterEach(() => {
115114
jest.restoreAllMocks();
116115
});
117116

118-
it("calls subscribeAll handler on default messages", () => {
119-
const spy = jest.fn();
120-
client.subscribeAll(spy);
121-
const message = {
122-
action: "built",
123-
time: 100,
124-
hash: "1234567890abcdef",
125-
errors: [],
126-
warnings: [],
127-
modules: [],
128-
};
129-
EventSourceStub.lastInstance().onmessage(makeMessage(message));
130-
expect(spy).toHaveBeenCalledTimes(1);
131-
expect(spy).toHaveBeenCalledWith(message);
132-
});
133-
134-
it("calls subscribeAll handler on custom messages", () => {
135-
const spy = jest.fn();
136-
client.subscribeAll(spy);
137-
EventSourceStub.lastInstance().onmessage(
138-
makeMessage({ action: "thingy" }),
139-
);
140-
expect(spy).toHaveBeenCalledTimes(1);
141-
expect(spy).toHaveBeenCalledWith({ action: "thingy" });
142-
});
143-
144-
it("calls only the custom handler for custom messages", () => {
145-
const spy = jest.fn();
146-
client.subscribe(spy);
147-
EventSourceStub.lastInstance().onmessage(
148-
makeMessage({ custom: "thingy" }),
149-
);
150-
EventSourceStub.lastInstance().onmessage(
151-
makeMessage({ action: "built" }),
152-
);
153-
expect(spy).toHaveBeenCalledTimes(1);
154-
expect(spy).toHaveBeenCalledWith({ custom: "thingy" });
155-
expect(processUpdate).not.toHaveBeenCalled();
156-
});
157-
158117
it("does not trigger webpack on errored builds", () => {
159118
EventSourceStub.lastInstance().onmessage(
160119
makeMessage({
@@ -289,41 +248,6 @@ describe("client", () => {
289248
});
290249
});
291250

292-
describe("with an overlay warnings filter function", () => {
293-
let EventSourceStub;
294-
295-
beforeEach(() => {
296-
EventSourceStub = makeEventSourceStub();
297-
globalThis.EventSource = EventSourceStub;
298-
jest.spyOn(console, "info").mockImplementation(() => {});
299-
jest.spyOn(console, "log").mockImplementation(() => {});
300-
jest.spyOn(console, "warn").mockImplementation(() => {});
301-
loadClient(
302-
'?overlay={"warnings":"function(message){return message.includes(`keep`)}"}',
303-
);
304-
});
305-
306-
afterEach(() => {
307-
jest.restoreAllMocks();
308-
});
309-
310-
it("only shows the warnings the filter keeps (dev-server parity)", () => {
311-
EventSourceStub.lastInstance().onmessage(
312-
makeMessage({
313-
action: "built",
314-
time: 100,
315-
hash: "1234567890abcdef",
316-
errors: [],
317-
warnings: ["drop this warning", "keep this warning"],
318-
modules: [],
319-
}),
320-
);
321-
expect(clientOverlay.showProblems).toHaveBeenCalledWith("warnings", [
322-
"keep this warning",
323-
]);
324-
});
325-
});
326-
327251
describe("with overlay warnings enabled via the dev-server-shaped option", () => {
328252
let EventSourceStub;
329253

@@ -341,69 +265,6 @@ describe("client", () => {
341265
jest.restoreAllMocks();
342266
});
343267

344-
it("shows overlay on errored builds", () => {
345-
EventSourceStub.lastInstance().onmessage(
346-
makeMessage({
347-
action: "built",
348-
time: 100,
349-
hash: "1234567890abcdef",
350-
errors: ["Something broke", "Actually, 2 things broke"],
351-
warnings: [],
352-
modules: [],
353-
}),
354-
);
355-
expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1);
356-
expect(clientOverlay.showProblems).toHaveBeenCalledWith("errors", [
357-
"Something broke",
358-
"Actually, 2 things broke",
359-
]);
360-
expect(console.error.mock.calls).toMatchSnapshot();
361-
});
362-
363-
it("shows overlay on warning builds", () => {
364-
EventSourceStub.lastInstance().onmessage(
365-
makeMessage({
366-
action: "built",
367-
time: 100,
368-
hash: "1234567890abcdef",
369-
errors: [],
370-
warnings: ["This isn't great, but it's not terrible"],
371-
modules: [],
372-
}),
373-
);
374-
expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1);
375-
expect(clientOverlay.showProblems).toHaveBeenCalledWith("warnings", [
376-
"This isn't great, but it's not terrible",
377-
]);
378-
});
379-
380-
it("hides overlay after warning build is fixed", () => {
381-
const es = EventSourceStub.lastInstance();
382-
es.onmessage(
383-
makeMessage({
384-
action: "built",
385-
time: 100,
386-
hash: "1234567890abcdef",
387-
errors: [],
388-
warnings: ["This isn't great, but it's not terrible"],
389-
modules: [],
390-
}),
391-
);
392-
es.onmessage(
393-
makeMessage({
394-
action: "built",
395-
time: 100,
396-
hash: "1234567890abcdef2",
397-
errors: [],
398-
warnings: [],
399-
modules: [],
400-
}),
401-
);
402-
expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1);
403-
expect(clientOverlay.clear).toHaveBeenCalledWith("");
404-
expect(clientOverlay.clear).toHaveBeenCalledWith("runtime");
405-
});
406-
407268
it("updates overlay after errored build becomes a warning", () => {
408269
const es = EventSourceStub.lastInstance();
409270
es.onmessage(
@@ -438,53 +299,6 @@ describe("client", () => {
438299
});
439300
});
440301

441-
describe("with name option", () => {
442-
let EventSourceStub;
443-
444-
beforeEach(() => {
445-
EventSourceStub = makeEventSourceStub();
446-
globalThis.EventSource = EventSourceStub;
447-
jest.spyOn(console, "info").mockImplementation(() => {});
448-
jest.spyOn(console, "log").mockImplementation(() => {});
449-
jest.spyOn(console, "warn").mockImplementation(() => {});
450-
loadClient("?name=test");
451-
});
452-
453-
afterEach(() => {
454-
jest.restoreAllMocks();
455-
});
456-
457-
it("does not trigger webpack when event name differs", () => {
458-
EventSourceStub.lastInstance().onmessage(
459-
makeMessage({
460-
name: "foo",
461-
action: "built",
462-
time: 100,
463-
hash: "1234567890abcdef",
464-
errors: [],
465-
warnings: [],
466-
modules: [],
467-
}),
468-
);
469-
expect(processUpdate).not.toHaveBeenCalled();
470-
});
471-
472-
it("does not trigger webpack on sync when event name differs", () => {
473-
EventSourceStub.lastInstance().onmessage(
474-
makeMessage({
475-
name: "bar",
476-
action: "sync",
477-
time: 100,
478-
hash: "1234567890abcdef",
479-
errors: [],
480-
warnings: [],
481-
modules: [],
482-
}),
483-
);
484-
expect(processUpdate).not.toHaveBeenCalled();
485-
});
486-
});
487-
488302
describe("with overlay runtime/trusted-types options", () => {
489303
it("forwards them to the overlay factory", () => {
490304
globalThis.EventSource = makeEventSourceStub();
@@ -543,55 +357,6 @@ describe("client", () => {
543357
});
544358
});
545359

546-
describe("with progress option", () => {
547-
const INDICATOR_ID = "webpack-dev-middleware-building-indicator";
548-
let EventSourceStub;
549-
550-
beforeEach(() => {
551-
EventSourceStub = makeEventSourceStub();
552-
globalThis.EventSource = EventSourceStub;
553-
jest.spyOn(console, "info").mockImplementation(() => {});
554-
jest.spyOn(console, "log").mockImplementation(() => {});
555-
});
556-
557-
afterEach(() => {
558-
jest.restoreAllMocks();
559-
});
560-
561-
it("keeps the badge until every compilation finished", () => {
562-
loadClient();
563-
const es = EventSourceStub.lastInstance();
564-
565-
es.onmessage(makeMessage({ action: "building", name: "app" }));
566-
es.onmessage(makeMessage({ action: "building", name: "admin" }));
567-
568-
es.onmessage(
569-
makeMessage({
570-
action: "built",
571-
name: "app",
572-
time: 1,
573-
hash: "h1",
574-
errors: [],
575-
warnings: [],
576-
}),
577-
);
578-
// "admin" is still building — its `built` has not arrived yet.
579-
expect(document.getElementById(INDICATOR_ID)).not.toBeNull();
580-
581-
es.onmessage(
582-
makeMessage({
583-
action: "built",
584-
name: "admin",
585-
time: 1,
586-
hash: "h2",
587-
errors: [],
588-
warnings: [],
589-
}),
590-
);
591-
expect(document.getElementById(INDICATOR_ID)).toBeNull();
592-
});
593-
});
594-
595360
describe("connection lifecycle", () => {
596361
let EventSourceStub;
597362
let client;
@@ -625,14 +390,6 @@ describe("client", () => {
625390
),
626391
).toBe(true);
627392
});
628-
629-
it("reuses the EventSource wrapper across reloads on the same path", () => {
630-
// Re-loading the entry on the same page should reuse the cached SSE
631-
// connection rather than opening a new one.
632-
jest.resetModules();
633-
require("../client-src");
634-
expect(EventSourceStub.instances).toHaveLength(1);
635-
});
636393
});
637394

638395
describe("with no EventSource", () => {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`error overlay (browser) paginates multiple problems with a counter 1`] = `
4+
[
5+
"[webpack-dev-middleware] connected",
6+
"[webpack-dev-middleware] bundle has 2 errors",
7+
"[webpack-dev-middleware] ./a.js 1:7
8+
Module parse failed: Unexpected token (1:7)
9+
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
10+
> broken a {{{
11+
./b.js 1:7
12+
Module parse failed: Unexpected token (1:7)
13+
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
14+
> broken b {{{",
15+
]
16+
`;

test/e2e/client.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ describe("hot client (browser)", () => {
242242
app = await createHotApp({
243243
code: `
244244
const hotClient = require(${JSON.stringify(CLIENT_ENTRY)});
245+
globalThis.__all = [];
246+
hotClient.subscribeAll((payload) => {
247+
globalThis.__all.push(payload.action);
248+
});
245249
hotClient.subscribe((payload) => {
246250
globalThis.__custom = payload;
247251
});
@@ -269,6 +273,13 @@ describe("hot client (browser)", () => {
269273
expect(await page.evaluate(() => globalThis.__custom.action)).toBe(
270274
"my-event",
271275
);
276+
277+
// subscribeAll saw both the protocol traffic (the connect-time sync) and
278+
// the custom payload; subscribe saw only the custom one.
279+
const all = await page.evaluate(() => globalThis.__all);
280+
281+
expect(all).toContain("sync");
282+
expect(all).toContain("my-event");
272283
});
273284

274285
it("disconnect() during the reconnect window cancels the pending reconnect", async () => {

0 commit comments

Comments
 (0)