@spokane-folio/security-incident 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +32 -0
- package/.github/workflows/CODEOWNERS +8 -0
- package/.github/workflows/pr-validation.yml +44 -0
- package/.github/workflows/release.yml +64 -0
- package/.prettierrc +6 -0
- package/.stripesclirc +4 -0
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +4 -0
- package/LICENSE +201 -0
- package/README.md +16 -0
- package/administrator-documentation/roles-and-permissions.md +65 -0
- package/administrator-documentation/track-settings-admin-guide-sketch.md +192 -0
- package/administrator-documentation/using-the-application.md +192 -0
- package/icons/app.png +0 -0
- package/icons/app.svg +1 -0
- package/icons/playButton.png +0 -0
- package/icons/profilePicThumbnail.png +0 -0
- package/jest.config.js +10 -0
- package/module-descriptor.json +75 -0
- package/output/service-worker.js +0 -0
- package/package.json +146 -0
- package/src/components/incidents/ColumnChooser.js +37 -0
- package/src/components/incidents/CreateMedia.js +132 -0
- package/src/components/incidents/CreatePane.js +1215 -0
- package/src/components/incidents/CreatePane.test.js +138 -0
- package/src/components/incidents/CreateReport.js +102 -0
- package/src/components/incidents/DetailsPane.js +1267 -0
- package/src/components/incidents/DetailsPane.test.js +150 -0
- package/src/components/incidents/EditPane.js +2334 -0
- package/src/components/incidents/EditPane.test.js +187 -0
- package/src/components/incidents/GetDetails.js +55 -0
- package/src/components/incidents/GetListDQLinkIncident.js +81 -0
- package/src/components/incidents/GetListDynamicQuery.js +66 -0
- package/src/components/incidents/GetLocations.js +57 -0
- package/src/components/incidents/GetMedia.js +98 -0
- package/src/components/incidents/GetName.js +111 -0
- package/src/components/incidents/GetNameCreatedBy.js +94 -0
- package/src/components/incidents/GetOrgLocaleSettings.js +61 -0
- package/src/components/incidents/GetPatronGroups.js +52 -0
- package/src/components/incidents/GetSelf.js +65 -0
- package/src/components/incidents/GetSummary.js +110 -0
- package/src/components/incidents/IncidentTypeCard.js +53 -0
- package/src/components/incidents/IncidentTypeCard.test.js +133 -0
- package/src/components/incidents/IncidentsPaneset.js +810 -0
- package/src/components/incidents/IncidentsPaneset.test.js +128 -0
- package/src/components/incidents/LinkedIncident.js +86 -0
- package/src/components/incidents/ModalAddMedia.js +262 -0
- package/src/components/incidents/ModalAddMedia.test.js +97 -0
- package/src/components/incidents/ModalAttentionDecOfService.js +111 -0
- package/src/components/incidents/ModalCustomWitness.js +469 -0
- package/src/components/incidents/ModalCustomWitness.test.js +147 -0
- package/src/components/incidents/ModalCustomerDetails.js +480 -0
- package/src/components/incidents/ModalCustomerDetails.test.js +116 -0
- package/src/components/incidents/ModalDescribeCustomer.js +361 -0
- package/src/components/incidents/ModalDescribeCustomer.test.js +156 -0
- package/src/components/incidents/ModalDirtyFormWarn.js +62 -0
- package/src/components/incidents/ModalLinkIncident.js +1213 -0
- package/src/components/incidents/ModalLinkIncidentStyle.css +32 -0
- package/src/components/incidents/ModalSelectIncidentTypes.js +178 -0
- package/src/components/incidents/ModalSelectIncidentTypes.test.js +273 -0
- package/src/components/incidents/ModalSelectKnownCustomer.js +395 -0
- package/src/components/incidents/ModalSelectWitness.js +406 -0
- package/src/components/incidents/ModalSelectWitness.test.js +308 -0
- package/src/components/incidents/ModalStyle.css +44 -0
- package/src/components/incidents/ModalTrespass.js +741 -0
- package/src/components/incidents/ModalViewCustomerDetails.js +241 -0
- package/src/components/incidents/ModalViewMedia.js +86 -0
- package/src/components/incidents/ModalViewTrespass.js +210 -0
- package/src/components/incidents/ResultsPane.js +437 -0
- package/src/components/incidents/ResultsPane.test.js +120 -0
- package/src/components/incidents/SearchCustomerOrWitness.js +108 -0
- package/src/components/incidents/Thumbnail.js +72 -0
- package/src/components/incidents/ThumbnailMarkRemoval.js +38 -0
- package/src/components/incidents/ThumbnailSkeleton.js +30 -0
- package/src/components/incidents/ThumbnailStyles.js +49 -0
- package/src/components/incidents/ThumbnailTempPreSave.js +71 -0
- package/src/components/incidents/UpdateReport.js +84 -0
- package/src/components/incidents/__snapshots__/CreatePane.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/DetailsPane.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/EditPane.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/IncidentTypeCard.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/IncidentsPaneset.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ModalAddMedia.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ModalCustomerDetails.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ModalSelectWitness.test.js.snap +3 -0
- package/src/components/incidents/__snapshots__/ResultsPane.test.js.snap +3 -0
- package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.css +5 -0
- package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.js +51 -0
- package/src/components/incidents/helpers/ProfilePicture/isAValidURL.js +3 -0
- package/src/components/incidents/helpers/ProfilePicture/useProfilePicture.js +127 -0
- package/src/components/incidents/helpers/buildQueryString.js +28 -0
- package/src/components/incidents/helpers/cleanFormValues.js +53 -0
- package/src/components/incidents/helpers/computeEditedCustomers.js +124 -0
- package/src/components/incidents/helpers/convertDateIgnoringTZ.js +8 -0
- package/src/components/incidents/helpers/convertUTCISOToLocalePrettyTime.js +15 -0
- package/src/components/incidents/helpers/convertUTCISOToPrettyDate.js +19 -0
- package/src/components/incidents/helpers/decodeParamsToForm.js +20 -0
- package/src/components/incidents/helpers/deepNormalizeForComparison.js +39 -0
- package/src/components/incidents/helpers/extractFilterString.js +12 -0
- package/src/components/incidents/helpers/formatDateAndTimeToUTCISO.js +14 -0
- package/src/components/incidents/helpers/formatDateToUTCISO.js +14 -0
- package/src/components/incidents/helpers/formatTimeToUTCISO.js +28 -0
- package/src/components/incidents/helpers/getCurrentTime.js +20 -0
- package/src/components/incidents/helpers/getTodayDate.js +12 -0
- package/src/components/incidents/helpers/handlebarsHelpers.js +148 -0
- package/src/components/incidents/helpers/hasFormChangedAtCreate.js +50 -0
- package/src/components/incidents/helpers/hasTopLevelChangeAffectedDeclaration.js +90 -0
- package/src/components/incidents/helpers/hasTopLevelFormChanged.js +111 -0
- package/src/components/incidents/helpers/identifyCurrentTrespassDocs.js +109 -0
- package/src/components/incidents/helpers/isSameHtml.js +13 -0
- package/src/components/incidents/helpers/isValidDateFormat.js +14 -0
- package/src/components/incidents/helpers/isValidTimeInput.js +11 -0
- package/src/components/incidents/helpers/isValidUTCTimeFormat.js +14 -0
- package/src/components/incidents/helpers/parseMMDDYYYY.js +7 -0
- package/src/components/incidents/helpers/parseQueryString.js +16 -0
- package/src/components/incidents/helpers/sortTrespassDocuments.js +44 -0
- package/src/components/incidents/helpers/stripHTML.js +11 -0
- package/src/components/incidents/helpers/trespassDocUtils.js +197 -0
- package/src/components/incidents/helpers/validateTrespassDetails.js +37 -0
- package/src/components/incidents/usePersistedColModalLink.js +70 -0
- package/src/components/incidents/usePersistedColumns.js +70 -0
- package/src/components/incidents/usePersistedSort.js +23 -0
- package/src/components/incidents/usePersistedSortModalLink.js +23 -0
- package/src/contexts/IncidentContext.js +433 -0
- package/src/index.js +61 -0
- package/src/routes/Application.js +13 -0
- package/src/settings/GetIncidentCategories.js +56 -0
- package/src/settings/GetIncidentTypesDetails.js +88 -0
- package/src/settings/GetIncidentTypesIds.js +74 -0
- package/src/settings/GetLocationsInService.js +54 -0
- package/src/settings/GetSingleCustomLocationDetails.js +60 -0
- package/src/settings/GetSingleIncidentTypeDetails.js +60 -0
- package/src/settings/GetTrespassReasons.js +67 -0
- package/src/settings/GetTrespassTemplates.js +51 -0
- package/src/settings/IncidentCategoriesPane.js +285 -0
- package/src/settings/IncidentCategoriesPane.test.js +229 -0
- package/src/settings/IncidentTypeDetailsPane.js +215 -0
- package/src/settings/IncidentTypeDetailsPane.test.js +220 -0
- package/src/settings/IncidentTypeEditPane.js +211 -0
- package/src/settings/IncidentTypeEditPane.test.js +170 -0
- package/src/settings/IncidentTypesPaneset.js +167 -0
- package/src/settings/IncidentTypesPaneset.test.js +124 -0
- package/src/settings/LocationInServiceEditPane.js +320 -0
- package/src/settings/LocationsPaneset.js +415 -0
- package/src/settings/LocationsPaneset.test.js +106 -0
- package/src/settings/ModalDeleteCategory.js +47 -0
- package/src/settings/ModalDeleteIncidentType.js +49 -0
- package/src/settings/ModalDeleteLocationInService.js +49 -0
- package/src/settings/ModalDeleteTrespassReason.js +49 -0
- package/src/settings/ModalPreviewTrespassDoc.js +65 -0
- package/src/settings/ModalTrespassDocTokens.js +83 -0
- package/src/settings/NewIncidentTypePane.js +182 -0
- package/src/settings/PutIncidentType.js +60 -0
- package/src/settings/PutLocationsInService.js +52 -0
- package/src/settings/PutTrespassReasons.js +61 -0
- package/src/settings/PutTrespassTemplate.js +50 -0
- package/src/settings/TrespassDoc.css +17 -0
- package/src/settings/TrespassDocDetailsPane.js +215 -0
- package/src/settings/TrespassDocEditPane.js +538 -0
- package/src/settings/TrespassDocPaneset.js +581 -0
- package/src/settings/TrespassReasonDetailsPane.js +171 -0
- package/src/settings/TrespassReasonEditPane.js +221 -0
- package/src/settings/TrespassReasonsPaneset.js +282 -0
- package/src/settings/__snapshots__/IncidentCategoriesPane.test.js.snap +3 -0
- package/src/settings/__snapshots__/IncidentTypeDetailsPane.test.js.snap +3 -0
- package/src/settings/__snapshots__/IncidentTypeEditPane.test.js.snap +3 -0
- package/src/settings/__snapshots__/IncidentTypesPaneset.test.js.snap +3 -0
- package/src/settings/__snapshots__/LocationsPaneset.test.js.snap +3 -0
- package/src/settings/data/exampleJSON.json +92 -0
- package/src/settings/data/templateTokens.js +396 -0
- package/src/settings/helpers/alphabetize.js +18 -0
- package/src/settings/helpers/getCategoryTitleById.js +13 -0
- package/src/settings/helpers/makeId.js +15 -0
- package/src/settings/index.js +48 -0
- package/stripes.config.js +10 -0
- package/test/jest/__mock__/index.js +8 -0
- package/test/jest/__mock__/intl.mock.js +27 -0
- package/test/jest/__mock__/stripes.mock.js +26 -0
- package/test/jest/__mock__/stripesComponents.mock.js +151 -0
- package/test/jest/__mock__/stripesConfig.mock.js +1 -0
- package/test/jest/__mock__/stripesCore.mock.js +9 -0
- package/test/jest/__mock__/stripesIcon.mock.js +5 -0
- package/test/jest/__mock__/stripesSmartComponents.mock.js +7 -0
- package/test/jest/__mock__/stripesUtils.mock.js +3 -0
- package/test/jest/eslintrc.js +12 -0
- package/test/jest/setupFiles.js +5 -0
- package/translations/ui-security-incident/en_US.json +542 -0
- package/ui-module-acceptance-criteria.md +34 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IncidentTypeEditPane.test.js
|
|
3
|
+
* Snapshot + smoke + basic behavior tests for the IncidentTypeEditPane.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { act } from 'react-dom/test-utils';
|
|
7
|
+
import { createRoot } from 'react-dom/client';
|
|
8
|
+
import IncidentTypeEditPane from './IncidentTypeEditPane';
|
|
9
|
+
|
|
10
|
+
/* ------------------------------------------------------------------ *
|
|
11
|
+
* 1) external-library mocks
|
|
12
|
+
* ------------------------------------------------------------------ */
|
|
13
|
+
jest.mock('react-intl', () => ({
|
|
14
|
+
useIntl : () => ({ formatMessage: ({ id }) => id }),
|
|
15
|
+
FormattedMessage : (p) => <span>{p.id}</span>,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('react-router-dom', () => ({
|
|
19
|
+
useParams: () => ({ id: 't1' }),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('@folio/stripes/components', () => {
|
|
23
|
+
const React = require('react');
|
|
24
|
+
const mk = (tag) => (p) => React.createElement(tag, p, p.children);
|
|
25
|
+
|
|
26
|
+
// IMPORTANT: render footer (and renderHeader) so buttons appear in DOM
|
|
27
|
+
const Pane = (p) => (
|
|
28
|
+
<div>
|
|
29
|
+
{typeof p.renderHeader === 'function' ? p.renderHeader({}) : p.renderHeader}
|
|
30
|
+
{p.children}
|
|
31
|
+
{p.footer}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// PaneFooter needs to render its start/end
|
|
36
|
+
const PaneFooter = (p) => <div>{p.renderStart}{p.renderEnd}</div>;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
Accordion : mk('div'),
|
|
40
|
+
AccordionSet: mk('div'),
|
|
41
|
+
Button : (p) => <button {...p}>{p.children}</button>,
|
|
42
|
+
Col : mk('div'),
|
|
43
|
+
Pane,
|
|
44
|
+
PaneHeader : mk('div'),
|
|
45
|
+
PaneFooter,
|
|
46
|
+
Row : mk('div'),
|
|
47
|
+
Select : (p) => <select {...p}>{p.children}</select>,
|
|
48
|
+
TextArea : (p) => <textarea {...p}>{p.children}</textarea>,
|
|
49
|
+
TextField : (p) => <input {...p} />,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/* ------------------------------------------------------------------ *
|
|
54
|
+
* 2) child-component mocks
|
|
55
|
+
* ------------------------------------------------------------------ */
|
|
56
|
+
jest.mock('./GetSingleIncidentTypeDetails', () => {
|
|
57
|
+
const React = require('react');
|
|
58
|
+
return (props) => {
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
props.handleFetchedDetails?.({
|
|
61
|
+
id: 't1',
|
|
62
|
+
title: 'Type 1 - Disorderly',
|
|
63
|
+
category_id: 'cat-1',
|
|
64
|
+
description: 'Initial description',
|
|
65
|
+
});
|
|
66
|
+
}, []);
|
|
67
|
+
return <div>Mock GetSingleIncidentTypeDetails</div>;
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
jest.mock('./GetIncidentTypesDetails', () => {
|
|
72
|
+
const React = require('react');
|
|
73
|
+
return (props) => {
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
props.handleIncidentTypes?.([
|
|
76
|
+
{ id: 't1', title: 'Type 1 - Disorderly', category_id: 'cat-1', description: 'Initial description' },
|
|
77
|
+
{ id: 't2', title: 'Type 2 - Theft', category_id: 'cat-2', description: 'Other' },
|
|
78
|
+
]);
|
|
79
|
+
}, []);
|
|
80
|
+
return <div>Mock GetIncidentTypesDetails</div>;
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
jest.mock('./GetIncidentCategories', () => () => (
|
|
85
|
+
<div>Mock GetIncidentCategories</div>
|
|
86
|
+
));
|
|
87
|
+
|
|
88
|
+
jest.mock('./PutIncidentType', () => (p) => (
|
|
89
|
+
<div>Mock PutIncidentType (context={p?.context})</div>
|
|
90
|
+
));
|
|
91
|
+
|
|
92
|
+
/* ------------------------------------------------------------------ *
|
|
93
|
+
* 3) IncidentContext mock
|
|
94
|
+
* ------------------------------------------------------------------ */
|
|
95
|
+
jest.mock('../contexts/IncidentContext', () => ({
|
|
96
|
+
useIncidents: () => ({
|
|
97
|
+
incidentCategories: [
|
|
98
|
+
{ id: 'cat-1', title: 'Behavior' },
|
|
99
|
+
{ id: 'cat-2', title: 'Property' },
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
/* ------------------------------------------------------------------ *
|
|
105
|
+
* 4) DOM setup / teardown
|
|
106
|
+
* ------------------------------------------------------------------ */
|
|
107
|
+
let container, root;
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
container = document.createElement('div');
|
|
110
|
+
document.body.appendChild(container);
|
|
111
|
+
root = createRoot(container);
|
|
112
|
+
});
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
root.unmount();
|
|
115
|
+
document.body.removeChild(container);
|
|
116
|
+
container = null;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/* ------------------------------------------------------------------ *
|
|
120
|
+
* 5) helpers
|
|
121
|
+
* ------------------------------------------------------------------ */
|
|
122
|
+
const findButtonByText = (rootEl, text) => {
|
|
123
|
+
const btns = Array.from(rootEl.querySelectorAll('button'));
|
|
124
|
+
return btns.find((b) => (b.textContent || '').includes(text));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/* ------------------------------------------------------------------ *
|
|
128
|
+
* 6) tests
|
|
129
|
+
* ------------------------------------------------------------------ */
|
|
130
|
+
it('renders without crashing (snapshot)', async () => {
|
|
131
|
+
await act(async () => {
|
|
132
|
+
root.render(<IncidentTypeEditPane handleCloseEdit={jest.fn()} />);
|
|
133
|
+
});
|
|
134
|
+
// allow effects (details/types) to flush
|
|
135
|
+
await act(async () => {});
|
|
136
|
+
expect(container.innerHTML).toMatchSnapshot();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('mounts key child components', async () => {
|
|
140
|
+
await act(async () => {
|
|
141
|
+
root.render(<IncidentTypeEditPane handleCloseEdit={jest.fn()} />);
|
|
142
|
+
});
|
|
143
|
+
await act(async () => {});
|
|
144
|
+
const txt = container.textContent;
|
|
145
|
+
expect(txt).toContain('Mock GetSingleIncidentTypeDetails');
|
|
146
|
+
expect(txt).toContain('Mock GetIncidentCategories');
|
|
147
|
+
expect(txt).toContain('Mock GetIncidentTypesDetails');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('enables the Save button once form data is present', async () => {
|
|
151
|
+
await act(async () => {
|
|
152
|
+
root.render(<IncidentTypeEditPane handleCloseEdit={jest.fn()} />);
|
|
153
|
+
});
|
|
154
|
+
await act(async () => {}); // flush state updates from useEffect
|
|
155
|
+
const saveBtn = findButtonByText(container, 'save-and-close-button');
|
|
156
|
+
expect(saveBtn).toBeTruthy();
|
|
157
|
+
expect(saveBtn.hasAttribute('disabled')).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('submits and renders PutIncidentType when Save is clicked', async () => {
|
|
161
|
+
await act(async () => {
|
|
162
|
+
root.render(<IncidentTypeEditPane handleCloseEdit={jest.fn()} />);
|
|
163
|
+
});
|
|
164
|
+
await act(async () => {}); // flush initial effects
|
|
165
|
+
const saveBtn = findButtonByText(container, 'save-and-close-button');
|
|
166
|
+
await act(async () => {
|
|
167
|
+
saveBtn.click();
|
|
168
|
+
});
|
|
169
|
+
expect(container.textContent).toContain('Mock PutIncidentType');
|
|
170
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { FormattedMessage } from 'react-intl';
|
|
3
|
+
import { useHistory, Switch, Route } from 'react-router-dom';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Col,
|
|
7
|
+
NavList,
|
|
8
|
+
NavListItem,
|
|
9
|
+
NavListSection,
|
|
10
|
+
Pane,
|
|
11
|
+
PaneHeader,
|
|
12
|
+
Paneset,
|
|
13
|
+
Row,
|
|
14
|
+
} from '@folio/stripes/components';
|
|
15
|
+
// import GetIncidentTypesIds from './GetIncidentTypesIds';
|
|
16
|
+
import GetIncidentTypesDetails from './GetIncidentTypesDetails';
|
|
17
|
+
import IncidentTypeDetailsPane from './IncidentTypeDetailsPane';
|
|
18
|
+
import IncidentTypeEditPane from './IncidentTypeEditPane';
|
|
19
|
+
import NewIncidentTypePane from './NewIncidentTypePane';
|
|
20
|
+
import { useIncidents } from '../contexts/IncidentContext';
|
|
21
|
+
|
|
22
|
+
const IncidentTypesPaneset = () => {
|
|
23
|
+
const { incidentTypesNamesIdsList } = useIncidents();
|
|
24
|
+
const history = useHistory();
|
|
25
|
+
const [typesList, setTypesList] = useState([]);
|
|
26
|
+
|
|
27
|
+
const sortTypesList = (responseTypesList) => {
|
|
28
|
+
const sorted = responseTypesList.sort((a, b) => {
|
|
29
|
+
// parse 'title' of object into numeric parts
|
|
30
|
+
const parseTitle = (title) => {
|
|
31
|
+
// split by any non-numeric character, filter boolean for empty str, map number ensure each part into Number
|
|
32
|
+
return title.split(/[^\d]+/).filter(Boolean).map(Number);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const aParts = parseTitle(a.title);
|
|
36
|
+
const bParts = parseTitle(b.title);
|
|
37
|
+
// itereate parts of both titles
|
|
38
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
39
|
+
const aValue = aParts[i] || 0; //if fewer parts the treated as 0
|
|
40
|
+
const bValue = bParts[i] || 0;
|
|
41
|
+
// if different
|
|
42
|
+
if (aValue !== bValue) {
|
|
43
|
+
// determine sort order, if a is less than b, negative value is returned, and a comes before b
|
|
44
|
+
// if a is greater than b, func returns positive value and a comes after b
|
|
45
|
+
return aValue - bValue;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
// if all parts equal or no difference found, return 0, no change in order
|
|
49
|
+
return 0;
|
|
50
|
+
});
|
|
51
|
+
return sorted;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// const handleGetTypesList = (list) => {
|
|
55
|
+
// const sortedList = sortTypesList(list);
|
|
56
|
+
// setTypesList(sortedList);
|
|
57
|
+
// };
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const sortedList = sortTypesList(incidentTypesNamesIdsList);
|
|
61
|
+
setTypesList(sortedList);
|
|
62
|
+
}, [incidentTypesNamesIdsList])
|
|
63
|
+
|
|
64
|
+
const handleShowDetails = (id) => {
|
|
65
|
+
history.push(`/settings/incidents/types/${id}`);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleCloseDetails = () => {
|
|
69
|
+
history.push(`/settings/incidents/types`);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleShowEdit = (id) => {
|
|
73
|
+
history.push(`/settings/incidents/types/${id}/edit`);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleCloseEdit = () => {
|
|
77
|
+
history.push(`/settings/incidents/types`);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleNew = () => {
|
|
81
|
+
history.push(`/settings/incidents/types/new`);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleCloseNew = () => {
|
|
85
|
+
history.push(`/settings/incidents/types`);
|
|
86
|
+
};
|
|
87
|
+
// <FormattedMessage id="settings.incident-types-new-button"/>
|
|
88
|
+
return (
|
|
89
|
+
<Paneset>
|
|
90
|
+
<Pane
|
|
91
|
+
paneTitle={<FormattedMessage id="settings.incident-types.paneTitle" />}
|
|
92
|
+
defaultWidth="fill"
|
|
93
|
+
renderHeader={(renderProps) => <PaneHeader {...renderProps} />}
|
|
94
|
+
>
|
|
95
|
+
<Row>
|
|
96
|
+
<Col xs={10}>
|
|
97
|
+
<Button buttonStyle="primary" onClick={handleNew}>
|
|
98
|
+
<FormattedMessage id="settings.incident-types-new-button" />
|
|
99
|
+
</Button>
|
|
100
|
+
</Col>
|
|
101
|
+
</Row>
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
typesList.length > 0 ? (
|
|
105
|
+
<NavList>
|
|
106
|
+
<NavListSection>
|
|
107
|
+
{typesList.map((type, index) => (
|
|
108
|
+
<NavListItem
|
|
109
|
+
key={index}
|
|
110
|
+
onClick={() => handleShowDetails(type.id)}
|
|
111
|
+
>
|
|
112
|
+
{type.title}
|
|
113
|
+
</NavListItem>
|
|
114
|
+
))}
|
|
115
|
+
</NavListSection>
|
|
116
|
+
</NavList>
|
|
117
|
+
) : (
|
|
118
|
+
<p>
|
|
119
|
+
<FormattedMessage
|
|
120
|
+
id="settings.incident-types-default-if-no-list"/>
|
|
121
|
+
</p>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
</Pane>
|
|
126
|
+
{/* <GetIncidentTypesIds
|
|
127
|
+
contextTypeProp="incident-types-paneset"
|
|
128
|
+
handleGetTypesList={handleGetTypesList}
|
|
129
|
+
/> */}
|
|
130
|
+
<GetIncidentTypesDetails
|
|
131
|
+
context='settings'
|
|
132
|
+
/>
|
|
133
|
+
<Switch>
|
|
134
|
+
<Route
|
|
135
|
+
exact
|
|
136
|
+
path="/settings/incidents/types/new"
|
|
137
|
+
render={(props) => (
|
|
138
|
+
<NewIncidentTypePane {...props} handleCloseNew={handleCloseNew} />
|
|
139
|
+
)}
|
|
140
|
+
/>
|
|
141
|
+
<Route
|
|
142
|
+
exact
|
|
143
|
+
path="/settings/incidents/types/:id"
|
|
144
|
+
render={(props) => (
|
|
145
|
+
<IncidentTypeDetailsPane
|
|
146
|
+
{...props}
|
|
147
|
+
handleCloseDetails={handleCloseDetails}
|
|
148
|
+
handleShowEdit={handleShowEdit}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
/>
|
|
152
|
+
<Route
|
|
153
|
+
exact
|
|
154
|
+
path="/settings/incidents/types/:id/edit"
|
|
155
|
+
render={(props) => (
|
|
156
|
+
<IncidentTypeEditPane
|
|
157
|
+
{...props}
|
|
158
|
+
handleCloseEdit={handleCloseEdit}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
/>
|
|
162
|
+
</Switch>
|
|
163
|
+
</Paneset>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default IncidentTypesPaneset;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IncidentTypesPaneset.test.js
|
|
3
|
+
* Snapshot + smoke tests for the IncidentTypesPaneset settings container.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { act } from 'react-dom/test-utils';
|
|
7
|
+
import { createRoot } from 'react-dom/client';
|
|
8
|
+
import IncidentTypesPaneset from './IncidentTypesPaneset';
|
|
9
|
+
|
|
10
|
+
/* ------------------------------------------------------------------ *
|
|
11
|
+
* 1. external-library mocks
|
|
12
|
+
* ------------------------------------------------------------------ */
|
|
13
|
+
jest.mock('react-intl', () => ({
|
|
14
|
+
useIntl : () => ({ formatMessage: ({ id }) => id }),
|
|
15
|
+
FormattedMessage : (p) => <span>{p.id}</span>,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('react-router-dom', () => ({
|
|
19
|
+
useHistory : () => ({ push: jest.fn(), replace: jest.fn() }),
|
|
20
|
+
useLocation: () => ({ pathname: '/settings/incidents/types', search: '' }),
|
|
21
|
+
// keep these inert for stable snapshots
|
|
22
|
+
Switch : (p) => <div>{p.children}</div>,
|
|
23
|
+
Route : () => null,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
jest.mock('@folio/stripes/components', () => {
|
|
27
|
+
const React = require('react');
|
|
28
|
+
const mk = (tag) => (p) => React.createElement(tag, p, p.children);
|
|
29
|
+
|
|
30
|
+
// Render NavList as simple UL/LI structure so items are visible in textContent
|
|
31
|
+
const NavList = (p) => <ul {...p}>{p.children}</ul>;
|
|
32
|
+
const NavListSection = (p) => <li {...p}>{p.children}</li>;
|
|
33
|
+
const NavListItem = (p) => <li onClick={p.onClick}>{p.children}</li>;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
Button : mk('button'),
|
|
37
|
+
Col : mk('div'),
|
|
38
|
+
Pane : mk('div'),
|
|
39
|
+
PaneHeader : mk('div'),
|
|
40
|
+
Paneset : mk('div'),
|
|
41
|
+
Row : mk('div'),
|
|
42
|
+
NavList,
|
|
43
|
+
NavListItem,
|
|
44
|
+
NavListSection,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/* ------------------------------------------------------------------ *
|
|
49
|
+
* 2. child-component mocks
|
|
50
|
+
* ------------------------------------------------------------------ */
|
|
51
|
+
jest
|
|
52
|
+
.mock('./GetIncidentTypesDetails', () => () => <div>Mock GetIncidentTypesDetails</div>)
|
|
53
|
+
.mock('./IncidentTypeDetailsPane', () => () => <div>Mock IncidentTypeDetailsPane</div>)
|
|
54
|
+
.mock('./IncidentTypeEditPane', () => () => <div>Mock IncidentTypeEditPane</div>)
|
|
55
|
+
.mock('./NewIncidentTypePane', () => () => <div>Mock NewIncidentTypePane</div>);
|
|
56
|
+
|
|
57
|
+
/* ------------------------------------------------------------------ *
|
|
58
|
+
* 3. IncidentContext mock
|
|
59
|
+
* ------------------------------------------------------------------ */
|
|
60
|
+
jest.mock('../contexts/IncidentContext', () => {
|
|
61
|
+
// Unsorted input list exercises the numeric-part sorter in the component
|
|
62
|
+
const incidentTypesNamesIdsList = [
|
|
63
|
+
{ id: 't10', title: 'Type 10 - Assault' },
|
|
64
|
+
{ id: 't2', title: 'Type 2 - Theft' },
|
|
65
|
+
{ id: 't1', title: 'Type 1 - Disorderly' },
|
|
66
|
+
{ id: 't21', title: 'Type 2.1 - Minor' },
|
|
67
|
+
];
|
|
68
|
+
return {
|
|
69
|
+
useIncidents: () => ({
|
|
70
|
+
incidentTypesNamesIdsList,
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/* ------------------------------------------------------------------ *
|
|
76
|
+
* 4. DOM setup / teardown
|
|
77
|
+
* ------------------------------------------------------------------ */
|
|
78
|
+
let container, root;
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
container = document.createElement('div');
|
|
81
|
+
document.body.appendChild(container);
|
|
82
|
+
root = createRoot(container);
|
|
83
|
+
});
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
root.unmount();
|
|
86
|
+
document.body.removeChild(container);
|
|
87
|
+
container = null;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/* ------------------------------------------------------------------ *
|
|
91
|
+
* 5. tests
|
|
92
|
+
* ------------------------------------------------------------------ */
|
|
93
|
+
it('renders without crashing (snapshot)', async () => {
|
|
94
|
+
await act(async () => { root.render(<IncidentTypesPaneset />); });
|
|
95
|
+
expect(container.innerHTML).toMatchSnapshot();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('mounts key child components', async () => {
|
|
99
|
+
await act(async () => { root.render(<IncidentTypesPaneset />); });
|
|
100
|
+
const txt = container.textContent;
|
|
101
|
+
expect(txt).toContain('Mock GetIncidentTypesDetails');
|
|
102
|
+
|
|
103
|
+
// list or fallback text should render
|
|
104
|
+
expect(
|
|
105
|
+
txt.includes('Type 1 - Disorderly') ||
|
|
106
|
+
txt.includes('settings.incident-types-default-if-no-list')
|
|
107
|
+
).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('sorts the incident type list by numeric parts in the title', async () => {
|
|
111
|
+
await act(async () => { root.render(<IncidentTypesPaneset />); });
|
|
112
|
+
const txt = container.textContent;
|
|
113
|
+
|
|
114
|
+
// ensure 1 < 2 < 2.1 < 10 by index positions in the rendered text
|
|
115
|
+
const i1 = txt.indexOf('Type 1 - Disorderly');
|
|
116
|
+
const i2 = txt.indexOf('Type 2 - Theft');
|
|
117
|
+
const i21 = txt.indexOf('Type 2.1 - Minor');
|
|
118
|
+
const i10 = txt.indexOf('Type 10 - Assault');
|
|
119
|
+
|
|
120
|
+
expect(i1).toBeGreaterThanOrEqual(0);
|
|
121
|
+
expect(i2).toBeGreaterThan(i1);
|
|
122
|
+
expect(i21).toBeGreaterThan(i2);
|
|
123
|
+
expect(i10).toBeGreaterThan(i21);
|
|
124
|
+
});
|