@oneuptime/common 12.0.9 → 12.0.11
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/Models/DatabaseModels/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
- package/Models/DatabaseModels/RunnerJob.ts +17 -2
- package/Server/API/LlmProviderAPI.ts +8 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
- package/Server/Services/NetworkDeviceLinkService.ts +338 -2
- package/Server/Services/NetworkDeviceService.ts +265 -12
- package/Server/Services/RunnerJobService.ts +24 -1
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
- package/Server/Types/Database/JSONColumnQuery.ts +42 -4
- package/Server/Utils/LLM/LLMService.ts +108 -19
- package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
- package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
- package/Server/Utils/SSRFProtection.ts +5 -4
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
- package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
- package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
- package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
- package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
- package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
- package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
- package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
- package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
- package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
- package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
- package/Tests/Types/API/URLTelScheme.test.ts +261 -0
- package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
- package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
- package/Tests/UI/Components/Button.test.tsx +279 -0
- package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
- package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
- package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
- package/Tests/UI/Components/Icon.test.tsx +57 -0
- package/Tests/UI/Components/List.test.tsx +20 -3
- package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
- package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
- package/Tests/UI/Components/Pagination.test.tsx +721 -146
- package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
- package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
- package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
- package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
- package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
- package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
- package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
- package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
- package/Types/API/Protocol.ts +9 -0
- package/Types/API/URL.ts +286 -20
- package/Types/Billing/PayAsYouGoPricing.ts +47 -0
- package/Types/CustomField/CustomFieldType.ts +28 -0
- package/Types/Monitor/MonitorType.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
- package/Types/Runbook/RunbookStepType.ts +23 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
- package/UI/Components/Button/Button.tsx +14 -22
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +4 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
- package/UI/Components/Dictionary/Dictionary.tsx +4 -39
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
- package/UI/Components/Pagination/Pagination.tsx +373 -328
- package/UI/Components/Pagination/PaginationUtil.ts +263 -0
- package/UI/Components/Pagination/Screenshots/README.md +15 -0
- package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
- package/UI/Components/TextArea/TextArea.tsx +3 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
- package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
- package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
- package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
- package/Utils/API.ts +243 -36
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
- package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +9 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +20 -2
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +5 -4
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Types/API/Protocol.js +9 -0
- package/build/dist/Types/API/Protocol.js.map +1 -1
- package/build/dist/Types/API/URL.js +241 -18
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +10 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +10 -16
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
- package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
- package/build/dist/Utils/API.js +175 -34
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "@jest/globals";
|
|
10
10
|
import {
|
|
11
11
|
cleanup,
|
|
12
|
+
fireEvent,
|
|
12
13
|
render,
|
|
13
14
|
screen,
|
|
14
15
|
waitFor,
|
|
@@ -159,6 +160,34 @@ const getItemMock: MockFunction = getJestMockFunction();
|
|
|
159
160
|
const getCommonHeadersMock: MockFunction = getJestMockFunction();
|
|
160
161
|
const apiGetMock: MockFunction = getJestMockFunction();
|
|
161
162
|
|
|
163
|
+
/*
|
|
164
|
+
* How many mocked reads are still in flight.
|
|
165
|
+
*
|
|
166
|
+
* This page issues its fetches from three places that do not know about each
|
|
167
|
+
* other - the identity read, the readiness read, and one severity read per rule
|
|
168
|
+
* table - and nothing orders them. Waiting on any single one of them, which is
|
|
169
|
+
* what this file used to do, leaves the others free to land after the assertions
|
|
170
|
+
* run: see `waitForSettledPage` for what that cost.
|
|
171
|
+
*
|
|
172
|
+
* A counter over the mocks themselves is the one signal that covers all three
|
|
173
|
+
* without the test having to know which fetch each surface makes.
|
|
174
|
+
*/
|
|
175
|
+
let pendingRequestCount: number = 0;
|
|
176
|
+
|
|
177
|
+
type TrackRequestFunction = (result: unknown) => unknown;
|
|
178
|
+
|
|
179
|
+
const trackRequest: TrackRequestFunction = (result: unknown): unknown => {
|
|
180
|
+
if (!(result instanceof Promise)) {
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
pendingRequestCount++;
|
|
185
|
+
|
|
186
|
+
return result.finally((): void => {
|
|
187
|
+
pendingRequestCount--;
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
|
|
162
191
|
/*
|
|
163
192
|
* The arrow wrappers are load bearing: jest.mock is hoisted above the compiled
|
|
164
193
|
* requires, so the consts above are still in their temporal dead zone when the
|
|
@@ -169,10 +198,10 @@ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
|
169
198
|
__esModule: true,
|
|
170
199
|
default: {
|
|
171
200
|
getList: (...args: Array<any>) => {
|
|
172
|
-
return getListMock(...args);
|
|
201
|
+
return trackRequest(getListMock(...args));
|
|
173
202
|
},
|
|
174
203
|
getItem: (...args: Array<any>) => {
|
|
175
|
-
return getItemMock(...args);
|
|
204
|
+
return trackRequest(getItemMock(...args));
|
|
176
205
|
},
|
|
177
206
|
getCommonHeaders: (...args: Array<any>) => {
|
|
178
207
|
return getCommonHeadersMock(...args);
|
|
@@ -186,7 +215,7 @@ jest.mock("../../../UI/Utils/API/API", () => {
|
|
|
186
215
|
__esModule: true,
|
|
187
216
|
default: {
|
|
188
217
|
get: (...args: Array<any>) => {
|
|
189
|
-
return apiGetMock(...args);
|
|
218
|
+
return trackRequest(apiGetMock(...args));
|
|
190
219
|
},
|
|
191
220
|
/*
|
|
192
221
|
* Both spellings are used by the page and they are handed different
|
|
@@ -273,13 +302,63 @@ interface CapturedTableProps {
|
|
|
273
302
|
cardProps: { title: string; description: string };
|
|
274
303
|
}
|
|
275
304
|
|
|
305
|
+
/*
|
|
306
|
+
* One entry per MOUNTED table, holding the props it was last rendered with.
|
|
307
|
+
*
|
|
308
|
+
* This was an append-only log of every render, and that is what made this file
|
|
309
|
+
* flaky. The page draws its tables as soon as it knows who it is about and draws
|
|
310
|
+
* them again when readiness lands, so the LENGTH of a render log is a transient:
|
|
311
|
+
* it passes through 8 on the way to 16, and which of the two a test observes
|
|
312
|
+
* depends on whether the readiness read or the severity reads win a race nothing
|
|
313
|
+
* in the page orders. `waitFor(() => expect(capturedTables).toHaveLength(8))`
|
|
314
|
+
* then asserted equality against a counter that is only briefly 8 - so whenever
|
|
315
|
+
* the second render landed first, 8 never came back, and the wait burned its
|
|
316
|
+
* full timeout before failing. A different handful of tests lost that race on
|
|
317
|
+
* every run, each costing a second of wall clock on the way out.
|
|
318
|
+
*
|
|
319
|
+
* Keyed by the table's own identity, the count settles at 8 and stays there, and
|
|
320
|
+
* every test reads the props the table actually ended up with.
|
|
321
|
+
*/
|
|
276
322
|
let capturedTables: Array<CapturedTableProps> = [];
|
|
277
323
|
|
|
324
|
+
type TableIdentityFunction = (props: CapturedTableProps) => string;
|
|
325
|
+
|
|
326
|
+
/*
|
|
327
|
+
* What makes a table THAT table: the rule type and the severity band it is
|
|
328
|
+
* mounted for. Deliberately not `userPreferencesKey`, even though that is
|
|
329
|
+
* shorter and unique - that key is itself under test below, where eight tables
|
|
330
|
+
* on one route must not share one, and keying the capture on the thing being
|
|
331
|
+
* asserted would quietly turn that assertion into a tautology.
|
|
332
|
+
*/
|
|
333
|
+
const tableIdentity: TableIdentityFunction = (
|
|
334
|
+
props: CapturedTableProps,
|
|
335
|
+
): string => {
|
|
336
|
+
const severityId: ObjectID | NotificationRuleType | undefined =
|
|
337
|
+
props.query["incidentSeverityId"] ?? props.query["alertSeverityId"];
|
|
338
|
+
|
|
339
|
+
return `${String(props.query["ruleType"])}::${
|
|
340
|
+
severityId ? severityId.toString() : "no-severity"
|
|
341
|
+
}`;
|
|
342
|
+
};
|
|
343
|
+
|
|
278
344
|
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
279
345
|
return {
|
|
280
346
|
__esModule: true,
|
|
281
347
|
default: (props: CapturedTableProps) => {
|
|
282
|
-
|
|
348
|
+
const identity: string = tableIdentity(props);
|
|
349
|
+
|
|
350
|
+
const existingIndex: number = capturedTables.findIndex(
|
|
351
|
+
(candidate: CapturedTableProps): boolean => {
|
|
352
|
+
return tableIdentity(candidate) === identity;
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (existingIndex === -1) {
|
|
357
|
+
capturedTables.push(props);
|
|
358
|
+
} else {
|
|
359
|
+
capturedTables[existingIndex] = props;
|
|
360
|
+
}
|
|
361
|
+
|
|
283
362
|
return null;
|
|
284
363
|
},
|
|
285
364
|
};
|
|
@@ -612,11 +691,67 @@ const pageProps: PageComponentProps = {
|
|
|
612
691
|
hasPaymentMethod: false,
|
|
613
692
|
};
|
|
614
693
|
|
|
694
|
+
const ADMIN_TABLE_COUNT: number = 8;
|
|
695
|
+
const SELF_SERVE_TABLE_COUNT: number = 2;
|
|
696
|
+
|
|
697
|
+
type WaitForSettledPageFunction = (expectedTableCount: number) => Promise<void>;
|
|
698
|
+
|
|
699
|
+
/*
|
|
700
|
+
* Wait until the page has stopped moving, rather than until one thing about it
|
|
701
|
+
* has happened.
|
|
702
|
+
*
|
|
703
|
+
* Two conditions, and neither is sufficient alone. `pendingRequestCount` reaches
|
|
704
|
+
* zero at the start, before anything has been asked for, and it dips to zero
|
|
705
|
+
* between the identity read finishing and the severity reads it unblocks being
|
|
706
|
+
* issued. The table count only reaches its total once every rule table has its
|
|
707
|
+
* severity list. Together they describe exactly one moment: every read this page
|
|
708
|
+
* makes has landed and every table it draws has been drawn with the result.
|
|
709
|
+
*
|
|
710
|
+
* The old wait was `expect(apiGetMock).toHaveBeenCalled()` - satisfied the
|
|
711
|
+
* instant the readiness request was ISSUED, with its response still in flight -
|
|
712
|
+
* followed by an exact-length check on a growing render log. Assertions
|
|
713
|
+
* therefore ran against whatever the page happened to look like mid-load, which
|
|
714
|
+
* is why several of them had to reach for the most recent render by hand, and
|
|
715
|
+
* why the ones that did not were the ones that failed on a busy machine.
|
|
716
|
+
*
|
|
717
|
+
* Both conditions are stable once true: nothing here issues a fetch after the
|
|
718
|
+
* tables are up, and the capture is keyed by table identity, so the count stops
|
|
719
|
+
* at its total instead of climbing past it.
|
|
720
|
+
*/
|
|
721
|
+
const waitForSettledPage: WaitForSettledPageFunction = async (
|
|
722
|
+
expectedTableCount: number,
|
|
723
|
+
): Promise<void> => {
|
|
724
|
+
await waitFor(
|
|
725
|
+
(): void => {
|
|
726
|
+
expect(pendingRequestCount).toBe(0);
|
|
727
|
+
expect(capturedTables).toHaveLength(expectedTableCount);
|
|
728
|
+
},
|
|
729
|
+
/*
|
|
730
|
+
* Everything here resolves from a mock, so a healthy page settles in
|
|
731
|
+
* milliseconds and this budget is never spent. It is above the one-second
|
|
732
|
+
* default because the runner this has to be green on is a shared CI box
|
|
733
|
+
* running the whole Common suite in band, and a page that settles slowly
|
|
734
|
+
* because the machine is busy is not a page that is broken. It stays below
|
|
735
|
+
* Jest's own five-second test timeout so a page that really is stuck still
|
|
736
|
+
* fails here, naming the count it reached, rather than as a bare timeout.
|
|
737
|
+
*/
|
|
738
|
+
{ timeout: 4000 },
|
|
739
|
+
);
|
|
740
|
+
};
|
|
741
|
+
|
|
615
742
|
type RenderAdminPageFunction = (
|
|
616
743
|
targetUserId?: string | undefined,
|
|
617
744
|
) => Promise<HTMLElement>;
|
|
618
745
|
|
|
619
746
|
/*
|
|
747
|
+
* Render the admin page and return once it has finished loading.
|
|
748
|
+
*
|
|
749
|
+
* There is deliberately no shorter variant that returns earlier. This used to be
|
|
750
|
+
* two helpers - one that waited for the readiness request to be issued and one
|
|
751
|
+
* that waited for the tables on top of it - and every test that reached for the
|
|
752
|
+
* first was reading a page still mid-load. There is nothing here worth asserting
|
|
753
|
+
* before the page has settled, so the wait is not optional.
|
|
754
|
+
*
|
|
620
755
|
* The user id is read off the URL at offset 1 - this is a sub-page, so the last
|
|
621
756
|
* segment is "notification-rules" and the id is the one before it. Driving that
|
|
622
757
|
* through a real jsdom URL rather than a stub is deliberate: reading the wrong
|
|
@@ -634,26 +769,11 @@ const renderAdminPage: RenderAdminPageFunction = async (
|
|
|
634
769
|
|
|
635
770
|
const { container } = render(<UserViewNotificationRules {...pageProps} />);
|
|
636
771
|
|
|
637
|
-
await
|
|
638
|
-
expect(apiGetMock).toHaveBeenCalled();
|
|
639
|
-
});
|
|
772
|
+
await waitForSettledPage(ADMIN_TABLE_COUNT);
|
|
640
773
|
|
|
641
774
|
return container;
|
|
642
775
|
};
|
|
643
776
|
|
|
644
|
-
type RenderAdminPageWithTablesFunction = () => Promise<HTMLElement>;
|
|
645
|
-
|
|
646
|
-
const renderAdminPageWithTables: RenderAdminPageWithTablesFunction =
|
|
647
|
-
async (): Promise<HTMLElement> => {
|
|
648
|
-
const container: HTMLElement = await renderAdminPage();
|
|
649
|
-
|
|
650
|
-
await waitFor((): void => {
|
|
651
|
-
expect(capturedTables).toHaveLength(8);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
return container;
|
|
655
|
-
};
|
|
656
|
-
|
|
657
777
|
type RenderSelfServePageFunction = (
|
|
658
778
|
Page: FunctionComponent<PageComponentProps>,
|
|
659
779
|
) => Promise<void>;
|
|
@@ -663,9 +783,7 @@ const renderSelfServePage: RenderSelfServePageFunction = async (
|
|
|
663
783
|
): Promise<void> => {
|
|
664
784
|
render(<Page {...pageProps} />);
|
|
665
785
|
|
|
666
|
-
await
|
|
667
|
-
expect(capturedTables).toHaveLength(2);
|
|
668
|
-
});
|
|
786
|
+
await waitForSettledPage(SELF_SERVE_TABLE_COUNT);
|
|
669
787
|
};
|
|
670
788
|
|
|
671
789
|
type TablesForFunction = (
|
|
@@ -699,32 +817,6 @@ const getCapturedTables: GetCapturedTablesFunction =
|
|
|
699
817
|
return capturedTables;
|
|
700
818
|
};
|
|
701
819
|
|
|
702
|
-
/*
|
|
703
|
-
* The most recent props each table was rendered with, keyed by the preferences
|
|
704
|
-
* key that already uniquely identifies a table on this page.
|
|
705
|
-
*
|
|
706
|
-
* The page renders its tables as soon as it knows WHO it is about and re-renders
|
|
707
|
-
* them when readiness lands, so `capturedTables` legitimately holds two entries
|
|
708
|
-
* per table. Assertions about copy that depends on the readiness payload - the
|
|
709
|
-
* empty-state sentence, which changes sign with the project's fallback switch -
|
|
710
|
-
* have to read the settled render rather than whichever one happened to be
|
|
711
|
-
* first.
|
|
712
|
-
*/
|
|
713
|
-
type LatestTablesFunction = () => Array<CapturedTableProps>;
|
|
714
|
-
|
|
715
|
-
const latestTables: LatestTablesFunction = (): Array<CapturedTableProps> => {
|
|
716
|
-
const byKey: Map<string, CapturedTableProps> = new Map<
|
|
717
|
-
string,
|
|
718
|
-
CapturedTableProps
|
|
719
|
-
>();
|
|
720
|
-
|
|
721
|
-
for (const table of capturedTables) {
|
|
722
|
-
byKey.set(table.userPreferencesKey, table);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
return [...byKey.values()];
|
|
726
|
-
};
|
|
727
|
-
|
|
728
820
|
type FormFieldForFunction = (
|
|
729
821
|
table: CapturedTableProps,
|
|
730
822
|
key: string,
|
|
@@ -975,6 +1067,7 @@ const NO_ITEMS_MESSAGE: string =
|
|
|
975
1067
|
|
|
976
1068
|
beforeEach((): void => {
|
|
977
1069
|
capturedTables = [];
|
|
1070
|
+
pendingRequestCount = 0;
|
|
978
1071
|
|
|
979
1072
|
getListMock.mockReset();
|
|
980
1073
|
getItemMock.mockReset();
|
|
@@ -998,15 +1091,234 @@ beforeEach((): void => {
|
|
|
998
1091
|
.mockReturnValue([Permission.ProjectAdmin]);
|
|
999
1092
|
});
|
|
1000
1093
|
|
|
1001
|
-
afterEach((): void => {
|
|
1094
|
+
afterEach(async (): Promise<void> => {
|
|
1002
1095
|
cleanup();
|
|
1096
|
+
|
|
1097
|
+
/*
|
|
1098
|
+
* Unmounting stops the RENDERING, not the fetching. A rule table that belongs
|
|
1099
|
+
* to the viewer loads the seven notification-method models one after another,
|
|
1100
|
+
* so a test that reads its own page leaves a chain of awaits running after its
|
|
1101
|
+
* last assertion - and every one of those calls is recorded on the same
|
|
1102
|
+
* `getListMock` the next test resets and then asserts against.
|
|
1103
|
+
*
|
|
1104
|
+
* That is how "never reads a notification method model" comes to fail on a
|
|
1105
|
+
* page that never asked for one: the request was the previous test's. Letting
|
|
1106
|
+
* the chain finish here, while the mocks are still the ones that started it,
|
|
1107
|
+
* keeps each test's call log its own.
|
|
1108
|
+
*/
|
|
1109
|
+
for (
|
|
1110
|
+
let attempt: number = 0;
|
|
1111
|
+
pendingRequestCount > 0 && attempt < 100;
|
|
1112
|
+
attempt++
|
|
1113
|
+
) {
|
|
1114
|
+
await new Promise<void>((resolve: () => void): void => {
|
|
1115
|
+
setTimeout(resolve, 0);
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1003
1119
|
jest.restoreAllMocks();
|
|
1004
1120
|
});
|
|
1005
1121
|
|
|
1122
|
+
/*
|
|
1123
|
+
* ------------------------------------------------------------------ *
|
|
1124
|
+
* The harness, pinned
|
|
1125
|
+
* ------------------------------------------------------------------
|
|
1126
|
+
*
|
|
1127
|
+
* Everything below this block reads the page through `capturedTables`, and for
|
|
1128
|
+
* a while what it read depended on the weather.
|
|
1129
|
+
*
|
|
1130
|
+
* This page issues three kinds of read that nothing sequences: the identity, the
|
|
1131
|
+
* readiness summary, and one severity list per rule table. The tables appear
|
|
1132
|
+
* when the severity lists land; their props change again when readiness lands.
|
|
1133
|
+
* The file used to wait for the readiness request to be ISSUED and then for the
|
|
1134
|
+
* render log to be exactly eight entries long - a value that is true on the way
|
|
1135
|
+
* past, not at the end - so on a loaded machine the second redraw got there
|
|
1136
|
+
* first, eight never came back, and the wait spent its full second before
|
|
1137
|
+
* failing. Whichever tests happened to lose that race were the ones that went
|
|
1138
|
+
* red, which is why the failing set moved around between runs.
|
|
1139
|
+
*
|
|
1140
|
+
* These tests force both orderings on purpose rather than waiting to be unlucky
|
|
1141
|
+
* in CI. They are about the harness, not about the product, and they are here so
|
|
1142
|
+
* that the next person to reach for a render count in this file finds out
|
|
1143
|
+
* immediately.
|
|
1144
|
+
*/
|
|
1145
|
+
describe("the page is read at rest, whichever order its fetches land in", () => {
|
|
1146
|
+
const SLOW_FETCH_MS: number = 25;
|
|
1147
|
+
|
|
1148
|
+
type AfterEverythingElseFunction = (value: unknown) => Promise<unknown>;
|
|
1149
|
+
|
|
1150
|
+
const afterEverythingElse: AfterEverythingElseFunction = (
|
|
1151
|
+
value: unknown,
|
|
1152
|
+
): Promise<unknown> => {
|
|
1153
|
+
return new Promise((resolve: (settled: unknown) => void): void => {
|
|
1154
|
+
setTimeout((): void => {
|
|
1155
|
+
resolve(value);
|
|
1156
|
+
}, SLOW_FETCH_MS);
|
|
1157
|
+
});
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
type DelayReadinessFunction = () => void;
|
|
1161
|
+
|
|
1162
|
+
/* Readiness lands last: the tables are drawn, then redrawn with the methods. */
|
|
1163
|
+
const readinessLandsAfterTheTables: DelayReadinessFunction = (): void => {
|
|
1164
|
+
const response: HTTPResponse<JSONObject> = new HTTPResponse<JSONObject>(
|
|
1165
|
+
200,
|
|
1166
|
+
readinessJson(),
|
|
1167
|
+
{},
|
|
1168
|
+
);
|
|
1169
|
+
|
|
1170
|
+
apiGetMock.mockImplementation((): Promise<unknown> => {
|
|
1171
|
+
return afterEverythingElse(response);
|
|
1172
|
+
});
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
type DelaySeveritiesFunction = () => void;
|
|
1176
|
+
|
|
1177
|
+
/* And the other way round: readiness is sitting there before a table exists. */
|
|
1178
|
+
const readinessLandsBeforeTheTables: DelaySeveritiesFunction = (): void => {
|
|
1179
|
+
const immediate: (data: any) => any =
|
|
1180
|
+
getListMock.getMockImplementation() as (data: any) => any;
|
|
1181
|
+
|
|
1182
|
+
getListMock.mockImplementation((data: any): unknown => {
|
|
1183
|
+
const result: Promise<unknown> = immediate(data) as Promise<unknown>;
|
|
1184
|
+
|
|
1185
|
+
if (
|
|
1186
|
+
data.modelType === IncidentSeverity ||
|
|
1187
|
+
data.modelType === AlertSeverity
|
|
1188
|
+
) {
|
|
1189
|
+
return result.then((value: unknown): Promise<unknown> => {
|
|
1190
|
+
return afterEverythingElse(value);
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
return result;
|
|
1195
|
+
});
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
type AssertSettledFunction = () => void;
|
|
1199
|
+
|
|
1200
|
+
/*
|
|
1201
|
+
* What "settled" has to mean, spelled out: not merely eight tables, but eight
|
|
1202
|
+
* tables holding the props they end up with. The two below are the props that
|
|
1203
|
+
* only exist once readiness and the project setting have both landed, so a
|
|
1204
|
+
* table captured mid-load fails them - which is exactly what several tests in
|
|
1205
|
+
* this file were quietly doing whenever they read `capturedTables[0]`.
|
|
1206
|
+
*/
|
|
1207
|
+
const assertEveryTableIsSettled: AssertSettledFunction = (): void => {
|
|
1208
|
+
expect(capturedTables).toHaveLength(ADMIN_TABLE_COUNT);
|
|
1209
|
+
|
|
1210
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1211
|
+
expect(methodOptionsFor(table)).toEqual([
|
|
1212
|
+
{ label: `Email: ${MASKED_EMAIL}`, value: EMAIL_METHOD_ID },
|
|
1213
|
+
{ label: `SMS: ${MASKED_PHONE}`, value: SMS_METHOD_ID },
|
|
1214
|
+
]);
|
|
1215
|
+
|
|
1216
|
+
expect(table.noItemsMessage).toContain(
|
|
1217
|
+
"fall back to whatever verified method they have",
|
|
1218
|
+
);
|
|
1219
|
+
expect(table.noItemsMessage).not.toContain("could not be read");
|
|
1220
|
+
});
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
test("captures one entry per mounted table, not one per redraw", async () => {
|
|
1224
|
+
await renderAdminPage();
|
|
1225
|
+
|
|
1226
|
+
const identities: Set<string> = new Set<string>(
|
|
1227
|
+
capturedTables.map((table: CapturedTableProps): string => {
|
|
1228
|
+
return `${String(table.query["ruleType"])}::${table.userPreferencesKey}`;
|
|
1229
|
+
}),
|
|
1230
|
+
);
|
|
1231
|
+
|
|
1232
|
+
/*
|
|
1233
|
+
* Four rule types, two severity bands each. If this ever reads more than the
|
|
1234
|
+
* number of tables actually on the route, the capture has gone back to being
|
|
1235
|
+
* a render log and every length assertion in this file is a race again.
|
|
1236
|
+
*/
|
|
1237
|
+
expect(identities.size).toBe(ADMIN_TABLE_COUNT);
|
|
1238
|
+
expect(capturedTables).toHaveLength(ADMIN_TABLE_COUNT);
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
test("a redraw replaces what a table captured rather than appending to it", async () => {
|
|
1242
|
+
await renderAdminPage();
|
|
1243
|
+
|
|
1244
|
+
const before: CapturedTableProps = capturedTables[0]!;
|
|
1245
|
+
|
|
1246
|
+
/*
|
|
1247
|
+
* Recheck refetches readiness and redraws all eight tables - the same second
|
|
1248
|
+
* pass that used to arrive uninvited and break the count. Driving it
|
|
1249
|
+
* deliberately is the cheapest way to prove the capture survives one.
|
|
1250
|
+
*/
|
|
1251
|
+
fireEvent.click(screen.getByRole("button", { name: /Recheck/i }));
|
|
1252
|
+
|
|
1253
|
+
await waitForSettledPage(ADMIN_TABLE_COUNT);
|
|
1254
|
+
|
|
1255
|
+
expect(capturedTables).toHaveLength(ADMIN_TABLE_COUNT);
|
|
1256
|
+
expect(capturedTables[0]).not.toBe(before);
|
|
1257
|
+
expect(capturedTables[0]!.userPreferencesKey).toBe(
|
|
1258
|
+
before.userPreferencesKey,
|
|
1259
|
+
);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
test("hands the test a page with nothing still in flight", async () => {
|
|
1263
|
+
await renderAdminPage();
|
|
1264
|
+
|
|
1265
|
+
/*
|
|
1266
|
+
* The old wait was satisfied by the readiness request being ISSUED. A test
|
|
1267
|
+
* that then asserted on readiness-derived copy was asserting against a
|
|
1268
|
+
* response that had not arrived.
|
|
1269
|
+
*/
|
|
1270
|
+
expect(pendingRequestCount).toBe(0);
|
|
1271
|
+
assertEveryTableIsSettled();
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
test("reads the same page when readiness lands after the rule tables", async () => {
|
|
1275
|
+
readinessLandsAfterTheTables();
|
|
1276
|
+
|
|
1277
|
+
await renderAdminPage();
|
|
1278
|
+
|
|
1279
|
+
expect(pendingRequestCount).toBe(0);
|
|
1280
|
+
assertEveryTableIsSettled();
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
test("reads the same page when readiness lands before the rule tables", async () => {
|
|
1284
|
+
readinessLandsBeforeTheTables();
|
|
1285
|
+
|
|
1286
|
+
await renderAdminPage();
|
|
1287
|
+
|
|
1288
|
+
expect(pendingRequestCount).toBe(0);
|
|
1289
|
+
assertEveryTableIsSettled();
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
test("waits out the seven method reads a self-serve page makes for its owner", async () => {
|
|
1293
|
+
// A self-serve page is always the viewer's own, so this is the owner path.
|
|
1294
|
+
await renderSelfServePage(IncidentOnCallRules);
|
|
1295
|
+
|
|
1296
|
+
/*
|
|
1297
|
+
* A rule table belonging to the viewer loads the seven notification-method
|
|
1298
|
+
* models one after another, and unmounting the page does not stop that
|
|
1299
|
+
* chain - it stops the rendering. Any of those seven still running when the
|
|
1300
|
+
* next test resets `getListMock` is recorded as that test's request, which
|
|
1301
|
+
* is how "never reads a notification method model" comes to fail on a page
|
|
1302
|
+
* that never asked for one.
|
|
1303
|
+
*
|
|
1304
|
+
* Returning only once the chain is done is what keeps each test's call log
|
|
1305
|
+
* its own; the drain in `afterEach` is the same guarantee for a test that
|
|
1306
|
+
* renders without going through these helpers.
|
|
1307
|
+
*/
|
|
1308
|
+
expect(pendingRequestCount).toBe(0);
|
|
1309
|
+
|
|
1310
|
+
const requested: Array<unknown> = requestedModelTypes();
|
|
1311
|
+
|
|
1312
|
+
for (const methodModel of NOTIFICATION_METHOD_MODELS) {
|
|
1313
|
+
expect(requested).toContain(methodModel);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1006
1318
|
describe("the shared rules table, as the admin page wires it", () => {
|
|
1007
1319
|
for (const testCase of RULE_TYPE_CASES) {
|
|
1008
1320
|
test(`${testCase.label} rules are banded by their own severity model and foreign key column`, async () => {
|
|
1009
|
-
await
|
|
1321
|
+
await renderAdminPage();
|
|
1010
1322
|
|
|
1011
1323
|
const tables: Array<CapturedTableProps> = tablesFor(testCase.ruleType);
|
|
1012
1324
|
|
|
@@ -1038,7 +1350,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1038
1350
|
});
|
|
1039
1351
|
|
|
1040
1352
|
test(`${testCase.label} rules stamp the same column on a newly created rule`, async () => {
|
|
1041
|
-
await
|
|
1353
|
+
await renderAdminPage();
|
|
1042
1354
|
|
|
1043
1355
|
const table: CapturedTableProps = tablesFor(testCase.ruleType)[0]!;
|
|
1044
1356
|
|
|
@@ -1066,7 +1378,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1066
1378
|
}
|
|
1067
1379
|
|
|
1068
1380
|
test("every table is scoped to the user in the URL, never to the signed-in admin", async () => {
|
|
1069
|
-
await
|
|
1381
|
+
await renderAdminPage();
|
|
1070
1382
|
|
|
1071
1383
|
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1072
1384
|
expect(table.query["userId"]?.toString()).toBe(TARGET_USER_ID_STRING);
|
|
@@ -1091,7 +1403,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1091
1403
|
});
|
|
1092
1404
|
|
|
1093
1405
|
test("never reads a notification method model", async () => {
|
|
1094
|
-
await
|
|
1406
|
+
await renderAdminPage();
|
|
1095
1407
|
|
|
1096
1408
|
/*
|
|
1097
1409
|
* The redesign, in one assertion.
|
|
@@ -1121,7 +1433,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1121
1433
|
});
|
|
1122
1434
|
|
|
1123
1435
|
test("asks the server for no column of a method model either", async () => {
|
|
1124
|
-
await
|
|
1436
|
+
await renderAdminPage();
|
|
1125
1437
|
|
|
1126
1438
|
/*
|
|
1127
1439
|
* The same rule as the test above, applied to the OTHER way this page could
|
|
@@ -1143,7 +1455,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1143
1455
|
* So the readiness card masked every identifier and the table one card below
|
|
1144
1456
|
* printed them in full.
|
|
1145
1457
|
*/
|
|
1146
|
-
for (const table of
|
|
1458
|
+
for (const table of capturedTables) {
|
|
1147
1459
|
const projection: Array<string> = Object.keys(table.selectMoreFields);
|
|
1148
1460
|
|
|
1149
1461
|
for (const column of table.columns) {
|
|
@@ -1165,10 +1477,10 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1165
1477
|
});
|
|
1166
1478
|
|
|
1167
1479
|
test("labels a listed rule with the masked identifier, not the raw one", async () => {
|
|
1168
|
-
await
|
|
1480
|
+
await renderAdminPage();
|
|
1169
1481
|
|
|
1170
1482
|
const methodColumn: CapturedColumn = columnNamed(
|
|
1171
|
-
|
|
1483
|
+
capturedTables[0]!,
|
|
1172
1484
|
"Notification Method",
|
|
1173
1485
|
);
|
|
1174
1486
|
|
|
@@ -1189,9 +1501,9 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1189
1501
|
});
|
|
1190
1502
|
|
|
1191
1503
|
test("builds the rule form's method dropdown out of the masked readiness payload", async () => {
|
|
1192
|
-
await
|
|
1504
|
+
await renderAdminPage();
|
|
1193
1505
|
|
|
1194
|
-
|
|
1506
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1195
1507
|
/*
|
|
1196
1508
|
* Masked label, real foreign key. The admin picks "SMS: +1 ••• ••• 4821"
|
|
1197
1509
|
* and the form submits `userSmsId`, so the page can point a rule at a
|
|
@@ -1205,9 +1517,9 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1205
1517
|
});
|
|
1206
1518
|
|
|
1207
1519
|
test("the dropdown withholds the unverified method", async () => {
|
|
1208
|
-
await
|
|
1520
|
+
await renderAdminPage();
|
|
1209
1521
|
|
|
1210
|
-
const values: Array<string> = methodOptionsFor(
|
|
1522
|
+
const values: Array<string> = methodOptionsFor(capturedTables[0]!).map(
|
|
1211
1523
|
(option: { label: string; value: string }) => {
|
|
1212
1524
|
return option.value;
|
|
1213
1525
|
},
|
|
@@ -1223,10 +1535,10 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1223
1535
|
});
|
|
1224
1536
|
|
|
1225
1537
|
test("a rule created from that dropdown points at the method's own column", async () => {
|
|
1226
|
-
await
|
|
1538
|
+
await renderAdminPage();
|
|
1227
1539
|
|
|
1228
1540
|
const created: UserNotificationRule =
|
|
1229
|
-
await
|
|
1541
|
+
await capturedTables[0]!.onBeforeCreate(new UserNotificationRule(), {
|
|
1230
1542
|
notificationMethod: SMS_METHOD_ID,
|
|
1231
1543
|
});
|
|
1232
1544
|
|
|
@@ -1246,7 +1558,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1246
1558
|
new HTTPErrorResponse(500, { message: "readiness is down" }, {}) as never,
|
|
1247
1559
|
);
|
|
1248
1560
|
|
|
1249
|
-
await
|
|
1561
|
+
await renderAdminPage();
|
|
1250
1562
|
|
|
1251
1563
|
/*
|
|
1252
1564
|
* The failure mode this seam had to be designed around. Readiness is the
|
|
@@ -1259,14 +1571,14 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1259
1571
|
expect(requestedModelTypes()).not.toContain(methodModel);
|
|
1260
1572
|
}
|
|
1261
1573
|
|
|
1262
|
-
|
|
1574
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1263
1575
|
expect(methodOptionsFor(table)).toEqual([]);
|
|
1264
1576
|
expect(table.isCreateable).toBe(true);
|
|
1265
1577
|
});
|
|
1266
1578
|
});
|
|
1267
1579
|
|
|
1268
1580
|
test("keeps the severity id in every preferences key and namespaces the admin surface away from user settings", async () => {
|
|
1269
|
-
await
|
|
1581
|
+
await renderAdminPage();
|
|
1270
1582
|
|
|
1271
1583
|
const adminKeys: Array<string> = capturedTables.map(
|
|
1272
1584
|
(table: CapturedTableProps) => {
|
|
@@ -1320,7 +1632,7 @@ describe("the shared rules table, as the admin page wires it", () => {
|
|
|
1320
1632
|
});
|
|
1321
1633
|
|
|
1322
1634
|
test("never mounts a table over a notification method model", async () => {
|
|
1323
|
-
await
|
|
1635
|
+
await renderAdminPage();
|
|
1324
1636
|
|
|
1325
1637
|
/*
|
|
1326
1638
|
* The structural form of "an admin may not add a method for somebody else".
|
|
@@ -1466,7 +1778,7 @@ describe("the self-serve settings pages are unchanged by the extraction", () =>
|
|
|
1466
1778
|
|
|
1467
1779
|
describe("the on-behalf-of banner", () => {
|
|
1468
1780
|
test("names the person being edited and says the change is recorded and disclosed", async () => {
|
|
1469
|
-
const container: HTMLElement = await
|
|
1781
|
+
const container: HTMLElement = await renderAdminPage();
|
|
1470
1782
|
|
|
1471
1783
|
expect(container.textContent).toContain(
|
|
1472
1784
|
`You are editing on behalf of ${TARGET_USER_NAME}`,
|
|
@@ -1489,7 +1801,7 @@ describe("the on-behalf-of banner", () => {
|
|
|
1489
1801
|
.spyOn(PermissionUtil, "getAllPermissions")
|
|
1490
1802
|
.mockReturnValue([Permission.ReadProjectUserNotificationRule]);
|
|
1491
1803
|
|
|
1492
|
-
const container: HTMLElement = await
|
|
1804
|
+
const container: HTMLElement = await renderAdminPage();
|
|
1493
1805
|
|
|
1494
1806
|
expect(container.textContent).toContain(
|
|
1495
1807
|
`You are viewing ${TARGET_USER_NAME}`,
|
|
@@ -1523,7 +1835,7 @@ describe("the on-behalf-of banner", () => {
|
|
|
1523
1835
|
.spyOn(PermissionUtil, "getAllPermissions")
|
|
1524
1836
|
.mockReturnValue([Permission.ProjectOwner]);
|
|
1525
1837
|
|
|
1526
|
-
await
|
|
1838
|
+
await renderAdminPage();
|
|
1527
1839
|
|
|
1528
1840
|
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1529
1841
|
expect(table.isCreateable).toBe(true);
|
|
@@ -1537,7 +1849,7 @@ describe("the on-behalf-of banner", () => {
|
|
|
1537
1849
|
.mockReturnValue(new ObjectID(TARGET_USER_ID_STRING));
|
|
1538
1850
|
jest.spyOn(PermissionUtil, "getAllPermissions").mockReturnValue([]);
|
|
1539
1851
|
|
|
1540
|
-
const container: HTMLElement = await
|
|
1852
|
+
const container: HTMLElement = await renderAdminPage();
|
|
1541
1853
|
|
|
1542
1854
|
expect(container.textContent).toContain(
|
|
1543
1855
|
"These are your own notification rules.",
|
|
@@ -1584,7 +1896,7 @@ describe("the on-behalf-of banner", () => {
|
|
|
1584
1896
|
new HTTPErrorResponse(500, { message: "readiness is down" }, {}) as never,
|
|
1585
1897
|
);
|
|
1586
1898
|
|
|
1587
|
-
const container: HTMLElement = await
|
|
1899
|
+
const container: HTMLElement = await renderAdminPage();
|
|
1588
1900
|
|
|
1589
1901
|
expect(container.textContent).toContain("readiness is down");
|
|
1590
1902
|
expect(container.textContent).toContain(
|
|
@@ -1617,7 +1929,7 @@ describe("the on-behalf-of banner", () => {
|
|
|
1617
1929
|
|
|
1618
1930
|
describe("notification methods are read-only", () => {
|
|
1619
1931
|
test("lists each method masked, with its verification state", async () => {
|
|
1620
|
-
await
|
|
1932
|
+
await renderAdminPage();
|
|
1621
1933
|
|
|
1622
1934
|
const methods: HTMLElement = cardNamed("Notification methods");
|
|
1623
1935
|
|
|
@@ -1634,7 +1946,7 @@ describe("notification methods are read-only", () => {
|
|
|
1634
1946
|
});
|
|
1635
1947
|
|
|
1636
1948
|
test("offers no control for adding a method on the user's behalf", async () => {
|
|
1637
|
-
await
|
|
1949
|
+
await renderAdminPage();
|
|
1638
1950
|
|
|
1639
1951
|
const methods: HTMLElement = cardNamed("Notification methods");
|
|
1640
1952
|
|
|
@@ -1662,7 +1974,7 @@ describe("notification methods are read-only", () => {
|
|
|
1662
1974
|
test("the no-methods empty state offers a reminder rather than a form", async () => {
|
|
1663
1975
|
respondWithReadiness(NO_METHODS_READINESS);
|
|
1664
1976
|
|
|
1665
|
-
await
|
|
1977
|
+
await renderAdminPage();
|
|
1666
1978
|
|
|
1667
1979
|
const methods: HTMLElement = cardNamed("Notification methods");
|
|
1668
1980
|
|
|
@@ -1694,7 +2006,7 @@ describe("notification methods are read-only", () => {
|
|
|
1694
2006
|
test("the rule tables stay editable while the methods stay locked", async () => {
|
|
1695
2007
|
respondWithReadiness(NO_METHODS_READINESS);
|
|
1696
2008
|
|
|
1697
|
-
await
|
|
2009
|
+
await renderAdminPage();
|
|
1698
2010
|
|
|
1699
2011
|
/*
|
|
1700
2012
|
* The whole design of this phase in one assertion: rules are the admin's to
|
|
@@ -1727,14 +2039,14 @@ describe("notification methods are read-only", () => {
|
|
|
1727
2039
|
*/
|
|
1728
2040
|
describe("the coverage grid, now shared with the readiness page", () => {
|
|
1729
2041
|
test("draws one matrix per severity kind", async () => {
|
|
1730
|
-
await
|
|
2042
|
+
await renderAdminPage();
|
|
1731
2043
|
|
|
1732
2044
|
expect(matrixFor("Incident severities")).toBeInTheDocument();
|
|
1733
2045
|
expect(matrixFor("Alert severities")).toBeInTheDocument();
|
|
1734
2046
|
});
|
|
1735
2047
|
|
|
1736
2048
|
test("neither kind's severities appear under the other kind's columns", async () => {
|
|
1737
|
-
await
|
|
2049
|
+
await renderAdminPage();
|
|
1738
2050
|
|
|
1739
2051
|
const incidentMatrix: HTMLElement = matrixFor("Incident severities");
|
|
1740
2052
|
const alertMatrix: HTMLElement = matrixFor("Alert severities");
|
|
@@ -1755,7 +2067,7 @@ describe("the coverage grid, now shared with the readiness page", () => {
|
|
|
1755
2067
|
});
|
|
1756
2068
|
|
|
1757
2069
|
test("marks the severity with no rule as a gap rather than as covered", async () => {
|
|
1758
|
-
await
|
|
2070
|
+
await renderAdminPage();
|
|
1759
2071
|
|
|
1760
2072
|
const incidentMatrix: HTMLElement = matrixFor("Incident severities");
|
|
1761
2073
|
|
|
@@ -1770,7 +2082,7 @@ describe("the coverage grid, now shared with the readiness page", () => {
|
|
|
1770
2082
|
});
|
|
1771
2083
|
|
|
1772
2084
|
test("a severity-less rule type is listed apart from the grids", async () => {
|
|
1773
|
-
const container: HTMLElement = await
|
|
2085
|
+
const container: HTMLElement = await renderAdminPage();
|
|
1774
2086
|
|
|
1775
2087
|
/*
|
|
1776
2088
|
* WHEN_USER_GOES_ON_CALL carries no severity at all. Putting it in a
|
|
@@ -1794,7 +2106,7 @@ describe("the coverage grid, now shared with the readiness page", () => {
|
|
|
1794
2106
|
*/
|
|
1795
2107
|
describe("the rules are editable, not merely addable and removable", () => {
|
|
1796
2108
|
test("an admin gets the row editor on every table", async () => {
|
|
1797
|
-
await
|
|
2109
|
+
await renderAdminPage();
|
|
1798
2110
|
|
|
1799
2111
|
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1800
2112
|
expect(table.isEditable).toBe(true);
|
|
@@ -1807,7 +2119,7 @@ describe("the rules are editable, not merely addable and removable", () => {
|
|
|
1807
2119
|
.mockReturnValue(new ObjectID(TARGET_USER_ID_STRING));
|
|
1808
2120
|
jest.spyOn(PermissionUtil, "getAllPermissions").mockReturnValue([]);
|
|
1809
2121
|
|
|
1810
|
-
await
|
|
2122
|
+
await renderAdminPage();
|
|
1811
2123
|
|
|
1812
2124
|
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1813
2125
|
expect(table.isEditable).toBe(true);
|
|
@@ -1815,7 +2127,7 @@ describe("the rules are editable, not merely addable and removable", () => {
|
|
|
1815
2127
|
});
|
|
1816
2128
|
|
|
1817
2129
|
test("the form offers the delay, which is what the server lets an editor change", async () => {
|
|
1818
|
-
await
|
|
2130
|
+
await renderAdminPage();
|
|
1819
2131
|
|
|
1820
2132
|
const table: CapturedTableProps = capturedTables[0]!;
|
|
1821
2133
|
const delayField: CapturedFormField = formFieldFor(
|
|
@@ -1834,7 +2146,7 @@ describe("the rules are editable, not merely addable and removable", () => {
|
|
|
1834
2146
|
});
|
|
1835
2147
|
|
|
1836
2148
|
test("the form withholds the method dropdown when editing, because update cannot carry it", async () => {
|
|
1837
|
-
await
|
|
2149
|
+
await renderAdminPage();
|
|
1838
2150
|
|
|
1839
2151
|
const methodField: CapturedFormField = formFieldFor(
|
|
1840
2152
|
capturedTables[0]!,
|
|
@@ -1855,7 +2167,7 @@ describe("the rules are editable, not merely addable and removable", () => {
|
|
|
1855
2167
|
});
|
|
1856
2168
|
|
|
1857
2169
|
test("the form offers nothing the model refuses to update", async () => {
|
|
1858
|
-
await
|
|
2170
|
+
await renderAdminPage();
|
|
1859
2171
|
|
|
1860
2172
|
const keys: Array<string> = formFieldKeys(capturedTables[0]!);
|
|
1861
2173
|
|
|
@@ -1884,7 +2196,7 @@ describe("the rules are editable, not merely addable and removable", () => {
|
|
|
1884
2196
|
*/
|
|
1885
2197
|
describe("the page says the same thing at the moment of the write", () => {
|
|
1886
2198
|
test("the modal, the create button and the delete prompt all name the person", async () => {
|
|
1887
|
-
await
|
|
2199
|
+
await renderAdminPage();
|
|
1888
2200
|
|
|
1889
2201
|
/*
|
|
1890
2202
|
* ModelTable derives the modal title, the create button and the delete
|
|
@@ -1901,7 +2213,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
1901
2213
|
});
|
|
1902
2214
|
|
|
1903
2215
|
test("the form fields stop speaking in the first person", async () => {
|
|
1904
|
-
await
|
|
2216
|
+
await renderAdminPage();
|
|
1905
2217
|
|
|
1906
2218
|
const table: CapturedTableProps = capturedTables[0]!;
|
|
1907
2219
|
|
|
@@ -1927,7 +2239,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
1927
2239
|
.spyOn(UserUtil, "getUserId")
|
|
1928
2240
|
.mockReturnValue(new ObjectID(TARGET_USER_ID_STRING));
|
|
1929
2241
|
|
|
1930
|
-
await
|
|
2242
|
+
await renderAdminPage();
|
|
1931
2243
|
|
|
1932
2244
|
const table: CapturedTableProps = capturedTables[0]!;
|
|
1933
2245
|
|
|
@@ -1938,7 +2250,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
1938
2250
|
});
|
|
1939
2251
|
|
|
1940
2252
|
test("an opt-out row is labelled rather than left blank", async () => {
|
|
1941
|
-
await
|
|
2253
|
+
await renderAdminPage();
|
|
1942
2254
|
|
|
1943
2255
|
const methodColumn: CapturedColumn = columnNamed(
|
|
1944
2256
|
capturedTables[0]!,
|
|
@@ -1963,7 +2275,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
1963
2275
|
});
|
|
1964
2276
|
|
|
1965
2277
|
test("an ordinary rule is not labelled as muted", async () => {
|
|
1966
|
-
await
|
|
2278
|
+
await renderAdminPage();
|
|
1967
2279
|
|
|
1968
2280
|
const methodColumn: CapturedColumn = columnNamed(
|
|
1969
2281
|
capturedTables[0]!,
|
|
@@ -1979,7 +2291,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
1979
2291
|
});
|
|
1980
2292
|
|
|
1981
2293
|
test("the opt-out row is selected for, or the label could never fire", async () => {
|
|
1982
|
-
await
|
|
2294
|
+
await renderAdminPage();
|
|
1983
2295
|
|
|
1984
2296
|
/*
|
|
1985
2297
|
* The cell can only branch on a column the table actually asked the API
|
|
@@ -1993,9 +2305,9 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
1993
2305
|
});
|
|
1994
2306
|
|
|
1995
2307
|
test("the empty state promises a fallback only where the project has one", async () => {
|
|
1996
|
-
await
|
|
2308
|
+
await renderAdminPage();
|
|
1997
2309
|
|
|
1998
|
-
|
|
2310
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
1999
2311
|
expect(table.noItemsMessage).toContain(
|
|
2000
2312
|
"fall back to whatever verified method they have",
|
|
2001
2313
|
);
|
|
@@ -2007,7 +2319,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
2007
2319
|
project.disableOnCallNotificationFallback = true;
|
|
2008
2320
|
getItemMock.mockResolvedValue(project as never);
|
|
2009
2321
|
|
|
2010
|
-
await
|
|
2322
|
+
await renderAdminPage();
|
|
2011
2323
|
|
|
2012
2324
|
/*
|
|
2013
2325
|
* The same hole means two different things depending on one project
|
|
@@ -2016,11 +2328,9 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
2016
2328
|
* on one screen. With the switch off a missing rule is not a late page, it
|
|
2017
2329
|
* is no page.
|
|
2018
2330
|
*/
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
expect(table.noItemsMessage).not.toContain("fall back to whatever");
|
|
2023
|
-
});
|
|
2331
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
2332
|
+
expect(table.noItemsMessage).toContain("dropped");
|
|
2333
|
+
expect(table.noItemsMessage).not.toContain("fall back to whatever");
|
|
2024
2334
|
});
|
|
2025
2335
|
});
|
|
2026
2336
|
|
|
@@ -2029,7 +2339,7 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
2029
2339
|
new HTTPErrorResponse(500, { message: "readiness is down" }, {}) as never,
|
|
2030
2340
|
);
|
|
2031
2341
|
|
|
2032
|
-
await
|
|
2342
|
+
await renderAdminPage();
|
|
2033
2343
|
|
|
2034
2344
|
/*
|
|
2035
2345
|
* A failed read is not a project with the fallback off. Saying "your pages
|
|
@@ -2037,12 +2347,10 @@ describe("the page says the same thing at the moment of the write", () => {
|
|
|
2037
2347
|
* claim, and saying they fall back is the comforting version of the same
|
|
2038
2348
|
* mistake.
|
|
2039
2349
|
*/
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
expect(table.noItemsMessage).not.toContain("fall back to whatever");
|
|
2045
|
-
});
|
|
2350
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
2351
|
+
expect(table.noItemsMessage).toContain("could not be read");
|
|
2352
|
+
expect(table.noItemsMessage).not.toContain("dropped");
|
|
2353
|
+
expect(table.noItemsMessage).not.toContain("fall back to whatever");
|
|
2046
2354
|
});
|
|
2047
2355
|
});
|
|
2048
2356
|
});
|
|
@@ -2070,7 +2378,7 @@ describe("no unmasked identifier reaches the DOM", () => {
|
|
|
2070
2378
|
};
|
|
2071
2379
|
|
|
2072
2380
|
test("the admin page renders masked identifiers only", async () => {
|
|
2073
|
-
const container: HTMLElement = await
|
|
2381
|
+
const container: HTMLElement = await renderAdminPage();
|
|
2074
2382
|
|
|
2075
2383
|
// The masked forms are present, so this is not passing by rendering nothing.
|
|
2076
2384
|
expect(container.textContent).toContain(MASKED_EMAIL);
|
|
@@ -2082,7 +2390,7 @@ describe("no unmasked identifier reaches the DOM", () => {
|
|
|
2082
2390
|
test("an unreachable user's empty state leaks nothing either", async () => {
|
|
2083
2391
|
respondWithReadiness(NO_METHODS_READINESS);
|
|
2084
2392
|
|
|
2085
|
-
const container: HTMLElement = await
|
|
2393
|
+
const container: HTMLElement = await renderAdminPage();
|
|
2086
2394
|
|
|
2087
2395
|
expect(container.textContent).toContain(
|
|
2088
2396
|
"has no notification methods at all",
|
|
@@ -2098,11 +2406,11 @@ describe("no unmasked identifier reaches the DOM", () => {
|
|
|
2098
2406
|
* they are checked at the prop.
|
|
2099
2407
|
*/
|
|
2100
2408
|
test("no dropdown label carries a raw identifier", async () => {
|
|
2101
|
-
await
|
|
2409
|
+
await renderAdminPage();
|
|
2102
2410
|
|
|
2103
2411
|
const labels: Array<string> = [];
|
|
2104
2412
|
|
|
2105
|
-
|
|
2413
|
+
capturedTables.forEach((table: CapturedTableProps) => {
|
|
2106
2414
|
methodOptionsFor(table).forEach(
|
|
2107
2415
|
(option: { label: string; value: string }) => {
|
|
2108
2416
|
labels.push(option.label);
|
|
@@ -2133,7 +2441,7 @@ describe("no unmasked identifier reaches the DOM", () => {
|
|
|
2133
2441
|
test("the mail draft carries the login address and no method identifier", async () => {
|
|
2134
2442
|
respondWithReadiness(NO_METHODS_READINESS);
|
|
2135
2443
|
|
|
2136
|
-
await
|
|
2444
|
+
await renderAdminPage();
|
|
2137
2445
|
|
|
2138
2446
|
const href: string = mailtoHrefIn(cardNamed("Notification methods"));
|
|
2139
2447
|
|