diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 524664c..4e0b996 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: version: 22.15.29 msw: specifier: ^2.10.3 - version: 2.10.3(@types/node@22.15.29)(typescript@5.8.3) + version: 2.10.4(@types/node@22.15.29)(typescript@5.8.3) tsdown: specifier: ^0.12.7 version: 0.12.7(typescript@5.8.3) @@ -248,8 +248,8 @@ packages: '@hono/node-server': ^1.11.1 hono: ^4.6.0 - '@inquirer/confirm@5.1.12': - resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} + '@inquirer/confirm@5.1.14': + resolution: {integrity: sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -257,8 +257,8 @@ packages: '@types/node': optional: true - '@inquirer/core@10.1.13': - resolution: {integrity: sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==} + '@inquirer/core@10.1.15': + resolution: {integrity: sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -266,12 +266,12 @@ packages: '@types/node': optional: true - '@inquirer/figures@1.0.12': - resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} + '@inquirer/figures@1.0.13': + resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} engines: {node: '>=18'} - '@inquirer/type@3.0.7': - resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==} + '@inquirer/type@3.0.8': + resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -532,8 +532,8 @@ packages: '@types/semver@7.7.0': resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} - '@types/statuses@2.0.5': - resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -919,8 +919,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.10.3: - resolution: {integrity: sha512-rpqW4wIqISJlgDfu3tiqzuWC/d6jofSuMUsBu1rwepzSwX21aQoagsd+fjahJ8sewa6FwlYhu4no+jfGVQm2IA==} + msw@2.10.4: + resolution: {integrity: sha512-6R1or/qyele7q3RyPwNuvc0IxO8L8/Aim6Sz5ncXEgcWUNxSKE+udriTOWHtpMwmfkLYlacA2y7TIx4cL5lgHA==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -1122,8 +1122,8 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} stream-combiner2@1.1.1: @@ -1364,7 +1364,7 @@ snapshots: '@bundled-es-modules/statuses@1.0.1': dependencies: - statuses: 2.0.1 + statuses: 2.0.2 '@bundled-es-modules/tough-cookie@0.1.6': dependencies: @@ -1487,17 +1487,17 @@ snapshots: - bufferutil - utf-8-validate - '@inquirer/confirm@5.1.12(@types/node@22.15.29)': + '@inquirer/confirm@5.1.14(@types/node@22.15.29)': dependencies: - '@inquirer/core': 10.1.13(@types/node@22.15.29) - '@inquirer/type': 3.0.7(@types/node@22.15.29) + '@inquirer/core': 10.1.15(@types/node@22.15.29) + '@inquirer/type': 3.0.8(@types/node@22.15.29) optionalDependencies: '@types/node': 22.15.29 - '@inquirer/core@10.1.13(@types/node@22.15.29)': + '@inquirer/core@10.1.15(@types/node@22.15.29)': dependencies: - '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@22.15.29) + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@22.15.29) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -1507,9 +1507,9 @@ snapshots: optionalDependencies: '@types/node': 22.15.29 - '@inquirer/figures@1.0.12': {} + '@inquirer/figures@1.0.13': {} - '@inquirer/type@3.0.7(@types/node@22.15.29)': + '@inquirer/type@3.0.8(@types/node@22.15.29)': optionalDependencies: '@types/node': 22.15.29 @@ -1730,7 +1730,7 @@ snapshots: '@types/semver@7.7.0': {} - '@types/statuses@2.0.5': {} + '@types/statuses@2.0.6': {} '@types/tough-cookie@4.0.5': {} @@ -2072,17 +2072,17 @@ snapshots: ms@2.1.3: {} - msw@2.10.3(@types/node@22.15.29)(typescript@5.8.3): + msw@2.10.4(@types/node@22.15.29)(typescript@5.8.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.12(@types/node@22.15.29) + '@inquirer/confirm': 5.1.14(@types/node@22.15.29) '@mswjs/interceptors': 0.39.2 '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 - '@types/statuses': 2.0.5 + '@types/statuses': 2.0.6 graphql: 16.11.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 @@ -2321,7 +2321,7 @@ snapshots: split2@4.2.0: {} - statuses@2.0.1: {} + statuses@2.0.2: {} stream-combiner2@1.1.1: dependencies: diff --git a/src/index.ts b/src/index.ts index 8945845..7703ed7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { RequestHandler, WebSocketHandler, getResponse, + type UnhandledRequestStrategy, } from 'msw' import { type WebSocketClientEventMap, @@ -25,6 +26,7 @@ import { export interface CreateNetworkFixtureArgs { initialHandlers: Array + onUnhandledRequest?: UnhandledRequestStrategy } /** @@ -48,7 +50,6 @@ export interface CreateNetworkFixtureArgs { */ export function createNetworkFixture( args?: CreateNetworkFixtureArgs, - /** @todo `onUnhandledRequest`? */ ): [ TestFixture, { auto: boolean }, @@ -58,9 +59,10 @@ export function createNetworkFixture( const worker = new NetworkFixture({ page, initialHandlers: args?.initialHandlers || [], + onUnhandledRequest: args?.onUnhandledRequest || 'warn', }) - await worker.start() + await worker.start({onUnhandledRequest: args?.onUnhandledRequest}) await use(worker) await worker.stop() }, @@ -74,12 +76,13 @@ export class NetworkFixture extends SetupApi { constructor(args: { page: Page initialHandlers: Array + onUnhandledRequest?: UnhandledRequestStrategy }) { super(...args.initialHandlers) this.#page = args.page } - public async start() { + public async start({ onUnhandledRequest }: { onUnhandledRequest?: UnhandledRequestStrategy } = {}) { // Handle HTTP requests. await this.#page.route(/.+/, async (route, request) => { const fetchRequest = new Request(request.url(), { @@ -112,6 +115,10 @@ export class NetworkFixture extends SetupApi { : undefined, }) return + } else { + if (typeof onUnhandledRequest === 'function') { + await onUnhandledRequest(fetchRequest, { warning: console.warn, error: console.error }) + } } route.continue() diff --git a/tests/auto.test.ts b/tests/auto.test.ts index 2f482aa..0a7cbd5 100644 --- a/tests/auto.test.ts +++ b/tests/auto.test.ts @@ -6,6 +6,8 @@ interface Fixtures { network: NetworkFixture } +const unhandledRoutes: string[] = []; + const test = testBase.extend({ network: createNetworkFixture({ initialHandlers: [ @@ -13,6 +15,10 @@ const test = testBase.extend({ return new Response('hello world') }), ], + onUnhandledRequest: (request) => { + // store unhandled routes so we can assert on them + unhandledRoutes.push(new URL(request.url).pathname); + }, }), }) @@ -27,3 +33,17 @@ test('automatically applies the network fixture', async ({ page }) => { expect(data).toBe('hello world') }) + +test("invokes the onUnhandledRequest handler", async ({ + page, +}) => { + await page.goto('/') + + const data = await page.evaluate(() => { + return fetch('/unhandled-resource').then((response) => { + return; // no-op + }) + }) + + expect(unhandledRoutes).toContain("/unhandled-resource"); +}); \ No newline at end of file