From 8ee8231a43a1e273c167f25411f5ff8e8c81f5cb Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 24 Jun 2025 11:31:27 +0200 Subject: [PATCH] Adds Redux and React-Intl to storybook (#35094) --- .storybook/main.ts | 1 + .storybook/preview.ts | 29 -- .storybook/preview.tsx | 136 +++++++ .storybook/static/mockServiceWorker.js | 344 +++++++++++++++++ .../components/button/button.stories.tsx | 6 +- app/javascript/mastodon/reducers/index.ts | 15 +- app/javascript/mastodon/store/store.ts | 32 +- eslint.config.mjs | 4 +- package.json | 7 + yarn.lock | 355 +++++++++++++++++- 10 files changed, 876 insertions(+), 53 deletions(-) delete mode 100644 .storybook/preview.ts create mode 100644 .storybook/preview.tsx create mode 100644 .storybook/static/mockServiceWorker.js diff --git a/.storybook/main.ts b/.storybook/main.ts index 638806c085..ba0ac2ae52 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -11,6 +11,7 @@ const config: StorybookConfig = { name: '@storybook/react-vite', options: {}, }, + staticDirs: ['./static'], }; export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts deleted file mode 100644 index a0bec9085f..0000000000 --- a/.storybook/preview.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Preview } from '@storybook/react-vite'; - -// If you want to run the dark theme during development, -// you can change the below to `/application.scss` -import '../app/javascript/styles/mastodon-light.scss'; - -const preview: Preview = { - // Auto-generate docs: https://storybook.js.org/docs/writing-docs/autodocs - tags: ['autodocs'], - parameters: { - layout: 'centered', - - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - - a11y: { - // 'todo' - show a11y violations in the test UI only - // 'error' - fail CI on a11y violations - // 'off' - skip a11y checks entirely - test: 'todo', - }, - }, -}; - -export default preview; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000000..c879cf10d1 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,136 @@ +import { useEffect, useState } from 'react'; + +import { IntlProvider } from 'react-intl'; + +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; + +import type { Preview } from '@storybook/react-vite'; +import { http, passthrough } from 'msw'; +import { initialize, mswLoader } from 'msw-storybook-addon'; + +import type { LocaleData } from '@/mastodon/locales'; +import { reducerWithInitialState, rootReducer } from '@/mastodon/reducers'; +import { defaultMiddleware } from '@/mastodon/store/store'; + +// If you want to run the dark theme during development, +// you can change the below to `/application.scss` +import '../app/javascript/styles/mastodon-light.scss'; + +const localeFiles = import.meta.glob('@/mastodon/locales/*.json', { + query: { as: 'json' }, +}); + +// Initialize MSW +initialize(); + +const preview: Preview = { + // Auto-generate docs: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + globalTypes: { + locale: { + description: 'Locale for the story', + toolbar: { + title: 'Locale', + icon: 'globe', + items: Object.keys(localeFiles).map((path) => + path.replace('/mastodon/locales/', '').replace('.json', ''), + ), + dynamicTitle: true, + }, + }, + }, + initialGlobals: { + locale: 'en', + }, + decorators: [ + (Story, { parameters }) => { + const { state = {} } = parameters; + let reducer = rootReducer; + if (typeof state === 'object' && state) { + reducer = reducerWithInitialState(state as Record); + } + const store = configureStore({ + reducer, + middleware(getDefaultMiddleware) { + return getDefaultMiddleware(defaultMiddleware); + }, + }); + return ( + + + + ); + }, + (Story, { globals }) => { + const currentLocale = (globals.locale as string) || 'en'; + const [messages, setMessages] = useState< + Record> + >({}); + const currentLocaleData = messages[currentLocale]; + + useEffect(() => { + async function loadLocaleData() { + const { default: localeFile } = (await import( + `@/mastodon/locales/${currentLocale}.json` + )) as { default: LocaleData['messages'] }; + setMessages((prevLocales) => ({ + ...prevLocales, + [currentLocale]: localeFile, + })); + } + if (!currentLocaleData) { + void loadLocaleData(); + } + }, [currentLocale, currentLocaleData]); + + return ( + + + + ); + }, + ], + loaders: [mswLoader], + parameters: { + layout: 'centered', + + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + + a11y: { + // 'todo' - show a11y violations in the test UI only + // 'error' - fail CI on a11y violations + // 'off' - skip a11y checks entirely + test: 'todo', + }, + + state: {}, + + // Force docs to use an iframe as it breaks MSW handlers. + // See: https://github.com/mswjs/msw-storybook-addon/issues/83 + docs: { + story: { + inline: false, + }, + }, + + msw: { + handlers: [ + http.get('/index.json', passthrough), + http.get('/packs-dev/*', passthrough), + http.get('/sounds/*', passthrough), + ], + }, + }, +}; + +export default preview; diff --git a/.storybook/static/mockServiceWorker.js b/.storybook/static/mockServiceWorker.js new file mode 100644 index 0000000000..de7bc0f292 --- /dev/null +++ b/.storybook/static/mockServiceWorker.js @@ -0,0 +1,344 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.10.2' +const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + */ +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @returns {Promise} + */ +async function getResponse(event, client, requestId) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(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 +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + 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: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/app/javascript/mastodon/components/button/button.stories.tsx b/app/javascript/mastodon/components/button/button.stories.tsx index b4cca32abd..dc32779928 100644 --- a/app/javascript/mastodon/components/button/button.stories.tsx +++ b/app/javascript/mastodon/components/button/button.stories.tsx @@ -36,7 +36,8 @@ export default meta; type Story = StoryObj; const buttonTest: Story['play'] = async ({ args, canvas, userEvent }) => { - await userEvent.click(canvas.getByRole('button')); + const button = await canvas.findByRole('button'); + await userEvent.click(button); await expect(args.onClick).toHaveBeenCalled(); }; @@ -45,7 +46,8 @@ const disabledButtonTest: Story['play'] = async ({ canvas, userEvent, }) => { - await userEvent.click(canvas.getByRole('button')); + const button = await canvas.findByRole('button'); + await userEvent.click(button); await expect(args.onClick).not.toHaveBeenCalled(); }; diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 794d993f75..cbf22b3118 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -1,4 +1,4 @@ -import { Record as ImmutableRecord } from 'immutable'; +import { Record as ImmutableRecord, mergeDeep } from 'immutable'; import { loadingBarReducer } from 'react-redux-loading-bar'; import { combineReducers } from 'redux-immutable'; @@ -98,6 +98,15 @@ const initialRootState = Object.fromEntries( const RootStateRecord = ImmutableRecord(initialRootState, 'RootState'); -const rootReducer = combineReducers(reducers, RootStateRecord); +export const rootReducer = combineReducers(reducers, RootStateRecord); -export { rootReducer }; +export function reducerWithInitialState( + stateOverrides: Record = {}, +) { + const initialStateRecord = mergeDeep(initialRootState, stateOverrides); + const PatchedRootStateRecord = ImmutableRecord( + initialStateRecord, + 'RootState', + ); + return combineReducers(reducers, PatchedRootStateRecord); +} diff --git a/app/javascript/mastodon/store/store.ts b/app/javascript/mastodon/store/store.ts index 9f43f58a43..9f83f2f3e1 100644 --- a/app/javascript/mastodon/store/store.ts +++ b/app/javascript/mastodon/store/store.ts @@ -6,24 +6,26 @@ import { errorsMiddleware } from './middlewares/errors'; import { loadingBarMiddleware } from './middlewares/loading_bar'; import { soundsMiddleware } from './middlewares/sounds'; +export const defaultMiddleware = { + // In development, Redux Toolkit enables 2 default middlewares to detect + // common issues with states. Unfortunately, our use of ImmutableJS for state + // triggers both, so lets disable them until our state is fully refactored + + // https://redux-toolkit.js.org/api/serializabilityMiddleware + // This checks recursively that every values in the state are serializable in JSON + // Which is not the case, as we use ImmutableJS structures, but also File objects + serializableCheck: false, + + // https://redux-toolkit.js.org/api/immutabilityMiddleware + // This checks recursively if every value in the state is immutable (ie, a JS primitive type) + // But this is not the case, as our Root State is an ImmutableJS map, which is an object + immutableCheck: false, +} as const; + export const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - // In development, Redux Toolkit enables 2 default middlewares to detect - // common issues with states. Unfortunately, our use of ImmutableJS for state - // triggers both, so lets disable them until our state is fully refactored - - // https://redux-toolkit.js.org/api/serializabilityMiddleware - // This checks recursively that every values in the state are serializable in JSON - // Which is not the case, as we use ImmutableJS structures, but also File objects - serializableCheck: false, - - // https://redux-toolkit.js.org/api/immutabilityMiddleware - // This checks recursively if every value in the state is immutable (ie, a JS primitive type) - // But this is not the case, as our Root State is an ImmutableJS map, which is an object - immutableCheck: false, - }) + getDefaultMiddleware(defaultMiddleware) .concat( loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], diff --git a/eslint.config.mjs b/eslint.config.mjs index 06b70aee54..ecd188e3c5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -258,7 +258,7 @@ export default tseslint.config([ 'app/javascript/**/*.stories.tsx', 'app/javascript/**/*.test.ts', 'app/javascript/**/*.test.tsx', - '.storybook/**/*.ts', + '.storybook/**/*', ], }, ], @@ -406,7 +406,7 @@ export default tseslint.config([ }, }, { - files: ['**/*.stories.ts', '**/*.stories.tsx', '.storybook/**/*.ts'], + files: ['**/*.stories.ts', '**/*.stories.tsx', '.storybook/*'], rules: { 'import/no-default-export': 'off', }, diff --git a/package.json b/package.json index 9329a3f70d..5431e0ca3c 100644 --- a/package.json +++ b/package.json @@ -172,6 +172,8 @@ "globals": "^16.0.0", "husky": "^9.0.11", "lint-staged": "^16.0.0", + "msw": "^2.10.2", + "msw-storybook-addon": "^2.0.5", "playwright": "^1.52.0", "prettier": "^3.3.3", "react-test-renderer": "^18.2.0", @@ -202,5 +204,10 @@ "react-router-dom": { "optional": true } + }, + "msw": { + "workerDirectory": [ + ".storybook/static" + ] } } diff --git a/yarn.lock b/yarn.lock index 05e8dc67eb..d9b506ab0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,6 +1189,34 @@ __metadata: languageName: node linkType: hard +"@bundled-es-modules/cookie@npm:^2.0.1": + version: 2.0.1 + resolution: "@bundled-es-modules/cookie@npm:2.0.1" + dependencies: + cookie: "npm:^0.7.2" + checksum: 10c0/dfac5e36127e827c5557b8577f17a8aa94c057baff6d38555917927b99da0ecf0b1357e7fedadc8853ecdbd4a8a7fa1f5e64111b2a656612f4a36376f5bdbe8d + languageName: node + linkType: hard + +"@bundled-es-modules/statuses@npm:^1.0.1": + version: 1.0.1 + resolution: "@bundled-es-modules/statuses@npm:1.0.1" + dependencies: + statuses: "npm:^2.0.1" + checksum: 10c0/c1a8ede3efa8da61ccda4b98e773582a9733edfbeeee569d4630785f8e018766202edb190a754a3ec7a7f6bd738e857829affc2fdb676b6dab4db1bb44e62785 + languageName: node + linkType: hard + +"@bundled-es-modules/tough-cookie@npm:^0.1.6": + version: 0.1.6 + resolution: "@bundled-es-modules/tough-cookie@npm:0.1.6" + dependencies: + "@types/tough-cookie": "npm:^4.0.5" + tough-cookie: "npm:^4.1.4" + checksum: 10c0/28bcac878bff6b34719ba3aa8341e9924772ee55de5487680ebe784981ec9fccb70ed5d46f563e2404855a04de606f9e56aa4202842d4f5835bc04a4fe820571 + languageName: node + linkType: hard + "@csstools/cascade-layer-name-parser@npm:^2.0.5": version: 2.0.5 resolution: "@csstools/cascade-layer-name-parser@npm:2.0.5" @@ -2412,6 +2440,61 @@ __metadata: languageName: node linkType: hard +"@inquirer/confirm@npm:^5.0.0": + version: 5.1.12 + resolution: "@inquirer/confirm@npm:5.1.12" + dependencies: + "@inquirer/core": "npm:^10.1.13" + "@inquirer/type": "npm:^3.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/581aedfe8ce45e177fb4470a12f874f5162a4396636bf4140edc5812ffc8ed0d1fa7e9bbc3a7af618203089a084f489e0b32112947eedc6930a766fad992449e + languageName: node + linkType: hard + +"@inquirer/core@npm:^10.1.13": + version: 10.1.13 + resolution: "@inquirer/core@npm:10.1.13" + dependencies: + "@inquirer/figures": "npm:^1.0.12" + "@inquirer/type": "npm:^3.0.7" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^2.0.0" + signal-exit: "npm:^4.1.0" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/919208a31307297d5a07a44b9ebe69a999ce1470b31a2e1b5a04538bc36624d2053808cd6c677637a61690af09bdbdd635bd7031b64e3dd86c5b18df3ca7c3f9 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.12": + version: 1.0.12 + resolution: "@inquirer/figures@npm:1.0.12" + checksum: 10c0/08694288bdf9aa474571ca94272113a5ac443229519ce71447eba9eb7d5a2007901bdc3e92216d929a69746dcbac29683886c20e67b7864a7c7f6c59b99d3269 + languageName: node + linkType: hard + +"@inquirer/type@npm:^3.0.7": + version: 3.0.7 + resolution: "@inquirer/type@npm:3.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/bbaa33c274a10f70d3a587264e1db6dbfcd8c1458d595c54870d1d5b3fc113ab5063203ec12a098485bb9e2fcef1a87d8c6ecd2a6d44ddc575f5c4715379be5e + languageName: node + linkType: hard + "@ioredis/commands@npm:^1.1.1": version: 1.2.0 resolution: "@ioredis/commands@npm:1.2.0" @@ -2608,6 +2691,8 @@ __metadata: lint-staged: "npm:^16.0.0" lodash: "npm:^4.17.21" marky: "npm:^1.2.5" + msw: "npm:^2.10.2" + msw-storybook-addon: "npm:^2.0.5" path-complete-extname: "npm:^1.0.0" playwright: "npm:^1.52.0" postcss-preset-env: "npm:^10.1.5" @@ -2721,6 +2806,20 @@ __metadata: languageName: node linkType: hard +"@mswjs/interceptors@npm:^0.39.1": + version: 0.39.2 + resolution: "@mswjs/interceptors@npm:0.39.2" + dependencies: + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/logger": "npm:^0.3.0" + "@open-draft/until": "npm:^2.0.0" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + strict-event-emitter: "npm:^0.5.1" + checksum: 10c0/5698e33930a6b6e7cc78cf762291be60c91c6348faa22750acc41ef41528e7891e74541ccfb668ba470d964233fd2121c44d0224a2917eedeba2459cf0b78ca2 + languageName: node + linkType: hard + "@napi-rs/wasm-runtime@npm:^0.2.7": version: 0.2.7 resolution: "@napi-rs/wasm-runtime@npm:0.2.7" @@ -2781,6 +2880,30 @@ __metadata: languageName: node linkType: hard +"@open-draft/deferred-promise@npm:^2.2.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 10c0/eafc1b1d0fc8edb5e1c753c5e0f3293410b40dde2f92688211a54806d4136887051f39b98c1950370be258483deac9dfd17cf8b96557553765198ef2547e4549 + languageName: node + linkType: hard + +"@open-draft/logger@npm:^0.3.0": + version: 0.3.0 + resolution: "@open-draft/logger@npm:0.3.0" + dependencies: + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.0" + checksum: 10c0/90010647b22e9693c16258f4f9adb034824d1771d3baa313057b9a37797f571181005bc50415a934eaf7c891d90ff71dcd7a9d5048b0b6bb438f31bef2c7c5c1 + languageName: node + linkType: hard + +"@open-draft/until@npm:^2.0.0, @open-draft/until@npm:^2.1.0": + version: 2.1.0 + resolution: "@open-draft/until@npm:2.1.0" + checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf + languageName: node + linkType: hard + "@opentelemetry/api@npm:^1.4.0": version: 1.6.0 resolution: "@opentelemetry/api@npm:1.6.0" @@ -3786,6 +3909,13 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 + languageName: node + linkType: hard + "@types/cors@npm:^2.8.16": version: 2.8.18 resolution: "@types/cors@npm:2.8.18" @@ -4201,6 +4331,20 @@ __metadata: languageName: node linkType: hard +"@types/statuses@npm:^2.0.4": + version: 2.0.6 + resolution: "@types/statuses@npm:2.0.6" + checksum: 10c0/dd88c220b0e2c6315686289525fd61472d2204d2e4bef4941acfb76bda01d3066f749ac74782aab5b537a45314fcd7d6261eefa40b6ec872691f5803adaa608d + languageName: node + linkType: hard + +"@types/tough-cookie@npm:^4.0.5": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.2": version: 2.0.3 resolution: "@types/trusted-types@npm:2.0.3" @@ -4882,6 +5026,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + "ansi-escapes@npm:^7.0.0": version: 7.0.0 resolution: "ansi-escapes@npm:7.0.0" @@ -5658,6 +5811,13 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -5808,6 +5968,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.7.2": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 + languageName: node + linkType: hard + "core-js-compat@npm:^3.40.0": version: 3.41.0 resolution: "core-js-compat@npm:3.41.0" @@ -7712,6 +7879,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:^16.8.1": + version: 16.11.0 + resolution: "graphql@npm:16.11.0" + checksum: 10c0/124da7860a2292e9acf2fed0c71fc0f6a9b9ca865d390d112bdd563c1f474357141501c12891f4164fe984315764736ad67f705219c62f7580681d431a85db88 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -7769,6 +7943,13 @@ __metadata: languageName: node linkType: hard +"headers-polyfill@npm:^4.0.2": + version: 4.0.3 + resolution: "headers-polyfill@npm:4.0.3" + checksum: 10c0/53e85b2c6385f8d411945fb890c5369f1469ce8aa32a6e8d28196df38568148de640c81cf88cbc7c67767103dd9acba48f4f891982da63178fc6e34560022afe + languageName: node + linkType: hard + "help-me@npm:^5.0.0": version: 5.0.0 resolution: "help-me@npm:5.0.0" @@ -8263,6 +8444,13 @@ __metadata: languageName: node linkType: hard +"is-node-process@npm:^1.0.1, is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 10c0/5b24fda6776d00e42431d7bcd86bce81cb0b6cabeb944142fe7b077a54ada2e155066ad06dbe790abdb397884bdc3151e04a9707b8cd185099efbc79780573ed + languageName: node + linkType: hard + "is-number-object@npm:^1.1.1": version: 1.1.1 resolution: "is-number-object@npm:1.1.1" @@ -9343,6 +9531,57 @@ __metadata: languageName: node linkType: hard +"msw-storybook-addon@npm:^2.0.5": + version: 2.0.5 + resolution: "msw-storybook-addon@npm:2.0.5" + dependencies: + is-node-process: "npm:^1.0.1" + peerDependencies: + msw: ^2.0.0 + checksum: 10c0/3f26fd4a8a6b1b4da165a8940eca4da2e175a69036a1c85c07ec1952fbb595252db689c4380d8f88ec1cfaa66a6696e90ef0c26b2d1bf17c30092b81247d1d40 + languageName: node + linkType: hard + +"msw@npm:^2.10.2": + version: 2.10.2 + resolution: "msw@npm:2.10.2" + dependencies: + "@bundled-es-modules/cookie": "npm:^2.0.1" + "@bundled-es-modules/statuses": "npm:^1.0.1" + "@bundled-es-modules/tough-cookie": "npm:^0.1.6" + "@inquirer/confirm": "npm:^5.0.0" + "@mswjs/interceptors": "npm:^0.39.1" + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/until": "npm:^2.1.0" + "@types/cookie": "npm:^0.6.0" + "@types/statuses": "npm:^2.0.4" + graphql: "npm:^16.8.1" + headers-polyfill: "npm:^4.0.2" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + path-to-regexp: "npm:^6.3.0" + picocolors: "npm:^1.1.1" + strict-event-emitter: "npm:^0.5.1" + type-fest: "npm:^4.26.1" + yargs: "npm:^17.7.2" + peerDependencies: + typescript: ">= 4.8.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 10c0/fb44961e17e12864b4764b4c015f6ce7c907081f8dcd237ecd635eab00b787847406fbd36a2bcf2ef4c21114a3610ac03c7f93f3080f509a69b0c1c5285fd683 + languageName: node + linkType: hard + +"mute-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "mute-stream@npm:2.0.0" + checksum: 10c0/2cf48a2087175c60c8dcdbc619908b49c07f7adcfc37d29236b0c5c612d6204f789104c98cc44d38acab7b3c96f4a3ec2cfdc4934d0738d876dbefa2a12c69f4 + languageName: node + linkType: hard + "nano-spawn@npm:^1.0.0": version: 1.0.1 resolution: "nano-spawn@npm:1.0.1" @@ -9625,6 +9864,13 @@ __metadata: languageName: node linkType: hard +"outvariant@npm:^1.4.0, outvariant@npm:^1.4.3": + version: 1.4.3 + resolution: "outvariant@npm:1.4.3" + checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c + languageName: node + linkType: hard + "own-keys@npm:^1.0.1": version: 1.0.1 resolution: "own-keys@npm:1.0.1" @@ -9778,6 +10024,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^6.3.0": + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -10580,6 +10833,15 @@ __metadata: languageName: node linkType: hard +"psl@npm:^1.1.33": + version: 1.15.0 + resolution: "psl@npm:1.15.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/d8d45a99e4ca62ca12ac3c373e63d80d2368d38892daa40cfddaa1eb908be98cd549ac059783ef3a56cfd96d57ae8e2fd9ae53d1378d90d42bc661ff924e102a + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.0 resolution: "pump@npm:3.0.0" @@ -10597,7 +10859,7 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.0, punycode@npm:^2.3.0, punycode@npm:^2.3.1": +"punycode@npm:^2.1.0, punycode@npm:^2.1.1, punycode@npm:^2.3.0, punycode@npm:^2.3.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 @@ -10613,6 +10875,13 @@ __metadata: languageName: node linkType: hard +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -11307,6 +11576,13 @@ __metadata: languageName: node linkType: hard +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + "reselect@npm:^5.1.0": version: 5.1.0 resolution: "reselect@npm:5.1.0" @@ -12147,6 +12423,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:^2.0.1": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + "std-env@npm:^3.9.0": version: 3.9.0 resolution: "std-env@npm:3.9.0" @@ -12187,6 +12470,13 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.5.1": + version: 0.5.1 + resolution: "strict-event-emitter@npm:0.5.1" + checksum: 10c0/f5228a6e6b6393c57f52f62e673cfe3be3294b35d6f7842fc24b172ae0a6e6c209fa83241d0e433fc267c503bc2f4ffdbe41a9990ff8ffd5ac425ec0489417f7 + languageName: node + linkType: hard + "string-argv@npm:^0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" @@ -12820,6 +13110,18 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^4.1.4": + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" + dependencies: + psl: "npm:^1.1.33" + punycode: "npm:^2.1.1" + universalify: "npm:^0.2.0" + url-parse: "npm:^1.5.3" + checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45 + languageName: node + linkType: hard + "tough-cookie@npm:^5.1.1": version: 5.1.2 resolution: "tough-cookie@npm:5.1.2" @@ -12956,6 +13258,20 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^4.26.1": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -13173,6 +13489,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: 10c0/cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1" @@ -13281,6 +13604,16 @@ __metadata: languageName: node linkType: hard +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: "npm:^2.1.1" + requires-port: "npm:^1.0.0" + checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 + languageName: node + linkType: hard + "use-composed-ref@npm:^1.3.0": version: 1.3.0 resolution: "use-composed-ref@npm:1.3.0" @@ -14043,6 +14376,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -14162,7 +14506,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.5.1": +"yargs@npm:^17.5.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -14184,6 +14528,13 @@ __metadata: languageName: node linkType: hard +"yoctocolors-cjs@npm:^2.1.2": + version: 2.1.2 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f + languageName: node + linkType: hard + "zlibjs@npm:^0.3.1": version: 0.3.1 resolution: "zlibjs@npm:0.3.1"