@oneuptime/common 14.0.5 → 14.0.7
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/InventoryItem.ts +23 -12
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentService.ts +134 -44
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/MetricService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Database/QueryHelper.ts +54 -1
- package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +12 -1
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -3
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +3 -1
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/NetworkTopologyExplorerSearch.test.tsx +1267 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +12 -0
- package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
- package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
- package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
- package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
- package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +96 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Types/BaseDatabase/AggregationType.test.ts +5 -1
- package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
- package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +8 -14
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +4 -3
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +27 -6
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
- package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
- package/Types/BaseDatabase/AggregationType.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +20 -7
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
- package/Types/Rum/WebVitals.ts +13 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/Input/Input.tsx +11 -0
- package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -0
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.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/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +96 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +47 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +12 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +1 -1
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +31 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js +7 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +20 -7
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +12 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js.map +1 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
jest,
|
|
8
|
+
test,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
import {
|
|
11
|
+
cleanup,
|
|
12
|
+
render,
|
|
13
|
+
RenderResult,
|
|
14
|
+
waitFor,
|
|
15
|
+
within,
|
|
16
|
+
} from "@testing-library/react";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import { MemoryRouter } from "react-router-dom";
|
|
19
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* The alert, incident and episode create wizards end on a review step that
|
|
23
|
+
* lists what each earlier step picked. The form holds only the id of the
|
|
24
|
+
* chosen state or severity, and the review step used to print a fixed
|
|
25
|
+
* sentence for it - "Initial state will be set to selected state", or on the
|
|
26
|
+
* episode wizards "Severity will be set to selected value" - so the one place
|
|
27
|
+
* meant to confirm the choice never named it. Pick "Acknowledged" and the
|
|
28
|
+
* review said nothing about Acknowledged.
|
|
29
|
+
*
|
|
30
|
+
* The review step now looks the choice up and shows its name in its color.
|
|
31
|
+
* These tests drive the four real pages, with ModelForm mocked to capture the
|
|
32
|
+
* fields it is handed (as AffectedResourcesSummaryStep.test.tsx does), and
|
|
33
|
+
* render each field's summary against a ModelAPI that answers per model type.
|
|
34
|
+
* They check what a user reads there, what is asked of the API, and that no
|
|
35
|
+
* summary on these pages says a choice "will be set to selected" - the shape
|
|
36
|
+
* of the sentence a copy-pasted field would bring back.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
jest.mock("react-i18next", () => {
|
|
40
|
+
return {
|
|
41
|
+
useTranslation: () => {
|
|
42
|
+
return {
|
|
43
|
+
t: (key: string, options?: { defaultValue?: string }): string => {
|
|
44
|
+
return options?.defaultValue ?? key;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
type SummaryItem = Record<string, unknown>;
|
|
52
|
+
|
|
53
|
+
type CapturedField = {
|
|
54
|
+
field?: Record<string, unknown> | undefined;
|
|
55
|
+
overrideField?: Record<string, unknown> | undefined;
|
|
56
|
+
overrideFieldKey?: string | undefined;
|
|
57
|
+
fieldType?: string | undefined;
|
|
58
|
+
dropdownModal?: { type: unknown } | undefined;
|
|
59
|
+
getSummaryElement?:
|
|
60
|
+
| ((item: SummaryItem) => React.ReactElement | undefined)
|
|
61
|
+
| undefined;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type CapturedFormProps = {
|
|
65
|
+
fields: Array<CapturedField>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let capturedForm: CapturedFormProps | null = null;
|
|
69
|
+
|
|
70
|
+
jest.mock("../../../UI/Components/Forms/ModelForm", () => {
|
|
71
|
+
// Only the component is stubbed: the pages import FormType from here too.
|
|
72
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
73
|
+
"../../../UI/Components/Forms/ModelForm",
|
|
74
|
+
) as Record<string, unknown>;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
__esModule: true,
|
|
78
|
+
...actual,
|
|
79
|
+
default: (props: CapturedFormProps): React.ReactElement => {
|
|
80
|
+
capturedForm = props;
|
|
81
|
+
return <div data-testid="model-form" />;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
87
|
+
const getItemMock: MockFunction = getJestMockFunction();
|
|
88
|
+
|
|
89
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
90
|
+
return {
|
|
91
|
+
__esModule: true,
|
|
92
|
+
default: {
|
|
93
|
+
getList: (...args: Array<unknown>) => {
|
|
94
|
+
return getListMock(...args);
|
|
95
|
+
},
|
|
96
|
+
getItem: (...args: Array<unknown>) => {
|
|
97
|
+
return getItemMock(...args);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
import AlertCreate from "../../../../App/FeatureSet/Dashboard/src/Pages/Alerts/Create";
|
|
104
|
+
import AlertEpisodeCreate from "../../../../App/FeatureSet/Dashboard/src/Pages/Alerts/EpisodeCreate";
|
|
105
|
+
import IncidentCreate from "../../../../App/FeatureSet/Dashboard/src/Pages/Incidents/Create";
|
|
106
|
+
import IncidentEpisodeCreate from "../../../../App/FeatureSet/Dashboard/src/Pages/Incidents/EpisodeCreate";
|
|
107
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
108
|
+
import AlertSeverity from "../../../Models/DatabaseModels/AlertSeverity";
|
|
109
|
+
import AlertState from "../../../Models/DatabaseModels/AlertState";
|
|
110
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
111
|
+
import IncidentSeverity from "../../../Models/DatabaseModels/IncidentSeverity";
|
|
112
|
+
import IncidentState from "../../../Models/DatabaseModels/IncidentState";
|
|
113
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
114
|
+
import Route from "../../../Types/API/Route";
|
|
115
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
116
|
+
import Color, { RGB } from "../../../Types/Color";
|
|
117
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
118
|
+
import FormFieldSchemaType from "../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
119
|
+
import Navigation from "../../../UI/Utils/Navigation";
|
|
120
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
121
|
+
|
|
122
|
+
const PROJECT_ID: string = "3b1d7c2e-5f4a-4e6b-9c8d-7a6f5e4d3c2b";
|
|
123
|
+
|
|
124
|
+
// What the pages preselect on mount: the first state by order.
|
|
125
|
+
const FIRST_ALERT_STATE_ID: string = "11111111-1111-4111-8111-000000000001";
|
|
126
|
+
const FIRST_INCIDENT_STATE_ID: string = "11111111-1111-4111-8111-000000000002";
|
|
127
|
+
|
|
128
|
+
type NamedFixture = {
|
|
129
|
+
id: string;
|
|
130
|
+
name: string;
|
|
131
|
+
color: string;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const ACKNOWLEDGED_ALERT_STATE: NamedFixture = {
|
|
135
|
+
id: "22222222-2222-4222-8222-000000000001",
|
|
136
|
+
name: "Acknowledged",
|
|
137
|
+
color: "#f59e0b",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const INVESTIGATING_INCIDENT_STATE: NamedFixture = {
|
|
141
|
+
id: "22222222-2222-4222-8222-000000000002",
|
|
142
|
+
name: "Investigating",
|
|
143
|
+
color: "#7c3aed",
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const HIGH_ALERT_SEVERITY: NamedFixture = {
|
|
147
|
+
id: "33333333-3333-4333-8333-000000000001",
|
|
148
|
+
name: "High",
|
|
149
|
+
color: "#dc2626",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const MAJOR_INCIDENT_SEVERITY: NamedFixture = {
|
|
153
|
+
id: "33333333-3333-4333-8333-000000000002",
|
|
154
|
+
name: "Major Incident",
|
|
155
|
+
color: "#ea580c",
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
type NamedModel = BaseModel & {
|
|
159
|
+
name?: string | undefined;
|
|
160
|
+
color?: Color | undefined;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
type NamedModelType = { new (): NamedModel };
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* The one record of each model type the API knows by id. A lookup of the
|
|
167
|
+
* wrong model type, or of any other id, finds nothing.
|
|
168
|
+
*/
|
|
169
|
+
const LOOKUP_FIXTURES: Map<unknown, NamedFixture> = new Map<
|
|
170
|
+
unknown,
|
|
171
|
+
NamedFixture
|
|
172
|
+
>([
|
|
173
|
+
[AlertState, ACKNOWLEDGED_ALERT_STATE],
|
|
174
|
+
[IncidentState, INVESTIGATING_INCIDENT_STATE],
|
|
175
|
+
[AlertSeverity, HIGH_ALERT_SEVERITY],
|
|
176
|
+
[IncidentSeverity, MAJOR_INCIDENT_SEVERITY],
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
enum LookupAnswer {
|
|
180
|
+
Found = "found",
|
|
181
|
+
Missing = "missing",
|
|
182
|
+
Failed = "failed",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let lookupAnswer: LookupAnswer = LookupAnswer.Found;
|
|
186
|
+
|
|
187
|
+
const LOOKUP_FAILURE_MESSAGE: string = "Could not reach the server right now.";
|
|
188
|
+
|
|
189
|
+
const STATE_PLACEHOLDER: string = "Initial state will be set to selected state";
|
|
190
|
+
const SEVERITY_PLACEHOLDER: string = "Severity will be set to selected value";
|
|
191
|
+
const ANY_PLACEHOLDER_PATTERN: RegExp = /will be set to selected/i;
|
|
192
|
+
|
|
193
|
+
type ItemRequest = {
|
|
194
|
+
modelType: unknown;
|
|
195
|
+
id: ObjectID;
|
|
196
|
+
select: Record<string, unknown>;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
type ListRequest = {
|
|
200
|
+
modelType: unknown;
|
|
201
|
+
query: Record<string, unknown>;
|
|
202
|
+
select: Record<string, unknown>;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
type ListAnswer = {
|
|
206
|
+
data: Array<NamedModel>;
|
|
207
|
+
count: number;
|
|
208
|
+
skip: number;
|
|
209
|
+
limit: number;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
function makeModel(
|
|
213
|
+
modelType: NamedModelType,
|
|
214
|
+
fixture: NamedFixture,
|
|
215
|
+
): NamedModel {
|
|
216
|
+
const model: NamedModel = new modelType();
|
|
217
|
+
model._id = fixture.id;
|
|
218
|
+
model.name = fixture.name;
|
|
219
|
+
model.color = new Color(fixture.color);
|
|
220
|
+
return model;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function listOf(data: Array<NamedModel>): ListAnswer {
|
|
224
|
+
return { data: data, count: data.length, skip: 0, limit: data.length };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function answerItem(request: ItemRequest): Promise<NamedModel | null> {
|
|
228
|
+
const fixture: NamedFixture | undefined = LOOKUP_FIXTURES.get(
|
|
229
|
+
request.modelType,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (!fixture) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (lookupAnswer === LookupAnswer.Failed) {
|
|
237
|
+
throw new Error(LOOKUP_FAILURE_MESSAGE);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
lookupAnswer === LookupAnswer.Missing ||
|
|
242
|
+
request.id.toString() !== fixture.id
|
|
243
|
+
) {
|
|
244
|
+
/*
|
|
245
|
+
* What ModelAPI.getItem really resolves for an id the server cannot
|
|
246
|
+
* find: the API answers {} and the client turns that into a model with
|
|
247
|
+
* no id - never null.
|
|
248
|
+
*/
|
|
249
|
+
return new (request.modelType as NamedModelType)();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return makeModel(request.modelType as NamedModelType, fixture);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function answerList(request: ListRequest): Promise<ListAnswer> {
|
|
256
|
+
const fixture: NamedFixture | undefined = LOOKUP_FIXTURES.get(
|
|
257
|
+
request.modelType,
|
|
258
|
+
);
|
|
259
|
+
const idQuery: unknown = request.query["_id"];
|
|
260
|
+
|
|
261
|
+
// A lookup by ids, as the incident severity summary makes.
|
|
262
|
+
if (fixture && idQuery instanceof Includes) {
|
|
263
|
+
if (lookupAnswer === LookupAnswer.Failed) {
|
|
264
|
+
throw new Error(LOOKUP_FAILURE_MESSAGE);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ids: Array<string> = idQuery.values.map(
|
|
268
|
+
(value: string | ObjectID | number): string => {
|
|
269
|
+
return value.toString();
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (lookupAnswer === LookupAnswer.Missing || !ids.includes(fixture.id)) {
|
|
274
|
+
return listOf([]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return listOf([makeModel(request.modelType as NamedModelType, fixture)]);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (request.modelType === AlertState) {
|
|
281
|
+
return listOf([
|
|
282
|
+
makeModel(AlertState, {
|
|
283
|
+
id: FIRST_ALERT_STATE_ID,
|
|
284
|
+
name: "Created",
|
|
285
|
+
color: "#ef4444",
|
|
286
|
+
}),
|
|
287
|
+
]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (request.modelType === IncidentState) {
|
|
291
|
+
return listOf([
|
|
292
|
+
makeModel(IncidentState, {
|
|
293
|
+
id: FIRST_INCIDENT_STATE_ID,
|
|
294
|
+
name: "Created",
|
|
295
|
+
color: "#ef4444",
|
|
296
|
+
}),
|
|
297
|
+
]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Labels, teams, users, monitors and the rest: nothing to show.
|
|
301
|
+
return listOf([]);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
type Lookup = {
|
|
305
|
+
modelType: unknown;
|
|
306
|
+
ids: Array<string>;
|
|
307
|
+
select: unknown;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/*
|
|
311
|
+
* Every request made since the page finished mounting, whichever of getItem
|
|
312
|
+
* or getList carried it, as the model type, ids and columns it asked for.
|
|
313
|
+
*/
|
|
314
|
+
function lookupsMade(): Array<Lookup> {
|
|
315
|
+
const itemLookups: Array<Lookup> = getItemMock.mock.calls.map(
|
|
316
|
+
(call: Array<unknown>): Lookup => {
|
|
317
|
+
const request: ItemRequest = call[0] as ItemRequest;
|
|
318
|
+
return {
|
|
319
|
+
modelType: request.modelType,
|
|
320
|
+
ids: [request.id.toString()],
|
|
321
|
+
select: request.select,
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const listLookups: Array<Lookup> = getListMock.mock.calls.map(
|
|
327
|
+
(call: Array<unknown>): Lookup => {
|
|
328
|
+
const request: ListRequest = call[0] as ListRequest;
|
|
329
|
+
const idQuery: unknown = request.query["_id"];
|
|
330
|
+
return {
|
|
331
|
+
modelType: request.modelType,
|
|
332
|
+
ids:
|
|
333
|
+
idQuery instanceof Includes
|
|
334
|
+
? idQuery.values.map(
|
|
335
|
+
(value: string | ObjectID | number): string => {
|
|
336
|
+
return value.toString();
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
: [],
|
|
340
|
+
select: request.select,
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
return [...itemLookups, ...listLookups];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
type PageUnderTest = {
|
|
349
|
+
name: string;
|
|
350
|
+
component: React.FunctionComponent<PageComponentProps>;
|
|
351
|
+
route: string;
|
|
352
|
+
stateFieldKey: string;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const ALERT_CREATE: PageUnderTest = {
|
|
356
|
+
name: "alert create",
|
|
357
|
+
component: AlertCreate,
|
|
358
|
+
route: "/dashboard/alerts/create",
|
|
359
|
+
stateFieldKey: "currentAlertState",
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const ALERT_EPISODE_CREATE: PageUnderTest = {
|
|
363
|
+
name: "alert episode create",
|
|
364
|
+
component: AlertEpisodeCreate,
|
|
365
|
+
route: "/dashboard/alerts/episodes/create",
|
|
366
|
+
stateFieldKey: "currentAlertState",
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const INCIDENT_CREATE: PageUnderTest = {
|
|
370
|
+
name: "incident create",
|
|
371
|
+
component: IncidentCreate,
|
|
372
|
+
route: "/dashboard/incidents/create",
|
|
373
|
+
stateFieldKey: "currentIncidentState",
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const INCIDENT_EPISODE_CREATE: PageUnderTest = {
|
|
377
|
+
name: "incident episode create",
|
|
378
|
+
component: IncidentEpisodeCreate,
|
|
379
|
+
route: "/dashboard/incidents/episodes/create",
|
|
380
|
+
stateFieldKey: "currentIncidentState",
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const PAGES: Array<PageUnderTest> = [
|
|
384
|
+
ALERT_CREATE,
|
|
385
|
+
ALERT_EPISODE_CREATE,
|
|
386
|
+
INCIDENT_CREATE,
|
|
387
|
+
INCIDENT_EPISODE_CREATE,
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
type LookupFieldCase = {
|
|
391
|
+
page: PageUnderTest;
|
|
392
|
+
fieldKey: string;
|
|
393
|
+
modelType: NamedModelType;
|
|
394
|
+
chosen: NamedFixture;
|
|
395
|
+
placeholder: string;
|
|
396
|
+
emptyMessage: string;
|
|
397
|
+
notFoundMessage: string;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const STATE_FIELD_CASES: Array<LookupFieldCase> = [
|
|
401
|
+
{
|
|
402
|
+
page: ALERT_CREATE,
|
|
403
|
+
fieldKey: "currentAlertState",
|
|
404
|
+
modelType: AlertState,
|
|
405
|
+
chosen: ACKNOWLEDGED_ALERT_STATE,
|
|
406
|
+
placeholder: STATE_PLACEHOLDER,
|
|
407
|
+
emptyMessage: "Will use first available state by priority",
|
|
408
|
+
notFoundMessage: "The selected alert state could not be found.",
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
page: ALERT_EPISODE_CREATE,
|
|
412
|
+
fieldKey: "currentAlertState",
|
|
413
|
+
modelType: AlertState,
|
|
414
|
+
chosen: ACKNOWLEDGED_ALERT_STATE,
|
|
415
|
+
placeholder: STATE_PLACEHOLDER,
|
|
416
|
+
emptyMessage: "Will use first available state by priority",
|
|
417
|
+
notFoundMessage: "The selected alert state could not be found.",
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
page: INCIDENT_CREATE,
|
|
421
|
+
fieldKey: "currentIncidentState",
|
|
422
|
+
modelType: IncidentState,
|
|
423
|
+
chosen: INVESTIGATING_INCIDENT_STATE,
|
|
424
|
+
placeholder: STATE_PLACEHOLDER,
|
|
425
|
+
emptyMessage: "Will use first available state by priority",
|
|
426
|
+
notFoundMessage: "The selected incident state could not be found.",
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
page: INCIDENT_EPISODE_CREATE,
|
|
430
|
+
fieldKey: "currentIncidentState",
|
|
431
|
+
modelType: IncidentState,
|
|
432
|
+
chosen: INVESTIGATING_INCIDENT_STATE,
|
|
433
|
+
placeholder: STATE_PLACEHOLDER,
|
|
434
|
+
emptyMessage: "Will use first available state by priority",
|
|
435
|
+
notFoundMessage: "The selected incident state could not be found.",
|
|
436
|
+
},
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
const EPISODE_SEVERITY_FIELD_CASES: Array<LookupFieldCase> = [
|
|
440
|
+
{
|
|
441
|
+
page: ALERT_EPISODE_CREATE,
|
|
442
|
+
fieldKey: "alertSeverity",
|
|
443
|
+
modelType: AlertSeverity,
|
|
444
|
+
chosen: HIGH_ALERT_SEVERITY,
|
|
445
|
+
placeholder: SEVERITY_PLACEHOLDER,
|
|
446
|
+
emptyMessage: "No alert severity selected.",
|
|
447
|
+
notFoundMessage: "The selected alert severity could not be found.",
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
/*
|
|
451
|
+
* Looked up by ids through the list endpoint, as the incident create
|
|
452
|
+
* page's severity already was; an empty answer reads as the list's own
|
|
453
|
+
* empty message.
|
|
454
|
+
*/
|
|
455
|
+
page: INCIDENT_EPISODE_CREATE,
|
|
456
|
+
fieldKey: "incidentSeverity",
|
|
457
|
+
modelType: IncidentSeverity,
|
|
458
|
+
chosen: MAJOR_INCIDENT_SEVERITY,
|
|
459
|
+
placeholder: SEVERITY_PLACEHOLDER,
|
|
460
|
+
emptyMessage: "No incident severity selected.",
|
|
461
|
+
notFoundMessage: "No Incident Severities.",
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
async function openForm(page: PageUnderTest): Promise<Array<CapturedField>> {
|
|
466
|
+
render(
|
|
467
|
+
<MemoryRouter>
|
|
468
|
+
<page.component
|
|
469
|
+
pageRoute={new Route(page.route)}
|
|
470
|
+
currentProject={new Project()}
|
|
471
|
+
hasPaymentMethod={true}
|
|
472
|
+
/>
|
|
473
|
+
</MemoryRouter>,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// The episode pages hold a loader up until their first-state lookup lands.
|
|
477
|
+
await waitFor(() => {
|
|
478
|
+
expect(capturedForm).not.toBeNull();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
/*
|
|
482
|
+
* Whatever the page asks for on mount has been asked by now, so from here
|
|
483
|
+
* on every request is one a summary made.
|
|
484
|
+
*/
|
|
485
|
+
getListMock.mockClear();
|
|
486
|
+
getItemMock.mockClear();
|
|
487
|
+
|
|
488
|
+
return capturedForm!.fields;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function keyOf(field: CapturedField): string | undefined {
|
|
492
|
+
return (
|
|
493
|
+
field.overrideFieldKey ||
|
|
494
|
+
Object.keys(field.field || field.overrideField || {})[0]
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function findField(
|
|
499
|
+
fields: Array<CapturedField>,
|
|
500
|
+
fieldKey: string,
|
|
501
|
+
): CapturedField {
|
|
502
|
+
const field: CapturedField | undefined = fields.find(
|
|
503
|
+
(candidate: CapturedField): boolean => {
|
|
504
|
+
return keyOf(candidate) === fieldKey;
|
|
505
|
+
},
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
expect(field).toBeDefined();
|
|
509
|
+
|
|
510
|
+
return field!;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Renders a field's summary and waits for any lookup it starts to settle.
|
|
514
|
+
async function renderSummary(
|
|
515
|
+
field: CapturedField,
|
|
516
|
+
item: SummaryItem,
|
|
517
|
+
): Promise<RenderResult> {
|
|
518
|
+
expect(field.getSummaryElement).toBeDefined();
|
|
519
|
+
|
|
520
|
+
const result: RenderResult = render(
|
|
521
|
+
<MemoryRouter>{field.getSummaryElement!(item)}</MemoryRouter>,
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
await waitFor(() => {
|
|
525
|
+
expect(
|
|
526
|
+
within(result.container).queryAllByTestId("component-loader"),
|
|
527
|
+
).toHaveLength(0);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/*
|
|
534
|
+
* Whether anything in the summary is painted in the given color: the pills
|
|
535
|
+
* put it on the pill itself or, in their minimal form, on a dot beside the
|
|
536
|
+
* name.
|
|
537
|
+
*/
|
|
538
|
+
function paintsColor(container: HTMLElement, hex: string): boolean {
|
|
539
|
+
const rgb: RGB = Color.colorToRgb(new Color(hex));
|
|
540
|
+
const expected: string = `rgb(${rgb.red}, ${rgb.green}, ${rgb.blue})`;
|
|
541
|
+
|
|
542
|
+
return Array.from(container.querySelectorAll<HTMLElement>("*")).some(
|
|
543
|
+
(element: HTMLElement): boolean => {
|
|
544
|
+
return element.style.backgroundColor === expected;
|
|
545
|
+
},
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/*
|
|
550
|
+
* A value for a field as the form would hold it once something is picked:
|
|
551
|
+
* the known record's id for a state or severity, so its lookup succeeds, and
|
|
552
|
+
* a fresh id (or a list of one) for everything else.
|
|
553
|
+
*/
|
|
554
|
+
function representativeValue(field: CapturedField): unknown {
|
|
555
|
+
const fixture: NamedFixture | undefined = field.dropdownModal
|
|
556
|
+
? LOOKUP_FIXTURES.get(field.dropdownModal.type)
|
|
557
|
+
: undefined;
|
|
558
|
+
|
|
559
|
+
if (fixture) {
|
|
560
|
+
return fixture.id;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (field.fieldType === FormFieldSchemaType.Dropdown) {
|
|
564
|
+
return ObjectID.generate().toString();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return [ObjectID.generate().toString()];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
describe("the create wizards' review step", () => {
|
|
571
|
+
beforeEach(() => {
|
|
572
|
+
capturedForm = null;
|
|
573
|
+
lookupAnswer = LookupAnswer.Found;
|
|
574
|
+
getListMock.mockReset();
|
|
575
|
+
getItemMock.mockReset();
|
|
576
|
+
getListMock.mockImplementation(answerList as never);
|
|
577
|
+
getItemMock.mockImplementation(answerItem as never);
|
|
578
|
+
|
|
579
|
+
jest
|
|
580
|
+
.spyOn(ProjectUtil, "getCurrentProjectId")
|
|
581
|
+
.mockReturnValue(new ObjectID(PROJECT_ID));
|
|
582
|
+
jest.spyOn(Navigation, "getQueryStringByName").mockReturnValue(null);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
afterEach(() => {
|
|
586
|
+
cleanup();
|
|
587
|
+
jest.restoreAllMocks();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
describe.each([...STATE_FIELD_CASES, ...EPISODE_SEVERITY_FIELD_CASES])(
|
|
591
|
+
"the $fieldKey summary on the $page.name page",
|
|
592
|
+
(fieldCase: LookupFieldCase) => {
|
|
593
|
+
test("names the chosen value in its color instead of a placeholder sentence", async () => {
|
|
594
|
+
const field: CapturedField = findField(
|
|
595
|
+
await openForm(fieldCase.page),
|
|
596
|
+
fieldCase.fieldKey,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const { container } = await renderSummary(field, {
|
|
600
|
+
[fieldCase.fieldKey]: fieldCase.chosen.id,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
expect(
|
|
604
|
+
within(container).getByText(fieldCase.chosen.name),
|
|
605
|
+
).toBeInTheDocument();
|
|
606
|
+
expect(paintsColor(container, fieldCase.chosen.color)).toBe(true);
|
|
607
|
+
expect(container).not.toHaveTextContent(fieldCase.placeholder);
|
|
608
|
+
expect(container.textContent).not.toMatch(ANY_PLACEHOLDER_PATTERN);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("looks up exactly the chosen id of its own model type, asking for the name and color", async () => {
|
|
612
|
+
const field: CapturedField = findField(
|
|
613
|
+
await openForm(fieldCase.page),
|
|
614
|
+
fieldCase.fieldKey,
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
await renderSummary(field, {
|
|
618
|
+
[fieldCase.fieldKey]: fieldCase.chosen.id,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
expect(lookupsMade()).toEqual([
|
|
622
|
+
{
|
|
623
|
+
modelType: fieldCase.modelType,
|
|
624
|
+
ids: [fieldCase.chosen.id],
|
|
625
|
+
select: expect.objectContaining({ name: true, color: true }),
|
|
626
|
+
},
|
|
627
|
+
]);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test("keeps its nothing-chosen message and looks nothing up when nothing is chosen", async () => {
|
|
631
|
+
const field: CapturedField = findField(
|
|
632
|
+
await openForm(fieldCase.page),
|
|
633
|
+
fieldCase.fieldKey,
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
const { container } = await renderSummary(field, {});
|
|
637
|
+
|
|
638
|
+
// Let anything the summary might have started get as far as the API.
|
|
639
|
+
await new Promise<void>((resolve: () => void) => {
|
|
640
|
+
setTimeout(resolve, 0);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
expect(container).toHaveTextContent(fieldCase.emptyMessage);
|
|
644
|
+
expect(container.textContent).not.toMatch(ANY_PLACEHOLDER_PATTERN);
|
|
645
|
+
expect(lookupsMade()).toEqual([]);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test("shows the error when the lookup fails, not a placeholder sentence", async () => {
|
|
649
|
+
lookupAnswer = LookupAnswer.Failed;
|
|
650
|
+
|
|
651
|
+
const field: CapturedField = findField(
|
|
652
|
+
await openForm(fieldCase.page),
|
|
653
|
+
fieldCase.fieldKey,
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
const { container } = await renderSummary(field, {
|
|
657
|
+
[fieldCase.fieldKey]: fieldCase.chosen.id,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
expect(container).toHaveTextContent(LOOKUP_FAILURE_MESSAGE);
|
|
661
|
+
expect(container).not.toHaveTextContent(fieldCase.chosen.name);
|
|
662
|
+
expect(container.textContent).not.toMatch(ANY_PLACEHOLDER_PATTERN);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("says so when the chosen value no longer exists", async () => {
|
|
666
|
+
lookupAnswer = LookupAnswer.Missing;
|
|
667
|
+
|
|
668
|
+
const field: CapturedField = findField(
|
|
669
|
+
await openForm(fieldCase.page),
|
|
670
|
+
fieldCase.fieldKey,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
const { container } = await renderSummary(field, {
|
|
674
|
+
[fieldCase.fieldKey]: fieldCase.chosen.id,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
expect(container).toHaveTextContent(fieldCase.notFoundMessage);
|
|
678
|
+
expect(container).not.toHaveTextContent(fieldCase.chosen.name);
|
|
679
|
+
expect(container.textContent).not.toMatch(ANY_PLACEHOLDER_PATTERN);
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
test("the alert create page's severity keeps the form's own dropdown summary", async () => {
|
|
685
|
+
/*
|
|
686
|
+
* Only the episode wizards had a severity sentence to replace. The alert
|
|
687
|
+
* wizard's severity never had a summary of its own, so the form shows the
|
|
688
|
+
* picked option's label for it, and it stays that way.
|
|
689
|
+
*/
|
|
690
|
+
const field: CapturedField = findField(
|
|
691
|
+
await openForm(ALERT_CREATE),
|
|
692
|
+
"alertSeverity",
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
expect(field.getSummaryElement).toBeUndefined();
|
|
696
|
+
expect(field.fieldType).toBe(FormFieldSchemaType.Dropdown);
|
|
697
|
+
expect(field.dropdownModal?.type).toBe(AlertSeverity);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
describe.each(PAGES)("on the $name page", (page: PageUnderTest) => {
|
|
701
|
+
test("no summary says a choice will be set to the selected one, chosen or not", async () => {
|
|
702
|
+
const fields: Array<CapturedField> = await openForm(page);
|
|
703
|
+
const withSummary: Array<CapturedField> = fields.filter(
|
|
704
|
+
(field: CapturedField): boolean => {
|
|
705
|
+
return Boolean(field.getSummaryElement);
|
|
706
|
+
},
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
// Not vacuous: the state field is among the summaries rendered.
|
|
710
|
+
expect(withSummary.map(keyOf)).toContain(page.stateFieldKey);
|
|
711
|
+
|
|
712
|
+
const chosenItem: SummaryItem = {};
|
|
713
|
+
for (const field of withSummary) {
|
|
714
|
+
chosenItem[keyOf(field)!] = representativeValue(field);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const offenders: Array<string> = [];
|
|
718
|
+
|
|
719
|
+
for (const field of withSummary) {
|
|
720
|
+
for (const [label, item] of [
|
|
721
|
+
["chosen", chosenItem],
|
|
722
|
+
["nothing chosen", {}],
|
|
723
|
+
] as Array<[string, SummaryItem]>) {
|
|
724
|
+
const result: RenderResult = await renderSummary(field, item);
|
|
725
|
+
const text: string = result.container.textContent || "";
|
|
726
|
+
|
|
727
|
+
if (ANY_PLACEHOLDER_PATTERN.test(text)) {
|
|
728
|
+
offenders.push(`${keyOf(field)} (${label}): "${text}"`);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
result.unmount();
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
expect(offenders).toEqual([]);
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
});
|