diff --git a/README.md b/README.md index 0cc4b6f30..108e8be0f 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed', () => { Build the MSW handlers from the duck's exported URL constants, and start and stop the server around the tests: ```js -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { PATROLS_API_URL } from '../../../ducks/patrols'; diff --git a/jest-config/transformers/transformImportMetaEnv.js b/jest-config/transformers/transformImportMetaEnv.js index 9684f0974..0825b0b63 100644 --- a/jest-config/transformers/transformImportMetaEnv.js +++ b/jest-config/transformers/transformImportMetaEnv.js @@ -1,19 +1,23 @@ 'use strict'; // Custom Babel plugin to transform `import.meta.env.*` into `process.env.*`, -// and to strip any other `import.meta.*` access down to an empty object so -// Jest's CJS transform doesn't choke on syntax Node can't parse outside a -// module. -module.exports = ({ types }) => ({ +// `import.meta.url` into the CJS module's file URL, and to strip any other +// `import.meta.*` access down to an empty object so Jest's CJS transform +// doesn't choke on syntax Node can't parse outside a module. +const isImportMeta = (types, node) => types.isMetaProperty(node) + && node.meta.name === 'import' + && node.property.name === 'meta'; + +module.exports = ({ template, types }) => ({ name: 'transform-import-meta-env', visitor: { MemberExpression: (path) => { - if ( + if (isImportMeta(types, path.node.object) && types.isIdentifier(path.node.property, { name: 'url' })) { + path.replaceWith(template.expression.ast`require('url').pathToFileURL(__filename).href`); + } else if ( types.isMemberExpression(path.node.object) && - types.isMetaProperty(path.node.object.object) && - path.node.object.object.meta.name === 'import' && - path.node.object.object.property.name === 'meta' && + isImportMeta(types, path.node.object.object) && types.isIdentifier(path.node.object.property, { name: 'env' }) ) { path.replaceWith( @@ -29,7 +33,7 @@ module.exports = ({ types }) => ({ } }, MetaProperty: (path) => { - if (path.node.meta.name === 'import' && path.node.property.name === 'meta') { + if (isImportMeta(types, path.node)) { path.replaceWith(types.objectExpression([])); } }, diff --git a/package.json b/package.json index 6c23670f8..ccfa67618 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "@dnd-kit/dom": "^0.5.0", "@dnd-kit/helpers": "^0.5.0", "@dnd-kit/react": "^0.5.0", - "@rjsf/core": "^6.10.1", - "@rjsf/react-bootstrap": "^6.10.1", - "@rjsf/utils": "^6.10.1", - "@rjsf/validator-ajv8": "^6.10.1", + "@rjsf/core": "^6.11.0", + "@rjsf/react-bootstrap": "^6.11.0", + "@rjsf/utils": "^6.11.0", + "@rjsf/validator-ajv8": "^6.11.0", "@turf/buffer": "^7.4.0", "@turf/turf": "^7.4.0", "ajv": "^8.20.0", @@ -40,7 +40,7 @@ "localforage": "^1.10.0", "lodash": "^4.18.1", "lodash-es": "^4.18.1", - "mapbox-gl": "^3.31.0", + "mapbox-gl": "^3.32.0", "pluralize": "^8.0.0", "proj4": "^2.22.0", "react": "^19.3.0", @@ -102,10 +102,10 @@ "jest-environment-jsdom": "^30.5.2", "jest-fixed-jsdom": "^0.0.11", "jest-webgl-canvas-mock": "^2.5.3", - "msw": "2.15.0", + "msw": "3.0.1", "redux-mock-store": "^1.5.5", "resize-observer-polyfill": "^1.5.1", - "sass": "^1.105.0", + "sass": "^1.105.1", "socket.io-mock": "^1.3.2", "stylelint": "^17.15.0", "stylelint-config-css-modules": "^4.6.0", @@ -134,7 +134,7 @@ "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|json)$)": "/jest-config/transformers/fileTransform.js" }, "transformIgnorePatterns": [ - "/node_modules/(?!(@open-draft|@remix-run|@rjsf|@x0k|cookie-es|geodesy|geokdbush|kdbush|lodash-es|msw|nanoid|react-error-boundary|react-ga4|react-router|rettime|until-async|uuid)/)" + "/node_modules/(?!(@msw|@mswjs|@open-draft|@remix-run|@rjsf|@x0k|cookie|cookie-es|geodesy|geokdbush|kdbush|lodash-es|msw|nanoid|react-error-boundary|react-ga4|react-router|rettime|until-async|uuid)/)" ], "watchPlugins": [ "jest-watch-typeahead/filename", diff --git a/src/AddToPatrolModal/AddToPatrolModal.test.js b/src/AddToPatrolModal/AddToPatrolModal.test.js index 9ddb56fc3..fdb1a68da 100644 --- a/src/AddToPatrolModal/AddToPatrolModal.test.js +++ b/src/AddToPatrolModal/AddToPatrolModal.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import merge from 'lodash/merge'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; diff --git a/src/DetailViewComponents/ActivitySection/ContainedReportListItem/index.test.js b/src/DetailViewComponents/ActivitySection/ContainedReportListItem/index.test.js index aa788086a..06991d537 100644 --- a/src/DetailViewComponents/ActivitySection/ContainedReportListItem/index.test.js +++ b/src/DetailViewComponents/ActivitySection/ContainedReportListItem/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import userEvent from '@testing-library/user-event'; diff --git a/src/DetailViewComponents/ActivitySection/index.test.js b/src/DetailViewComponents/ActivitySection/index.test.js index 8539f8aa6..b6fe65dba 100644 --- a/src/DetailViewComponents/ActivitySection/index.test.js +++ b/src/DetailViewComponents/ActivitySection/index.test.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import userEvent from '@testing-library/user-event'; diff --git a/src/Nav/index.test.js b/src/Nav/index.test.js index dc00fd217..02e9bd8fd 100644 --- a/src/Nav/index.test.js +++ b/src/Nav/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import userEvent from '@testing-library/user-event'; @@ -75,6 +75,8 @@ describe('the Nav component', () => { }); test('navigates to login if can not fetch the current user', async () => { + await waitFor(() => expect(store.getState().data.user).not.toEqual({})); + server.use( http.get(CURRENT_USER_API_URL, () => { return HttpResponse.json(null, { status: 403 }); diff --git a/src/NotificationMenu/index.test.js b/src/NotificationMenu/index.test.js index a8903694b..1ae57bb44 100644 --- a/src/NotificationMenu/index.test.js +++ b/src/NotificationMenu/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import userEvent from '@testing-library/user-event'; diff --git a/src/PatrolDetailView/index.test.js b/src/PatrolDetailView/index.test.js index 401435377..1ccd43bf4 100644 --- a/src/PatrolDetailView/index.test.js +++ b/src/PatrolDetailView/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { useLocation, useSearchParams } from 'react-router'; diff --git a/src/ReportFormSummary/index.test.js b/src/ReportFormSummary/index.test.js index 89d15c9f3..72d25e196 100644 --- a/src/ReportFormSummary/index.test.js +++ b/src/ReportFormSummary/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { EVENT_TYPE_SCHEMA_V1_URL } from '../ducks/event-schemas'; diff --git a/src/SideBar/EventsManager/EventOverview/LinksSection/LinkItem/index.test.js b/src/SideBar/EventsManager/EventOverview/LinksSection/LinkItem/index.test.js index 9449840ff..0cdcae533 100644 --- a/src/SideBar/EventsManager/EventOverview/LinksSection/LinkItem/index.test.js +++ b/src/SideBar/EventsManager/EventOverview/LinksSection/LinkItem/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; diff --git a/src/SideBar/EventsManager/EventOverview/ReportDetailView/index.test.js b/src/SideBar/EventsManager/EventOverview/ReportDetailView/index.test.js index f796d7b66..6a9835efb 100644 --- a/src/SideBar/EventsManager/EventOverview/ReportDetailView/index.test.js +++ b/src/SideBar/EventsManager/EventOverview/ReportDetailView/index.test.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { MemoryRouter } from 'react-router'; import { point } from '@turf/turf'; import { Provider } from 'react-redux'; diff --git a/src/SideBar/EventsManager/EventOverview/index.test.js b/src/SideBar/EventsManager/EventOverview/index.test.js index cbed51b54..ea5b4a01e 100644 --- a/src/SideBar/EventsManager/EventOverview/index.test.js +++ b/src/SideBar/EventsManager/EventOverview/index.test.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect } from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { useLocation, useSearchParams } from 'react-router'; diff --git a/src/SideBar/PatrolsManager/Activity/SummaryStats/index.test.js b/src/SideBar/PatrolsManager/Activity/SummaryStats/index.test.js index d71ff40b8..7d1495373 100644 --- a/src/SideBar/PatrolsManager/Activity/SummaryStats/index.test.js +++ b/src/SideBar/PatrolsManager/Activity/SummaryStats/index.test.js @@ -34,7 +34,6 @@ const TRUCK = { const TEAM_AND_TRACKING_OPTIONS = { assets: [TRUCK], - leaders: [DOG, PILOT, RANGER], members: [DOG, PILOT, RANGER], teams: [], }; diff --git a/src/SideBar/PatrolsManager/LegForm/StaticFields/index.js b/src/SideBar/PatrolsManager/LegForm/StaticFields/index.js index fe2c52d7e..c1aad9e78 100644 --- a/src/SideBar/PatrolsManager/LegForm/StaticFields/index.js +++ b/src/SideBar/PatrolsManager/LegForm/StaticFields/index.js @@ -275,7 +275,7 @@ const StaticFields = ({ id: teamLeadSelectId, label: t('teamLeadLabel'), onChange: (teamLead) => onChangeLeg({ teamLead }), - options: teamAndTrackingOptions.leaders, + options: teamAndTrackingOptions.members, renderOptionIcon: renderSubjectOptionIcon, value: leg.teamLead, })} diff --git a/src/SideBar/PatrolsManager/LegForm/StaticFields/index.test.js b/src/SideBar/PatrolsManager/LegForm/StaticFields/index.test.js index b86dc78de..2d5e0d9c9 100644 --- a/src/SideBar/PatrolsManager/LegForm/StaticFields/index.test.js +++ b/src/SideBar/PatrolsManager/LegForm/StaticFields/index.test.js @@ -18,11 +18,10 @@ describe('SideBar - PatrolsManager - LegForm - StaticFields', () => { data: { patrolTeamAndTrackingOptions: { assets: [{ id: 'asset-1', name: 'Radio 7' }], - leaders: [ - { id: 'leader-1', image_url: '/static/ranger-black.svg', name: 'Alex' }, - { id: 'leader-2', image_url: '/static/ranger-black.svg', name: 'Priya' }, + members: [ + { id: 'member-1', image_url: '/static/ranger-black.svg', name: 'Maya Chen' }, + { id: 'member-2', image_url: '/static/ranger-black.svg', name: 'Priya' }, ], - members: [{ id: 'member-1', name: 'Maya Chen' }], teams: [{ display: 'Alpha', id: 'team-1' }], }, }, @@ -82,14 +81,18 @@ describe('SideBar - PatrolsManager - LegForm - StaticFields', () => { }); }); - test('offers the leaders the site serves as team leads, each with its icon', async () => { + test('offers only the team members the site serves as team leads, each with its icon', async () => { + store.data.patrolTeamAndTrackingOptions.leaders = [{ id: 'leader-1', name: 'Alex' }]; + renderStaticFields(); await userEvent.click(screen.getByLabelText('Team Lead')); - const optionIcon = (await screen.findByText('Alex')).querySelector('img'); + const optionIcon = (await screen.findByText('Maya Chen')).querySelector('img'); expect(optionIcon).toHaveAttribute('src', expect.stringContaining('/static/ranger-black.svg')); + expect(screen.getByText('Priya')).toBeVisible(); + expect(screen.queryByText('Alex')).not.toBeInTheDocument(); }); test('reports the team lead the user picks', async () => { @@ -99,7 +102,7 @@ describe('SideBar - PatrolsManager - LegForm - StaticFields', () => { await userEvent.click(await screen.findByText('Priya')); expect(onChangeLeg).toHaveBeenCalledWith({ - teamLead: store.data.patrolTeamAndTrackingOptions.leaders[1], + teamLead: store.data.patrolTeamAndTrackingOptions.members[1], }); }); diff --git a/src/SideBar/PatrolsManager/LegForm/index.test.js b/src/SideBar/PatrolsManager/LegForm/index.test.js index 1885fde31..f6dfd98bb 100644 --- a/src/SideBar/PatrolsManager/LegForm/index.test.js +++ b/src/SideBar/PatrolsManager/LegForm/index.test.js @@ -56,7 +56,7 @@ describe('SideBar - PatrolsManager - LegForm', () => { [routinePatrol.value]: { isLoading: false, schema: patrolTypeFieldsSchema }, }, patrolStore: {}, - patrolTeamAndTrackingOptions: { assets: [], leaders: [], members: [], teams: [] }, + patrolTeamAndTrackingOptions: { assets: [], members: [], teams: [] }, patrolTypes, userContent: {}, }, diff --git a/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.js b/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.js index 574f11835..49d7045b1 100644 --- a/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.js +++ b/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.js @@ -1,5 +1,4 @@ import { isFuture } from 'date-fns'; -import uniq from 'lodash/uniq'; import parseLegDraftDateTime from '../parseLegDraftDateTime'; @@ -19,8 +18,7 @@ const buildLegSegment = (leg, { isFirstLeg = true } = {}) => { end_location: leg.endLocation, events: [], leader: leg.teamLead, - // The API rejects a lead who is not one of the members. - members: uniq([...(leg.teamLead ? [leg.teamLead.id] : []), ...leg.teamMembers.map((teamMember) => teamMember.id)]), + members: leg.teamMembers.map((teamMember) => teamMember.id), patrol_type: leg.patrolType?.value ?? null, priority: leg.patrolType?.default_priority ?? 0, scheduled_end: isEndScheduled ? endDateTime.toISOString() : null, diff --git a/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.test.js b/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.test.js index f5e4e6c68..20e119984 100644 --- a/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.test.js +++ b/src/SideBar/PatrolsManager/LegForm/utils/buildLegSegment/index.test.js @@ -84,14 +84,15 @@ describe('SideBar - PatrolsManager - LegForm - utils - buildLegSegment', () => { expect(segment.team).toBe('team-1'); }); - test('adds the team lead to the members the API stores', () => { + test('leaves the team lead off the members when the user did not pick them as one', () => { const segment = buildLegSegment({ ...leg, teamLead: { id: 'leader-1', name: 'Alex' }, teamMembers: [{ id: 'member-1', name: 'Nadia' }], }); - expect(segment.members).toEqual(['leader-1', 'member-1']); + expect(segment.leader).toEqual({ id: 'leader-1', name: 'Alex' }); + expect(segment.members).toEqual(['member-1']); }); test('leaves the team, the members and the assets empty when the user picked none', () => { diff --git a/src/SideBar/PatrolsManager/LegManager/EditLeg/index.test.js b/src/SideBar/PatrolsManager/LegManager/EditLeg/index.test.js index c91973c34..decc9280f 100644 --- a/src/SideBar/PatrolsManager/LegManager/EditLeg/index.test.js +++ b/src/SideBar/PatrolsManager/LegManager/EditLeg/index.test.js @@ -118,7 +118,6 @@ describe('SideBar - PatrolsManager - LegManager - EditLeg', () => { patrolStore: { [patrol.id]: patrol }, patrolTeamAndTrackingOptions: { assets: [asset], - leaders: [teamLead], members: [teamLead, teamMember, unassignedMember], teams: [team], }, diff --git a/src/SideBar/PatrolsManager/LegManager/NewLeg/index.test.js b/src/SideBar/PatrolsManager/LegManager/NewLeg/index.test.js index fd1f07097..2bb722e8d 100644 --- a/src/SideBar/PatrolsManager/LegManager/NewLeg/index.test.js +++ b/src/SideBar/PatrolsManager/LegManager/NewLeg/index.test.js @@ -80,7 +80,6 @@ describe('SideBar - PatrolsManager - LegManager - NewLeg', () => { patrolStore: { [patrol.id]: patrol }, patrolTeamAndTrackingOptions: { assets: [asset], - leaders: [teamLead], members: [teamLead, teamMember], teams: [team], }, diff --git a/src/SideBar/PatrolsManager/LegManager/NewLeg/utils/buildNewLegDraft/index.test.js b/src/SideBar/PatrolsManager/LegManager/NewLeg/utils/buildNewLegDraft/index.test.js index 0d4357ab7..1f7da028e 100644 --- a/src/SideBar/PatrolsManager/LegManager/NewLeg/utils/buildNewLegDraft/index.test.js +++ b/src/SideBar/PatrolsManager/LegManager/NewLeg/utils/buildNewLegDraft/index.test.js @@ -7,7 +7,7 @@ describe('SideBar - PatrolsManager - LegManager - NewLeg - utils - buildNewLegDr const member = { id: 'member-1', name: 'Nadia' }; const team = { display: 'Alpha', id: 'team-1' }; - const teamAndTrackingOptions = { assets: [asset], leaders: [], members: [member], teams: [team] }; + const teamAndTrackingOptions = { assets: [asset], members: [member], teams: [team] }; const previousPatrolSegment = { assets: [asset.id], diff --git a/src/SideBar/PatrolsManager/LegManager/index.test.js b/src/SideBar/PatrolsManager/LegManager/index.test.js index d0e6e360a..3d2e01b6c 100644 --- a/src/SideBar/PatrolsManager/LegManager/index.test.js +++ b/src/SideBar/PatrolsManager/LegManager/index.test.js @@ -48,7 +48,7 @@ describe('SideBar - PatrolsManager - LegManager', () => { store = { data: { patrolStore: { [patrol.id]: patrol }, - patrolTeamAndTrackingOptions: { assets: [], hasFetched: true, leaders: [], members: [], teams: [] }, + patrolTeamAndTrackingOptions: { assets: [], hasFetched: true, members: [], teams: [] }, patrolTypes, }, }; diff --git a/src/SideBar/PatrolsManager/NewPatrol/index.test.js b/src/SideBar/PatrolsManager/NewPatrol/index.test.js index ab42a54ea..4d2537784 100644 --- a/src/SideBar/PatrolsManager/NewPatrol/index.test.js +++ b/src/SideBar/PatrolsManager/NewPatrol/index.test.js @@ -61,7 +61,7 @@ describe('SideBar - PatrolsManager - NewPatrol', () => { [routinePatrol.value]: { isLoading: false, schema: patrolTypeFieldsSchema }, }, patrolStore: {}, - patrolTeamAndTrackingOptions: { assets: [], leaders: [], members: [], teams: [] }, + patrolTeamAndTrackingOptions: { assets: [], members: [], teams: [] }, patrolTypes, user: { permissions: { [PERMISSION_KEYS.PATROLS]: [PERMISSIONS.CREATE, PERMISSIONS.READ] } }, userContent: {}, diff --git a/src/SideBar/PatrolsManager/PatrolOverview/index.test.js b/src/SideBar/PatrolsManager/PatrolOverview/index.test.js index 650cd0867..8cf67f6ee 100644 --- a/src/SideBar/PatrolsManager/PatrolOverview/index.test.js +++ b/src/SideBar/PatrolsManager/PatrolOverview/index.test.js @@ -682,7 +682,9 @@ describe('SideBar - PatrolsManager - PatrolOverview', () => { await userEvent.click(screen.getByText('Go Back')); - expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument(); + }); expect(screen.getByTestId('test-location')).toHaveTextContent(`/patrols/${patrolWithoutLeader.id}`); }); @@ -724,7 +726,9 @@ describe('SideBar - PatrolsManager - PatrolOverview', () => { await userEvent.click(await screen.findByText('Discard')); - expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument(); + }); await waitFor(() => { expect(screen.getByTestId('test-location')).not.toHaveTextContent(patrolWithoutLeader.id); }); @@ -742,7 +746,9 @@ describe('SideBar - PatrolsManager - PatrolOverview', () => { await userEvent.click(await screen.findByTestId('navigation-prompt-positive-continue-btn')); - expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument(); + }); await waitFor(() => { expect(screen.getByTestId('test-location')).not.toHaveTextContent(patrolWithoutLeader.id); }); diff --git a/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.js b/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.js index 7e216380f..df6cb029b 100644 --- a/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.js +++ b/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.js @@ -40,7 +40,7 @@ const FiltersPopover = ({ className = '', onClose, ref, trigger, ...otherProps } const tracker = useContext(TrackerContext); const patrolFilter = useSelector((state) => state.data.patrolFilter); - const patrolLeaders = useSelector((state) => state.data.patrolTeamAndTrackingOptions.leaders); + const patrolMembers = useSelector((state) => state.data.patrolTeamAndTrackingOptions.members); const patrolTypes = useSelector((state) => state.data.patrolTypes); const bodyRef = useRef(null); @@ -57,10 +57,10 @@ const FiltersPopover = ({ className = '', onClose, ref, trigger, ...otherProps } || !isEqual(INITIAL_FILTER_STATE.filter.patrol_type, patrolFilter.filter.patrol_type) || !isEqual(INITIAL_FILTER_STATE.filter.tracked_by, patrolFilter.filter.tracked_by); - // A filter can outlive the leader it names, and it still filters the feed, + // A filter can outlive the member it names, and it still filters the feed, // so it stays in the select where it can be removed. - const teamLeads = patrolFilter.filter.tracked_by.map((leaderId) => patrolLeaders - .find((patrolLeader) => patrolLeader.id === leaderId) ?? { id: leaderId, name: t('unknownTeamLeadLabel') }); + const teamLeads = patrolFilter.filter.tracked_by.map((leaderId) => patrolMembers + .find((patrolMember) => patrolMember.id === leaderId) ?? { id: leaderId, name: t('unknownTeamLeadLabel') }); const statusOptions = STATUS_IDS.map((statusId) => ({ label: t(`patrolStatuses.${statusId}`), value: statusId })); @@ -173,7 +173,7 @@ const FiltersPopover = ({ className = '', onClose, ref, trigger, ...otherProps } onChange={onChangeTeamLeads} onMenuClose={() => setIsTeamLeadMenuOpen(false)} onMenuOpen={() => setIsTeamLeadMenuOpen(true)} - options={patrolLeaders} + options={patrolMembers} renderOptionIcon={renderTeamLeadIcon} value={teamLeads} /> diff --git a/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.test.js b/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.test.js index ae0d38561..4dcd9cdfb 100644 --- a/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.test.js +++ b/src/SideBar/PatrolsManager/PatrolsFeed/Filters/FiltersPopover/index.test.js @@ -32,7 +32,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters - FiltersPopover', () status: INITIAL_FILTER_STATE.status, }, patrolTeamAndTrackingOptions: { - leaders: [{ id: 'Leader 1', name: 'Alpha' }, { id: 'Leader 2', name: 'Bravo' }], + members: [{ id: 'Member 1', name: 'Alpha' }, { id: 'Member 2', name: 'Bravo' }], }, patrolTypes: [ { display: 'Dog Patrol', icon_id: 'dog-patrol-icon', id: 'dog-patrol-id', value: 'dog_patrol' }, @@ -90,16 +90,28 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters - FiltersPopover', () expect(screen.getByRole('dialog')).toHaveFocus(); }); + test('offers only the team members the site serves as team leads', async () => { + store.data.patrolTeamAndTrackingOptions.leaders = [{ id: 'Leader 1', name: 'Charlie' }]; + + renderFiltersPopover(); + + await userEvent.click(teamLeadSelect()); + + expect(await screen.findByText('Alpha')).toBeVisible(); + expect(screen.getByText('Bravo')).toBeVisible(); + expect(screen.queryByText('Charlie')).not.toBeInTheDocument(); + }); + test('shows the team leads in the filter', () => { - store.data.patrolFilter.filter.tracked_by = ['Leader 2']; + store.data.patrolFilter.filter.tracked_by = ['Member 2']; renderFiltersPopover(); expect(screen.getByRole('dialog')).toHaveTextContent('Bravo'); }); - test('shows a team lead selection whose leader is no longer offered as an unknown team lead', () => { - store.data.patrolFilter.filter.tracked_by = ['Leader 1', 'Retired leader']; + test('shows a team lead selection whose member is no longer offered as an unknown team lead', () => { + store.data.patrolFilter.filter.tracked_by = ['Member 1', 'Retired member']; renderFiltersPopover(); @@ -108,7 +120,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters - FiltersPopover', () }); test('removes an unknown team lead from the filter when the user clears it', async () => { - store.data.patrolFilter.filter.tracked_by = ['Retired leader']; + store.data.patrolFilter.filter.tracked_by = ['Retired member']; renderFiltersPopover(); @@ -120,19 +132,19 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters - FiltersPopover', () }); test('adds a team lead to the filter when the user picks one', async () => { - store.data.patrolFilter.filter.tracked_by = ['Leader 1']; + store.data.patrolFilter.filter.tracked_by = ['Member 1']; renderFiltersPopover(); await userEvent.type(teamLeadSelect(), 'Bravo'); await userEvent.keyboard('{Enter}'); - expect(updatePatrolFilter).toHaveBeenCalledWith({ filter: { tracked_by: ['Leader 1', 'Leader 2'] } }); + expect(updatePatrolFilter).toHaveBeenCalledWith({ filter: { tracked_by: ['Member 1', 'Member 2'] } }); expect(track).toHaveBeenCalledWith('Set the team lead filter'); }); test('clears the team lead filter when the user removes every team lead', async () => { - store.data.patrolFilter.filter.tracked_by = ['Leader 1']; + store.data.patrolFilter.filter.tracked_by = ['Member 1']; renderFiltersPopover(); @@ -262,7 +274,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters - FiltersPopover', () }); test.each([ - ['team lead', (patrolFilter) => { patrolFilter.filter.tracked_by = ['Leader 1']; }], + ['team lead', (patrolFilter) => { patrolFilter.filter.tracked_by = ['Member 1']; }], ['status', (patrolFilter) => { patrolFilter.status = ['done']; }], ['patrol type', (patrolFilter) => { patrolFilter.filter.patrol_type = ['fence-patrol-id']; }], ])('offers to reset all filters once the %s filter is set', (_, setFilter) => { @@ -378,7 +390,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters - FiltersPopover', () await userEvent.click(teamLeadSelect()); await userEvent.click(await screen.findByText('Bravo')); - expect(updatePatrolFilter).toHaveBeenCalledWith({ filter: { tracked_by: ['Leader 2'] } }); + expect(updatePatrolFilter).toHaveBeenCalledWith({ filter: { tracked_by: ['Member 2'] } }); expect(onClose).not.toHaveBeenCalled(); }); diff --git a/src/SideBar/PatrolsManager/PatrolsFeed/Filters/index.test.js b/src/SideBar/PatrolsManager/PatrolsFeed/Filters/index.test.js index 4f949fd34..6a72eaf22 100644 --- a/src/SideBar/PatrolsManager/PatrolsFeed/Filters/index.test.js +++ b/src/SideBar/PatrolsManager/PatrolsFeed/Filters/index.test.js @@ -39,7 +39,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed - Filters', () => { }, status: INITIAL_FILTER_STATE.status, }, - patrolTeamAndTrackingOptions: { leaders: [] }, + patrolTeamAndTrackingOptions: { members: [] }, patrolTypes: [], }, }; diff --git a/src/SideBar/PatrolsManager/PatrolsFeed/index.test.js b/src/SideBar/PatrolsManager/PatrolsFeed/index.test.js index aeacd1f32..f43199a2c 100644 --- a/src/SideBar/PatrolsManager/PatrolsFeed/index.test.js +++ b/src/SideBar/PatrolsManager/PatrolsFeed/index.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import userEvent from '@testing-library/user-event'; import { activePatrol, scheduledPatrol } from '../../../__test-helpers/fixtures/patrols'; @@ -62,7 +62,7 @@ describe('SideBar - PatrolsManager - PatrolsFeed', () => { status: INITIAL_FILTER_STATE.status, }, patrolStore: { [activePatrol.id]: activePatrol, [scheduledPatrol.id]: scheduledPatrol }, - patrolTeamAndTrackingOptions: { leaders: [] }, + patrolTeamAndTrackingOptions: { members: [] }, patrolTypes: [], patrolsFeed: [activePatrol.id, scheduledPatrol.id], subjectStore: {}, diff --git a/src/SideBar/PatrolsManager/utils/buildLegDraft/index.test.js b/src/SideBar/PatrolsManager/utils/buildLegDraft/index.test.js index 907c2bb50..a61d07c41 100644 --- a/src/SideBar/PatrolsManager/utils/buildLegDraft/index.test.js +++ b/src/SideBar/PatrolsManager/utils/buildLegDraft/index.test.js @@ -95,7 +95,7 @@ describe('SideBar - PatrolsManager - utils - buildLegDraft', () => { const draft = buildLegDraft( { assets: [asset.id], members: [member.id], team: team.id }, patrolTypes, - { assets: [asset], leaders: [], members: [member], teams: [team] } + { assets: [asset], members: [member], teams: [team] } ); expect(draft.assets).toEqual([asset]); @@ -111,7 +111,7 @@ describe('SideBar - PatrolsManager - utils - buildLegDraft', () => { const draft = buildLegDraft( { assets: [deactivatedAsset.id], members: [member.id, deactivatedMember.id] }, patrolTypes, - { assets: [], leaders: [], members: [member], teams: [] }, + { assets: [], members: [member], teams: [] }, { [deactivatedAsset.id]: deactivatedAsset, [deactivatedMember.id]: deactivatedMember } ); diff --git a/src/SideBar/index.test.js b/src/SideBar/index.test.js index b35293642..7e72cd44c 100644 --- a/src/SideBar/index.test.js +++ b/src/SideBar/index.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { Provider } from 'react-redux'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { useLocation } from 'react-router'; import userEvent from '@testing-library/user-event'; diff --git a/src/SideBar/useFetchPatrolsFeed/index.test.js b/src/SideBar/useFetchPatrolsFeed/index.test.js index cbd7310d6..5d2d170c1 100644 --- a/src/SideBar/useFetchPatrolsFeed/index.test.js +++ b/src/SideBar/useFetchPatrolsFeed/index.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { CancelToken } from 'axios'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; diff --git a/src/SideBar/useReportsFeed/index.js b/src/SideBar/useReportsFeed/index.js index f40b88f72..5fdd9a7da 100644 --- a/src/SideBar/useReportsFeed/index.js +++ b/src/SideBar/useReportsFeed/index.js @@ -1,6 +1,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import debounce from 'lodash/debounce'; +import { isCancel } from 'axios'; import isEqual from 'react-fast-compare'; import { useDispatch, useSelector } from 'react-redux'; @@ -53,6 +54,11 @@ const useReportsFeed = () => { } return dispatch(fetchEventFeed({}, objectToParamString(eventParams.current))) + .catch((error) => { + if (!isCancel(error)) { + console.warn('Failed to fetch the event feed', error); + } + }) .finally(() => setEventLoadState(false)); }), [dispatch]); diff --git a/src/SideBar/useReportsFeed/index.test.js b/src/SideBar/useReportsFeed/index.test.js index 268bcb2b6..0cc664c5a 100644 --- a/src/SideBar/useReportsFeed/index.test.js +++ b/src/SideBar/useReportsFeed/index.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; @@ -54,9 +54,12 @@ describe('useReportsFeed', () => { jest.restoreAllMocks(); }); + const renderUseReportsFeed = (builtStore = mockStore(store)) => renderHook(() => useReportsFeed(), { + wrapper: ({ children }) => {children}, + }); + test('returns the reportsFetchFeed properties and methods', async () => { - const wrapper = ({ children }) => {children}; - const { result } = renderHook(() => useReportsFeed(), { wrapper }); + const { result } = renderUseReportsFeed(); const reportsFetchFeed = result.current; @@ -68,9 +71,7 @@ describe('useReportsFeed', () => { }); test('returns the same feed until something in it changes', () => { - const builtStore = mockStore(store); - const wrapper = ({ children }) => {children}; - const { rerender, result } = renderHook(() => useReportsFeed(), { wrapper }); + const { rerender, result } = renderUseReportsFeed(); const initialFeed = result.current; rerender(); @@ -80,8 +81,7 @@ describe('useReportsFeed', () => { test('loads the reports feed for georestricted users', async () => { const builtStore = mockStore(store); - const wrapper = ({ children }) => {children}; - renderHook(() => useReportsFeed(), { wrapper }); + renderUseReportsFeed(builtStore); const actions = builtStore.getActions(); @@ -96,8 +96,7 @@ describe('useReportsFeed', () => { test('loads the reports feed normally', async () => { store.data.user.permissions = []; const builtStore = mockStore(store); - const wrapper = ({ children }) => {children}; - renderHook(() => useReportsFeed(), { wrapper }); + renderUseReportsFeed(builtStore); const actions = builtStore.getActions(); @@ -108,4 +107,48 @@ describe('useReportsFeed', () => { expect(actions[2].type).toBe('FEED_FETCH_SUCCESS'); }); }); + + test('warns and stops loading when the event feed request fails', async () => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + server.use(http.get(EVENTS_API_URL, () => new HttpResponse(null, { status: 500 }))); + + const { result } = renderUseReportsFeed(); + + await waitFor(() => { + expect(result.current.loadingEventFeed).toBe(false); + }); + expect(consoleWarnSpy).toHaveBeenCalledWith('Failed to fetch the event feed', expect.any(Error)); + }); + + test('does not warn when a newer event feed request cancels the pending one', async () => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + let releaseFirstRequest; + const firstRequestGate = new Promise((resolve) => { + releaseFirstRequest = resolve; + }); + let requestCount = 0; + server.use(http.get(EVENTS_API_URL, async () => { + requestCount += 1; + if (requestCount === 1) { + await firstRequestGate; + } + + return HttpResponse.json(eventFeedResponse); + })); + const builtStore = mockStore(store); + + const { result } = renderUseReportsFeed(builtStore); + await waitFor(() => { + expect(requestCount).toBe(1); + }); + result.current.loadFeedEvents(); + + await waitFor(() => { + expect(builtStore.getActions().map((action) => action.type)).toContain('FEED_FETCH_SUCCESS'); + }); + releaseFirstRequest(); + + expect(builtStore.getActions().map((action) => action.type)).toContain('FEED_FETCH_ERROR'); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + }); }); diff --git a/src/TimepointPopup/index.test.js b/src/TimepointPopup/index.test.js index 275026ffb..4ed419013 100644 --- a/src/TimepointPopup/index.test.js +++ b/src/TimepointPopup/index.test.js @@ -1,6 +1,6 @@ import React from 'react'; import axios from 'axios'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { Provider } from 'react-redux'; import userEvent from '@testing-library/user-event'; diff --git a/src/ducks/analyzers.test.js b/src/ducks/analyzers.test.js index d238375bc..9404d1d9b 100644 --- a/src/ducks/analyzers.test.js +++ b/src/ducks/analyzers.test.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import analyzersReducer, { diff --git a/src/ducks/community.test.js b/src/ducks/community.test.js index adcf1e190..769914bbd 100644 --- a/src/ducks/community.test.js +++ b/src/ducks/community.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import communityReducer, { diff --git a/src/ducks/event-categories/index.test.js b/src/ducks/event-categories/index.test.js index d45fbcab4..a97ee6283 100644 --- a/src/ducks/event-categories/index.test.js +++ b/src/ducks/event-categories/index.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import eventCategoriesReducer, { diff --git a/src/ducks/event-schemas/index.test.js b/src/ducks/event-schemas/index.test.js index 342e7f97d..8cb3c845d 100644 --- a/src/ducks/event-schemas/index.test.js +++ b/src/ducks/event-schemas/index.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import eventSchemasReducer, { diff --git a/src/ducks/event-types/index.test.js b/src/ducks/event-types/index.test.js index 5025b0579..9c140e59e 100644 --- a/src/ducks/event-types/index.test.js +++ b/src/ducks/event-types/index.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import eventTypesReducer, { diff --git a/src/ducks/events.test.js b/src/ducks/events.test.js index 819356822..3382c4bf6 100644 --- a/src/ducks/events.test.js +++ b/src/ducks/events.test.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { createMapMock } from '../__test-helpers/mocks'; diff --git a/src/ducks/messaging.test.js b/src/ducks/messaging.test.js index 926c5da85..3c09ad1b7 100644 --- a/src/ducks/messaging.test.js +++ b/src/ducks/messaging.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { fetchAllMessages, MESSAGING_API_URL } from './messaging'; diff --git a/src/ducks/patrol-schemas/index.js b/src/ducks/patrol-schemas/index.js index c0cb7a97d..a917aa266 100644 --- a/src/ducks/patrol-schemas/index.js +++ b/src/ducks/patrol-schemas/index.js @@ -33,14 +33,14 @@ export const fetchDefaultPatrolSegmentTypeSchema = () => async (dispatch) => { dispatch({ type: FETCH_DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA }); try { - const { data } = await axios.get(DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_API_URL, { params: SCHEMA_REQUEST_PARAMS }); + const response = await axios.get(DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_API_URL, { params: SCHEMA_REQUEST_PARAMS }); - const schema = data?.json ? data : data?.data; + dispatch({ payload: response.data, type: FETCH_DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_SUCCESS }); - dispatch({ payload: schema, type: FETCH_DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_SUCCESS }); - - return schema; + return response.data; } catch (error) { + console.warn('error fetching the default patrol segment type schema', error); + dispatch({ payload: error, type: FETCH_DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_FAILURE }); return null; @@ -51,15 +51,15 @@ export const fetchPatrolTypeSchema = (patrolTypeValue) => async (dispatch) => { dispatch({ payload: { patrolTypeValue }, type: FETCH_PATROL_TYPE_SCHEMA }); try { - const { data } = await axios.get( + const response = await axios.get( PATROL_TYPE_SCHEMA_API_URL(patrolTypeValue), { params: PATROL_TYPE_SCHEMA_REQUEST_PARAMS } ); - const schema = data?.json ? data : data?.data; - - dispatch({ payload: { patrolTypeValue, schema }, type: FETCH_PATROL_TYPE_SCHEMA_SUCCESS }); + dispatch({ payload: { patrolTypeValue, schema: response.data }, type: FETCH_PATROL_TYPE_SCHEMA_SUCCESS }); } catch (error) { + console.warn('error fetching the patrol type schema', error); + dispatch({ payload: { error, patrolTypeValue }, type: FETCH_PATROL_TYPE_SCHEMA_FAILURE }); } }; diff --git a/src/ducks/patrol-schemas/index.test.js b/src/ducks/patrol-schemas/index.test.js index a51c58a3e..0513b903b 100644 --- a/src/ducks/patrol-schemas/index.test.js +++ b/src/ducks/patrol-schemas/index.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { defaultPatrolSegmentTypeSchema, patrolTypeFieldsSchema } from '../../__test-helpers/fixtures/patrol-schemas'; @@ -23,11 +23,11 @@ import patrolSchemasReducer, { const server = setupServer( http.get( PATROL_TYPE_SCHEMA_API_URL('dog_patrol'), - () => HttpResponse.json({ data: patrolTypeFieldsSchema }) + () => HttpResponse.json(patrolTypeFieldsSchema) ), http.get( DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_API_URL, - () => HttpResponse.json({ data: defaultPatrolSegmentTypeSchema }) + () => HttpResponse.json(defaultPatrolSegmentTypeSchema) ), ); @@ -60,7 +60,7 @@ describe('Ducks - Patrol schemas', () => { server.use(http.get(PATROL_TYPE_SCHEMA_API_URL('dog_patrol'), ({ request }) => { requestParams = new URL(request.url).searchParams; - return HttpResponse.json({ data: patrolTypeFieldsSchema }); + return HttpResponse.json(patrolTypeFieldsSchema); })); await fetchPatrolTypeSchema('dog_patrol')(jest.fn()); @@ -69,24 +69,14 @@ describe('Ducks - Patrol schemas', () => { expect(requestParams.get('s_format')).toBe('enum'); }); - test('reads the schema out of an answer that does not wrap it', async () => { - const dispatch = jest.fn(); - server.use(http.get(PATROL_TYPE_SCHEMA_API_URL('dog_patrol'), () => HttpResponse.json(patrolTypeFieldsSchema))); - - await fetchPatrolTypeSchema('dog_patrol')(dispatch); - - expect(dispatch).toHaveBeenCalledWith({ - payload: { patrolTypeValue: 'dog_patrol', schema: patrolTypeFieldsSchema }, - type: FETCH_PATROL_TYPE_SCHEMA_SUCCESS, - }); - }); - test('reports the failure when the schema cannot be fetched', async () => { const dispatch = jest.fn(); + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); server.use(http.get(PATROL_TYPE_SCHEMA_API_URL('dog_patrol'), () => HttpResponse.error())); await fetchPatrolTypeSchema('dog_patrol')(dispatch); + expect(warn).toHaveBeenCalledWith('error fetching the patrol type schema', expect.any(Error)); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch.mock.calls[1][0].type).toBe(FETCH_PATROL_TYPE_SCHEMA_FAILURE); expect(dispatch.mock.calls[1][0].payload.patrolTypeValue).toBe('dog_patrol'); @@ -109,34 +99,26 @@ describe('Ducks - Patrol schemas', () => { test('reports the error of a schema that could not be fetched', async () => { const store = mockStore({ data: {}, view: {} }); + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); server.use(http.get( DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_API_URL, () => new HttpResponse(null, { status: 500 }) )); expect(await store.dispatch(fetchDefaultPatrolSegmentTypeSchema())).toBeNull(); + expect(warn).toHaveBeenCalledWith('error fetching the default patrol segment type schema', expect.any(Error)); expect(store.getActions()).toEqual([ { type: FETCH_DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA }, { payload: expect.any(Error), type: FETCH_DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_FAILURE }, ]); }); - test('reads the schema out of an answer that does not wrap it', async () => { - const store = mockStore({ data: {}, view: {} }); - server.use(http.get( - DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_API_URL, - () => HttpResponse.json(defaultPatrolSegmentTypeSchema) - )); - - expect(await store.dispatch(fetchDefaultPatrolSegmentTypeSchema())).toEqual(defaultPatrolSegmentTypeSchema); - }); - test('asks for the schema pre rendered and with its choice lists as enumerations', async () => { let requestParams; server.use(http.get(DEFAULT_PATROL_SEGMENT_TYPE_SCHEMA_API_URL, ({ request }) => { requestParams = new URL(request.url).searchParams; - return HttpResponse.json({ data: defaultPatrolSegmentTypeSchema }); + return HttpResponse.json(defaultPatrolSegmentTypeSchema); })); await fetchDefaultPatrolSegmentTypeSchema()(jest.fn()); @@ -144,7 +126,6 @@ describe('Ducks - Patrol schemas', () => { expect(requestParams.get('pre_render')).toBe('true'); expect(requestParams.get('s_format')).toBe('enum'); }); - }); describe('patrolSchemasReducer', () => { diff --git a/src/ducks/patrols/index.test.js b/src/ducks/patrols/index.test.js index 071eed7fe..74b359a5f 100644 --- a/src/ducks/patrols/index.test.js +++ b/src/ducks/patrols/index.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { mockStore } from '../../__test-helpers/MockStore'; diff --git a/src/ducks/system-status.test.js b/src/ducks/system-status.test.js index 85930d749..dc322af83 100644 --- a/src/ducks/system-status.test.js +++ b/src/ducks/system-status.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { diff --git a/src/ducks/user-content/index.test.js b/src/ducks/user-content/index.test.js index 4f08ed519..a66358937 100644 --- a/src/ducks/user-content/index.test.js +++ b/src/ducks/user-content/index.test.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import userContentReducer, { diff --git a/src/setupTests.js b/src/setupTests.js index 09190d6f8..16b4b6945 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -1,5 +1,6 @@ import '@testing-library/jest-dom'; import 'jest-webgl-canvas-mock'; +import { clearImmediate, setImmediate } from 'node:timers'; import dotenv from 'dotenv'; import ReactGA4 from 'react-ga4'; import ResizeObserver from 'resize-observer-polyfill'; @@ -44,6 +45,9 @@ global.console = { debug: console.debug, }; +global.clearImmediate = clearImmediate; +global.setImmediate = setImmediate; + global.IntersectionObserver = class IntersectionObserver { disconnect = jest.fn(); observe = jest.fn(); diff --git a/src/utils/events.test.js b/src/utils/events.test.js index c82697a8e..a3f200232 100644 --- a/src/utils/events.test.js +++ b/src/utils/events.test.js @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import { setupServer } from 'msw/node'; import { diff --git a/src/utils/parallelPaginatedRequest.test.js b/src/utils/parallelPaginatedRequest.test.js index ca6a59f61..6ba2f8394 100644 --- a/src/utils/parallelPaginatedRequest.test.js +++ b/src/utils/parallelPaginatedRequest.test.js @@ -1,5 +1,5 @@ import { setupServer } from 'msw/node'; -import { http, HttpResponse } from 'msw'; +import { http, HttpResponse } from 'msw/http'; import parallelPaginatedRequest from './parallelPaginatedRequest'; import { EVENTS_API_URL } from '../ducks/events'; diff --git a/src/utils/patrols.js b/src/utils/patrols.js index c4170ada6..17af12012 100644 --- a/src/utils/patrols.js +++ b/src/utils/patrols.js @@ -1003,11 +1003,6 @@ const withPatrolSegmentContinuedAt = (patrolSegment, startTime, { isPause }) => // the icon the server derived from its type is not the client's to send. ...omit(patrolSegment, ['end_location', 'events', 'icon_id', 'id', 'image_url', 'start_location', 'updates']), is_pause: isPause, - // The API rejects a leg whose lead is not one of its members, and a lead is - // on the team they lead — a leg from before rosters existed has none. - ...(patrolSegment.leader?.id - ? { members: uniq([patrolSegment.leader.id, ...(patrolSegment.members ?? [])]) } - : {}), scheduled_end: null, scheduled_start: null, time_range: { end_time: null, start_time: startTime }, diff --git a/src/utils/patrols.test.js b/src/utils/patrols.test.js index a845027c5..a96add923 100644 --- a/src/utils/patrols.test.js +++ b/src/utils/patrols.test.js @@ -575,28 +575,21 @@ describe('Patrols utils', () => { }); }); - test('puts the lead on the team of the copy when the leg it copies leaves them off', () => { + test('keeps the lead off the team members of the copy when the leg it copies leaves them off', () => { const patrolWithLeadOffTheTeam = { patrol_segments: [{ ...runningLeg, members: ['member-1'] }] }; const { patrol_segments: [, pause] } = buildPatrolPauseUpdate(patrolWithLeadOffTheTeam); - expect(pause.members).toEqual(['leader-1', 'member-1']); + expect(pause.leader).toEqual({ id: 'leader-1' }); + expect(pause.members).toEqual(['member-1']); }); - test('gives the copy a team of the lead alone when the leg it copies has no team', () => { - const patrolWithNoTeam = { patrol_segments: [omit(runningLeg, ['members'])] }; + test('sends no team members for the copy when the leg it copies has none', () => { + const patrolWithNoTeamMembers = { patrol_segments: [omit(runningLeg, ['members'])] }; - const { patrol_segments: [, pause] } = buildPatrolPauseUpdate(patrolWithNoTeam); + const { patrol_segments: [, pause] } = buildPatrolPauseUpdate(patrolWithNoTeamMembers); - expect(pause.members).toEqual(['leader-1']); - }); - - test('keeps the team of the copy as it was when the leg it copies has no lead', () => { - const patrolWithNoLead = { patrol_segments: [{ ...runningLeg, leader: null }] }; - - const { patrol_segments: [, pause] } = buildPatrolPauseUpdate(patrolWithNoLead); - - expect(pause.members).toEqual(['leader-1', 'member-1']); + expect(pause).not.toHaveProperty('members'); }); test('leaves the identity and the history of the leg it copies behind', () => { @@ -1763,7 +1756,7 @@ describe('Patrols utils', () => { let teamAndTrackingOptions; beforeEach(() => { - teamAndTrackingOptions = { assets: [asset], leaders: [], members: [member], teams: [team] }; + teamAndTrackingOptions = { assets: [asset], members: [member], teams: [team] }; }); test('resolves the team, members and assets the leg carries as ids', () => { @@ -1847,7 +1840,7 @@ describe('Patrols utils', () => { let teamAndTrackingOptions; beforeEach(() => { - teamAndTrackingOptions = { assets: [asset], leaders: [leader], members: [leader, teamMember], teams: [] }; + teamAndTrackingOptions = { assets: [asset], members: [leader, teamMember], teams: [] }; }); test('returns the leg\'s leader, team members and assets, the leader first', () => { diff --git a/yarn.lock b/yarn.lock index 82d14df3c..f095dd1a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3556,17 +3556,24 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.41.3": - version: 0.41.9 - resolution: "@mswjs/interceptors@npm:0.41.9" +"@msw/url@npm:^0.1.2": + version: 0.1.2 + resolution: "@msw/url@npm:0.1.2" + checksum: 10c0/e9b367312dc9acce878b019ff6e8e67ab71fef1b0ccae9e7ef3b9562c681a26675329b70685aa50281d6fcc1bbc48402549424d5160a8f414be730af64448d06 + languageName: node + linkType: hard + +"@mswjs/interceptors@npm:^0.45.6": + version: 0.45.6 + resolution: "@mswjs/interceptors@npm:0.45.6" dependencies: - "@open-draft/deferred-promise": "npm:^2.2.0" - "@open-draft/logger": "npm:^0.3.0" - "@open-draft/until": "npm:^2.0.0" + "@open-draft/until": "npm:^3.0.1" + "@types/debug": "npm:^4.1.13" + debug: "npm:^4.4.3" is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" - strict-event-emitter: "npm:^0.5.1" - checksum: 10c0/2efff40877e07ce29846be76c2683177308a72a3ccfe4c096b496412a279acd92b5b9cdad40ad71c36b149a4a7d9e9b4e7d295893a8ba9661b43334f5784ecd8 + rettime: "npm:^0.11.12" + checksum: 10c0/4bc8a464d2a1ef94e99fbad16e9dcea93a2031e8847eef3709b57a50969bf1421c54b39c33416a619394fd2a01b90da06fe3adde9aa385e6ead85a24ad91d294 languageName: node linkType: hard @@ -3616,34 +3623,10 @@ __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/deferred-promise@npm:^3.0.0": - version: 3.0.0 - resolution: "@open-draft/deferred-promise@npm:3.0.0" - checksum: 10c0/4dd697e55495e436be9536413cc9975e792e9ca7472e81e3d3d69e9b65cb678465aac90b463ac02f2b490c0581c4e9aa8a33d2a5857decbe2c6d9ffb310f8e1f - 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": - version: 2.1.0 - resolution: "@open-draft/until@npm:2.1.0" - checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf +"@open-draft/until@npm:^3.0.1": + version: 3.0.1 + resolution: "@open-draft/until@npm:3.0.1" + checksum: 10c0/a89d1e5016241d7de5dee89840efbe9250d8102315a275c181aa68269c405e41a7f1724441fbe867af8a873c0f35c97c5f11917f480053a55924b80c2368bfb0 languageName: node linkType: hard @@ -3896,7 +3879,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/core@npm:^6.10.1": +"@rjsf/core@npm:^6.11.0": version: 6.11.0 resolution: "@rjsf/core@npm:6.11.0" dependencies: @@ -3908,7 +3891,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/react-bootstrap@npm:^6.10.1": +"@rjsf/react-bootstrap@npm:^6.11.0": version: 6.11.0 resolution: "@rjsf/react-bootstrap@npm:6.11.0" dependencies: @@ -3922,7 +3905,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/utils@npm:^6.10.1": +"@rjsf/utils@npm:^6.11.0": version: 6.11.0 resolution: "@rjsf/utils@npm:6.11.0" dependencies: @@ -3937,7 +3920,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/validator-ajv8@npm:^6.10.1": +"@rjsf/validator-ajv8@npm:^6.11.0": version: 6.11.0 resolution: "@rjsf/validator-ajv8@npm:6.11.0" dependencies: @@ -6349,6 +6332,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.1.13": + version: 4.1.13 + resolution: "@types/debug@npm:4.1.13" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/e5e124021bbdb23a82727eee0a726ae0fc8a3ae1f57253cbcc47497f259afb357de7f6941375e773e1abbfa1604c1555b901a409d762ec2bb4c1612131d4afb7 + languageName: node + linkType: hard + "@types/earcut@npm:^2.1.4": version: 2.1.4 resolution: "@types/earcut@npm:2.1.4" @@ -6441,6 +6433,13 @@ __metadata: languageName: node linkType: hard +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + "@types/node@npm:*": version: 26.6.3 resolution: "@types/node@npm:26.6.3" @@ -6505,13 +6504,6 @@ __metadata: languageName: node linkType: hard -"@types/statuses@npm:^2.0.6": - version: 2.0.6 - resolution: "@types/statuses@npm:2.0.6" - checksum: 10c0/dd88c220b0e2c6315686289525fd61472d2204d2e4bef4941acfb76bda01d3066f749ac74782aab5b537a45314fcd7d6261eefa40b6ec872691f5803adaa608d - languageName: node - linkType: hard - "@types/tough-cookie@npm:*": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" @@ -6987,7 +6979,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.1.0": +"ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": version: 6.2.3 resolution: "ansi-styles@npm:6.2.3" checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 @@ -7636,6 +7628,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^9.0.1": + version: 9.0.1 + resolution: "cliui@npm:9.0.1" + dependencies: + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/13441832e9efe7c7a76bd2b8e683555c478d461a9f249dc5db9b17fe8d4b47fa9277b503914b90bd00e4a151abb6b9b02b2288972ffe2e5e3ca40bcb1c2330d3 + languageName: node + linkType: hard + "clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" @@ -7750,10 +7753,10 @@ __metadata: languageName: node linkType: hard -"cookie@npm:^1.1.1": - version: 1.1.1 - resolution: "cookie@npm:1.1.1" - checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c +"cookie@npm:^2.0.1": + version: 2.0.1 + resolution: "cookie@npm:2.0.1" + checksum: 10c0/dcf3bd6ec9d0f4f32be7575e23242866cb53be55fc3d4cf36215638596661cec1327a3b4ca286ba15eba8f2abd443e422b8c5f708af4ea9f75b4d3dedc67ee4c languageName: node linkType: hard @@ -7926,10 +7929,10 @@ __metadata: "@dnd-kit/helpers": "npm:^0.5.0" "@dnd-kit/react": "npm:^0.5.0" "@eslint/js": "npm:^10.0.1" - "@rjsf/core": "npm:^6.10.1" - "@rjsf/react-bootstrap": "npm:^6.10.1" - "@rjsf/utils": "npm:^6.10.1" - "@rjsf/validator-ajv8": "npm:^6.10.1" + "@rjsf/core": "npm:^6.11.0" + "@rjsf/react-bootstrap": "npm:^6.11.0" + "@rjsf/utils": "npm:^6.11.0" + "@rjsf/validator-ajv8": "npm:^6.11.0" "@testing-library/dom": "npm:^10.4.2" "@testing-library/jest-dom": "npm:^7.0.1" "@testing-library/react": "npm:^16.3.3" @@ -7969,8 +7972,8 @@ __metadata: localforage: "npm:^1.10.0" lodash: "npm:^4.18.1" lodash-es: "npm:^4.18.1" - mapbox-gl: "npm:^3.31.0" - msw: "npm:2.15.0" + mapbox-gl: "npm:^3.32.0" + msw: "npm:3.0.1" pluralize: "npm:^8.0.0" proj4: "npm:^2.22.0" react: "npm:^19.3.0" @@ -7999,7 +8002,7 @@ __metadata: redux-thunk: "npm:^3.1.0" reselect: "npm:^5.3.0" resize-observer-polyfill: "npm:^1.5.1" - sass: "npm:^1.105.0" + sass: "npm:^1.105.1" socket.io-client: "npm:^4.8.4" socket.io-mock: "npm:^1.3.2" stylelint: "npm:^17.15.0" @@ -8282,6 +8285,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -9170,7 +9180,7 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.5.0": +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.5.0": version: 1.7.0 resolution: "get-east-asian-width@npm:1.7.0" checksum: 10c0/680a5f611a186e4a065291f31821f7e4ad7742625be814e05b8ab5535c6fa53562bc0333a83138fca5f2e1030ed960a561b2fd4629e0cff0dc68b19cf46d7c53 @@ -9374,13 +9384,6 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^16.13.2": - version: 16.14.2 - resolution: "graphql@npm:16.14.2" - checksum: 10c0/a95a96961eaff55cc9fe9d31fae6f33499ac988b972d07ea5085024cb1333f515b902f376e7393a5489aa82200a8aff3eb96580e4d1b69d702ed19b6eb1ce97a - languageName: node - linkType: hard - "has-bigints@npm:^1.0.2": version: 1.1.0 resolution: "has-bigints@npm:1.1.0" @@ -11161,7 +11164,7 @@ __metadata: languageName: node linkType: hard -"mapbox-gl@npm:^3.31.0": +"mapbox-gl@npm:^3.32.0": version: 3.32.0 resolution: "mapbox-gl@npm:3.32.0" checksum: 10c0/ad7bdc9d6c42b4febc3fb16b86e788f55f3fe552733b0a56723a8be41e27bde282da9235fafec7bd9f427cef2c5ae59c46954157bdc696d453c2ae4b65a5318e @@ -11352,36 +11355,36 @@ __metadata: languageName: node linkType: hard -"msw@npm:2.15.0": - version: 2.15.0 - resolution: "msw@npm:2.15.0" +"msw@npm:3.0.1": + version: 3.0.1 + resolution: "msw@npm:3.0.1" dependencies: "@inquirer/confirm": "npm:^6.0.11" - "@mswjs/interceptors": "npm:^0.41.3" - "@open-draft/deferred-promise": "npm:^3.0.0" - "@types/statuses": "npm:^2.0.6" - cookie: "npm:^1.1.1" - graphql: "npm:^16.13.2" + "@msw/url": "npm:^0.1.2" + "@mswjs/interceptors": "npm:^0.45.6" + cookie: "npm:^2.0.1" headers-polyfill: "npm:^5.0.1" is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" - path-to-regexp: "npm:^6.3.0" - picocolors: "npm:^1.1.1" - rettime: "npm:^0.11.11" - statuses: "npm:^2.0.2" - strict-event-emitter: "npm:^0.5.1" - tough-cookie: "npm:^6.0.1" + rettime: "npm:^0.11.12" + tough-cookie: "npm:^6.0.2" type-fest: "npm:^5.5.0" until-async: "npm:^3.0.2" - yargs: "npm:^17.7.2" + yargs: "npm:^18.2.0" peerDependencies: - typescript: ">= 4.8.x" + graphql: ">=16" + typescript: ">=5.9.x" + vite: ">=6" peerDependenciesMeta: + graphql: + optional: true typescript: optional: true + vite: + optional: true bin: msw: cli/index.js - checksum: 10c0/0d3dbf1b062a82b3711ff195e10ffd9e6f2a5e337b4a0f08cabdb8d0b96c5c0c057f1cdcb67da14c356612554ea463fb5ad8f9c618b5ac009fc4eee43a83c94e + checksum: 10c0/b23534e29a011ad3959ab07ed9cba7127241aa87322b4bfb3bde72e1c2aa2b919c167a89e76b7c9888540a534c917388d1c504ebe2aa86f4589cc3f545908d6a languageName: node linkType: hard @@ -11634,7 +11637,7 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.4.0, outvariant@npm:^1.4.3": +"outvariant@npm:^1.4.3": version: 1.4.3 resolution: "outvariant@npm:1.4.3" checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c @@ -11774,13 +11777,6 @@ __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" @@ -12650,7 +12646,7 @@ __metadata: languageName: node linkType: hard -"rettime@npm:^0.11.11": +"rettime@npm:^0.11.12": version: 0.11.12 resolution: "rettime@npm:0.11.12" checksum: 10c0/d785a4b82535ccff6a47e9418456140d3b9317914e4a5134c41ba2e07f0573397b76e299ab9f4bbb73227a19f370056c2d5df6c4703193a9c5741f24705bd952 @@ -12886,7 +12882,7 @@ __metadata: languageName: node linkType: hard -"sass@npm:^1.105.0": +"sass@npm:^1.105.1": version: 1.105.1 resolution: "sass@npm:1.105.1" dependencies: @@ -13207,13 +13203,6 @@ __metadata: languageName: node linkType: hard -"statuses@npm:^2.0.2": - version: 2.0.2 - resolution: "statuses@npm:2.0.2" - checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f - languageName: node - linkType: hard - "stop-iteration-iterator@npm:^1.1.0": version: 1.1.0 resolution: "stop-iteration-iterator@npm:1.1.0" @@ -13224,13 +13213,6 @@ __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-length@npm:^4.0.2": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -13272,7 +13254,18 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^8.2.2": +"string-width@npm:^7.0.0, string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string-width@npm:^8.2.1, string-width@npm:^8.2.2": version: 8.3.0 resolution: "string-width@npm:8.3.0" dependencies: @@ -13783,7 +13776,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^6.0.1": +"tough-cookie@npm:^6.0.2": version: 6.0.2 resolution: "tough-cookie@npm:6.0.2" dependencies: @@ -14638,6 +14631,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + "write-file-atomic@npm:^5.0.1": version: 5.0.1 resolution: "write-file-atomic@npm:5.0.1" @@ -14743,6 +14747,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^22.0.0": + version: 22.0.0 + resolution: "yargs-parser@npm:22.0.0" + checksum: 10c0/cb7ef81759c4271cb1d96b9351dbbc9a9ce35d3e1122d2b739bf6c432603824fa02c67cc12dcef6ea80283379d63495686e8f41cc7b06c6576e792aba4d33e1c + languageName: node + linkType: hard + "yargs@npm:^17.7.2": version: 17.7.3 resolution: "yargs@npm:17.7.3" @@ -14758,6 +14769,20 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^18.2.0": + version: 18.2.0 + resolution: "yargs@npm:18.2.0" + dependencies: + cliui: "npm:^9.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + string-width: "npm:^8.2.1" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^22.0.0" + checksum: 10c0/7419338c3c00f9143ec6b6ab168335061c6f5342d161642aec23ff119e28cac1c417e0eca1d7dc82fd084f07207cc5d34c7d8315f605c0e3dbf12c350e405f3f + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0"