@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,32 @@
1
+ .modalContent {
2
+ display: flex;
3
+ flex-direction: column;
4
+ padding: 0;
5
+ height: 100%;
6
+ overflow: hidden;
7
+ }
8
+
9
+ .modalBody {
10
+ overflow: hidden;
11
+ display: flex;
12
+ flex-grow: 1;
13
+ min-height: 0;
14
+ padding-bottom: 0;
15
+ }
16
+
17
+ .leftPaneScroll {
18
+ display: flex;
19
+ flex-direction: column;
20
+ min-height: 0;
21
+ max-height: 100%;
22
+ overflow-y: auto;
23
+ padding-bottom: 120px;
24
+ }
25
+
26
+ .mclContainer {
27
+ height: 100%;
28
+ display: flex;
29
+ flex-grow: 1;
30
+ overflow-y: auto;
31
+ padding-bottom: 90px;
32
+ }
@@ -0,0 +1,178 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import {
5
+ Button,
6
+ Checkbox,
7
+ Col,
8
+ Modal,
9
+ ModalFooter,
10
+ Pane,
11
+ Paneset,
12
+ Row,
13
+ } from '@folio/stripes/components';
14
+ import IncidentTypeCard from './IncidentTypeCard';
15
+ import GetIncidentTypesDetails from '../../settings/GetIncidentTypesDetails';
16
+ import GetIncidentCategories from '../../settings/GetIncidentCategories';
17
+ import { useIncidents } from '../../contexts/IncidentContext';
18
+ import css from './ModalStyle.css';
19
+
20
+ const ModalSelectIncidentTypes = ({ handleIncidentTypeToggle, formDataIncidentTypes }) => {
21
+ const {
22
+ isModalSelectTypesOpen,
23
+ closeModalSelectTypes,
24
+ incidentTypesList, // general list of available types, not an instance list
25
+ incidentCategories
26
+ } = useIncidents();
27
+
28
+ // for Checkbox filtering
29
+ const [filterArgs, setFilterArgs] = useState([]);
30
+
31
+ // handles Checkbox filtering
32
+ const filterHandler = (event) => {
33
+ if (event.target.checked) {
34
+ setFilterArgs((prevArgs) => [...prevArgs, event.target.value]);
35
+ } else {
36
+ setFilterArgs((prevArgs) =>
37
+ prevArgs.filter((arg) => arg !== event.target.value)
38
+ );
39
+ }
40
+ };
41
+ // handles Checkbox filtering
42
+ const filteredList = incidentTypesList.filter((type) =>
43
+ // if filterArgs is empty, new array has all incidentTypes (no filter)
44
+ // else new array is only incidentTypes w/ that category_id(s)
45
+ filterArgs.length > 0 ? filterArgs.includes(type.category_id) : true
46
+ );
47
+
48
+ if (!isModalSelectTypesOpen) {
49
+ return null;
50
+ };
51
+
52
+ const handleTypeToggle = (typeObject) => {
53
+ handleIncidentTypeToggle(typeObject)
54
+ // allow keyboard only users immediate access to the close button on select
55
+ document.getElementById('close-continue-button-access').focus();
56
+ };
57
+
58
+ const handleSave = () => {
59
+ closeModalSelectTypes();
60
+ setFilterArgs([]);
61
+ };
62
+
63
+ const handleCancelDismiss = () => {
64
+ closeModalSelectTypes();
65
+ setFilterArgs([]);
66
+ };
67
+
68
+ const chunkArray = (array, size) => {
69
+ const chunkedArr = [];
70
+ for (let i = 0; i < array.length; i += size) {
71
+ chunkedArr.push(array.slice(i, i + size));
72
+ }
73
+ return chunkedArr;
74
+ };
75
+
76
+ const chunkedIncidentTypes = chunkArray(filteredList, 3);
77
+
78
+ const footer = (
79
+ <ModalFooter
80
+ className={css.customModalFooter}
81
+ style={{ position: 'relative' }}
82
+ >
83
+ <Button
84
+ id="close-continue-button-access"
85
+ onClick={handleSave}
86
+ buttonStyle="primary"
87
+ >
88
+ <FormattedMessage id="close-continue-button" />
89
+ </Button>
90
+ <Button onClick={handleCancelDismiss}>
91
+ <FormattedMessage id="cancel-button" />
92
+ </Button>
93
+ </ModalFooter>
94
+ );
95
+
96
+ return (
97
+ <Modal
98
+ style={{
99
+ display: 'flex',
100
+ flexDirection: 'column',
101
+ maxHeight: '90vh',
102
+ minHeight: '550px',
103
+ overflow: 'hidden',
104
+ }}
105
+ open
106
+ dismissible
107
+ closeOnBackgroundClick
108
+ label={<FormattedMessage id="modal-incident-type-label" />}
109
+ size="large"
110
+ onClose={handleCancelDismiss}
111
+ footer={footer}
112
+ contentClass={css.modalContent}
113
+ >
114
+ <GetIncidentTypesDetails context="incidents" />
115
+ <GetIncidentCategories />
116
+
117
+ <div className={css.modalBody}>
118
+ <Paneset style={{ height: '100%', flexGrow: 1 }}>
119
+ <Pane
120
+ defaultWidth="20%"
121
+ paneTitle={
122
+ <FormattedMessage id="modal-incident-type.filter-pane.paneTitle" />
123
+ }
124
+ >
125
+ {incidentCategories.map((cat) => (
126
+ <Checkbox
127
+ key={cat.id}
128
+ label={cat.title}
129
+ value={cat.id}
130
+ onChange={filterHandler}
131
+ />
132
+ ))}
133
+
134
+ </Pane>
135
+ <Pane
136
+ defaultWidth="80%"
137
+ style={{ overflowY: 'auto', flexGrow: 1 }}
138
+ paneTitle={
139
+ <FormattedMessage id="modal-incident-type.incident-types-pane.paneTitle" />
140
+ }
141
+ >
142
+ <div className={css.incidentTypeCardsContainer}>
143
+ {chunkedIncidentTypes.map((row, rowIndex) => (
144
+ <Row key={rowIndex}>
145
+ {row.map((type, index) => {
146
+ // in context of map, isSelected logic runs on each instance of a card, and each card will only have one opportunity to return true
147
+ const isSelected = formDataIncidentTypes.some(
148
+ (selectedType) => selectedType.id === type.id
149
+ );
150
+ return (
151
+ <Col xs={4} key={type.id}>
152
+ <IncidentTypeCard
153
+ id={type.id}
154
+ category_id={type.category_id}
155
+ title={type.title}
156
+ description={type.description}
157
+ handleTypeToggle={handleTypeToggle}
158
+ isSelected={isSelected}
159
+ />
160
+ </Col>
161
+ );
162
+ })}
163
+ </Row>
164
+ ))}
165
+ </div>
166
+ </Pane>
167
+ </Paneset>
168
+ </div>
169
+ </Modal>
170
+ );
171
+ };
172
+
173
+ ModalSelectIncidentTypes.propTypes = {
174
+ handleIncidentTypeSelection: PropTypes.func,
175
+ currentInstanceIncidentTypes: PropTypes.arrayOf(PropTypes.object),
176
+ };
177
+
178
+ export default ModalSelectIncidentTypes;
@@ -0,0 +1,273 @@
1
+
2
+ // This test file is currently still being built/is incomplete.
3
+ import React from 'react';
4
+ import ReactDOM from 'react-dom';
5
+ import { act } from 'react-dom/test-utils';
6
+ import ModalSelectIncidentTypes from './ModalSelectIncidentTypes';
7
+
8
+ // --- Mocks for the Incident Context ---
9
+ const mockIncidentContext = {
10
+ isModalSelectTypesOpen: false,
11
+ closeModalSelectTypes: jest.fn(),
12
+ incidentTypesList: [],
13
+ incidentCategories: [],
14
+ };
15
+
16
+ // Provide a mock for the useIncidents hook
17
+ jest.mock('../../contexts/IncidentContext', () => ({
18
+ useIncidents: () => mockIncidentContext,
19
+ }));
20
+
21
+ // Mocks for the child components used inside ModalSelectIncidentTypes
22
+ jest.mock('../../settings/GetIncidentTypesDetails', () => () => <div>Mock GetIncidentTypesDetails</div>);
23
+ jest.mock('../../settings/GetIncidentCategories', () => () => <div>Mock GetIncidentCategories</div>);
24
+ jest.mock('./IncidentTypeCard', () => {
25
+ // We'll pretend IncidentTypeCard is a simple clickable div for test purposes
26
+ return function IncidentTypeCard(props) {
27
+ const { id, title, isSelected, handleTypeToggle } = props;
28
+ const handleClick = () => {
29
+ handleTypeToggle({ id, title });
30
+ };
31
+ return (
32
+ <div data-testid={`card-${id}`} onClick={handleClick}>
33
+ {title} {isSelected ? '(selected)' : '(not selected)'}
34
+ </div>
35
+ );
36
+ };
37
+ });
38
+
39
+ // Minimal mocks for stripes components
40
+ jest.mock('@folio/stripes/components', () => {
41
+ const React = require('react');
42
+ return {
43
+ Modal: (props) => {
44
+ // In your test, we want the label's ID to appear in the output
45
+ let labelId = '';
46
+ if (props.label && props.label.props && props.label.props.id) {
47
+ labelId = props.label.props.id;
48
+ }
49
+ return (
50
+ <div>
51
+ {labelId && <h2>{labelId}</h2>}
52
+ {props.children}
53
+ </div>
54
+ );
55
+ },
56
+ ModalFooter: (props) => <div>{props.children}</div>,
57
+ Button: (props) => <button {...props}>{props.children}</button>,
58
+ Checkbox: (props) => {
59
+ const { label, value, onChange, ...rest } = props;
60
+ const handleChange = (evt) => {
61
+ onChange({ target: { checked: evt.target.checked, value } });
62
+ };
63
+ return (
64
+ <label>
65
+ <input type="checkbox" onChange={handleChange} {...rest} />
66
+ {label}
67
+ </label>
68
+ );
69
+ },
70
+ Row: (props) => <div>{props.children}</div>,
71
+ Col: (props) => <div>{props.children}</div>,
72
+ Pane: (props) => <div>{props.children}</div>,
73
+ Paneset: (props) => <div>{props.children}</div>,
74
+ };
75
+ });
76
+
77
+ describe('ModalSelectIncidentTypes', () => {
78
+ let container;
79
+ let handleIncidentTypeToggle;
80
+
81
+ beforeEach(() => {
82
+ container = document.createElement('div');
83
+ document.body.appendChild(container);
84
+
85
+ // Provide a fresh mock toggle function for each test
86
+ handleIncidentTypeToggle = jest.fn();
87
+
88
+ // Reset the stable context defaults
89
+ mockIncidentContext.isModalSelectTypesOpen = false;
90
+ mockIncidentContext.closeModalSelectTypes.mockClear();
91
+ mockIncidentContext.incidentTypesList = [];
92
+ mockIncidentContext.incidentCategories = [];
93
+ });
94
+
95
+ afterEach(() => {
96
+ ReactDOM.unmountComponentAtNode(container);
97
+ container.remove();
98
+ container = null;
99
+ });
100
+
101
+ test('returns null when modal is not open', () => {
102
+ act(() => {
103
+ ReactDOM.render(
104
+ <ModalSelectIncidentTypes
105
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
106
+ formDataIncidentTypes={[]}
107
+ />,
108
+ container
109
+ );
110
+ });
111
+ // The entire container should be empty
112
+ expect(container.innerHTML).toBe('');
113
+ });
114
+
115
+ test('renders basic structure when open', () => {
116
+ mockIncidentContext.isModalSelectTypesOpen = true;
117
+ mockIncidentContext.incidentCategories = [
118
+ { id: 'cat-1', title: 'Category One' },
119
+ { id: 'cat-2', title: 'Category Two' },
120
+ ];
121
+ mockIncidentContext.incidentTypesList = [
122
+ { id: 'type-1', title: 'Type One', category_id: 'cat-1' },
123
+ { id: 'type-2', title: 'Type Two', category_id: 'cat-2' },
124
+ ];
125
+
126
+ act(() => {
127
+ ReactDOM.render(
128
+ <ModalSelectIncidentTypes
129
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
130
+ formDataIncidentTypes={[]}
131
+ />,
132
+ container
133
+ );
134
+ });
135
+
136
+ const text = container.textContent;
137
+ // Should see the label for the modal
138
+ expect(text).toMatch(/modal-incident-type-label/);
139
+
140
+ // Should see the checkboxes for categories
141
+ expect(text).toMatch(/Category One/);
142
+ expect(text).toMatch(/Category Two/);
143
+
144
+ // Should see the 2 "cards"
145
+ expect(text).toMatch(/Type One \(not selected\)/);
146
+ expect(text).toMatch(/Type Two \(not selected\)/);
147
+
148
+ // Should see child mocks
149
+ expect(text).toMatch(/Mock GetIncidentTypesDetails/);
150
+ expect(text).toMatch(/Mock GetIncidentCategories/);
151
+ });
152
+
153
+ test('clicking type card calls handleIncidentTypeToggle', () => {
154
+ mockIncidentContext.isModalSelectTypesOpen = true;
155
+ mockIncidentContext.incidentTypesList = [
156
+ { id: 'type-10', title: 'Type Ten', category_id: 'cat-A' },
157
+ ];
158
+
159
+ act(() => {
160
+ ReactDOM.render(
161
+ <ModalSelectIncidentTypes
162
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
163
+ formDataIncidentTypes={[]}
164
+ />,
165
+ container
166
+ );
167
+ });
168
+
169
+ // The "card" is a <div data-testid="card-type-10" /> from our mock
170
+ const cardDiv = container.querySelector('[data-testid="card-type-10"]');
171
+ expect(cardDiv).toBeTruthy();
172
+
173
+ act(() => {
174
+ cardDiv.dispatchEvent(new MouseEvent('click', { bubbles: true }));
175
+ });
176
+ // Should have called handleIncidentTypeToggle once
177
+ expect(handleIncidentTypeToggle).toHaveBeenCalledTimes(1);
178
+
179
+ // The param: { id: 'type-10', title: 'Type Ten' }
180
+ expect(handleIncidentTypeToggle).toHaveBeenCalledWith({
181
+ id: 'type-10',
182
+ title: 'Type Ten',
183
+ });
184
+ });
185
+
186
+ test('clicking category checkboxes filters the incident types shown', () => {
187
+ mockIncidentContext.isModalSelectTypesOpen = true;
188
+ mockIncidentContext.incidentCategories = [
189
+ { id: 'cat-x', title: 'Category X' },
190
+ { id: 'cat-y', title: 'Category Y' },
191
+ ];
192
+ mockIncidentContext.incidentTypesList = [
193
+ { id: 'type-1', title: 'Type One', category_id: 'cat-x' },
194
+ { id: 'type-2', title: 'Type Two', category_id: 'cat-x' },
195
+ { id: 'type-3', title: 'Type Three', category_id: 'cat-y' },
196
+ ];
197
+
198
+ act(() => {
199
+ ReactDOM.render(
200
+ <ModalSelectIncidentTypes
201
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
202
+ formDataIncidentTypes={[]}
203
+ />,
204
+ container
205
+ );
206
+ });
207
+
208
+ let text = container.textContent;
209
+ // All 3 types appear initially
210
+ expect(text).toMatch(/Type One \(not selected\)/);
211
+ expect(text).toMatch(/Type Two \(not selected\)/);
212
+ expect(text).toMatch(/Type Three \(not selected\)/);
213
+
214
+ // Now let's simulate checking a single category, e.g. cat-x
215
+ const checkboxes = container.querySelectorAll('input[type="checkbox"]');
216
+ expect(checkboxes.length).toBe(2); // cat-x and cat-y
217
+ const catXBox = [...checkboxes].find(cb => cb.parentElement.textContent.includes('Category X'));
218
+
219
+ act(() => {
220
+ catXBox.checked = true;
221
+ catXBox.dispatchEvent(new MouseEvent('change', { bubbles: true }));
222
+ });
223
+
224
+ // With only cat-x checked, we expect to see only Type One, Type Two
225
+ text = container.textContent;
226
+ expect(text).toMatch(/Type One \(not selected\)/);
227
+ expect(text).toMatch(/Type Two \(not selected\)/);
228
+ // Type Three should be filtered out
229
+ expect(text).not.toMatch(/Type Three \(not selected\)/);
230
+ });
231
+
232
+ test('clicking "Close and Continue" calls closeModalSelectTypes and resets filterArgs', () => {
233
+ mockIncidentContext.isModalSelectTypesOpen = true;
234
+
235
+ act(() => {
236
+ ReactDOM.render(
237
+ <ModalSelectIncidentTypes
238
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
239
+ formDataIncidentTypes={[]}
240
+ />,
241
+ container
242
+ );
243
+ });
244
+
245
+ const button = container.querySelector('#close-continue-button-access');
246
+ act(() => {
247
+ button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
248
+ });
249
+ expect(mockIncidentContext.closeModalSelectTypes).toHaveBeenCalledTimes(1);
250
+ });
251
+
252
+ test('clicking "Cancel" also closes the modal', () => {
253
+ mockIncidentContext.isModalSelectTypesOpen = true;
254
+
255
+ act(() => {
256
+ ReactDOM.render(
257
+ <ModalSelectIncidentTypes
258
+ handleIncidentTypeToggle={handleIncidentTypeToggle}
259
+ formDataIncidentTypes={[]}
260
+ />,
261
+ container
262
+ );
263
+ });
264
+
265
+ const cancelButton = [...container.querySelectorAll('button')].find(btn =>
266
+ btn.textContent.includes('cancel-button')
267
+ );
268
+ act(() => {
269
+ cancelButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
270
+ });
271
+ expect(mockIncidentContext.closeModalSelectTypes).toHaveBeenCalledTimes(1);
272
+ });
273
+ });