@oneuptime/common 14.0.8 → 14.0.9
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 +2 -0
- package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
- package/Models/DatabaseModels/Project.ts +55 -5
- package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
- package/Models/DatabaseModels/RunnerJob.ts +75 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
- package/Server/API/AIChatAPI.ts +6 -3
- package/Server/API/AIInvestigationAPI.ts +17 -10
- package/Server/API/AutoRemediationAPI.ts +207 -25
- package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- package/Server/Infrastructure/GlobalCache.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/TelemetryIngest.ts +41 -18
- package/Server/Services/AIService.ts +70 -1
- package/Server/Services/AlertService.ts +352 -11
- package/Server/Services/AlertSeverityService.ts +65 -15
- package/Server/Services/AutoRemediationRuleService.ts +1 -1
- package/Server/Services/IncidentService.ts +280 -104
- package/Server/Services/IncidentSeverityService.ts +66 -15
- package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
- package/Server/Services/KubernetesAiAgentService.ts +1634 -0
- package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
- package/Server/Services/KubernetesClusterService.ts +129 -21
- package/Server/Services/ProjectService.ts +32 -0
- package/Server/Services/ProxmoxResourceService.ts +322 -5
- package/Server/Services/RunnerJobService.ts +241 -104
- package/Server/Services/RunnerService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +737 -1
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
- package/Server/Services/TeamComplianceSettingService.ts +1125 -9
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
- package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
- package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
- package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
- package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
- package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
- package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
- package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
- package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
- package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
- package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
- package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
- package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
- package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
- package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
- package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
- package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
- package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
- package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
- package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
- package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
- package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
- package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
- package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
- package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
- package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
- package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
- package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
- package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
- package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
- package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
- package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
- package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
- package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
- package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
- package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
- package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
- package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
- package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
- package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
- package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
- package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
- package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
- package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
- package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
- package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
- package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
- package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
- package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
- package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
- package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
- package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
- package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
- package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
- package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
- package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
- package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
- package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
- package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
- package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
- package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
- package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
- package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
- package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
- package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
- package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
- package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
- package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
- package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
- package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
- package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
- package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
- package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
- package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
- package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
- package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
- package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
- package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
- package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
- package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
- package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
- package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
- package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
- package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
- package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
- package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
- package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
- package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
- package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
- package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
- package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
- package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
- package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
- package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
- package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
- package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
- package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
- package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
- package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
- package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
- package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
- package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
- package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
- package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
- package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
- package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
- package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
- package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
- package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
- package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
- package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
- package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
- package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
- package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
- package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
- package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
- package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
- package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
- package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
- package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
- package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
- package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
- package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
- package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
- package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
- package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
- package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
- package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
- package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
- package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
- package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
- package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
- package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
- package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
- package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
- package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
- package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
- package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
- package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
- package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
- package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
- package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
- package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
- package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
- package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
- package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
- package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
- package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
- package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
- package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
- package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
- package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
- package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
- package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
- package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
- package/Tests/UI/Components/Input.test.tsx +80 -1
- package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
- package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
- package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
- package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
- package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
- package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
- package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
- package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
- package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
- package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
- package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
- package/Tests/UI/Components/Toggle.test.tsx +66 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
- package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
- package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
- package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
- package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
- package/Types/AI/AIRunType.ts +9 -4
- package/Types/AI/InvestigationNotStartedReason.ts +6 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
- package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
- package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
- package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
- package/Types/Team/ComplianceNotificationChannel.ts +22 -0
- package/Types/Team/ComplianceRule.ts +434 -0
- package/Types/Team/ComplianceRuleType.ts +21 -0
- package/Types/Team/TeamComplianceStatus.ts +81 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
- package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
- package/UI/Components/ActionButton/RowActions.tsx +238 -0
- package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
- package/UI/Components/Charts/Line/LineChart.tsx +32 -2
- package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
- package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
- package/UI/Components/Charts/Utils/XAxis.ts +75 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
- package/UI/Components/Forms/Fields/FormField.tsx +10 -0
- package/UI/Components/Input/Input.tsx +90 -57
- package/UI/Components/List/ListRow.tsx +8 -74
- package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
- package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
- package/UI/Components/OrderedStatesList/Item.tsx +7 -72
- package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
- package/UI/Components/Table/TableBody.tsx +8 -2
- package/UI/Components/Table/TableRow.tsx +20 -140
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
- package/UI/Components/TelemetryViewer/types.ts +2 -0
- package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
- package/UI/Components/Toggle/Toggle.tsx +26 -0
- package/UI/Components/Tooltip/Tooltip.tsx +8 -1
- package/UI/Styles/Theme.css +14 -2
- package/Utils/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +56 -5
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +6 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
- package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +59 -1
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +248 -13
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +51 -15
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +199 -72
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +178 -92
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +1 -1
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +9 -4
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRule.js +304 -0
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
- package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
- package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
- package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
- package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +69 -54
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/List/ListRow.js +4 -39
- package/build/dist/UI/Components/List/ListRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
- package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +6 -2
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +12 -75
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1757 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
jest,
|
|
7
|
+
test,
|
|
8
|
+
} from "@jest/globals";
|
|
9
|
+
import {
|
|
10
|
+
RenderHookResult,
|
|
11
|
+
act,
|
|
12
|
+
cleanup,
|
|
13
|
+
fireEvent,
|
|
14
|
+
renderHook,
|
|
15
|
+
} from "@testing-library/react";
|
|
16
|
+
import type { SpyInstance } from "jest-mock";
|
|
17
|
+
import getJestMockFunction, { MockFunction } from "../../../MockType";
|
|
18
|
+
import useHistogramRangeSelection, {
|
|
19
|
+
HISTOGRAM_PRESS_STILLNESS_PX,
|
|
20
|
+
HistogramPlaceholderProps,
|
|
21
|
+
HistogramPointerState,
|
|
22
|
+
HistogramRangeSelectionOptions,
|
|
23
|
+
HistogramRangeSelectionState,
|
|
24
|
+
} from "../../../../UI/Components/Charts/Utils/useHistogramRangeSelection";
|
|
25
|
+
import {
|
|
26
|
+
ChartPointerEvent,
|
|
27
|
+
DOUBLE_CLICK_DISAMBIGUATION_MS,
|
|
28
|
+
} from "../../../../UI/Components/Charts/ChartLibrary/Utils/DoubleClick";
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Issue #4116. Right after a drag zooms an explorer (Traces, Logs, ...),
|
|
32
|
+
* its histogram refetches, and a reader double-clicks to undo the zoom at
|
|
33
|
+
* exactly that moment. When the new buckets landed during the double-
|
|
34
|
+
* click's SECOND press, recharts re-keyed the bars and the selection band
|
|
35
|
+
* under the press, and Chrome sends no click and no dblclick for a press
|
|
36
|
+
* whose node left the page: the chart stayed zoomed. Worse, the hook read
|
|
37
|
+
* that press's release - whose bar now came from the NEW data, at the same
|
|
38
|
+
* tooltip index - as a drag from an old bar to a new one, and zoomed the
|
|
39
|
+
* explorer to a bogus window. New buckets landing during the FIRST press
|
|
40
|
+
* zoomed in once more before the reset.
|
|
41
|
+
*
|
|
42
|
+
* These tests drive useHistogramRangeSelection the way recharts 3 drives a
|
|
43
|
+
* chart root's handlers: each gets the chart state (the bar under the
|
|
44
|
+
* pointer, `activeLabel`) and the React event (the browser's click count,
|
|
45
|
+
* `detail`, and `clientX`). Pinned here:
|
|
46
|
+
*
|
|
47
|
+
* - on a zoomed chart the second press of a double-click (detail 2) IS
|
|
48
|
+
* the zoom-out: it cancels the first click's zoom-in without a render,
|
|
49
|
+
* starts no selection, and its release - over the chart or, heard by
|
|
50
|
+
* the page, off it - zooms out in the next task even when no dblclick
|
|
51
|
+
* follows, and exactly once when one does;
|
|
52
|
+
* - a press becomes a drag only when the POINTER moves (more than
|
|
53
|
+
* HISTOGRAM_PRESS_STILLNESS_PX) onto another bar. Another bar coming
|
|
54
|
+
* under a still pointer is not a drag. It is new data, and the press
|
|
55
|
+
* zooms nowhere, when it shows at the very spot pressed (the same
|
|
56
|
+
* tooltip index) or when the pointer never moved at all, not by a
|
|
57
|
+
* pixel: recharts clamps a stale index onto the last bar of shorter
|
|
58
|
+
* data, so a press right of where a shorter refetch ends is let go at
|
|
59
|
+
* another index, as after a drift. When a hand's drift crossed into
|
|
60
|
+
* the next bar, it is a click on the bar it pressed;
|
|
61
|
+
* - a second press whose own release never came (the window lost focus
|
|
62
|
+
* mid-press) is ended by the next release heard, which the browser
|
|
63
|
+
* counts 1 - a later, unrelated click's: it zooms nothing out, clears
|
|
64
|
+
* the band the first click painted, and leaves no page-wide listener
|
|
65
|
+
* behind;
|
|
66
|
+
* - a click waiting to zoom in is dropped, band and all, the moment the
|
|
67
|
+
* host stops offering onZoomOut (the zoom ended another way), and so
|
|
68
|
+
* is the band a double-click's second press kept for the zoom-out; a
|
|
69
|
+
* drag's band stays;
|
|
70
|
+
* - a box standing in for the chart (placeholderProps: its loader, its
|
|
71
|
+
* empty box) takes a double-click's second press as the chart does:
|
|
72
|
+
* its release, over the chart that replaced the box by then or
|
|
73
|
+
* anywhere on the page, zooms out in the next task, once, dblclick or
|
|
74
|
+
* not. A press on it counted 1, a right-button press, or any press on
|
|
75
|
+
* an unzoomed chart is nothing, and listens for nothing;
|
|
76
|
+
* - only the main button selects;
|
|
77
|
+
* - a press let go off the chart, which only the page hears, is read at
|
|
78
|
+
* the pointer's place: let go where it went down, a click on the bar
|
|
79
|
+
* it pressed; after crossing bars, a drag to the last one;
|
|
80
|
+
* - drags, flicks and clicks select what they always did, an unzoomed
|
|
81
|
+
* chart has no double-click gesture, and a host that hands the
|
|
82
|
+
* handlers no event keeps the old label-only reading.
|
|
83
|
+
*
|
|
84
|
+
* Before the fix, every case here in which new bars land mid-press, a
|
|
85
|
+
* second press has to zoom out without its dblclick, or a few pixels of
|
|
86
|
+
* the pointer's own movement decide the drag, failed. Before its
|
|
87
|
+
* follow-ups, so did a later click's release after a lost one (it zoomed
|
|
88
|
+
* out), a click still waiting when the zoom ended another way (it zoomed
|
|
89
|
+
* back in), and a right-click (it zoomed into its bar). Before commit
|
|
90
|
+
* b6ec899df4, so did a still press right of where a shorter refetch ends
|
|
91
|
+
* (it zoomed into the bar the refetch left out, 10:25), every case of a
|
|
92
|
+
* box standing in for the chart (there were no placeholderProps to spread
|
|
93
|
+
* on one), and the band a second press kept for a zoom-out that is not
|
|
94
|
+
* coming (it stayed lit). The rest - drags, flicks and clicks, a hand's
|
|
95
|
+
* drift, hosts that hand no event, the unzoomed chart, and double-clicks
|
|
96
|
+
* that keep their dblclick - guard what already worked.
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
const MINUTE_MS: number = 60 * 1000;
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Neighbouring one-minute bars, labelled as recharts reports them (the
|
|
103
|
+
* instant each bucket starts), and the clientX each is pressed at.
|
|
104
|
+
*/
|
|
105
|
+
const BAR_A: string = "2026-09-28T10:00:00.000Z";
|
|
106
|
+
const BAR_B: string = "2026-09-28T10:01:00.000Z";
|
|
107
|
+
const BAR_C: string = "2026-09-28T10:02:00.000Z";
|
|
108
|
+
const X_A: number = 100;
|
|
109
|
+
const X_B: number = 140;
|
|
110
|
+
const X_C: number = 170;
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* A bar of the chart's NEW data. When the refetched buckets land under a
|
|
114
|
+
* press, recharts hands the handlers whichever bar now sits at the
|
|
115
|
+
* pointer's tooltip index: at A's spot, this one.
|
|
116
|
+
*/
|
|
117
|
+
const LANDED_BAR: string = "2026-09-28T10:04:05.000Z";
|
|
118
|
+
|
|
119
|
+
// The instant `index` one-minute bars after `from`.
|
|
120
|
+
function minuteBarAfter(from: string, index: number): string {
|
|
121
|
+
return new Date(Date.parse(from) + index * MINUTE_MS).toISOString();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* A chart of thirty bars, 10:00 to 10:29, pressed far right, on 10:25 (the
|
|
126
|
+
* 26th bar, index 25) at X_LATE - and 10:26 just right of it.
|
|
127
|
+
*/
|
|
128
|
+
const HALF_HOUR_FROM: string = "2026-09-28T10:00:00.000Z";
|
|
129
|
+
const LATE_BAR: string = minuteBarAfter(HALF_HOUR_FROM, 25);
|
|
130
|
+
const BAR_AFTER_LATE_BAR: string = minuteBarAfter(HALF_HOUR_FROM, 26);
|
|
131
|
+
const X_LATE: number = 500;
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* A refetch of only ten bars, 11:00 to 11:09, landing under that press.
|
|
135
|
+
* recharts keeps the index the pointer last hovered, and one past the end
|
|
136
|
+
* of shorter data it clamps onto the LAST bar: the release of a press that
|
|
137
|
+
* never moved reports 11:09 at index 9. Had the pointer moved - or Chrome
|
|
138
|
+
* made up a move at the same spot, as it does when the page under a still
|
|
139
|
+
* pointer changes - recharts would hit-test the new bars instead and report
|
|
140
|
+
* the one under the pointer, 11:08. Either way the index changed as it
|
|
141
|
+
* does when a hand's drift crosses a bar's edge.
|
|
142
|
+
*/
|
|
143
|
+
const REFETCH_FROM: string = "2026-09-28T11:00:00.000Z";
|
|
144
|
+
const REFETCH_LAST_BAR: string = minuteBarAfter(REFETCH_FROM, 9);
|
|
145
|
+
const REFETCH_BAR_UNDER_POINTER: string = minuteBarAfter(REFETCH_FROM, 8);
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
* Where on the axis recharts reports each bar (`activeTooltipIndex`, a
|
|
149
|
+
* string in recharts 3). It works the index out again only when the
|
|
150
|
+
* pointer moves, so bars landing under a still pointer keep the pressed
|
|
151
|
+
* spot's index: the landed bar sits at A's.
|
|
152
|
+
*/
|
|
153
|
+
const AXIS_INDEX: Record<string, string> = {
|
|
154
|
+
[BAR_A]: "0",
|
|
155
|
+
[BAR_B]: "1",
|
|
156
|
+
[BAR_C]: "2",
|
|
157
|
+
[LANDED_BAR]: "0",
|
|
158
|
+
[LATE_BAR]: "25",
|
|
159
|
+
[BAR_AFTER_LATE_BAR]: "26",
|
|
160
|
+
[REFETCH_BAR_UNDER_POINTER]: "8",
|
|
161
|
+
[REFETCH_LAST_BAR]: "9",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// No selection band on the chart.
|
|
165
|
+
const NO_BAND: [null, null] = [null, null];
|
|
166
|
+
|
|
167
|
+
interface Harness {
|
|
168
|
+
// The hook as the host last rendered it.
|
|
169
|
+
result: { current: HistogramRangeSelectionState };
|
|
170
|
+
rerender: (options: HistogramRangeSelectionOptions) => void;
|
|
171
|
+
unmount: () => void;
|
|
172
|
+
onTimeRangeSelect: MockFunction;
|
|
173
|
+
// Handed to the hook only on a zoomed chart.
|
|
174
|
+
onZoomOut: MockFunction;
|
|
175
|
+
// How many times the host has rendered the hook so far.
|
|
176
|
+
renderCount: () => number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/*
|
|
180
|
+
* A host histogram with one-minute bars. A zoomed one shows a window a
|
|
181
|
+
* drag zoomed into, and so offers a way back out: `onZoomOut`.
|
|
182
|
+
*/
|
|
183
|
+
function renderSelection(isZoomed: boolean): Harness {
|
|
184
|
+
const onTimeRangeSelect: MockFunction = getJestMockFunction();
|
|
185
|
+
const onZoomOut: MockFunction = getJestMockFunction();
|
|
186
|
+
const renders: { count: number } = { count: 0 };
|
|
187
|
+
const initialProps: HistogramRangeSelectionOptions = {
|
|
188
|
+
onTimeRangeSelect: onTimeRangeSelect,
|
|
189
|
+
onZoomOut: isZoomed ? onZoomOut : undefined,
|
|
190
|
+
bucketIntervalMs: MINUTE_MS,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const hook: RenderHookResult<
|
|
194
|
+
HistogramRangeSelectionState,
|
|
195
|
+
HistogramRangeSelectionOptions
|
|
196
|
+
> = renderHook(
|
|
197
|
+
(options: HistogramRangeSelectionOptions): HistogramRangeSelectionState => {
|
|
198
|
+
renders.count += 1;
|
|
199
|
+
return useHistogramRangeSelection(options);
|
|
200
|
+
},
|
|
201
|
+
{ initialProps: initialProps },
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
result: hook.result,
|
|
206
|
+
rerender: (options: HistogramRangeSelectionOptions): void => {
|
|
207
|
+
hook.rerender(options);
|
|
208
|
+
},
|
|
209
|
+
unmount: hook.unmount,
|
|
210
|
+
onTimeRangeSelect: onTimeRangeSelect,
|
|
211
|
+
onZoomOut: onZoomOut,
|
|
212
|
+
renderCount: (): number => {
|
|
213
|
+
return renders.count;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type PointerHandler = (
|
|
219
|
+
state?: HistogramPointerState | null,
|
|
220
|
+
event?: ChartPointerEvent | null,
|
|
221
|
+
) => void;
|
|
222
|
+
|
|
223
|
+
/*
|
|
224
|
+
* Calls a chart-root handler as recharts does: with the bar under the
|
|
225
|
+
* pointer (none when `label` is undefined) and its place on the axis, and
|
|
226
|
+
* the React event. With no `event` at all it gets the bar alone, as from
|
|
227
|
+
* a host that hands none.
|
|
228
|
+
*/
|
|
229
|
+
function send(
|
|
230
|
+
handler: PointerHandler,
|
|
231
|
+
label: string | undefined,
|
|
232
|
+
event?: ChartPointerEvent | null,
|
|
233
|
+
): void {
|
|
234
|
+
act(() => {
|
|
235
|
+
const state: HistogramPointerState =
|
|
236
|
+
label === undefined
|
|
237
|
+
? { activeLabel: undefined }
|
|
238
|
+
: { activeLabel: label, activeTooltipIndex: AXIS_INDEX[label] };
|
|
239
|
+
if (event === undefined) {
|
|
240
|
+
handler(state);
|
|
241
|
+
} else {
|
|
242
|
+
handler(state, event);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function press(
|
|
248
|
+
harness: Harness,
|
|
249
|
+
label: string | undefined,
|
|
250
|
+
event?: ChartPointerEvent | null,
|
|
251
|
+
): void {
|
|
252
|
+
send(harness.result.current.onMouseDown, label, event);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function move(
|
|
256
|
+
harness: Harness,
|
|
257
|
+
label: string | undefined,
|
|
258
|
+
event?: ChartPointerEvent | null,
|
|
259
|
+
): void {
|
|
260
|
+
send(harness.result.current.onMouseMove, label, event);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function release(
|
|
264
|
+
harness: Harness,
|
|
265
|
+
label: string | undefined,
|
|
266
|
+
event?: ChartPointerEvent | null,
|
|
267
|
+
): void {
|
|
268
|
+
send(harness.result.current.onMouseUp, label, event);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// The browser's dblclick, which the host hands to the hook.
|
|
272
|
+
function dblclick(harness: Harness): void {
|
|
273
|
+
act(() => {
|
|
274
|
+
harness.result.current.onDoubleClick();
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
type PlaceholderPressEvent = Parameters<
|
|
279
|
+
HistogramPlaceholderProps["onMouseDown"]
|
|
280
|
+
>[0];
|
|
281
|
+
|
|
282
|
+
/*
|
|
283
|
+
* A press on a box standing in for the chart - its loader, its empty box -
|
|
284
|
+
* which the host spreads placeholderProps onto. React hands it the event;
|
|
285
|
+
* the click count and the button are all the hook reads of it.
|
|
286
|
+
*/
|
|
287
|
+
function pressPlaceholder(harness: Harness, event: ChartPointerEvent): void {
|
|
288
|
+
act(() => {
|
|
289
|
+
harness.result.current.placeholderProps.onMouseDown(
|
|
290
|
+
event as unknown as PlaceholderPressEvent,
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/*
|
|
296
|
+
* The main button going down or coming up at `clientX`. `clicks` is the
|
|
297
|
+
* browser's click count (MouseEvent.detail): 2 on both halves of a
|
|
298
|
+
* double-click's second click.
|
|
299
|
+
*/
|
|
300
|
+
function mainButton(clientX: number, clicks: number): ChartPointerEvent {
|
|
301
|
+
return { clientX: clientX, detail: clicks, button: 0 };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// The pointer moving to `clientX` with the button held. A move counts no clicks.
|
|
305
|
+
function pointerAt(clientX: number): ChartPointerEvent {
|
|
306
|
+
return { clientX: clientX, detail: 0, button: 0 };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/*
|
|
310
|
+
* The main button coming up away from the chart, where only the page hears
|
|
311
|
+
* it: the native event carries the click count and where the pointer is
|
|
312
|
+
* (clientX 0, left of every bar, unless said), but no bar.
|
|
313
|
+
*/
|
|
314
|
+
function releaseOnThePage(clicks: number, clientX: number = 0): void {
|
|
315
|
+
fireEvent.mouseUp(document.body, {
|
|
316
|
+
button: 0,
|
|
317
|
+
detail: clicks,
|
|
318
|
+
clientX: clientX,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/*
|
|
323
|
+
* Another button than the main one going down or coming up at `clientX`:
|
|
324
|
+
* 1 is the middle button, 2 the right one.
|
|
325
|
+
*/
|
|
326
|
+
function otherButton(
|
|
327
|
+
button: number,
|
|
328
|
+
clientX: number,
|
|
329
|
+
clicks: number,
|
|
330
|
+
): ChartPointerEvent {
|
|
331
|
+
return { clientX: clientX, detail: clicks, button: button };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
* The page-wide mouseup listeners a press adds to hear its release off the
|
|
336
|
+
* chart, and the ones taken away again.
|
|
337
|
+
*/
|
|
338
|
+
interface PageListenerSpies {
|
|
339
|
+
add: SpyInstance<Window["addEventListener"]>;
|
|
340
|
+
remove: SpyInstance<Window["removeEventListener"]>;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function spyOnPageListeners(): PageListenerSpies {
|
|
344
|
+
return {
|
|
345
|
+
add: jest.spyOn(window, "addEventListener"),
|
|
346
|
+
remove: jest.spyOn(window, "removeEventListener"),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function mouseUpListeners(calls: Array<Array<unknown>>): Array<unknown> {
|
|
351
|
+
return calls
|
|
352
|
+
.filter((call: Array<unknown>): boolean => {
|
|
353
|
+
return call[0] === "mouseup";
|
|
354
|
+
})
|
|
355
|
+
.map((call: Array<unknown>): unknown => {
|
|
356
|
+
return call[1];
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// The mouseup listeners the page still has: added and not yet taken away.
|
|
361
|
+
function pageListenersLeft(spies: PageListenerSpies): Array<unknown> {
|
|
362
|
+
const removed: Array<unknown> = mouseUpListeners(spies.remove.mock.calls);
|
|
363
|
+
|
|
364
|
+
return mouseUpListeners(spies.add.mock.calls).filter(
|
|
365
|
+
(added: unknown): boolean => {
|
|
366
|
+
return !removed.includes(added);
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// The host re-rendering the chart with its zoom gone: no onZoomOut on offer.
|
|
372
|
+
function endZoomAnotherWay(harness: Harness): void {
|
|
373
|
+
harness.rerender({
|
|
374
|
+
onTimeRangeSelect: harness.onTimeRangeSelect,
|
|
375
|
+
onZoomOut: undefined,
|
|
376
|
+
bucketIntervalMs: MINUTE_MS,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/*
|
|
381
|
+
* A double-click's first click on bar A, and its second press there: what
|
|
382
|
+
* the browser sends up to the second release.
|
|
383
|
+
*/
|
|
384
|
+
function clickAThenPressAgain(harness: Harness): void {
|
|
385
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
386
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
387
|
+
press(harness, BAR_A, mainButton(X_A, 2));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function letTimePass(ms: number): void {
|
|
391
|
+
act(() => {
|
|
392
|
+
jest.advanceTimersByTime(ms);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// The first and last bar the selection band spans.
|
|
397
|
+
function band(harness: Harness): [string | null, string | null] {
|
|
398
|
+
return [
|
|
399
|
+
harness.result.current.selectionStart,
|
|
400
|
+
harness.result.current.selectionEnd,
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// The window a selection from bar `first` through bar `last` zooms into.
|
|
405
|
+
function windowOf(first: string, last: string): [string, string] {
|
|
406
|
+
return [first, new Date(Date.parse(last) + MINUTE_MS).toISOString()];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function selections(onTimeRangeSelect: MockFunction): Array<[string, string]> {
|
|
410
|
+
return onTimeRangeSelect.mock.calls.map(
|
|
411
|
+
(call: Array<unknown>): [string, string] => {
|
|
412
|
+
return [(call[0] as Date).toISOString(), (call[1] as Date).toISOString()];
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
interface ChartUnderTest {
|
|
418
|
+
name: string;
|
|
419
|
+
isZoomed: boolean;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const CHARTS: Array<ChartUnderTest> = [
|
|
423
|
+
{ name: "an unzoomed chart", isZoomed: false },
|
|
424
|
+
{ name: "a zoomed chart", isZoomed: true },
|
|
425
|
+
];
|
|
426
|
+
|
|
427
|
+
beforeEach(() => {
|
|
428
|
+
jest.useFakeTimers();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
afterEach(() => {
|
|
432
|
+
cleanup();
|
|
433
|
+
jest.restoreAllMocks();
|
|
434
|
+
jest.useRealTimers();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe("a double-click on a zoomed chart", () => {
|
|
438
|
+
test("the second press cancels the first click's zoom-in the moment it lands, renders nothing, and leaves the zoom-out to its release", () => {
|
|
439
|
+
const harness: Harness = renderSelection(true);
|
|
440
|
+
|
|
441
|
+
// The first click lights its bar while its zoom-in waits.
|
|
442
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
443
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
444
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
445
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
446
|
+
|
|
447
|
+
/*
|
|
448
|
+
* The second press may land on that band: a render now could take
|
|
449
|
+
* the node away, and the browser's click and dblclick with it.
|
|
450
|
+
*/
|
|
451
|
+
const rendersBefore: number = harness.renderCount();
|
|
452
|
+
press(harness, BAR_A, mainButton(X_A, 2));
|
|
453
|
+
|
|
454
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
455
|
+
expect(harness.renderCount()).toBe(rendersBefore);
|
|
456
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
457
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
458
|
+
|
|
459
|
+
// However long the button is held, nothing zooms in, nor out yet.
|
|
460
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
461
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
462
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
463
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
464
|
+
|
|
465
|
+
release(harness, BAR_A, mainButton(X_A, 2));
|
|
466
|
+
letTimePass(0);
|
|
467
|
+
|
|
468
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
469
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("new bars landing under the second press: its release zooms nowhere, and the chart zooms out once, in the next task", () => {
|
|
473
|
+
const harness: Harness = renderSelection(true);
|
|
474
|
+
|
|
475
|
+
clickAThenPressAgain(harness);
|
|
476
|
+
const rendersBefore: number = harness.renderCount();
|
|
477
|
+
|
|
478
|
+
// The new bars land; recharts reports the one now under the pointer.
|
|
479
|
+
release(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
480
|
+
|
|
481
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
482
|
+
// The release renders nothing either: the band stays for the zoom-out.
|
|
483
|
+
expect(harness.renderCount()).toBe(rendersBefore);
|
|
484
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
485
|
+
// Not yet: a dblclick the browser still sends comes first.
|
|
486
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
487
|
+
|
|
488
|
+
// None came (its node was re-keyed away), and the next task zooms out.
|
|
489
|
+
letTimePass(0);
|
|
490
|
+
|
|
491
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
492
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
493
|
+
|
|
494
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
495
|
+
|
|
496
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
497
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("the second press starts no selection: a move to another bar while it is held paints nothing, and releasing there zooms out", () => {
|
|
501
|
+
const harness: Harness = renderSelection(true);
|
|
502
|
+
|
|
503
|
+
clickAThenPressAgain(harness);
|
|
504
|
+
move(harness, BAR_C, pointerAt(X_C));
|
|
505
|
+
|
|
506
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
507
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
508
|
+
|
|
509
|
+
release(harness, BAR_C, mainButton(X_C, 2));
|
|
510
|
+
letTimePass(0);
|
|
511
|
+
|
|
512
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
513
|
+
|
|
514
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
515
|
+
|
|
516
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const SECOND_RELEASES: Array<{
|
|
520
|
+
name: string;
|
|
521
|
+
release: (harness: Harness) => void;
|
|
522
|
+
}> = [
|
|
523
|
+
{
|
|
524
|
+
name: "over the bar it pressed",
|
|
525
|
+
release: (harness: Harness): void => {
|
|
526
|
+
release(harness, BAR_A, mainButton(X_A, 2));
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "over a bar of new data that landed under it",
|
|
531
|
+
release: (harness: Harness): void => {
|
|
532
|
+
release(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "off the chart, where only the page hears it",
|
|
537
|
+
release: (): void => {
|
|
538
|
+
releaseOnThePage(2);
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
for (const secondRelease of SECOND_RELEASES) {
|
|
544
|
+
test(`one that keeps its dblclick zooms out exactly once, its second press released ${secondRelease.name}`, () => {
|
|
545
|
+
const harness: Harness = renderSelection(true);
|
|
546
|
+
|
|
547
|
+
clickAThenPressAgain(harness);
|
|
548
|
+
secondRelease.release(harness);
|
|
549
|
+
// The browser's dblclick, straight after the release's click.
|
|
550
|
+
dblclick(harness);
|
|
551
|
+
|
|
552
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
553
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
554
|
+
|
|
555
|
+
// The zoom-out the release left for the next task stood down.
|
|
556
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
557
|
+
|
|
558
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
559
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const DBLCLICK_FATES: Array<{ name: string; arrives: boolean }> = [
|
|
564
|
+
{ name: "whether its dblclick is lost", arrives: false },
|
|
565
|
+
{ name: "or delivered", arrives: true },
|
|
566
|
+
];
|
|
567
|
+
|
|
568
|
+
for (const dblclickFate of DBLCLICK_FATES) {
|
|
569
|
+
test(`new bars landing under the FIRST press: neither click zooms in, and the double-click zooms out once, ${dblclickFate.name}`, () => {
|
|
570
|
+
const harness: Harness = renderSelection(true);
|
|
571
|
+
|
|
572
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
573
|
+
release(harness, LANDED_BAR, mainButton(X_A, 1));
|
|
574
|
+
|
|
575
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
576
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
577
|
+
|
|
578
|
+
press(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
579
|
+
release(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
580
|
+
if (dblclickFate.arrives) {
|
|
581
|
+
dblclick(harness);
|
|
582
|
+
}
|
|
583
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
584
|
+
|
|
585
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
586
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
test("a second press with no bar under it (the new data has fewer bars) is still the zoom-out", () => {
|
|
591
|
+
const harness: Harness = renderSelection(true);
|
|
592
|
+
|
|
593
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
594
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
595
|
+
press(harness, undefined, mainButton(X_A, 2));
|
|
596
|
+
release(harness, undefined, mainButton(X_A, 2));
|
|
597
|
+
letTimePass(0);
|
|
598
|
+
|
|
599
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
600
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
601
|
+
|
|
602
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
603
|
+
|
|
604
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
/*
|
|
608
|
+
* The chart never hears a release off it; the page does, through the
|
|
609
|
+
* listener the press added. That is still the end of the zoom-out press:
|
|
610
|
+
* it zooms out, and the band it kept goes with the zoom.
|
|
611
|
+
*/
|
|
612
|
+
test("a second press released off the chart, which only the page hears, is still the zoom-out: once, in the next task, and nothing zooms in", () => {
|
|
613
|
+
const harness: Harness = renderSelection(true);
|
|
614
|
+
|
|
615
|
+
clickAThenPressAgain(harness);
|
|
616
|
+
releaseOnThePage(2);
|
|
617
|
+
|
|
618
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
619
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
620
|
+
|
|
621
|
+
letTimePass(0);
|
|
622
|
+
|
|
623
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
624
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
625
|
+
|
|
626
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
627
|
+
|
|
628
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
629
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
/*
|
|
633
|
+
* A second press whose release the chart never heard must leave nothing
|
|
634
|
+
* armed behind: a later release over the chart that no press on it
|
|
635
|
+
* began (a drag from elsewhere on the page, ending over the chart) is
|
|
636
|
+
* not the end of that press, and must not zoom out at some random later
|
|
637
|
+
* moment.
|
|
638
|
+
*/
|
|
639
|
+
test("after a second press released off the chart, a release over it that no press on it began sets nothing off, and a later drag selects as ever", () => {
|
|
640
|
+
const harness: Harness = renderSelection(true);
|
|
641
|
+
|
|
642
|
+
clickAThenPressAgain(harness);
|
|
643
|
+
releaseOnThePage(2);
|
|
644
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
645
|
+
const zoomOutsBefore: number = harness.onZoomOut.mock.calls.length;
|
|
646
|
+
|
|
647
|
+
release(harness, BAR_B, mainButton(X_B, 1));
|
|
648
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
649
|
+
|
|
650
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(zoomOutsBefore);
|
|
651
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
652
|
+
|
|
653
|
+
press(harness, BAR_B, mainButton(X_B, 1));
|
|
654
|
+
move(harness, BAR_C, pointerAt(X_C));
|
|
655
|
+
|
|
656
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
657
|
+
expect(band(harness)).toEqual([BAR_B, BAR_C]);
|
|
658
|
+
|
|
659
|
+
release(harness, BAR_C, mainButton(X_C, 1));
|
|
660
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
661
|
+
|
|
662
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
663
|
+
windowOf(BAR_B, BAR_C),
|
|
664
|
+
]);
|
|
665
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(zoomOutsBefore);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
/*
|
|
669
|
+
* The window can lose a release altogether (it lost focus mid-press).
|
|
670
|
+
* The next press is a new gesture, and the release the page hears at
|
|
671
|
+
* its end is that press's, not the lost one's.
|
|
672
|
+
*/
|
|
673
|
+
test("a second press whose release never comes, then a drag let go on the page: the drag selects, and nothing zooms out", () => {
|
|
674
|
+
const harness: Harness = renderSelection(true);
|
|
675
|
+
|
|
676
|
+
clickAThenPressAgain(harness);
|
|
677
|
+
press(harness, BAR_B, mainButton(X_B, 1));
|
|
678
|
+
move(harness, BAR_C, pointerAt(X_C));
|
|
679
|
+
|
|
680
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
681
|
+
expect(band(harness)).toEqual([BAR_B, BAR_C]);
|
|
682
|
+
|
|
683
|
+
releaseOnThePage(1);
|
|
684
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
685
|
+
|
|
686
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
687
|
+
windowOf(BAR_B, BAR_C),
|
|
688
|
+
]);
|
|
689
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
690
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
691
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
test("a second press released over the chart, and heard by the page as well, zooms out once", () => {
|
|
695
|
+
const harness: Harness = renderSelection(true);
|
|
696
|
+
|
|
697
|
+
clickAThenPressAgain(harness);
|
|
698
|
+
release(harness, BAR_A, mainButton(X_A, 2));
|
|
699
|
+
// The same mouseup, bubbling on up to the page.
|
|
700
|
+
releaseOnThePage(2);
|
|
701
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
702
|
+
|
|
703
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
704
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
/*
|
|
708
|
+
* The refetch that started all this re-renders the host in between, with
|
|
709
|
+
* a handler that knows the latest window.
|
|
710
|
+
*/
|
|
711
|
+
test("the zoom-out calls the onZoomOut of the host's newest render", () => {
|
|
712
|
+
const harness: Harness = renderSelection(true);
|
|
713
|
+
|
|
714
|
+
clickAThenPressAgain(harness);
|
|
715
|
+
release(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
716
|
+
|
|
717
|
+
const newestZoomOut: MockFunction = getJestMockFunction();
|
|
718
|
+
harness.rerender({
|
|
719
|
+
onTimeRangeSelect: harness.onTimeRangeSelect,
|
|
720
|
+
onZoomOut: newestZoomOut,
|
|
721
|
+
bucketIntervalMs: MINUTE_MS,
|
|
722
|
+
});
|
|
723
|
+
letTimePass(0);
|
|
724
|
+
|
|
725
|
+
expect(newestZoomOut).toHaveBeenCalledTimes(1);
|
|
726
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("a chart unmounted before its zoom-out runs never zooms out", () => {
|
|
730
|
+
const harness: Harness = renderSelection(true);
|
|
731
|
+
|
|
732
|
+
clickAThenPressAgain(harness);
|
|
733
|
+
release(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
734
|
+
harness.unmount();
|
|
735
|
+
|
|
736
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
737
|
+
|
|
738
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
739
|
+
|
|
740
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
741
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
describe("a press whose bar changes under a still pointer", () => {
|
|
746
|
+
for (const chart of CHARTS) {
|
|
747
|
+
test(`on ${chart.name}, is not a drag, paints nothing and zooms nowhere`, () => {
|
|
748
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
749
|
+
|
|
750
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
751
|
+
// The new bars land: recharts reports the one now under the pointer.
|
|
752
|
+
move(harness, LANDED_BAR, pointerAt(X_A));
|
|
753
|
+
|
|
754
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
755
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
756
|
+
|
|
757
|
+
// A hand on a mouse is never quite still.
|
|
758
|
+
move(harness, LANDED_BAR, pointerAt(X_A + 2));
|
|
759
|
+
|
|
760
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
761
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
762
|
+
|
|
763
|
+
release(harness, LANDED_BAR, mainButton(X_A + 1, 1));
|
|
764
|
+
|
|
765
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
766
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
767
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
768
|
+
|
|
769
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
770
|
+
|
|
771
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
772
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/*
|
|
777
|
+
* Pressed at the very edge of its bar, so a few pixels take the pointer
|
|
778
|
+
* onto the next one.
|
|
779
|
+
*/
|
|
780
|
+
const DIRECTIONS: Array<{
|
|
781
|
+
name: string;
|
|
782
|
+
pressedBar: string;
|
|
783
|
+
pressedX: number;
|
|
784
|
+
otherBar: string;
|
|
785
|
+
sign: number;
|
|
786
|
+
}> = [
|
|
787
|
+
{
|
|
788
|
+
name: "rightwards",
|
|
789
|
+
pressedBar: BAR_A,
|
|
790
|
+
pressedX: X_A,
|
|
791
|
+
otherBar: BAR_B,
|
|
792
|
+
sign: 1,
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
name: "leftwards",
|
|
796
|
+
pressedBar: BAR_B,
|
|
797
|
+
pressedX: X_B,
|
|
798
|
+
otherBar: BAR_A,
|
|
799
|
+
sign: -1,
|
|
800
|
+
},
|
|
801
|
+
];
|
|
802
|
+
|
|
803
|
+
for (const direction of DIRECTIONS) {
|
|
804
|
+
test(`a pointer moved ${direction.name} by HISTOGRAM_PRESS_STILLNESS_PX has not left its bar; one pixel further, onto another bar, is a drag`, () => {
|
|
805
|
+
const harness: Harness = renderSelection(false);
|
|
806
|
+
|
|
807
|
+
press(harness, direction.pressedBar, mainButton(direction.pressedX, 1));
|
|
808
|
+
move(
|
|
809
|
+
harness,
|
|
810
|
+
direction.otherBar,
|
|
811
|
+
pointerAt(
|
|
812
|
+
direction.pressedX + direction.sign * HISTOGRAM_PRESS_STILLNESS_PX,
|
|
813
|
+
),
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
817
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
818
|
+
|
|
819
|
+
const draggedX: number =
|
|
820
|
+
direction.pressedX +
|
|
821
|
+
direction.sign * (HISTOGRAM_PRESS_STILLNESS_PX + 1);
|
|
822
|
+
move(harness, direction.otherBar, pointerAt(draggedX));
|
|
823
|
+
|
|
824
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
825
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
826
|
+
|
|
827
|
+
release(harness, direction.otherBar, mainButton(draggedX, 1));
|
|
828
|
+
|
|
829
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
830
|
+
windowOf(BAR_A, BAR_B),
|
|
831
|
+
]);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
/*
|
|
835
|
+
* A trackpad click, or a slightly unsteady mouse, lets go a pixel or
|
|
836
|
+
* two over the neighbour of the bar it pressed. It is still a click on
|
|
837
|
+
* the bar pressed - not dropped as if new data had landed under it,
|
|
838
|
+
* which a first cut of the #4116 fix did.
|
|
839
|
+
*/
|
|
840
|
+
for (const chart of CHARTS) {
|
|
841
|
+
test(`on ${chart.name}, a click that drifts ${direction.name} across its bar's edge zooms into the bar it pressed`, () => {
|
|
842
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
843
|
+
const driftedX: number =
|
|
844
|
+
direction.pressedX + direction.sign * HISTOGRAM_PRESS_STILLNESS_PX;
|
|
845
|
+
|
|
846
|
+
press(harness, direction.pressedBar, mainButton(direction.pressedX, 1));
|
|
847
|
+
move(harness, direction.otherBar, pointerAt(driftedX));
|
|
848
|
+
release(harness, direction.otherBar, mainButton(driftedX, 1));
|
|
849
|
+
|
|
850
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
851
|
+
|
|
852
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
853
|
+
|
|
854
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
855
|
+
windowOf(direction.pressedBar, direction.pressedBar),
|
|
856
|
+
]);
|
|
857
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
/*
|
|
864
|
+
* A still press on a bar right of where a shorter refetch ends (commit
|
|
865
|
+
* b6ec899df4). recharts clamps the stale index onto the new last bar, so
|
|
866
|
+
* the release reports another index as well as another bar - just what a
|
|
867
|
+
* hand's drift across an edge reports - and the hook took it for one: a
|
|
868
|
+
* click on the pressed bar, which zoomed into 10:25, a bar no longer on the
|
|
869
|
+
* chart. What tells the two apart is the pointer: a drift moves it; new
|
|
870
|
+
* bars land under a pointer that never moved at all, not by a pixel. A move
|
|
871
|
+
* Chrome makes up at the very same spot, when the page under a still
|
|
872
|
+
* pointer changes, does not count as one.
|
|
873
|
+
*/
|
|
874
|
+
describe("a still press on a bar right of where a shorter refetch ends", () => {
|
|
875
|
+
const BETWEEN_PRESS_AND_RELEASE: Array<{
|
|
876
|
+
name: string;
|
|
877
|
+
between: (harness: Harness) => void;
|
|
878
|
+
// What recharts reports under the pointer at the release.
|
|
879
|
+
releasedOver: string;
|
|
880
|
+
}> = [
|
|
881
|
+
{
|
|
882
|
+
name: "with no move between (recharts clamps its stale index onto the new last bar)",
|
|
883
|
+
between: (): void => {
|
|
884
|
+
// Nothing: the new bars land, and the pointer stays where it is.
|
|
885
|
+
},
|
|
886
|
+
releasedOver: REFETCH_LAST_BAR,
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
name: "with a move Chrome makes up at the same spot once the new bars are drawn (recharts hit-tests them)",
|
|
890
|
+
between: (harness: Harness): void => {
|
|
891
|
+
move(harness, REFETCH_BAR_UNDER_POINTER, pointerAt(X_LATE));
|
|
892
|
+
},
|
|
893
|
+
releasedOver: REFETCH_BAR_UNDER_POINTER,
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: "with a move Chrome makes up at the same spot before the new bars land",
|
|
897
|
+
between: (harness: Harness): void => {
|
|
898
|
+
move(harness, LATE_BAR, pointerAt(X_LATE));
|
|
899
|
+
},
|
|
900
|
+
releasedOver: REFETCH_LAST_BAR,
|
|
901
|
+
},
|
|
902
|
+
];
|
|
903
|
+
|
|
904
|
+
for (const chart of CHARTS) {
|
|
905
|
+
for (const path of BETWEEN_PRESS_AND_RELEASE) {
|
|
906
|
+
test(`on ${chart.name}, ${path.name}: zooms nowhere, paints nothing and leaves nothing waiting`, () => {
|
|
907
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
908
|
+
|
|
909
|
+
press(harness, LATE_BAR, mainButton(X_LATE, 1));
|
|
910
|
+
path.between(harness);
|
|
911
|
+
|
|
912
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
913
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
914
|
+
|
|
915
|
+
release(harness, path.releasedOver, mainButton(X_LATE, 1));
|
|
916
|
+
|
|
917
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
918
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
919
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
920
|
+
// No click waits out the double-click window to zoom in later.
|
|
921
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
922
|
+
|
|
923
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
924
|
+
|
|
925
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
926
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
927
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/*
|
|
932
|
+
* The same chart, the same bars, but the pointer really moved: two
|
|
933
|
+
* pixels right, over the next bar. A hand's drift - the index moves
|
|
934
|
+
* with it, as with the clamp - and still a click on the bar pressed.
|
|
935
|
+
*/
|
|
936
|
+
test(`on ${chart.name}, a real move of 2px across the pressed bar's edge is still a click on the bar it pressed`, () => {
|
|
937
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
938
|
+
|
|
939
|
+
press(harness, LATE_BAR, mainButton(X_LATE, 1));
|
|
940
|
+
move(harness, BAR_AFTER_LATE_BAR, pointerAt(X_LATE + 2));
|
|
941
|
+
|
|
942
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
943
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
944
|
+
|
|
945
|
+
release(harness, BAR_AFTER_LATE_BAR, mainButton(X_LATE + 2, 1));
|
|
946
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
947
|
+
|
|
948
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
949
|
+
windowOf(LATE_BAR, LATE_BAR),
|
|
950
|
+
]);
|
|
951
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
952
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
describe("drags and clicks select what they always did", () => {
|
|
958
|
+
for (const chart of CHARTS) {
|
|
959
|
+
test(`on ${chart.name}, a drag paints its band as it goes and zooms into both end bars whole, at once`, () => {
|
|
960
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
961
|
+
|
|
962
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
963
|
+
move(harness, BAR_B, pointerAt(X_B));
|
|
964
|
+
|
|
965
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
966
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
967
|
+
|
|
968
|
+
release(harness, BAR_B, mainButton(X_B, 1));
|
|
969
|
+
|
|
970
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
971
|
+
windowOf(BAR_A, BAR_B),
|
|
972
|
+
]);
|
|
973
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
974
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
975
|
+
|
|
976
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
977
|
+
|
|
978
|
+
expect(harness.onTimeRangeSelect).toHaveBeenCalledTimes(1);
|
|
979
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
test(`on ${chart.name}, the same drag right to left paints its band in time order and zooms into the same window`, () => {
|
|
983
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
984
|
+
|
|
985
|
+
press(harness, BAR_B, mainButton(X_B, 1));
|
|
986
|
+
move(harness, BAR_A, pointerAt(X_A));
|
|
987
|
+
|
|
988
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
989
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
990
|
+
|
|
991
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
992
|
+
|
|
993
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
994
|
+
windowOf(BAR_A, BAR_B),
|
|
995
|
+
]);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
test(`on ${chart.name}, a flick released over another bar before any of its moves came in is a drag to that bar`, () => {
|
|
999
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
1000
|
+
|
|
1001
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1002
|
+
release(harness, BAR_C, mainButton(X_C, 1));
|
|
1003
|
+
|
|
1004
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1005
|
+
windowOf(BAR_A, BAR_C),
|
|
1006
|
+
]);
|
|
1007
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1008
|
+
|
|
1009
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1010
|
+
|
|
1011
|
+
expect(harness.onTimeRangeSelect).toHaveBeenCalledTimes(1);
|
|
1012
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
test("on an unzoomed chart, a still click zooms into its bar at once", () => {
|
|
1017
|
+
const harness: Harness = renderSelection(false);
|
|
1018
|
+
|
|
1019
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1020
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
1021
|
+
|
|
1022
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1023
|
+
windowOf(BAR_A, BAR_A),
|
|
1024
|
+
]);
|
|
1025
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
test("on a zoomed chart, a still click zooms into its bar only once the double-click window has passed, the bar lit meanwhile", () => {
|
|
1029
|
+
const harness: Harness = renderSelection(true);
|
|
1030
|
+
|
|
1031
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1032
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
1033
|
+
|
|
1034
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
1035
|
+
|
|
1036
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS - 1);
|
|
1037
|
+
|
|
1038
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1039
|
+
|
|
1040
|
+
letTimePass(1);
|
|
1041
|
+
|
|
1042
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1043
|
+
windowOf(BAR_A, BAR_A),
|
|
1044
|
+
]);
|
|
1045
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1046
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1047
|
+
});
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const EVENTLESS_HOSTS: Array<{
|
|
1051
|
+
name: string;
|
|
1052
|
+
event: null | undefined;
|
|
1053
|
+
}> = [
|
|
1054
|
+
{ name: "no event at all", event: undefined },
|
|
1055
|
+
{ name: "a null event", event: null },
|
|
1056
|
+
];
|
|
1057
|
+
|
|
1058
|
+
for (const host of EVENTLESS_HOSTS) {
|
|
1059
|
+
describe(`a host that hands the handlers ${host.name} keeps the label-only reading`, () => {
|
|
1060
|
+
test("a press, a move onto another bar and a release there drag as they always did", () => {
|
|
1061
|
+
const harness: Harness = renderSelection(false);
|
|
1062
|
+
|
|
1063
|
+
press(harness, BAR_A, host.event);
|
|
1064
|
+
move(harness, BAR_B, host.event);
|
|
1065
|
+
|
|
1066
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
1067
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
1068
|
+
|
|
1069
|
+
release(harness, BAR_B, host.event);
|
|
1070
|
+
|
|
1071
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1072
|
+
windowOf(BAR_A, BAR_B),
|
|
1073
|
+
]);
|
|
1074
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
test("a press and a release over another bar, with no move between, drag from the one to the other", () => {
|
|
1078
|
+
const harness: Harness = renderSelection(false);
|
|
1079
|
+
|
|
1080
|
+
press(harness, BAR_A, host.event);
|
|
1081
|
+
release(harness, BAR_B, host.event);
|
|
1082
|
+
|
|
1083
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1084
|
+
windowOf(BAR_A, BAR_B),
|
|
1085
|
+
]);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("a press and a release over its own bar zoom into that bar", () => {
|
|
1089
|
+
const harness: Harness = renderSelection(false);
|
|
1090
|
+
|
|
1091
|
+
press(harness, BAR_A, host.event);
|
|
1092
|
+
release(harness, BAR_A, host.event);
|
|
1093
|
+
|
|
1094
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1095
|
+
windowOf(BAR_A, BAR_A),
|
|
1096
|
+
]);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
test("on a zoomed chart, a double-click zooms out through its dblclick alone, and neither click zooms in", () => {
|
|
1100
|
+
const harness: Harness = renderSelection(true);
|
|
1101
|
+
|
|
1102
|
+
press(harness, BAR_A, host.event);
|
|
1103
|
+
release(harness, BAR_A, host.event);
|
|
1104
|
+
press(harness, BAR_A, host.event);
|
|
1105
|
+
release(harness, BAR_A, host.event);
|
|
1106
|
+
|
|
1107
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1108
|
+
|
|
1109
|
+
dblclick(harness);
|
|
1110
|
+
|
|
1111
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1112
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1113
|
+
|
|
1114
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1115
|
+
|
|
1116
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1117
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
describe("an unzoomed chart, which offers no zoom-out", () => {
|
|
1123
|
+
test("a press the browser counts as a double-click's second is an ordinary press: a still click zooms into its bar at once", () => {
|
|
1124
|
+
const harness: Harness = renderSelection(false);
|
|
1125
|
+
|
|
1126
|
+
press(harness, BAR_A, mainButton(X_A, 2));
|
|
1127
|
+
release(harness, BAR_A, mainButton(X_A, 2));
|
|
1128
|
+
|
|
1129
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1130
|
+
windowOf(BAR_A, BAR_A),
|
|
1131
|
+
]);
|
|
1132
|
+
|
|
1133
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1134
|
+
|
|
1135
|
+
expect(harness.onTimeRangeSelect).toHaveBeenCalledTimes(1);
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test("a drag from a press the browser counts as a double-click's second selects as any drag does", () => {
|
|
1139
|
+
const harness: Harness = renderSelection(false);
|
|
1140
|
+
|
|
1141
|
+
press(harness, BAR_A, mainButton(X_A, 2));
|
|
1142
|
+
move(harness, BAR_B, pointerAt(X_B));
|
|
1143
|
+
|
|
1144
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
1145
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
1146
|
+
|
|
1147
|
+
release(harness, BAR_B, mainButton(X_B, 2));
|
|
1148
|
+
|
|
1149
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1150
|
+
windowOf(BAR_A, BAR_B),
|
|
1151
|
+
]);
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
test("a dblclick with no click waiting does nothing", () => {
|
|
1155
|
+
const harness: Harness = renderSelection(false);
|
|
1156
|
+
const rendersBefore: number = harness.renderCount();
|
|
1157
|
+
|
|
1158
|
+
dblclick(harness);
|
|
1159
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1160
|
+
|
|
1161
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1162
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1163
|
+
expect(harness.renderCount()).toBe(rendersBefore);
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
/*
|
|
1168
|
+
* The window can lose the release of a double-click's second press: it
|
|
1169
|
+
* lost focus with the button down. The page-wide listener that press added
|
|
1170
|
+
* then hears the next release, which belongs to a later, unrelated click -
|
|
1171
|
+
* one the browser counts 1. That release ends the second press, and must
|
|
1172
|
+
* not zoom the chart out.
|
|
1173
|
+
*/
|
|
1174
|
+
describe("a second press whose own release never comes", () => {
|
|
1175
|
+
const LATER_RELEASES: Array<{
|
|
1176
|
+
name: string;
|
|
1177
|
+
release: (harness: Harness) => void;
|
|
1178
|
+
}> = [
|
|
1179
|
+
{
|
|
1180
|
+
name: "a click elsewhere on the page, which only the page hears",
|
|
1181
|
+
release: (): void => {
|
|
1182
|
+
releaseOnThePage(1, X_C + 400);
|
|
1183
|
+
},
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
name: "a press made elsewhere on the page and let go over the chart",
|
|
1187
|
+
release: (harness: Harness): void => {
|
|
1188
|
+
release(harness, BAR_B, mainButton(X_B, 1));
|
|
1189
|
+
},
|
|
1190
|
+
},
|
|
1191
|
+
];
|
|
1192
|
+
|
|
1193
|
+
for (const laterRelease of LATER_RELEASES) {
|
|
1194
|
+
test(`the release of ${laterRelease.name}, counted 1, ends it without zooming out, and the page stops listening`, () => {
|
|
1195
|
+
const harness: Harness = renderSelection(true);
|
|
1196
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1197
|
+
|
|
1198
|
+
clickAThenPressAgain(harness);
|
|
1199
|
+
|
|
1200
|
+
// The second press listens on the page for its release.
|
|
1201
|
+
expect(pageListenersLeft(pageListeners)).toHaveLength(1);
|
|
1202
|
+
|
|
1203
|
+
laterRelease.release(harness);
|
|
1204
|
+
|
|
1205
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1206
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1207
|
+
|
|
1208
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1209
|
+
|
|
1210
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1211
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1212
|
+
|
|
1213
|
+
// Later releases on the page, however counted, set nothing off either.
|
|
1214
|
+
releaseOnThePage(1);
|
|
1215
|
+
releaseOnThePage(2);
|
|
1216
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1217
|
+
|
|
1218
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1219
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1220
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
test("after that, a double-click zooms out once, as ever", () => {
|
|
1225
|
+
const harness: Harness = renderSelection(true);
|
|
1226
|
+
|
|
1227
|
+
clickAThenPressAgain(harness);
|
|
1228
|
+
releaseOnThePage(1, X_C + 400);
|
|
1229
|
+
// The reader carries on for a while before double-clicking the chart.
|
|
1230
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1231
|
+
|
|
1232
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1233
|
+
|
|
1234
|
+
clickAThenPressAgain(harness);
|
|
1235
|
+
release(harness, BAR_A, mainButton(X_A, 2));
|
|
1236
|
+
letTimePass(0);
|
|
1237
|
+
|
|
1238
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1239
|
+
|
|
1240
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1241
|
+
|
|
1242
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1243
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
/*
|
|
1247
|
+
* The second press dropped the first click's zoom-in but kept its band,
|
|
1248
|
+
* for the zoom-out to clear. When a later click's release ends that
|
|
1249
|
+
* press, no zoom-out is coming, so the band goes then. Before commit
|
|
1250
|
+
* b6ec899df4 nothing cleared it: bar A stayed lit, as if about to open,
|
|
1251
|
+
* however long one waited, until the next click on the chart replaced
|
|
1252
|
+
* it. Same cause as the last test of the next describe.
|
|
1253
|
+
*/
|
|
1254
|
+
for (const laterRelease of LATER_RELEASES) {
|
|
1255
|
+
test(`the first click's band, kept for a zoom-out that is not coming, goes when the release of ${laterRelease.name} ends the press`, () => {
|
|
1256
|
+
const harness: Harness = renderSelection(true);
|
|
1257
|
+
|
|
1258
|
+
clickAThenPressAgain(harness);
|
|
1259
|
+
|
|
1260
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
1261
|
+
|
|
1262
|
+
laterRelease.release(harness);
|
|
1263
|
+
|
|
1264
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1265
|
+
|
|
1266
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1267
|
+
|
|
1268
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1269
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1270
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
/*
|
|
1276
|
+
* A click on a zoomed chart waits out the double-click window before it
|
|
1277
|
+
* zooms in. If the zoom ends another way meanwhile - Reset zoom, the time
|
|
1278
|
+
* picker, an empty state's own double-click - the host stops offering
|
|
1279
|
+
* onZoomOut, and that click must not zoom back into the window the reader
|
|
1280
|
+
* has just left, nor leave its bar lit.
|
|
1281
|
+
*/
|
|
1282
|
+
describe("a zoom that ends another way while a click on the chart waits", () => {
|
|
1283
|
+
test("the click waiting to zoom in is dropped, and its band goes at once", () => {
|
|
1284
|
+
const harness: Harness = renderSelection(true);
|
|
1285
|
+
|
|
1286
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1287
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
1288
|
+
|
|
1289
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
1290
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1291
|
+
|
|
1292
|
+
endZoomAnotherWay(harness);
|
|
1293
|
+
|
|
1294
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1295
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1296
|
+
|
|
1297
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1298
|
+
|
|
1299
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1300
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1301
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
test("a click after that zooms in at once, as on any unzoomed chart", () => {
|
|
1305
|
+
const harness: Harness = renderSelection(true);
|
|
1306
|
+
|
|
1307
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1308
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
1309
|
+
endZoomAnotherWay(harness);
|
|
1310
|
+
|
|
1311
|
+
press(harness, BAR_B, mainButton(X_B, 1));
|
|
1312
|
+
release(harness, BAR_B, mainButton(X_B, 1));
|
|
1313
|
+
|
|
1314
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1315
|
+
windowOf(BAR_B, BAR_B),
|
|
1316
|
+
]);
|
|
1317
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1318
|
+
|
|
1319
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1320
|
+
|
|
1321
|
+
expect(harness.onTimeRangeSelect).toHaveBeenCalledTimes(1);
|
|
1322
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
const SECOND_PRESS_ENDINGS: Array<{
|
|
1326
|
+
name: string;
|
|
1327
|
+
end: (harness: Harness) => void;
|
|
1328
|
+
}> = [
|
|
1329
|
+
{
|
|
1330
|
+
name: "let go over the chart, its dblclick following",
|
|
1331
|
+
end: (harness: Harness): void => {
|
|
1332
|
+
release(harness, BAR_A, mainButton(X_A, 2));
|
|
1333
|
+
dblclick(harness);
|
|
1334
|
+
},
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
name: "let go over the chart, its dblclick lost",
|
|
1338
|
+
end: (harness: Harness): void => {
|
|
1339
|
+
release(harness, LANDED_BAR, mainButton(X_A, 2));
|
|
1340
|
+
},
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
name: "let go off the chart",
|
|
1344
|
+
end: (): void => {
|
|
1345
|
+
releaseOnThePage(2);
|
|
1346
|
+
},
|
|
1347
|
+
},
|
|
1348
|
+
];
|
|
1349
|
+
|
|
1350
|
+
for (const ending of SECOND_PRESS_ENDINGS) {
|
|
1351
|
+
test(`withdrawn during a double-click's second press, then that press ${ending.name}: nothing zooms in, and nothing zooms out`, () => {
|
|
1352
|
+
const harness: Harness = renderSelection(true);
|
|
1353
|
+
|
|
1354
|
+
clickAThenPressAgain(harness);
|
|
1355
|
+
endZoomAnotherWay(harness);
|
|
1356
|
+
ending.end(harness);
|
|
1357
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1358
|
+
|
|
1359
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1360
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/*
|
|
1365
|
+
* The second press already dropped the first click's zoom-in and kept
|
|
1366
|
+
* its band for the zoom-out to clear. Before commit b6ec899df4 the hook
|
|
1367
|
+
* cleared the band when onZoomOut went only if a click was still
|
|
1368
|
+
* waiting, so this one stayed - through the press's release and every
|
|
1369
|
+
* timer, until the next click on the chart: bar A lit, as if about to
|
|
1370
|
+
* open, on a chart that had left the zoom. Clearing it at once is safe:
|
|
1371
|
+
* with the zoom gone, a dblclick lost to that render has nothing to do.
|
|
1372
|
+
*/
|
|
1373
|
+
test("withdrawn during a double-click's second press, the band that press kept for the zoom-out goes at once", () => {
|
|
1374
|
+
const harness: Harness = renderSelection(true);
|
|
1375
|
+
|
|
1376
|
+
clickAThenPressAgain(harness);
|
|
1377
|
+
|
|
1378
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
1379
|
+
|
|
1380
|
+
endZoomAnotherWay(harness);
|
|
1381
|
+
|
|
1382
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
/*
|
|
1386
|
+
* A drag's band is the reader's selection in progress, not one kept for
|
|
1387
|
+
* a zoom-out: it stays, and the drag still selects when let go.
|
|
1388
|
+
*/
|
|
1389
|
+
test("withdrawn in the middle of a drag, the drag keeps its band and still selects when let go", () => {
|
|
1390
|
+
const harness: Harness = renderSelection(true);
|
|
1391
|
+
|
|
1392
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1393
|
+
move(harness, BAR_B, pointerAt(X_B));
|
|
1394
|
+
|
|
1395
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
1396
|
+
|
|
1397
|
+
endZoomAnotherWay(harness);
|
|
1398
|
+
|
|
1399
|
+
expect(band(harness)).toEqual([BAR_A, BAR_B]);
|
|
1400
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
1401
|
+
|
|
1402
|
+
move(harness, BAR_C, pointerAt(X_C));
|
|
1403
|
+
release(harness, BAR_C, mainButton(X_C, 1));
|
|
1404
|
+
|
|
1405
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1406
|
+
windowOf(BAR_A, BAR_C),
|
|
1407
|
+
]);
|
|
1408
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1409
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1410
|
+
});
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
describe("only the main button selects: a right-click opens a context menu", () => {
|
|
1414
|
+
for (const chart of CHARTS) {
|
|
1415
|
+
test(`on ${chart.name}, a right-button press on a bar and its release select nothing, zoom nowhere, and listen for nothing on the page`, () => {
|
|
1416
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
1417
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1418
|
+
const rendersBefore: number = harness.renderCount();
|
|
1419
|
+
|
|
1420
|
+
press(harness, BAR_A, otherButton(2, X_A, 1));
|
|
1421
|
+
|
|
1422
|
+
expect(mouseUpListeners(pageListeners.add.mock.calls)).toEqual([]);
|
|
1423
|
+
|
|
1424
|
+
release(harness, BAR_A, otherButton(2, X_A, 1));
|
|
1425
|
+
|
|
1426
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1427
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
1428
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1429
|
+
expect(harness.renderCount()).toBe(rendersBefore);
|
|
1430
|
+
|
|
1431
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1432
|
+
|
|
1433
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1434
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
test(`on ${chart.name}, a right-button double-click on a bar neither zooms in nor zooms out`, () => {
|
|
1438
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
1439
|
+
|
|
1440
|
+
press(harness, BAR_A, otherButton(2, X_A, 1));
|
|
1441
|
+
release(harness, BAR_A, otherButton(2, X_A, 1));
|
|
1442
|
+
press(harness, BAR_A, otherButton(2, X_A, 2));
|
|
1443
|
+
release(harness, BAR_A, otherButton(2, X_A, 2));
|
|
1444
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1445
|
+
|
|
1446
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1447
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1448
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
test(`on ${chart.name}, a middle-button drag across bars paints no band and selects nothing`, () => {
|
|
1452
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
1453
|
+
|
|
1454
|
+
press(harness, BAR_A, otherButton(1, X_A, 1));
|
|
1455
|
+
move(harness, BAR_B, pointerAt(X_B));
|
|
1456
|
+
move(harness, BAR_C, pointerAt(X_C));
|
|
1457
|
+
|
|
1458
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
1459
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1460
|
+
|
|
1461
|
+
release(harness, BAR_C, otherButton(1, X_C, 1));
|
|
1462
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1463
|
+
|
|
1464
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1465
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1466
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
/*
|
|
1472
|
+
* A press let go off the chart - dragged out past its top or bottom edge,
|
|
1473
|
+
* say - is heard only by the page. The page's release has no bar, but it
|
|
1474
|
+
* does say where the pointer is, and that decides click or drag as over
|
|
1475
|
+
* the chart: a press that stayed put is a click on the bar it pressed.
|
|
1476
|
+
*/
|
|
1477
|
+
describe("a press let go off the chart, where only the page hears it", () => {
|
|
1478
|
+
const STILL_RELEASES: Array<{ name: string; offsetPx: number }> = [
|
|
1479
|
+
{ name: "straight above or below where it went down", offsetPx: 0 },
|
|
1480
|
+
{
|
|
1481
|
+
name: `${HISTOGRAM_PRESS_STILLNESS_PX}px right of where it went down`,
|
|
1482
|
+
offsetPx: HISTOGRAM_PRESS_STILLNESS_PX,
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
name: `${HISTOGRAM_PRESS_STILLNESS_PX}px left of where it went down`,
|
|
1486
|
+
offsetPx: -HISTOGRAM_PRESS_STILLNESS_PX,
|
|
1487
|
+
},
|
|
1488
|
+
];
|
|
1489
|
+
|
|
1490
|
+
for (const stillRelease of STILL_RELEASES) {
|
|
1491
|
+
test(`on an unzoomed chart, one let go ${stillRelease.name} is a click on the bar it pressed, and zooms into it at once`, () => {
|
|
1492
|
+
const harness: Harness = renderSelection(false);
|
|
1493
|
+
|
|
1494
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1495
|
+
releaseOnThePage(1, X_A + stillRelease.offsetPx);
|
|
1496
|
+
|
|
1497
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1498
|
+
windowOf(BAR_A, BAR_A),
|
|
1499
|
+
]);
|
|
1500
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1501
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
1502
|
+
|
|
1503
|
+
// The press is over: the page's next release is nothing to it.
|
|
1504
|
+
releaseOnThePage(1, X_A + stillRelease.offsetPx);
|
|
1505
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1506
|
+
|
|
1507
|
+
expect(harness.onTimeRangeSelect).toHaveBeenCalledTimes(1);
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
test("on a zoomed chart, one let go straight above or below where it went down waits out the double-click window, its bar lit, then zooms into that bar", () => {
|
|
1512
|
+
const harness: Harness = renderSelection(true);
|
|
1513
|
+
|
|
1514
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1515
|
+
releaseOnThePage(1, X_A);
|
|
1516
|
+
|
|
1517
|
+
expect(band(harness)).toEqual([BAR_A, BAR_A]);
|
|
1518
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1519
|
+
|
|
1520
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
1521
|
+
|
|
1522
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1523
|
+
windowOf(BAR_A, BAR_A),
|
|
1524
|
+
]);
|
|
1525
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1526
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
for (const chart of CHARTS) {
|
|
1530
|
+
test(`on ${chart.name}, one let go far away after crossing bars is a drag to the last bar it crossed, as before`, () => {
|
|
1531
|
+
const harness: Harness = renderSelection(chart.isZoomed);
|
|
1532
|
+
|
|
1533
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1534
|
+
move(harness, BAR_B, pointerAt(X_B));
|
|
1535
|
+
move(harness, BAR_C, pointerAt(X_C));
|
|
1536
|
+
|
|
1537
|
+
expect(harness.result.current.isDragging).toBe(true);
|
|
1538
|
+
expect(band(harness)).toEqual([BAR_A, BAR_C]);
|
|
1539
|
+
|
|
1540
|
+
releaseOnThePage(1, X_C + 400);
|
|
1541
|
+
|
|
1542
|
+
expect(selections(harness.onTimeRangeSelect)).toEqual([
|
|
1543
|
+
windowOf(BAR_A, BAR_C),
|
|
1544
|
+
]);
|
|
1545
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1546
|
+
expect(harness.result.current.isDragging).toBe(false);
|
|
1547
|
+
|
|
1548
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1549
|
+
|
|
1550
|
+
expect(harness.onTimeRangeSelect).toHaveBeenCalledTimes(1);
|
|
1551
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
/*
|
|
1557
|
+
* A box standing in for the chart - its loader while a window loads, its
|
|
1558
|
+
* empty box when the window holds nothing - takes the double-click as the
|
|
1559
|
+
* chart does, through placeholderProps (commit b6ec899df4). Right after a
|
|
1560
|
+
* zoom the chart replaces its loader at any moment, a double-click in
|
|
1561
|
+
* progress included, and then the browser sends no dblclick: the node the
|
|
1562
|
+
* second press went down on is gone. So the box's own second press is the
|
|
1563
|
+
* zoom-out's press, and its release - over the chart that took the box's
|
|
1564
|
+
* place, whose handler hands the hook a bar of the new data, or anywhere
|
|
1565
|
+
* on the page - zooms out in the next task, as a second press on the
|
|
1566
|
+
* chart's own bars does.
|
|
1567
|
+
*/
|
|
1568
|
+
describe("a press on the box standing in for the chart (placeholderProps)", () => {
|
|
1569
|
+
const RELEASES_AFTER_THE_SWAP: Array<{
|
|
1570
|
+
name: string;
|
|
1571
|
+
release: (harness: Harness) => void;
|
|
1572
|
+
}> = [
|
|
1573
|
+
{
|
|
1574
|
+
name: "over the chart that took the box's place",
|
|
1575
|
+
release: (harness: Harness): void => {
|
|
1576
|
+
release(harness, BAR_B, mainButton(X_B, 2));
|
|
1577
|
+
},
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
name: "anywhere on the page",
|
|
1581
|
+
release: (): void => {
|
|
1582
|
+
releaseOnThePage(2, X_B);
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
];
|
|
1586
|
+
|
|
1587
|
+
for (const releaseAfterTheSwap of RELEASES_AFTER_THE_SWAP) {
|
|
1588
|
+
test(`on a zoomed chart, the second press of a double-click on the box, let go ${releaseAfterTheSwap.name}, zooms out once, in the next task, with no dblclick`, () => {
|
|
1589
|
+
const harness: Harness = renderSelection(true);
|
|
1590
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1591
|
+
|
|
1592
|
+
// The first click: nothing to it, and nothing listened for.
|
|
1593
|
+
pressPlaceholder(harness, mainButton(X_B, 1));
|
|
1594
|
+
|
|
1595
|
+
expect(mouseUpListeners(pageListeners.add.mock.calls)).toEqual([]);
|
|
1596
|
+
|
|
1597
|
+
// The second press listens on the page for its release.
|
|
1598
|
+
pressPlaceholder(harness, mainButton(X_B, 2));
|
|
1599
|
+
|
|
1600
|
+
expect(pageListenersLeft(pageListeners)).toHaveLength(1);
|
|
1601
|
+
|
|
1602
|
+
// The chart has replaced the box by the release.
|
|
1603
|
+
releaseAfterTheSwap.release(harness);
|
|
1604
|
+
|
|
1605
|
+
// Not yet: a dblclick the browser still sends would come first.
|
|
1606
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1607
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1608
|
+
|
|
1609
|
+
letTimePass(0);
|
|
1610
|
+
|
|
1611
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1612
|
+
|
|
1613
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1614
|
+
|
|
1615
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1616
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1617
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
test("on a zoomed chart, the same double-click with its dblclick delivered after all zooms out exactly once", () => {
|
|
1622
|
+
const harness: Harness = renderSelection(true);
|
|
1623
|
+
|
|
1624
|
+
pressPlaceholder(harness, mainButton(X_B, 1));
|
|
1625
|
+
pressPlaceholder(harness, mainButton(X_B, 2));
|
|
1626
|
+
release(harness, BAR_B, mainButton(X_B, 2));
|
|
1627
|
+
// The browser's dblclick, straight after the release's click.
|
|
1628
|
+
dblclick(harness);
|
|
1629
|
+
|
|
1630
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1631
|
+
|
|
1632
|
+
// The zoom-out the release left for the next task stood down.
|
|
1633
|
+
letTimePass(0);
|
|
1634
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1635
|
+
|
|
1636
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1637
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
test("on a zoomed chart, a double-click wholly on the box, its dblclick through placeholderProps, zooms out exactly once", () => {
|
|
1641
|
+
const harness: Harness = renderSelection(true);
|
|
1642
|
+
|
|
1643
|
+
pressPlaceholder(harness, mainButton(X_B, 1));
|
|
1644
|
+
releaseOnThePage(1, X_B);
|
|
1645
|
+
pressPlaceholder(harness, mainButton(X_B, 2));
|
|
1646
|
+
// The box is no chart: only the page hears its release.
|
|
1647
|
+
releaseOnThePage(2, X_B);
|
|
1648
|
+
act(() => {
|
|
1649
|
+
harness.result.current.placeholderProps.onDoubleClick();
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1653
|
+
|
|
1654
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1655
|
+
|
|
1656
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1657
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1658
|
+
});
|
|
1659
|
+
|
|
1660
|
+
/*
|
|
1661
|
+
* A click on a bar was waiting out the double-click window when the
|
|
1662
|
+
* chart went and the box came (a refetch that clears the bars first):
|
|
1663
|
+
* the box's second press drops it, as the chart's own would have.
|
|
1664
|
+
*/
|
|
1665
|
+
test("on a zoomed chart, a click on a bar still waiting to zoom in when the box took the chart's place: the box's second press drops it, and its release zooms out once", () => {
|
|
1666
|
+
const harness: Harness = renderSelection(true);
|
|
1667
|
+
|
|
1668
|
+
press(harness, BAR_A, mainButton(X_A, 1));
|
|
1669
|
+
release(harness, BAR_A, mainButton(X_A, 1));
|
|
1670
|
+
|
|
1671
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1672
|
+
|
|
1673
|
+
pressPlaceholder(harness, mainButton(X_A, 2));
|
|
1674
|
+
|
|
1675
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1676
|
+
|
|
1677
|
+
releaseOnThePage(2, X_A);
|
|
1678
|
+
letTimePass(0);
|
|
1679
|
+
|
|
1680
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1681
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1682
|
+
|
|
1683
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1684
|
+
|
|
1685
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1686
|
+
expect(harness.onZoomOut).toHaveBeenCalledTimes(1);
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
test("on an unzoomed chart, which offers no zoom-out, the same second press is nothing: it listens for nothing, and its release over the chart selects nothing", () => {
|
|
1690
|
+
const harness: Harness = renderSelection(false);
|
|
1691
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1692
|
+
|
|
1693
|
+
pressPlaceholder(harness, mainButton(X_B, 1));
|
|
1694
|
+
pressPlaceholder(harness, mainButton(X_B, 2));
|
|
1695
|
+
|
|
1696
|
+
expect(mouseUpListeners(pageListeners.add.mock.calls)).toEqual([]);
|
|
1697
|
+
|
|
1698
|
+
release(harness, BAR_B, mainButton(X_B, 2));
|
|
1699
|
+
releaseOnThePage(2, X_B);
|
|
1700
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1701
|
+
|
|
1702
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1703
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1704
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1705
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
test("on a zoomed chart, a press on the box counted 1 is nothing: it listens for nothing, and no release after it sets anything off", () => {
|
|
1709
|
+
const harness: Harness = renderSelection(true);
|
|
1710
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1711
|
+
|
|
1712
|
+
pressPlaceholder(harness, mainButton(X_B, 1));
|
|
1713
|
+
|
|
1714
|
+
expect(mouseUpListeners(pageListeners.add.mock.calls)).toEqual([]);
|
|
1715
|
+
|
|
1716
|
+
release(harness, BAR_B, mainButton(X_B, 1));
|
|
1717
|
+
releaseOnThePage(1, X_B);
|
|
1718
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1719
|
+
|
|
1720
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1721
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1722
|
+
expect(band(harness)).toEqual(NO_BAND);
|
|
1723
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
test("on a zoomed chart, a right-button second press on the box is nothing: it listens for nothing, and its release zooms nothing out", () => {
|
|
1727
|
+
const harness: Harness = renderSelection(true);
|
|
1728
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1729
|
+
|
|
1730
|
+
pressPlaceholder(harness, otherButton(2, X_B, 1));
|
|
1731
|
+
pressPlaceholder(harness, otherButton(2, X_B, 2));
|
|
1732
|
+
|
|
1733
|
+
expect(mouseUpListeners(pageListeners.add.mock.calls)).toEqual([]);
|
|
1734
|
+
|
|
1735
|
+
release(harness, BAR_B, otherButton(2, X_B, 2));
|
|
1736
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1737
|
+
|
|
1738
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1739
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1740
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
test("on a zoomed chart whose zoom ends another way during the box's second press, its release zooms nothing out and leaves nothing listening", () => {
|
|
1744
|
+
const harness: Harness = renderSelection(true);
|
|
1745
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1746
|
+
|
|
1747
|
+
pressPlaceholder(harness, mainButton(X_B, 1));
|
|
1748
|
+
pressPlaceholder(harness, mainButton(X_B, 2));
|
|
1749
|
+
endZoomAnotherWay(harness);
|
|
1750
|
+
releaseOnThePage(2, X_B);
|
|
1751
|
+
letTimePass(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
|
|
1752
|
+
|
|
1753
|
+
expect(harness.onZoomOut).not.toHaveBeenCalled();
|
|
1754
|
+
expect(harness.onTimeRangeSelect).not.toHaveBeenCalled();
|
|
1755
|
+
expect(pageListenersLeft(pageListeners)).toEqual([]);
|
|
1756
|
+
});
|
|
1757
|
+
});
|