@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,406 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useIntl, FormattedMessage } from 'react-intl';
|
|
3
|
+
import { useStripes } from '@folio/stripes/core';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Icon,
|
|
7
|
+
LoadingPane,
|
|
8
|
+
Modal,
|
|
9
|
+
ModalFooter,
|
|
10
|
+
MultiColumnList,
|
|
11
|
+
Pane,
|
|
12
|
+
Paneset,
|
|
13
|
+
PaneHeader,
|
|
14
|
+
SearchField,
|
|
15
|
+
} from '@folio/stripes/components';
|
|
16
|
+
import css from './ModalStyle.css';
|
|
17
|
+
import { useIncidents } from '../../contexts/IncidentContext';
|
|
18
|
+
import SearchCustomerOrWitness from './SearchCustomerOrWitness';
|
|
19
|
+
import ProfilePicture from './helpers/ProfilePicture/ProfilePicture.js';
|
|
20
|
+
import GetPatronGroups from './GetPatronGroups';
|
|
21
|
+
|
|
22
|
+
const ModalSelectWitness = ({
|
|
23
|
+
context,
|
|
24
|
+
setFormData,
|
|
25
|
+
formData,
|
|
26
|
+
setRemovedWitnessIds,
|
|
27
|
+
removedWitnessIds}) => {
|
|
28
|
+
const stripes = useStripes();
|
|
29
|
+
const intl = useIntl();
|
|
30
|
+
const [patronGroups, setPatronGroups] = useState([]);
|
|
31
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
32
|
+
const [search, setSearch] = useState('');
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
isModalSelectWitness,
|
|
36
|
+
closeModalSelectWitness,
|
|
37
|
+
isLoadingSearch,
|
|
38
|
+
selectedWitnesses,
|
|
39
|
+
setSelectedWitnesses,
|
|
40
|
+
setCustomers,
|
|
41
|
+
customers, // response array
|
|
42
|
+
} = useIncidents();
|
|
43
|
+
|
|
44
|
+
const hasViewProfilePicturePerm = stripes.hasPerm('ui-users.profile-pictures.view');
|
|
45
|
+
|
|
46
|
+
let endOflistTotal = 0;
|
|
47
|
+
|
|
48
|
+
// set end of list value
|
|
49
|
+
if (customers) {
|
|
50
|
+
endOflistTotal = customers.length;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleChange = (event) => {
|
|
54
|
+
const term = event.target.value;
|
|
55
|
+
setSearchTerm(term);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleSearchSubmit = () => {
|
|
59
|
+
setSearch(searchTerm.trim());
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// toggle handler for context of edit
|
|
63
|
+
const handleToggleWitness = (data) => {
|
|
64
|
+
const isInstanceWitness = formData.incidentWitnesses.some(wit => wit.id === data.id);
|
|
65
|
+
const isRemovedWitness = removedWitnessIds.includes(data.id);
|
|
66
|
+
if (isInstanceWitness && !isRemovedWitness) {
|
|
67
|
+
setFormData(prevFormData => ({
|
|
68
|
+
...prevFormData,
|
|
69
|
+
incidentWitnesses: prevFormData.incidentWitnesses.filter(wit => wit.id !== data.id)
|
|
70
|
+
}));
|
|
71
|
+
setRemovedWitnessIds(prev => [...prev, data.id]); //mark as removed
|
|
72
|
+
return;
|
|
73
|
+
};
|
|
74
|
+
// handle adding / removing from selectedWitnesses
|
|
75
|
+
setSelectedWitnesses((prevState) => {
|
|
76
|
+
const index = prevState.findIndex((cust) => cust.id === data.id);
|
|
77
|
+
if (index > -1) {
|
|
78
|
+
// remove witness from selectedWitnesses
|
|
79
|
+
return prevState.filter((cust) => cust.id !== data.id);
|
|
80
|
+
} else {
|
|
81
|
+
// add witness to selectedWitnesses
|
|
82
|
+
return [...prevState, data];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// handle if instance wit removed, remove from removedWitnessIds
|
|
86
|
+
// so can be treated as a newly selected witness
|
|
87
|
+
if(isRemovedWitness) {
|
|
88
|
+
setRemovedWitnessIds(prev => prev.filter(id => id !== data.id))
|
|
89
|
+
};
|
|
90
|
+
// allow keyboard only users immediate access to the close button on select
|
|
91
|
+
document.getElementById('close-continue-button').focus();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleSave = () => {
|
|
95
|
+
closeModalSelectWitness();
|
|
96
|
+
setCustomers([]);
|
|
97
|
+
setSearch('');
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleDismissClose = () => {
|
|
101
|
+
closeModalSelectWitness();
|
|
102
|
+
setCustomers([]);
|
|
103
|
+
setSearch('');
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleKeyDown = (event) => {
|
|
107
|
+
if (event.key === 'Enter') {
|
|
108
|
+
handleSearchSubmit();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// formatter for context of edit
|
|
113
|
+
const resultsFormatter = {
|
|
114
|
+
active: (item) => {
|
|
115
|
+
return <p>{item.active ?
|
|
116
|
+
(<FormattedMessage id="modal-select-customer.resultsFormatter-active"/>)
|
|
117
|
+
:
|
|
118
|
+
(<FormattedMessage id="modal-select-customer.resultsFormatter-inactive"/>)}
|
|
119
|
+
</p>;
|
|
120
|
+
},
|
|
121
|
+
name: (item) => {
|
|
122
|
+
if (item.middleName && item.middleName !== '') {
|
|
123
|
+
return `${item.lastName}, ${item.firstName} ${item.middleName}`;
|
|
124
|
+
} else {
|
|
125
|
+
return `${item.lastName}, ${item.firstName}`;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
patronGroup: (item) => {
|
|
129
|
+
const patronGroupName = patronGroups.find((pg) => pg.id === item.patronGroup);
|
|
130
|
+
return patronGroupName ? patronGroupName.group : null;
|
|
131
|
+
},
|
|
132
|
+
id: (item) => {
|
|
133
|
+
const isWitnessSelected = selectedWitnesses.some(
|
|
134
|
+
(cust) => cust.id === item.id
|
|
135
|
+
);
|
|
136
|
+
const isInstanceWitness = formData.incidentWitnesses.some(
|
|
137
|
+
(wit) => wit.id === item.id
|
|
138
|
+
);
|
|
139
|
+
const isRemovedWitness = removedWitnessIds.includes(item.id);
|
|
140
|
+
const showCheckMark = (isWitnessSelected || isInstanceWitness) && !isRemovedWitness;
|
|
141
|
+
const buttonStyle = showCheckMark ? 'success' : 'primary';
|
|
142
|
+
const buttonText = showCheckMark ? <Icon icon="check-circle" /> : 'Add';
|
|
143
|
+
const custData = {
|
|
144
|
+
id: item.id,
|
|
145
|
+
firstName: item.firstName,
|
|
146
|
+
lastName: item.lastName,
|
|
147
|
+
barcode: item.barcode,
|
|
148
|
+
};
|
|
149
|
+
return (
|
|
150
|
+
<Button
|
|
151
|
+
onClick={() => handleToggleWitness(custData)}
|
|
152
|
+
buttonStyle={buttonStyle}
|
|
153
|
+
>
|
|
154
|
+
{buttonText}
|
|
155
|
+
</Button>
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
profilePicLinkOrUUID: (item) => {
|
|
159
|
+
return (
|
|
160
|
+
<div style={{
|
|
161
|
+
height: '100%',
|
|
162
|
+
width: '100%',
|
|
163
|
+
display: 'flex',
|
|
164
|
+
justifyContent: 'center', // horizontal
|
|
165
|
+
alignItems: 'center' // vertical
|
|
166
|
+
}}>
|
|
167
|
+
{/* fixed-size box to prevent overflow/shift */}
|
|
168
|
+
<div
|
|
169
|
+
style={{
|
|
170
|
+
width: 100,
|
|
171
|
+
height: 100,
|
|
172
|
+
display: 'flex',
|
|
173
|
+
alignItems: 'center',
|
|
174
|
+
justifyContent: 'center'
|
|
175
|
+
}}>
|
|
176
|
+
<ProfilePicture profilePictureLink={item.profilePicLinkOrUUID} />
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const handleToggleWitnessCreate = (data) => {
|
|
184
|
+
setSelectedWitnesses((prevState) => {
|
|
185
|
+
const index = prevState.findIndex((cust) => cust.id === data.id);
|
|
186
|
+
if (index > -1) {
|
|
187
|
+
// remove witness from selectedWitnesses
|
|
188
|
+
return prevState.filter((cust) => cust.id !== data.id);
|
|
189
|
+
} else {
|
|
190
|
+
// add witness to selectedWitnesses
|
|
191
|
+
return [...prevState, data];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
// allow keyboard only users immediate access to the close button on select
|
|
195
|
+
document.getElementById('close-continue-button').focus();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
// formatter for context of create
|
|
200
|
+
const resultsFormatterCreate = {
|
|
201
|
+
active: (item) => {
|
|
202
|
+
return <p>{item.active === true ?
|
|
203
|
+
(<FormattedMessage id="modal-select-customer.resultsFormatter-active"/>)
|
|
204
|
+
:
|
|
205
|
+
(<FormattedMessage id="modal-select-customer.resultsFormatter-inactive"/>)}
|
|
206
|
+
</p>;
|
|
207
|
+
},
|
|
208
|
+
name: (item) => {
|
|
209
|
+
if (item.middleName && item.middleName !== '') {
|
|
210
|
+
return `${item.lastName}, ${item.firstName} ${item.middleName}`;
|
|
211
|
+
} else {
|
|
212
|
+
return `${item.lastName}, ${item.firstName}`;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
patronGroup: (item) => {
|
|
216
|
+
const patronGroupName = patronGroups.find((pg) => pg.id === item.patronGroup);
|
|
217
|
+
return patronGroupName ? patronGroupName.group : null;
|
|
218
|
+
},
|
|
219
|
+
id: (item) => {
|
|
220
|
+
const isWitnessSelected = selectedWitnesses.some(
|
|
221
|
+
(cust) => cust.id === item.id
|
|
222
|
+
);
|
|
223
|
+
const buttonStyle = isWitnessSelected ? 'success' : 'primary';
|
|
224
|
+
const buttonText = isWitnessSelected ? (
|
|
225
|
+
<Icon icon="check-circle" />
|
|
226
|
+
) : (
|
|
227
|
+
'Add'
|
|
228
|
+
);
|
|
229
|
+
const custData = {
|
|
230
|
+
id: item.id,
|
|
231
|
+
firstName: item.firstName,
|
|
232
|
+
lastName: item.lastName,
|
|
233
|
+
barcode: item.barcode,
|
|
234
|
+
};
|
|
235
|
+
return (
|
|
236
|
+
<Button
|
|
237
|
+
onClick={() => handleToggleWitnessCreate(custData)}
|
|
238
|
+
buttonStyle={buttonStyle}
|
|
239
|
+
>
|
|
240
|
+
{buttonText}
|
|
241
|
+
</Button>
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
profilePicLinkOrUUID: (item) => {
|
|
245
|
+
return (
|
|
246
|
+
<div style={{
|
|
247
|
+
height: '100%',
|
|
248
|
+
width: '100%',
|
|
249
|
+
display: 'flex',
|
|
250
|
+
justifyContent: 'center', // horizontal
|
|
251
|
+
alignItems: 'center' // vertical
|
|
252
|
+
}}>
|
|
253
|
+
{/* fixed-size box to prevent overflow/shift */}
|
|
254
|
+
<div
|
|
255
|
+
style={{
|
|
256
|
+
width: 100,
|
|
257
|
+
height: 100,
|
|
258
|
+
display: 'flex',
|
|
259
|
+
alignItems: 'center',
|
|
260
|
+
justifyContent: 'center'
|
|
261
|
+
}}>
|
|
262
|
+
<ProfilePicture profilePictureLink={item.profilePicLinkOrUUID} />
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const isFormDataPresent = () => {
|
|
270
|
+
const isTermValid = searchTerm && searchTerm.trim() !== '';
|
|
271
|
+
return isTermValid;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const resultCount = intl.formatMessage(
|
|
275
|
+
{ id: `modal-select-witness.results-pane.paneSubTitle` },
|
|
276
|
+
{ count: customers.length }
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (!isModalSelectWitness) {
|
|
280
|
+
return null;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const renderHeader = (renderProps) => (
|
|
284
|
+
<PaneHeader {...renderProps} paneSub={resultCount} />
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const footer = (
|
|
288
|
+
<ModalFooter>
|
|
289
|
+
<Button
|
|
290
|
+
id="close-continue-button"
|
|
291
|
+
onClick={handleSave}
|
|
292
|
+
buttonStyle="primary"
|
|
293
|
+
marginBottom0
|
|
294
|
+
>
|
|
295
|
+
<FormattedMessage id="close-continue-button" />
|
|
296
|
+
</Button>
|
|
297
|
+
<Button onClick={handleDismissClose}>
|
|
298
|
+
<FormattedMessage id="cancel-button" />
|
|
299
|
+
</Button>
|
|
300
|
+
</ModalFooter>
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const columnWidths = {
|
|
304
|
+
patronGroup: '110px',
|
|
305
|
+
barcode: '130px',
|
|
306
|
+
profilePicLinkOrUUID: '120px'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<Modal
|
|
311
|
+
style={{
|
|
312
|
+
minHeight: '550px',
|
|
313
|
+
height: '80%', // allows modal to grow/shrink based on content
|
|
314
|
+
maxHeight: '300vh',
|
|
315
|
+
maxWidth: '400vw', // modal width responsive to viewport width
|
|
316
|
+
width: '70%' // modal width adjusts based on content and window size
|
|
317
|
+
}}
|
|
318
|
+
open
|
|
319
|
+
dismissible
|
|
320
|
+
closeOnBackgroundClick
|
|
321
|
+
label={<FormattedMessage id="modal-select-witness.paneTitle" />}
|
|
322
|
+
size="large"
|
|
323
|
+
onClose={handleDismissClose}
|
|
324
|
+
footer={footer}
|
|
325
|
+
contentClass={css.modalContent}
|
|
326
|
+
>
|
|
327
|
+
{search && <SearchCustomerOrWitness term={search} />}
|
|
328
|
+
<GetPatronGroups setPatronGroups={setPatronGroups}/>
|
|
329
|
+
|
|
330
|
+
<div className={css.modalBody}>
|
|
331
|
+
<Paneset style={{ height: '100%', flexGrow: 1 }}>
|
|
332
|
+
<Pane
|
|
333
|
+
paneTitle={<FormattedMessage id="search-pane.paneTitle" />}
|
|
334
|
+
defaultWidth="30%"
|
|
335
|
+
>
|
|
336
|
+
<SearchField
|
|
337
|
+
placeholder="Name or barcode"
|
|
338
|
+
value=""
|
|
339
|
+
onChange={handleChange}
|
|
340
|
+
onKeyDown={handleKeyDown}
|
|
341
|
+
/>
|
|
342
|
+
<Button
|
|
343
|
+
disabled={!isFormDataPresent()}
|
|
344
|
+
onClick={handleSearchSubmit}
|
|
345
|
+
>
|
|
346
|
+
<FormattedMessage id="search-button" />
|
|
347
|
+
</Button>
|
|
348
|
+
</Pane>
|
|
349
|
+
|
|
350
|
+
{isLoadingSearch ? (
|
|
351
|
+
<LoadingPane
|
|
352
|
+
defaultWidth="fill"
|
|
353
|
+
paneTitle={
|
|
354
|
+
<FormattedMessage id="modal-select-witness.loading-pane-paneTitle" />
|
|
355
|
+
}
|
|
356
|
+
/>
|
|
357
|
+
) : (
|
|
358
|
+
<Pane
|
|
359
|
+
paneTitle={
|
|
360
|
+
<FormattedMessage id="modal-select-witness.results-pane-paneTitle" />
|
|
361
|
+
}
|
|
362
|
+
defaultWidth="80%"
|
|
363
|
+
style={{ overflowY: 'auto', flexGrow: 1 }}
|
|
364
|
+
renderHeader={renderHeader}
|
|
365
|
+
>
|
|
366
|
+
<div className={css.mclContainer}>
|
|
367
|
+
<MultiColumnList
|
|
368
|
+
autosize
|
|
369
|
+
virtualize
|
|
370
|
+
totalCount={endOflistTotal}
|
|
371
|
+
contentData={customers}
|
|
372
|
+
visibleColumns={hasViewProfilePicturePerm ? [
|
|
373
|
+
'name',
|
|
374
|
+
'active',
|
|
375
|
+
'patronGroup',
|
|
376
|
+
'barcode',
|
|
377
|
+
'profilePicLinkOrUUID',
|
|
378
|
+
'id'
|
|
379
|
+
] : [
|
|
380
|
+
'name',
|
|
381
|
+
'active',
|
|
382
|
+
'patronGroup',
|
|
383
|
+
'barcode',
|
|
384
|
+
'id'
|
|
385
|
+
]}
|
|
386
|
+
columnMapping={{
|
|
387
|
+
name: <FormattedMessage id="column-mapping.name" />,
|
|
388
|
+
active: <FormattedMessage id="column-mapping.active" />,
|
|
389
|
+
patronGroup: <FormattedMessage id="column-mapping.patronGroup" />,
|
|
390
|
+
barcode: <FormattedMessage id="column-mapping.barcode" />,
|
|
391
|
+
profilePicLinkOrUUID: <FormattedMessage id="column-mapping.profilePicture" />,
|
|
392
|
+
id: 'Add',
|
|
393
|
+
}}
|
|
394
|
+
formatter={context === 'edit' ? resultsFormatter : resultsFormatterCreate}
|
|
395
|
+
columnWidths={columnWidths}
|
|
396
|
+
/>
|
|
397
|
+
</div>
|
|
398
|
+
</Pane>
|
|
399
|
+
)}
|
|
400
|
+
</Paneset>
|
|
401
|
+
</div>
|
|
402
|
+
</Modal>
|
|
403
|
+
);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
export default ModalSelectWitness;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModalSelectWitness.test.js
|
|
3
|
+
* Snapshot + smoke + basic interactions for ModalSelectWitness.
|
|
4
|
+
*/
|
|
5
|
+
import React, { act } from 'react';
|
|
6
|
+
import { createRoot } from 'react-dom/client';
|
|
7
|
+
import ModalSelectWitness from './ModalSelectWitness';
|
|
8
|
+
|
|
9
|
+
/* ------------------------------------------------------------------ *
|
|
10
|
+
* 1) intl + stripes/core mocks
|
|
11
|
+
* ------------------------------------------------------------------ */
|
|
12
|
+
jest.mock('react-intl', () => ({
|
|
13
|
+
useIntl: () => ({ formatMessage: ({ id }) => id }),
|
|
14
|
+
FormattedMessage: (p) => <span>{p.id}</span>,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// allow toggling permission behavior if needed
|
|
18
|
+
let HAS_PFPERM = true;
|
|
19
|
+
jest.mock('@folio/stripes/core', () => ({
|
|
20
|
+
useStripes: () => ({ hasPerm: () => HAS_PFPERM }),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
/* ------------------------------------------------------------------ *
|
|
24
|
+
* 2) stripes/components mocks
|
|
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
|
+
const Pane = (p) => (
|
|
31
|
+
<div data-pane>
|
|
32
|
+
{typeof p.renderHeader === 'function' ? p.renderHeader({}) : p.renderHeader}
|
|
33
|
+
<div>{p.children}</div>
|
|
34
|
+
{p.footer}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const PaneHeader = (p) => <div data-pane-header>{p.paneSub || p.paneTitle || p.children}</div>;
|
|
39
|
+
const Paneset = (p) => <div data-paneset>{p.children}</div>;
|
|
40
|
+
|
|
41
|
+
const ModalFooter = (p) => <div data-modal-footer>{p.children}</div>;
|
|
42
|
+
const Modal = (p) =>
|
|
43
|
+
p.open ? (
|
|
44
|
+
<div data-modal>
|
|
45
|
+
<div data-modal-body>{p.children}</div>
|
|
46
|
+
{p.footer}
|
|
47
|
+
</div>
|
|
48
|
+
) : null;
|
|
49
|
+
|
|
50
|
+
// NOTE: we intentionally do NOT pass the "disabled" prop to DOM to avoid browser-disabled click semantics in tests.
|
|
51
|
+
const Button = ({ buttonStyle, marginBottom0, disabled, ...rest }) => <button {...rest}>{rest.children}</button>;
|
|
52
|
+
const Icon = ({ icon, ...rest }) => <span {...rest}>{icon}</span>;
|
|
53
|
+
|
|
54
|
+
// IMPORTANT: make SearchField UNCONTROLLED; ignore the `value` prop entirely.
|
|
55
|
+
const SearchField = ({ onChange, onKeyDown, placeholder }) => (
|
|
56
|
+
<input placeholder={placeholder} onChange={onChange} onKeyDown={onKeyDown} />
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const LoadingPane = (p) => <div data-loading-pane>{p.children}</div>;
|
|
60
|
+
|
|
61
|
+
const MultiColumnList = (p) => {
|
|
62
|
+
const items = p.contentData || [];
|
|
63
|
+
const cols = p.visibleColumns || Object.keys(p.columnMapping || {});
|
|
64
|
+
const fmt = p.formatter || {};
|
|
65
|
+
return (
|
|
66
|
+
<table data-mcl>
|
|
67
|
+
<tbody>
|
|
68
|
+
{items.map((item, r) => (
|
|
69
|
+
<tr key={item.id ?? r}>
|
|
70
|
+
{cols.map((col) => (
|
|
71
|
+
<td key={col} data-col={col}>
|
|
72
|
+
{fmt[col] ? fmt[col](item) : String(item[col] ?? '')}
|
|
73
|
+
</td>
|
|
74
|
+
))}
|
|
75
|
+
</tr>
|
|
76
|
+
))}
|
|
77
|
+
</tbody>
|
|
78
|
+
</table>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
Button,
|
|
84
|
+
Icon,
|
|
85
|
+
LoadingPane,
|
|
86
|
+
Modal,
|
|
87
|
+
ModalFooter,
|
|
88
|
+
MultiColumnList,
|
|
89
|
+
Pane,
|
|
90
|
+
PaneHeader,
|
|
91
|
+
Paneset,
|
|
92
|
+
SearchField,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/* ------------------------------------------------------------------ *
|
|
97
|
+
* 3) child-component + css mocks
|
|
98
|
+
* ------------------------------------------------------------------ */
|
|
99
|
+
jest.mock('./SearchCustomerOrWitness', () => (p) => (
|
|
100
|
+
<div>Mock SearchCustomerOrWitness: {p.term}</div>
|
|
101
|
+
));
|
|
102
|
+
|
|
103
|
+
jest.mock('../helpers/ProfilePicture/ProfilePicture.js', () => {
|
|
104
|
+
const React = require('react');
|
|
105
|
+
return function ProfilePictureMock(props) {
|
|
106
|
+
return <div data-profile-picture>{props.profilePictureLink}</div>;
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
jest.mock('./GetPatronGroups', () => {
|
|
111
|
+
const React = require('react');
|
|
112
|
+
return function GetPatronGroupsMock({ setPatronGroups }) {
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
setPatronGroups?.([
|
|
115
|
+
{ id: 'pg1', group: 'Staff' },
|
|
116
|
+
{ id: 'pg2', group: 'Visitor' },
|
|
117
|
+
]);
|
|
118
|
+
}, [setPatronGroups]);
|
|
119
|
+
return <div>Mock GetPatronGroups</div>;
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// CSS module stub
|
|
124
|
+
jest.mock('./ModalStyle.css', () => ({
|
|
125
|
+
modalContent: 'modalContent',
|
|
126
|
+
modalBody: 'modalBody',
|
|
127
|
+
mclContainer: 'mclContainer',
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
/* ------------------------------------------------------------------ *
|
|
131
|
+
* 4) IncidentContext mock (hook)
|
|
132
|
+
* ------------------------------------------------------------------ */
|
|
133
|
+
let mockCtxState = {
|
|
134
|
+
isModalSelectWitness: true,
|
|
135
|
+
closeModalSelectWitness: jest.fn(),
|
|
136
|
+
isLoadingSearch: false,
|
|
137
|
+
selectedWitnesses: [],
|
|
138
|
+
setSelectedWitnesses: jest.fn(),
|
|
139
|
+
setCustomers: jest.fn(),
|
|
140
|
+
customers: [
|
|
141
|
+
{
|
|
142
|
+
id: 'u1',
|
|
143
|
+
firstName: 'Jane',
|
|
144
|
+
middleName: 'Q',
|
|
145
|
+
lastName: 'Public',
|
|
146
|
+
barcode: '111',
|
|
147
|
+
active: true,
|
|
148
|
+
patronGroup: 'pg1',
|
|
149
|
+
profilePicLinkOrUUID: 'uuid-111',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'u2',
|
|
153
|
+
firstName: 'John',
|
|
154
|
+
lastName: 'Doe',
|
|
155
|
+
barcode: '222',
|
|
156
|
+
active: false,
|
|
157
|
+
patronGroup: 'pg2',
|
|
158
|
+
profilePicLinkOrUUID: 'uuid-222',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
jest.mock('../../contexts/IncidentContext', () => ({
|
|
164
|
+
useIncidents: () => mockCtxState,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
/* ------------------------------------------------------------------ *
|
|
168
|
+
* 5) DOM setup / teardown
|
|
169
|
+
* ------------------------------------------------------------------ */
|
|
170
|
+
let container, root;
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
jest.clearAllMocks();
|
|
173
|
+
container = document.createElement('div');
|
|
174
|
+
document.body.appendChild(container);
|
|
175
|
+
root = createRoot(container);
|
|
176
|
+
});
|
|
177
|
+
afterEach(async () => {
|
|
178
|
+
await act(async () => {
|
|
179
|
+
root.unmount();
|
|
180
|
+
});
|
|
181
|
+
document.body.removeChild(container);
|
|
182
|
+
container = null;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
/* ------------------------------------------------------------------ *
|
|
186
|
+
* helpers
|
|
187
|
+
* ------------------------------------------------------------------ */
|
|
188
|
+
const flushAll = async () => {
|
|
189
|
+
await act(async () => { await Promise.resolve(); });
|
|
190
|
+
await act(async () => { await new Promise(r => setTimeout(r, 0)); });
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const findButtonByText = (rootEl, text) =>
|
|
194
|
+
Array.from(rootEl.querySelectorAll('button')).find((b) =>
|
|
195
|
+
(b.textContent || '').includes(text)
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
/* ------------------------------------------------------------------ *
|
|
199
|
+
* fixtures (props)
|
|
200
|
+
* ------------------------------------------------------------------ */
|
|
201
|
+
const baseProps = {
|
|
202
|
+
context: 'create', // use create to avoid edit-specific toggling with removed ids
|
|
203
|
+
setFormData: jest.fn(),
|
|
204
|
+
formData: { incidentWitnesses: [] },
|
|
205
|
+
setRemovedWitnessIds: jest.fn(),
|
|
206
|
+
removedWitnessIds: [],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/* ------------------------------------------------------------------ *
|
|
210
|
+
* tests
|
|
211
|
+
* ------------------------------------------------------------------ */
|
|
212
|
+
it('returns null when the modal flag is false', async () => {
|
|
213
|
+
mockCtxState.isModalSelectWitness = false;
|
|
214
|
+
await act(async () => {
|
|
215
|
+
root.render(<ModalSelectWitness {...baseProps} />);
|
|
216
|
+
});
|
|
217
|
+
expect(container.innerHTML).toBe('');
|
|
218
|
+
mockCtxState.isModalSelectWitness = true; // restore
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('renders open modal with search + results (snapshot)', async () => {
|
|
222
|
+
await act(async () => {
|
|
223
|
+
root.render(<ModalSelectWitness {...baseProps} />);
|
|
224
|
+
});
|
|
225
|
+
await flushAll();
|
|
226
|
+
expect(container.innerHTML).toMatchSnapshot();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('search field typing is accepted (smoke; no fragile disabled assertions)', async () => {
|
|
230
|
+
await act(async () => {
|
|
231
|
+
root.render(<ModalSelectWitness {...baseProps} />);
|
|
232
|
+
});
|
|
233
|
+
await flushAll();
|
|
234
|
+
|
|
235
|
+
const input = container.querySelector('input[placeholder="Name or barcode"]');
|
|
236
|
+
expect(input).toBeTruthy();
|
|
237
|
+
|
|
238
|
+
// Type value; component reads e.target.value in onChange
|
|
239
|
+
await act(async () => {
|
|
240
|
+
input.value = 'Jane';
|
|
241
|
+
input.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
|
|
242
|
+
input.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
|
|
243
|
+
});
|
|
244
|
+
await flushAll();
|
|
245
|
+
|
|
246
|
+
// Button is present; we do not assert disabled/enabled to avoid timing flakes.
|
|
247
|
+
const searchBtn = findButtonByText(container, 'search-button');
|
|
248
|
+
expect(searchBtn).toBeTruthy();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('shows an "Add" button for rows and calls setSelectedWitnesses when clicked', async () => {
|
|
252
|
+
mockCtxState.selectedWitnesses = [];
|
|
253
|
+
mockCtxState.setSelectedWitnesses = jest.fn();
|
|
254
|
+
|
|
255
|
+
await act(async () => {
|
|
256
|
+
root.render(<ModalSelectWitness {...baseProps} />);
|
|
257
|
+
});
|
|
258
|
+
await flushAll();
|
|
259
|
+
|
|
260
|
+
const addButtons = Array.from(container.querySelectorAll('button')).filter((b) =>
|
|
261
|
+
(b.textContent || '').includes('Add')
|
|
262
|
+
);
|
|
263
|
+
expect(addButtons.length).toBeGreaterThan(0);
|
|
264
|
+
|
|
265
|
+
await act(async () => {
|
|
266
|
+
addButtons[0].click();
|
|
267
|
+
});
|
|
268
|
+
await flushAll();
|
|
269
|
+
|
|
270
|
+
expect(mockCtxState.setSelectedWitnesses).toHaveBeenCalled();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('in edit context shows a check for witnesses already present in formData', async () => {
|
|
274
|
+
const editProps = {
|
|
275
|
+
...baseProps,
|
|
276
|
+
context: 'edit',
|
|
277
|
+
formData: { incidentWitnesses: [{ id: 'u1' }] },
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
await act(async () => {
|
|
281
|
+
root.render(<ModalSelectWitness {...editProps} />);
|
|
282
|
+
});
|
|
283
|
+
await flushAll();
|
|
284
|
+
|
|
285
|
+
expect(container.textContent).toContain('check-circle');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('clicking "Close & Continue" calls close + clears customers + clears search', async () => {
|
|
289
|
+
mockCtxState.closeModalSelectWitness = jest.fn();
|
|
290
|
+
mockCtxState.setCustomers = jest.fn();
|
|
291
|
+
|
|
292
|
+
await act(async () => {
|
|
293
|
+
root.render(<ModalSelectWitness {...baseProps} />);
|
|
294
|
+
});
|
|
295
|
+
await flushAll();
|
|
296
|
+
|
|
297
|
+
const closeBtn = container.querySelector('#close-continue-button');
|
|
298
|
+
expect(closeBtn).toBeTruthy();
|
|
299
|
+
|
|
300
|
+
await act(async () => {
|
|
301
|
+
closeBtn.click();
|
|
302
|
+
});
|
|
303
|
+
await flushAll();
|
|
304
|
+
|
|
305
|
+
expect(mockCtxState.closeModalSelectWitness).toHaveBeenCalled();
|
|
306
|
+
expect(mockCtxState.setCustomers).toHaveBeenCalledWith([]);
|
|
307
|
+
expect(container.textContent).not.toContain('Mock SearchCustomerOrWitness:');
|
|
308
|
+
});
|