@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.
Files changed (188) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/workflows/CODEOWNERS +8 -0
  3. package/.github/workflows/pr-validation.yml +44 -0
  4. package/.github/workflows/release.yml +64 -0
  5. package/.prettierrc +6 -0
  6. package/.stripesclirc +4 -0
  7. package/CHANGELOG.md +8 -0
  8. package/CONTRIBUTING.md +4 -0
  9. package/LICENSE +201 -0
  10. package/README.md +16 -0
  11. package/administrator-documentation/roles-and-permissions.md +65 -0
  12. package/administrator-documentation/track-settings-admin-guide-sketch.md +192 -0
  13. package/administrator-documentation/using-the-application.md +192 -0
  14. package/icons/app.png +0 -0
  15. package/icons/app.svg +1 -0
  16. package/icons/playButton.png +0 -0
  17. package/icons/profilePicThumbnail.png +0 -0
  18. package/jest.config.js +10 -0
  19. package/module-descriptor.json +75 -0
  20. package/output/service-worker.js +0 -0
  21. package/package.json +146 -0
  22. package/src/components/incidents/ColumnChooser.js +37 -0
  23. package/src/components/incidents/CreateMedia.js +132 -0
  24. package/src/components/incidents/CreatePane.js +1215 -0
  25. package/src/components/incidents/CreatePane.test.js +138 -0
  26. package/src/components/incidents/CreateReport.js +102 -0
  27. package/src/components/incidents/DetailsPane.js +1267 -0
  28. package/src/components/incidents/DetailsPane.test.js +150 -0
  29. package/src/components/incidents/EditPane.js +2334 -0
  30. package/src/components/incidents/EditPane.test.js +187 -0
  31. package/src/components/incidents/GetDetails.js +55 -0
  32. package/src/components/incidents/GetListDQLinkIncident.js +81 -0
  33. package/src/components/incidents/GetListDynamicQuery.js +66 -0
  34. package/src/components/incidents/GetLocations.js +57 -0
  35. package/src/components/incidents/GetMedia.js +98 -0
  36. package/src/components/incidents/GetName.js +111 -0
  37. package/src/components/incidents/GetNameCreatedBy.js +94 -0
  38. package/src/components/incidents/GetOrgLocaleSettings.js +61 -0
  39. package/src/components/incidents/GetPatronGroups.js +52 -0
  40. package/src/components/incidents/GetSelf.js +65 -0
  41. package/src/components/incidents/GetSummary.js +110 -0
  42. package/src/components/incidents/IncidentTypeCard.js +53 -0
  43. package/src/components/incidents/IncidentTypeCard.test.js +133 -0
  44. package/src/components/incidents/IncidentsPaneset.js +810 -0
  45. package/src/components/incidents/IncidentsPaneset.test.js +128 -0
  46. package/src/components/incidents/LinkedIncident.js +86 -0
  47. package/src/components/incidents/ModalAddMedia.js +262 -0
  48. package/src/components/incidents/ModalAddMedia.test.js +97 -0
  49. package/src/components/incidents/ModalAttentionDecOfService.js +111 -0
  50. package/src/components/incidents/ModalCustomWitness.js +469 -0
  51. package/src/components/incidents/ModalCustomWitness.test.js +147 -0
  52. package/src/components/incidents/ModalCustomerDetails.js +480 -0
  53. package/src/components/incidents/ModalCustomerDetails.test.js +116 -0
  54. package/src/components/incidents/ModalDescribeCustomer.js +361 -0
  55. package/src/components/incidents/ModalDescribeCustomer.test.js +156 -0
  56. package/src/components/incidents/ModalDirtyFormWarn.js +62 -0
  57. package/src/components/incidents/ModalLinkIncident.js +1213 -0
  58. package/src/components/incidents/ModalLinkIncidentStyle.css +32 -0
  59. package/src/components/incidents/ModalSelectIncidentTypes.js +178 -0
  60. package/src/components/incidents/ModalSelectIncidentTypes.test.js +273 -0
  61. package/src/components/incidents/ModalSelectKnownCustomer.js +395 -0
  62. package/src/components/incidents/ModalSelectWitness.js +406 -0
  63. package/src/components/incidents/ModalSelectWitness.test.js +308 -0
  64. package/src/components/incidents/ModalStyle.css +44 -0
  65. package/src/components/incidents/ModalTrespass.js +741 -0
  66. package/src/components/incidents/ModalViewCustomerDetails.js +241 -0
  67. package/src/components/incidents/ModalViewMedia.js +86 -0
  68. package/src/components/incidents/ModalViewTrespass.js +210 -0
  69. package/src/components/incidents/ResultsPane.js +437 -0
  70. package/src/components/incidents/ResultsPane.test.js +120 -0
  71. package/src/components/incidents/SearchCustomerOrWitness.js +108 -0
  72. package/src/components/incidents/Thumbnail.js +72 -0
  73. package/src/components/incidents/ThumbnailMarkRemoval.js +38 -0
  74. package/src/components/incidents/ThumbnailSkeleton.js +30 -0
  75. package/src/components/incidents/ThumbnailStyles.js +49 -0
  76. package/src/components/incidents/ThumbnailTempPreSave.js +71 -0
  77. package/src/components/incidents/UpdateReport.js +84 -0
  78. package/src/components/incidents/__snapshots__/CreatePane.test.js.snap +3 -0
  79. package/src/components/incidents/__snapshots__/DetailsPane.test.js.snap +3 -0
  80. package/src/components/incidents/__snapshots__/EditPane.test.js.snap +3 -0
  81. package/src/components/incidents/__snapshots__/IncidentTypeCard.test.js.snap +3 -0
  82. package/src/components/incidents/__snapshots__/IncidentsPaneset.test.js.snap +3 -0
  83. package/src/components/incidents/__snapshots__/ModalAddMedia.test.js.snap +3 -0
  84. package/src/components/incidents/__snapshots__/ModalCustomerDetails.test.js.snap +3 -0
  85. package/src/components/incidents/__snapshots__/ModalSelectWitness.test.js.snap +3 -0
  86. package/src/components/incidents/__snapshots__/ResultsPane.test.js.snap +3 -0
  87. package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.css +5 -0
  88. package/src/components/incidents/helpers/ProfilePicture/ProfilePicture.js +51 -0
  89. package/src/components/incidents/helpers/ProfilePicture/isAValidURL.js +3 -0
  90. package/src/components/incidents/helpers/ProfilePicture/useProfilePicture.js +127 -0
  91. package/src/components/incidents/helpers/buildQueryString.js +28 -0
  92. package/src/components/incidents/helpers/cleanFormValues.js +53 -0
  93. package/src/components/incidents/helpers/computeEditedCustomers.js +124 -0
  94. package/src/components/incidents/helpers/convertDateIgnoringTZ.js +8 -0
  95. package/src/components/incidents/helpers/convertUTCISOToLocalePrettyTime.js +15 -0
  96. package/src/components/incidents/helpers/convertUTCISOToPrettyDate.js +19 -0
  97. package/src/components/incidents/helpers/decodeParamsToForm.js +20 -0
  98. package/src/components/incidents/helpers/deepNormalizeForComparison.js +39 -0
  99. package/src/components/incidents/helpers/extractFilterString.js +12 -0
  100. package/src/components/incidents/helpers/formatDateAndTimeToUTCISO.js +14 -0
  101. package/src/components/incidents/helpers/formatDateToUTCISO.js +14 -0
  102. package/src/components/incidents/helpers/formatTimeToUTCISO.js +28 -0
  103. package/src/components/incidents/helpers/getCurrentTime.js +20 -0
  104. package/src/components/incidents/helpers/getTodayDate.js +12 -0
  105. package/src/components/incidents/helpers/handlebarsHelpers.js +148 -0
  106. package/src/components/incidents/helpers/hasFormChangedAtCreate.js +50 -0
  107. package/src/components/incidents/helpers/hasTopLevelChangeAffectedDeclaration.js +90 -0
  108. package/src/components/incidents/helpers/hasTopLevelFormChanged.js +111 -0
  109. package/src/components/incidents/helpers/identifyCurrentTrespassDocs.js +109 -0
  110. package/src/components/incidents/helpers/isSameHtml.js +13 -0
  111. package/src/components/incidents/helpers/isValidDateFormat.js +14 -0
  112. package/src/components/incidents/helpers/isValidTimeInput.js +11 -0
  113. package/src/components/incidents/helpers/isValidUTCTimeFormat.js +14 -0
  114. package/src/components/incidents/helpers/parseMMDDYYYY.js +7 -0
  115. package/src/components/incidents/helpers/parseQueryString.js +16 -0
  116. package/src/components/incidents/helpers/sortTrespassDocuments.js +44 -0
  117. package/src/components/incidents/helpers/stripHTML.js +11 -0
  118. package/src/components/incidents/helpers/trespassDocUtils.js +197 -0
  119. package/src/components/incidents/helpers/validateTrespassDetails.js +37 -0
  120. package/src/components/incidents/usePersistedColModalLink.js +70 -0
  121. package/src/components/incidents/usePersistedColumns.js +70 -0
  122. package/src/components/incidents/usePersistedSort.js +23 -0
  123. package/src/components/incidents/usePersistedSortModalLink.js +23 -0
  124. package/src/contexts/IncidentContext.js +433 -0
  125. package/src/index.js +61 -0
  126. package/src/routes/Application.js +13 -0
  127. package/src/settings/GetIncidentCategories.js +56 -0
  128. package/src/settings/GetIncidentTypesDetails.js +88 -0
  129. package/src/settings/GetIncidentTypesIds.js +74 -0
  130. package/src/settings/GetLocationsInService.js +54 -0
  131. package/src/settings/GetSingleCustomLocationDetails.js +60 -0
  132. package/src/settings/GetSingleIncidentTypeDetails.js +60 -0
  133. package/src/settings/GetTrespassReasons.js +67 -0
  134. package/src/settings/GetTrespassTemplates.js +51 -0
  135. package/src/settings/IncidentCategoriesPane.js +285 -0
  136. package/src/settings/IncidentCategoriesPane.test.js +229 -0
  137. package/src/settings/IncidentTypeDetailsPane.js +215 -0
  138. package/src/settings/IncidentTypeDetailsPane.test.js +220 -0
  139. package/src/settings/IncidentTypeEditPane.js +211 -0
  140. package/src/settings/IncidentTypeEditPane.test.js +170 -0
  141. package/src/settings/IncidentTypesPaneset.js +167 -0
  142. package/src/settings/IncidentTypesPaneset.test.js +124 -0
  143. package/src/settings/LocationInServiceEditPane.js +320 -0
  144. package/src/settings/LocationsPaneset.js +415 -0
  145. package/src/settings/LocationsPaneset.test.js +106 -0
  146. package/src/settings/ModalDeleteCategory.js +47 -0
  147. package/src/settings/ModalDeleteIncidentType.js +49 -0
  148. package/src/settings/ModalDeleteLocationInService.js +49 -0
  149. package/src/settings/ModalDeleteTrespassReason.js +49 -0
  150. package/src/settings/ModalPreviewTrespassDoc.js +65 -0
  151. package/src/settings/ModalTrespassDocTokens.js +83 -0
  152. package/src/settings/NewIncidentTypePane.js +182 -0
  153. package/src/settings/PutIncidentType.js +60 -0
  154. package/src/settings/PutLocationsInService.js +52 -0
  155. package/src/settings/PutTrespassReasons.js +61 -0
  156. package/src/settings/PutTrespassTemplate.js +50 -0
  157. package/src/settings/TrespassDoc.css +17 -0
  158. package/src/settings/TrespassDocDetailsPane.js +215 -0
  159. package/src/settings/TrespassDocEditPane.js +538 -0
  160. package/src/settings/TrespassDocPaneset.js +581 -0
  161. package/src/settings/TrespassReasonDetailsPane.js +171 -0
  162. package/src/settings/TrespassReasonEditPane.js +221 -0
  163. package/src/settings/TrespassReasonsPaneset.js +282 -0
  164. package/src/settings/__snapshots__/IncidentCategoriesPane.test.js.snap +3 -0
  165. package/src/settings/__snapshots__/IncidentTypeDetailsPane.test.js.snap +3 -0
  166. package/src/settings/__snapshots__/IncidentTypeEditPane.test.js.snap +3 -0
  167. package/src/settings/__snapshots__/IncidentTypesPaneset.test.js.snap +3 -0
  168. package/src/settings/__snapshots__/LocationsPaneset.test.js.snap +3 -0
  169. package/src/settings/data/exampleJSON.json +92 -0
  170. package/src/settings/data/templateTokens.js +396 -0
  171. package/src/settings/helpers/alphabetize.js +18 -0
  172. package/src/settings/helpers/getCategoryTitleById.js +13 -0
  173. package/src/settings/helpers/makeId.js +15 -0
  174. package/src/settings/index.js +48 -0
  175. package/stripes.config.js +10 -0
  176. package/test/jest/__mock__/index.js +8 -0
  177. package/test/jest/__mock__/intl.mock.js +27 -0
  178. package/test/jest/__mock__/stripes.mock.js +26 -0
  179. package/test/jest/__mock__/stripesComponents.mock.js +151 -0
  180. package/test/jest/__mock__/stripesConfig.mock.js +1 -0
  181. package/test/jest/__mock__/stripesCore.mock.js +9 -0
  182. package/test/jest/__mock__/stripesIcon.mock.js +5 -0
  183. package/test/jest/__mock__/stripesSmartComponents.mock.js +7 -0
  184. package/test/jest/__mock__/stripesUtils.mock.js +3 -0
  185. package/test/jest/eslintrc.js +12 -0
  186. package/test/jest/setupFiles.js +5 -0
  187. package/translations/ui-security-incident/en_US.json +542 -0
  188. 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
+ });