gloomberb 0.10.0 → 0.10.2
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/package.json +8 -1
- package/src/api-client/auth.ts +1 -0
- package/src/api-client/index.test.ts +359 -108
- package/src/api-client/index.ts +24 -13
- package/src/api-client/socket.ts +70 -35
- package/src/api-client/types.ts +1 -1
- package/src/app/language-observer.test.tsx +82 -0
- package/src/app/language-observer.tsx +13 -0
- package/src/app/pane-runtime/index.tsx +2 -2
- package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
- package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
- package/src/app/pane-runtime/plugin-bindings.ts +2 -2
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
- package/src/app/runtime/broker-import.ts +2 -2
- package/src/app/runtime/plugin-bindings.ts +2 -2
- package/src/app/runtime/startup.ts +2 -2
- package/src/app.tsx +16 -7
- package/src/brokers/account-cache.ts +6 -6
- package/src/brokers/broker-ticker-sync.ts +3 -3
- package/src/brokers/remote-broker-adapter.ts +2 -2
- package/src/brokers/sync-broker-instance.ts +5 -6
- package/src/cli/commands/api.ts +3 -6
- package/src/cli/commands/automation.ts +7 -15
- package/src/cli/commands/market.ts +26 -61
- package/src/cli/commands/overview.ts +34 -45
- package/src/cli/commands/search.ts +25 -27
- package/src/cli/commands/system.ts +27 -57
- package/src/cli/commands/ticker.ts +62 -63
- package/src/cli/context.test.ts +63 -0
- package/src/cli/context.ts +15 -1
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/data.ts +2 -19
- package/src/cli/pane-functions/index.ts +26 -25
- package/src/cli/pane-functions/report.ts +1 -1
- package/src/cli/pane-functions/screenshot.ts +3 -3
- package/src/cli/scoped-context.ts +41 -0
- package/src/components/chart/composite/composite-chart.test.tsx +615 -18
- package/src/components/chart/composite/composite-chart.tsx +179 -79
- package/src/components/chart/composite/format.test.ts +20 -7
- package/src/components/chart/composite/index.ts +0 -21
- package/src/components/chart/composite/interactions.test.ts +90 -3
- package/src/components/chart/composite/interactions.ts +189 -24
- package/src/components/chart/composite/price-series.ts +0 -2
- package/src/components/chart/composite/rasterizer.ts +10 -9
- package/src/components/chart/composite/renderers.test.ts +108 -2
- package/src/components/chart/composite/scene.test.ts +36 -0
- package/src/components/chart/composite/scene.ts +5 -2
- package/src/components/chart/composite/text-renderer.ts +37 -25
- package/src/components/chart/composite/time-axis.test.ts +243 -0
- package/src/components/chart/composite/time-axis.ts +608 -0
- package/src/components/chart/composite/types.ts +12 -1
- package/src/components/chart/core/data.ts +2 -159
- package/src/components/chart/core/date-window.ts +6 -225
- package/src/components/chart/core/palette.ts +1 -12
- package/src/components/chart/core/pointer.ts +0 -142
- package/src/components/chart/core/types.ts +1 -58
- package/src/components/chart/cursor-motion.ts +2 -4
- package/src/components/chart/indicators/types.ts +0 -9
- package/src/components/chart/market-session.ts +0 -11
- package/src/components/chart/native/kitty/protocol.ts +1 -1
- package/src/components/chart/native/raster/layout.ts +3 -10
- package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
- package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
- package/src/components/chart/static/chart/surface.tsx +1 -2
- package/src/components/command-bar/action-runtime.ts +2 -2
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +2 -2
- package/src/components/command-bar/panel/index.tsx +11 -9
- package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
- package/src/components/command-bar/surface/index.tsx +2 -2
- package/src/components/command-bar/workflow/body.tsx +3 -3
- package/src/components/command-bar/workflow/collection-actions.ts +2 -2
- package/src/components/command-bar/workflow/coordinator.ts +2 -2
- package/src/components/command-bar/workflow/field-row.tsx +25 -11
- package/src/components/command-bar/workflow/tickers.ts +2 -2
- package/src/components/feed-data-table/stack-view.test.tsx +28 -36
- package/src/components/feed-data-table/stack-view.tsx +3 -1
- package/src/components/layout/detached-pane-shell.tsx +6 -6
- package/src/components/layout/header.tsx +13 -9
- package/src/components/layout/pane/content.test.tsx +42 -3
- package/src/components/layout/pane/content.tsx +3 -1
- package/src/components/layout/pane/footer/index.test.tsx +45 -2
- package/src/components/layout/pane/footer/registration.tsx +3 -1
- package/src/components/layout/shell/index.tsx +1 -2
- package/src/components/layout/shell/native/surfaces.ts +1 -1
- package/src/components/layout/shell/native/window-state.ts +1 -1
- package/src/components/layout/status-bar.tsx +12 -8
- package/src/components/layout/window-controls.tsx +8 -6
- package/src/components/onboarding/onboarding-wizard.tsx +3 -1
- package/src/components/pane-settings-dialog/desktop.tsx +0 -2
- package/src/components/pane-settings-dialog.tsx +0 -2
- package/src/components/pane-template-wizard.tsx +10 -9
- package/src/components/price-performance.tsx +2 -113
- package/src/components/price-sparkline/model.ts +15 -17
- package/src/components/price-sparkline/view.tsx +8 -7
- package/src/components/speedometer-gauge/desktop.tsx +2 -1
- package/src/components/speedometer-gauge/index.tsx +0 -2
- package/src/components/speedometer-gauge/terminal.tsx +24 -5
- package/src/components/ticker/badge/index.tsx +1 -18
- package/src/components/ui/data-table/opentui/index.tsx +3 -3
- package/src/components/ui/frame.tsx +1 -2
- package/src/components/ui/page-stack-view.test.tsx +126 -0
- package/src/components/ui/page-stack-view.tsx +24 -12
- package/src/components/ui/tabs.tsx +7 -3
- package/src/components/ui/ui.test.tsx +23 -2
- package/src/components/use-external-link-footer.ts +4 -2
- package/src/core/app-service-ports.ts +46 -0
- package/src/core/app-services.test.ts +66 -0
- package/src/core/app-services.ts +17 -15
- package/src/data/config/chart-settings.ts +3 -10
- package/src/data/config/store/index.test.ts +99 -2
- package/src/data/config/store/migrations.ts +278 -0
- package/src/data/config/store/normalize.ts +44 -186
- package/src/data/resource-store.ts +73 -12
- package/src/data/sqlite/cache.test.ts +104 -15
- package/src/i18n/index.test.ts +17 -0
- package/src/i18n/index.ts +23 -6
- package/src/i18n/ja.ts +13 -0
- package/src/i18n/ko.ts +13 -0
- package/src/i18n/react.ts +6 -0
- package/src/i18n/zh-cn.ts +13 -0
- package/src/i18n/zh-tw.ts +13 -0
- package/src/market-data/coordinator/auxiliary.ts +7 -3
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/index.test.ts +26 -0
- package/src/market-data/coordinator/index.ts +11 -5
- package/src/market-data/coordinator/quotes.ts +4 -40
- package/src/market-data/coordinator/subscriptions.test.ts +89 -1
- package/src/market-data/hooks.tsx +47 -1
- package/src/market-data/market/status.ts +6 -6
- package/src/market-data/performance.ts +1 -1
- package/src/market-data/quote-subscription-target.test.ts +37 -0
- package/src/market-data/quote-subscription-target.ts +60 -0
- package/src/market-data/quotes/freshness.ts +1 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/request-types.ts +3 -3
- package/src/news/aggregator.test.ts +97 -1
- package/src/news/aggregator.ts +100 -57
- package/src/news/hooks.test.tsx +64 -0
- package/src/news/hooks.ts +18 -25
- package/src/plugins/builtin/account-management/footer.ts +4 -2
- package/src/plugins/builtin/account-management/pane.tsx +9 -4
- package/src/plugins/builtin/ai/agent-history.ts +11 -7
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
- package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
- package/src/plugins/builtin/ai/index.test.ts +2 -2
- package/src/plugins/builtin/ai/index.tsx +10 -11
- package/src/plugins/builtin/ai/pane-settings.ts +1 -1
- package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +0 -6
- package/src/plugins/builtin/ai/screener/footer.ts +4 -1
- package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
- package/src/plugins/builtin/ai/workspace/model.ts +2 -27
- package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
- package/src/plugins/builtin/analytics/metrics.ts +1 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
- package/src/plugins/builtin/broker-manager/index.tsx +4 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
- package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
- package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
- package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
- package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
- package/src/plugins/builtin/chart-composer/index.tsx +0 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
- package/src/plugins/builtin/chart-composer/presets.ts +1 -19
- package/src/plugins/builtin/chart-composer/settings.ts +1 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
- package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
- package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
- package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
- package/src/plugins/builtin/chat/controller/session.ts +2 -0
- package/src/plugins/builtin/chat/controller/state.ts +7 -0
- package/src/plugins/builtin/chat/controller.test.ts +73 -20
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
- package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
- package/src/plugins/builtin/correlation/compute.ts +4 -10
- package/src/plugins/builtin/econ/index.tsx +17 -19
- package/src/plugins/builtin/holders/types.ts +0 -1
- package/src/plugins/builtin/insider/index.tsx +6 -31
- package/src/plugins/builtin/news/index.tsx +5 -7
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
- package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
- package/src/plugins/builtin/news/wire/read-state.ts +18 -28
- package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
- package/src/plugins/builtin/options/footer.test.ts +115 -0
- package/src/plugins/builtin/options/footer.ts +110 -0
- package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
- package/src/plugins/builtin/options/live-quotes.ts +147 -0
- package/src/plugins/builtin/options/view.test.tsx +71 -1
- package/src/plugins/builtin/options/view.tsx +39 -24
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
- package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
- package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
- package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
- package/src/plugins/builtin/sec/filing-content.ts +94 -0
- package/src/plugins/builtin/sec/index.tsx +21 -104
- package/src/plugins/builtin/sectors/sector-model.ts +1 -7
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
- package/src/plugins/builtin/shared/pane-footer.ts +82 -0
- package/src/plugins/builtin/shared/read-state.ts +64 -3
- package/src/plugins/builtin/substack/read-state.ts +16 -41
- package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
- package/src/plugins/builtin/yield-curve/index.tsx +11 -9
- package/src/plugins/ibkr/dialogs.tsx +3 -3
- package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
- package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
- package/src/plugins/ibkr/gateway/history.ts +2 -2
- package/src/plugins/ibkr/gateway/selection.ts +2 -2
- package/src/plugins/ibkr/gateway/service/index.ts +2 -2
- package/src/plugins/ibkr/gateway/service/native.ts +2 -2
- package/src/plugins/plugin-persistence.ts +3 -4
- package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
- package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
- package/src/plugins/prediction-markets/detail/book.tsx +2 -2
- package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
- package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
- package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
- package/src/plugins/registry/context.ts +3 -4
- package/src/plugins/registry/index.ts +5 -6
- package/src/react/input.test.ts +126 -3
- package/src/react/input.ts +120 -1
- package/src/remote/app-host.tsx +2 -13
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
- package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
- package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
- package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
- package/src/renderers/electrobun/bun/index.ts +123 -83
- package/src/renderers/electrobun/shared/protocol.ts +146 -7
- package/src/renderers/electrobun/view/app-services.ts +5 -8
- package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
- package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
- package/src/renderers/electrobun/view/config-host.ts +1 -1
- package/src/renderers/electrobun/view/context-menu.ts +1 -17
- package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
- package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
- package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
- package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
- package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
- package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
- package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
- package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
- package/src/renderers/electrobun/view/host/box.tsx +0 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
- package/src/renderers/electrobun/view/host/input.tsx +0 -1
- package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
- package/src/renderers/electrobun/view/host/native.ts +0 -1
- package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
- package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
- package/src/renderers/electrobun/view/host/text.tsx +0 -1
- package/src/renderers/electrobun/view/http-fetch.ts +3 -11
- package/src/renderers/electrobun/view/input-host.tsx +10 -53
- package/src/renderers/electrobun/view/key-event.ts +0 -1
- package/src/renderers/electrobun/view/main.tsx +3 -3
- package/src/renderers/electrobun/view/native-renderer.ts +0 -1
- package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
- package/src/renderers/electrobun/view/native-stubs/chart/surface-manager.ts +11 -0
- package/src/renderers/electrobun/view/native-stubs/native-stubs.test.ts +47 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
- package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
- package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
- package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
- package/src/renderers/electrobun/view/toast-host.tsx +0 -1
- package/src/renderers/electrobun/view/ui-host.tsx +1 -2
- package/src/renderers/electrobun/view/update-host.ts +1 -1
- package/src/renderers/opentui/chart-surface.tsx +2 -1
- package/src/renderers/opentui/dialog-host.tsx +20 -4
- package/src/renderers/opentui/image/loader.ts +12 -2
- package/src/renderers/opentui/input-host.tsx +7 -49
- package/src/renderers/opentui/start.tsx +2 -0
- package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
- package/src/sources/gloomberb-cloud/index.test.ts +173 -0
- package/src/sources/gloomberb-cloud/index.ts +42 -20
- package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
- package/src/sources/provider-router/cache.ts +1 -1
- package/src/sources/provider-router/document-routes.ts +10 -5
- package/src/sources/provider-router/financials.test.ts +14 -0
- package/src/sources/provider-router/financials.ts +6 -1
- package/src/sources/provider-router/history.test.ts +163 -1
- package/src/sources/provider-router/history.ts +278 -347
- package/src/sources/provider-router/index.ts +2 -2
- package/src/sources/provider-router/routing.test.ts +39 -0
- package/src/sources/provider-router/routing.ts +24 -11
- package/src/sources/provider-router/supplemental.ts +37 -17
- package/src/sources/sec-edgar/content.ts +2 -12
- package/src/sources/sec-edgar.ts +2 -13
- package/src/sources/yahoo-finance/history.ts +2 -2
- package/src/sources/yahoo-finance/options.ts +5 -0
- package/src/sources/yahoo-finance.ts +2 -2
- package/src/state/app/bootstrap.ts +2 -2
- package/src/state/app/context/index.tsx +2 -2
- package/src/state/hooks/quote-streaming.ts +66 -1
- package/src/state/hooks/session-snapshot.ts +2 -2
- package/src/state/selectors.ts +0 -9
- package/src/sync/controller.test.ts +98 -0
- package/src/sync/controller.ts +29 -5
- package/src/sync/react.ts +2 -2
- package/src/sync/types.ts +2 -2
- package/src/theme/colors.test.ts +10 -0
- package/src/theme/colors.ts +75 -78
- package/src/theme/theme-context.test.tsx +67 -0
- package/src/theme/theme-context.tsx +9 -5
- package/src/tickers/open-target.ts +2 -2
- package/src/tickers/search/upsert.ts +2 -2
- package/src/time-series/alignment.ts +0 -7
- package/src/time-series/chart-data.ts +145 -0
- package/src/time-series/date-window.ts +44 -0
- package/src/time-series/history-window.ts +21 -0
- package/src/time-series/hooks.test.ts +59 -0
- package/src/time-series/hooks.ts +33 -12
- package/src/time-series/market-resolution.ts +2 -2
- package/src/time-series/market.ts +1 -1
- package/src/time-series/range.ts +9 -0
- package/src/{components/chart/core → time-series}/resolution.ts +3 -102
- package/src/time-series/resolve.test.ts +203 -1
- package/src/time-series/resolve.ts +96 -18
- package/src/time-series/spec-studies.test.ts +15 -0
- package/src/time-series/spec.ts +2 -2
- package/src/time-series/studies.ts +5 -2
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.test.tsx +358 -0
- package/src/time-series/use-chart-resolution.ts +46 -4
- package/src/types/broker.ts +2 -2
- package/src/types/data-provider.ts +3 -3
- package/src/types/financials.ts +12 -0
- package/src/types/plugin.ts +2 -2
- package/src/ui/context-menu.tsx +3 -2
- package/src/ui/dialog.tsx +2 -2
- package/src/utils/html-entities.ts +3 -5
- package/src/utils/main-thread-monitor.ts +1 -1
- package/src/utils/price-history.ts +5 -5
- package/src/version.ts +3 -1
- package/src/components/chart/core/viewport.test.ts +0 -8
- package/src/components/chart/core/viewport.ts +0 -24
- package/src/time-series/index.ts +0 -14
- /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createElement,
|
|
5
5
|
forwardRef,
|
|
6
6
|
useMemo,
|
|
7
|
+
useState,
|
|
7
8
|
type ForwardedRef,
|
|
8
9
|
type ReactNode,
|
|
9
10
|
} from "react";
|
|
@@ -24,6 +25,10 @@ import {
|
|
|
24
25
|
type KeyEventLike,
|
|
25
26
|
} from "../../../react/input";
|
|
26
27
|
import { CompositeChart } from "./composite-chart";
|
|
28
|
+
import {
|
|
29
|
+
resolveCompositeMinimumSpanMs,
|
|
30
|
+
zoomCompositeViewport,
|
|
31
|
+
} from "./interactions";
|
|
27
32
|
|
|
28
33
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
29
34
|
let chartShortcut: ((event: KeyEventLike) => void) | null = null;
|
|
@@ -154,6 +159,42 @@ function series(id: string, panelId: string, axis: ResolvedSeries["axis"], unit:
|
|
|
154
159
|
};
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
function twoSessionCandles(): ResolvedSeries {
|
|
163
|
+
const points = [
|
|
164
|
+
...Array.from({ length: 7 }, (_, index) => (
|
|
165
|
+
Date.UTC(2025, 0, 2, 17, index * 30)
|
|
166
|
+
)),
|
|
167
|
+
...Array.from({ length: 8 }, (_, index) => (
|
|
168
|
+
Date.UTC(2025, 0, 3, 13, 30 + index * 30)
|
|
169
|
+
)),
|
|
170
|
+
].map((timestamp, index) => {
|
|
171
|
+
const date = new Date(timestamp);
|
|
172
|
+
const value = 100 + index * 0.1;
|
|
173
|
+
return {
|
|
174
|
+
date,
|
|
175
|
+
observedAt: date,
|
|
176
|
+
value,
|
|
177
|
+
open: value,
|
|
178
|
+
high: value + 1,
|
|
179
|
+
low: value - 1,
|
|
180
|
+
close: value,
|
|
181
|
+
volume: 100,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
...series("price", "main", "left", "USD", []),
|
|
186
|
+
nativeFrequency: "intraday",
|
|
187
|
+
dataShape: "ohlc",
|
|
188
|
+
style: "candles",
|
|
189
|
+
timeBasis: {
|
|
190
|
+
kind: "market",
|
|
191
|
+
timeZone: "America/New_York",
|
|
192
|
+
cadenceMs: 30 * 60_000,
|
|
193
|
+
},
|
|
194
|
+
points,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
157
198
|
describe("CompositeChart", () => {
|
|
158
199
|
test("lays out mixed panels with one shared legend and time axis", async () => {
|
|
159
200
|
testSetup = await testRender(
|
|
@@ -180,8 +221,8 @@ describe("CompositeChart", () => {
|
|
|
180
221
|
expect(frame).toContain("ACME Price");
|
|
181
222
|
expect(frame).toContain("OTHER Revenue");
|
|
182
223
|
expect(frame).toContain("$103");
|
|
183
|
-
expect(frame.match(/
|
|
184
|
-
expect(frame.match(/
|
|
224
|
+
expect(frame.match(/Jan 1/g)).toHaveLength(1);
|
|
225
|
+
expect(frame.match(/Jan 3/g)).toHaveLength(1);
|
|
185
226
|
});
|
|
186
227
|
|
|
187
228
|
test("keeps legend items compact and anchors an accessory to the right edge", async () => {
|
|
@@ -244,6 +285,42 @@ describe("CompositeChart", () => {
|
|
|
244
285
|
expect(testSetup.captureCharFrame()).toContain("OTHER Revenue");
|
|
245
286
|
});
|
|
246
287
|
|
|
288
|
+
test("uses buffered market anchors so closed sessions do not create chart holes", async () => {
|
|
289
|
+
const candles = twoSessionCandles();
|
|
290
|
+
const currentSession = {
|
|
291
|
+
...candles,
|
|
292
|
+
points: candles.points.filter((point) => point.date.getUTCDate() === 3),
|
|
293
|
+
};
|
|
294
|
+
testSetup = await testRender(
|
|
295
|
+
<CompositeChart
|
|
296
|
+
width={72}
|
|
297
|
+
height={12}
|
|
298
|
+
series={[candles]}
|
|
299
|
+
legendSeries={[currentSession]}
|
|
300
|
+
panels={[{ id: "main" }]}
|
|
301
|
+
viewport={{
|
|
302
|
+
start: new Date("2025-01-02T17:00:00.000Z"),
|
|
303
|
+
end: new Date("2025-01-03T17:00:00.000Z"),
|
|
304
|
+
}}
|
|
305
|
+
showLegend={false}
|
|
306
|
+
/>,
|
|
307
|
+
{ width: 74, height: 14 },
|
|
308
|
+
);
|
|
309
|
+
await act(async () => testSetup!.renderOnce());
|
|
310
|
+
|
|
311
|
+
const bodyColumns = [...new Set(testSetup.captureCharFrame()
|
|
312
|
+
.split("\n")
|
|
313
|
+
.flatMap((line) => [...line].flatMap((cell, index) => cell === "█" ? [index] : [])))]
|
|
314
|
+
.sort((left, right) => left - right);
|
|
315
|
+
const largestGap = bodyColumns.slice(1).reduce(
|
|
316
|
+
(largest, column, index) => Math.max(largest, column - bodyColumns[index]!),
|
|
317
|
+
0,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(bodyColumns.length).toBeGreaterThan(10);
|
|
321
|
+
expect(largestGap).toBeLessThan(10);
|
|
322
|
+
});
|
|
323
|
+
|
|
247
324
|
test("shows an explicit legend toggle action on hover", async () => {
|
|
248
325
|
const toggled: string[] = [];
|
|
249
326
|
testSetup = await testRender(
|
|
@@ -443,6 +520,7 @@ describe("CompositeChart", () => {
|
|
|
443
520
|
|
|
444
521
|
test("zooms with plus and resets the interaction viewport with zero", async () => {
|
|
445
522
|
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
523
|
+
const viewportInteractions: string[] = [];
|
|
446
524
|
testSetup = await testRender(
|
|
447
525
|
<InputHostProvider host={chartInputHost}>
|
|
448
526
|
<CompositeChart
|
|
@@ -455,20 +533,20 @@ describe("CompositeChart", () => {
|
|
|
455
533
|
start: new Date("2025-01-01T00:00:00.000Z"),
|
|
456
534
|
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
457
535
|
}}
|
|
458
|
-
onViewportChange={(next) =>
|
|
459
|
-
|
|
460
|
-
|
|
536
|
+
onViewportChange={(next, interaction) => {
|
|
537
|
+
viewportInteractions.push(interaction);
|
|
538
|
+
viewportChanges.push(next
|
|
539
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
540
|
+
: null);
|
|
541
|
+
}}
|
|
461
542
|
/>
|
|
462
543
|
</InputHostProvider>,
|
|
463
544
|
{ width: 62, height: 14 },
|
|
464
545
|
);
|
|
465
546
|
|
|
466
547
|
await act(async () => testSetup!.renderOnce());
|
|
467
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
468
|
-
expect(viewportChanges).toEqual([
|
|
469
|
-
start: "2025-01-01T00:00:00.000Z",
|
|
470
|
-
end: "2025-01-09T00:00:00.000Z",
|
|
471
|
-
}]);
|
|
548
|
+
expect(testSetup.captureCharFrame()).toContain("Jan 1");
|
|
549
|
+
expect(viewportChanges).toEqual([]);
|
|
472
550
|
|
|
473
551
|
const zoomIn = keyEvent("=");
|
|
474
552
|
zoomIn.sequence = "+";
|
|
@@ -477,16 +555,322 @@ describe("CompositeChart", () => {
|
|
|
477
555
|
await act(async () => testSetup!.renderOnce());
|
|
478
556
|
|
|
479
557
|
expect(zoomIn.defaultPrevented).toBe(true);
|
|
480
|
-
expect(testSetup.captureCharFrame()).not.toContain("
|
|
558
|
+
expect(testSetup.captureCharFrame()).not.toContain("Jan 1");
|
|
481
559
|
expect(viewportChanges.at(-1)?.start).not.toBe("2025-01-01T00:00:00.000Z");
|
|
560
|
+
expect(viewportInteractions.at(-1)).toBe("zoom");
|
|
482
561
|
|
|
483
562
|
await act(async () => chartShortcut?.(keyEvent("0")));
|
|
484
563
|
await act(async () => testSetup!.renderOnce());
|
|
485
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
486
|
-
expect(viewportChanges.at(-1)).
|
|
564
|
+
expect(testSetup.captureCharFrame()).toContain("Jan 1");
|
|
565
|
+
expect(viewportChanges.at(-1)).toBeNull();
|
|
566
|
+
expect(viewportInteractions.at(-1)).toBe("reset");
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("clears the interaction when zoom returns to the authored viewport", async () => {
|
|
570
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
571
|
+
testSetup = await testRender(
|
|
572
|
+
<InputHostProvider host={chartInputHost}>
|
|
573
|
+
<CompositeChart
|
|
574
|
+
width={60}
|
|
575
|
+
height={12}
|
|
576
|
+
focused
|
|
577
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
578
|
+
panels={[{ id: "main" }]}
|
|
579
|
+
viewport={{
|
|
580
|
+
start: new Date("2025-01-01T00:00:00.000Z"),
|
|
581
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
582
|
+
}}
|
|
583
|
+
onViewportChange={(next) => viewportChanges.push(next
|
|
584
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
585
|
+
: null)}
|
|
586
|
+
/>
|
|
587
|
+
</InputHostProvider>,
|
|
588
|
+
{ width: 62, height: 14 },
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
await act(async () => testSetup!.renderOnce());
|
|
592
|
+
const zoomIn = keyEvent("=");
|
|
593
|
+
zoomIn.sequence = "+";
|
|
594
|
+
zoomIn.shift = true;
|
|
595
|
+
await act(async () => chartShortcut?.(zoomIn));
|
|
596
|
+
await act(async () => testSetup!.renderOnce());
|
|
597
|
+
expect(viewportChanges.at(-1)).not.toBeNull();
|
|
598
|
+
|
|
599
|
+
await act(async () => chartShortcut?.(keyEvent("-")));
|
|
600
|
+
await act(async () => testSetup!.renderOnce());
|
|
601
|
+
|
|
602
|
+
expect(viewportChanges.at(-1)).toBeNull();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("preserves a zoomed viewport when adaptive data refreshes its buffer", async () => {
|
|
606
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
607
|
+
let replacePoints: ((points: TimeSeriesPoint[]) => void) | null = null;
|
|
608
|
+
const initialPoints = series(
|
|
609
|
+
"price",
|
|
610
|
+
"main",
|
|
611
|
+
"left",
|
|
612
|
+
"USD",
|
|
613
|
+
[100, 101, 102, 103, 104, 105, 106, 107, 108],
|
|
614
|
+
).points;
|
|
615
|
+
function Harness() {
|
|
616
|
+
const [points, setPoints] = useState(initialPoints);
|
|
617
|
+
replacePoints = setPoints;
|
|
618
|
+
return (
|
|
619
|
+
<InputHostProvider host={chartInputHost}>
|
|
620
|
+
<CompositeChart
|
|
621
|
+
width={60}
|
|
622
|
+
height={12}
|
|
623
|
+
focused
|
|
624
|
+
series={[{
|
|
625
|
+
...series("price", "main", "left", "USD", []),
|
|
626
|
+
points,
|
|
627
|
+
}]}
|
|
628
|
+
panels={[{ id: "main" }]}
|
|
629
|
+
viewport={{
|
|
630
|
+
start: new Date("2025-01-01T00:00:00.000Z"),
|
|
631
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
632
|
+
}}
|
|
633
|
+
onViewportChange={(next) => viewportChanges.push(next
|
|
634
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
635
|
+
: null)}
|
|
636
|
+
/>
|
|
637
|
+
</InputHostProvider>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
testSetup = await testRender(
|
|
642
|
+
<Harness />,
|
|
643
|
+
{ width: 62, height: 14 },
|
|
644
|
+
);
|
|
645
|
+
await act(async () => testSetup!.renderOnce());
|
|
646
|
+
|
|
647
|
+
for (let index = 0; index < 2; index += 1) {
|
|
648
|
+
const zoomIn = keyEvent("=");
|
|
649
|
+
zoomIn.sequence = "+";
|
|
650
|
+
zoomIn.shift = true;
|
|
651
|
+
await act(async () => chartShortcut?.(zoomIn));
|
|
652
|
+
await act(async () => testSetup!.renderOnce());
|
|
653
|
+
}
|
|
654
|
+
const zoomedViewport = viewportChanges.at(-1);
|
|
655
|
+
expect(zoomedViewport?.start).not.toBe("2025-01-01T00:00:00.000Z");
|
|
656
|
+
const changeCount = viewportChanges.length;
|
|
657
|
+
|
|
658
|
+
await act(async () => replacePoints?.(initialPoints.slice(2)));
|
|
659
|
+
await act(async () => {
|
|
660
|
+
await testSetup!.renderOnce();
|
|
661
|
+
await testSetup!.renderOnce();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
expect(viewportChanges).toHaveLength(changeCount);
|
|
665
|
+
expect(viewportChanges.at(-1)).toEqual(zoomedViewport);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test("keeps a panned viewport when backfill moves beyond the authored range", async () => {
|
|
669
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
670
|
+
const viewportInteractions: string[] = [];
|
|
671
|
+
let replacePoints: ((points: TimeSeriesPoint[]) => void) | null = null;
|
|
672
|
+
const initialPoints = series(
|
|
673
|
+
"price",
|
|
674
|
+
"main",
|
|
675
|
+
"left",
|
|
676
|
+
"USD",
|
|
677
|
+
[100, 101, 102, 103, 104, 105, 106, 107, 108],
|
|
678
|
+
).points;
|
|
679
|
+
const olderPoints = Array.from({ length: 11 }, (_, index) => {
|
|
680
|
+
const date = new Date(
|
|
681
|
+
Date.UTC(2024, 11, 25 + index) - (index === 10 ? 60_000 : 0),
|
|
682
|
+
);
|
|
683
|
+
return { date, observedAt: date, value: 90 + index };
|
|
684
|
+
});
|
|
685
|
+
function Harness() {
|
|
686
|
+
const [points, setPoints] = useState(initialPoints);
|
|
687
|
+
replacePoints = setPoints;
|
|
688
|
+
return (
|
|
689
|
+
<InputHostProvider host={chartInputHost}>
|
|
690
|
+
<CompositeChart
|
|
691
|
+
width={60}
|
|
692
|
+
height={12}
|
|
693
|
+
focused
|
|
694
|
+
series={[{
|
|
695
|
+
...series("price", "main", "left", "USD", []),
|
|
696
|
+
points,
|
|
697
|
+
}]}
|
|
698
|
+
panels={[{ id: "main" }]}
|
|
699
|
+
viewport={{
|
|
700
|
+
start: new Date("2025-01-06T00:00:00.000Z"),
|
|
701
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
702
|
+
}}
|
|
703
|
+
onViewportChange={(next, interaction) => {
|
|
704
|
+
viewportInteractions.push(interaction);
|
|
705
|
+
viewportChanges.push(next
|
|
706
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
707
|
+
: null);
|
|
708
|
+
}}
|
|
709
|
+
/>
|
|
710
|
+
</InputHostProvider>
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
testSetup = await testRender(<Harness />, { width: 62, height: 14 });
|
|
715
|
+
await act(async () => testSetup!.renderOnce());
|
|
716
|
+
for (let index = 0; index < 120; index += 1) {
|
|
717
|
+
const panOlder = keyEvent("left");
|
|
718
|
+
panOlder.shift = true;
|
|
719
|
+
await act(async () => chartShortcut?.(panOlder));
|
|
720
|
+
await act(async () => testSetup!.renderOnce());
|
|
721
|
+
}
|
|
722
|
+
const pannedViewport = viewportChanges.at(-1);
|
|
723
|
+
expect(pannedViewport).toEqual({
|
|
487
724
|
start: "2025-01-01T00:00:00.000Z",
|
|
488
|
-
end: "2025-01-
|
|
725
|
+
end: "2025-01-04T00:00:00.000Z",
|
|
489
726
|
});
|
|
727
|
+
|
|
728
|
+
await act(async () => replacePoints?.(olderPoints));
|
|
729
|
+
await act(async () => {
|
|
730
|
+
await testSetup!.renderOnce();
|
|
731
|
+
await testSetup!.renderOnce();
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
expect(viewportChanges.at(-1)).toEqual({
|
|
735
|
+
start: "2024-12-31T23:59:00.000Z",
|
|
736
|
+
end: "2025-01-03T23:59:00.000Z",
|
|
737
|
+
});
|
|
738
|
+
expect(viewportInteractions.at(-1)).toBe("sync");
|
|
739
|
+
expect(testSetup.captureCharFrame()).not.toContain("No chart data");
|
|
740
|
+
expect(testSetup.captureCharFrame()).not.toContain("Jan 9");
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test("keeps the latest pan through delayed adaptive viewport and series echoes", async () => {
|
|
744
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
745
|
+
let publishViewport: ((
|
|
746
|
+
next: { start: string; end: string },
|
|
747
|
+
showSecondary?: boolean,
|
|
748
|
+
) => void) | null = null;
|
|
749
|
+
let publishExternalViewport: (() => void) | null = null;
|
|
750
|
+
function Harness() {
|
|
751
|
+
const [viewport, setViewport] = useState({
|
|
752
|
+
start: new Date("2025-01-05T00:00:00.000Z"),
|
|
753
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
754
|
+
});
|
|
755
|
+
const [showSecondary, setShowSecondary] = useState(false);
|
|
756
|
+
const [viewportResetKey, setViewportResetKey] = useState("price:1D:auto");
|
|
757
|
+
publishViewport = (next, nextShowSecondary = showSecondary) => {
|
|
758
|
+
setViewport({
|
|
759
|
+
start: new Date(next.start),
|
|
760
|
+
end: new Date(next.end),
|
|
761
|
+
});
|
|
762
|
+
setShowSecondary(nextShowSecondary);
|
|
763
|
+
};
|
|
764
|
+
publishExternalViewport = () => {
|
|
765
|
+
setViewport({
|
|
766
|
+
start: new Date("2025-01-01T00:00:00.000Z"),
|
|
767
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
768
|
+
});
|
|
769
|
+
setViewportResetKey("price:1W:auto");
|
|
770
|
+
};
|
|
771
|
+
return (
|
|
772
|
+
<CompositeChart
|
|
773
|
+
width={60}
|
|
774
|
+
height={12}
|
|
775
|
+
interactive
|
|
776
|
+
series={[
|
|
777
|
+
series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108]),
|
|
778
|
+
series("secondary", "main", "left", "USD", showSecondary ? [90, 91, 92] : []),
|
|
779
|
+
]}
|
|
780
|
+
panels={[{ id: "main" }]}
|
|
781
|
+
viewport={viewport}
|
|
782
|
+
viewportResetKey={viewportResetKey}
|
|
783
|
+
onViewportChange={(next) => {
|
|
784
|
+
viewportChanges.push(next
|
|
785
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
786
|
+
: null);
|
|
787
|
+
}}
|
|
788
|
+
/>
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
testSetup = await testRender(
|
|
793
|
+
<CaptureChartSurfaceProvider>
|
|
794
|
+
<Harness />
|
|
795
|
+
</CaptureChartSurfaceProvider>,
|
|
796
|
+
{ width: 62, height: 14 },
|
|
797
|
+
);
|
|
798
|
+
await act(async () => testSetup!.renderOnce());
|
|
799
|
+
|
|
800
|
+
for (let index = 0; index < 10; index += 1) {
|
|
801
|
+
await act(async () => {
|
|
802
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
803
|
+
scroll: { direction: "up", delta: 1 },
|
|
804
|
+
}));
|
|
805
|
+
});
|
|
806
|
+
await act(async () => {
|
|
807
|
+
await testSetup!.renderOnce();
|
|
808
|
+
await testSetup!.renderOnce();
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
const firstPan = viewportChanges[0]!;
|
|
812
|
+
const latestPan = viewportChanges.at(-1)!;
|
|
813
|
+
expect(firstPan).not.toBeNull();
|
|
814
|
+
expect(latestPan).not.toBeNull();
|
|
815
|
+
expect(latestPan).not.toEqual(firstPan);
|
|
816
|
+
|
|
817
|
+
await act(async () => publishViewport!(firstPan, true));
|
|
818
|
+
await act(async () => {
|
|
819
|
+
await testSetup!.renderOnce();
|
|
820
|
+
await testSetup!.renderOnce();
|
|
821
|
+
await testSetup!.renderOnce();
|
|
822
|
+
});
|
|
823
|
+
expect(viewportChanges).not.toContain(null);
|
|
824
|
+
expect(viewportChanges.at(-1)).toEqual(latestPan);
|
|
825
|
+
|
|
826
|
+
await act(async () => publishViewport!(latestPan));
|
|
827
|
+
await act(async () => {
|
|
828
|
+
await testSetup!.renderOnce();
|
|
829
|
+
await testSetup!.renderOnce();
|
|
830
|
+
await testSetup!.renderOnce();
|
|
831
|
+
});
|
|
832
|
+
expect(viewportChanges).not.toContain(null);
|
|
833
|
+
expect(viewportChanges.at(-1)).toEqual(latestPan);
|
|
834
|
+
|
|
835
|
+
for (let index = 0; index < 32; index += 1) {
|
|
836
|
+
await act(async () => {
|
|
837
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
838
|
+
scroll: { direction: "up", delta: 100 },
|
|
839
|
+
}));
|
|
840
|
+
});
|
|
841
|
+
await act(async () => {
|
|
842
|
+
await testSetup!.renderOnce();
|
|
843
|
+
await testSetup!.renderOnce();
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
const boundaryViewport = viewportChanges.at(-1)!;
|
|
847
|
+
expect(boundaryViewport).not.toBeNull();
|
|
848
|
+
|
|
849
|
+
await act(async () => publishViewport!(boundaryViewport));
|
|
850
|
+
await act(async () => {
|
|
851
|
+
await testSetup!.renderOnce();
|
|
852
|
+
await testSetup!.renderOnce();
|
|
853
|
+
});
|
|
854
|
+
const boundaryChangeCount = viewportChanges.length;
|
|
855
|
+
|
|
856
|
+
await act(async () => {
|
|
857
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
858
|
+
scroll: { direction: "up", delta: 100 },
|
|
859
|
+
}));
|
|
860
|
+
});
|
|
861
|
+
await act(async () => {
|
|
862
|
+
await testSetup!.renderOnce();
|
|
863
|
+
await testSetup!.renderOnce();
|
|
864
|
+
});
|
|
865
|
+
expect(viewportChanges).toHaveLength(boundaryChangeCount);
|
|
866
|
+
expect(viewportChanges).not.toContain(null);
|
|
867
|
+
|
|
868
|
+
await act(async () => publishExternalViewport!());
|
|
869
|
+
await act(async () => {
|
|
870
|
+
await testSetup!.renderOnce();
|
|
871
|
+
await testSetup!.renderOnce();
|
|
872
|
+
});
|
|
873
|
+
expect(viewportChanges.at(-1)).toBeNull();
|
|
490
874
|
});
|
|
491
875
|
|
|
492
876
|
test("activates and navigates a buffered viewport from the first mouse gesture", async () => {
|
|
@@ -515,7 +899,7 @@ describe("CompositeChart", () => {
|
|
|
515
899
|
);
|
|
516
900
|
|
|
517
901
|
await act(async () => testSetup!.renderOnce());
|
|
518
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
902
|
+
expect(testSetup.captureCharFrame()).toContain("Jan 5");
|
|
519
903
|
|
|
520
904
|
await act(async () => {
|
|
521
905
|
capturedSurfaceProps!.onMouseDown(pointerEvent(10, 3));
|
|
@@ -526,7 +910,7 @@ describe("CompositeChart", () => {
|
|
|
526
910
|
|
|
527
911
|
expect(activations).toBe(1);
|
|
528
912
|
expect(cursorChanges.length).toBeGreaterThan(0);
|
|
529
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
913
|
+
expect(testSetup.captureCharFrame()).toContain("Jan 3");
|
|
530
914
|
});
|
|
531
915
|
|
|
532
916
|
test("maps the pointer crosshair level through both axes and labels its date", async () => {
|
|
@@ -584,7 +968,7 @@ describe("CompositeChart", () => {
|
|
|
584
968
|
);
|
|
585
969
|
|
|
586
970
|
await act(async () => testSetup!.renderOnce());
|
|
587
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
971
|
+
expect(testSetup.captureCharFrame()).toContain("Jan 9");
|
|
588
972
|
|
|
589
973
|
await act(async () => {
|
|
590
974
|
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
@@ -594,7 +978,220 @@ describe("CompositeChart", () => {
|
|
|
594
978
|
});
|
|
595
979
|
await act(async () => testSetup!.renderOnce());
|
|
596
980
|
|
|
597
|
-
expect(testSetup.captureCharFrame()).not.toContain("
|
|
981
|
+
expect(testSetup.captureCharFrame()).not.toContain("Jan 9");
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
test("uses a supplied market timeline to keep control-wheel zoom under the pointer", async () => {
|
|
985
|
+
const anchor = twoSessionCandles();
|
|
986
|
+
const display = {
|
|
987
|
+
...anchor,
|
|
988
|
+
id: "secondary",
|
|
989
|
+
label: "Secondary",
|
|
990
|
+
timeBasis: undefined,
|
|
991
|
+
};
|
|
992
|
+
const viewport = {
|
|
993
|
+
start: new Date(anchor.points[0]!.date.getTime()),
|
|
994
|
+
end: new Date(anchor.points.at(-1)!.date.getTime()),
|
|
995
|
+
};
|
|
996
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
997
|
+
testSetup = await testRender(
|
|
998
|
+
<CaptureChartSurfaceProvider>
|
|
999
|
+
<CompositeChart
|
|
1000
|
+
width={60}
|
|
1001
|
+
height={12}
|
|
1002
|
+
interactive
|
|
1003
|
+
series={[display]}
|
|
1004
|
+
timelineSeries={[anchor]}
|
|
1005
|
+
panels={[{ id: "main" }]}
|
|
1006
|
+
viewport={viewport}
|
|
1007
|
+
onViewportChange={(next) => viewportChanges.push(next
|
|
1008
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
1009
|
+
: null)}
|
|
1010
|
+
/>
|
|
1011
|
+
</CaptureChartSurfaceProvider>,
|
|
1012
|
+
{ width: 62, height: 14 },
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
await act(async () => testSetup!.renderOnce());
|
|
1016
|
+
const pointerX = 35;
|
|
1017
|
+
const plotWidth = capturedSurfaceNode!.width as number;
|
|
1018
|
+
const minimumSpan = resolveCompositeMinimumSpanMs([display], viewport);
|
|
1019
|
+
const expected = zoomCompositeViewport(
|
|
1020
|
+
viewport,
|
|
1021
|
+
viewport,
|
|
1022
|
+
1 + 4 * 0.04,
|
|
1023
|
+
pointerX / Math.max(plotWidth - 1, 1),
|
|
1024
|
+
minimumSpan,
|
|
1025
|
+
[anchor],
|
|
1026
|
+
);
|
|
1027
|
+
|
|
1028
|
+
await act(async () => {
|
|
1029
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(pointerX, 3, {
|
|
1030
|
+
ctrl: true,
|
|
1031
|
+
scroll: { direction: "up", delta: 4 },
|
|
1032
|
+
}));
|
|
1033
|
+
});
|
|
1034
|
+
await act(async () => {
|
|
1035
|
+
await testSetup!.renderOnce();
|
|
1036
|
+
await testSetup!.renderOnce();
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
expect(viewportChanges.at(-1)).toEqual({
|
|
1040
|
+
start: expected.start.toISOString(),
|
|
1041
|
+
end: expected.end.toISOString(),
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
test("clears a drag interaction when the pointer returns to the authored viewport", async () => {
|
|
1046
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
1047
|
+
const interactions: string[] = [];
|
|
1048
|
+
testSetup = await testRender(
|
|
1049
|
+
<CaptureChartSurfaceProvider>
|
|
1050
|
+
<CompositeChart
|
|
1051
|
+
width={60}
|
|
1052
|
+
height={12}
|
|
1053
|
+
interactive
|
|
1054
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1055
|
+
panels={[{ id: "main" }]}
|
|
1056
|
+
viewport={{
|
|
1057
|
+
start: new Date("2025-01-05T00:00:00.000Z"),
|
|
1058
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
1059
|
+
}}
|
|
1060
|
+
onViewportChange={(next, interaction) => {
|
|
1061
|
+
interactions.push(interaction);
|
|
1062
|
+
viewportChanges.push(next
|
|
1063
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
1064
|
+
: null);
|
|
1065
|
+
}}
|
|
1066
|
+
/>
|
|
1067
|
+
</CaptureChartSurfaceProvider>,
|
|
1068
|
+
{ width: 62, height: 14 },
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
await act(async () => testSetup!.renderOnce());
|
|
1072
|
+
await act(async () => {
|
|
1073
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(20, 3));
|
|
1074
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 3));
|
|
1075
|
+
});
|
|
1076
|
+
await act(async () => testSetup!.renderOnce());
|
|
1077
|
+
expect(viewportChanges.at(-1)).not.toBeNull();
|
|
1078
|
+
|
|
1079
|
+
await act(async () => {
|
|
1080
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(20, 3));
|
|
1081
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(20, 3));
|
|
1082
|
+
});
|
|
1083
|
+
await act(async () => {
|
|
1084
|
+
await testSetup!.renderOnce();
|
|
1085
|
+
await testSetup!.renderOnce();
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
expect(viewportChanges.at(-1)).toBeNull();
|
|
1089
|
+
expect(interactions.at(-1)).toBe("pan");
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
test("always treats horizontal control-wheel movement as a pan", async () => {
|
|
1093
|
+
const interactions: string[] = [];
|
|
1094
|
+
testSetup = await testRender(
|
|
1095
|
+
<CaptureChartSurfaceProvider>
|
|
1096
|
+
<CompositeChart
|
|
1097
|
+
width={60}
|
|
1098
|
+
height={12}
|
|
1099
|
+
interactive
|
|
1100
|
+
series={[twoSessionCandles()]}
|
|
1101
|
+
panels={[{ id: "main" }]}
|
|
1102
|
+
viewport={{
|
|
1103
|
+
start: new Date("2025-01-03T13:30:00.000Z"),
|
|
1104
|
+
end: new Date("2025-01-03T17:00:00.000Z"),
|
|
1105
|
+
}}
|
|
1106
|
+
onViewportChange={(_next, interaction) => interactions.push(interaction)}
|
|
1107
|
+
/>
|
|
1108
|
+
</CaptureChartSurfaceProvider>,
|
|
1109
|
+
{ width: 62, height: 14 },
|
|
1110
|
+
);
|
|
1111
|
+
|
|
1112
|
+
await act(async () => testSetup!.renderOnce());
|
|
1113
|
+
await act(async () => {
|
|
1114
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
1115
|
+
ctrl: true,
|
|
1116
|
+
scroll: { direction: "left", delta: 4 },
|
|
1117
|
+
}));
|
|
1118
|
+
});
|
|
1119
|
+
await act(async () => {
|
|
1120
|
+
await testSetup!.renderOnce();
|
|
1121
|
+
await testSetup!.renderOnce();
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
expect(interactions.at(-1)).toBe("pan");
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test("keeps the date axis and mouse recovery when refreshed data misses the zoomed window", async () => {
|
|
1128
|
+
let replacePoints: ((points: TimeSeriesPoint[]) => void) | null = null;
|
|
1129
|
+
function Harness() {
|
|
1130
|
+
const [points, setPoints] = useState(
|
|
1131
|
+
series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108]).points,
|
|
1132
|
+
);
|
|
1133
|
+
replacePoints = setPoints;
|
|
1134
|
+
return (
|
|
1135
|
+
<CompositeChart
|
|
1136
|
+
width={60}
|
|
1137
|
+
height={12}
|
|
1138
|
+
interactive
|
|
1139
|
+
series={[{
|
|
1140
|
+
...series("price", "main", "left", "USD", []),
|
|
1141
|
+
points,
|
|
1142
|
+
}]}
|
|
1143
|
+
panels={[{ id: "main" }]}
|
|
1144
|
+
viewport={{
|
|
1145
|
+
start: new Date("2025-01-01T00:00:00.000Z"),
|
|
1146
|
+
end: new Date("2025-01-09T00:00:00.000Z"),
|
|
1147
|
+
}}
|
|
1148
|
+
/>
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
testSetup = await testRender(
|
|
1153
|
+
<CaptureChartSurfaceProvider>
|
|
1154
|
+
<Harness />
|
|
1155
|
+
</CaptureChartSurfaceProvider>,
|
|
1156
|
+
{ width: 62, height: 14 },
|
|
1157
|
+
);
|
|
1158
|
+
await act(async () => testSetup!.renderOnce());
|
|
1159
|
+
|
|
1160
|
+
for (let index = 0; index < 8; index += 1) {
|
|
1161
|
+
await act(async () => {
|
|
1162
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
1163
|
+
ctrl: true,
|
|
1164
|
+
scroll: { direction: "up", delta: 8 },
|
|
1165
|
+
}));
|
|
1166
|
+
});
|
|
1167
|
+
await act(async () => testSetup!.renderOnce());
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
await act(async () => {
|
|
1171
|
+
replacePoints?.([
|
|
1172
|
+
point("2025-01-01", 100),
|
|
1173
|
+
point("2025-01-09", 108),
|
|
1174
|
+
]);
|
|
1175
|
+
});
|
|
1176
|
+
await act(async () => testSetup!.renderOnce());
|
|
1177
|
+
|
|
1178
|
+
const emptyFrame = testSetup.captureCharFrame();
|
|
1179
|
+
expect(emptyFrame).toContain("No chart data");
|
|
1180
|
+
expect(emptyFrame).toContain("Jan ");
|
|
1181
|
+
expect(typeof capturedSurfaceProps!.onMouseScroll).toBe("function");
|
|
1182
|
+
|
|
1183
|
+
await act(async () => {
|
|
1184
|
+
capturedSurfaceProps!.onMouseScroll(pointerEvent(25, 3, {
|
|
1185
|
+
ctrl: true,
|
|
1186
|
+
scroll: { direction: "down", delta: 4 },
|
|
1187
|
+
}));
|
|
1188
|
+
});
|
|
1189
|
+
await act(async () => testSetup!.renderOnce());
|
|
1190
|
+
|
|
1191
|
+
const recoveredFrame = testSetup.captureCharFrame();
|
|
1192
|
+
expect(recoveredFrame).not.toContain("No chart data");
|
|
1193
|
+
expect(recoveredFrame).toContain("2025-01-01");
|
|
1194
|
+
expect(recoveredFrame).toContain("Jan 9");
|
|
598
1195
|
});
|
|
599
1196
|
|
|
600
1197
|
test("shows useful UTC times for an intraday shared cursor and time axis", async () => {
|