Skip to content

Commit b1fedb4

Browse files
authored
Merge pull request #147 from TheSoftwareHouse/feat/zn-525/setup-msw
Feat/zn 525/setup msw
2 parents 0c3ac74 + d2256c5 commit b1fedb4

10 files changed

Lines changed: 1195 additions & 2179 deletions

File tree

.changeset/eighty-turkeys-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-starter-boilerplate': patch
3+
---
4+
5+
feat: restore msw in development in new version

package-lock.json

Lines changed: 999 additions & 2072 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"inquirer-directory": "2.2.0",
106106
"jsdom": "22.1.0",
107107
"lint-staged": "14.0.1",
108-
"msw": "1.3.0",
108+
"msw": "2.2.13",
109109
"npm-run-all": "4.1.5",
110110
"plop": "4.0.1",
111111
"prettier": "3.0.3",

public/mockServiceWorker.js

Lines changed: 81 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
/* tslint:disable */
33

44
/**
5-
* Mock Service Worker (1.3.0).
5+
* Mock Service Worker.
66
* @see https://github.com/mswjs/msw
77
* - Please do NOT modify this file.
88
* - Please do NOT serve this file on production.
99
*/
1010

11-
const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'
11+
const PACKAGE_VERSION = '2.2.13'
12+
const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
13+
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
1214
const activeClientIds = new Set()
1315

1416
self.addEventListener('install', function () {
@@ -47,7 +49,10 @@ self.addEventListener('message', async function (event) {
4749
case 'INTEGRITY_CHECK_REQUEST': {
4850
sendToClient(client, {
4951
type: 'INTEGRITY_CHECK_RESPONSE',
50-
payload: INTEGRITY_CHECKSUM,
52+
payload: {
53+
packageVersion: PACKAGE_VERSION,
54+
checksum: INTEGRITY_CHECKSUM,
55+
},
5156
})
5257
break
5358
}
@@ -86,12 +91,6 @@ self.addEventListener('message', async function (event) {
8691

8792
self.addEventListener('fetch', function (event) {
8893
const { request } = event
89-
const accept = request.headers.get('accept') || ''
90-
91-
// Bypass server-sent events.
92-
if (accept.includes('text/event-stream')) {
93-
return
94-
}
9594

9695
// Bypass navigation requests.
9796
if (request.mode === 'navigate') {
@@ -112,29 +111,8 @@ self.addEventListener('fetch', function (event) {
112111
}
113112

114113
// Generate unique request ID.
115-
const requestId = Math.random().toString(16).slice(2)
116-
117-
event.respondWith(
118-
handleRequest(event, requestId).catch((error) => {
119-
if (error.name === 'NetworkError') {
120-
console.warn(
121-
'[MSW] Successfully emulated a network error for the "%s %s" request.',
122-
request.method,
123-
request.url,
124-
)
125-
return
126-
}
127-
128-
// At this point, any exception indicates an issue with the original request/response.
129-
console.error(
130-
`\
131-
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
132-
request.method,
133-
request.url,
134-
`${error.name}: ${error.message}`,
135-
)
136-
}),
137-
)
114+
const requestId = crypto.randomUUID()
115+
event.respondWith(handleRequest(event, requestId))
138116
})
139117

140118
async function handleRequest(event, requestId) {
@@ -146,21 +124,24 @@ async function handleRequest(event, requestId) {
146124
// this message will pend indefinitely.
147125
if (client && activeClientIds.has(client.id)) {
148126
;(async function () {
149-
const clonedResponse = response.clone()
150-
sendToClient(client, {
151-
type: 'RESPONSE',
152-
payload: {
153-
requestId,
154-
type: clonedResponse.type,
155-
ok: clonedResponse.ok,
156-
status: clonedResponse.status,
157-
statusText: clonedResponse.statusText,
158-
body:
159-
clonedResponse.body === null ? null : await clonedResponse.text(),
160-
headers: Object.fromEntries(clonedResponse.headers.entries()),
161-
redirected: clonedResponse.redirected,
127+
const responseClone = response.clone()
128+
129+
sendToClient(
130+
client,
131+
{
132+
type: 'RESPONSE',
133+
payload: {
134+
requestId,
135+
isMockedResponse: IS_MOCKED_RESPONSE in response,
136+
type: responseClone.type,
137+
status: responseClone.status,
138+
statusText: responseClone.statusText,
139+
body: responseClone.body,
140+
headers: Object.fromEntries(responseClone.headers.entries()),
141+
},
162142
},
163-
})
143+
[responseClone.body],
144+
)
164145
})()
165146
}
166147

@@ -196,20 +177,20 @@ async function resolveMainClient(event) {
196177

197178
async function getResponse(event, client, requestId) {
198179
const { request } = event
199-
const clonedRequest = request.clone()
180+
181+
// Clone the request because it might've been already used
182+
// (i.e. its body has been read and sent to the client).
183+
const requestClone = request.clone()
200184

201185
function passthrough() {
202-
// Clone the request because it might've been already used
203-
// (i.e. its body has been read and sent to the client).
204-
const headers = Object.fromEntries(clonedRequest.headers.entries())
186+
const headers = Object.fromEntries(requestClone.headers.entries())
205187

206-
// Remove MSW-specific request headers so the bypassed requests
207-
// comply with the server's CORS preflight check.
208-
// Operate with the headers as an object because request "Headers"
209-
// are immutable.
210-
delete headers['x-msw-bypass']
188+
// Remove internal MSW request header so the passthrough request
189+
// complies with any potential CORS preflight checks on the server.
190+
// Some servers forbid unknown request headers.
191+
delete headers['x-msw-intention']
211192

212-
return fetch(clonedRequest, { headers })
193+
return fetch(requestClone, { headers })
213194
}
214195

215196
// Bypass mocking when the client is not active.
@@ -225,57 +206,46 @@ async function getResponse(event, client, requestId) {
225206
return passthrough()
226207
}
227208

228-
// Bypass requests with the explicit bypass header.
229-
// Such requests can be issued by "ctx.fetch()".
230-
if (request.headers.get('x-msw-bypass') === 'true') {
231-
return passthrough()
232-
}
233-
234209
// Notify the client that a request has been intercepted.
235-
const clientMessage = await sendToClient(client, {
236-
type: 'REQUEST',
237-
payload: {
238-
id: requestId,
239-
url: request.url,
240-
method: request.method,
241-
headers: Object.fromEntries(request.headers.entries()),
242-
cache: request.cache,
243-
mode: request.mode,
244-
credentials: request.credentials,
245-
destination: request.destination,
246-
integrity: request.integrity,
247-
redirect: request.redirect,
248-
referrer: request.referrer,
249-
referrerPolicy: request.referrerPolicy,
250-
body: await request.text(),
251-
bodyUsed: request.bodyUsed,
252-
keepalive: request.keepalive,
210+
const requestBuffer = await request.arrayBuffer()
211+
const clientMessage = await sendToClient(
212+
client,
213+
{
214+
type: 'REQUEST',
215+
payload: {
216+
id: requestId,
217+
url: request.url,
218+
mode: request.mode,
219+
method: request.method,
220+
headers: Object.fromEntries(request.headers.entries()),
221+
cache: request.cache,
222+
credentials: request.credentials,
223+
destination: request.destination,
224+
integrity: request.integrity,
225+
redirect: request.redirect,
226+
referrer: request.referrer,
227+
referrerPolicy: request.referrerPolicy,
228+
body: requestBuffer,
229+
keepalive: request.keepalive,
230+
},
253231
},
254-
})
232+
[requestBuffer],
233+
)
255234

256235
switch (clientMessage.type) {
257236
case 'MOCK_RESPONSE': {
258237
return respondWithMock(clientMessage.data)
259238
}
260239

261-
case 'MOCK_NOT_FOUND': {
240+
case 'PASSTHROUGH': {
262241
return passthrough()
263242
}
264-
265-
case 'NETWORK_ERROR': {
266-
const { name, message } = clientMessage.data
267-
const networkError = new Error(message)
268-
networkError.name = name
269-
270-
// Rejecting a "respondWith" promise emulates a network error.
271-
throw networkError
272-
}
273243
}
274244

275245
return passthrough()
276246
}
277247

278-
function sendToClient(client, message) {
248+
function sendToClient(client, message, transferrables = []) {
279249
return new Promise((resolve, reject) => {
280250
const channel = new MessageChannel()
281251

@@ -287,17 +257,28 @@ function sendToClient(client, message) {
287257
resolve(event.data)
288258
}
289259

290-
client.postMessage(message, [channel.port2])
260+
client.postMessage(
261+
message,
262+
[channel.port2].concat(transferrables.filter(Boolean)),
263+
)
291264
})
292265
}
293266

294-
function sleep(timeMs) {
295-
return new Promise((resolve) => {
296-
setTimeout(resolve, timeMs)
267+
async function respondWithMock(response) {
268+
// Setting response status code to 0 is a no-op.
269+
// However, when responding with a "Response.error()", the produced Response
270+
// instance will have status code set to 0. Since it's not possible to create
271+
// a Response instance with status code 0, handle that use-case separately.
272+
if (response.status === 0) {
273+
return Response.error()
274+
}
275+
276+
const mockedResponse = new Response(response.body, response)
277+
278+
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
279+
value: true,
280+
enumerable: true,
297281
})
298-
}
299282

300-
async function respondWithMock(response) {
301-
await sleep(response.delay)
302-
return new Response(response.body, response)
283+
return mockedResponse
303284
}

src/api/actions/auth/auth.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type User = {
2323

2424
export type GetUsersResponse = {
2525
users: User[];
26-
nextPage: number | null;
26+
nextPage?: number | null;
2727
};
2828

2929
export type GetUsersInfiniteArgs = {

src/api/mocks/handlers.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { DefaultBodyType, HttpResponse, PathParams } from 'msw';
2+
3+
import {
4+
GetMeQueryResponse,
5+
GetUsersResponse,
6+
LoginMutationArguments,
7+
LoginMutationResponse,
8+
} from 'api/actions/auth/auth.types';
9+
10+
import { http } from './http';
11+
12+
const authorizeHandler = http.post<LoginMutationArguments, never, LoginMutationResponse>('/authorize', async () =>
13+
HttpResponse.json(
14+
{
15+
accessToken: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3',
16+
tokenType: 'bearer',
17+
expires: 123,
18+
refreshToken: 'IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk',
19+
},
20+
{ status: 200 },
21+
),
22+
);
23+
const meHandler = http.get<PathParams, DefaultBodyType, GetMeQueryResponse>('/me', async () =>
24+
HttpResponse.json(
25+
{
26+
firstName: 'Mike',
27+
lastName: 'Tyson',
28+
username: 'mike',
29+
},
30+
{ status: 200 },
31+
),
32+
);
33+
34+
const createUsers = (numUsers = 40) => {
35+
return Array.from({ length: numUsers }, (el, index) => ({ id: `${index}`, name: `User ${index + 1}` }));
36+
};
37+
38+
const usersHandler = http.get<PathParams, DefaultBodyType, GetUsersResponse>('/users', ({ request }) => {
39+
const url = new URL(request.url);
40+
41+
const pageParam = url.searchParams.get('page');
42+
const countParam = url.searchParams.get('count');
43+
const page = pageParam ? parseInt(pageParam) : null;
44+
const count = countParam ? parseInt(countParam) : null;
45+
const allUsers = createUsers();
46+
47+
if (page === null || count === null) {
48+
return HttpResponse.json({ users: allUsers }, { status: 200 });
49+
}
50+
51+
const start = page * count;
52+
const end = start + count;
53+
const nextPageCursor = end >= allUsers.length ? null : page + 1;
54+
const paginatedUsers = allUsers.slice(start, end);
55+
56+
return HttpResponse.json({ users: paginatedUsers, nextPage: nextPageCursor }, { status: 200 });
57+
});
58+
59+
export const handlers = [authorizeHandler, meHandler, usersHandler];

src/api/mocks/http.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { http as baseHttp } from 'msw';
2+
3+
const BASE_URL = import.meta.env.VITE_API_URL;
4+
5+
const createRestHandler = <MethodType extends keyof typeof baseHttp>(
6+
method: MethodType,
7+
): (typeof baseHttp)[MethodType] => {
8+
const wrapperFn = ((...params: Parameters<(typeof baseHttp)[MethodType]>) => {
9+
const [path, resolver] = params;
10+
11+
const url = new RegExp('^(?:[a-z+]+:)?//', 'i').test(path.toString()) ? path : `${BASE_URL}${path}`;
12+
13+
return baseHttp[method](url, resolver);
14+
}) as (typeof baseHttp)[MethodType];
15+
16+
return wrapperFn;
17+
};
18+
19+
export const http = {
20+
head: createRestHandler('head'),
21+
get: createRestHandler('get'),
22+
post: createRestHandler('post'),
23+
put: createRestHandler('put'),
24+
delete: createRestHandler('delete'),
25+
patch: createRestHandler('patch'),
26+
options: createRestHandler('options'),
27+
};

src/api/mocks/mock-worker.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { setupWorker } from 'msw/browser';
2+
3+
import { handlers } from './handlers';
4+
5+
export const worker = setupWorker(...handlers);

0 commit comments

Comments
 (0)