gloomberb 0.10.3 → 0.10.5
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/LICENSE +1 -1
- package/README.md +4 -4
- package/README.zh-CN.md +3 -3
- package/package.json +4 -5
- package/src/api-client/auth.ts +8 -0
- package/src/api-client/index.ts +70 -0
- package/src/api-client/types.ts +49 -0
- package/src/app/runtime/desktop-deeplink.test.ts +8 -0
- package/src/app/runtime/desktop-deeplink.ts +14 -1
- package/src/cli/entry.ts +11 -1
- package/src/cli/native-smoke.ts +5 -0
- package/src/components/chart/composite/composite-chart.test.tsx +340 -17
- package/src/components/chart/composite/composite-chart.tsx +859 -68
- package/src/components/chart/composite/interactions.test.ts +78 -22
- package/src/components/chart/composite/interactions.ts +45 -17
- package/src/components/chart/composite/rasterizer.ts +8 -2
- package/src/components/chart/composite/renderers.test.ts +61 -1
- package/src/components/chart/composite/scene.ts +14 -4
- package/src/components/chart/composite/tools.test.ts +371 -0
- package/src/components/chart/composite/tools.ts +508 -0
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart/native/raster/primitives.ts +18 -0
- package/src/components/chart/price-axis-labels.tsx +42 -2
- package/src/components/chart/static/chart/axis-overlays.test.tsx +2 -0
- package/src/components/chart/static/chart/axis-overlays.tsx +46 -0
- package/src/components/command-bar/action-runtime.ts +3 -0
- package/src/components/command-bar/assist/inventory.test.ts +93 -0
- package/src/components/command-bar/assist/inventory.ts +92 -0
- package/src/components/command-bar/assist/model.test.ts +104 -0
- package/src/components/command-bar/assist/model.ts +169 -0
- package/src/components/command-bar/assist/runtime.ts +185 -0
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/keyboard-shortcuts.ts +5 -0
- package/src/components/command-bar/list/model.ts +15 -2
- package/src/components/command-bar/list/navigation.ts +6 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/panel/body.tsx +2 -0
- package/src/components/command-bar/panel/index.tsx +3 -0
- package/src/components/command-bar/panel/runtime.ts +6 -0
- package/src/components/command-bar/panel/types.ts +1 -0
- package/src/components/command-bar/routes/root/provider-search.ts +7 -3
- package/src/components/command-bar/routes/root/results.ts +28 -2
- package/src/components/command-bar/routes/root/runtime.ts +66 -36
- package/src/components/command-bar/routes/root/shortcuts.ts +8 -4
- package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
- package/src/components/command-bar/routing/list-state.ts +2 -1
- package/src/components/command-bar/routing/navigation-state.ts +17 -0
- package/src/components/command-bar/selection-runtime.ts +26 -0
- package/src/components/command-bar/surface/assist.test.tsx +276 -0
- package/src/components/command-bar/surface/index.test.tsx +65 -0
- package/src/components/command-bar/surface/index.tsx +93 -2
- package/src/components/command-bar/surface/portfolio.test.tsx +2 -1
- package/src/components/command-bar/view-model.test.ts +24 -0
- package/src/components/command-bar/view-model.ts +9 -2
- package/src/components/command-bar/workflow/broker.test.ts +22 -0
- package/src/components/command-bar/workflow/broker.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +40 -2
- package/src/components/layout/floating-pane.tsx +4 -1
- package/src/components/layout/pane/header.tsx +64 -6
- package/src/components/layout/pane/index.tsx +4 -1
- package/src/components/layout/shell/index.tsx +23 -0
- package/src/components/layout/shell/pane/layers.tsx +5 -0
- package/src/components/onboarding/account-step/chooser-panel.tsx +74 -0
- package/src/components/onboarding/account-step/form-panel.tsx +157 -0
- package/src/components/onboarding/account-step/index.tsx +58 -0
- package/src/components/onboarding/account-step/model.test.ts +67 -0
- package/src/components/onboarding/account-step/model.ts +126 -0
- package/src/components/onboarding/account-step/signed-in-panel.tsx +31 -0
- package/src/components/onboarding/onboarding-steps.tsx +91 -55
- package/src/components/onboarding/onboarding-wizard.test.tsx +73 -6
- package/src/components/onboarding/onboarding-wizard.tsx +93 -9
- package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +3 -3
- package/src/components/onboarding/wizard-account.ts +235 -0
- package/src/components/onboarding/wizard-keyboard.ts +67 -1
- package/src/components/onboarding/wizard-model.ts +2 -2
- package/src/i18n/es.ts +1093 -0
- package/src/i18n/index.test.ts +5 -2
- package/src/i18n/index.ts +3 -0
- package/src/i18n/ja.ts +64 -3
- package/src/i18n/ko.ts +64 -3
- package/src/i18n/languages.test.ts +4 -1
- package/src/i18n/languages.ts +12 -1
- package/src/i18n/zh-cn.ts +64 -3
- package/src/i18n/zh-tw.ts +64 -3
- package/src/market-data/coordinator/quotes.ts +63 -1
- package/src/market-data/coordinator/subscriptions.test.ts +33 -0
- package/src/market-data/quotes/cadence.ts +2 -0
- package/src/plugins/builtin/account-management/footer.ts +9 -3
- package/src/plugins/builtin/account-management/model.ts +53 -1
- package/src/plugins/builtin/account-management/pane.tsx +107 -24
- package/src/plugins/builtin/ai/index.tsx +7 -2
- package/src/plugins/builtin/ai/screener/market-runtime.ts +4 -2
- package/src/plugins/builtin/ai/screener/pane.tsx +3 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +0 -1
- package/src/plugins/builtin/chart-composer/index.tsx +10 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +111 -44
- package/src/plugins/builtin/chart-composer/quick-add.tsx +27 -3
- package/src/plugins/builtin/chart-composer/semantic.ts +21 -0
- package/src/plugins/builtin/chat/controller/persistence.ts +5 -0
- package/src/plugins/builtin/cloud/plugin.tsx +9 -1
- package/src/plugins/builtin/cloud/upgrade-command.ts +28 -0
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +54 -0
- package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
- package/src/plugins/builtin/help/index.tsx +39 -1
- package/src/plugins/builtin/news/wire/news/footer.ts +18 -38
- package/src/plugins/builtin/options/footer.test.ts +20 -124
- package/src/plugins/builtin/options/footer.ts +32 -90
- package/src/plugins/builtin/options/index.tsx +6 -0
- package/src/plugins/builtin/options/view.tsx +3 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +12 -4
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +17 -13
- package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.test.tsx +76 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.ts +78 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +131 -0
- package/src/plugins/builtin/shared/live-streaming.ts +60 -0
- package/src/plugins/builtin/shared/plan-access.test.ts +69 -0
- package/src/plugins/builtin/shared/plan-access.ts +109 -0
- package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +11 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -3
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +4 -2
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/registry/index.test.ts +51 -0
- package/src/plugins/registry/index.ts +12 -0
- package/src/plugins/registry/pane-settings.ts +29 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/view/host/box.test.tsx +97 -0
- package/src/renderers/electrobun/view/host/box.tsx +3 -0
- package/src/renderers/electrobun/view/host/chart-surface.tsx +85 -1
- package/src/renderers/electrobun/view/host/focus-scope.test.ts +37 -0
- package/src/renderers/electrobun/view/host/focus-scope.ts +56 -0
- package/src/renderers/electrobun/view/host/input.tsx +16 -1
- package/src/renderers/electrobun/view/host/mouse.ts +1 -0
- package/src/renderers/electrobun/view/host/style.ts +1 -1
- package/src/renderers/electrobun/view/host/tabs.tsx +4 -2
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/opentui/chart-surface.tsx +2 -0
- package/src/renderers/opentui/dialog-host.tsx +279 -80
- package/src/renderers/opentui/host.tsx +3 -0
- package/src/renderers/opentui/test-utils.tsx +9 -6
- package/src/renderers/opentui/toast-host.tsx +136 -7
- package/src/renderers/opentui/ui-host.tsx +19 -3
- package/src/sources/gloomberb-cloud/index.test.ts +8 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -4
- package/src/sources/provider-router/history.test.ts +3 -3
- package/src/sources/provider-router/history.ts +15 -4
- package/src/sources/yahoo-finance/history.test.ts +47 -0
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/state/hooks/quote-streaming.test.tsx +124 -1
- package/src/state/hooks/quote-streaming.ts +90 -7
- package/src/theme/colors.ts +20 -1
- package/src/theme/themes.test.ts +3 -0
- package/src/tickers/search/index.test.ts +284 -2
- package/src/tickers/search/index.ts +55 -18
- package/src/tickers/search/ranking.ts +143 -18
- package/src/tickers/search/types.ts +1 -0
- package/src/{sources/gloomberb-cloud → time-series}/history-quality.ts +27 -3
- package/src/time-series/resolution.test.ts +51 -0
- package/src/time-series/resolution.ts +41 -0
- package/src/time-series/resolve.test.ts +207 -1
- package/src/time-series/resolve.ts +196 -29
- package/src/time-series/spec-studies.test.ts +4 -0
- package/src/time-series/spec.ts +15 -1
- package/src/time-series/types.ts +7 -0
- package/src/time-series/use-chart-resolution.test.tsx +37 -0
- package/src/time-series/use-chart-resolution.ts +17 -1
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +17 -0
- package/src/updater/github-releases.test.ts +3 -3
- package/src/updater/github-releases.ts +1 -1
|
@@ -82,6 +82,8 @@ function pointerEvent(
|
|
|
82
82
|
localY: number,
|
|
83
83
|
options: {
|
|
84
84
|
ctrl?: boolean;
|
|
85
|
+
shift?: boolean;
|
|
86
|
+
alt?: boolean;
|
|
85
87
|
scroll?: { direction: "up" | "down" | "left" | "right"; delta: number };
|
|
86
88
|
} = {},
|
|
87
89
|
) {
|
|
@@ -89,14 +91,18 @@ function pointerEvent(
|
|
|
89
91
|
return {
|
|
90
92
|
x: (node.x as number) + localX,
|
|
91
93
|
y: (node.y as number) + localY,
|
|
92
|
-
modifiers: {
|
|
94
|
+
modifiers: {
|
|
95
|
+
shift: options.shift === true,
|
|
96
|
+
alt: options.alt === true,
|
|
97
|
+
ctrl: options.ctrl === true,
|
|
98
|
+
},
|
|
93
99
|
scroll: options.scroll,
|
|
94
100
|
preventDefault() {},
|
|
95
101
|
stopPropagation() {},
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
function keyEvent(name: string): KeyEventLike {
|
|
105
|
+
function keyEvent(name: string, shift = false): KeyEventLike {
|
|
100
106
|
let defaultPrevented = false;
|
|
101
107
|
let propagationStopped = false;
|
|
102
108
|
return {
|
|
@@ -104,7 +110,7 @@ function keyEvent(name: string): KeyEventLike {
|
|
|
104
110
|
name,
|
|
105
111
|
sequence: name,
|
|
106
112
|
ctrl: false,
|
|
107
|
-
shift
|
|
113
|
+
shift,
|
|
108
114
|
alt: false,
|
|
109
115
|
meta: false,
|
|
110
116
|
get defaultPrevented() {
|
|
@@ -196,6 +202,26 @@ function twoSessionCandles(): ResolvedSeries {
|
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
describe("CompositeChart", () => {
|
|
205
|
+
test("shows the regular-session move beside the latest intraday value", async () => {
|
|
206
|
+
testSetup = await testRender(
|
|
207
|
+
<CompositeChart
|
|
208
|
+
width={60}
|
|
209
|
+
height={12}
|
|
210
|
+
series={[{
|
|
211
|
+
...series("price", "main", "left", "USD", [100, 110]),
|
|
212
|
+
latestChangePercent: 10.65,
|
|
213
|
+
}]}
|
|
214
|
+
panels={[{ id: "main" }]}
|
|
215
|
+
showLatestChangePercent
|
|
216
|
+
/>,
|
|
217
|
+
{ width: 62, height: 14 },
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
await act(async () => testSetup!.renderOnce());
|
|
221
|
+
|
|
222
|
+
expect(testSetup.captureCharFrame()).toContain("ACME Price $110 +10.65%");
|
|
223
|
+
});
|
|
224
|
+
|
|
199
225
|
test("lays out mixed panels with one shared legend and time axis", async () => {
|
|
200
226
|
testSetup = await testRender(
|
|
201
227
|
<CompositeChart
|
|
@@ -321,7 +347,7 @@ describe("CompositeChart", () => {
|
|
|
321
347
|
expect(largestGap).toBeLessThan(10);
|
|
322
348
|
});
|
|
323
349
|
|
|
324
|
-
test("
|
|
350
|
+
test("toggles a series from its legend entry", async () => {
|
|
325
351
|
const toggled: string[] = [];
|
|
326
352
|
testSetup = await testRender(
|
|
327
353
|
<CompositeChart
|
|
@@ -340,17 +366,9 @@ describe("CompositeChart", () => {
|
|
|
340
366
|
await act(async () => {
|
|
341
367
|
await testSetup!.renderOnce();
|
|
342
368
|
});
|
|
343
|
-
const
|
|
344
|
-
const priceColumn = firstLegendLine.indexOf("ACME Price");
|
|
369
|
+
const priceColumn = testSetup.captureCharFrame().split("\n")[0]!.indexOf("ACME Price");
|
|
345
370
|
expect(priceColumn).toBeGreaterThan(0);
|
|
346
371
|
|
|
347
|
-
await act(async () => {
|
|
348
|
-
await testSetup!.mockMouse.moveTo(2, 5);
|
|
349
|
-
await testSetup!.mockMouse.moveTo(priceColumn, 0);
|
|
350
|
-
await testSetup!.renderOnce();
|
|
351
|
-
});
|
|
352
|
-
expect(testSetup.captureCharFrame().split("\n")[0]).toContain("Hide");
|
|
353
|
-
|
|
354
372
|
await act(async () => {
|
|
355
373
|
await testSetup!.mockMouse.click(priceColumn, 0);
|
|
356
374
|
await testSetup!.renderOnce();
|
|
@@ -490,7 +508,8 @@ describe("CompositeChart", () => {
|
|
|
490
508
|
|
|
491
509
|
await act(async () => chartShortcut?.(keyEvent("escape")));
|
|
492
510
|
await act(async () => testSetup!.renderOnce());
|
|
493
|
-
|
|
511
|
+
// Escape drops the cursor, so its date leaves the time axis with it.
|
|
512
|
+
expect(testSetup.captureCharFrame()).not.toContain("2025-01-02");
|
|
494
513
|
});
|
|
495
514
|
|
|
496
515
|
test("does not emit the same snapped cursor timestamp twice", async () => {
|
|
@@ -913,6 +932,304 @@ describe("CompositeChart", () => {
|
|
|
913
932
|
expect(testSetup.captureCharFrame()).toContain("Jan 3");
|
|
914
933
|
});
|
|
915
934
|
|
|
935
|
+
test("zooms to a shift-drag selection instead of panning", async () => {
|
|
936
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
937
|
+
testSetup = await testRender(
|
|
938
|
+
<CaptureChartSurfaceProvider>
|
|
939
|
+
<CompositeChart
|
|
940
|
+
width={60}
|
|
941
|
+
height={12}
|
|
942
|
+
interactive
|
|
943
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
944
|
+
panels={[{ id: "main" }]}
|
|
945
|
+
onViewportChange={(next) => {
|
|
946
|
+
viewportChanges.push(next
|
|
947
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
948
|
+
: null);
|
|
949
|
+
}}
|
|
950
|
+
/>
|
|
951
|
+
</CaptureChartSurfaceProvider>,
|
|
952
|
+
{ width: 62, height: 14 },
|
|
953
|
+
);
|
|
954
|
+
await act(async () => testSetup!.renderOnce());
|
|
955
|
+
|
|
956
|
+
await act(async () => {
|
|
957
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(10, 3, { shift: true }));
|
|
958
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 3, { shift: true }));
|
|
959
|
+
});
|
|
960
|
+
await act(async () => testSetup!.renderOnce());
|
|
961
|
+
// The selection must not pan the way an unmodified drag would, and the
|
|
962
|
+
// range has to read as text for renderers that cannot draw the band.
|
|
963
|
+
expect(viewportChanges).toHaveLength(0);
|
|
964
|
+
expect(testSetup.captureCharFrame()).toContain("→");
|
|
965
|
+
|
|
966
|
+
await act(async () => {
|
|
967
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(30, 3, { shift: true }));
|
|
968
|
+
});
|
|
969
|
+
await act(async () => testSetup!.renderOnce());
|
|
970
|
+
|
|
971
|
+
const zoomed = viewportChanges.at(-1);
|
|
972
|
+
expect(zoomed).toBeTruthy();
|
|
973
|
+
expect(new Date(zoomed!.start).getTime()).toBeGreaterThan(Date.parse("2025-01-01T00:00:00.000Z"));
|
|
974
|
+
expect(new Date(zoomed!.end).getTime()).toBeLessThan(Date.parse("2025-01-09T00:00:00.000Z"));
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
test("reports an alt-drag measurement without moving the viewport", async () => {
|
|
978
|
+
const viewportChanges: Array<{ start: string; end: string } | null> = [];
|
|
979
|
+
testSetup = await testRender(
|
|
980
|
+
<CaptureChartSurfaceProvider>
|
|
981
|
+
<CompositeChart
|
|
982
|
+
width={60}
|
|
983
|
+
height={12}
|
|
984
|
+
interactive
|
|
985
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
986
|
+
panels={[{ id: "main" }]}
|
|
987
|
+
onViewportChange={(next) => {
|
|
988
|
+
viewportChanges.push(next
|
|
989
|
+
? { start: next.start.toISOString(), end: next.end.toISOString() }
|
|
990
|
+
: null);
|
|
991
|
+
}}
|
|
992
|
+
/>
|
|
993
|
+
</CaptureChartSurfaceProvider>,
|
|
994
|
+
{ width: 62, height: 14 },
|
|
995
|
+
);
|
|
996
|
+
await act(async () => testSetup!.renderOnce());
|
|
997
|
+
|
|
998
|
+
await act(async () => {
|
|
999
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6, { alt: true }));
|
|
1000
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(34, 1, { alt: true }));
|
|
1001
|
+
});
|
|
1002
|
+
await act(async () => testSetup!.renderOnce());
|
|
1003
|
+
|
|
1004
|
+
const measuringFrame = testSetup.captureCharFrame();
|
|
1005
|
+
// The readout sits in the middle of the box it measures, not in the legend.
|
|
1006
|
+
expect(measuringFrame).toContain("Δ");
|
|
1007
|
+
expect(measuringFrame).toContain("bars");
|
|
1008
|
+
expect(measuringFrame.split("\n")[0]).not.toContain("Δ");
|
|
1009
|
+
expect(viewportChanges).toHaveLength(0);
|
|
1010
|
+
|
|
1011
|
+
await act(async () => {
|
|
1012
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(34, 1, { alt: true }));
|
|
1013
|
+
});
|
|
1014
|
+
await act(async () => testSetup!.renderOnce());
|
|
1015
|
+
expect(testSetup.captureCharFrame()).not.toContain("Δ");
|
|
1016
|
+
expect(viewportChanges).toHaveLength(0);
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("arms a measurement from the keyboard when a modifier drag cannot reach the app", async () => {
|
|
1020
|
+
testSetup = await testRender(
|
|
1021
|
+
<InputHostProvider host={chartInputHost}>
|
|
1022
|
+
<CaptureChartSurfaceProvider>
|
|
1023
|
+
<CompositeChart
|
|
1024
|
+
width={60}
|
|
1025
|
+
height={12}
|
|
1026
|
+
focused
|
|
1027
|
+
interactive
|
|
1028
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1029
|
+
panels={[{ id: "main" }]}
|
|
1030
|
+
/>
|
|
1031
|
+
</CaptureChartSurfaceProvider>
|
|
1032
|
+
</InputHostProvider>,
|
|
1033
|
+
{ width: 62, height: 14 },
|
|
1034
|
+
);
|
|
1035
|
+
await act(async () => testSetup!.renderOnce());
|
|
1036
|
+
|
|
1037
|
+
await act(async () => chartShortcut?.(keyEvent("m", true)));
|
|
1038
|
+
await act(async () => testSetup!.renderOnce());
|
|
1039
|
+
|
|
1040
|
+
// No modifier on the drag: the armed tool is what makes it a measurement.
|
|
1041
|
+
await act(async () => {
|
|
1042
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1043
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(34, 1));
|
|
1044
|
+
});
|
|
1045
|
+
await act(async () => testSetup!.renderOnce());
|
|
1046
|
+
expect(testSetup.captureCharFrame()).toContain("Δ");
|
|
1047
|
+
|
|
1048
|
+
await act(async () => {
|
|
1049
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(34, 1));
|
|
1050
|
+
});
|
|
1051
|
+
await act(async () => testSetup!.renderOnce());
|
|
1052
|
+
expect(testSetup.captureCharFrame()).not.toContain("Δ");
|
|
1053
|
+
|
|
1054
|
+
// Sticky: the tool still owns the next drag until it is dismissed.
|
|
1055
|
+
await act(async () => {
|
|
1056
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1057
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 2));
|
|
1058
|
+
});
|
|
1059
|
+
await act(async () => testSetup!.renderOnce());
|
|
1060
|
+
expect(testSetup.captureCharFrame()).toContain("Δ");
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test("arms and disarms a chart tool from the toolbar", async () => {
|
|
1064
|
+
testSetup = await testRender(
|
|
1065
|
+
<CaptureChartSurfaceProvider>
|
|
1066
|
+
<CompositeChart
|
|
1067
|
+
width={60}
|
|
1068
|
+
height={12}
|
|
1069
|
+
interactive
|
|
1070
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1071
|
+
panels={[{ id: "main" }]}
|
|
1072
|
+
/>
|
|
1073
|
+
</CaptureChartSurfaceProvider>,
|
|
1074
|
+
{ width: 62, height: 14 },
|
|
1075
|
+
);
|
|
1076
|
+
await act(async () => testSetup!.renderOnce());
|
|
1077
|
+
|
|
1078
|
+
// Pressing the ruler icon has to arm the tool for an unmodified drag.
|
|
1079
|
+
await act(async () => {
|
|
1080
|
+
await testSetup!.mockMouse.moveTo(11, 1);
|
|
1081
|
+
await testSetup!.mockMouse.click(11, 1);
|
|
1082
|
+
});
|
|
1083
|
+
await act(async () => testSetup!.renderOnce());
|
|
1084
|
+
await act(async () => {
|
|
1085
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1086
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 2));
|
|
1087
|
+
});
|
|
1088
|
+
await act(async () => testSetup!.renderOnce());
|
|
1089
|
+
expect(testSetup.captureCharFrame()).toContain("Δ");
|
|
1090
|
+
|
|
1091
|
+
await act(async () => {
|
|
1092
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(30, 2));
|
|
1093
|
+
await testSetup!.mockMouse.click(11, 1);
|
|
1094
|
+
});
|
|
1095
|
+
await act(async () => testSetup!.renderOnce());
|
|
1096
|
+
await act(async () => {
|
|
1097
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1098
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 2));
|
|
1099
|
+
});
|
|
1100
|
+
await act(async () => testSetup!.renderOnce());
|
|
1101
|
+
expect(testSetup.captureCharFrame()).not.toContain("Δ");
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
test("draws a line, then grabs its end to reshape it", async () => {
|
|
1105
|
+
testSetup = await testRender(
|
|
1106
|
+
<InputHostProvider host={chartInputHost}>
|
|
1107
|
+
<CaptureChartSurfaceProvider>
|
|
1108
|
+
<CompositeChart
|
|
1109
|
+
width={60}
|
|
1110
|
+
height={12}
|
|
1111
|
+
focused
|
|
1112
|
+
interactive
|
|
1113
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1114
|
+
panels={[{ id: "main" }]}
|
|
1115
|
+
/>
|
|
1116
|
+
</CaptureChartSurfaceProvider>
|
|
1117
|
+
</InputHostProvider>,
|
|
1118
|
+
{ width: 62, height: 14 },
|
|
1119
|
+
);
|
|
1120
|
+
await act(async () => testSetup!.renderOnce());
|
|
1121
|
+
await act(async () => chartShortcut?.(keyEvent("d", true)));
|
|
1122
|
+
await act(async () => testSetup!.renderOnce());
|
|
1123
|
+
const base = capturedSurfaceProps!.bitmaps?.[0] as { pixels: Uint8Array } | undefined;
|
|
1124
|
+
|
|
1125
|
+
await act(async () => {
|
|
1126
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1127
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 2));
|
|
1128
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(30, 2));
|
|
1129
|
+
});
|
|
1130
|
+
await act(async () => testSetup!.renderOnce());
|
|
1131
|
+
const drawn = capturedSurfaceProps!.bitmaps?.[0] as { pixels: Uint8Array } | undefined;
|
|
1132
|
+
expect(Buffer.from(drawn!.pixels).equals(Buffer.from(base!.pixels))).toBe(false);
|
|
1133
|
+
|
|
1134
|
+
// Grabbing the endpoint that was just dropped has to reshape, not redraw.
|
|
1135
|
+
await act(async () => {
|
|
1136
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1137
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(14, 8));
|
|
1138
|
+
capturedSurfaceProps!.onMouseUp(pointerEvent(14, 8));
|
|
1139
|
+
});
|
|
1140
|
+
await act(async () => testSetup!.renderOnce());
|
|
1141
|
+
const reshaped = capturedSurfaceProps!.bitmaps?.[0] as { pixels: Uint8Array } | undefined;
|
|
1142
|
+
expect(reshaped).toBeTruthy();
|
|
1143
|
+
expect(Buffer.from(reshaped!.pixels).equals(Buffer.from(drawn!.pixels))).toBe(false);
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
test("disarms a chart tool with escape", async () => {
|
|
1147
|
+
testSetup = await testRender(
|
|
1148
|
+
<InputHostProvider host={chartInputHost}>
|
|
1149
|
+
<CaptureChartSurfaceProvider>
|
|
1150
|
+
<CompositeChart
|
|
1151
|
+
width={60}
|
|
1152
|
+
height={12}
|
|
1153
|
+
focused
|
|
1154
|
+
interactive
|
|
1155
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1156
|
+
panels={[{ id: "main" }]}
|
|
1157
|
+
/>
|
|
1158
|
+
</CaptureChartSurfaceProvider>
|
|
1159
|
+
</InputHostProvider>,
|
|
1160
|
+
{ width: 62, height: 14 },
|
|
1161
|
+
);
|
|
1162
|
+
await act(async () => testSetup!.renderOnce());
|
|
1163
|
+
|
|
1164
|
+
await act(async () => chartShortcut?.(keyEvent("z", true)));
|
|
1165
|
+
await act(async () => chartShortcut?.(keyEvent("escape")));
|
|
1166
|
+
await act(async () => testSetup!.renderOnce());
|
|
1167
|
+
|
|
1168
|
+
// Disarmed: an unmodified drag pans instead of selecting a range.
|
|
1169
|
+
await act(async () => {
|
|
1170
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6));
|
|
1171
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(30, 2));
|
|
1172
|
+
});
|
|
1173
|
+
await act(async () => testSetup!.renderOnce());
|
|
1174
|
+
expect(testSetup.captureCharFrame()).not.toContain("→");
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
test("keeps a measurement readable in a narrow pane", async () => {
|
|
1178
|
+
testSetup = await testRender(
|
|
1179
|
+
<CaptureChartSurfaceProvider>
|
|
1180
|
+
<CompositeChart
|
|
1181
|
+
width={28}
|
|
1182
|
+
height={12}
|
|
1183
|
+
interactive
|
|
1184
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1185
|
+
panels={[{ id: "main" }]}
|
|
1186
|
+
/>
|
|
1187
|
+
</CaptureChartSurfaceProvider>,
|
|
1188
|
+
{ width: 30, height: 14 },
|
|
1189
|
+
);
|
|
1190
|
+
await act(async () => testSetup!.renderOnce());
|
|
1191
|
+
|
|
1192
|
+
await act(async () => {
|
|
1193
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(2, 6, { alt: true }));
|
|
1194
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(14, 1, { alt: true }));
|
|
1195
|
+
});
|
|
1196
|
+
await act(async () => testSetup!.renderOnce());
|
|
1197
|
+
|
|
1198
|
+
// Too narrow for the whole summary, but the change still has to be visible.
|
|
1199
|
+
expect(testSetup.captureCharFrame()).toContain("Δ");
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
test("ends a tool drag whose release landed on another pane", async () => {
|
|
1203
|
+
testSetup = await testRender(
|
|
1204
|
+
<CaptureChartSurfaceProvider>
|
|
1205
|
+
<CompositeChart
|
|
1206
|
+
width={60}
|
|
1207
|
+
height={12}
|
|
1208
|
+
interactive
|
|
1209
|
+
series={[series("price", "main", "left", "USD", [100, 101, 102, 103, 104, 105, 106, 107, 108])]}
|
|
1210
|
+
panels={[{ id: "main" }]}
|
|
1211
|
+
/>
|
|
1212
|
+
</CaptureChartSurfaceProvider>,
|
|
1213
|
+
{ width: 62, height: 14 },
|
|
1214
|
+
);
|
|
1215
|
+
await act(async () => testSetup!.renderOnce());
|
|
1216
|
+
|
|
1217
|
+
await act(async () => {
|
|
1218
|
+
capturedSurfaceProps!.onMouseDown(pointerEvent(8, 6, { alt: true }));
|
|
1219
|
+
capturedSurfaceProps!.onMouseDrag(pointerEvent(34, 1, { alt: true }));
|
|
1220
|
+
});
|
|
1221
|
+
await act(async () => testSetup!.renderOnce());
|
|
1222
|
+
expect(testSetup.captureCharFrame()).toContain("Δ");
|
|
1223
|
+
|
|
1224
|
+
// No mouse-up arrives: the pointer was released over a floating pane. The
|
|
1225
|
+
// next plain move only happens with the button already up.
|
|
1226
|
+
await act(async () => {
|
|
1227
|
+
capturedSurfaceProps!.onMouseMove(pointerEvent(20, 4));
|
|
1228
|
+
});
|
|
1229
|
+
await act(async () => testSetup!.renderOnce());
|
|
1230
|
+
expect(testSetup.captureCharFrame()).not.toContain("Δ");
|
|
1231
|
+
});
|
|
1232
|
+
|
|
916
1233
|
test("maps the pointer crosshair level through both axes and labels its date", async () => {
|
|
917
1234
|
testSetup = await testRender(
|
|
918
1235
|
<CaptureChartSurfaceProvider>
|
|
@@ -1175,8 +1492,14 @@ describe("CompositeChart", () => {
|
|
|
1175
1492
|
});
|
|
1176
1493
|
await act(async () => testSetup!.renderOnce());
|
|
1177
1494
|
|
|
1495
|
+
// An observation-free window keeps the chart frame and its axes; only the
|
|
1496
|
+
// plotted points go away.
|
|
1497
|
+
// An observation-free window keeps the chart frame and its axes; only the
|
|
1498
|
+
// plotted points go away.
|
|
1178
1499
|
const emptyFrame = testSetup.captureCharFrame();
|
|
1179
|
-
expect(emptyFrame).toContain("No chart data");
|
|
1500
|
+
expect(emptyFrame).not.toContain("No chart data");
|
|
1501
|
+
expect(emptyFrame).not.toContain("•");
|
|
1502
|
+
expect(emptyFrame).toContain("$108");
|
|
1180
1503
|
expect(emptyFrame).toContain("Jan ");
|
|
1181
1504
|
expect(typeof capturedSurfaceProps!.onMouseScroll).toBe("function");
|
|
1182
1505
|
|
|
@@ -1189,7 +1512,7 @@ describe("CompositeChart", () => {
|
|
|
1189
1512
|
await act(async () => testSetup!.renderOnce());
|
|
1190
1513
|
|
|
1191
1514
|
const recoveredFrame = testSetup.captureCharFrame();
|
|
1192
|
-
expect(recoveredFrame).
|
|
1515
|
+
expect(recoveredFrame).toContain("•");
|
|
1193
1516
|
expect(recoveredFrame).toContain("2025-01-01");
|
|
1194
1517
|
expect(recoveredFrame).toContain("Jan 9");
|
|
1195
1518
|
});
|
|
@@ -1220,7 +1543,7 @@ describe("CompositeChart", () => {
|
|
|
1220
1543
|
});
|
|
1221
1544
|
|
|
1222
1545
|
const frame = testSetup.captureCharFrame();
|
|
1223
|
-
expect(frame).toContain("
|
|
1546
|
+
expect(frame).toContain("12:05 UTC");
|
|
1224
1547
|
expect(frame).toContain("09:30 UTC");
|
|
1225
1548
|
expect(frame).toContain("16:00 UTC");
|
|
1226
1549
|
});
|