@oneuptime/common 12.0.19 → 12.0.21
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/AnalyticsModels/ChangeEvent.ts +331 -0
- package/Models/AnalyticsModels/Index.ts +2 -0
- package/Server/API/EnterpriseLicenseAPI.ts +94 -19
- package/Server/EnvironmentConfig.ts +15 -5
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +0 -2
- package/Server/Services/ChangeEventService.ts +11 -0
- package/Server/Services/EnterpriseLicenseService.ts +3 -3
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/ProjectService.ts +104 -0
- package/Server/Utils/Attribution.ts +11 -11
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +4 -5
- package/Server/Utils/Response.ts +2 -2
- package/Server/Utils/StartServer.ts +2 -2
- package/Server/Utils/Telemetry/ChangeEventRow.ts +228 -0
- package/Server/Views/Partials/AnalyticsConsent.ejs +441 -0
- package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +366 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
- package/Tests/App/Dashboard/EventOverlayHook.test.tsx +249 -0
- package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
- package/Tests/App/Dashboard/InvestigationArtifacts.test.tsx +315 -0
- package/Tests/App/Dashboard/InvestigationDrawer.test.tsx +258 -0
- package/Tests/App/Dashboard/MetricChartsCompare.test.tsx +171 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
- package/Tests/App/Dashboard/MetricViewCompare.test.tsx +233 -0
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -0
- package/Tests/Server/Infrastructure/Postgres/AddUserSlackAndMicrosoftTeamsMigration.test.ts +236 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +138 -0
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
- package/Tests/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.test.ts +224 -0
- package/Tests/Server/Utils/Attribution.test.ts +4 -5
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +2 -2
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.test.ts +300 -0
- package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +53 -0
- package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
- package/Tests/UI/Components/Charts/ChartGhostSeries.test.tsx +103 -0
- package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
- package/Tests/UI/Components/Charts/TooltipEntries.test.ts +191 -0
- package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
- package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
- package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +13 -2
- package/Types/Analytics/RevenueEvent.ts +1 -0
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +7 -0
- package/Types/Marketing/Attribution.ts +9 -15
- package/Types/Marketing/MarketingEvent.ts +19 -12
- package/UI/Components/Charts/Area/AreaChart.tsx +23 -1
- package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +16 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +208 -97
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +26 -3
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +232 -118
- package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +120 -0
- package/UI/Components/Charts/Line/LineChart.tsx +23 -1
- package/UI/Components/Detail/Detail.tsx +18 -1
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -0
- package/Utils/Metrics/MetricExplorerUrl.ts +119 -5
- package/Utils/Telemetry/CrossSignalScope.ts +19 -10
- package/build/dist/Models/AnalyticsModels/ChangeEvent.js +298 -0
- package/build/dist/Models/AnalyticsModels/ChangeEvent.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/Index.js +2 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +66 -12
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -5
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +0 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/ChangeEventService.js +9 -0
- package/build/dist/Server/Services/ChangeEventService.js.map +1 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js +3 -3
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +88 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js +6 -6
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +4 -5
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +2 -2
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +2 -2
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ChangeEventRow.js +150 -0
- package/build/dist/Server/Utils/Telemetry/ChangeEventRow.js.map +1 -0
- package/build/dist/Types/Analytics/RevenueEvent.js +1 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +7 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/Marketing/Attribution.js +9 -15
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +12 -4
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +10 -2
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +9 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +114 -51
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +35 -23
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +122 -55
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +86 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +10 -2
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +17 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js +81 -0
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +14 -8
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +0 -117
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +0 -106
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +0 -1
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import BasicForm from "../../../../UI/Components/Forms/BasicForm";
|
|
2
|
+
import Fields from "../../../../UI/Components/Forms/Types/Fields";
|
|
3
|
+
import FormFieldSchemaType from "../../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
4
|
+
import FormValues from "../../../../UI/Components/Forms/Types/FormValues";
|
|
5
|
+
import { FormStep } from "../../../../UI/Components/Forms/Types/FormStep";
|
|
6
|
+
import ScanTargetUtil from "../../../../Utils/NetworkDiscovery/ScanTargetUtil";
|
|
7
|
+
import getJestMockFunction, { MockFunction } from "../../../MockType";
|
|
8
|
+
import "@testing-library/jest-dom";
|
|
9
|
+
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
10
|
+
import userEvent from "@testing-library/user-event";
|
|
11
|
+
import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import { afterEach, describe, expect, test } from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* "Next" has to be a gate, not a page-turn.
|
|
17
|
+
*
|
|
18
|
+
* Issue #3377, against the Create Network Device Discovery Scan wizard: every
|
|
19
|
+
* step accepted whatever was typed into it. An invalid scan target cleared
|
|
20
|
+
* step 1, cleared step 2, cleared step 3, and then failed the final submit
|
|
21
|
+
* with one combined banner rendered above the SCHEDULE fields — describing a
|
|
22
|
+
* value entered two steps earlier, with nothing marking the field at fault.
|
|
23
|
+
*
|
|
24
|
+
* The fix is a field-level validator, and the reason a field-level validator
|
|
25
|
+
* is sufficient is the behaviour pinned here: BasicForm validates only the
|
|
26
|
+
* current step, refuses to advance while that step has an error, and renders
|
|
27
|
+
* the message inline under the offending input. Nothing in the repo tested
|
|
28
|
+
* that — no test clicked a Next button — so the whole mechanism the fix leans
|
|
29
|
+
* on was unpinned.
|
|
30
|
+
*
|
|
31
|
+
* The wizard below is shaped like the real one and uses the real scan-target
|
|
32
|
+
* parser, so a regression in either the step machinery or the parser shows up
|
|
33
|
+
* here as a step that turns when it should not.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Type without userEvent's default inter-keystroke await, and give the typing
|
|
38
|
+
* tests explicit timeouts. Both are load-bearing and both are borrowed from
|
|
39
|
+
* BasicForm.test.tsx: Input syncs a display value from props onto the DOM
|
|
40
|
+
* node, so a keystroke arriving before React has committed the previous one is
|
|
41
|
+
* destroyed rather than delayed; and re-validating once per keystroke has no
|
|
42
|
+
* headroom against jest's 5s default on a loaded runner.
|
|
43
|
+
*/
|
|
44
|
+
function setupUser(): UserEvent {
|
|
45
|
+
return userEvent.setup({ delay: null });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const TEST_TIMEOUT_MS: number = 30000;
|
|
49
|
+
|
|
50
|
+
const SUBMIT_BUTTON_TEXT: string = "Create Scan";
|
|
51
|
+
|
|
52
|
+
const STEPS: Array<FormStep<FormValues<any>>> = [
|
|
53
|
+
{ title: "Scan Target", id: "scan-target" },
|
|
54
|
+
{ title: "SNMP Credentials", id: "snmp" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const FIELDS: Fields<FormValues<any>> = [
|
|
58
|
+
{
|
|
59
|
+
field: { cidr: true },
|
|
60
|
+
title: "Scan Target",
|
|
61
|
+
stepId: "scan-target",
|
|
62
|
+
fieldType: FormFieldSchemaType.Text,
|
|
63
|
+
required: true,
|
|
64
|
+
dataTestId: "cidr",
|
|
65
|
+
/*
|
|
66
|
+
* The real validator's shape: delegate to the parser, and stay silent on
|
|
67
|
+
* empty so `required` keeps its own shorter message.
|
|
68
|
+
*/
|
|
69
|
+
customValidation: (values: FormValues<any>): string | null => {
|
|
70
|
+
const raw: string =
|
|
71
|
+
values["cidr"] === undefined || values["cidr"] === null
|
|
72
|
+
? ""
|
|
73
|
+
: String(values["cidr"]);
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Empty is `required`'s; blank is the parser's. validateRequired
|
|
77
|
+
* measures the UNTRIMMED string, so a lone space satisfies it — see the
|
|
78
|
+
* whitespace test below.
|
|
79
|
+
*/
|
|
80
|
+
if (raw === "") {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return ScanTargetUtil.getValidationError(raw.trim());
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
field: { snmpCommunityString: true },
|
|
89
|
+
title: "SNMP Community String",
|
|
90
|
+
stepId: "snmp",
|
|
91
|
+
fieldType: FormFieldSchemaType.Text,
|
|
92
|
+
required: true,
|
|
93
|
+
dataTestId: "community",
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
interface RenderWizardResult {
|
|
98
|
+
handleSubmit: MockFunction;
|
|
99
|
+
user: UserEvent;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderWizard(): RenderWizardResult {
|
|
103
|
+
const handleSubmit: MockFunction = getJestMockFunction();
|
|
104
|
+
|
|
105
|
+
render(
|
|
106
|
+
<BasicForm
|
|
107
|
+
id="discovery-wizard"
|
|
108
|
+
fields={FIELDS}
|
|
109
|
+
steps={STEPS}
|
|
110
|
+
initialValues={{}}
|
|
111
|
+
onSubmit={handleSubmit}
|
|
112
|
+
submitButtonText={SUBMIT_BUTTON_TEXT}
|
|
113
|
+
/>,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return { handleSubmit, user: setupUser() };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* The button's LABEL is "Next" until the last step, but its test id is always
|
|
121
|
+
* the submitButtonText it was given — see BasicForm's Button props. Reading it
|
|
122
|
+
* by test id therefore works on every step.
|
|
123
|
+
*/
|
|
124
|
+
function stepButton(): HTMLElement {
|
|
125
|
+
return screen.getByTestId(SUBMIT_BUTTON_TEXT);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function errorMessages(): Array<string> {
|
|
129
|
+
return screen
|
|
130
|
+
.queryAllByTestId("error-message")
|
|
131
|
+
.map((element: HTMLElement): string => {
|
|
132
|
+
return element.textContent || "";
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function onStepOne(): boolean {
|
|
137
|
+
return screen.queryByTestId("cidr") !== null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function onStepTwo(): boolean {
|
|
141
|
+
return screen.queryByTestId("community") !== null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
describe("BasicForm wizard — an invalid field holds its own step", () => {
|
|
145
|
+
afterEach(() => {
|
|
146
|
+
cleanup();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test(
|
|
150
|
+
"the reported case: a phone-number-shaped scan target does not advance",
|
|
151
|
+
async () => {
|
|
152
|
+
const { handleSubmit, user }: RenderWizardResult = renderWizard();
|
|
153
|
+
|
|
154
|
+
await user.type(screen.getByTestId("cidr"), "9876543210");
|
|
155
|
+
await user.click(stepButton());
|
|
156
|
+
|
|
157
|
+
await waitFor(() => {
|
|
158
|
+
expect(errorMessages().join(" ")).toContain(
|
|
159
|
+
"is not a valid scan target",
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Still on step one, and nothing was submitted.
|
|
164
|
+
expect(onStepOne()).toBe(true);
|
|
165
|
+
expect(onStepTwo()).toBe(false);
|
|
166
|
+
expect(handleSubmit).not.toHaveBeenCalled();
|
|
167
|
+
},
|
|
168
|
+
TEST_TIMEOUT_MS,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
test(
|
|
172
|
+
"the message is rendered inline, on the step that owns the field",
|
|
173
|
+
async () => {
|
|
174
|
+
const { user }: RenderWizardResult = renderWizard();
|
|
175
|
+
|
|
176
|
+
await user.type(screen.getByTestId("cidr"), "10.0.0.256");
|
|
177
|
+
await user.click(stepButton());
|
|
178
|
+
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(errorMessages().join(" ")).toContain("between 0 and 255");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* Exactly one message, attached to the one field on this step — not a
|
|
185
|
+
* combined banner, and not a message about a field the operator cannot
|
|
186
|
+
* see.
|
|
187
|
+
*/
|
|
188
|
+
expect(errorMessages()).toHaveLength(1);
|
|
189
|
+
expect(screen.getByTestId("cidr")).toBeInTheDocument();
|
|
190
|
+
},
|
|
191
|
+
TEST_TIMEOUT_MS,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
test(
|
|
195
|
+
"an over-sized but well-formed target is stopped at step one too",
|
|
196
|
+
async () => {
|
|
197
|
+
const { user }: RenderWizardResult = renderWizard();
|
|
198
|
+
|
|
199
|
+
// Syntactically perfect; 16.7M addresses.
|
|
200
|
+
await user.type(screen.getByTestId("cidr"), "10.0.0.0/8");
|
|
201
|
+
await user.click(stepButton());
|
|
202
|
+
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
expect(errorMessages().join(" ")).toContain("scan limit");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(onStepTwo()).toBe(false);
|
|
208
|
+
},
|
|
209
|
+
TEST_TIMEOUT_MS,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
test(
|
|
213
|
+
"a blank field does not advance either, even though `required` passes it",
|
|
214
|
+
async () => {
|
|
215
|
+
const { handleSubmit, user }: RenderWizardResult = renderWizard();
|
|
216
|
+
|
|
217
|
+
/*
|
|
218
|
+
* The trap this whole distinction exists for: Validation.validateRequired
|
|
219
|
+
* tests `!content || content.length === 0` on the RAW string, so a single
|
|
220
|
+
* space has length 1 and satisfies it. No length rule fires either (the
|
|
221
|
+
* inferred maxLength is an upper bound, and trims to 0 anyway). If the
|
|
222
|
+
* field's own validator also read " " as empty, nothing on the form would
|
|
223
|
+
* speak — and the step would turn on a target the server rejects.
|
|
224
|
+
*/
|
|
225
|
+
await user.type(screen.getByTestId("cidr"), " ");
|
|
226
|
+
await user.click(stepButton());
|
|
227
|
+
|
|
228
|
+
await waitFor(() => {
|
|
229
|
+
expect(errorMessages().join(" ")).toContain(
|
|
230
|
+
"A scan target is required",
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(onStepTwo()).toBe(false);
|
|
235
|
+
expect(handleSubmit).not.toHaveBeenCalled();
|
|
236
|
+
},
|
|
237
|
+
TEST_TIMEOUT_MS,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
test(
|
|
241
|
+
"an empty required field is reported as required, not as malformed",
|
|
242
|
+
async () => {
|
|
243
|
+
const { handleSubmit, user }: RenderWizardResult = renderWizard();
|
|
244
|
+
|
|
245
|
+
await user.click(stepButton());
|
|
246
|
+
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(errorMessages().join(" ")).toContain("Scan Target is required.");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
/*
|
|
252
|
+
* The customValidation returns null for empty precisely so this message
|
|
253
|
+
* survives — it runs after validateRequired and would otherwise replace
|
|
254
|
+
* it with the parser's longer sentence.
|
|
255
|
+
*/
|
|
256
|
+
expect(errorMessages().join(" ")).not.toContain("is not a valid");
|
|
257
|
+
expect(onStepTwo()).toBe(false);
|
|
258
|
+
expect(handleSubmit).not.toHaveBeenCalled();
|
|
259
|
+
},
|
|
260
|
+
TEST_TIMEOUT_MS,
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("BasicForm wizard — a corrected field releases the step", () => {
|
|
265
|
+
afterEach(() => {
|
|
266
|
+
cleanup();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test(
|
|
270
|
+
"correcting the value advances to the next step",
|
|
271
|
+
async () => {
|
|
272
|
+
const { user }: RenderWizardResult = renderWizard();
|
|
273
|
+
|
|
274
|
+
await user.type(screen.getByTestId("cidr"), "9876543210");
|
|
275
|
+
await user.click(stepButton());
|
|
276
|
+
|
|
277
|
+
await waitFor(() => {
|
|
278
|
+
expect(errorMessages().length).toBeGreaterThan(0);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
await user.clear(screen.getByTestId("cidr"));
|
|
282
|
+
await user.type(screen.getByTestId("cidr"), "192.168.1.0/24");
|
|
283
|
+
await user.click(stepButton());
|
|
284
|
+
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(onStepTwo()).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(onStepOne()).toBe(false);
|
|
290
|
+
expect(errorMessages()).toHaveLength(0);
|
|
291
|
+
},
|
|
292
|
+
TEST_TIMEOUT_MS,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
test(
|
|
296
|
+
"a valid target advances first time, with no error shown on the way",
|
|
297
|
+
async () => {
|
|
298
|
+
const { user }: RenderWizardResult = renderWizard();
|
|
299
|
+
|
|
300
|
+
await user.type(screen.getByTestId("cidr"), "10.16-22.0-255.51-66");
|
|
301
|
+
await user.click(stepButton());
|
|
302
|
+
|
|
303
|
+
await waitFor(() => {
|
|
304
|
+
expect(onStepTwo()).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(errorMessages()).toHaveLength(0);
|
|
308
|
+
},
|
|
309
|
+
TEST_TIMEOUT_MS,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
test(
|
|
313
|
+
"the whole wizard submits once every step is satisfied",
|
|
314
|
+
async () => {
|
|
315
|
+
const { handleSubmit, user }: RenderWizardResult = renderWizard();
|
|
316
|
+
|
|
317
|
+
await user.type(screen.getByTestId("cidr"), "192.168.1.0/24");
|
|
318
|
+
await user.click(stepButton());
|
|
319
|
+
|
|
320
|
+
await waitFor(() => {
|
|
321
|
+
expect(onStepTwo()).toBe(true);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await user.type(screen.getByTestId("community"), "public");
|
|
325
|
+
await user.click(stepButton());
|
|
326
|
+
|
|
327
|
+
await waitFor(() => {
|
|
328
|
+
expect(handleSubmit).toHaveBeenCalled();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
expect(handleSubmit.mock.calls[0]?.[0]).toMatchObject({
|
|
332
|
+
cidr: "192.168.1.0/24",
|
|
333
|
+
snmpCommunityString: "public",
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
TEST_TIMEOUT_MS,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("BasicForm wizard — a later step's field never blocks an earlier one", () => {
|
|
341
|
+
afterEach(() => {
|
|
342
|
+
cleanup();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test(
|
|
346
|
+
"step two's required field is not demanded while on step one",
|
|
347
|
+
async () => {
|
|
348
|
+
const { user }: RenderWizardResult = renderWizard();
|
|
349
|
+
|
|
350
|
+
await user.type(screen.getByTestId("cidr"), "192.168.1.0/24");
|
|
351
|
+
await user.click(stepButton());
|
|
352
|
+
|
|
353
|
+
/*
|
|
354
|
+
* The community string is required and empty. If the step filter ever
|
|
355
|
+
* stopped applying, "Next" would become a dead button: the error would
|
|
356
|
+
* block the advance while its field is on a step that has not been
|
|
357
|
+
* rendered yet, so nothing on screen could explain the refusal.
|
|
358
|
+
*/
|
|
359
|
+
await waitFor(() => {
|
|
360
|
+
expect(onStepTwo()).toBe(true);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(errorMessages()).toHaveLength(0);
|
|
364
|
+
},
|
|
365
|
+
TEST_TIMEOUT_MS,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
test(
|
|
369
|
+
"step two blocks on its own field once the operator gets there",
|
|
370
|
+
async () => {
|
|
371
|
+
const { handleSubmit, user }: RenderWizardResult = renderWizard();
|
|
372
|
+
|
|
373
|
+
await user.type(screen.getByTestId("cidr"), "192.168.1.0/24");
|
|
374
|
+
await user.click(stepButton());
|
|
375
|
+
|
|
376
|
+
await waitFor(() => {
|
|
377
|
+
expect(onStepTwo()).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await user.click(stepButton());
|
|
381
|
+
|
|
382
|
+
await waitFor(() => {
|
|
383
|
+
expect(errorMessages().join(" ")).toContain(
|
|
384
|
+
"SNMP Community String is required.",
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
expect(handleSubmit).not.toHaveBeenCalled();
|
|
389
|
+
},
|
|
390
|
+
TEST_TIMEOUT_MS,
|
|
391
|
+
);
|
|
392
|
+
});
|