Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
22 changes: 13 additions & 9 deletions jest-config/transformers/transformImportMetaEnv.js
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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([]));
}
},
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -134,7 +134,7 @@
"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|json)$)": "<rootDir>/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",
Expand Down
2 changes: 1 addition & 1 deletion src/AddToPatrolModal/AddToPatrolModal.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/DetailViewComponents/ActivitySection/index.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
4 changes: 3 additions & 1 deletion src/Nav/index.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 });
Expand Down
2 changes: 1 addition & 1 deletion src/NotificationMenu/index.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/PatrolDetailView/index.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/ReportFormSummary/index.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/SideBar/EventsManager/EventOverview/index.test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const TRUCK = {

const TEAM_AND_TRACKING_OPTIONS = {
assets: [TRUCK],
leaders: [DOG, PILOT, RANGER],
members: [DOG, PILOT, RANGER],
teams: [],
};
Expand Down
2 changes: 1 addition & 1 deletion src/SideBar/PatrolsManager/LegForm/StaticFields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})}
Expand Down
17 changes: 10 additions & 7 deletions src/SideBar/PatrolsManager/LegForm/StaticFields/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' }],
},
},
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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],
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/SideBar/PatrolsManager/LegForm/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { isFuture } from 'date-fns';
import uniq from 'lodash/uniq';

import parseLegDraftDateTime from '../parseLegDraftDateTime';

Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ describe('SideBar - PatrolsManager - LegManager - EditLeg', () => {
patrolStore: { [patrol.id]: patrol },
patrolTeamAndTrackingOptions: {
assets: [asset],
leaders: [teamLead],
members: [teamLead, teamMember, unassignedMember],
teams: [team],
},
Expand Down
1 change: 0 additions & 1 deletion src/SideBar/PatrolsManager/LegManager/NewLeg/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ describe('SideBar - PatrolsManager - LegManager - NewLeg', () => {
patrolStore: { [patrol.id]: patrol },
patrolTeamAndTrackingOptions: {
assets: [asset],
leaders: [teamLead],
members: [teamLead, teamMember],
teams: [team],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion src/SideBar/PatrolsManager/LegManager/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/SideBar/PatrolsManager/NewPatrol/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand Down
12 changes: 9 additions & 3 deletions src/SideBar/PatrolsManager/PatrolOverview/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
});

Expand Down Expand Up @@ -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);
});
Expand All @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 }));

Expand Down Expand Up @@ -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}
/>
Expand Down
Loading
Loading