Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fbb2400
test: adopt a hybrid unit + browser e2e model, shard CI, and snapshot…
bjohansebas Jul 25, 2026
711f0ef
test(e2e): snapshot the logging=log module detail and drop the covere…
bjohansebas Jul 25, 2026
969ec3f
test(e2e): move the reconnection tests to the browser and snapshot th…
bjohansebas Jul 25, 2026
b133e33
test(e2e): exercise the indicator's shared state with two real bundle…
bjohansebas Jul 25, 2026
c22ccda
test(e2e): cover the client option surface in the browser and slim th…
bjohansebas Jul 25, 2026
a7a52bc
test(e2e): assert the multi-bundle problem union in the browser
bjohansebas Jul 25, 2026
b01db52
test(e2e): collapse the rebuilding path in console snapshots
bjohansebas Jul 25, 2026
0a5c6e5
test(e2e): isolate the indicator shared-state tests from the hot client
bjohansebas Jul 25, 2026
cb7592d
test: drop a stray build artifact committed into fixtures
bjohansebas Jul 25, 2026
413541b
test: let test:only take file arguments without losing the e2e exclusion
bjohansebas Jul 25, 2026
a525f05
test(e2e): exercise the overlay's shared state with two real bundled …
bjohansebas Jul 25, 2026
0fef0a2
test(e2e): dismiss the overlay with real clicks
bjohansebas Jul 25, 2026
847721e
chore: gitignore the js3 fixture output
bjohansebas Jul 25, 2026
b8d7747
test(e2e): accumulate runtime errors in the browser and settle on web…
bjohansebas Jul 25, 2026
04968c2
fix(client): replace webpack's logging runtime with a self-contained …
bjohansebas Jul 26, 2026
bb135d3
test(e2e): enforce the Trusted Types CSP for real
bjohansebas Jul 26, 2026
3e86c54
fix(client): keep HMR alive when Trusted Types blocks the logging run…
bjohansebas Jul 26, 2026
1f591f5
test: prove SSE broadcast with two real pages and a mid-connection pu…
bjohansebas Jul 26, 2026
742c60e
test(e2e): poll app text on an interval so hidden pages resolve
bjohansebas Jul 26, 2026
e1b9eca
test(e2e): drive instance.invalidate() and instance.close() against a…
bjohansebas Jul 26, 2026
17a475c
test(e2e): keyboard pagination, paginate=false, and the cross-copy ru…
bjohansebas Jul 26, 2026
69b00b1
test: drop two overlay unit tests the e2e suite already covers
bjohansebas Jul 26, 2026
76e9b0f
test(e2e): cover pagination clamping and page retention, drop the jsd…
bjohansebas Jul 26, 2026
eaff5d2
test: drop two more overlay unit tests the e2e suite covers
bjohansebas Jul 26, 2026
3ff5d95
test: assert the dismiss hint where it is acted on
bjohansebas Jul 26, 2026
e29bef1
test(e2e): assert linkification on real error messages
bjohansebas Jul 26, 2026
6a7670a
test: drop the hot unit tests the browser suites already prove
bjohansebas Jul 26, 2026
6d89ed4
test(e2e): snapshot the console wherever it is asserted
bjohansebas Jul 26, 2026
2e0fb14
test(e2e): problem-type transitions, runtimeErrors=false, warning upd…
bjohansebas Jul 26, 2026
25d5dc5
test(e2e): read the SSE protocol raw off a real server
bjohansebas Jul 26, 2026
60432e2
test(e2e): make the warning-dedup sequence independent of the catch-u…
bjohansebas Jul 26, 2026
d9614bb
test(e2e): adopt webpack-dev-server's 400s test cap for cold CI runners
bjohansebas Jul 26, 2026
68d8e29
test(e2e): share the waits, fixtures, and teardown across the browser…
bjohansebas Jul 26, 2026
2904197
test(e2e): click the backdrop by raw mouse coordinates
bjohansebas Jul 26, 2026
309d187
test(e2e): open the clicked file reference through a real endpoint
bjohansebas Jul 26, 2026
13c7722
test(e2e): feed the client real heartbeats and a malformed frame
bjohansebas Jul 26, 2026
ca99266
test(e2e): finish the client migration — runtime public path and no-E…
bjohansebas Jul 26, 2026
443decb
test(e2e): pin only the first three connects of the watchdog cycle
bjohansebas Jul 26, 2026
c624688
test(e2e): process the update paths against the real HMR runtime
bjohansebas Jul 26, 2026
1d41b29
test(e2e): click frames through DOM-domain coordinates
bjohansebas Jul 26, 2026
13218bd
test(e2e): the pre-lock catch-up race and the missing-update-chunk path
bjohansebas Jul 26, 2026
7917f70
test: drop the last process-update unit
bjohansebas Jul 26, 2026
da0e720
test(e2e): the React-boundary heuristic and the runtime slot reset, f…
bjohansebas Jul 26, 2026
434e51b
test(e2e): assert the badge and file-path rendering on real errors
bjohansebas Jul 26, 2026
8f13db3
test(e2e): retire the last jsdom suite
bjohansebas Jul 26, 2026
d6167b3
ci: collect server-side coverage from the e2e phase
bjohansebas Jul 26, 2026
3863108
test(e2e): let the watcher settle before frame-level assertions
bjohansebas Jul 26, 2026
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
33 changes: 30 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,20 @@ jobs:
run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi

test:
name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }}
name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }} (${{ matrix.shard }})

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [20.x, 22.x, 24.x, 25.x]
webpack-version: [latest]
shard: ["1/4", "2/4", "3/4", "4/4"]

runs-on: ${{ matrix.os }}

concurrency:
group: test-${{ matrix.os }}-v${{ matrix.node-version }}-${{ matrix.webpack-version }}-${{ github.ref }}
group: test-${{ matrix.os }}-v${{ matrix.node-version }}-${{ matrix.webpack-version }}-${{ matrix.shard }}-${{ github.ref }}
cancel-in-progress: true

steps:
Expand All @@ -84,9 +85,35 @@ jobs:
run: npm ci

- name: Run tests for webpack version ${{ matrix.webpack-version }}
run: npm run test:coverage -- --ci
run: npm run test:coverage -- --ci --shard=${{ matrix.shard }}

- name: Run browser e2e tests
run: npm run test:e2e -- --ci --shard=${{ matrix.shard }} --collectCoverageFrom="src/**/*.js" --coverage --coverageDirectory=coverage-e2e

- name: Upload coverage artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage-${{ matrix.os }}-${{ matrix.node-version }}-${{ strategy.job-index }}
path: |
coverage/lcov.info
coverage-e2e/lcov.info

upload-coverage:
name: Upload coverage to codecov
needs: test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Download coverage artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: coverage-*
path: coverage

- name: Submit coverage data to codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: coverage
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ Thumbs.db
*.sublime-project
*.sublime-workspace
yarn.lock
/test/fixtures/js3
/coverage-e2e
23 changes: 18 additions & 5 deletions client-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ function createEventSourceWrapper() {
let timer;
/** @type {ReturnType<typeof setTimeout>} */
let reconnectTimer;
let closed = false;

const handleOnline = () => {
log.info("connected");
Expand All @@ -162,18 +163,30 @@ function createEventSourceWrapper() {
};

/**
* Close the connection and stop the activity timer without scheduling a
* reconnection. A reconnection that is already pending is cancelled too, so
* closing during the reconnect window really is final.
* Tear the current connection down without deciding whether it is final.
*/
const close = () => {
const stop = () => {
clearInterval(timer);
clearTimeout(reconnectTimer);
source.close();
};

/**
* Close for good: no reconnection is scheduled, a pending one is cancelled,
* and error events already queued behind the close (the EventSource fires
* one when its connection dies) can no longer resurrect the wrapper.
*/
const close = () => {
closed = true;
stop();
};

const handleDisconnect = () => {
close();
if (closed) {
return;
}

stop();
reconnectTimer = setTimeout(init, /** @type {number} */ (options.timeout));
};

Expand Down
30 changes: 29 additions & 1 deletion client-src/utils/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,32 @@ export function setLogLevel(level) {

setLogLevel(DEFAULT_LEVEL);

export const log = logger.getLogger(LOGGER_NAME);
const rawLog = logger.getLogger(LOGGER_NAME);

/**
* Guard a logger method: under a `require-trusted-types-for 'script'`
* Content Security Policy, tapable (bundled through webpack's logging
* runtime) cannot compile its hooks — `new Function` throws an EvalError on
* the first log call. Swallowing it keeps HMR fully functional with logging
* off instead of breaking whatever listener happened to log.
* @param {string} method logger method name
* @returns {(...args: unknown[]) => void} guarded method
*/
function guarded(method) {
return (...args) => {
try {
rawLog[method](...args);
} catch {
// Logging is unavailable (e.g. Trusted Types enforcement).
}
};
}

export const log = {
error: guarded("error"),
warn: guarded("warn"),
info: guarded("info"),
log: guarded("log"),
groupCollapsed: guarded("groupCollapsed"),
groupEnd: guarded("groupEnd"),
};
7 changes: 7 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
// The browser e2e suites run serially through `npm run test:e2e` — excluded
// here so the regular run stays parallel. The ignore lifts itself when a
// test/e2e path is requested explicitly (a CLI flag would swallow positional
// file arguments, --testPathIgnorePatterns being variadic).
const runningE2E = process.argv.some((arg) => arg.includes("test/e2e"));

module.exports = {
testEnvironment: "node",
collectCoverage: false,
coveragePathIgnorePatterns: ["test", "<rootDir>/node_modules"],
moduleFileExtensions: ["js", "json"],
testMatch: ["**/test/**/*.test.js"],
testPathIgnorePatterns: runningE2E ? [] : ["/node_modules/", "/test/e2e/"],
setupFilesAfterEnv: ["<rootDir>/setupTest.js"],
globalSetup: "./test/helpers/globalSetup.js",
snapshotResolver: "./test/helpers/snapshotResolver.js",
Expand Down
Loading
Loading