Fix MSW and storybook setup (#2976)
* Fix MSW and storybook setup * Fix * Fix * Fixes * Fix * Fix * Fix
This commit is contained in:
@ -1,12 +1,9 @@
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.7/iframeResizer.contentWindow.min.js"></script>
|
||||
<!-- <script>
|
||||
window.global = window;
|
||||
</script> -->
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { Preview, ReactRenderer } from '@storybook/react';
|
||||
import { withThemeFromJSXProvider } from "@storybook/addon-themes";
|
||||
import { initialize, mswLoader } from 'msw-storybook-addon';
|
||||
import { initialize, mswDecorator } from 'msw-storybook-addon';
|
||||
|
||||
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
||||
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
||||
@ -9,7 +9,20 @@ import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
||||
import { lightTheme, darkTheme } from '../src/modules/ui/theme/constants/theme';
|
||||
import 'react-loading-skeleton/dist/skeleton.css';
|
||||
|
||||
initialize({ onUnhandledRequest: 'bypass' });
|
||||
initialize({
|
||||
onUnhandledRequest: async (request: Request) => {
|
||||
const fileExtensionsToIgnore = /\.(ts|tsx|js|jsx|svg|css|png)(\?v=[a-zA-Z0-9]+)?/;
|
||||
|
||||
if (fileExtensionsToIgnore.test(request.url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestBody = await request.json();
|
||||
console.warn(`Unhandled ${request.method} request to ${request.url}
|
||||
with payload ${JSON.stringify(requestBody)}\n
|
||||
This request should be mocked with MSW`);
|
||||
},
|
||||
});
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [
|
||||
@ -22,6 +35,7 @@ const preview: Preview = {
|
||||
Provider: ThemeProvider,
|
||||
}),
|
||||
RootDecorator,
|
||||
mswDecorator,
|
||||
],
|
||||
parameters: {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
@ -39,7 +53,6 @@ const preview: Preview = {
|
||||
cookie: {
|
||||
tokenPair: `{%22accessToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-07-18T15:06:40.704Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22refreshToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-10-15T15:06:41.558Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22__typename%22:%22AuthTokenPair%22}`,
|
||||
},
|
||||
loaders: [mswLoader],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -145,8 +145,8 @@
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"msw": "1.3.2",
|
||||
"msw-storybook-addon": "^1.10.0",
|
||||
"msw": "^2.0.11",
|
||||
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
|
||||
"prettier": "^3.1.0",
|
||||
"storybook": "^7.6.3",
|
||||
"storybook-addon-cookie": "^3.1.0",
|
||||
|
||||
@ -2,13 +2,14 @@
|
||||
/* tslint:disable */
|
||||
|
||||
/**
|
||||
* Mock Service Worker (1.3.2).
|
||||
* Mock Service Worker (2.0.11).
|
||||
* @see https://github.com/mswjs/msw
|
||||
* - Please do NOT modify this file.
|
||||
* - Please do NOT serve this file on production.
|
||||
*/
|
||||
|
||||
const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'
|
||||
const INTEGRITY_CHECKSUM = 'c5f7f8e188b673ea4e677df7ea3c5a39'
|
||||
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
||||
const activeClientIds = new Set()
|
||||
|
||||
self.addEventListener('install', function () {
|
||||
@ -86,12 +87,6 @@ self.addEventListener('message', async function (event) {
|
||||
|
||||
self.addEventListener('fetch', function (event) {
|
||||
const { request } = event
|
||||
const accept = request.headers.get('accept') || ''
|
||||
|
||||
// Bypass server-sent events.
|
||||
if (accept.includes('text/event-stream')) {
|
||||
return
|
||||
}
|
||||
|
||||
// Bypass navigation requests.
|
||||
if (request.mode === 'navigate') {
|
||||
@ -112,29 +107,8 @@ self.addEventListener('fetch', function (event) {
|
||||
}
|
||||
|
||||
// Generate unique request ID.
|
||||
const requestId = Math.random().toString(16).slice(2)
|
||||
|
||||
event.respondWith(
|
||||
handleRequest(event, requestId).catch((error) => {
|
||||
if (error.name === 'NetworkError') {
|
||||
console.warn(
|
||||
'[MSW] Successfully emulated a network error for the "%s %s" request.',
|
||||
request.method,
|
||||
request.url,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// At this point, any exception indicates an issue with the original request/response.
|
||||
console.error(
|
||||
`\
|
||||
[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.`,
|
||||
request.method,
|
||||
request.url,
|
||||
`${error.name}: ${error.message}`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
const requestId = crypto.randomUUID()
|
||||
event.respondWith(handleRequest(event, requestId))
|
||||
})
|
||||
|
||||
async function handleRequest(event, requestId) {
|
||||
@ -146,21 +120,24 @@ async function handleRequest(event, requestId) {
|
||||
// this message will pend indefinitely.
|
||||
if (client && activeClientIds.has(client.id)) {
|
||||
;(async function () {
|
||||
const clonedResponse = response.clone()
|
||||
sendToClient(client, {
|
||||
type: 'RESPONSE',
|
||||
payload: {
|
||||
requestId,
|
||||
type: clonedResponse.type,
|
||||
ok: clonedResponse.ok,
|
||||
status: clonedResponse.status,
|
||||
statusText: clonedResponse.statusText,
|
||||
body:
|
||||
clonedResponse.body === null ? null : await clonedResponse.text(),
|
||||
headers: Object.fromEntries(clonedResponse.headers.entries()),
|
||||
redirected: clonedResponse.redirected,
|
||||
const responseClone = response.clone()
|
||||
|
||||
sendToClient(
|
||||
client,
|
||||
{
|
||||
type: 'RESPONSE',
|
||||
payload: {
|
||||
requestId,
|
||||
isMockedResponse: IS_MOCKED_RESPONSE in response,
|
||||
type: responseClone.type,
|
||||
status: responseClone.status,
|
||||
statusText: responseClone.statusText,
|
||||
body: responseClone.body,
|
||||
headers: Object.fromEntries(responseClone.headers.entries()),
|
||||
},
|
||||
},
|
||||
})
|
||||
[responseClone.body],
|
||||
)
|
||||
})()
|
||||
}
|
||||
|
||||
@ -196,20 +173,20 @@ async function resolveMainClient(event) {
|
||||
|
||||
async function getResponse(event, client, requestId) {
|
||||
const { request } = event
|
||||
const clonedRequest = request.clone()
|
||||
|
||||
// Clone the request because it might've been already used
|
||||
// (i.e. its body has been read and sent to the client).
|
||||
const requestClone = request.clone()
|
||||
|
||||
function passthrough() {
|
||||
// Clone the request because it might've been already used
|
||||
// (i.e. its body has been read and sent to the client).
|
||||
const headers = Object.fromEntries(clonedRequest.headers.entries())
|
||||
const headers = Object.fromEntries(requestClone.headers.entries())
|
||||
|
||||
// Remove MSW-specific request headers so the bypassed requests
|
||||
// comply with the server's CORS preflight check.
|
||||
// Operate with the headers as an object because request "Headers"
|
||||
// are immutable.
|
||||
delete headers['x-msw-bypass']
|
||||
// Remove internal MSW request header so the passthrough request
|
||||
// complies with any potential CORS preflight checks on the server.
|
||||
// Some servers forbid unknown request headers.
|
||||
delete headers['x-msw-intention']
|
||||
|
||||
return fetch(clonedRequest, { headers })
|
||||
return fetch(requestClone, { headers })
|
||||
}
|
||||
|
||||
// Bypass mocking when the client is not active.
|
||||
@ -227,31 +204,36 @@ async function getResponse(event, client, requestId) {
|
||||
|
||||
// Bypass requests with the explicit bypass header.
|
||||
// Such requests can be issued by "ctx.fetch()".
|
||||
if (request.headers.get('x-msw-bypass') === 'true') {
|
||||
const mswIntention = request.headers.get('x-msw-intention')
|
||||
if (['bypass', 'passthrough'].includes(mswIntention)) {
|
||||
return passthrough()
|
||||
}
|
||||
|
||||
// Notify the client that a request has been intercepted.
|
||||
const clientMessage = await sendToClient(client, {
|
||||
type: 'REQUEST',
|
||||
payload: {
|
||||
id: requestId,
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
cache: request.cache,
|
||||
mode: request.mode,
|
||||
credentials: request.credentials,
|
||||
destination: request.destination,
|
||||
integrity: request.integrity,
|
||||
redirect: request.redirect,
|
||||
referrer: request.referrer,
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
body: await request.text(),
|
||||
bodyUsed: request.bodyUsed,
|
||||
keepalive: request.keepalive,
|
||||
const requestBuffer = await request.arrayBuffer()
|
||||
const clientMessage = await sendToClient(
|
||||
client,
|
||||
{
|
||||
type: 'REQUEST',
|
||||
payload: {
|
||||
id: requestId,
|
||||
url: request.url,
|
||||
mode: request.mode,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
cache: request.cache,
|
||||
credentials: request.credentials,
|
||||
destination: request.destination,
|
||||
integrity: request.integrity,
|
||||
redirect: request.redirect,
|
||||
referrer: request.referrer,
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
body: requestBuffer,
|
||||
keepalive: request.keepalive,
|
||||
},
|
||||
},
|
||||
})
|
||||
[requestBuffer],
|
||||
)
|
||||
|
||||
switch (clientMessage.type) {
|
||||
case 'MOCK_RESPONSE': {
|
||||
@ -261,21 +243,12 @@ async function getResponse(event, client, requestId) {
|
||||
case 'MOCK_NOT_FOUND': {
|
||||
return passthrough()
|
||||
}
|
||||
|
||||
case 'NETWORK_ERROR': {
|
||||
const { name, message } = clientMessage.data
|
||||
const networkError = new Error(message)
|
||||
networkError.name = name
|
||||
|
||||
// Rejecting a "respondWith" promise emulates a network error.
|
||||
throw networkError
|
||||
}
|
||||
}
|
||||
|
||||
return passthrough()
|
||||
}
|
||||
|
||||
function sendToClient(client, message) {
|
||||
function sendToClient(client, message, transferrables = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel()
|
||||
|
||||
@ -287,17 +260,28 @@ function sendToClient(client, message) {
|
||||
resolve(event.data)
|
||||
}
|
||||
|
||||
client.postMessage(message, [channel.port2])
|
||||
})
|
||||
}
|
||||
|
||||
function sleep(timeMs) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, timeMs)
|
||||
client.postMessage(
|
||||
message,
|
||||
[channel.port2].concat(transferrables.filter(Boolean)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function respondWithMock(response) {
|
||||
await sleep(response.delay)
|
||||
return new Response(response.body, response)
|
||||
// Setting response status code to 0 is a no-op.
|
||||
// However, when responding with a "Response.error()", the produced Response
|
||||
// instance will have status code set to 0. Since it's not possible to create
|
||||
// a Response instance with status code 0, handle that use-case separately.
|
||||
if (response.status === 0) {
|
||||
return Response.error()
|
||||
}
|
||||
|
||||
const mockedResponse = new Response(response.body, response)
|
||||
|
||||
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
|
||||
value: true,
|
||||
enumerable: true,
|
||||
})
|
||||
|
||||
return mockedResponse
|
||||
}
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { graphql } from 'msw';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { CREATE_EVENT } from '@/analytics/graphql/queries/createEvent';
|
||||
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||
|
||||
import { mockedCompaniesData } from './mock-data/companies';
|
||||
import { mockedObjectMetadataItems } from './mock-data/metadata';
|
||||
import { mockedPeopleData } from './mock-data/people';
|
||||
import { mockedUsersData } from './mock-data/users';
|
||||
import { mockedViewFieldsData } from './mock-data/view-fields';
|
||||
import { mockedViewsData } from './mock-data/views';
|
||||
|
||||
@ -20,53 +18,52 @@ const metadataGraphql = graphql.link(
|
||||
|
||||
export const graphqlMocks = {
|
||||
handlers: [
|
||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
currentUser: mockedUsersData[0],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
graphql.mutation(getOperationName(CREATE_EVENT) ?? '', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
// graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
||||
// return HttpResponse.json({
|
||||
// data: {
|
||||
// currentUser: mockedUsersData[0],
|
||||
// },
|
||||
// });
|
||||
// }),
|
||||
graphql.mutation(getOperationName(CREATE_EVENT) ?? '', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
createEvent: { success: 1, __typename: 'Event' },
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphql.query(
|
||||
getOperationName(GET_CLIENT_CONFIG) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
clientConfig: {
|
||||
signInPrefilled: true,
|
||||
dataModelSettingsEnabled: true,
|
||||
developersSettingsEnabled: true,
|
||||
debugMode: false,
|
||||
authProviders: { google: true, password: true, magicLink: false },
|
||||
telemetry: { enabled: false, anonymizationEnabled: true },
|
||||
support: {
|
||||
supportDriver: 'front',
|
||||
supportFrontChatId: null,
|
||||
},
|
||||
graphql.query(getOperationName(GET_CLIENT_CONFIG) ?? '', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
clientConfig: {
|
||||
signInPrefilled: true,
|
||||
dataModelSettingsEnabled: true,
|
||||
developersSettingsEnabled: true,
|
||||
debugMode: false,
|
||||
authProviders: { google: true, password: true, magicLink: false },
|
||||
telemetry: { enabled: false, anonymizationEnabled: true },
|
||||
support: {
|
||||
supportDriver: 'front',
|
||||
supportFrontChatId: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
metadataGraphql.query(
|
||||
getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(ctx.data({ objects: mockedObjectMetadataItems }));
|
||||
() => {
|
||||
return HttpResponse.json({
|
||||
data: { objects: mockedObjectMetadataItems },
|
||||
});
|
||||
},
|
||||
),
|
||||
graphql.query('FindManyViews', (req, res, ctx) => {
|
||||
const objectMetadataId = req.variables.filter.objectMetadataId.eq;
|
||||
const viewType = req.variables.filter.type.eq;
|
||||
graphql.query('FindManyViews', ({ variables }) => {
|
||||
const objectMetadataId = variables.filter.objectMetadataId.eq;
|
||||
const viewType = variables.filter.type.eq;
|
||||
|
||||
return res(
|
||||
ctx.data({
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
views: {
|
||||
edges: mockedViewsData
|
||||
.filter(
|
||||
@ -85,14 +82,14 @@ export const graphqlMocks = {
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphql.query('FindManyViewFields', (req, res, ctx) => {
|
||||
const viewId = req.variables.filter.view.eq;
|
||||
graphql.query('FindManyViewFields', ({ variables }) => {
|
||||
const viewId = variables.filter.view.eq;
|
||||
|
||||
return res(
|
||||
ctx.data({
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
viewFields: {
|
||||
edges: mockedViewFieldsData
|
||||
.filter((viewField) => viewField.viewId === viewId)
|
||||
@ -107,12 +104,12 @@ export const graphqlMocks = {
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphql.query('FindManyCompanies', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
graphql.query('FindManyCompanies', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
companies: {
|
||||
edges: mockedCompaniesData.map((company) => ({
|
||||
node: company,
|
||||
@ -125,12 +122,12 @@ export const graphqlMocks = {
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphql.query('FindManyPeople', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
graphql.query('FindManyPeople', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
people: {
|
||||
edges: mockedPeopleData.map((person) => ({
|
||||
node: person,
|
||||
@ -143,12 +140,12 @@ export const graphqlMocks = {
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphql.query('FindManyActivities', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
graphql.query('FindManyActivities', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
activities: {
|
||||
edges: mockedActivities.map((activities) => ({
|
||||
node: activities,
|
||||
@ -161,8 +158,8 @@ export const graphqlMocks = {
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user