@oneuptime/common 12.0.34 → 13.0.0
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/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.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/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
act,
|
|
4
|
+
cleanup,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from "@testing-library/react";
|
|
10
|
+
import React from "react";
|
|
11
|
+
import EmailPreferences from "../../../../App/FeatureSet/Dashboard/src/Pages/UserSettings/EmailPreferences";
|
|
12
|
+
import EmailNoiseCard from "../../../../App/FeatureSet/Dashboard/src/Components/EmailPreferences/EmailNoiseCard";
|
|
13
|
+
import UserNotificationEmailRollupSetting from "../../../Models/DatabaseModels/UserNotificationEmailRollupSetting";
|
|
14
|
+
import Route from "../../../Types/API/Route";
|
|
15
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
16
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
17
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
18
|
+
import API from "../../../UI/Utils/API/API";
|
|
19
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
20
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
21
|
+
import User from "../../../UI/Utils/User";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* User Settings > Email Preferences: the two controls that change how MUCH
|
|
25
|
+
* email a project sends one person, rendered for real.
|
|
26
|
+
*
|
|
27
|
+
* Both used to live above the tabs on Notification Settings, where they were
|
|
28
|
+
* read as settings for whichever tab happened to be open. Neither is a
|
|
29
|
+
* per-event choice, so neither can be a row in that matrix.
|
|
30
|
+
*
|
|
31
|
+
* What can go wrong here is quiet in every case, which is why these are
|
|
32
|
+
* render tests and not source assertions:
|
|
33
|
+
*
|
|
34
|
+
* - the preset posting without the tenantid header. The endpoint takes the
|
|
35
|
+
* project from that header alone and rejects the request without it, so
|
|
36
|
+
* the button does nothing and the only clue is a toast;
|
|
37
|
+
* - the preset reporting success on a failed request. It switches off
|
|
38
|
+
* twenty-one kinds of email; a green line over a request that changed
|
|
39
|
+
* nothing means the user believes they are done and keeps receiving them;
|
|
40
|
+
* - the rollup switch reading an absent row as OFF. Rollup shipped on with
|
|
41
|
+
* no backfill, so almost nobody has a row - reading no-row as off would
|
|
42
|
+
* show the overwhelming majority of users a switch that disagrees with
|
|
43
|
+
* the mail arriving in their inbox;
|
|
44
|
+
* - the create/update split going the wrong way. An update with no row
|
|
45
|
+
* writes nothing; a create against an existing row is rejected by the
|
|
46
|
+
* service's one-row rule. Either way the switch moves and the preference
|
|
47
|
+
* does not stick, which is worse than an error the user can see;
|
|
48
|
+
* - the undo link going missing. The preset is a bulk edit whose reversal
|
|
49
|
+
* is on another page now; without the link the copy promises a way back
|
|
50
|
+
* and provides none.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
54
|
+
"11111111-1111-4111-8111-111111111111",
|
|
55
|
+
);
|
|
56
|
+
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
57
|
+
const ROLLUP_ROW_ID: string = "55555555-5555-4555-8555-555555555555";
|
|
58
|
+
|
|
59
|
+
describe("user settings > email preferences", () => {
|
|
60
|
+
let post: jest.SpyInstance;
|
|
61
|
+
let getList: jest.SpyInstance;
|
|
62
|
+
let create: jest.SpyInstance;
|
|
63
|
+
let updateById: jest.SpyInstance;
|
|
64
|
+
let rollupRows: Array<UserNotificationEmailRollupSetting>;
|
|
65
|
+
|
|
66
|
+
function rollupRow(
|
|
67
|
+
isEnabled: boolean | undefined,
|
|
68
|
+
): UserNotificationEmailRollupSetting {
|
|
69
|
+
const row: UserNotificationEmailRollupSetting =
|
|
70
|
+
new UserNotificationEmailRollupSetting();
|
|
71
|
+
row._id = ROLLUP_ROW_ID;
|
|
72
|
+
row.projectId = PROJECT_ID;
|
|
73
|
+
row.userId = USER_ID;
|
|
74
|
+
if (isEnabled !== undefined) {
|
|
75
|
+
row.isEnabled = isEnabled;
|
|
76
|
+
}
|
|
77
|
+
return row;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
rollupRows = [];
|
|
82
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
83
|
+
jest.spyOn(User, "getUserId").mockReturnValue(USER_ID);
|
|
84
|
+
jest
|
|
85
|
+
.spyOn(ModelAPI, "getCommonHeaders")
|
|
86
|
+
.mockReturnValue({ tenantid: PROJECT_ID.toString() });
|
|
87
|
+
jest
|
|
88
|
+
.spyOn(API, "getFriendlyMessage")
|
|
89
|
+
.mockImplementation((err: unknown): string => {
|
|
90
|
+
return err instanceof Error || err instanceof HTTPErrorResponse
|
|
91
|
+
? err.message
|
|
92
|
+
: "Request failed";
|
|
93
|
+
});
|
|
94
|
+
getList = jest
|
|
95
|
+
.spyOn(ModelAPI, "getList")
|
|
96
|
+
.mockImplementation(async (): Promise<any> => {
|
|
97
|
+
return { data: rollupRows, count: rollupRows.length };
|
|
98
|
+
});
|
|
99
|
+
create = jest
|
|
100
|
+
.spyOn(ModelAPI, "create")
|
|
101
|
+
.mockImplementation(async (): Promise<any> => {
|
|
102
|
+
return {};
|
|
103
|
+
});
|
|
104
|
+
updateById = jest
|
|
105
|
+
.spyOn(ModelAPI, "updateById")
|
|
106
|
+
.mockImplementation(async (): Promise<any> => {
|
|
107
|
+
return {};
|
|
108
|
+
});
|
|
109
|
+
post = jest
|
|
110
|
+
.spyOn(API, "post")
|
|
111
|
+
.mockImplementation(async (): Promise<any> => {
|
|
112
|
+
return new HTTPResponse(200, { success: true }, {});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
cleanup();
|
|
118
|
+
jest.restoreAllMocks();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
function renderPage(): void {
|
|
122
|
+
render(
|
|
123
|
+
<EmailPreferences
|
|
124
|
+
pageRoute={new Route("/user-settings/email-preferences")}
|
|
125
|
+
currentProject={null}
|
|
126
|
+
hasPaymentMethod={true}
|
|
127
|
+
/>,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rollupSwitch(): HTMLElement {
|
|
132
|
+
return screen.getByRole("switch", {
|
|
133
|
+
name: /^Roll up notification emails:/,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function waitForPageReady(): Promise<HTMLElement> {
|
|
138
|
+
return screen.findByRole("switch", {
|
|
139
|
+
name: /^Roll up notification emails:/,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
describe("both controls are on the page", () => {
|
|
144
|
+
test("the routine-email preset and the rollup switch render together", async () => {
|
|
145
|
+
renderPage();
|
|
146
|
+
await waitForPageReady();
|
|
147
|
+
|
|
148
|
+
expect(screen.getByText("Fewer routine emails")).toBeVisible();
|
|
149
|
+
expect(screen.getByText("Email Rollup")).toBeVisible();
|
|
150
|
+
expect(
|
|
151
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
152
|
+
).toBeEnabled();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("nothing is written just by opening the page", async () => {
|
|
156
|
+
renderPage();
|
|
157
|
+
await waitForPageReady();
|
|
158
|
+
|
|
159
|
+
expect(post).not.toHaveBeenCalled();
|
|
160
|
+
expect(create).not.toHaveBeenCalled();
|
|
161
|
+
expect(updateById).not.toHaveBeenCalled();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("the rollup row is read for this member in this project only", async () => {
|
|
165
|
+
renderPage();
|
|
166
|
+
await waitForPageReady();
|
|
167
|
+
|
|
168
|
+
const read: any = getList.mock.calls.find((call: Array<any>): boolean => {
|
|
169
|
+
return call[0].modelType === UserNotificationEmailRollupSetting;
|
|
170
|
+
})![0];
|
|
171
|
+
|
|
172
|
+
expect(read.query.projectId).toEqual(PROJECT_ID);
|
|
173
|
+
expect(read.query.userId).toEqual(USER_ID);
|
|
174
|
+
/* The service refuses a second row, so one is all there can be. */
|
|
175
|
+
expect(read.limit).toBe(1);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/*
|
|
180
|
+
* THE UNDO. The preset is a bulk edit of twenty-one event types and its
|
|
181
|
+
* reversal is a page away, so the link is the only thing standing between
|
|
182
|
+
* "reversible" and "reversible in theory".
|
|
183
|
+
*/
|
|
184
|
+
describe("the way back to the per-event switches", () => {
|
|
185
|
+
test("the standing copy links to Notification Settings", async () => {
|
|
186
|
+
renderPage();
|
|
187
|
+
await waitForPageReady();
|
|
188
|
+
|
|
189
|
+
expect(screen.getByText(/Applies to you in this project/)).toBeVisible();
|
|
190
|
+
|
|
191
|
+
const link: HTMLElement = screen.getByRole("link", {
|
|
192
|
+
name: "Turn individual emails back on in Notification Settings",
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(link).toHaveAttribute(
|
|
196
|
+
"href",
|
|
197
|
+
`/dashboard/${PROJECT_ID.toString()}/user-settings/notification-settings`,
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("the confirmation links there too, and is announced", async () => {
|
|
202
|
+
renderPage();
|
|
203
|
+
await waitForPageReady();
|
|
204
|
+
fireEvent.click(
|
|
205
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const status: HTMLElement = await screen.findByRole("status");
|
|
209
|
+
|
|
210
|
+
expect(status).toHaveTextContent("Routine emails turned off.");
|
|
211
|
+
expect(
|
|
212
|
+
screen.getByRole("link", {
|
|
213
|
+
name: "Review your individual preferences in Notification Settings",
|
|
214
|
+
}),
|
|
215
|
+
).toHaveAttribute(
|
|
216
|
+
"href",
|
|
217
|
+
`/dashboard/${PROJECT_ID.toString()}/user-settings/notification-settings`,
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("no link carries the unpopulated :projectId template", async () => {
|
|
222
|
+
renderPage();
|
|
223
|
+
await waitForPageReady();
|
|
224
|
+
|
|
225
|
+
for (const link of screen.getAllByRole("link")) {
|
|
226
|
+
expect(link.getAttribute("href")).not.toContain(":projectId");
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("reducing routine emails", () => {
|
|
232
|
+
test("posts once to the bulk endpoint with an empty body and the tenant header", async () => {
|
|
233
|
+
renderPage();
|
|
234
|
+
await waitForPageReady();
|
|
235
|
+
fireEvent.click(
|
|
236
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
237
|
+
);
|
|
238
|
+
await screen.findByRole("status");
|
|
239
|
+
|
|
240
|
+
expect(post).toHaveBeenCalledTimes(1);
|
|
241
|
+
expect(post.mock.calls[0][0].url.toString()).toContain(
|
|
242
|
+
"/user-notification-setting/reduce-routine-emails",
|
|
243
|
+
);
|
|
244
|
+
/*
|
|
245
|
+
* The endpoint takes the user from the session and the project from the
|
|
246
|
+
* header, and ignores the body. Sending ids in the body would look like
|
|
247
|
+
* it worked while meaning nothing.
|
|
248
|
+
*/
|
|
249
|
+
expect(post.mock.calls[0][0].data).toEqual({});
|
|
250
|
+
expect(post.mock.calls[0][0].headers).toEqual({
|
|
251
|
+
tenantid: PROJECT_ID.toString(),
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("cannot be double-submitted while the request is in flight", async () => {
|
|
256
|
+
let resolveRequest!: (response: HTTPResponse<any>) => void;
|
|
257
|
+
post.mockReturnValue(
|
|
258
|
+
new Promise<HTTPResponse<any>>(
|
|
259
|
+
(resolve: (response: HTTPResponse<any>) => void): void => {
|
|
260
|
+
resolveRequest = resolve;
|
|
261
|
+
},
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
renderPage();
|
|
266
|
+
await waitForPageReady();
|
|
267
|
+
fireEvent.click(
|
|
268
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const saving: HTMLElement = screen.getByRole("button", {
|
|
272
|
+
name: "Saving email preferences…",
|
|
273
|
+
});
|
|
274
|
+
expect(saving).toBeDisabled();
|
|
275
|
+
fireEvent.click(saving);
|
|
276
|
+
expect(post).toHaveBeenCalledTimes(1);
|
|
277
|
+
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
|
278
|
+
|
|
279
|
+
await act(async () => {
|
|
280
|
+
resolveRequest(new HTTPResponse(200, { success: true }, {}));
|
|
281
|
+
});
|
|
282
|
+
await screen.findByRole("status");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test.each(["resolved HTTP error", "network rejection"])(
|
|
286
|
+
"shows %s instead of a success line, and allows a retry",
|
|
287
|
+
async (kind: string) => {
|
|
288
|
+
if (kind === "resolved HTTP error") {
|
|
289
|
+
post.mockResolvedValueOnce(
|
|
290
|
+
new HTTPErrorResponse(
|
|
291
|
+
503,
|
|
292
|
+
{ message: "Preferences could not be saved" },
|
|
293
|
+
{},
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
post.mockRejectedValueOnce(
|
|
298
|
+
new Error("Preferences could not be saved"),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
renderPage();
|
|
303
|
+
await waitForPageReady();
|
|
304
|
+
fireEvent.click(
|
|
305
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(await screen.findByRole("alert")).toHaveTextContent(
|
|
309
|
+
"Preferences could not be saved",
|
|
310
|
+
);
|
|
311
|
+
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
|
312
|
+
|
|
313
|
+
fireEvent.click(
|
|
314
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
315
|
+
);
|
|
316
|
+
await screen.findByRole("status");
|
|
317
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
test("can be applied again after it has succeeded once", async () => {
|
|
322
|
+
const apply: jest.Mock = jest.fn().mockResolvedValue(undefined);
|
|
323
|
+
render(<EmailNoiseCard onApply={apply} />);
|
|
324
|
+
|
|
325
|
+
fireEvent.click(
|
|
326
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
327
|
+
);
|
|
328
|
+
await screen.findByRole("status");
|
|
329
|
+
fireEvent.click(
|
|
330
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
await waitFor(() => {
|
|
334
|
+
expect(apply).toHaveBeenCalledTimes(2);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
/*
|
|
339
|
+
* The copy is the user-facing statement of what the endpoint actually
|
|
340
|
+
* does - email column only, this project, these categories. Softening it
|
|
341
|
+
* turns an accurate promise into a false one, and nothing else in the
|
|
342
|
+
* product would report the difference.
|
|
343
|
+
*/
|
|
344
|
+
test("says what it does and does not touch", async () => {
|
|
345
|
+
renderPage();
|
|
346
|
+
await waitForPageReady();
|
|
347
|
+
|
|
348
|
+
expect(
|
|
349
|
+
screen.getByText(/Other notification channels, paging, account and/),
|
|
350
|
+
).toBeVisible();
|
|
351
|
+
expect(
|
|
352
|
+
screen.getByText(
|
|
353
|
+
/Your choices for incident and alert creation, state changes/,
|
|
354
|
+
),
|
|
355
|
+
).toBeVisible();
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe("the rollup switch: an absent row means ON", () => {
|
|
360
|
+
test("a member with no row sees it on", async () => {
|
|
361
|
+
renderPage();
|
|
362
|
+
await waitForPageReady();
|
|
363
|
+
|
|
364
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "true");
|
|
365
|
+
expect(
|
|
366
|
+
screen.getByText(
|
|
367
|
+
"On: notifications that arrive together are delivered as one email.",
|
|
368
|
+
),
|
|
369
|
+
).toBeVisible();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("a row with no value still reads as on", async () => {
|
|
373
|
+
rollupRows = [rollupRow(undefined)];
|
|
374
|
+
renderPage();
|
|
375
|
+
await waitForPageReady();
|
|
376
|
+
|
|
377
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "true");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("only an explicit false reads as off", async () => {
|
|
381
|
+
rollupRows = [rollupRow(false)];
|
|
382
|
+
renderPage();
|
|
383
|
+
await waitForPageReady();
|
|
384
|
+
|
|
385
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "false");
|
|
386
|
+
expect(
|
|
387
|
+
screen.getByText(
|
|
388
|
+
"Off: every notification arrives as its own email, immediately.",
|
|
389
|
+
),
|
|
390
|
+
).toBeVisible();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("the accessible name says the state and what pressing it does", async () => {
|
|
394
|
+
renderPage();
|
|
395
|
+
await waitForPageReady();
|
|
396
|
+
|
|
397
|
+
expect(
|
|
398
|
+
screen.getByRole("switch", {
|
|
399
|
+
name: "Roll up notification emails: On. Click to disable.",
|
|
400
|
+
}),
|
|
401
|
+
).toBeInTheDocument();
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe("the rollup switch: writing the choice", () => {
|
|
406
|
+
test("a member with no row gets one created, then it is read back", async () => {
|
|
407
|
+
renderPage();
|
|
408
|
+
await waitForPageReady();
|
|
409
|
+
const readsBefore: number = getList.mock.calls.length;
|
|
410
|
+
|
|
411
|
+
fireEvent.click(rollupSwitch());
|
|
412
|
+
|
|
413
|
+
await waitFor(() => {
|
|
414
|
+
expect(create).toHaveBeenCalledTimes(1);
|
|
415
|
+
});
|
|
416
|
+
const created: UserNotificationEmailRollupSetting =
|
|
417
|
+
create.mock.calls[0][0].model;
|
|
418
|
+
expect(created.projectId).toEqual(PROJECT_ID);
|
|
419
|
+
expect(created.userId).toEqual(USER_ID);
|
|
420
|
+
expect(created.isEnabled).toBe(false);
|
|
421
|
+
expect(updateById).not.toHaveBeenCalled();
|
|
422
|
+
|
|
423
|
+
/*
|
|
424
|
+
* The service rejects a second row for the same (user, project), so the
|
|
425
|
+
* created row has to be read back or the NEXT toggle tries to create
|
|
426
|
+
* another one and fails on a switch that looked like it worked.
|
|
427
|
+
*/
|
|
428
|
+
await waitFor(() => {
|
|
429
|
+
expect(getList.mock.calls.length).toBeGreaterThan(readsBefore);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("a member who already has a row has it updated by id, value only", async () => {
|
|
434
|
+
rollupRows = [rollupRow(true)];
|
|
435
|
+
renderPage();
|
|
436
|
+
await waitForPageReady();
|
|
437
|
+
|
|
438
|
+
fireEvent.click(rollupSwitch());
|
|
439
|
+
|
|
440
|
+
await waitFor(() => {
|
|
441
|
+
expect(updateById).toHaveBeenCalledTimes(1);
|
|
442
|
+
});
|
|
443
|
+
expect(create).not.toHaveBeenCalled();
|
|
444
|
+
expect(updateById.mock.calls[0][0].id.toString()).toBe(ROLLUP_ROW_ID);
|
|
445
|
+
/*
|
|
446
|
+
* isEnabled is the only updatable column on the model; sending
|
|
447
|
+
* projectId or userId is rejected by column access control.
|
|
448
|
+
*/
|
|
449
|
+
expect(updateById.mock.calls[0][0].data).toEqual({ isEnabled: false });
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("the switch moves before the write lands", async () => {
|
|
453
|
+
rollupRows = [rollupRow(true)];
|
|
454
|
+
let finishWrite!: () => void;
|
|
455
|
+
updateById.mockReturnValue(
|
|
456
|
+
new Promise<void>((resolve: () => void): void => {
|
|
457
|
+
finishWrite = resolve;
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
renderPage();
|
|
462
|
+
await waitForPageReady();
|
|
463
|
+
fireEvent.click(rollupSwitch());
|
|
464
|
+
|
|
465
|
+
await waitFor(() => {
|
|
466
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "false");
|
|
467
|
+
});
|
|
468
|
+
expect(updateById).toHaveBeenCalledTimes(1);
|
|
469
|
+
|
|
470
|
+
await act(async () => {
|
|
471
|
+
finishWrite();
|
|
472
|
+
});
|
|
473
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "false");
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("a failed write puts the switch back and says why", async () => {
|
|
477
|
+
rollupRows = [rollupRow(true)];
|
|
478
|
+
updateById.mockRejectedValue(new Error("Rollup setting was not saved"));
|
|
479
|
+
|
|
480
|
+
renderPage();
|
|
481
|
+
await waitForPageReady();
|
|
482
|
+
fireEvent.click(rollupSwitch());
|
|
483
|
+
|
|
484
|
+
expect(
|
|
485
|
+
await screen.findByText("Rollup setting was not saved"),
|
|
486
|
+
).toBeVisible();
|
|
487
|
+
await waitFor(() => {
|
|
488
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "true");
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("it cannot be toggled twice while a write is in flight", async () => {
|
|
493
|
+
rollupRows = [rollupRow(true)];
|
|
494
|
+
let finishWrite!: () => void;
|
|
495
|
+
updateById.mockReturnValue(
|
|
496
|
+
new Promise<void>((resolve: () => void): void => {
|
|
497
|
+
finishWrite = resolve;
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
renderPage();
|
|
502
|
+
await waitForPageReady();
|
|
503
|
+
fireEvent.click(rollupSwitch());
|
|
504
|
+
fireEvent.click(rollupSwitch());
|
|
505
|
+
|
|
506
|
+
expect(updateById).toHaveBeenCalledTimes(1);
|
|
507
|
+
|
|
508
|
+
await act(async () => {
|
|
509
|
+
finishWrite();
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
/*
|
|
514
|
+
* The question a reader actually has in front of a batching switch is
|
|
515
|
+
* whether it can delay a page. Unanswered, the safe-feeling move is to
|
|
516
|
+
* turn the feature off out of fear.
|
|
517
|
+
*/
|
|
518
|
+
test("it says what rollup never delays", async () => {
|
|
519
|
+
renderPage();
|
|
520
|
+
await waitForPageReady();
|
|
521
|
+
|
|
522
|
+
expect(
|
|
523
|
+
screen.getByText(
|
|
524
|
+
/On-call paging, security and sign-in email, and billing email are never rolled up and are never delayed/,
|
|
525
|
+
),
|
|
526
|
+
).toBeVisible();
|
|
527
|
+
expect(
|
|
528
|
+
screen.getByText(/This is your own setting, in this project only/),
|
|
529
|
+
).toBeVisible();
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe("scoping", () => {
|
|
534
|
+
test("switching projects re-reads the rollup row against the new project", async () => {
|
|
535
|
+
rollupRows = [rollupRow(false)];
|
|
536
|
+
const { rerender } = render(
|
|
537
|
+
<EmailPreferences
|
|
538
|
+
pageRoute={new Route("/user-settings/email-preferences")}
|
|
539
|
+
currentProject={null}
|
|
540
|
+
hasPaymentMethod={true}
|
|
541
|
+
/>,
|
|
542
|
+
);
|
|
543
|
+
await waitForPageReady();
|
|
544
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "false");
|
|
545
|
+
|
|
546
|
+
const otherProjectId: ObjectID = new ObjectID(
|
|
547
|
+
"44444444-4444-4444-8444-444444444444",
|
|
548
|
+
);
|
|
549
|
+
jest
|
|
550
|
+
.spyOn(ProjectUtil, "getCurrentProjectId")
|
|
551
|
+
.mockReturnValue(otherProjectId);
|
|
552
|
+
/* No opt-out exists in the other project. */
|
|
553
|
+
rollupRows = [];
|
|
554
|
+
getList.mockClear();
|
|
555
|
+
|
|
556
|
+
rerender(
|
|
557
|
+
<EmailPreferences
|
|
558
|
+
pageRoute={new Route("/user-settings/email-preferences")}
|
|
559
|
+
currentProject={null}
|
|
560
|
+
hasPaymentMethod={true}
|
|
561
|
+
/>,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
await waitFor(() => {
|
|
565
|
+
expect(rollupSwitch()).toHaveAttribute("aria-checked", "true");
|
|
566
|
+
});
|
|
567
|
+
expect(getList.mock.calls.length).toBeGreaterThan(0);
|
|
568
|
+
for (const call of getList.mock.calls) {
|
|
569
|
+
expect(call[0].query.projectId).toEqual(otherProjectId);
|
|
570
|
+
expect(call[0].query.userId).toEqual(USER_ID);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("the success line from one project does not follow you to the next", async () => {
|
|
575
|
+
const { rerender } = render(
|
|
576
|
+
<EmailPreferences
|
|
577
|
+
pageRoute={new Route("/user-settings/email-preferences")}
|
|
578
|
+
currentProject={null}
|
|
579
|
+
hasPaymentMethod={true}
|
|
580
|
+
/>,
|
|
581
|
+
);
|
|
582
|
+
await waitForPageReady();
|
|
583
|
+
fireEvent.click(
|
|
584
|
+
screen.getByRole("button", { name: "Reduce routine emails" }),
|
|
585
|
+
);
|
|
586
|
+
await screen.findByRole("status");
|
|
587
|
+
|
|
588
|
+
jest
|
|
589
|
+
.spyOn(ProjectUtil, "getCurrentProjectId")
|
|
590
|
+
.mockReturnValue(new ObjectID("44444444-4444-4444-8444-444444444444"));
|
|
591
|
+
rerender(
|
|
592
|
+
<EmailPreferences
|
|
593
|
+
pageRoute={new Route("/user-settings/email-preferences")}
|
|
594
|
+
currentProject={null}
|
|
595
|
+
hasPaymentMethod={true}
|
|
596
|
+
/>,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
await waitForPageReady();
|
|
600
|
+
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
});
|