msw 2.10.4 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/lib/core/{HttpResponse-C7FhBLaS.d.mts → HttpResponse-DiuKTgC7.d.mts} +21 -7
  2. package/lib/core/{HttpResponse-DWu36LsY.d.ts → HttpResponse-DlQEvD4q.d.ts} +21 -7
  3. package/lib/core/HttpResponse.d.mts +1 -1
  4. package/lib/core/HttpResponse.d.ts +1 -1
  5. package/lib/core/SetupApi.d.mts +1 -1
  6. package/lib/core/SetupApi.d.ts +1 -1
  7. package/lib/core/getResponse.d.mts +1 -1
  8. package/lib/core/getResponse.d.ts +1 -1
  9. package/lib/core/graphql.d.mts +2 -2
  10. package/lib/core/graphql.d.ts +2 -2
  11. package/lib/core/graphql.js +2 -8
  12. package/lib/core/graphql.js.map +1 -1
  13. package/lib/core/graphql.mjs +2 -8
  14. package/lib/core/graphql.mjs.map +1 -1
  15. package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
  16. package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
  17. package/lib/core/handlers/GraphQLHandler.js +36 -9
  18. package/lib/core/handlers/GraphQLHandler.js.map +1 -1
  19. package/lib/core/handlers/GraphQLHandler.mjs +36 -9
  20. package/lib/core/handlers/GraphQLHandler.mjs.map +1 -1
  21. package/lib/core/handlers/HttpHandler.d.mts +15 -5
  22. package/lib/core/handlers/HttpHandler.d.ts +15 -5
  23. package/lib/core/handlers/HttpHandler.js +21 -10
  24. package/lib/core/handlers/HttpHandler.js.map +1 -1
  25. package/lib/core/handlers/HttpHandler.mjs +21 -10
  26. package/lib/core/handlers/HttpHandler.mjs.map +1 -1
  27. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  28. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  29. package/lib/core/handlers/RequestHandler.js +1 -1
  30. package/lib/core/handlers/RequestHandler.js.map +1 -1
  31. package/lib/core/handlers/RequestHandler.mjs +1 -1
  32. package/lib/core/handlers/RequestHandler.mjs.map +1 -1
  33. package/lib/core/http.d.mts +4 -4
  34. package/lib/core/http.d.ts +4 -4
  35. package/lib/core/http.js +2 -2
  36. package/lib/core/http.js.map +1 -1
  37. package/lib/core/http.mjs +2 -2
  38. package/lib/core/http.mjs.map +1 -1
  39. package/lib/core/index.d.mts +2 -2
  40. package/lib/core/index.d.ts +2 -2
  41. package/lib/core/index.js.map +1 -1
  42. package/lib/core/index.mjs.map +1 -1
  43. package/lib/core/passthrough.d.mts +1 -1
  44. package/lib/core/passthrough.d.ts +1 -1
  45. package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
  46. package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
  47. package/lib/core/utils/executeHandlers.d.mts +1 -1
  48. package/lib/core/utils/executeHandlers.d.ts +1 -1
  49. package/lib/core/utils/handleRequest.d.mts +1 -1
  50. package/lib/core/utils/handleRequest.d.ts +1 -1
  51. package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
  52. package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
  53. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
  54. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
  55. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  56. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  57. package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
  58. package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
  59. package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
  60. package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
  61. package/lib/iife/index.js +62 -30
  62. package/lib/iife/index.js.map +1 -1
  63. package/lib/mockServiceWorker.js +1 -1
  64. package/package.json +2 -2
  65. package/src/core/graphql.ts +8 -12
  66. package/src/core/handlers/GraphQLHandler.test.ts +86 -51
  67. package/src/core/handlers/GraphQLHandler.ts +81 -17
  68. package/src/core/handlers/HttpHandler.test.ts +60 -30
  69. package/src/core/handlers/HttpHandler.ts +61 -12
  70. package/src/core/handlers/RequestHandler.ts +2 -2
  71. package/src/core/http.ts +5 -5
  72. package/src/core/index.ts +4 -0
  73. package/src/core/utils/handleRequest.test.ts +84 -0
@@ -1,6 +1,4 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
1
+ // @vitest-environment jsdom
4
2
  import { createRequestId } from '@mswjs/interceptors'
5
3
  import { HttpHandler, HttpRequestResolverExtras } from './HttpHandler'
6
4
  import { HttpResponse } from '..'
@@ -13,7 +11,7 @@ const resolver: ResponseResolver<
13
11
  }
14
12
 
15
13
  describe('info', () => {
16
- test('exposes request handler information', () => {
14
+ it('exposes request handler information', () => {
17
15
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
18
16
 
19
17
  expect(handler.info.header).toEqual('GET /user/:userId')
@@ -24,7 +22,7 @@ describe('info', () => {
24
22
  })
25
23
 
26
24
  describe('parse', () => {
27
- test('parses a URL given a matching request', async () => {
25
+ it('parses a URL given a matching request', async () => {
28
26
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
29
27
  const request = new Request(new URL('/user/abc-123', location.href))
30
28
 
@@ -39,7 +37,7 @@ describe('parse', () => {
39
37
  })
40
38
  })
41
39
 
42
- test('parses a URL and ignores the request method', async () => {
40
+ it('parses a URL and ignores the request method', async () => {
43
41
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
44
42
  const request = new Request(new URL('/user/def-456', location.href), {
45
43
  method: 'POST',
@@ -56,7 +54,7 @@ describe('parse', () => {
56
54
  })
57
55
  })
58
56
 
59
- test('returns negative match result given a non-matching request', async () => {
57
+ it('returns negative match result given a non-matching request', async () => {
60
58
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
61
59
  const request = new Request(new URL('/login', location.href))
62
60
 
@@ -71,21 +69,21 @@ describe('parse', () => {
71
69
  })
72
70
 
73
71
  describe('predicate', () => {
74
- test('returns true given a matching request', async () => {
72
+ it('returns true given a matching request', async () => {
75
73
  const handler = new HttpHandler('POST', '/login', resolver)
76
74
  const request = new Request(new URL('/login', location.href), {
77
75
  method: 'POST',
78
76
  })
79
77
 
80
- expect(
78
+ await expect(
81
79
  handler.predicate({
82
80
  request,
83
81
  parsedResult: await handler.parse({ request }),
84
82
  }),
85
- ).toBe(true)
83
+ ).resolves.toBe(true)
86
84
  })
87
85
 
88
- test('respects RegExp as the request method', async () => {
86
+ it('supports RegExp as the request method', async () => {
89
87
  const handler = new HttpHandler(/.+/, '/login', resolver)
90
88
  const requests = [
91
89
  new Request(new URL('/login', location.href)),
@@ -94,30 +92,60 @@ describe('predicate', () => {
94
92
  ]
95
93
 
96
94
  for (const request of requests) {
97
- expect(
95
+ await expect(
98
96
  handler.predicate({
99
97
  request,
100
98
  parsedResult: await handler.parse({ request }),
101
99
  }),
102
- ).toBe(true)
100
+ ).resolves.toBe(true)
103
101
  }
104
102
  })
105
103
 
106
- test('returns false given a non-matching request', async () => {
104
+ it('returns false given a non-matching request', async () => {
107
105
  const handler = new HttpHandler('POST', '/login', resolver)
108
106
  const request = new Request(new URL('/user/abc-123', location.href))
109
107
 
110
- expect(
108
+ await expect(
111
109
  handler.predicate({
112
110
  request,
113
111
  parsedResult: await handler.parse({ request }),
114
112
  }),
115
- ).toBe(false)
113
+ ).resolves.toBe(false)
114
+ })
115
+
116
+ it('supports custom predicate function', async () => {
117
+ const handler = new HttpHandler(
118
+ 'GET',
119
+ ({ request }) => {
120
+ return new URL(request.url).searchParams.get('a') === '1'
121
+ },
122
+ resolver,
123
+ )
124
+
125
+ {
126
+ const request = new Request(new URL('/login?a=1', location.href))
127
+ await expect(
128
+ handler.predicate({
129
+ request,
130
+ parsedResult: await handler.parse({ request }),
131
+ }),
132
+ ).resolves.toBe(true)
133
+ }
134
+
135
+ {
136
+ const request = new Request(new URL('/login', location.href))
137
+ await expect(
138
+ handler.predicate({
139
+ request,
140
+ parsedResult: await handler.parse({ request }),
141
+ }),
142
+ ).resolves.toBe(false)
143
+ }
116
144
  })
117
145
  })
118
146
 
119
147
  describe('test', () => {
120
- test('returns true given a matching request', async () => {
148
+ it('returns true given a matching request', async () => {
121
149
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
122
150
  const firstTest = await handler.test({
123
151
  request: new Request(new URL('/user/abc-123', location.href)),
@@ -130,7 +158,7 @@ describe('test', () => {
130
158
  expect(secondTest).toBe(true)
131
159
  })
132
160
 
133
- test('returns false given a non-matching request', async () => {
161
+ it('returns false given a non-matching request', async () => {
134
162
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
135
163
  const firstTest = await handler.test({
136
164
  request: new Request(new URL('/login', location.href)),
@@ -149,7 +177,7 @@ describe('test', () => {
149
177
  })
150
178
 
151
179
  describe('run', () => {
152
- test('returns a mocked response given a matching request', async () => {
180
+ it('returns a mocked response given a matching request', async () => {
153
181
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
154
182
  const request = new Request(new URL('/user/abc-123', location.href))
155
183
  const requestId = createRequestId()
@@ -169,10 +197,12 @@ describe('run', () => {
169
197
  expect(result!.request.url).toBe('http://localhost/user/abc-123')
170
198
  expect(result!.response?.status).toBe(200)
171
199
  expect(result!.response?.statusText).toBe('OK')
172
- expect(await result?.response?.json()).toEqual({ userId: 'abc-123' })
200
+ await expect(result?.response?.json()).resolves.toEqual({
201
+ userId: 'abc-123',
202
+ })
173
203
  })
174
204
 
175
- test('returns null given a non-matching request', async () => {
205
+ it('returns null given a non-matching request', async () => {
176
206
  const handler = new HttpHandler('POST', '/login', resolver)
177
207
  const result = await handler.run({
178
208
  request: new Request(new URL('/users', location.href)),
@@ -182,7 +212,7 @@ describe('run', () => {
182
212
  expect(result).toBeNull()
183
213
  })
184
214
 
185
- test('returns an empty "params" object given request with no URL parameters', async () => {
215
+ it('returns an empty "params" object given request with no URL parameters', async () => {
186
216
  const handler = new HttpHandler('GET', '/users', resolver)
187
217
  const result = await handler.run({
188
218
  request: new Request(new URL('/users', location.href)),
@@ -192,7 +222,7 @@ describe('run', () => {
192
222
  expect(result?.parsedResult?.match?.params).toEqual({})
193
223
  })
194
224
 
195
- test('exhausts resolver until its generator completes', async () => {
225
+ it('exhausts resolver until its generator completes', async () => {
196
226
  const handler = new HttpHandler('GET', '/users', function* () {
197
227
  let count = 0
198
228
 
@@ -212,12 +242,12 @@ describe('run', () => {
212
242
  return result?.response?.text()
213
243
  }
214
244
 
215
- expect(await run()).toBe('pending')
216
- expect(await run()).toBe('pending')
217
- expect(await run()).toBe('pending')
218
- expect(await run()).toBe('pending')
219
- expect(await run()).toBe('pending')
220
- expect(await run()).toBe('complete')
221
- expect(await run()).toBe('complete')
245
+ await expect(run()).resolves.toBe('pending')
246
+ await expect(run()).resolves.toBe('pending')
247
+ await expect(run()).resolves.toBe('pending')
248
+ await expect(run()).resolves.toBe('pending')
249
+ await expect(run()).resolves.toBe('pending')
250
+ await expect(run()).resolves.toBe('complete')
251
+ await expect(run()).resolves.toBe('complete')
222
252
  })
223
253
  })
@@ -25,7 +25,7 @@ type HttpHandlerMethod = string | RegExp
25
25
 
26
26
  export interface HttpHandlerInfo extends RequestHandlerDefaultInfo {
27
27
  method: HttpHandlerMethod
28
- path: Path
28
+ path: HttpRequestPredicate<PathParams>
29
29
  }
30
30
 
31
31
  export enum HttpMethods {
@@ -52,6 +52,24 @@ export type HttpRequestResolverExtras<Params extends PathParams> = {
52
52
  cookies: Record<string, string>
53
53
  }
54
54
 
55
+ export type HttpCustomPredicate<Params extends PathParams> = (args: {
56
+ request: Request
57
+ cookies: Record<string, string>
58
+ }) =>
59
+ | HttpCustomPredicateResult<Params>
60
+ | Promise<HttpCustomPredicateResult<Params>>
61
+
62
+ export type HttpCustomPredicateResult<Params extends PathParams> =
63
+ | boolean
64
+ | {
65
+ matches: boolean
66
+ params: Params
67
+ }
68
+
69
+ export type HttpRequestPredicate<Params extends PathParams> =
70
+ | Path
71
+ | HttpCustomPredicate<Params>
72
+
55
73
  /**
56
74
  * Request handler for HTTP requests.
57
75
  * Provides request matching based on method and URL.
@@ -63,14 +81,17 @@ export class HttpHandler extends RequestHandler<
63
81
  > {
64
82
  constructor(
65
83
  method: HttpHandlerMethod,
66
- path: Path,
84
+ predicate: HttpRequestPredicate<PathParams>,
67
85
  resolver: ResponseResolver<HttpRequestResolverExtras<any>, any, any>,
68
86
  options?: RequestHandlerOptions,
69
87
  ) {
88
+ const displayPath =
89
+ typeof predicate === 'function' ? '[custom predicate]' : predicate
90
+
70
91
  super({
71
92
  info: {
72
- header: `${method} ${path}`,
73
- path,
93
+ header: `${method}${displayPath ? ` ${displayPath}` : ''}`,
94
+ path: predicate,
74
95
  method,
75
96
  },
76
97
  resolver,
@@ -83,7 +104,7 @@ export class HttpHandler extends RequestHandler<
83
104
  private checkRedundantQueryParameters() {
84
105
  const { method, path } = this.info
85
106
 
86
- if (path instanceof RegExp) {
107
+ if (!path || path instanceof RegExp || typeof path === 'function') {
87
108
  return
88
109
  }
89
110
 
@@ -95,7 +116,7 @@ export class HttpHandler extends RequestHandler<
95
116
  }
96
117
 
97
118
  const searchParams = getSearchParams(path)
98
- const queryParams: string[] = []
119
+ const queryParams: Array<string> = []
99
120
 
100
121
  searchParams.forEach((_, paramName) => {
101
122
  queryParams.push(paramName)
@@ -111,20 +132,48 @@ export class HttpHandler extends RequestHandler<
111
132
  resolutionContext?: ResponseResolutionContext
112
133
  }) {
113
134
  const url = new URL(args.request.url)
114
- const match = matchRequestUrl(
115
- url,
116
- this.info.path,
117
- args.resolutionContext?.baseUrl,
118
- )
119
135
  const cookies = getAllRequestCookies(args.request)
120
136
 
137
+ /**
138
+ * Handle custom predicate functions.
139
+ * @note Invoke this during parsing so the user can parse the path parameters
140
+ * manually. Otherwise, `params` is always an empty object, which isn't nice.
141
+ */
142
+ if (typeof this.info.path === 'function') {
143
+ const customPredicateResult = await this.info.path({
144
+ request: args.request,
145
+ cookies,
146
+ })
147
+
148
+ const match =
149
+ typeof customPredicateResult === 'boolean'
150
+ ? {
151
+ matches: customPredicateResult,
152
+ params: {},
153
+ }
154
+ : customPredicateResult
155
+
156
+ return {
157
+ match,
158
+ cookies,
159
+ }
160
+ }
161
+
162
+ const match = this.info.path
163
+ ? matchRequestUrl(url, this.info.path, args.resolutionContext?.baseUrl)
164
+ : { matches: false, params: {} }
165
+
121
166
  return {
122
167
  match,
123
168
  cookies,
124
169
  }
125
170
  }
126
171
 
127
- predicate(args: { request: Request; parsedResult: HttpRequestParsedResult }) {
172
+ async predicate(args: {
173
+ request: Request
174
+ parsedResult: HttpRequestParsedResult
175
+ resolutionContext?: ResponseResolutionContext
176
+ }) {
128
177
  const hasMatchingMethod = this.matchMethod(args.request.method)
129
178
  const hasMatchingUrl = args.parsedResult.match.matches
130
179
  return hasMatchingMethod && hasMatchingUrl
@@ -179,7 +179,7 @@ export abstract class RequestHandler<
179
179
  request: Request
180
180
  parsedResult: ParsedResult
181
181
  resolutionContext?: ResponseResolutionContext
182
- }): boolean
182
+ }): boolean | Promise<boolean>
183
183
 
184
184
  /**
185
185
  * Print out the successfully handled request.
@@ -273,7 +273,7 @@ export abstract class RequestHandler<
273
273
  request: args.request,
274
274
  resolutionContext: args.resolutionContext,
275
275
  })
276
- const shouldInterceptRequest = this.predicate({
276
+ const shouldInterceptRequest = await this.predicate({
277
277
  request: args.request,
278
278
  parsedResult,
279
279
  resolutionContext: args.resolutionContext,
package/src/core/http.ts CHANGED
@@ -7,8 +7,9 @@ import {
7
7
  HttpMethods,
8
8
  HttpHandler,
9
9
  HttpRequestResolverExtras,
10
+ HttpRequestPredicate,
10
11
  } from './handlers/HttpHandler'
11
- import type { Path, PathParams } from './utils/matching/matchRequestUrl'
12
+ import type { PathParams } from './utils/matching/matchRequestUrl'
12
13
 
13
14
  export type HttpRequestHandler = <
14
15
  Params extends PathParams<keyof Params> = PathParams,
@@ -18,9 +19,8 @@ export type HttpRequestHandler = <
18
19
  // returns plain "Response" and the one returning "HttpResponse"
19
20
  // to enforce a stricter response body type.
20
21
  ResponseBodyType extends DefaultBodyType = undefined,
21
- RequestPath extends Path = Path,
22
22
  >(
23
- path: RequestPath,
23
+ predicate: HttpRequestPredicate<Params>,
24
24
  resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
25
25
  options?: RequestHandlerOptions,
26
26
  ) => HttpHandler
@@ -38,8 +38,8 @@ export type HttpResponseResolver<
38
38
  function createHttpHandler<Method extends HttpMethods | RegExp>(
39
39
  method: Method,
40
40
  ): HttpRequestHandler {
41
- return (path, resolver, options = {}) => {
42
- return new HttpHandler(method, path, resolver, options)
41
+ return (predicate, resolver, options = {}) => {
42
+ return new HttpHandler(method, predicate, resolver, options)
43
43
  }
44
44
  }
45
45
 
package/src/core/index.ts CHANGED
@@ -42,6 +42,7 @@ export type {
42
42
  export type {
43
43
  RequestQuery,
44
44
  HttpRequestParsedResult,
45
+ HttpCustomPredicate,
45
46
  } from './handlers/HttpHandler'
46
47
  export type { HttpRequestHandler, HttpResponseResolver } from './http'
47
48
 
@@ -49,7 +50,10 @@ export type {
49
50
  GraphQLQuery,
50
51
  GraphQLVariables,
51
52
  GraphQLRequestBody,
53
+ GraphQLResponseBody,
52
54
  GraphQLJsonRequestBody,
55
+ GraphQLOperationType,
56
+ GraphQLCustomPredicate,
53
57
  } from './handlers/GraphQLHandler'
54
58
  export type { GraphQLRequestHandler, GraphQLResponseResolver } from './graphql'
55
59
 
@@ -487,3 +487,87 @@ describe('[Private] - resolutionContext - used for extensions', () => {
487
487
  })
488
488
  })
489
489
  })
490
+
491
+ describe('handler with custom predicate', () => {
492
+ test('matches if custom predicate returns true', async () => {
493
+ const { emitter, events } = setup()
494
+
495
+ const requestId = createRequestId()
496
+ const request = new Request(new URL('http://localhost/login'), {
497
+ method: 'POST',
498
+ body: JSON.stringify({ username: 'test', password: 'password' }),
499
+ headers: { 'Content-Type': 'application/json' },
500
+ })
501
+ const handlers: Array<RequestHandler> = [
502
+ http.post(
503
+ async ({ request }) => {
504
+ const body = await request.clone().json()
505
+ return body.username === 'test' && body.password === 'password'
506
+ },
507
+ () =>
508
+ HttpResponse.json({
509
+ success: true,
510
+ }),
511
+ ),
512
+ ]
513
+
514
+ const result = await handleRequest(
515
+ request,
516
+ requestId,
517
+ handlers,
518
+ options,
519
+ emitter,
520
+ handleRequestOptions,
521
+ )
522
+
523
+ expect(result).toBeDefined()
524
+ expect(await result?.json()).toStrictEqual({ success: true })
525
+ expect(events).toEqual([
526
+ ['request:start', { request, requestId }],
527
+ ['request:match', { request, requestId }],
528
+ ['request:end', { request, requestId }],
529
+ ])
530
+ expect(handleRequestOptions.onMockedResponse).toHaveBeenCalledTimes(1)
531
+ })
532
+
533
+ test('does not match if custom predicate returns false', async () => {
534
+ const { emitter, events } = setup()
535
+
536
+ const requestId = createRequestId()
537
+ const request = new Request(new URL('http://localhost/login'), {
538
+ method: 'POST',
539
+ body: JSON.stringify({ username: 'test', password: 'passwordd' }),
540
+ headers: { 'Content-Type': 'application/json' },
541
+ })
542
+ const handlers: Array<RequestHandler> = [
543
+ http.post(
544
+ async ({ request }) => {
545
+ const body = await request.clone().json()
546
+ return body.username === 'test' && body.password === 'password'
547
+ },
548
+ () =>
549
+ HttpResponse.json({
550
+ success: true,
551
+ }),
552
+ ),
553
+ ]
554
+
555
+ const result = await handleRequest(
556
+ request,
557
+ requestId,
558
+ handlers,
559
+ options,
560
+ emitter,
561
+ handleRequestOptions,
562
+ )
563
+
564
+ expect(result).toBeUndefined()
565
+ expect(events).toEqual([
566
+ ['request:start', { request, requestId }],
567
+ ['request:unhandled', { request, requestId }],
568
+ ['request:end', { request, requestId }],
569
+ ])
570
+ expect(options.onUnhandledRequest).toHaveBeenCalledTimes(1)
571
+ expect(handleRequestOptions.onPassthroughResponse).toHaveBeenCalledTimes(1)
572
+ })
573
+ })