@oneuptime/common 12.0.34 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { describe, expect, it } from "@jest/globals";
|
|
5
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
6
|
+
import {
|
|
7
|
+
REPLAY_CONTROL_HEIGHT_CLASS,
|
|
8
|
+
ReplayButtonGroup,
|
|
9
|
+
ReplayClock,
|
|
10
|
+
ReplayPill,
|
|
11
|
+
ReplaySwitch,
|
|
12
|
+
ReplayToolButton,
|
|
13
|
+
ReplayToolbarDivider,
|
|
14
|
+
getReplaySegmentClassName,
|
|
15
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/ReplayUi";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* The player's shared chrome.
|
|
19
|
+
*
|
|
20
|
+
* These are presentational primitives, so most of what is worth pinning
|
|
21
|
+
* is the part a redesign is most likely to quietly break: the accessible
|
|
22
|
+
* name, the toggle semantics, and the two or three class invariants the
|
|
23
|
+
* whole look rests on (one control height, one radius, "on" drawn
|
|
24
|
+
* differently inside a group than outside one). The visual details that
|
|
25
|
+
* are genuinely taste - which gray, how much padding - are deliberately
|
|
26
|
+
* NOT asserted; a test that pins those makes the design unchangeable
|
|
27
|
+
* without testing anything a viewer would notice.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
function noop(): void {
|
|
31
|
+
/* Click handlers the assertion does not care about. */
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("ReplayToolButton accessible name", () => {
|
|
35
|
+
/*
|
|
36
|
+
* The regression this exists for: the button used to fall back to its
|
|
37
|
+
* title for aria-label whenever no explicit label was passed, so the
|
|
38
|
+
* stage's "Fit" button announced itself as "Scale the picture to fit
|
|
39
|
+
* the stage". That breaks WCAG 2.5.3 (Label in Name) - a voice user
|
|
40
|
+
* saying "click Fit" hits a control whose name does not contain the
|
|
41
|
+
* word - and it broke getByRole(name: "Fit") for anyone testing it.
|
|
42
|
+
*/
|
|
43
|
+
it("uses the visible label as the name and keeps the title a tooltip", () => {
|
|
44
|
+
render(
|
|
45
|
+
<ReplayToolButton
|
|
46
|
+
label="Fit"
|
|
47
|
+
title="Scale the picture to fit the stage"
|
|
48
|
+
onClick={noop}
|
|
49
|
+
/>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const button: HTMLElement = screen.getByRole("button", { name: "Fit" });
|
|
53
|
+
|
|
54
|
+
expect(button).toHaveAttribute(
|
|
55
|
+
"title",
|
|
56
|
+
"Scale the picture to fit the stage",
|
|
57
|
+
);
|
|
58
|
+
expect(button).not.toHaveAttribute("aria-label");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("falls back to the title only when there is no visible text at all", () => {
|
|
62
|
+
render(
|
|
63
|
+
<ReplayToolButton
|
|
64
|
+
icon={IconProp.Keyboard}
|
|
65
|
+
title="Keyboard shortcuts (?)"
|
|
66
|
+
onClick={noop}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
screen.getByRole("button", { name: "Keyboard shortcuts (?)" }),
|
|
72
|
+
).toHaveAttribute("aria-label", "Keyboard shortcuts (?)");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("lets an explicit ariaLabel win over a short visible label", () => {
|
|
76
|
+
render(
|
|
77
|
+
<ReplayToolButton
|
|
78
|
+
label="10s"
|
|
79
|
+
icon={IconProp.Backward}
|
|
80
|
+
ariaLabel="Back 10 seconds (J)"
|
|
81
|
+
title="Back 10 seconds (J)"
|
|
82
|
+
onClick={noop}
|
|
83
|
+
/>,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const button: HTMLElement = screen.getByRole("button", {
|
|
87
|
+
name: "Back 10 seconds (J)",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(button).toHaveTextContent("10s");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("ReplayToolButton toggle semantics", () => {
|
|
95
|
+
it("renders aria-pressed only when it is actually a toggle", () => {
|
|
96
|
+
const { rerender } = render(
|
|
97
|
+
<ReplayToolButton label="Details" onClick={noop} />,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(screen.getByRole("button")).not.toHaveAttribute("aria-pressed");
|
|
101
|
+
|
|
102
|
+
rerender(
|
|
103
|
+
<ReplayToolButton label="Wide" isPressed={false} onClick={noop} />,
|
|
104
|
+
);
|
|
105
|
+
expect(screen.getByRole("button")).toHaveAttribute("aria-pressed", "false");
|
|
106
|
+
|
|
107
|
+
rerender(<ReplayToolButton label="Wide" isPressed={true} onClick={noop} />);
|
|
108
|
+
expect(screen.getByRole("button")).toHaveAttribute("aria-pressed", "true");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("draws a pressed ghost as a solid fill and a pressed segment as a raised thumb", () => {
|
|
112
|
+
const { rerender } = render(
|
|
113
|
+
<ReplayToolButton label="Wide" isPressed={true} onClick={noop} />,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
* Outside a group there is no track behind the button, so "on" has to
|
|
118
|
+
* be carried by the button itself.
|
|
119
|
+
*/
|
|
120
|
+
expect(screen.getByRole("button").className).toContain("bg-gray-900");
|
|
121
|
+
|
|
122
|
+
rerender(
|
|
123
|
+
<ReplayToolButton
|
|
124
|
+
label="Wide"
|
|
125
|
+
variant="segment"
|
|
126
|
+
isPressed={true}
|
|
127
|
+
onClick={noop}
|
|
128
|
+
/>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
/* Inside a group the recessed track is the contrast; the thumb is white. */
|
|
132
|
+
expect(screen.getByRole("button").className).toContain("bg-white");
|
|
133
|
+
expect(screen.getByRole("button").className).toContain("shadow-sm");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("ReplayToolButton disabled state", () => {
|
|
138
|
+
it("does not fire onClick and is announced as disabled", () => {
|
|
139
|
+
let clicks: number = 0;
|
|
140
|
+
|
|
141
|
+
render(
|
|
142
|
+
<ReplayToolButton
|
|
143
|
+
label="Next error"
|
|
144
|
+
isDisabled={true}
|
|
145
|
+
onClick={(): void => {
|
|
146
|
+
clicks += 1;
|
|
147
|
+
}}
|
|
148
|
+
/>,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const button: HTMLElement = screen.getByRole("button", {
|
|
152
|
+
name: "Next error",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(button).toBeDisabled();
|
|
156
|
+
fireEvent.click(button);
|
|
157
|
+
expect(clicks).toBe(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("keeps the disabled look even when the button is a pressed segment", () => {
|
|
161
|
+
render(
|
|
162
|
+
<ReplayToolButton
|
|
163
|
+
label="Tab 3 · no footage"
|
|
164
|
+
variant="segment"
|
|
165
|
+
isPressed={true}
|
|
166
|
+
isDisabled={true}
|
|
167
|
+
onClick={noop}
|
|
168
|
+
/>,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
/* Disabled wins: a dead control must never look selected. */
|
|
172
|
+
expect(screen.getByRole("button").className).toContain("text-gray-300");
|
|
173
|
+
expect(screen.getByRole("button").className).not.toContain("shadow-sm");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("ReplayToolButton geometry", () => {
|
|
178
|
+
it("is square when it draws a single glyph and padded when it draws more", () => {
|
|
179
|
+
const { rerender } = render(
|
|
180
|
+
<ReplayToolButton
|
|
181
|
+
icon={IconProp.Keyboard}
|
|
182
|
+
title="Keyboard shortcuts"
|
|
183
|
+
onClick={noop}
|
|
184
|
+
/>,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(screen.getByRole("button").className).toContain("w-8");
|
|
188
|
+
|
|
189
|
+
/*
|
|
190
|
+
* "Previous error" is a chevron AND an alert glyph with no label; a
|
|
191
|
+
* 32px square would clip the second one.
|
|
192
|
+
*/
|
|
193
|
+
rerender(
|
|
194
|
+
<ReplayToolButton
|
|
195
|
+
icon={IconProp.ChevronLeft}
|
|
196
|
+
trailingIcon={IconProp.Alert}
|
|
197
|
+
title="Previous error"
|
|
198
|
+
onClick={noop}
|
|
199
|
+
/>,
|
|
200
|
+
);
|
|
201
|
+
expect(screen.getByRole("button").className).not.toContain("w-8");
|
|
202
|
+
expect(screen.getByRole("button").className).toContain("px-2.5");
|
|
203
|
+
|
|
204
|
+
rerender(<ReplayToolButton label="Details" onClick={noop} />);
|
|
205
|
+
expect(screen.getByRole("button").className).toContain("px-2.5");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/*
|
|
209
|
+
* The single rule the whole chrome rests on: one height. Twenty
|
|
210
|
+
* controls at four different heights is what made the old row look
|
|
211
|
+
* like a pile rather than a toolbar, so every path through the
|
|
212
|
+
* component - ghost, segment, disabled, icon-only - keeps it, and the
|
|
213
|
+
* exported class stays the one source of that number.
|
|
214
|
+
*/
|
|
215
|
+
it("gives every variant the same control height and radius", () => {
|
|
216
|
+
const variants: Array<React.ReactElement> = [
|
|
217
|
+
<ReplayToolButton key="a" label="Ghost" onClick={noop} />,
|
|
218
|
+
<ReplayToolButton
|
|
219
|
+
key="b"
|
|
220
|
+
label="Segment"
|
|
221
|
+
variant="segment"
|
|
222
|
+
isPressed={true}
|
|
223
|
+
onClick={noop}
|
|
224
|
+
/>,
|
|
225
|
+
<ReplayToolButton
|
|
226
|
+
key="c"
|
|
227
|
+
label="Disabled"
|
|
228
|
+
isDisabled={true}
|
|
229
|
+
onClick={noop}
|
|
230
|
+
/>,
|
|
231
|
+
<ReplayToolButton
|
|
232
|
+
key="d"
|
|
233
|
+
icon={IconProp.Info}
|
|
234
|
+
title="Icon only"
|
|
235
|
+
onClick={noop}
|
|
236
|
+
/>,
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
variants.forEach((element: React.ReactElement): void => {
|
|
240
|
+
const { unmount } = render(element);
|
|
241
|
+
const button: HTMLElement = screen.getByRole("button");
|
|
242
|
+
|
|
243
|
+
expect(button.className).toContain(REPLAY_CONTROL_HEIGHT_CLASS);
|
|
244
|
+
expect(button.className).toContain("rounded-lg");
|
|
245
|
+
unmount();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("getReplaySegmentClassName", () => {
|
|
251
|
+
/*
|
|
252
|
+
* The header's browser-tab pills and the rail's signal tabs are real
|
|
253
|
+
* tabs (role="tab", aria-selected, roving tabindex), so they cannot be
|
|
254
|
+
* ReplayToolButtons - but they must not look like a fifth kind of chip
|
|
255
|
+
* either. The exported class is what keeps them identical.
|
|
256
|
+
*/
|
|
257
|
+
it("matches what a ReplayToolButton segment renders, for each state", () => {
|
|
258
|
+
render(
|
|
259
|
+
<ReplayToolButton
|
|
260
|
+
label="Selected"
|
|
261
|
+
variant="segment"
|
|
262
|
+
isPressed={true}
|
|
263
|
+
onClick={noop}
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const rendered: string = screen.getByRole("button").className;
|
|
268
|
+
const helper: string = getReplaySegmentClassName({ isSelected: true });
|
|
269
|
+
|
|
270
|
+
helper.split(" ").forEach((token: string): void => {
|
|
271
|
+
expect(rendered).toContain(token);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("distinguishes selected, idle and disabled", () => {
|
|
276
|
+
const selected: string = getReplaySegmentClassName({ isSelected: true });
|
|
277
|
+
const idle: string = getReplaySegmentClassName({ isSelected: false });
|
|
278
|
+
const disabled: string = getReplaySegmentClassName({
|
|
279
|
+
isSelected: true,
|
|
280
|
+
isDisabled: true,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(selected).toContain("bg-white");
|
|
284
|
+
expect(idle).not.toContain("bg-white ");
|
|
285
|
+
expect(disabled).toContain("cursor-not-allowed");
|
|
286
|
+
expect(disabled).not.toContain("shadow-sm");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("keeps the shared height on every state so a tab row cannot go ragged", () => {
|
|
290
|
+
[
|
|
291
|
+
getReplaySegmentClassName({ isSelected: true }),
|
|
292
|
+
getReplaySegmentClassName({ isSelected: false }),
|
|
293
|
+
getReplaySegmentClassName({ isSelected: false, isDisabled: true }),
|
|
294
|
+
].forEach((className: string): void => {
|
|
295
|
+
expect(className).toContain(REPLAY_CONTROL_HEIGHT_CLASS);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("ReplayButtonGroup", () => {
|
|
301
|
+
it("is a labelled group holding its members on one recessed track", () => {
|
|
302
|
+
render(
|
|
303
|
+
<ReplayButtonGroup
|
|
304
|
+
ariaLabel="Jump between signals"
|
|
305
|
+
dataTestId="signal-group"
|
|
306
|
+
>
|
|
307
|
+
<ReplayToolButton label="Next error" variant="segment" onClick={noop} />
|
|
308
|
+
<ReplayToolButton
|
|
309
|
+
label="Frustration"
|
|
310
|
+
variant="segment"
|
|
311
|
+
onClick={noop}
|
|
312
|
+
/>
|
|
313
|
+
</ReplayButtonGroup>,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const group: HTMLElement = screen.getByRole("group", {
|
|
317
|
+
name: "Jump between signals",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(group).toBe(screen.getByTestId("signal-group"));
|
|
321
|
+
/* ONE container background for N buttons: no per-button outlines. */
|
|
322
|
+
expect(group.className).toContain("bg-gray-100");
|
|
323
|
+
expect(group.querySelectorAll("button")).toHaveLength(2);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("can carry another role for the tablists that reuse the track", () => {
|
|
327
|
+
render(
|
|
328
|
+
<ReplayButtonGroup role="tablist" ariaLabel="Browser tabs">
|
|
329
|
+
<button type="button" role="tab" aria-selected={true}>
|
|
330
|
+
Tab 1
|
|
331
|
+
</button>
|
|
332
|
+
</ReplayButtonGroup>,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
expect(
|
|
336
|
+
screen.getByRole("tablist", { name: "Browser tabs" }),
|
|
337
|
+
).toBeInTheDocument();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe("ReplayToolbarDivider", () => {
|
|
342
|
+
it("is decorative and never announced", () => {
|
|
343
|
+
render(<ReplayToolbarDivider />);
|
|
344
|
+
|
|
345
|
+
expect(screen.getByTestId("replay-toolbar-divider")).toHaveAttribute(
|
|
346
|
+
"aria-hidden",
|
|
347
|
+
"true",
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe("ReplayPill", () => {
|
|
353
|
+
it("renders its text with a tone and an optional pulsing dot", () => {
|
|
354
|
+
render(
|
|
355
|
+
<ReplayPill dataTestId="live" tone="live" hasPulse={true} title="Live">
|
|
356
|
+
Live
|
|
357
|
+
</ReplayPill>,
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const pill: HTMLElement = screen.getByTestId("live");
|
|
361
|
+
|
|
362
|
+
expect(pill).toHaveTextContent("Live");
|
|
363
|
+
expect(pill.className).toContain("bg-red-50");
|
|
364
|
+
expect(pill.querySelector(".animate-pulse")).not.toBeNull();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("omits the dot unless it is asked for", () => {
|
|
368
|
+
render(<ReplayPill dataTestId="quiet">Sealed</ReplayPill>);
|
|
369
|
+
|
|
370
|
+
expect(
|
|
371
|
+
screen.getByTestId("quiet").querySelector(".animate-pulse"),
|
|
372
|
+
).toBeNull();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("passes a role through for the pills that are alerts", () => {
|
|
376
|
+
render(
|
|
377
|
+
<ReplayPill dataTestId="err" tone="danger" role="alert">
|
|
378
|
+
Footage did not arrive.
|
|
379
|
+
</ReplayPill>,
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
383
|
+
"Footage did not arrive.",
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
/*
|
|
388
|
+
* The buffering pill is a duplicate of what the stage overlay already
|
|
389
|
+
* announces; two live regions meant a screen reader said "Buffering"
|
|
390
|
+
* twice per stall. It stays visible and stays silent.
|
|
391
|
+
*/
|
|
392
|
+
it("can hide itself from assistive tech without hiding itself from the eye", () => {
|
|
393
|
+
render(
|
|
394
|
+
<ReplayPill dataTestId="buffering" isHiddenFromScreenReaders={true}>
|
|
395
|
+
Loading footage
|
|
396
|
+
</ReplayPill>,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
expect(screen.getByTestId("buffering")).toHaveAttribute(
|
|
400
|
+
"aria-hidden",
|
|
401
|
+
"true",
|
|
402
|
+
);
|
|
403
|
+
expect(screen.getByTestId("buffering")).toHaveTextContent(
|
|
404
|
+
"Loading footage",
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe("ReplaySwitch", () => {
|
|
410
|
+
it("is a switch with its state on the pressable element", () => {
|
|
411
|
+
render(
|
|
412
|
+
<ReplaySwitch
|
|
413
|
+
dataTestId="skip-idle"
|
|
414
|
+
label="Skip idle"
|
|
415
|
+
isChecked={false}
|
|
416
|
+
onChange={noop}
|
|
417
|
+
/>,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const control: HTMLElement = screen.getByTestId("skip-idle");
|
|
421
|
+
|
|
422
|
+
expect(control).toBe(screen.getByRole("switch"));
|
|
423
|
+
expect(control).toHaveAttribute("aria-checked", "false");
|
|
424
|
+
expect(control).toHaveTextContent("Skip idle");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
/*
|
|
428
|
+
* Common/UI's Toggle calls its consumer's onChange twice per click for
|
|
429
|
+
* anything that also wires onFocus/onBlur; a transport switch that
|
|
430
|
+
* fires twice would toggle the engine's skip-idle intent straight back
|
|
431
|
+
* off. One click, one call, with the NEXT value.
|
|
432
|
+
*/
|
|
433
|
+
it("emits exactly one change per click, carrying the next value", () => {
|
|
434
|
+
const values: Array<boolean> = [];
|
|
435
|
+
|
|
436
|
+
const { rerender } = render(
|
|
437
|
+
<ReplaySwitch
|
|
438
|
+
dataTestId="skip-idle"
|
|
439
|
+
label="Skip idle"
|
|
440
|
+
isChecked={false}
|
|
441
|
+
onChange={(value: boolean): void => {
|
|
442
|
+
values.push(value);
|
|
443
|
+
}}
|
|
444
|
+
/>,
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
fireEvent.click(screen.getByTestId("skip-idle"));
|
|
448
|
+
expect(values).toEqual([true]);
|
|
449
|
+
|
|
450
|
+
rerender(
|
|
451
|
+
<ReplaySwitch
|
|
452
|
+
dataTestId="skip-idle"
|
|
453
|
+
label="Skip idle"
|
|
454
|
+
isChecked={true}
|
|
455
|
+
onChange={(value: boolean): void => {
|
|
456
|
+
values.push(value);
|
|
457
|
+
}}
|
|
458
|
+
/>,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
expect(screen.getByTestId("skip-idle")).toHaveAttribute(
|
|
462
|
+
"aria-checked",
|
|
463
|
+
"true",
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
fireEvent.click(screen.getByTestId("skip-idle"));
|
|
467
|
+
expect(values).toEqual([true, false]);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("is controlled: it never moves on its own", () => {
|
|
471
|
+
render(
|
|
472
|
+
<ReplaySwitch
|
|
473
|
+
dataTestId="skip-idle"
|
|
474
|
+
label="Skip idle"
|
|
475
|
+
isChecked={false}
|
|
476
|
+
onChange={noop}
|
|
477
|
+
/>,
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
fireEvent.click(screen.getByTestId("skip-idle"));
|
|
481
|
+
|
|
482
|
+
/* The parent did not change the prop, so the switch must not lie. */
|
|
483
|
+
expect(screen.getByTestId("skip-idle")).toHaveAttribute(
|
|
484
|
+
"aria-checked",
|
|
485
|
+
"false",
|
|
486
|
+
);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("stands at the chrome's own height rather than Common/UI's 24px toggle", () => {
|
|
490
|
+
render(
|
|
491
|
+
<ReplaySwitch
|
|
492
|
+
dataTestId="skip-idle"
|
|
493
|
+
label="Skip idle"
|
|
494
|
+
isChecked={false}
|
|
495
|
+
onChange={noop}
|
|
496
|
+
/>,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
expect(screen.getByTestId("skip-idle").className).toContain(
|
|
500
|
+
REPLAY_CONTROL_HEIGHT_CLASS,
|
|
501
|
+
);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe("ReplayClock", () => {
|
|
506
|
+
/*
|
|
507
|
+
* The readout is two spans so the elapsed half can carry the weight -
|
|
508
|
+
* but its TEXT is still read as one string by the E2E clock
|
|
509
|
+
* assertions and by anyone copying it. Two adjacent spans separated by
|
|
510
|
+
* nothing but a flex gap would render "0:12/ 10:00".
|
|
511
|
+
*/
|
|
512
|
+
it("reads as one 'current / total' string", () => {
|
|
513
|
+
render(
|
|
514
|
+
<ReplayClock
|
|
515
|
+
dataTestId="replay-time"
|
|
516
|
+
currentText="0:12"
|
|
517
|
+
totalText="10:00"
|
|
518
|
+
/>,
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
expect(screen.getByTestId("replay-time")).toHaveTextContent("0:12 / 10:00");
|
|
522
|
+
expect(screen.getByTestId("replay-time").textContent).toBe("0:12 / 10:00");
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("weights the elapsed half and mutes the total", () => {
|
|
526
|
+
render(
|
|
527
|
+
<ReplayClock
|
|
528
|
+
dataTestId="replay-time"
|
|
529
|
+
currentText="1:02.3"
|
|
530
|
+
totalText="4:12"
|
|
531
|
+
/>,
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
const clock: HTMLElement = screen.getByTestId("replay-time");
|
|
535
|
+
const parts: Array<Element> = Array.from(clock.children);
|
|
536
|
+
|
|
537
|
+
expect(parts[0]?.textContent).toBe("1:02.3");
|
|
538
|
+
expect(parts[0]?.className).toContain("font-semibold");
|
|
539
|
+
expect(parts[1]?.className).toContain("text-gray-400");
|
|
540
|
+
/* Both halves tabular, so a tick never nudges the buttons beside it. */
|
|
541
|
+
expect(clock.className).toContain("tabular-nums");
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
/* aria-live off: the offset is announced once, by the player's own region. */
|
|
545
|
+
it("does not announce every tick", () => {
|
|
546
|
+
render(
|
|
547
|
+
<ReplayClock
|
|
548
|
+
dataTestId="replay-time"
|
|
549
|
+
currentText="0:01"
|
|
550
|
+
totalText="1:00"
|
|
551
|
+
/>,
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
expect(screen.getByTestId("replay-time")).toHaveAttribute(
|
|
555
|
+
"aria-live",
|
|
556
|
+
"off",
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
@@ -2839,6 +2839,11 @@ const realBreadcrumbTrails: Array<BreadcrumbTrailFixture> = [
|
|
|
2839
2839
|
pagePattern: "/dashboard/:projectId/user-settings/calendar-feed",
|
|
2840
2840
|
titles: ["Project", "User Settings", "Calendar Feed"],
|
|
2841
2841
|
},
|
|
2842
|
+
{
|
|
2843
|
+
getter: "getUserSettingsBreadcrumbs",
|
|
2844
|
+
pagePattern: "/dashboard/:projectId/user-settings/email-preferences",
|
|
2845
|
+
titles: ["Project", "User Settings", "Email Preferences"],
|
|
2846
|
+
},
|
|
2842
2847
|
{
|
|
2843
2848
|
getter: "getUserSettingsBreadcrumbs",
|
|
2844
2849
|
pagePattern:
|
|
@@ -638,6 +638,7 @@ const realRoutePatterns: Array<string> = [
|
|
|
638
638
|
"/dashboard/:projectId/user-settings/on-call-logs/:id",
|
|
639
639
|
"/dashboard/:projectId/user-settings/incoming-call-phone-numbers",
|
|
640
640
|
"/dashboard/:projectId/user-settings/calendar-feed",
|
|
641
|
+
"/dashboard/:projectId/user-settings/email-preferences",
|
|
641
642
|
"/dashboard/:projectId/user-settings/setup",
|
|
642
643
|
"/dashboard/:projectId/users/*",
|
|
643
644
|
"/dashboard/:projectId/users",
|
|
@@ -55,6 +55,7 @@ const EXPECTED_ARGUMENT_IDS: Array<ArgumentId> = [
|
|
|
55
55
|
"monitorStatusIds",
|
|
56
56
|
"monitorTypes",
|
|
57
57
|
"labelIds",
|
|
58
|
+
"labelVariableId",
|
|
58
59
|
];
|
|
59
60
|
|
|
60
61
|
const DISPLAY_SECTION_ARGUMENT_IDS: Array<ArgumentId> = [
|
|
@@ -68,6 +69,7 @@ const FILTER_SECTION_ARGUMENT_IDS: Array<ArgumentId> = [
|
|
|
68
69
|
"monitorStatusIds",
|
|
69
70
|
"monitorTypes",
|
|
70
71
|
"labelIds",
|
|
72
|
+
"labelVariableId",
|
|
71
73
|
];
|
|
72
74
|
|
|
73
75
|
const VALID_INPUT_TYPES: Set<string> = new Set<string>(
|
|
@@ -157,6 +159,7 @@ describe("DashboardMonitorListComponentUtil", () => {
|
|
|
157
159
|
expect(component.arguments.monitorStatusIds).toBeUndefined();
|
|
158
160
|
expect(component.arguments.monitorTypes).toBeUndefined();
|
|
159
161
|
expect(component.arguments.labelIds).toBeUndefined();
|
|
162
|
+
expect(component.arguments.labelVariableId).toBeUndefined();
|
|
160
163
|
});
|
|
161
164
|
|
|
162
165
|
test("uses the same default row count that the maxRows field shows as its placeholder", () => {
|
|
@@ -188,7 +191,7 @@ describe("DashboardMonitorListComponentUtil", () => {
|
|
|
188
191
|
});
|
|
189
192
|
|
|
190
193
|
describe("getComponentConfigArguments", () => {
|
|
191
|
-
test("declares
|
|
194
|
+
test("declares the documented arguments, in form order", () => {
|
|
192
195
|
expect(
|
|
193
196
|
getArguments().map((arg: MonitorListArgument) => {
|
|
194
197
|
return arg.id;
|
|
@@ -343,6 +346,13 @@ describe("DashboardMonitorListComponentUtil", () => {
|
|
|
343
346
|
expect(arg.dropdownOptions).toBeUndefined();
|
|
344
347
|
});
|
|
345
348
|
|
|
349
|
+
test("declares a dedicated project-label variable binding alongside fixed labels", () => {
|
|
350
|
+
const arg: MonitorListArgument = getArgumentById("labelVariableId");
|
|
351
|
+
expect(arg.type).toBe(ComponentInputType.ProjectLabelVariable);
|
|
352
|
+
expect(arg.required).toBe(false);
|
|
353
|
+
expect(arg.entityFilterModelType).toBeUndefined();
|
|
354
|
+
});
|
|
355
|
+
|
|
346
356
|
test("renders the monitor-type filter as a static multi-select, not an entity lookup", () => {
|
|
347
357
|
const arg: MonitorListArgument = getArgumentById("monitorTypes");
|
|
348
358
|
|