Skip to content

Commit

Permalink
feat: make life-cycle event listeners have object argument
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Jul 20, 2023
1 parent 789327a commit 47fd803
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 72 deletions.
13 changes: 10 additions & 3 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,20 +474,27 @@ bypass(request, {
## Life-cycle events
The life-cycle events listeners now accept a single argument being an object with contextual properties.
```diff
-server.events.on('request:start', (request, requestId) = {})
+server.events.on('request:start', ({ request, requestId}) => {})
```
The request and response instances exposed in the life-cycle API have also been updated to return Fetch API `Request` and `Response` respectively.
The request ID is now exposed as a standalone argument (previously, `req.id`).
```js
server.events.on('request:start', (request, requestId) => {
server.events.on('request:start', ({ request, requestId }) => {
console.log(request.method, request.url)
})
```
To read a request body, make sure to clone the request first. Otherwise, it won't be performed as it would be already read.
```js
server.events.on('request:match', async (request) => {
server.events.on('request:match', async ({ request }) => {
// Make sure to clone the request so it could be
// processed further down the line.
const clone = request.clone()
Expand All @@ -500,7 +507,7 @@ server.events.on('request:match', async (request) => {
The `response:*` events now always contain the response reference, the related request, and its id in the listener arguments.
```js
worker.events.on('response:mocked', (response, request, requestId) => {
worker.events.on('response:mocked', ({ response, request, requestId }) => {
console.log('response to %s %s is:', request.method, request.url, response)
})
```
Expand Down
10 changes: 6 additions & 4 deletions src/browser/setupWorker/start/createFallbackRequestListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function createFallbackRequestListener(
{
onMockedResponse(_, { handler, parsedRequest }) {
if (!options.quiet) {
context.emitter.once('response:mocked', (response) => {
context.emitter.once('response:mocked', ({ response }) => {
handler.log(requestCloneForLogs, response, parsedRequest)
})
}
Expand All @@ -48,9 +48,11 @@ export function createFallbackRequestListener(
({ response, isMockedResponse, request, requestId }) => {
context.emitter.emit(
isMockedResponse ? 'response:mocked' : 'response:bypass',
response,
request,
requestId,
{
response,
request,
requestId,
},
)
},
)
Expand Down
2 changes: 1 addition & 1 deletion src/browser/setupWorker/start/createRequestListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const createRequestListener = (
)

if (!options.quiet) {
context.emitter.once('response:mocked', (response) => {
context.emitter.once('response:mocked', ({ response }) => {
handler.log(requestCloneForLogs, response, parsedRequest)
})
}
Expand Down
21 changes: 7 additions & 14 deletions src/browser/setupWorker/start/createResponseListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,17 @@ export function createResponseListener(context: SetupWorkerInternalContext) {
? Response.error()
: new Response(responseJson.body, responseJson)

if (responseJson.isMockedResponse) {
context.emitter.emit(
'response:mocked',
context.emitter.emit(
responseJson.isMockedResponse ? 'response:mocked' : 'response:bypass',
{
response,
/**
* @todo @fixme In this context, we don't know anything about
* the request.
*/
null as any,
responseJson.requestId,
)
} else {
context.emitter.emit(
'response:bypass',
response,
null as any,
responseJson.requestId,
)
}
request: null as any,
requestId: responseJson.requestId,
},
)
}
}
52 changes: 45 additions & 7 deletions src/core/sharedOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,51 @@ export interface SharedOptions {
}

export type LifeCycleEventsMap = {
'request:start': [request: Request, requestId: string]
'request:match': [request: Request, requestId: string]
'request:unhandled': [request: Request, requestId: string]
'request:end': [request: Request, requestId: string]
'response:mocked': [response: Response, request: Request, requestId: string]
'response:bypass': [response: Response, request: Request, requestId: string]
unhandledException: [error: Error, request: Request, requestId: string]
'request:start': [
args: {
request: Request
requestId: string
},
]
'request:match': [
args: {
request: Request
requestId: string
},
]
'request:unhandled': [
args: {
request: Request
requestId: string
},
]
'request:end': [
args: {
request: Request
requestId: string
},
]
'response:mocked': [
args: {
response: Response
request: Request
requestId: string
},
]
'response:bypass': [
args: {
response: Response
request: Request
requestId: string
},
]
unhandledException: [
args: {
error: Error
request: Request
requestId: string
},
]
}

export type LifeCycleEventEmitter<
Expand Down
30 changes: 15 additions & 15 deletions src/core/utils/handleRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ test('returns undefined for a request with the "x-msw-intention" header equal to

expect(result).toBeUndefined()
expect(events).toEqual([
['request:start', request, requestId],
['request:end', request, requestId],
['request:start', { request, requestId }],
['request:end', { request, requestId }],
])
expect(options.onUnhandledRequest).not.toHaveBeenCalled()
expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
Expand Down Expand Up @@ -125,9 +125,9 @@ test('reports request as unhandled when it has no matching request handlers', as

expect(result).toBeUndefined()
expect(events).toEqual([
['request:start', request, requestId],
['request:unhandled', request, requestId],
['request:end', request, requestId],
['request:start', { request, requestId }],
['request:unhandled', { request, requestId }],
['request:end', { request, requestId }],
])
expect(options.onUnhandledRequest).toHaveBeenNthCalledWith(1, request, {
warning: expect.any(Function),
Expand Down Expand Up @@ -160,8 +160,8 @@ test('returns undefined on a request handler that returns no response', async ()

expect(result).toBeUndefined()
expect(events).toEqual([
['request:start', request, requestId],
['request:end', request, requestId],
['request:start', { request, requestId }],
['request:end', { request, requestId }],
])
expect(options.onUnhandledRequest).not.toHaveBeenCalled()
expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
Expand Down Expand Up @@ -205,9 +205,9 @@ test('returns the mocked response for a request with a matching request handler'

expect(result).toEqual(mockedResponse)
expect(events).toEqual([
['request:start', request, requestId],
['request:match', request, requestId],
['request:end', request, requestId],
['request:start', { request, requestId }],
['request:match', { request, requestId }],
['request:end', { request, requestId }],
])
expect(callbacks.onPassthroughResponse).not.toHaveBeenCalled()

Expand Down Expand Up @@ -278,9 +278,9 @@ test('returns a transformed response if the "transformResponse" option is provid
)

expect(events).toEqual([
['request:start', request, requestId],
['request:match', request, requestId],
['request:end', request, requestId],
['request:start', { request, requestId }],
['request:match', { request, requestId }],
['request:end', { request, requestId }],
])
expect(callbacks.onPassthroughResponse).not.toHaveBeenCalled()

Expand Down Expand Up @@ -336,8 +336,8 @@ it('returns undefined without warning on a passthrough request', async () => {

expect(result).toBeUndefined()
expect(events).toEqual([
['request:start', request, requestId],
['request:end', request, requestId],
['request:start', { request, requestId }],
['request:end', { request, requestId }],
])
expect(options.onUnhandledRequest).not.toHaveBeenCalled()
expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
Expand Down
22 changes: 13 additions & 9 deletions src/core/utils/handleRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ export async function handleRequest(
emitter: Emitter<LifeCycleEventsMap>,
handleRequestOptions?: HandleRequestOptions,
): Promise<Response | undefined> {
emitter.emit('request:start', request, requestId)
emitter.emit('request:start', { request, requestId })

// Perform bypassed requests (i.e. issued via "ctx.fetch") as-is.
if (request.headers.get('x-msw-intention') === 'bypass') {
emitter.emit('request:end', request, requestId)
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
Expand All @@ -63,16 +63,20 @@ export async function handleRequest(

if (lookupResult.error) {
// Allow developers to react to unhandled exceptions in request handlers.
emitter.emit('unhandledException', lookupResult.error, request, requestId)
emitter.emit('unhandledException', {
error: lookupResult.error,
request,
requestId,
})
throw lookupResult.error
}

// If the handler lookup returned nothing, no request handler was found
// matching this request. Report the request as unhandled.
if (!lookupResult.data) {
await onUnhandledRequest(request, handlers, options.onUnhandledRequest)
emitter.emit('request:unhandled', request, requestId)
emitter.emit('request:end', request, requestId)
emitter.emit('request:unhandled', { request, requestId })
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
Expand All @@ -82,7 +86,7 @@ export async function handleRequest(
// When the handled request returned no mocked response, warn the developer,
// as it may be an oversight on their part. Perform the request as-is.
if (!response) {
emitter.emit('request:end', request, requestId)
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
Expand All @@ -93,15 +97,15 @@ export async function handleRequest(
response.status === 302 &&
response.headers.get('x-msw-intention') === 'passthrough'
) {
emitter.emit('request:end', request, requestId)
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}

// Store all the received response cookies in the virtual cookie store.
readResponseCookies(request, response)

emitter.emit('request:match', request, requestId)
emitter.emit('request:match', { request, requestId })

const requiredLookupResult =
lookupResult.data as RequiredDeep<ResponseLookupResult>
Expand All @@ -115,7 +119,7 @@ export async function handleRequest(
requiredLookupResult,
)

emitter.emit('request:end', request, requestId)
emitter.emit('request:end', { request, requestId })

return transformedResponse
}
8 changes: 5 additions & 3 deletions src/node/SetupServerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ export class SetupServerApi
({ response, isMockedResponse, request, requestId }) => {
this.emitter.emit(
isMockedResponse ? 'response:mocked' : 'response:bypass',
response,
request,
requestId,
{
response,
request,
requestId,
},
)
},
)
Expand Down
4 changes: 2 additions & 2 deletions test/browser/msw-api/regression/handle-stream.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { setupWorker } from 'msw/browser'

const worker = setupWorker()

worker.events.on('response:bypass', async (res) => {
const responseText = await res.text()
worker.events.on('response:bypass', async ({ response }) => {
const responseText = await response.clone().text()
console.warn(`[response:bypass] ${responseText}`)
})

Expand Down
14 changes: 7 additions & 7 deletions test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,39 @@ const worker = setupWorker(
}),
)

worker.events.on('request:start', (request, requestId) => {
worker.events.on('request:start', ({ request, requestId }) => {
console.warn(`[request:start] ${request.method} ${request.url} ${requestId}`)
})

worker.events.on('request:match', (request, requestId) => {
worker.events.on('request:match', ({ request, requestId }) => {
console.warn(`[request:match] ${request.method} ${request.url} ${requestId}`)
})

worker.events.on('request:unhandled', (request, requestId) => {
worker.events.on('request:unhandled', ({ request, requestId }) => {
console.warn(
`[request:unhandled] ${request.method} ${request.url} ${requestId}`,
)
})

const requestEndListner: (
...args: LifeCycleEventsMap['request:end']
) => void = (request, requestId) => {
) => void = ({ request, requestId }) => {
console.warn(`[request:end] ${request.method} ${request.url} ${requestId}`)
}

worker.events.on('request:end', requestEndListner)

worker.events.on('response:mocked', async (response, request, requestId) => {
worker.events.on('response:mocked', async ({ response, requestId }) => {
const body = await response.clone().text()
console.warn(`[response:mocked] ${body} ${requestId}`)
})

worker.events.on('response:bypass', async (response, request, requestId) => {
worker.events.on('response:bypass', async ({ response, requestId }) => {
const body = await response.clone().text()
console.warn(`[response:bypass] ${body} ${requestId}`)
})

worker.events.on('unhandledException', (error, request, requestId) => {
worker.events.on('unhandledException', ({ error, request, requestId }) => {
console.warn(
`[unhandledException] ${request.method} ${request.url} ${requestId} ${error.message}`,
)
Expand Down
Loading

0 comments on commit 47fd803

Please sign in to comment.