|
1 | | -import collectConsole from "../helpers/console-collector"; |
| 1 | +import collectConsole, { normalizeConsole } from "../helpers/console-collector"; |
2 | 2 | import createHotApp from "../helpers/hot-app"; |
3 | 3 | import runBrowser from "../helpers/run-browser"; |
4 | 4 |
|
@@ -165,6 +165,77 @@ describe("hot client (browser)", () => { |
165 | 165 |
|
166 | 166 | await waitForAppText(page, "v2"); |
167 | 167 | expect(await readReloadMarker(page)).toBe(true); |
| 168 | + |
| 169 | + // The whole story in one place: connect, silent gap while the server was |
| 170 | + // down, reconnect, and the catch-up sync applying the missed build. |
| 171 | + await console_.waitFor("App is up to date"); |
| 172 | + expect(normalizeConsole(console_.messages)).toMatchSnapshot(); |
| 173 | + }); |
| 174 | + |
| 175 | + it("watchdog-reconnects a silent connection and stays armed afterwards", async () => { |
| 176 | + app = await createHotApp({ |
| 177 | + query: "?timeout=1000", |
| 178 | + code: acceptedApp("v1"), |
| 179 | + // A heartbeat far beyond the client timeout leaves the connection open |
| 180 | + // but silent, so only the inactivity watchdog can trigger reconnects. |
| 181 | + hot: { heartbeat: 3600000 }, |
| 182 | + }); |
| 183 | + ({ page, browser } = await runBrowser()); |
| 184 | + const console_ = collectConsole(page); |
| 185 | + |
| 186 | + await page.goto(app.url); |
| 187 | + await waitForAppText(page, "v1"); |
| 188 | + |
| 189 | + // Three connects = the initial one plus two watchdog cycles: the second |
| 190 | + // proves the watchdog fires on pure silence (no error event involved), |
| 191 | + // the third that it re-arms after a reconnect instead of dying with the |
| 192 | + // first clearInterval. |
| 193 | + await console_.waitForCount("connected", 3); |
| 194 | + |
| 195 | + // Nothing but connects: the silent cycles produce no other output. The |
| 196 | + // snapshot is taken before the edit — the watchdog keeps cycling, so any |
| 197 | + // later cut would race with the next reconnect. |
| 198 | + expect(normalizeConsole(console_.messages)).toMatchSnapshot(); |
| 199 | + |
| 200 | + // The reconnected connection still delivers updates. |
| 201 | + app.edit(acceptedApp("v2")); |
| 202 | + await waitForAppText(page, "v2"); |
| 203 | + |
| 204 | + expect( |
| 205 | + await page.evaluate(() => document.getElementById("app").textContent), |
| 206 | + ).toBe("v2"); |
| 207 | + }); |
| 208 | + |
| 209 | + it("reconnects manually with setOptionsAndConnect() after disconnect()", async () => { |
| 210 | + app = await createHotApp({ |
| 211 | + code: ` |
| 212 | + globalThis.hotClient = require(${JSON.stringify(CLIENT_ENTRY)}); |
| 213 | + document.getElementById("app").textContent = "v1"; |
| 214 | + if (module.hot) { |
| 215 | + module.hot.accept(); |
| 216 | + } |
| 217 | + `, |
| 218 | + }); |
| 219 | + ({ page, browser } = await runBrowser()); |
| 220 | + const console_ = collectConsole(page); |
| 221 | + |
| 222 | + await page.goto(app.url); |
| 223 | + await waitForAppText(page, "v1"); |
| 224 | + await console_.waitFor("connected"); |
| 225 | + |
| 226 | + // disconnect() drops the cached wrapper, so a manual connect starts a |
| 227 | + // fresh connection on the same path. |
| 228 | + await page.evaluate(() => { |
| 229 | + globalThis.hotClient.disconnect(); |
| 230 | + globalThis.hotClient.setOptionsAndConnect({}); |
| 231 | + }); |
| 232 | + await console_.waitForCount("connected", 2); |
| 233 | + |
| 234 | + app.edit(acceptedApp("v2")); |
| 235 | + await waitForAppText(page, "v2"); |
| 236 | + await console_.waitFor("App is up to date"); |
| 237 | + |
| 238 | + expect(normalizeConsole(console_.messages)).toMatchSnapshot(); |
168 | 239 | }); |
169 | 240 |
|
170 | 241 | it("routes server publish() payloads to subscribe handlers", async () => { |
|
0 commit comments