@oneuptime/common 14.0.2 → 14.0.3
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/AlertFeed.ts +2 -0
- package/Models/DatabaseModels/IncidentAlert.ts +540 -0
- package/Models/DatabaseModels/IncidentFeed.ts +2 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Models/DatabaseModels/RumApplication.ts +52 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/IncidentAlertService.ts +1811 -0
- package/Server/Services/IncidentService.ts +210 -10
- package/Server/Services/IncidentStateTimelineService.ts +24 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
- package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
- package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
- package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
- package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
- package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
- package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
- package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
- package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
- package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
- package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
- package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
- package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
- package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
- package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
- package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
- package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
- package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
- package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
- package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
- package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
- package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
- package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
- package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
- package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
- package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
- package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
- package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
- package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
- package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
- package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
- package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
- package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
- package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
- package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
- package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
- package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
- package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
- package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
- package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
- package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
- package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
- package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
- package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
- package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
- package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
- package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
- package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
- package/Tests/Types/Rum/WebVitals.test.ts +173 -0
- package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
- package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
- package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
- package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
- package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
- package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
- package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
- package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
- package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
- package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
- package/Types/Incident/IncidentAlertLink.ts +38 -0
- package/Types/Permission.ts +48 -0
- package/Types/Rum/SessionReplay.ts +25 -6
- package/Types/Rum/WebVitals.ts +52 -0
- package/UI/Components/Detail/FieldLabel.tsx +8 -0
- package/UI/Components/InfoCard/InfoCard.tsx +47 -1
- package/UI/Components/ModelTable/BaseModelTable.tsx +95 -17
- package/UI/Components/ModelTable/Column.ts +5 -0
- package/UI/Components/Table/TableHeader.tsx +47 -2
- package/UI/Components/Table/Types/Column.ts +7 -0
- package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
- package/UI/Utils/PermissionGate.ts +11 -2
- package/Utils/Incident/IncidentFromAlerts.ts +516 -0
- package/Utils/Rum/SessionTraceState.ts +212 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
- package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +164 -12
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +25 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
- package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +66 -14
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +18 -2
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
- package/build/dist/UI/Utils/PermissionGate.js +4 -2
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
- package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
- package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
act,
|
|
4
|
+
cleanup,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
} from "@testing-library/react";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import {
|
|
11
|
+
afterEach,
|
|
12
|
+
beforeEach,
|
|
13
|
+
describe,
|
|
14
|
+
expect,
|
|
15
|
+
jest,
|
|
16
|
+
test,
|
|
17
|
+
} from "@jest/globals";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* The (i) tooltips on the Kubernetes cluster overview, rendered for real.
|
|
21
|
+
*
|
|
22
|
+
* The page is rendered whole, with only its data sources stubbed (the
|
|
23
|
+
* cluster lookup, the inventory summary, the metric aggregates and the
|
|
24
|
+
* top-pod lists) and the heavy neighbours it composes swapped for stubs
|
|
25
|
+
* (model detail card, activity cards, refresh control, the chart itself).
|
|
26
|
+
* Everything that draws a title and its (i) - GoldenMetricTile, InfoCard,
|
|
27
|
+
* Card, the page's own chart card, chips and badges - is the real code.
|
|
28
|
+
*
|
|
29
|
+
* Tooltips are read through aria-describedby after a hover, so each text is
|
|
30
|
+
* checked against the button it belongs to even where two metrics share a
|
|
31
|
+
* title ("CPU" is both a tile and a chart).
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const mockLineChartProps: Array<Record<string, unknown>> = [];
|
|
35
|
+
const mockNavigations: Array<unknown> = [];
|
|
36
|
+
|
|
37
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
38
|
+
return {
|
|
39
|
+
__esModule: true,
|
|
40
|
+
default: {
|
|
41
|
+
getLastParamAsObjectID: () => {
|
|
42
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
43
|
+
"../../../Types/ObjectID",
|
|
44
|
+
) as { default: new (id: string) => unknown };
|
|
45
|
+
return new ObjectIDType("0193c0de-7777-4aaa-8bbb-000000000007");
|
|
46
|
+
},
|
|
47
|
+
navigate: (route: unknown) => {
|
|
48
|
+
mockNavigations.push(route);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
55
|
+
return {
|
|
56
|
+
__esModule: true,
|
|
57
|
+
default: {
|
|
58
|
+
getCurrentProjectId: () => {
|
|
59
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
60
|
+
"../../../Types/ObjectID",
|
|
61
|
+
) as { default: new (id: string) => unknown };
|
|
62
|
+
return new ObjectIDType("10000000-0000-4000-8000-000000000001");
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* The inventory summary the page reads over API.post. Mutable so a test can
|
|
70
|
+
* describe a quieter cluster; `mock` prefix because jest.mock factories are
|
|
71
|
+
* hoisted and may only close over mock-prefixed names.
|
|
72
|
+
*/
|
|
73
|
+
let mockSummary: Record<string, unknown> = {};
|
|
74
|
+
|
|
75
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
76
|
+
return {
|
|
77
|
+
__esModule: true,
|
|
78
|
+
default: {
|
|
79
|
+
post: () => {
|
|
80
|
+
return Promise.resolve({ data: mockSummary });
|
|
81
|
+
},
|
|
82
|
+
getFriendlyMessage: (error: unknown) => {
|
|
83
|
+
return (
|
|
84
|
+
((error as { message?: unknown } | null)?.message as string) ||
|
|
85
|
+
"Could not load"
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
93
|
+
return {
|
|
94
|
+
__esModule: true,
|
|
95
|
+
default: {
|
|
96
|
+
getItem: () => {
|
|
97
|
+
return Promise.resolve({
|
|
98
|
+
name: "production-us-east",
|
|
99
|
+
clusterIdentifier: "production-us-east-1",
|
|
100
|
+
otelCollectorStatus: "connected",
|
|
101
|
+
lastSeenAt: new Date(),
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
getCommonHeaders: () => {
|
|
105
|
+
return {};
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
|
|
112
|
+
return {
|
|
113
|
+
__esModule: true,
|
|
114
|
+
default: {
|
|
115
|
+
aggregate: () => {
|
|
116
|
+
return Promise.resolve({ data: [] });
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
jest.mock(
|
|
123
|
+
"../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesResourceUtils",
|
|
124
|
+
() => {
|
|
125
|
+
const actual: {
|
|
126
|
+
default: {
|
|
127
|
+
formatCpuValue: (value: number | null) => string;
|
|
128
|
+
formatMemoryValue: (bytes: number | null) => string;
|
|
129
|
+
};
|
|
130
|
+
} = jest.requireActual(
|
|
131
|
+
"../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesResourceUtils",
|
|
132
|
+
) as {
|
|
133
|
+
default: {
|
|
134
|
+
formatCpuValue: (value: number | null) => string;
|
|
135
|
+
formatMemoryValue: (bytes: number | null) => string;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
__esModule: true,
|
|
141
|
+
default: {
|
|
142
|
+
formatCpuValue: actual.default.formatCpuValue,
|
|
143
|
+
formatMemoryValue: actual.default.formatMemoryValue,
|
|
144
|
+
fetchResourceListWithMemory: () => {
|
|
145
|
+
return Promise.resolve([
|
|
146
|
+
{
|
|
147
|
+
name: "api-7d9f",
|
|
148
|
+
namespace: "shop",
|
|
149
|
+
cpuUtilization: 0.4,
|
|
150
|
+
memoryUsageBytes: 512 * 1024 * 1024,
|
|
151
|
+
memoryLimitBytes: null,
|
|
152
|
+
status: "",
|
|
153
|
+
age: "",
|
|
154
|
+
additionalAttributes: { "resource.k8s.node.name": "node-a" },
|
|
155
|
+
},
|
|
156
|
+
]);
|
|
157
|
+
},
|
|
158
|
+
fetchNodeAllocatableMemory: () => {
|
|
159
|
+
return Promise.resolve(new Map<string, number>());
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
jest.mock(
|
|
167
|
+
"../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesObjectFetcher",
|
|
168
|
+
() => {
|
|
169
|
+
return {
|
|
170
|
+
__esModule: true,
|
|
171
|
+
...(jest.requireActual(
|
|
172
|
+
"../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesObjectFetcher",
|
|
173
|
+
) as Record<string, unknown>),
|
|
174
|
+
fetchClusterWarningEvents: () => {
|
|
175
|
+
return Promise.resolve([]);
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
jest.mock(
|
|
182
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceActivity/ResourceActivityCards",
|
|
183
|
+
() => {
|
|
184
|
+
return {
|
|
185
|
+
__esModule: true,
|
|
186
|
+
default: () => {
|
|
187
|
+
return <div data-testid="resource-activity-cards" />;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
jest.mock(
|
|
194
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
|
|
195
|
+
() => {
|
|
196
|
+
return {
|
|
197
|
+
__esModule: true,
|
|
198
|
+
default: () => {
|
|
199
|
+
return <div data-testid="auto-refresh-control" />;
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
|
|
206
|
+
return {
|
|
207
|
+
__esModule: true,
|
|
208
|
+
default: () => {
|
|
209
|
+
return <div data-testid="cluster-details" />;
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
jest.mock(
|
|
215
|
+
"../../../UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker",
|
|
216
|
+
() => {
|
|
217
|
+
return {
|
|
218
|
+
__esModule: true,
|
|
219
|
+
default: () => {
|
|
220
|
+
return <div data-testid="time-range-picker" />;
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
|
|
227
|
+
return {
|
|
228
|
+
__esModule: true,
|
|
229
|
+
default: (props: Record<string, unknown>) => {
|
|
230
|
+
mockLineChartProps.push(props);
|
|
231
|
+
return <div data-testid="line-chart" />;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
import KubernetesClusterOverview, {
|
|
237
|
+
ClusterChartCard,
|
|
238
|
+
titleWithTooltip,
|
|
239
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/View/Index";
|
|
240
|
+
import { KUBERNETES_CLUSTER_METRIC_DESCRIPTIONS } from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/KubernetesClusterMetricDescriptions";
|
|
241
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
242
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
243
|
+
|
|
244
|
+
const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
|
|
245
|
+
|
|
246
|
+
const TEXT: typeof KUBERNETES_CLUSTER_METRIC_DESCRIPTIONS =
|
|
247
|
+
KUBERNETES_CLUSTER_METRIC_DESCRIPTIONS;
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* A busy cluster: something in every pod phase, a node not ready and every
|
|
251
|
+
* pressure condition, so every chip and badge the page can draw is drawn.
|
|
252
|
+
*/
|
|
253
|
+
const BUSY_SUMMARY: Record<string, unknown> = {
|
|
254
|
+
nodeCount: 3,
|
|
255
|
+
podCount: 12,
|
|
256
|
+
namespaceCount: 4,
|
|
257
|
+
deploymentCount: 5,
|
|
258
|
+
statefulSetCount: 1,
|
|
259
|
+
daemonSetCount: 1,
|
|
260
|
+
jobCount: 1,
|
|
261
|
+
cronJobCount: 1,
|
|
262
|
+
pvcCount: 2,
|
|
263
|
+
pvCount: 2,
|
|
264
|
+
containerCount: 20,
|
|
265
|
+
podPhaseCounts: { running: 8, pending: 2, failed: 1, succeeded: 1 },
|
|
266
|
+
nodeReadyCounts: { ready: 2, notReady: 1 },
|
|
267
|
+
nodePressureCounts: { memoryPressure: 1, diskPressure: 1, pidPressure: 1 },
|
|
268
|
+
degradedPods: [],
|
|
269
|
+
degradedNodes: [],
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const QUIET_SUMMARY: Record<string, unknown> = {
|
|
273
|
+
nodeCount: 3,
|
|
274
|
+
podCount: 12,
|
|
275
|
+
namespaceCount: 4,
|
|
276
|
+
deploymentCount: 5,
|
|
277
|
+
containerCount: 20,
|
|
278
|
+
podPhaseCounts: { running: 0, pending: 0, failed: 0, succeeded: 0 },
|
|
279
|
+
nodeReadyCounts: { ready: 3, notReady: 0 },
|
|
280
|
+
nodePressureCounts: { memoryPressure: 0, diskPressure: 0, pidPressure: 0 },
|
|
281
|
+
degradedPods: [],
|
|
282
|
+
degradedNodes: [],
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/*
|
|
286
|
+
* Every (i) the busy overview draws, in page order, with the text it must
|
|
287
|
+
* show. Twenty-six - every overview description in the record.
|
|
288
|
+
*/
|
|
289
|
+
const EXPECTED: Array<[string, string]> = [
|
|
290
|
+
["Cluster inventory counts", TEXT.inventoryCounts],
|
|
291
|
+
["Running", TEXT.podsRunning],
|
|
292
|
+
["Pending", TEXT.podsPending],
|
|
293
|
+
["Failed", TEXT.podsFailed],
|
|
294
|
+
["Nodes Not Ready", TEXT.nodesNotReady],
|
|
295
|
+
["Availability", TEXT.availability],
|
|
296
|
+
["CPU", TEXT.cpu],
|
|
297
|
+
["Memory", TEXT.memory],
|
|
298
|
+
["Filesystem", TEXT.filesystem],
|
|
299
|
+
["Network", TEXT.network],
|
|
300
|
+
["Availability", TEXT.availabilityChart],
|
|
301
|
+
["CPU", TEXT.cpuChart],
|
|
302
|
+
["Memory", TEXT.memoryChart],
|
|
303
|
+
["Filesystem", TEXT.filesystemChart],
|
|
304
|
+
["Network", TEXT.networkChart],
|
|
305
|
+
["Cluster Health", TEXT.clusterHealth],
|
|
306
|
+
["Nodes", TEXT.nodes],
|
|
307
|
+
["Pods", TEXT.pods],
|
|
308
|
+
["Namespaces", TEXT.namespaces],
|
|
309
|
+
["Agent Status", TEXT.agentStatus],
|
|
310
|
+
["Memory Pressure", TEXT.memoryPressure],
|
|
311
|
+
["Disk Pressure", TEXT.diskPressure],
|
|
312
|
+
["PID Pressure", TEXT.pidPressure],
|
|
313
|
+
["Pod Health", TEXT.podHealth],
|
|
314
|
+
["CPU Usage", TEXT.topCpuPods],
|
|
315
|
+
["Memory Usage", TEXT.topMemoryPods],
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
const ABOUT: RegExp = /^About /;
|
|
319
|
+
|
|
320
|
+
async function explanationOf(button: HTMLElement): Promise<string> {
|
|
321
|
+
fireEvent.mouseEnter(button);
|
|
322
|
+
await act(async () => {
|
|
323
|
+
jest.advanceTimersByTime(250);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const describedBy: string | null = button.getAttribute("aria-describedby");
|
|
327
|
+
|
|
328
|
+
expect(describedBy).toBeTruthy();
|
|
329
|
+
|
|
330
|
+
return document.getElementById(describedBy as string)?.textContent || "";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/*
|
|
334
|
+
* What activates the clickable card an (i) sits in: the card itself when it
|
|
335
|
+
* is a role="button" container, or the full-card button laid over it when
|
|
336
|
+
* the card uses the overlay pattern (a button beside the (i), never around
|
|
337
|
+
* it). Either shape is accepted - the test is about the (i), not the card.
|
|
338
|
+
*/
|
|
339
|
+
function cardActivatorOf(info: HTMLElement): HTMLElement {
|
|
340
|
+
const container: HTMLElement | null | undefined =
|
|
341
|
+
info.parentElement?.closest('[role="button"]');
|
|
342
|
+
|
|
343
|
+
if (container) {
|
|
344
|
+
return container as HTMLElement;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let node: HTMLElement | null = info.parentElement;
|
|
348
|
+
|
|
349
|
+
while (node) {
|
|
350
|
+
const overlay: HTMLButtonElement | undefined = Array.from(
|
|
351
|
+
node.querySelectorAll("button"),
|
|
352
|
+
).find((button: HTMLButtonElement): boolean => {
|
|
353
|
+
return !ABOUT.test(button.getAttribute("aria-label") || "");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (overlay) {
|
|
357
|
+
return overlay;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
node = node.parentElement;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
throw new Error("The (i) is not inside a clickable card.");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function infoButtons(): Array<HTMLElement> {
|
|
367
|
+
return screen.queryAllByRole("button", { name: ABOUT });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function renderOverview(): Promise<void> {
|
|
371
|
+
render(<KubernetesClusterOverview {...PAGE_PROPS} />);
|
|
372
|
+
|
|
373
|
+
// The last sections to settle: the summary cards and the top-pod lists.
|
|
374
|
+
await screen.findByRole("button", { name: "About Memory Usage" });
|
|
375
|
+
await screen.findByRole("button", { name: "About Pod Health" });
|
|
376
|
+
await screen.findAllByTestId("line-chart");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
beforeEach(() => {
|
|
380
|
+
jest.useFakeTimers();
|
|
381
|
+
mockLineChartProps.length = 0;
|
|
382
|
+
mockNavigations.length = 0;
|
|
383
|
+
mockSummary = BUSY_SUMMARY;
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
afterEach(() => {
|
|
387
|
+
cleanup();
|
|
388
|
+
jest.useRealTimers();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("ClusterChartCard (the overview's golden chart card)", () => {
|
|
392
|
+
const WINDOW: { start: Date; end: Date } = {
|
|
393
|
+
start: new Date("2026-09-24T10:00:00Z"),
|
|
394
|
+
end: new Date("2026-09-24T10:30:00Z"),
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
test("while the window is unresolved, the skeleton already explains the chart", async () => {
|
|
398
|
+
render(
|
|
399
|
+
<ClusterChartCard
|
|
400
|
+
title="CPU"
|
|
401
|
+
description={TEXT.cpuChart}
|
|
402
|
+
icon={IconProp.ChartBar}
|
|
403
|
+
iconColor="blue"
|
|
404
|
+
data={[]}
|
|
405
|
+
chartWindow={null}
|
|
406
|
+
syncId="sync"
|
|
407
|
+
/>,
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
expect(screen.getByText("CPU")).toBeInTheDocument();
|
|
411
|
+
expect(screen.queryByTestId("line-chart")).not.toBeInTheDocument();
|
|
412
|
+
expect(
|
|
413
|
+
await explanationOf(screen.getByRole("button", { name: "About CPU" })),
|
|
414
|
+
).toBe(TEXT.cpuChart);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("once loaded, the chart and the (i) are both there", async () => {
|
|
418
|
+
render(
|
|
419
|
+
<ClusterChartCard
|
|
420
|
+
title="Memory"
|
|
421
|
+
description={TEXT.memoryChart}
|
|
422
|
+
icon={IconProp.SquareStack}
|
|
423
|
+
iconColor="violet"
|
|
424
|
+
data={[]}
|
|
425
|
+
chartWindow={WINDOW}
|
|
426
|
+
syncId="kubernetes-overview-x"
|
|
427
|
+
/>,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(screen.getByTestId("line-chart")).toBeInTheDocument();
|
|
431
|
+
expect(mockLineChartProps[0]?.["syncid"]).toBe("kubernetes-overview-x");
|
|
432
|
+
expect(mockLineChartProps[0]?.["heightInPx"]).toBe(180);
|
|
433
|
+
expect(
|
|
434
|
+
await explanationOf(screen.getByRole("button", { name: "About Memory" })),
|
|
435
|
+
).toBe(TEXT.memoryChart);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("the header extra (the uptime badge) sits beside the title in both branches", () => {
|
|
439
|
+
const { rerender } = render(
|
|
440
|
+
<ClusterChartCard
|
|
441
|
+
title="Availability"
|
|
442
|
+
description={TEXT.availabilityChart}
|
|
443
|
+
icon={IconProp.Heartbeat}
|
|
444
|
+
iconColor="emerald"
|
|
445
|
+
data={[]}
|
|
446
|
+
chartWindow={null}
|
|
447
|
+
syncId="sync"
|
|
448
|
+
headerExtra={<span>99.9% uptime</span>}
|
|
449
|
+
/>,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
expect(screen.getByText("99.9% uptime")).toBeInTheDocument();
|
|
453
|
+
|
|
454
|
+
rerender(
|
|
455
|
+
<ClusterChartCard
|
|
456
|
+
title="Availability"
|
|
457
|
+
description={TEXT.availabilityChart}
|
|
458
|
+
icon={IconProp.Heartbeat}
|
|
459
|
+
iconColor="emerald"
|
|
460
|
+
data={[]}
|
|
461
|
+
chartWindow={WINDOW}
|
|
462
|
+
syncId="sync"
|
|
463
|
+
headerExtra={<span>99.9% uptime</span>}
|
|
464
|
+
/>,
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
expect(screen.getByText("99.9% uptime")).toBeInTheDocument();
|
|
468
|
+
expect(
|
|
469
|
+
screen.getByRole("button", { name: "About Availability" }),
|
|
470
|
+
).toBeInTheDocument();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("a chart without a description draws no (i)", () => {
|
|
474
|
+
render(
|
|
475
|
+
<ClusterChartCard
|
|
476
|
+
title="CPU"
|
|
477
|
+
icon={IconProp.ChartBar}
|
|
478
|
+
iconColor="blue"
|
|
479
|
+
data={[]}
|
|
480
|
+
chartWindow={WINDOW}
|
|
481
|
+
syncId="sync"
|
|
482
|
+
/>,
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
expect(infoButtons()).toHaveLength(0);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("the default y axis is a 0-100% scale; a custom one replaces it", () => {
|
|
489
|
+
render(
|
|
490
|
+
<ClusterChartCard
|
|
491
|
+
title="CPU"
|
|
492
|
+
description={TEXT.cpuChart}
|
|
493
|
+
icon={IconProp.ChartBar}
|
|
494
|
+
iconColor="blue"
|
|
495
|
+
data={[]}
|
|
496
|
+
chartWindow={WINDOW}
|
|
497
|
+
syncId="sync"
|
|
498
|
+
/>,
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const yAxis: { legend: string; options: { max: unknown } } =
|
|
502
|
+
mockLineChartProps[0]?.["yAxis"] as {
|
|
503
|
+
legend: string;
|
|
504
|
+
options: { max: unknown };
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
expect(yAxis.legend).toBe("%");
|
|
508
|
+
expect(yAxis.options.max).toBe(100);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe("titleWithTooltip", () => {
|
|
513
|
+
test("draws the title and an (i) named after it", async () => {
|
|
514
|
+
render(<h4>{titleWithTooltip("Pod Health", TEXT.podHealth)}</h4>);
|
|
515
|
+
|
|
516
|
+
expect(screen.getByRole("heading")).toHaveTextContent("Pod Health");
|
|
517
|
+
expect(
|
|
518
|
+
await explanationOf(
|
|
519
|
+
screen.getByRole("button", { name: "About Pod Health" }),
|
|
520
|
+
),
|
|
521
|
+
).toBe(TEXT.podHealth);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe("the cluster overview page", () => {
|
|
526
|
+
test("every metric on a busy cluster carries an (i) - all twenty-six of them", async () => {
|
|
527
|
+
await renderOverview();
|
|
528
|
+
|
|
529
|
+
const names: Array<string> = infoButtons().map(
|
|
530
|
+
(button: HTMLElement): string => {
|
|
531
|
+
return button.getAttribute("aria-label") || "";
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
expect(names).toEqual(
|
|
536
|
+
EXPECTED.map((pair: [string, string]): string => {
|
|
537
|
+
return `About ${pair[0]}`;
|
|
538
|
+
}),
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("each (i) explains its own metric", async () => {
|
|
543
|
+
await renderOverview();
|
|
544
|
+
|
|
545
|
+
const buttons: Array<HTMLElement> = infoButtons();
|
|
546
|
+
|
|
547
|
+
expect(buttons).toHaveLength(EXPECTED.length);
|
|
548
|
+
|
|
549
|
+
for (let index: number = 0; index < EXPECTED.length; index++) {
|
|
550
|
+
const [title, text] = EXPECTED[index]!;
|
|
551
|
+
|
|
552
|
+
expect({ title, shown: await explanationOf(buttons[index]!) }).toEqual({
|
|
553
|
+
title,
|
|
554
|
+
shown: text,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("the page shows every overview description in the record (Insights has the other)", async () => {
|
|
560
|
+
await renderOverview();
|
|
561
|
+
|
|
562
|
+
const shown: Set<string> = new Set(
|
|
563
|
+
EXPECTED.map((pair: [string, string]): string => {
|
|
564
|
+
return pair[1];
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
for (const [key, text] of Object.entries(TEXT)) {
|
|
569
|
+
expect({ key, shown: shown.has(text) }).toEqual({
|
|
570
|
+
key,
|
|
571
|
+
shown: key !== "networkThroughput",
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("no (i) is nested inside another button or a link", async () => {
|
|
577
|
+
await renderOverview();
|
|
578
|
+
|
|
579
|
+
for (const button of infoButtons()) {
|
|
580
|
+
expect(button.parentElement?.closest("button, a")).toBeNull();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("an (i) on a clickable summary card does not navigate", async () => {
|
|
585
|
+
await renderOverview();
|
|
586
|
+
|
|
587
|
+
const nodes: HTMLElement = screen.getByRole("button", {
|
|
588
|
+
name: "About Nodes",
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// The (i) swallows the click, so asking what "Nodes" means stays here.
|
|
592
|
+
expect(fireEvent.click(nodes)).toBe(false);
|
|
593
|
+
fireEvent.keyDown(nodes, { key: "Enter" });
|
|
594
|
+
expect(mockNavigations).toHaveLength(0);
|
|
595
|
+
|
|
596
|
+
// The card itself still navigates, so the check above means something.
|
|
597
|
+
fireEvent.click(cardActivatorOf(nodes));
|
|
598
|
+
expect(mockNavigations).toHaveLength(1);
|
|
599
|
+
expect(String(mockNavigations[0])).toContain("/nodes");
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("the chips and badges only appear - with their (i) - when there is something to count", async () => {
|
|
603
|
+
mockSummary = QUIET_SUMMARY;
|
|
604
|
+
|
|
605
|
+
render(<KubernetesClusterOverview {...PAGE_PROPS} />);
|
|
606
|
+
|
|
607
|
+
await screen.findByRole("button", { name: "About Memory Usage" });
|
|
608
|
+
|
|
609
|
+
for (const title of [
|
|
610
|
+
"Running",
|
|
611
|
+
"Pending",
|
|
612
|
+
"Failed",
|
|
613
|
+
"Nodes Not Ready",
|
|
614
|
+
"Memory Pressure",
|
|
615
|
+
"Disk Pressure",
|
|
616
|
+
"PID Pressure",
|
|
617
|
+
]) {
|
|
618
|
+
expect(
|
|
619
|
+
screen.queryByRole("button", { name: `About ${title}` }),
|
|
620
|
+
).not.toBeInTheDocument();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// What does not depend on those counts is still explained.
|
|
624
|
+
for (const title of [
|
|
625
|
+
"Cluster inventory counts",
|
|
626
|
+
"Cluster Health",
|
|
627
|
+
"Nodes",
|
|
628
|
+
"Agent Status",
|
|
629
|
+
"Pod Health",
|
|
630
|
+
"CPU Usage",
|
|
631
|
+
]) {
|
|
632
|
+
expect(
|
|
633
|
+
screen.getByRole("button", { name: `About ${title}` }),
|
|
634
|
+
).toBeInTheDocument();
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test("a pending-pod chip and its (i) read as one unit", async () => {
|
|
639
|
+
await renderOverview();
|
|
640
|
+
|
|
641
|
+
const info: HTMLElement = screen.getByRole("button", {
|
|
642
|
+
name: "About Pending",
|
|
643
|
+
});
|
|
644
|
+
const chip: HTMLElement | null = info.closest("span.rounded-full");
|
|
645
|
+
|
|
646
|
+
expect(chip).not.toBeNull();
|
|
647
|
+
expect(chip).toHaveTextContent("2Pending");
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test("a pressure badge's (i) sits right beside the badge it explains", async () => {
|
|
651
|
+
await renderOverview();
|
|
652
|
+
|
|
653
|
+
const info: HTMLElement = screen.getByRole("button", {
|
|
654
|
+
name: "About Disk Pressure",
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
expect(info.parentElement?.parentElement).toHaveTextContent(
|
|
658
|
+
"1 node: Disk Pressure",
|
|
659
|
+
);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test("a cluster with no pods has no Pod Health card, and no (i) for it", async () => {
|
|
663
|
+
mockSummary = { ...QUIET_SUMMARY, podCount: 0 };
|
|
664
|
+
|
|
665
|
+
render(<KubernetesClusterOverview {...PAGE_PROPS} />);
|
|
666
|
+
|
|
667
|
+
await screen.findByRole("button", { name: "About Memory Usage" });
|
|
668
|
+
|
|
669
|
+
expect(
|
|
670
|
+
screen.queryByRole("button", { name: "About Pod Health" }),
|
|
671
|
+
).not.toBeInTheDocument();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("tiles with no data yet still explain what they would show", async () => {
|
|
675
|
+
await renderOverview();
|
|
676
|
+
|
|
677
|
+
// The stubbed aggregates return nothing, so every value tile reads "-".
|
|
678
|
+
expect(screen.getAllByText("\u2014").length).toBeGreaterThanOrEqual(4);
|
|
679
|
+
|
|
680
|
+
for (const title of ["CPU", "Memory", "Filesystem", "Network"]) {
|
|
681
|
+
const [tile] = screen.getAllByRole("button", { name: `About ${title}` });
|
|
682
|
+
|
|
683
|
+
expect(tile).toBeDefined();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
expect(
|
|
687
|
+
await explanationOf(
|
|
688
|
+
screen.getAllByRole("button", { name: "About Filesystem" })[0]!,
|
|
689
|
+
),
|
|
690
|
+
).toBe(TEXT.filesystem);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("the page loads the cluster named in the route", async () => {
|
|
694
|
+
await renderOverview();
|
|
695
|
+
|
|
696
|
+
expect(screen.getByText("production-us-east")).toBeInTheDocument();
|
|
697
|
+
expect(screen.getByText("production-us-east-1")).toBeInTheDocument();
|
|
698
|
+
});
|
|
699
|
+
});
|