@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,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
|
+
});
|