@oneuptime/common 14.0.0 → 14.0.1
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/Index.ts +4 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorService.ts +3 -2
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Markdown.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.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/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +3 -2
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeAll,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
jest,
|
|
8
|
+
test,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
import "@testing-library/jest-dom";
|
|
11
|
+
import {
|
|
12
|
+
act,
|
|
13
|
+
cleanup,
|
|
14
|
+
fireEvent,
|
|
15
|
+
render,
|
|
16
|
+
screen,
|
|
17
|
+
within,
|
|
18
|
+
} from "@testing-library/react";
|
|
19
|
+
import * as fs from "fs";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
import * as React from "react";
|
|
22
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
23
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
24
|
+
import URL from "../../../Types/API/URL";
|
|
25
|
+
import { DOCS_URL } from "../../../UI/Config";
|
|
26
|
+
import DashboardNavbar from "../../../../App/FeatureSet/Dashboard/src/Components/NavBar/NavBar";
|
|
27
|
+
import GettingStarted from "../../../../App/FeatureSet/Dashboard/src/Components/Home/GettingStarted";
|
|
28
|
+
import HowOneUptimeWorks, {
|
|
29
|
+
HOW_IT_WORKS_FOOTNOTE,
|
|
30
|
+
HOW_IT_WORKS_STEPS,
|
|
31
|
+
HOW_IT_WORKS_TITLE,
|
|
32
|
+
HowItWorksStep,
|
|
33
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Home/HowOneUptimeWorks";
|
|
34
|
+
import Help from "../../../../App/FeatureSet/Dashboard/src/Components/Header/Help";
|
|
35
|
+
import RouteMap from "../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap";
|
|
36
|
+
import PageMap from "../../../../App/FeatureSet/Dashboard/src/Utils/PageMap";
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* "OneUptime is vast and users get confused" — this suite pins the fixes that
|
|
40
|
+
* orient a new user, by mounting the real components and reading them the
|
|
41
|
+
* way a user would:
|
|
42
|
+
*
|
|
43
|
+
* - the Products menu groups 40 products into small, named sections, with
|
|
44
|
+
* the core products first and in the order a problem flows through them;
|
|
45
|
+
* - Home explains how the core products fit together;
|
|
46
|
+
* - Help links to the documentation.
|
|
47
|
+
*
|
|
48
|
+
* react-i18next is answered from the REAL locale files, so the copy asserted
|
|
49
|
+
* here is the copy users see (and a missing key shows up as a raw key).
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
const LOCALES_DIR: string = path.join(
|
|
53
|
+
__dirname,
|
|
54
|
+
"../../../../App/FeatureSet/Dashboard/src/Locales",
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
type LocaleData = Record<string, unknown>;
|
|
58
|
+
|
|
59
|
+
function readLocale(code: string): LocaleData {
|
|
60
|
+
return JSON.parse(
|
|
61
|
+
fs.readFileSync(path.join(LOCALES_DIR, `${code}.json`), "utf8"),
|
|
62
|
+
) as LocaleData;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const EN: LocaleData = readLocale("en");
|
|
66
|
+
const DE: LocaleData = readLocale("de");
|
|
67
|
+
|
|
68
|
+
let activeLocale: LocaleData = EN;
|
|
69
|
+
|
|
70
|
+
function lookupNested(data: LocaleData, key: string): string | undefined {
|
|
71
|
+
let cursor: unknown = data;
|
|
72
|
+
for (const part of key.split(".")) {
|
|
73
|
+
if (!cursor || typeof cursor !== "object") {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
cursor = (cursor as Record<string, unknown>)[part];
|
|
77
|
+
}
|
|
78
|
+
return typeof cursor === "string" ? cursor : undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const navigateMock: MockFunction = getJestMockFunction();
|
|
82
|
+
const countMock: MockFunction = getJestMockFunction();
|
|
83
|
+
const captureMock: MockFunction = getJestMockFunction();
|
|
84
|
+
const getCurrentProjectIdMock: MockFunction = getJestMockFunction();
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Mirrors i18next's two lookup styles the dashboard uses: t("a.b.c", default)
|
|
88
|
+
* for nested keys, and t(text, { keySeparator: false, defaultValue }) for the
|
|
89
|
+
* flat English-text keys behind useTranslateValue. Lazily dereferenced, because
|
|
90
|
+
* jest.mock factories are hoisted above the consts they read.
|
|
91
|
+
*/
|
|
92
|
+
jest.mock("react-i18next", () => {
|
|
93
|
+
return {
|
|
94
|
+
useTranslation: () => {
|
|
95
|
+
return {
|
|
96
|
+
t: (key: string, options?: unknown): string => {
|
|
97
|
+
if (
|
|
98
|
+
options &&
|
|
99
|
+
typeof options === "object" &&
|
|
100
|
+
(options as { keySeparator?: unknown }).keySeparator === false
|
|
101
|
+
) {
|
|
102
|
+
const flat: unknown = activeLocale[key];
|
|
103
|
+
return typeof flat === "string"
|
|
104
|
+
? flat
|
|
105
|
+
: (options as { defaultValue?: string }).defaultValue ?? key;
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
lookupNested(activeLocale, key) ??
|
|
109
|
+
(typeof options === "string" ? options : key)
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
118
|
+
return {
|
|
119
|
+
__esModule: true,
|
|
120
|
+
default: {
|
|
121
|
+
count: (...args: Array<unknown>) => {
|
|
122
|
+
return countMock(...args);
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
129
|
+
return {
|
|
130
|
+
__esModule: true,
|
|
131
|
+
default: {
|
|
132
|
+
isOnThisPage: () => {
|
|
133
|
+
return false;
|
|
134
|
+
},
|
|
135
|
+
isStartWith: () => {
|
|
136
|
+
return false;
|
|
137
|
+
},
|
|
138
|
+
navigate: (...args: Array<unknown>) => {
|
|
139
|
+
return navigateMock(...args);
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
jest.mock("../../../UI/Utils/Analytics", () => {
|
|
146
|
+
return {
|
|
147
|
+
__esModule: true,
|
|
148
|
+
default: {
|
|
149
|
+
capture: (...args: Array<unknown>) => {
|
|
150
|
+
return captureMock(...args);
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
157
|
+
return {
|
|
158
|
+
__esModule: true,
|
|
159
|
+
default: {
|
|
160
|
+
getCurrentProjectId: (...args: Array<unknown>) => {
|
|
161
|
+
return getCurrentProjectIdMock(...args);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const PROJECT_ID: string = "65f00000000000000000aaaa";
|
|
168
|
+
|
|
169
|
+
type CountFor = (modelName: string) => number;
|
|
170
|
+
|
|
171
|
+
function answerCounts(countFor: CountFor): void {
|
|
172
|
+
countMock.mockImplementation((...args: Array<unknown>) => {
|
|
173
|
+
const request: { modelType: { name: string } } = args[0] as {
|
|
174
|
+
modelType: { name: string };
|
|
175
|
+
};
|
|
176
|
+
return Promise.resolve(countFor(request.modelType.name));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Monitor + team done, status page + on-call still to do.
|
|
181
|
+
const HALF_DONE: CountFor = (modelName: string): number => {
|
|
182
|
+
if (modelName === "Monitor") {
|
|
183
|
+
return 3;
|
|
184
|
+
}
|
|
185
|
+
if (modelName === "TeamMember") {
|
|
186
|
+
return 2;
|
|
187
|
+
}
|
|
188
|
+
return 0;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
async function renderGettingStarted(): Promise<void> {
|
|
192
|
+
await act(async () => {
|
|
193
|
+
render(<GettingStarted projectId={new ObjectID(PROJECT_ID)} />);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function productsMenu(): HTMLElement {
|
|
198
|
+
return screen.getByRole("dialog", { name: "Products menu" });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function openProductsMenu(): void {
|
|
202
|
+
render(<DashboardNavbar show={true} />);
|
|
203
|
+
// The button label is itself translated.
|
|
204
|
+
fireEvent.click(screen.getByText(String(activeLocale["Products"])));
|
|
205
|
+
act(() => {
|
|
206
|
+
jest.runOnlyPendingTimers();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface MenuSection {
|
|
211
|
+
heading: string;
|
|
212
|
+
items: Array<string>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Reads the open menu top to bottom, the way a user scans it.
|
|
216
|
+
function readMenuSections(): Array<MenuSection> {
|
|
217
|
+
const listbox: HTMLElement = within(productsMenu()).getByRole("listbox");
|
|
218
|
+
return Array.from(listbox.querySelectorAll("h3")).map(
|
|
219
|
+
(heading: HTMLHeadingElement): MenuSection => {
|
|
220
|
+
const section: HTMLElement = heading.closest(".mb-6") as HTMLElement;
|
|
221
|
+
const items: Array<string> = within(section)
|
|
222
|
+
.getAllByRole("option")
|
|
223
|
+
.map((option: HTMLElement): string => {
|
|
224
|
+
return option.querySelector(".font-medium, .text-sm")!.textContent!;
|
|
225
|
+
});
|
|
226
|
+
return { heading: heading.textContent || "", items };
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function optionFor(title: string): HTMLElement {
|
|
232
|
+
return within(productsMenu())
|
|
233
|
+
.getAllByRole("option")
|
|
234
|
+
.find((option: HTMLElement): boolean => {
|
|
235
|
+
return (option.textContent || "").startsWith(title);
|
|
236
|
+
})!;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
beforeAll(() => {
|
|
240
|
+
Element.prototype.scrollIntoView = (): void => {};
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
beforeEach(() => {
|
|
244
|
+
jest.useFakeTimers();
|
|
245
|
+
window.localStorage.clear();
|
|
246
|
+
activeLocale = EN;
|
|
247
|
+
getCurrentProjectIdMock.mockReturnValue(new ObjectID(PROJECT_ID));
|
|
248
|
+
answerCounts(HALF_DONE);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
afterEach(() => {
|
|
252
|
+
cleanup();
|
|
253
|
+
jest.clearAllTimers();
|
|
254
|
+
jest.useRealTimers();
|
|
255
|
+
navigateMock.mockReset();
|
|
256
|
+
countMock.mockReset();
|
|
257
|
+
captureMock.mockReset();
|
|
258
|
+
getCurrentProjectIdMock.mockReset();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("Products menu sections", () => {
|
|
262
|
+
test("sections appear in a deliberate order with plain names", () => {
|
|
263
|
+
openProductsMenu();
|
|
264
|
+
|
|
265
|
+
expect(
|
|
266
|
+
readMenuSections().map((section: MenuSection): string => {
|
|
267
|
+
return section.heading;
|
|
268
|
+
}),
|
|
269
|
+
).toEqual([
|
|
270
|
+
"Essentials",
|
|
271
|
+
"Observability",
|
|
272
|
+
"AI",
|
|
273
|
+
"Resources",
|
|
274
|
+
"Infrastructure",
|
|
275
|
+
"Dashboards & Automation",
|
|
276
|
+
"Settings",
|
|
277
|
+
]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("no section is a catch-all called 'More' inside the products menu", () => {
|
|
281
|
+
openProductsMenu();
|
|
282
|
+
|
|
283
|
+
for (const section of readMenuSections()) {
|
|
284
|
+
expect(section.heading).not.toBe("More");
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("no section is so long that it stops being scannable", () => {
|
|
289
|
+
openProductsMenu();
|
|
290
|
+
|
|
291
|
+
for (const section of readMenuSections()) {
|
|
292
|
+
expect(section.items.length).toBeLessThanOrEqual(12);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("Essentials follow a problem from detection to customer update", () => {
|
|
297
|
+
openProductsMenu();
|
|
298
|
+
|
|
299
|
+
expect(readMenuSections()[0]).toEqual({
|
|
300
|
+
heading: "Essentials",
|
|
301
|
+
items: [
|
|
302
|
+
"Monitors",
|
|
303
|
+
"Incidents",
|
|
304
|
+
"Alerts",
|
|
305
|
+
"On-Call Duty",
|
|
306
|
+
"Status Pages",
|
|
307
|
+
"Scheduled Maintenance",
|
|
308
|
+
"SLOs",
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("every per-platform product is under Infrastructure, most common first", () => {
|
|
314
|
+
openProductsMenu();
|
|
315
|
+
|
|
316
|
+
const infrastructure: MenuSection = readMenuSections().find(
|
|
317
|
+
(section: MenuSection): boolean => {
|
|
318
|
+
return section.heading === "Infrastructure";
|
|
319
|
+
},
|
|
320
|
+
)!;
|
|
321
|
+
|
|
322
|
+
expect(infrastructure.items).toEqual([
|
|
323
|
+
"Hosts",
|
|
324
|
+
"Kubernetes",
|
|
325
|
+
"Docker",
|
|
326
|
+
"Docker Swarm",
|
|
327
|
+
"Podman",
|
|
328
|
+
"Serverless",
|
|
329
|
+
"Cloud",
|
|
330
|
+
"Proxmox",
|
|
331
|
+
"VMware",
|
|
332
|
+
"Ceph",
|
|
333
|
+
"Network",
|
|
334
|
+
"IoT",
|
|
335
|
+
]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("Resources keeps only the catalogs that span every platform", () => {
|
|
339
|
+
openProductsMenu();
|
|
340
|
+
|
|
341
|
+
const resources: MenuSection = readMenuSections().find(
|
|
342
|
+
(section: MenuSection): boolean => {
|
|
343
|
+
return section.heading === "Resources";
|
|
344
|
+
},
|
|
345
|
+
)!;
|
|
346
|
+
|
|
347
|
+
expect(resources.items).toEqual([
|
|
348
|
+
"Inventory",
|
|
349
|
+
"Services",
|
|
350
|
+
"Real User Monitoring",
|
|
351
|
+
]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("regrouping lost no product: all 40 are still listed exactly once", () => {
|
|
355
|
+
openProductsMenu();
|
|
356
|
+
|
|
357
|
+
const titles: Array<string> = readMenuSections().flatMap(
|
|
358
|
+
(section: MenuSection): Array<string> => {
|
|
359
|
+
return section.items;
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
expect(titles).toHaveLength(40);
|
|
363
|
+
expect(new Set(titles).size).toBe(40);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("Products menu descriptions say what each product is for", () => {
|
|
368
|
+
test.each([
|
|
369
|
+
["Alerts", "Problems for your team to look into before users notice."],
|
|
370
|
+
["Incidents", "Problems that affect your users — respond and resolve."],
|
|
371
|
+
["Monitors", "Check websites, APIs and servers, and know when they fail."],
|
|
372
|
+
["On-Call Duty", "Who gets paged, and who is next if nobody answers."],
|
|
373
|
+
["Status Pages", "Show your customers what is up and what is down."],
|
|
374
|
+
])("%s", (title: string, description: string) => {
|
|
375
|
+
openProductsMenu();
|
|
376
|
+
|
|
377
|
+
expect(optionFor(title)).toHaveTextContent(description);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test.each([
|
|
381
|
+
["Alerts", "Notification management."],
|
|
382
|
+
["Monitors", "Monitor any resource."],
|
|
383
|
+
["Incidents", "Detect and resolve fast."],
|
|
384
|
+
["Status Pages", "Real-time status updates."],
|
|
385
|
+
["Project Settings", "Configure your project."],
|
|
386
|
+
])(
|
|
387
|
+
"%s no longer uses the vague description %p",
|
|
388
|
+
(title: string, oldDescription: string) => {
|
|
389
|
+
openProductsMenu();
|
|
390
|
+
|
|
391
|
+
expect(optionFor(title)).not.toHaveTextContent(oldDescription);
|
|
392
|
+
},
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
test("Alerts and Incidents are told apart by who is affected", () => {
|
|
396
|
+
openProductsMenu();
|
|
397
|
+
|
|
398
|
+
expect(optionFor("Incidents")).toHaveTextContent("affect your users");
|
|
399
|
+
expect(optionFor("Alerts")).toHaveTextContent("before users notice");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("the grouping is localised too (German)", () => {
|
|
403
|
+
activeLocale = DE;
|
|
404
|
+
openProductsMenu();
|
|
405
|
+
|
|
406
|
+
const headings: Array<string> = readMenuSections().map(
|
|
407
|
+
(section: MenuSection): string => {
|
|
408
|
+
return section.heading;
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
expect(headings).toContain(
|
|
412
|
+
lookupNested(DE, "navbar.categories.infrastructure"),
|
|
413
|
+
);
|
|
414
|
+
expect(headings).toContain(
|
|
415
|
+
lookupNested(DE, "navbar.categories.analyticsAutomation"),
|
|
416
|
+
);
|
|
417
|
+
expect(headings).not.toContain("navbar.categories.infrastructure");
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("How OneUptime works (Home)", () => {
|
|
422
|
+
test("the Getting Started card explains how the core products connect", async () => {
|
|
423
|
+
await renderGettingStarted();
|
|
424
|
+
|
|
425
|
+
const strip: HTMLElement = screen.getByTestId("how-oneuptime-works");
|
|
426
|
+
expect(strip).toHaveTextContent(HOW_IT_WORKS_TITLE);
|
|
427
|
+
expect(strip).toHaveTextContent(HOW_IT_WORKS_FOOTNOTE);
|
|
428
|
+
expect(
|
|
429
|
+
within(strip)
|
|
430
|
+
.getAllByRole("button")
|
|
431
|
+
.map((button: HTMLElement): string => {
|
|
432
|
+
return button.getAttribute("data-testid") || "";
|
|
433
|
+
}),
|
|
434
|
+
).toEqual([
|
|
435
|
+
"how-it-works-step-monitor",
|
|
436
|
+
"how-it-works-step-incident",
|
|
437
|
+
"how-it-works-step-on-call",
|
|
438
|
+
"how-it-works-step-status-page",
|
|
439
|
+
]);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("steps are numbered in flow order and read as a sentence", async () => {
|
|
443
|
+
await renderGettingStarted();
|
|
444
|
+
|
|
445
|
+
const steps: Array<HTMLElement> = within(
|
|
446
|
+
screen.getByTestId("how-oneuptime-works"),
|
|
447
|
+
).getAllByRole("listitem");
|
|
448
|
+
|
|
449
|
+
expect(steps).toHaveLength(4);
|
|
450
|
+
HOW_IT_WORKS_STEPS.forEach((step: HowItWorksStep, index: number) => {
|
|
451
|
+
expect(steps[index]).toHaveTextContent(`${index + 1}.${step.title}`);
|
|
452
|
+
expect(steps[index]).toHaveTextContent(step.description);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("the strip tells the user the rest of the product is optional", async () => {
|
|
457
|
+
await renderGettingStarted();
|
|
458
|
+
|
|
459
|
+
expect(
|
|
460
|
+
screen.getByTestId("how-oneuptime-works-footnote"),
|
|
461
|
+
).toHaveTextContent("optional");
|
|
462
|
+
expect(
|
|
463
|
+
screen.getByTestId("how-oneuptime-works-footnote"),
|
|
464
|
+
).toHaveTextContent("Products");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test.each([
|
|
468
|
+
["monitor", PageMap.MONITORS],
|
|
469
|
+
["incident", PageMap.INCIDENTS],
|
|
470
|
+
["on-call", PageMap.ON_CALL_DUTY],
|
|
471
|
+
["status-page", PageMap.STATUS_PAGES],
|
|
472
|
+
])(
|
|
473
|
+
"clicking the %s step opens that product in the current project",
|
|
474
|
+
async (key: string, pageMap: PageMap) => {
|
|
475
|
+
await renderGettingStarted();
|
|
476
|
+
|
|
477
|
+
fireEvent.click(screen.getByTestId(`how-it-works-step-${key}`));
|
|
478
|
+
|
|
479
|
+
expect(navigateMock).toHaveBeenCalledTimes(1);
|
|
480
|
+
const route: string = String(navigateMock.mock.calls[0]![0]);
|
|
481
|
+
expect(route).toContain(PROJECT_ID);
|
|
482
|
+
expect(route).not.toContain(":projectId");
|
|
483
|
+
expect(route).toBe(
|
|
484
|
+
RouteMap[pageMap]!.toString().replace(":projectId", PROJECT_ID),
|
|
485
|
+
);
|
|
486
|
+
expect(captureMock).toHaveBeenCalledWith(
|
|
487
|
+
"dashboard/home/how-it-works-step",
|
|
488
|
+
{ projectId: PROJECT_ID, step: key },
|
|
489
|
+
);
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
test("the steps are the Essentials products, in the menu's order", () => {
|
|
494
|
+
/*
|
|
495
|
+
* Home and the Products menu tell the same story; if someone reorders
|
|
496
|
+
* one, this makes them look at the other.
|
|
497
|
+
*/
|
|
498
|
+
jest.useRealTimers();
|
|
499
|
+
jest.useFakeTimers();
|
|
500
|
+
openProductsMenu();
|
|
501
|
+
const essentials: Array<string> = readMenuSections()[0]!.items;
|
|
502
|
+
|
|
503
|
+
const flow: Array<number> = [
|
|
504
|
+
"Monitors",
|
|
505
|
+
"Incidents",
|
|
506
|
+
"On-Call Duty",
|
|
507
|
+
"Status Pages",
|
|
508
|
+
].map((title: string): number => {
|
|
509
|
+
return essentials.indexOf(title);
|
|
510
|
+
});
|
|
511
|
+
expect(
|
|
512
|
+
flow.every((index: number) => {
|
|
513
|
+
return index >= 0;
|
|
514
|
+
}),
|
|
515
|
+
).toBe(true);
|
|
516
|
+
expect(
|
|
517
|
+
[...flow].sort((a: number, b: number) => {
|
|
518
|
+
return a - b;
|
|
519
|
+
}),
|
|
520
|
+
).toEqual(flow);
|
|
521
|
+
expect(
|
|
522
|
+
HOW_IT_WORKS_STEPS.map((step: HowItWorksStep) => {
|
|
523
|
+
return step.pageMap;
|
|
524
|
+
}),
|
|
525
|
+
).toEqual([
|
|
526
|
+
PageMap.MONITORS,
|
|
527
|
+
PageMap.INCIDENTS,
|
|
528
|
+
PageMap.ON_CALL_DUTY,
|
|
529
|
+
PageMap.STATUS_PAGES,
|
|
530
|
+
]);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
test("dismissing Getting Started also removes the explainer", async () => {
|
|
534
|
+
await renderGettingStarted();
|
|
535
|
+
expect(screen.getByTestId("how-oneuptime-works")).toBeInTheDocument();
|
|
536
|
+
|
|
537
|
+
fireEvent.click(screen.getByTestId("getting-started-dismiss"));
|
|
538
|
+
|
|
539
|
+
expect(screen.queryByTestId("how-oneuptime-works")).toBeNull();
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("a fully set-up project does not see the explainer", async () => {
|
|
543
|
+
answerCounts((): number => {
|
|
544
|
+
return 5;
|
|
545
|
+
});
|
|
546
|
+
await renderGettingStarted();
|
|
547
|
+
|
|
548
|
+
expect(screen.queryByTestId("how-oneuptime-works")).toBeNull();
|
|
549
|
+
expect(screen.queryByTestId("getting-started")).toBeNull();
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test("the explainer and the checklist are translated (German)", async () => {
|
|
553
|
+
activeLocale = DE;
|
|
554
|
+
await renderGettingStarted();
|
|
555
|
+
|
|
556
|
+
const card: HTMLElement = screen.getByTestId("getting-started");
|
|
557
|
+
for (const english of [
|
|
558
|
+
HOW_IT_WORKS_TITLE,
|
|
559
|
+
HOW_IT_WORKS_FOOTNOTE,
|
|
560
|
+
"Create your first monitor",
|
|
561
|
+
"Publish a status page",
|
|
562
|
+
"steps completed",
|
|
563
|
+
]) {
|
|
564
|
+
const german: unknown = DE[english];
|
|
565
|
+
expect(typeof german).toBe("string");
|
|
566
|
+
expect(german).not.toBe(english);
|
|
567
|
+
expect(card).toHaveTextContent(german as string);
|
|
568
|
+
expect(card).not.toHaveTextContent(english);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("the strip also renders on its own, outside the checklist", () => {
|
|
573
|
+
render(<HowOneUptimeWorks projectId={PROJECT_ID} />);
|
|
574
|
+
|
|
575
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(
|
|
576
|
+
HOW_IT_WORKS_STEPS.length,
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
describe("Help menu", () => {
|
|
582
|
+
function openHelp(): Array<HTMLAnchorElement | HTMLElement> {
|
|
583
|
+
render(<Help />);
|
|
584
|
+
fireEvent.click(screen.getByRole("button"));
|
|
585
|
+
return screen.getAllByRole("link").concat(
|
|
586
|
+
screen.queryAllByRole("button").filter((element: HTMLElement) => {
|
|
587
|
+
return element.tagName !== "BUTTON";
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
test("links to the documentation of this OneUptime instance", () => {
|
|
593
|
+
openHelp();
|
|
594
|
+
|
|
595
|
+
const docs: HTMLElement = screen.getByText("Documentation").closest("a")!;
|
|
596
|
+
expect(docs).toHaveAttribute(
|
|
597
|
+
"href",
|
|
598
|
+
URL.fromString(DOCS_URL.toString()).toString(),
|
|
599
|
+
);
|
|
600
|
+
expect(docs.getAttribute("href")).toContain(DOCS_URL.toString());
|
|
601
|
+
expect(docs).toHaveAttribute("target", "_blank");
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test("Documentation is the first entry", () => {
|
|
605
|
+
const entries: Array<HTMLElement> = openHelp();
|
|
606
|
+
|
|
607
|
+
const texts: Array<string> = entries.map((entry: HTMLElement) => {
|
|
608
|
+
return entry.textContent || "";
|
|
609
|
+
});
|
|
610
|
+
const docsIndex: number = texts.findIndex((text: string) => {
|
|
611
|
+
return text.includes("Documentation");
|
|
612
|
+
});
|
|
613
|
+
const firstEntry: HTMLElement = screen
|
|
614
|
+
.getByText("Documentation")
|
|
615
|
+
.closest("a")!;
|
|
616
|
+
expect(docsIndex).toBeGreaterThanOrEqual(0);
|
|
617
|
+
// Nothing precedes it in document order.
|
|
618
|
+
for (const entry of entries) {
|
|
619
|
+
if (entry === firstEntry) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
expect(
|
|
623
|
+
firstEntry.compareDocumentPosition(entry) &
|
|
624
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
625
|
+
).toBeTruthy();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test("the entry closes the menu when used", () => {
|
|
630
|
+
openHelp();
|
|
631
|
+
|
|
632
|
+
fireEvent.click(screen.getByText("Documentation"));
|
|
633
|
+
|
|
634
|
+
expect(screen.queryByText("Documentation")).toBeNull();
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("the label is translated (German)", () => {
|
|
638
|
+
activeLocale = DE;
|
|
639
|
+
render(<Help />);
|
|
640
|
+
fireEvent.click(screen.getByRole("button"));
|
|
641
|
+
|
|
642
|
+
const german: string = lookupNested(DE, "help.documentation")!;
|
|
643
|
+
expect(german).toBeTruthy();
|
|
644
|
+
expect(screen.getByText(german)).toBeInTheDocument();
|
|
645
|
+
});
|
|
646
|
+
});
|