diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 7f94841ce..916636177 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -27,11 +27,11 @@ jobs: - name: Lint run: yarn lint + - name: Test + run: yarn test + - name: Build run: yarn build - name: Build for Nexus run: yarn build-for-nexus - - - name: Test - run: yarn test diff --git a/app/pages/__tests__/InstanceCreatePage.spec.tsx b/app/pages/__tests__/InstanceCreatePage.spec.tsx index 7b30304bb..1f5bbb471 100644 --- a/app/pages/__tests__/InstanceCreatePage.spec.tsx +++ b/app/pages/__tests__/InstanceCreatePage.spec.tsx @@ -1,37 +1,17 @@ -import { - fireEvent, - lastPostBody, - renderAppAt, - screen, - waitFor, -} from 'app/test-utils' -import fetchMock from 'fetch-mock' - -import { org, project, instance, sessionMe } from '@oxide/api-mocks' +import { fireEvent, renderAppAt, screen, waitFor } from 'app/test-utils' +import { msw, org, project } from '@oxide/api-mocks' const submitButton = () => screen.getByRole('button', { name: 'Create instance' }) const projectUrl = `/api/organizations/${org.name}/projects/${project.name}` const instancesUrl = `${projectUrl}/instances` -const disksUrl = `${projectUrl}/disks` -const vpcsUrl = `${projectUrl}/vpcs` const formUrl = `/orgs/${org.name}/projects/${project.name}/instances/new` -const renderPage = () => { - // existing disk modal fetches disks on render even if it's not visible - fetchMock.get('/api/session/me', { status: 200, body: sessionMe }) - fetchMock.get(disksUrl, 200) - fetchMock.get(vpcsUrl, 200) - fetchMock.get(projectUrl, 200) - return renderAppAt(formUrl) -} - describe('InstanceCreatePage', () => { it('disables submit button on submit', async () => { - fetchMock.post(instancesUrl, 201) - renderPage() + renderAppAt(formUrl) const submit = submitButton() expect(submit).not.toBeDisabled() @@ -42,11 +22,10 @@ describe('InstanceCreatePage', () => { }) it('shows specific message for known server error code', async () => { - fetchMock.post(instancesUrl, { - status: 400, - body: { error_code: 'ObjectAlreadyExists' }, + msw.override('post', instancesUrl, 400, { + error_code: 'ObjectAlreadyExists', }) - renderPage() + renderAppAt(formUrl) fireEvent.click(submitButton()) @@ -58,11 +37,8 @@ describe('InstanceCreatePage', () => { }) it('shows generic message for unknown server error', async () => { - fetchMock.post(instancesUrl, { - status: 400, - body: { error_code: 'UnknownCode' }, - }) - renderPage() + msw.override('post', instancesUrl, 400, { error_code: 'UnknownCode' }) + renderAppAt(formUrl) fireEvent.click(submitButton()) @@ -71,38 +47,22 @@ describe('InstanceCreatePage', () => { expect(window.location.pathname).toEqual(formUrl) }) - it('posts form on submit', async () => { - const mock = fetchMock.post(instancesUrl, 201) - renderPage() - - fireEvent.change(screen.getByLabelText('Choose a name'), { - target: { value: 'new-instance' }, - }) - fireEvent.click(screen.getByLabelText(/6 CPUs/)) - fireEvent.click(submitButton()) - - await waitFor(() => - expect(lastPostBody(mock)).toEqual({ - name: 'new-instance', - description: 'An instance in project: mock-project', - hostname: '', - ncpus: 6, - memory: 25769803776, - }) - ) - }) - it('navigates to project instances page on success', async () => { - const mock = fetchMock.post(instancesUrl, { status: 201, body: instance }) - renderPage() + renderAppAt(formUrl) const instancesPage = `/orgs/${org.name}/projects/${project.name}/instances` expect(window.location.pathname).not.toEqual(instancesPage) + // TODO: once MSW data layer is in place, uncomment these and assert that + // the name shows up in the list of instances + + // fireEvent.change(screen.getByLabelText('Choose a name'), { + // target: { value: 'new-instance' }, + // }) + // fireEvent.click(screen.getByLabelText(/6 CPUs/)) + fireEvent.click(submitButton()) - await waitFor(() => expect(mock.called(instancesUrl)).toBeTruthy()) - await waitFor(() => expect(mock.done()).toBeTruthy()) await waitFor(() => expect(window.location.pathname).toEqual(instancesPage)) }) }) diff --git a/app/pages/__tests__/ProjectCreatePage.spec.tsx b/app/pages/__tests__/ProjectCreatePage.spec.tsx index 408a3a8d9..ddebe38d0 100644 --- a/app/pages/__tests__/ProjectCreatePage.spec.tsx +++ b/app/pages/__tests__/ProjectCreatePage.spec.tsx @@ -1,17 +1,7 @@ -import { - fireEvent, - lastPostBody, - renderAppAt, - screen, - waitFor, -} from 'app/test-utils' -import fetchMock from 'fetch-mock' - -import { org, project, projects, sessionMe } from '@oxide/api-mocks' +import { fireEvent, renderAppAt, screen, waitFor } from 'app/test-utils' +import { msw, org, project } from '@oxide/api-mocks' const projectsUrl = `/api/organizations/${org.name}/projects` -const projectUrl = `${projectsUrl}/${project.name}` -const instancesUrl = `${projectUrl}/instances?limit=10` const submitButton = () => screen.getByRole('button', { name: 'Create project' }) @@ -24,21 +14,13 @@ function enterName(value: string) { const formUrl = `/orgs/${org.name}/projects/new` const renderPage = () => { - // fetch projects list for org layout sidebar on project create - fetchMock.get('/api/session/me', { status: 200, body: sessionMe }) - fetchMock.get(projectsUrl, { status: 200, body: projects }) const result = renderAppAt(formUrl) enterName('valid-name') return result } describe('ProjectCreatePage', () => { - afterEach(() => { - fetchMock.reset() - }) - it('disables submit button on submit', async () => { - fetchMock.post(projectsUrl, { status: 201 }) renderPage() const submit = submitButton() @@ -50,9 +32,8 @@ describe('ProjectCreatePage', () => { }) it('shows message for known error code in project create code map', async () => { - fetchMock.post(projectsUrl, { - status: 400, - body: { error_code: 'ObjectAlreadyExists' }, + msw.override('post', projectsUrl, 400, { + error_code: 'ObjectAlreadyExists', }) renderPage() @@ -66,10 +47,7 @@ describe('ProjectCreatePage', () => { }) it('shows message for known error code in global code map', async () => { - fetchMock.post(projectsUrl, { - status: 403, - body: { error_code: 'Forbidden' }, - }) + msw.override('post', projectsUrl, 403, { error_code: 'Forbidden' }) renderPage() fireEvent.click(submitButton()) @@ -90,10 +68,7 @@ describe('ProjectCreatePage', () => { }) it('shows generic message for unknown server error', async () => { - fetchMock.post(projectsUrl, { - status: 400, - body: { error_code: 'UnknownCode' }, - }) + msw.override('post', projectsUrl, 400, { error_code: 'UnknownCode' }) renderPage() fireEvent.click(submitButton()) @@ -103,29 +78,7 @@ describe('ProjectCreatePage', () => { expect(window.location.pathname).toEqual(formUrl) }) - it('posts form on submit', async () => { - const mock = fetchMock.post(projectsUrl, { status: 201 }) - renderPage() - - fireEvent.click(submitButton()) - - await waitFor(() => - expect(lastPostBody(mock)).toEqual({ - name: 'valid-name', - description: '', - }) - ) - }) - it('navigates to project instances page on success', async () => { - fetchMock.post(projectsUrl, { - status: 201, - body: project, - }) - fetchMock.get(projectUrl, { status: 200 }) - // instances fetch after success - fetchMock.get(instancesUrl, { status: 200, body: { items: [] } }) - renderPage() const projectPath = `/orgs/${org.name}/projects/${project.name}/instances` expect(window.location.pathname).not.toEqual(projectPath) @@ -133,5 +86,7 @@ describe('ProjectCreatePage', () => { fireEvent.click(submitButton()) await waitFor(() => expect(window.location.pathname).toEqual(projectPath)) + + // TODO: navigate to projects page so you can see the project in the list? }) }) diff --git a/app/pages/project/networking/VpcPage/VpcPage.spec.ts b/app/pages/project/networking/VpcPage/VpcPage.spec.ts index 892133335..2f4fb1d84 100644 --- a/app/pages/project/networking/VpcPage/VpcPage.spec.ts +++ b/app/pages/project/networking/VpcPage/VpcPage.spec.ts @@ -1,36 +1,17 @@ import { fireEvent, - lastPostBody, renderAppAt, screen, userEvent, waitForElementToBeRemoved, } from 'app/test-utils' -import fetchMock from 'fetch-mock' -import { - org, - project, - vpc, - vpcSubnet, - vpcSubnet2, - vpcSubnets, -} from '@oxide/api-mocks' +import { msw, org, project, vpcSubnet, vpcSubnet2 } from '@oxide/api-mocks' -const vpcUrl = `/api/organizations/${org.name}/projects/${project.name}/vpcs/default` -const subnetsUrl = `${vpcUrl}/subnets` -const getSubnetsUrl = `${subnetsUrl}?limit=10` +const subnetsUrl = `/api/organizations/${org.name}/projects/${project.name}/vpcs/default/subnets` describe('VpcPage', () => { describe('subnets tab', () => { it('creating a subnet works', async () => { - fetchMock.get('/api/session/me', 200) - fetchMock.get(vpcUrl, { status: 200, body: vpc }) - fetchMock.getOnce(getSubnetsUrl, { status: 200, body: vpcSubnets }) - const postMock = fetchMock.postOnce(subnetsUrl, { - status: 201, - body: vpcSubnet2, - }) - renderAppAt('/orgs/mock-org/projects/mock-project/vpcs/default') screen.getByText('Subnets') @@ -52,35 +33,30 @@ describe('VpcPage', () => { const name = screen.getByRole('textbox', { name: 'Name' }) userEvent.type(name, 'mock-subnet-2') - // override the subnets GET to include both subnets - fetchMock.getOnce( - getSubnetsUrl, - { - status: 200, - body: { items: [vpcSubnet, vpcSubnet2] }, - }, - { overwriteRoutes: true } - ) + // this is temporary, a workaround for the fact that the mock server + // doesn't have a persistence layer yet + msw.override('get', subnetsUrl, 200, { items: [vpcSubnet, vpcSubnet2] }) // submit the form fireEvent.click(screen.getByRole('button', { name: 'Create subnet' })) // wait for modal to close - await waitForElementToBeRemoved(() => - screen.queryByRole('dialog', { name: 'Create subnet' }) + await waitForElementToBeRemoved( + () => screen.queryByRole('dialog', { name: 'Create subnet' }), + // fails in CI without a longer timeout (default 1000). boo + { timeout: 2000 } ) - // it posted the form - expect(lastPostBody(postMock)).toEqual({ - ipv4Block: '1.1.1.2/24', - ipv6Block: null, - name: 'mock-subnet-2', - description: '', - }) + // TODO: before, we asserted what body the form posted. MSW strongly + // discourages this because it's testing implementation details, but I + // can't shake the feeling that I want it. But it might feel better after + // the MSW mock is more sophisticated and actually handles a create by + // inserting the thing in the list of subnets. Then our assertion that it + // showed up in the list actually does check what was posted. // table should refetch and now include second subnet screen.getByRole('cell', { name: vpcSubnet.identity.name }) - screen.getByRole('cell', { name: vpcSubnet2.identity.name }) + await screen.findByRole('cell', { name: vpcSubnet2.identity.name }) }) }) }) diff --git a/app/routes.spec.tsx b/app/routes.spec.tsx index 3f601a09e..5806272a1 100644 --- a/app/routes.spec.tsx +++ b/app/routes.spec.tsx @@ -2,16 +2,13 @@ import { matchRoutes } from 'react-router' import { renderAppAt } from './test-utils' -import fetchMock from 'fetch-mock' -import { projects, sessionMe } from '@oxide/api-mocks' +import { projects } from '@oxide/api-mocks' import { getRouteConfig } from './routes' describe('routes', () => { it('should render successfully', async () => { - fetchMock.get('/api/session/me', { status: 200, body: sessionMe }) - fetchMock.get('/api/organizations/maze-war/projects', projects) const { findAllByText } = renderAppAt('/') await findAllByText(projects.items[0].name) }) diff --git a/app/test-utils.tsx b/app/test-utils.tsx index 2da931a4f..b90650337 100644 --- a/app/test-utils.tsx +++ b/app/test-utils.tsx @@ -2,7 +2,6 @@ import React from 'react' import { BrowserRouter } from 'react-router-dom' import { render } from '@testing-library/react' import { QueryClient, QueryClientProvider } from 'react-query' -import type { FetchMockStatic } from 'fetch-mock' import { routes } from './routes' const queryClient = new QueryClient({ @@ -13,6 +12,10 @@ const queryClient = new QueryClient({ }, }) +// this is necessary to prevent requests left in flight at the end of a test from +// coming back during another test and triggering whatever they would trigger +afterEach(() => queryClient.clear()) + const customRender = (ui: React.ReactElement) => render(ui, { wrapper: ({ children }) => ( @@ -33,10 +36,6 @@ export function renderAppAt(url: string) { }) } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const lastPostBody = (mock: FetchMockStatic): any => - JSON.parse(mock.lastOptions(undefined, 'POST')?.body as unknown as string) - export * from '@testing-library/react' export { default as userEvent } from '@testing-library/user-event' export { customRender as render } diff --git a/jest/setup.ts b/jest/setup.ts index 052de97fe..7655e0691 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -1,6 +1,7 @@ import '@testing-library/jest-dom' -import fetchMock from 'fetch-mock' import { setLogger } from 'react-query' +import 'whatwg-fetch' +import { msw } from '@oxide/api-mocks' // react-query calls console.error whenever a request fails. // this is annoying and we don't need it. leave log and warn there @@ -11,4 +12,6 @@ setLogger({ error: () => {}, }) -afterEach(() => fetchMock.reset()) +beforeAll(() => msw.server.listen()) +afterEach(() => msw.server.resetHandlers()) +afterAll(() => msw.server.close()) diff --git a/libs/api-mocks/disk.ts b/libs/api-mocks/disk.ts new file mode 100644 index 000000000..75670a79e --- /dev/null +++ b/libs/api-mocks/disk.ts @@ -0,0 +1,16 @@ +import type { Disk, DiskResultsPage } from '@oxide/api' +import { project } from './project' + +export const disk: Disk = { + id: 'disk-id', + name: 'disk-name', + description: "it's a disk", + projectId: project.id, + timeCreated: new Date().toISOString(), + timeModified: new Date().toISOString(), + state: { state: 'detached' }, + devicePath: '/uh', + size: 1000, +} + +export const disks: DiskResultsPage = { items: [disk] } diff --git a/libs/api-mocks/index.ts b/libs/api-mocks/index.ts index a953aeaab..d50bacc5e 100644 --- a/libs/api-mocks/index.ts +++ b/libs/api-mocks/index.ts @@ -1,6 +1,9 @@ +export * from './disk' export * from './instance' export * from './org' export * from './project' export * from './session' export * from './users' export * from './vpc' + +export * as msw from './msw/server' diff --git a/libs/api-mocks/instance.ts b/libs/api-mocks/instance.ts index 36dde3894..d9cc5a125 100644 --- a/libs/api-mocks/instance.ts +++ b/libs/api-mocks/instance.ts @@ -1,4 +1,4 @@ -import type { Instance } from '@oxide/api' +import type { Instance, InstanceResultsPage } from '@oxide/api' export const instance: Instance = { ncpus: 7, @@ -13,3 +13,5 @@ export const instance: Instance = { timeModified: new Date().toISOString(), timeRunStateUpdated: new Date().toISOString(), } + +export const instances: InstanceResultsPage = { items: [instance] } diff --git a/libs/api-mocks/msw/browser.ts b/libs/api-mocks/msw/browser.ts new file mode 100644 index 000000000..b234fdae4 --- /dev/null +++ b/libs/api-mocks/msw/browser.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw' +import { handlers } from './handlers' + +export const worker = setupWorker(...handlers) diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts new file mode 100644 index 000000000..0339d8587 --- /dev/null +++ b/libs/api-mocks/msw/handlers.ts @@ -0,0 +1,84 @@ +import { rest } from 'msw' +import * as mock from '@oxide/api-mocks' + +export const handlers = [ + rest.get('/api/session/me', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.sessionMe)) + }), + + rest.get('/api/organizations', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.orgs)) + }), + + rest.post('/api/organizations', (req, res, ctx) => { + return res(ctx.status(201), ctx.json(mock.org)) + }), + + rest.get('/api/organizations/:orgName', (req, res, ctx) => { + return res(ctx.status(404), ctx.text('Not found')) + }), + + rest.get('/api/organizations/:orgName/projects', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.projects)) + }), + + rest.post('/api/organizations/:orgName/projects', (req, res, ctx) => { + return res(ctx.status(201), ctx.json(mock.project)) + }), + + rest.get( + '/api/organizations/:orgName/projects/:projectName', + (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.project)) + } + ), + + rest.get( + '/api/organizations/:orgName/projects/:projectName/instances', + (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.instances)) + } + ), + + rest.post( + '/api/organizations/:orgName/projects/:projectName/instances', + (req, res, ctx) => { + return res(ctx.status(201), ctx.json(mock.instance)) + } + ), + + rest.get( + '/api/organizations/:orgName/projects/:projectName/disks', + (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.disks)) + } + ), + + rest.get( + '/api/organizations/:orgName/projects/:projectName/vpcs', + (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.vpcs)) + } + ), + + rest.get( + '/api/organizations/:orgName/projects/:projectName/vpcs/:vpcName', + (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.vpc)) + } + ), + + rest.get( + '/api/organizations/:orgName/projects/:projectName/vpcs/:vpcName/subnets', + (req, res, ctx) => { + return res(ctx.status(200), ctx.json(mock.vpcSubnets)) + } + ), + + rest.post( + '/api/organizations/:orgName/projects/:projectName/vpcs/:vpcName/subnets', + (req, res, ctx) => { + return res(ctx.status(201), ctx.json(mock.vpcSubnet2)) + } + ), +] diff --git a/libs/api-mocks/msw/server.ts b/libs/api-mocks/msw/server.ts new file mode 100644 index 000000000..1b43d697c --- /dev/null +++ b/libs/api-mocks/msw/server.ts @@ -0,0 +1,24 @@ +import { rest } from 'msw' +import { setupServer } from 'msw/node' +import { handlers } from './handlers' + +const server = setupServer(...handlers) + +// Override request handlers in order to test special cases +function override( + method: keyof typeof rest, + path: string, + status: number, + body: string | Record +) { + server.use( + rest[method](path, (_req, res, ctx) => { + return res( + ctx.status(status), + typeof body === 'string' ? ctx.text(body) : ctx.json(body) + ) + }) + ) +} + +export { server, rest, override } diff --git a/libs/api-mocks/vpc.ts b/libs/api-mocks/vpc.ts index 5321c6891..51e7d7cb1 100644 --- a/libs/api-mocks/vpc.ts +++ b/libs/api-mocks/vpc.ts @@ -1,5 +1,10 @@ import { project } from './project' -import type { Vpc, VpcSubnet, VpcSubnetResultsPage } from '@oxide/api' +import type { + Vpc, + VpcResultsPage, + VpcSubnet, + VpcSubnetResultsPage, +} from '@oxide/api' export const vpc: Vpc = { id: 'vpc-id', @@ -12,6 +17,8 @@ export const vpc: Vpc = { systemRouterId: 'router-id', // ??? } +export const vpcs: VpcResultsPage = { items: [vpc] } + export const vpcSubnet: VpcSubnet = { // this is supposed to be flattened into the top level. will fix in API identity: { diff --git a/libs/api/__tests__/hooks.spec.tsx b/libs/api/__tests__/hooks.spec.tsx index db6fa895a..07fcc4ada 100644 --- a/libs/api/__tests__/hooks.spec.tsx +++ b/libs/api/__tests__/hooks.spec.tsx @@ -2,13 +2,9 @@ import React from 'react' import { QueryClient, QueryClientProvider } from 'react-query' import { waitFor } from '@testing-library/react' import { renderHook, act } from '@testing-library/react-hooks' -import fetchMock from 'fetch-mock' -import { Response } from 'node-fetch' -import { org, orgs } from '@oxide/api-mocks' +import { msw, org, orgs } from '@oxide/api-mocks' import { useApiQuery, useApiMutation } from '../' -import { navToLogin } from '../nav-to-login' -jest.mock('../nav-to-login') // because useApiQuery and useApiMutation are almost entirely typed wrappers // around React Query's useQuery and useMutation, these tests are mostly about @@ -29,6 +25,13 @@ const wrapper = () => { const renderGetOrgs = () => renderHook(() => useApiQuery('organizationsGet', {}), wrapper()) +const renderGetOrg = () => + renderHook( + () => + useApiQuery('organizationsGetOrganization', { orgName: 'nonexistent' }), + wrapper() + ) + const renderCreateOrg = () => renderHook(() => useApiMutation('organizationsPost'), wrapper()) @@ -48,27 +51,27 @@ describe('useApiQuery', () => { describe('on error response', () => { it('passes through raw response', async () => { - const response = new Response('Not found', { status: 404 }) - fetchMock.get('/api/organizations', response) + const { result } = renderGetOrg() - const { result } = renderGetOrgs() + await waitFor(() => expect(result.current.error).not.toBeNull()) - await waitFor(() => expect(result.current.error).toEqual(response)) + const response = result.current.error + expect(response?.status).toEqual(404) }) it('parses error json if possible', async () => { const error = { abc: 'xyz' } - const response = new Response(JSON.stringify(error), { status: 404 }) - fetchMock.get('/api/organizations', response) + msw.override('get', '/api/organizations', 404, error) const { result } = renderGetOrgs() await waitFor(() => expect(result.current.error?.error).toEqual(error)) }) + // TODO: this test applies to the old generated client. now it's more like + // data is null. error appears to get the JSON parse error for some reason it('sets error.data to null if error body is not json', async () => { - const response = new Response('not json', { status: 404 }) - fetchMock.get('/api/organizations', response) + msw.override('get', '/api/organizations', 404, 'not json') const { result } = renderGetOrgs() @@ -77,21 +80,11 @@ describe('useApiQuery', () => { expect(result.current.error?.data).toBeNull() }) }) - - it('navigates to login if 401', async () => { - fetchMock.get('/api/organizations', 401) - renderGetOrgs() - await waitFor(() => expect(navToLogin).toHaveBeenCalled()) - }) }) describe('on success response', () => { it('returns data', async () => { - const response = new Response(JSON.stringify(orgs), { status: 200 }) - fetchMock.get('/api/organizations', response) - const { result } = renderGetOrgs() - await waitFor(() => expect(result.current.data).toEqual(orgs)) }) }) @@ -108,19 +101,20 @@ describe('useApiMutation', () => { describe('on error response', () => { it('passes through raw response', async () => { - const response = new Response('Bad request', { status: 400 }) - fetchMock.post('/api/organizations', response) + msw.override('post', '/api/organizations', 404, 'not json') const { result } = renderCreateOrg() act(() => result.current.mutate(createParams)) - await waitFor(() => expect(result.current.error).toEqual(response)) + await waitFor(() => expect(result.current.error).not.toBeNull()) + + const response = result.current.error + expect(response?.status).toEqual(404) }) it('parses error json if possible', async () => { const error = { abc: 'xyz' } - const response = new Response(JSON.stringify(error), { status: 400 }) - fetchMock.post('/api/organizations', response) + msw.override('post', '/api/organizations', 400, error) const { result } = renderCreateOrg() act(() => result.current.mutate(createParams)) @@ -129,8 +123,7 @@ describe('useApiMutation', () => { }) it('sets error.data to null if error body is not json', async () => { - const response = new Response('not json', { status: 404 }) - fetchMock.post('/api/organizations', response) + msw.override('post', '/api/organizations', 404, 'not json') const { result } = renderCreateOrg() act(() => result.current.mutate(createParams)) @@ -140,22 +133,10 @@ describe('useApiMutation', () => { expect(result.current.error?.data).toBeNull() }) }) - - it('navigates to login if 401', async () => { - fetchMock.post('/api/organizations', 401) - - const { result } = renderCreateOrg() - act(() => result.current.mutate(createParams)) - - await waitFor(() => expect(navToLogin).toHaveBeenCalled()) - }) }) describe('on success response', () => { it('returns data', async () => { - const response = new Response(JSON.stringify(org), { status: 201 }) - fetchMock.post('/api/organizations', response) - const { result } = renderCreateOrg() act(() => result.current.mutate(createParams)) diff --git a/package.json b/package.json index 729e7f5fd..e9359f5d6 100644 --- a/package.json +++ b/package.json @@ -98,10 +98,10 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.26.1", "eslint-plugin-react-hooks": "^4.2.0", - "fetch-mock": "^9.11.0", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", "jscodeshift": "^0.13.0", + "msw": "^0.36.3", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "plop": "^2.7.6", @@ -117,7 +117,8 @@ "typescript": "4.5.4", "url-loader": "^3.0.0", "vite": "^2.7.10", - "webpack": "^5.40.0" + "webpack": "^5.40.0", + "whatwg-fetch": "^3.6.2" }, "resolutions": { "**/trim": "^1.0.0" diff --git a/yarn.lock b/yarn.lock index b98894fdb..a427d834d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,7 +62,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.10", "@babel/core@^7.13.16", "@babel/core@^7.7.5": +"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.10", "@babel/core@^7.13.16", "@babel/core@^7.7.5": version "7.15.5" resolved "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz" integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg== @@ -1848,6 +1848,26 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@mswjs/cookies@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.1.6.tgz#176f77034ab6d7373ae5c94bcbac36fee8869249" + integrity sha512-A53XD5TOfwhpqAmwKdPtg1dva5wrng2gH5xMvklzbd9WLTSVU953eCRa8rtrrm6G7Cy60BOGsBRN89YQK0mlKA== + dependencies: + "@types/set-cookie-parser" "^2.4.0" + set-cookie-parser "^2.4.6" + +"@mswjs/interceptors@^0.12.7": + version "0.12.7" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" + integrity sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg== + dependencies: + "@open-draft/until" "^1.0.3" + "@xmldom/xmldom" "^0.7.2" + debug "^4.3.2" + headers-utils "^3.0.2" + outvariant "^1.2.0" + strict-event-emitter "^0.2.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -1956,6 +1976,11 @@ widest-line "^3.1.0" wrap-ansi "^4.0.0" +"@open-draft/until@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" + integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== + "@pmmmwh/react-refresh-webpack-plugin@^0.5.1": version "0.5.1" resolved "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.1.tgz" @@ -3311,6 +3336,11 @@ resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + "@types/d3-color@^2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-2.0.3.tgz#8bc4589073c80e33d126345542f588056511fe82" @@ -3413,6 +3443,14 @@ "@types/through" "*" rxjs "^6.4.0" +"@types/inquirer@^8.1.3": + version "8.1.3" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.1.3.tgz#dfda4c97cdbe304e4dceb378a80f79448ea5c8fe" + integrity sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ== + dependencies: + "@types/through" "*" + rxjs "^7.2.0" + "@types/interpret@*": version "1.1.1" resolved "https://registry.npmjs.org/@types/interpret/-/interpret-1.1.1.tgz" @@ -3452,6 +3490,11 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/js-levenshtein@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" + integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== + "@types/jscodeshift@^0.11.2": version "0.11.2" resolved "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.2.tgz" @@ -3630,6 +3673,13 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz" integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== +"@types/set-cookie-parser@^2.4.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz#b6a955219b54151bfebd4521170723df5e13caad" + integrity sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w== + dependencies: + "@types/node" "*" + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz" @@ -4081,6 +4131,11 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@xmldom/xmldom@^0.7.2": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" + integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" @@ -5258,6 +5313,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" @@ -5277,7 +5340,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5735,6 +5798,11 @@ cookie@0.4.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz" @@ -5782,7 +5850,7 @@ core-js-pure@^3.8.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.3.tgz" integrity sha512-qfskyO/KjtbYn09bn1IPkuhHl5PlJ6IzJ9s9sraJ1EqcuGyLGKzhSM1cY0zgyL9hx42eulQLZ6WaeK5ycJCkqw== -core-js@^3.0.0, core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: +core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: version "3.14.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.14.0.tgz" integrity sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA== @@ -7131,7 +7199,7 @@ eventemitter3@^4.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0, events@^3.2.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -7384,22 +7452,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fetch-mock@^9.11.0: - version "9.11.0" - resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz" - integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== - dependencies: - "@babel/core" "^7.0.0" - "@babel/runtime" "^7.0.0" - core-js "^3.0.0" - debug "^4.1.1" - glob-to-regexp "^0.4.0" - is-subset "^0.1.1" - lodash.isequal "^4.5.0" - path-to-regexp "^2.2.1" - querystring "^0.2.0" - whatwg-url "^6.5.0" - figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz" @@ -7949,7 +8001,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -8098,6 +8150,11 @@ graceful-fs@^4.2.2: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graphql@^15.5.1: + version "15.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" + integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== + growly@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz" @@ -8310,6 +8367,11 @@ header-case@^1.0.0: no-case "^2.2.0" upper-case "^1.1.3" +headers-utils@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/headers-utils/-/headers-utils-3.0.2.tgz#dfc65feae4b0e34357308aefbcafa99c895e59ef" + integrity sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ== + highlight.js@^10.1.1, highlight.js@~10.7.0: version "10.7.3" resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz" @@ -8647,6 +8709,26 @@ inquirer@^7.1.0: strip-ansi "^6.0.0" through "^2.3.6" +inquirer@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" + integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.2.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" @@ -8936,6 +9018,11 @@ is-negative-zero@^2.0.1: resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +is-node-process@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.0.1.tgz#4fc7ac3a91e8aac58175fe0578abbc56f2831b23" + integrity sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ== + is-number-object@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz" @@ -9037,11 +9124,6 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" @@ -9585,6 +9667,11 @@ jest@^26.6.3: import-local "^3.0.2" jest-cli "^26.6.3" +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz" @@ -9944,21 +10031,11 @@ lodash.get@^4.4.2: resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - lodash.template@^4.4.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz" @@ -10478,6 +10555,32 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +msw@^0.36.3: + version "0.36.3" + resolved "https://registry.yarnpkg.com/msw/-/msw-0.36.3.tgz#7feb243a5fcf563806d45edc027bc36144741170" + integrity sha512-Itzp/QhKaleZoslXDrNik3ramW9ynqzOdbwydX2ehBSSaZd5QoiAl/bHYcV33R6CEZcJgIX1N4s+G6XkF/bhkA== + dependencies: + "@mswjs/cookies" "^0.1.6" + "@mswjs/interceptors" "^0.12.7" + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.1" + "@types/inquirer" "^8.1.3" + "@types/js-levenshtein" "^1.1.0" + chalk "4.1.1" + chokidar "^3.4.2" + cookie "^0.4.1" + graphql "^15.5.1" + headers-utils "^3.0.2" + inquirer "^8.2.0" + is-node-process "^1.0.1" + js-levenshtein "^1.1.6" + node-fetch "^2.6.1" + path-to-regexp "^6.2.0" + statuses "^2.0.0" + strict-event-emitter "^0.2.0" + type-fest "^1.2.2" + yargs "^17.3.0" + mute-stream@0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" @@ -11006,7 +11109,7 @@ ora@^3.4.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -ora@~5.4.1: +ora@^5.4.1, ora@~5.4.1: version "5.4.1" resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== @@ -11031,6 +11134,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +outvariant@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.2.1.tgz#e630f6cdc1dbf398ed857e36f219de4a005ccd35" + integrity sha512-bcILvFkvpMXh66+Ubax/inxbKRyWTUiiFIW2DWkiS79wakrLGn3Ydy+GvukadiyfZjaL6C7YhIem4EZSM282wA== + overlayscrollbars@^1.13.1: version "1.13.1" resolved "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz" @@ -11335,10 +11443,10 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@^2.2.1: - version "2.4.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== +path-to-regexp@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" + integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg== path-type@^3.0.0: version "3.0.0" @@ -12770,6 +12878,13 @@ rxjs@^6.4.0, rxjs@^6.6.0: dependencies: tslib "^1.9.0" +rxjs@^7.2.0: + version "7.5.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.1.tgz#af73df343cbcab37628197f43ea0c8256f54b157" + integrity sha512-KExVEeZWxMZnZhUZtsJcFwz8IvPvgu4G2Z2QyqjZQzUGr32KDYuSxrEYO4w3tFFNbfLozcrKUTvTPi+E9ywJkQ== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" @@ -12962,6 +13077,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-cookie-parser@^2.4.6: + version "2.4.8" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" + integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz" @@ -13309,6 +13429,11 @@ static-extend@^0.1.1: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +statuses@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + store2@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/store2/-/store2-2.12.0.tgz" @@ -13346,6 +13471,13 @@ stream-shift@^1.0.0: resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +strict-event-emitter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd" + integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w== + dependencies: + events "^3.3.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -13966,13 +14098,6 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= - dependencies: - punycode "^2.1.0" - tr46@^2.0.2: version "2.1.0" resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz" @@ -14051,6 +14176,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== +tslib@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" @@ -14102,6 +14232,11 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" @@ -14617,11 +14752,6 @@ web-namespaces@^1.0.0: resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz" @@ -14771,20 +14901,16 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" +whatwg-fetch@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^6.5.0: - version "6.5.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz" @@ -14976,6 +15102,11 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.7: resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz" integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== +yargs-parser@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" + integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA== + yargs@^15.4.1: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" @@ -15019,6 +15150,19 @@ yargs@^17.0.1: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.3.0: + version "17.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" + integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"