Skip to content

Instantly share code, notes, and snippets.

@libetl
Last active April 9, 2024 08:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save libetl/2303c441340bd2f28215c80ff084dfc4 to your computer and use it in GitHub Desktop.
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')
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
})
}
})
}
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