Last active
April 9, 2024 08:26
-
-
Save libetl/2303c441340bd2f28215c80ff084dfc4 to your computer and use it in GitHub Desktop.
msw-to-intercept-connector. instead of reading its('request.body'), use its('request.jsonBody')
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { HttpHandler } from 'msw' | |
import { setupWorker, type SetupWorker } from 'msw/browser' | |
export function prepareWorker(workerHolder: { worker: SetupWorker | undefined }, prefix: string, mockHandlers: (() => HttpHandler)[], callback: () => void) { | |
if (!workerHolder.worker) { | |
workerHolder.worker = setupWorker() | |
workerHolder.worker | |
.start({ | |
serviceWorker: { | |
url: `${prefix}/mockServiceWorker.js`, | |
}, | |
}) | |
.then(() => { | |
callback() | |
}) | |
connectWorkerToCypressIntercept(workerHolder.worker) | |
console.log('started Mock Service Worker') | |
} | |
workerHolder.worker.resetHandlers() | |
workerHolder.worker.use(...(mockHandlers?.map((fn: () => any) => fn()) || [])) | |
} | |
export function connectWorkerToCypressIntercept(worker: SetupWorker): void { | |
worker.events.on('request:start', async ({ request, requestId }) => { | |
if (!(window as any).cy) return | |
const cy = (window as any).cy | |
const mapping = Object.values(cy.state('routes')).map((route: any) => ({ | |
alias: route.alias, | |
method: route.options.method, | |
matcher: new RegExp( | |
route.options.url | |
.replace(/\*/g, '.*') | |
.replace(/\?/g, '\\?') | |
.replace(/\/$/, '/?') | |
.replace(/([^/$])$/, '$1/?$'), | |
'i', | |
), | |
})) | |
const aliases = new Set( | |
mapping | |
.filter(mappingElement => mappingElement.matcher.test(new URL(request.url).href) && request.method === mappingElement.method) | |
.map(matchingMapping => matchingMapping.alias), | |
) | |
;(request as any).requestWaited = false | |
;(request as any).responseWaited = true | |
;(request as any).id = requestId | |
// to be able to use its('request.body') | |
;(request as any).request = request | |
aliases.forEach(alias => { | |
cy.state('aliasedRequests').push({ request, alias }) | |
}) | |
let tee: [ReadableStream, ReadableStream] | undefined = undefined | |
try { | |
tee = (request as any).body?.tee?.() | |
} catch (e) {} | |
if (tee?.length === 2) { | |
const uint8 = (await tee[1].getReader().read()).value | |
if (!uint8 || !uint8.length) return | |
const data = JSON.parse(String.fromCharCode(...uint8)) | |
;(request as any).jsonBody = data | |
;(request as any).request.jsonBody = data | |
} | |
}) | |
worker.events.on('request:end', request => { | |
if (!(window as any).cy) return | |
;(request as any).state = 'Complete' | |
// cypress 13 now sets request.request = request. Yikes. | |
;(request as any).request.state = 'Complete' | |
}) | |
worker.events.on('response:mocked', async ({ response, requestId }) => { | |
if (!(window as any).cy) return | |
const cy = (window as any).cy | |
const requests = cy.state('aliasedRequests').filter(({ request }) => request.id === requestId) | |
const mockResponse = { | |
body: undefined, | |
} | |
let tee: [ReadableStream, ReadableStream] | undefined = undefined | |
try { | |
tee = response.body?.tee?.() | |
} catch (e) {} | |
requests.forEach(({ request }) => { | |
if (!request.response) { | |
;(request as any).responseWaited = tee | |
request.response = mockResponse | |
} | |
}) | |
if (tee?.length === 2) { | |
const uint8 = (await tee[1].getReader().read()).value | |
if (!uint8 || !uint8.length) return | |
const data = JSON.parse(String.fromCharCode(...uint8)) | |
mockResponse.body = data | |
requests.forEach(({ request }) => { | |
;(request as any).responseWaited = false | |
}) | |
} | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { prepareWorker } from './msw-to-intercept-connector' | |
// in my example, the decorator is used in storybook. | |
const decorator = (storyFn: () => any, { parameters }: any) => { | |
const [ready, setReady] = React.useState(!!workerHolder.worker) | |
prepareWorker(workerHolder, window.location.origin, parameters.mockHandlers, () => { | |
setReady(true) | |
}) | |
if (!ready) storyFn() // this seems stupid to do but all the hooks must be run | |
// even if not included in the first contentful paint | |
return !ready ? ( | |
<h1> | |
<img src="https://mswjs.io/_astro/msw.0b63bcd8.svg" alt="msw" /> | |
Starting mock service worker | |
</h1> | |
) : ( | |
storyFn() | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment