gloomberb 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/app.tsx +184 -69
- package/src/cli/commands/ticker.ts +45 -15
- package/src/cli/helpers.ts +4 -2
- package/src/cli/registry.test.ts +42 -35
- package/src/cli.test.ts +4 -2
- package/src/components/chart/chart-control-hint.tsx +39 -0
- package/src/components/chart/chart-controller.test.ts +110 -0
- package/src/components/chart/chart-controller.ts +364 -2
- package/src/components/chart/chart-data.ts +177 -29
- package/src/components/chart/chart-indicator-selector.tsx +53 -0
- package/src/components/chart/chart-pane-settings.ts +20 -0
- package/src/components/chart/chart-renderer.ts +220 -30
- package/src/components/chart/chart-resolution.test.ts +67 -0
- package/src/components/chart/chart-resolution.ts +74 -4
- package/src/components/chart/chart-scroll.test.ts +69 -0
- package/src/components/chart/chart-scroll.ts +104 -0
- package/src/components/chart/chart-types.ts +12 -0
- package/src/components/chart/chart-viewport.test.ts +5 -1
- package/src/components/chart/chart-viewport.ts +1 -1
- package/src/components/chart/chart.test.ts +249 -10
- package/src/components/chart/comparison-chart-renderer.test.ts +71 -1
- package/src/components/chart/comparison-chart-renderer.ts +40 -3
- package/src/components/chart/comparison-stock-chart.tsx +218 -123
- package/src/components/chart/indicators/bands.test.ts +50 -0
- package/src/components/chart/indicators/bands.ts +29 -0
- package/src/components/chart/indicators/moving-averages.test.ts +54 -0
- package/src/components/chart/indicators/moving-averages.ts +42 -0
- package/src/components/chart/indicators/options.test.ts +32 -0
- package/src/components/chart/indicators/options.ts +107 -0
- package/src/components/chart/indicators/oscillators.test.ts +79 -0
- package/src/components/chart/indicators/oscillators.ts +92 -0
- package/src/components/chart/indicators/types.ts +36 -0
- package/src/components/chart/native/chart-rasterizer.ts +51 -0
- package/src/components/chart/native/kitty.test.ts +68 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +740 -9
- package/src/components/chart/stock-chart.test.ts +239 -0
- package/src/components/chart/stock-chart.tsx +2257 -372
- package/src/components/command-bar/command-bar.test.tsx +337 -5
- package/src/components/command-bar/command-bar.tsx +296 -120
- package/src/components/command-bar/helpers.ts +1 -1
- package/src/components/command-bar/root-shortcuts.ts +33 -4
- package/src/components/command-bar/view-model.test.ts +3 -3
- package/src/components/command-bar/view-model.ts +4 -4
- package/src/components/command-bar/workflow-ops.ts +10 -0
- package/src/components/layout/floating-pane.tsx +3 -1
- package/src/components/layout/header.tsx +5 -4
- package/src/components/layout/pane-sizing.ts +6 -0
- package/src/components/layout/pane.tsx +5 -1
- package/src/components/layout/shell.test.tsx +263 -32
- package/src/components/layout/shell.tsx +10 -6
- package/src/components/layout/status-bar.test.tsx +16 -7
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-editor.tsx +1 -0
- package/src/components/pane-settings-dialog.tsx +13 -152
- package/src/components/pane-template-wizard.tsx +1 -0
- package/src/components/price-selector-dialog.tsx +12 -7
- package/src/components/ticker-badge-text.test.tsx +50 -0
- package/src/components/ticker-badge-text.tsx +17 -2
- package/src/components/ticker-badge.tsx +2 -2
- package/src/components/ticker-list-table.tsx +62 -2
- package/src/components/toggle-list.tsx +15 -2
- package/src/components/ui/data-table.tsx +82 -5
- package/src/components/ui/external-link.tsx +48 -11
- package/src/components/ui/fields.tsx +2 -1
- package/src/components/ui/index.ts +8 -1
- package/src/components/ui/multi-select-chips.tsx +69 -0
- package/src/components/ui/multi-select-dialog.tsx +267 -0
- package/src/components/ui/multi-select.ts +78 -0
- package/src/components/ui/ui.test.tsx +343 -1
- package/src/data/plugin-state-store.ts +30 -19
- package/src/data/resource-store.ts +133 -103
- package/src/data/session-store.ts +19 -12
- package/src/data/sqlite-cache.test.ts +37 -2
- package/src/data/sqlite-database.ts +8 -2
- package/src/data/sqlite-retry.test.ts +63 -0
- package/src/data/sqlite-retry.ts +66 -0
- package/src/data/ticker-store.ts +29 -18
- package/src/index.tsx +6 -1
- package/src/market-data/coordinator.test.ts +114 -5
- package/src/market-data/coordinator.ts +70 -1
- package/src/market-data/hooks.test.tsx +73 -2
- package/src/market-data/hooks.tsx +16 -4
- package/src/notifications/app-notifier.test.ts +177 -0
- package/src/notifications/app-notifier.ts +241 -0
- package/src/pane-settings.test.ts +2 -0
- package/src/plugins/builtin/ai/index.test.tsx +1 -1
- package/src/plugins/builtin/ai/screener-pane.test.tsx +2 -0
- package/src/plugins/builtin/ai/screener-pane.tsx +1 -0
- package/src/plugins/builtin/ai/ticker-context.ts +3 -2
- package/src/plugins/builtin/alerts/alert-engine.test.ts +90 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +55 -0
- package/src/plugins/builtin/alerts/index.test.tsx +357 -0
- package/src/plugins/builtin/alerts/index.tsx +519 -0
- package/src/plugins/builtin/alerts/types.ts +14 -0
- package/src/plugins/builtin/analytics/index.test.tsx +300 -0
- package/src/plugins/builtin/analytics/index.tsx +775 -0
- package/src/plugins/builtin/analytics/metrics.test.ts +125 -0
- package/src/plugins/builtin/analytics/metrics.ts +155 -0
- package/src/plugins/builtin/ask-ai.test.tsx +2 -0
- package/src/plugins/builtin/chat-controller.test.ts +92 -18
- package/src/plugins/builtin/chat-controller.ts +40 -11
- package/src/plugins/builtin/chat.test.tsx +715 -4
- package/src/plugins/builtin/chat.tsx +420 -119
- package/src/plugins/builtin/comparison-chart.test.tsx +97 -57
- package/src/plugins/builtin/comparison-chart.tsx +1 -2
- package/src/plugins/builtin/correlation/compute.test.ts +147 -0
- package/src/plugins/builtin/correlation/compute.ts +115 -0
- package/src/plugins/builtin/correlation/index.tsx +385 -0
- package/src/plugins/builtin/correlation/plugin.test.ts +44 -0
- package/src/plugins/builtin/correlation/settings.test.ts +34 -0
- package/src/plugins/builtin/correlation/settings.ts +95 -0
- package/src/plugins/builtin/debug.tsx +36 -50
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +181 -0
- package/src/plugins/builtin/earnings/earnings-cache.ts +136 -0
- package/src/plugins/builtin/earnings/earnings-data.test.ts +59 -0
- package/src/plugins/builtin/earnings/earnings-data.ts +50 -0
- package/src/plugins/builtin/earnings/index.tsx +337 -0
- package/src/plugins/builtin/econ/calendar-source.test.ts +219 -0
- package/src/plugins/builtin/econ/calendar-source.ts +121 -0
- package/src/plugins/builtin/econ/fred-client.test.ts +63 -0
- package/src/plugins/builtin/econ/fred-client.ts +79 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +88 -0
- package/src/plugins/builtin/econ/index.tsx +999 -0
- package/src/plugins/builtin/econ/types.ts +13 -0
- package/src/plugins/builtin/fred-settings.ts +10 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +178 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +30 -0
- package/src/plugins/builtin/help.test.tsx +1 -5
- package/src/plugins/builtin/help.tsx +1 -16
- package/src/plugins/builtin/insider/index.tsx +417 -0
- package/src/plugins/builtin/insider/insider-data.test.ts +55 -0
- package/src/plugins/builtin/insider/insider-data.ts +49 -0
- package/src/plugins/builtin/layout-manager.tsx +33 -16
- package/src/plugins/builtin/market-movers/index.tsx +573 -0
- package/src/plugins/builtin/market-movers/screener.test.ts +143 -0
- package/src/plugins/builtin/market-movers/screener.ts +105 -0
- package/src/plugins/builtin/options.tsx +4 -3
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +11 -6
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -3
- package/src/plugins/builtin/portfolio-list/header.tsx +3 -2
- package/src/plugins/builtin/portfolio-list/metrics.ts +22 -11
- package/src/plugins/builtin/portfolio-list/pane.tsx +3 -9
- package/src/plugins/builtin/portfolio-list/settings.ts +9 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -0
- package/src/plugins/builtin/portfolio-list.test.tsx +3 -1
- package/src/plugins/builtin/portfolio-metrics.test.ts +13 -1
- package/src/plugins/builtin/sec.tsx +114 -7
- package/src/plugins/builtin/sectors/index.tsx +359 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +75 -13
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +316 -80
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +4 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +17 -12
- package/src/plugins/builtin/ticker-detail.test.tsx +3 -3
- package/src/plugins/builtin/world-indices/index.tsx +410 -0
- package/src/plugins/builtin/world-indices/indices.ts +52 -0
- package/src/plugins/builtin/yield-curve/index.tsx +242 -0
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +212 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +107 -0
- package/src/plugins/catalog.ts +22 -0
- package/src/plugins/gridlock-notification.ts +15 -0
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/gateway-service.test.ts +22 -0
- package/src/plugins/ibkr/gateway-service.ts +6 -0
- package/src/plugins/ibkr/index.test.tsx +1 -1
- package/src/plugins/ibkr/index.tsx +8 -2
- package/src/plugins/ibkr/trade-tab.tsx +23 -5
- package/src/plugins/ibkr/trade-utils.test.ts +1 -1
- package/src/plugins/ibkr/trade-utils.ts +7 -6
- package/src/plugins/ibkr/trading-pane.tsx +8 -7
- package/src/plugins/plugin-runtime.test.tsx +2 -0
- package/src/plugins/plugin-runtime.tsx +10 -0
- package/src/plugins/prediction-markets/controller-data.ts +129 -14
- package/src/plugins/prediction-markets/controller.ts +1 -1
- package/src/plugins/prediction-markets/detail/shared.tsx +7 -6
- package/src/plugins/prediction-markets/index.tsx +5 -1
- package/src/plugins/prediction-markets/pane.test.tsx +78 -0
- package/src/plugins/prediction-markets/pane.tsx +7 -3
- package/src/plugins/prediction-markets/plugin.test.tsx +48 -1
- package/src/plugins/prediction-markets/services/fetch.ts +17 -7
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +20 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +22 -5
- package/src/plugins/registry.ts +45 -13
- package/src/sources/provider-router.test.ts +3 -2
- package/src/sources/sec-edgar.ts +13 -1
- package/src/sources/yahoo-finance.ts +75 -1
- package/src/state/app-context-selector.test.tsx +17 -1
- package/src/state/app-context.test.tsx +24 -0
- package/src/state/app-context.tsx +29 -6
- package/src/theme/colors.ts +5 -0
- package/src/theme/themes.ts +25 -0
- package/src/types/config.test.ts +107 -0
- package/src/types/config.ts +30 -1
- package/src/types/data-provider.ts +13 -0
- package/src/types/plugin.ts +35 -1
- package/src/updater.test.ts +2 -0
- package/src/utils/api-client.ts +12 -0
- package/src/utils/format.test.ts +11 -1
- package/src/utils/format.ts +86 -8
- package/src/utils/inline-content-tokenizer.test.ts +22 -0
- package/src/utils/inline-content-tokenizer.ts +76 -0
- package/src/utils/link-tokenizer.test.ts +27 -0
- package/src/utils/link-tokenizer.ts +93 -0
- package/src/utils/market-format.test.ts +109 -0
- package/src/utils/market-format.ts +299 -0
- package/src/utils/market-status.ts +3 -2
- package/src/utils/selection-clipboard.test.ts +43 -0
- package/src/utils/selection-clipboard.ts +88 -0
- package/src/utils/terminal-input-reset.test.ts +50 -0
- package/src/utils/terminal-input-reset.ts +24 -0
- package/src/utils/throttled-fetch.test.ts +145 -0
- package/src/utils/throttled-fetch.ts +212 -0
- package/src/utils/ticker-tokenizer.test.ts +31 -0
- package/src/utils/ticker-tokenizer.ts +7 -42
- package/src/version.ts +1 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type AppAction,
|
|
11
11
|
} from "../../state/app-context";
|
|
12
12
|
import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../market-data/coordinator";
|
|
13
|
+
import { setSharedDataProviderForTests } from "../../plugins/registry";
|
|
13
14
|
import { cloneLayout, createDefaultConfig, type AppConfig } from "../../types/config";
|
|
14
15
|
import type { DataProvider } from "../../types/data-provider";
|
|
15
16
|
import type { PricePoint, TickerFinancials } from "../../types/financials";
|
|
@@ -17,6 +18,7 @@ import type { TickerRecord } from "../../types/ticker";
|
|
|
17
18
|
import { StockChart } from "./stock-chart";
|
|
18
19
|
|
|
19
20
|
const TEST_PANE_ID = "ticker-detail:test";
|
|
21
|
+
const CHART_DETAIL_REQUEST_DEBOUNCE_WAIT_MS = 220;
|
|
20
22
|
|
|
21
23
|
let testSetup: Awaited<ReturnType<typeof createTestRenderer>> | undefined;
|
|
22
24
|
let root: ReturnType<typeof createRoot> | undefined;
|
|
@@ -40,11 +42,14 @@ function makeTicker(symbol: string): TickerRecord {
|
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
function makeHistory(
|
|
45
|
+
function makeHistory(
|
|
46
|
+
length: number,
|
|
47
|
+
startDate = new Date(Date.UTC(2024, 0, 1)),
|
|
48
|
+
): PricePoint[] {
|
|
44
49
|
return Array.from({ length }, (_, index) => {
|
|
45
50
|
const trend = index < length / 2 ? index : length - index;
|
|
46
51
|
return {
|
|
47
|
-
date: new Date(
|
|
52
|
+
date: new Date(startDate.getTime() + index * 24 * 3600_000),
|
|
48
53
|
open: 100 + trend,
|
|
49
54
|
high: 101 + trend,
|
|
50
55
|
low: 99 + trend,
|
|
@@ -54,6 +59,49 @@ function makeHistory(length: number): PricePoint[] {
|
|
|
54
59
|
});
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
function makeIntradayHistory(
|
|
63
|
+
days: number,
|
|
64
|
+
intervalHours = 1,
|
|
65
|
+
startDate = new Date(Date.UTC(2024, 0, 1)),
|
|
66
|
+
): PricePoint[] {
|
|
67
|
+
const pointsPerDay = Math.floor(24 / intervalHours);
|
|
68
|
+
return Array.from({ length: days * pointsPerDay }, (_, index) => {
|
|
69
|
+
const date = new Date(startDate.getTime() + index * intervalHours * 3600_000);
|
|
70
|
+
const base = 100 + Math.sin(index / 12) * 4 + index * 0.02;
|
|
71
|
+
return {
|
|
72
|
+
date,
|
|
73
|
+
open: base - 0.4,
|
|
74
|
+
high: base + 0.8,
|
|
75
|
+
low: base - 0.9,
|
|
76
|
+
close: base,
|
|
77
|
+
volume: 5_000 + index * 10,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function makeMonthlyHistory(
|
|
83
|
+
months: number,
|
|
84
|
+
startDate = new Date(Date.UTC(2021, 5, 1)),
|
|
85
|
+
): PricePoint[] {
|
|
86
|
+
return Array.from({ length: months }, (_, index) => {
|
|
87
|
+
const date = new Date(Date.UTC(
|
|
88
|
+
startDate.getUTCFullYear(),
|
|
89
|
+
startDate.getUTCMonth() + index,
|
|
90
|
+
startDate.getUTCDate(),
|
|
91
|
+
12,
|
|
92
|
+
));
|
|
93
|
+
const base = 100 + index * 2.5;
|
|
94
|
+
return {
|
|
95
|
+
date,
|
|
96
|
+
open: base - 1,
|
|
97
|
+
high: base + 2,
|
|
98
|
+
low: base - 3,
|
|
99
|
+
close: base,
|
|
100
|
+
volume: 2_000 + index * 50,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
57
105
|
function makeChartConfig(symbol: string): AppConfig {
|
|
58
106
|
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
59
107
|
config.layout = {
|
|
@@ -98,11 +146,21 @@ function ChartHarness({
|
|
|
98
146
|
async function flushFrames(count = 2) {
|
|
99
147
|
for (let index = 0; index < count; index += 1) {
|
|
100
148
|
await act(async () => {
|
|
149
|
+
await Promise.resolve();
|
|
150
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
101
151
|
await testSetup!.renderOnce();
|
|
152
|
+
await Promise.resolve();
|
|
102
153
|
});
|
|
103
154
|
}
|
|
104
155
|
}
|
|
105
156
|
|
|
157
|
+
async function flushDebouncedChartRequests() {
|
|
158
|
+
await act(async () => {
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, CHART_DETAIL_REQUEST_DEBOUNCE_WAIT_MS));
|
|
160
|
+
});
|
|
161
|
+
await flushFrames(2);
|
|
162
|
+
}
|
|
163
|
+
|
|
106
164
|
async function emitKeypress(event: { name?: string; sequence?: string }) {
|
|
107
165
|
await act(async () => {
|
|
108
166
|
testSetup!.renderer.keyInput.emit("keypress", {
|
|
@@ -118,6 +176,14 @@ async function emitKeypress(event: { name?: string; sequence?: string }) {
|
|
|
118
176
|
});
|
|
119
177
|
}
|
|
120
178
|
|
|
179
|
+
function getFrameLineContaining(frame: string, text: string): string {
|
|
180
|
+
return frame.split("\n").find((line) => line.includes(text)) ?? "";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getFrameRowContaining(frame: string, text: string): number {
|
|
184
|
+
return frame.split("\n").findIndex((line) => line.includes(text));
|
|
185
|
+
}
|
|
186
|
+
|
|
121
187
|
function makeProvider(historyBySymbol: Record<string, PricePoint[]>): DataProvider {
|
|
122
188
|
return {
|
|
123
189
|
id: "test-provider",
|
|
@@ -140,7 +206,14 @@ function makeProvider(historyBySymbol: Record<string, PricePoint[]>): DataProvid
|
|
|
140
206
|
getNews: async () => [],
|
|
141
207
|
getArticleSummary: async () => null,
|
|
142
208
|
getPriceHistory: async (symbol) => historyBySymbol[symbol] ?? [],
|
|
209
|
+
getDetailedPriceHistory: async (symbol, _exchange, startDate, endDate) => (
|
|
210
|
+
historyBySymbol[symbol] ?? []
|
|
211
|
+
).filter((point) => {
|
|
212
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
213
|
+
return date >= startDate && date <= endDate;
|
|
214
|
+
}),
|
|
143
215
|
getChartResolutionSupport: async () => [
|
|
216
|
+
{ resolution: "1m", maxRange: "1D" },
|
|
144
217
|
{ resolution: "5m", maxRange: "1W" },
|
|
145
218
|
{ resolution: "15m", maxRange: "1M" },
|
|
146
219
|
{ resolution: "1h", maxRange: "3M" },
|
|
@@ -151,13 +224,15 @@ function makeProvider(historyBySymbol: Record<string, PricePoint[]>): DataProvid
|
|
|
151
224
|
};
|
|
152
225
|
}
|
|
153
226
|
|
|
154
|
-
afterEach(() => {
|
|
227
|
+
afterEach(async () => {
|
|
155
228
|
harnessDispatch = null;
|
|
156
229
|
sharedCoordinator = null;
|
|
157
230
|
setSharedMarketDataCoordinator(null);
|
|
231
|
+
setSharedDataProviderForTests(undefined);
|
|
158
232
|
if (root) {
|
|
159
|
-
act(() => {
|
|
233
|
+
await act(async () => {
|
|
160
234
|
root!.unmount();
|
|
235
|
+
await Promise.resolve();
|
|
161
236
|
});
|
|
162
237
|
root = undefined;
|
|
163
238
|
}
|
|
@@ -165,7 +240,6 @@ afterEach(() => {
|
|
|
165
240
|
testSetup.renderer.destroy();
|
|
166
241
|
testSetup = undefined;
|
|
167
242
|
}
|
|
168
|
-
actEnvironment.IS_REACT_ACT_ENVIRONMENT = false;
|
|
169
243
|
});
|
|
170
244
|
|
|
171
245
|
describe("StockChart renderer switching", () => {
|
|
@@ -176,6 +250,7 @@ describe("StockChart renderer switching", () => {
|
|
|
176
250
|
const provider = makeProvider({ [symbol]: [] });
|
|
177
251
|
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
178
252
|
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
253
|
+
setSharedDataProviderForTests(provider);
|
|
179
254
|
const financials: TickerFinancials = {
|
|
180
255
|
annualStatements: [],
|
|
181
256
|
quarterlyStatements: [],
|
|
@@ -217,8 +292,8 @@ describe("StockChart renderer switching", () => {
|
|
|
217
292
|
|
|
218
293
|
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
219
294
|
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
220
|
-
(testSetup.renderer as { _capabilities: unknown })._capabilities = { kitty_graphics: true };
|
|
221
|
-
(testSetup.renderer as { _resolution: unknown })._resolution = { width: 1200, height: 960 };
|
|
295
|
+
((testSetup.renderer as unknown) as { _capabilities: unknown })._capabilities = { kitty_graphics: true };
|
|
296
|
+
((testSetup.renderer as unknown) as { _resolution: unknown })._resolution = { width: 1200, height: 960 };
|
|
222
297
|
|
|
223
298
|
root = createRoot(testSetup.renderer);
|
|
224
299
|
act(() => {
|
|
@@ -257,6 +332,7 @@ describe("StockChart renderer switching", () => {
|
|
|
257
332
|
const provider = makeProvider({ [symbol]: makeHistory(180) });
|
|
258
333
|
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
259
334
|
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
335
|
+
setSharedDataProviderForTests(provider);
|
|
260
336
|
const financials: TickerFinancials = {
|
|
261
337
|
annualStatements: [],
|
|
262
338
|
quarterlyStatements: [],
|
|
@@ -277,13 +353,668 @@ describe("StockChart renderer switching", () => {
|
|
|
277
353
|
});
|
|
278
354
|
|
|
279
355
|
await flushFrames(3);
|
|
280
|
-
expect(testSetup.captureCharFrame()).toContain("AAPL -
|
|
356
|
+
expect(testSetup.captureCharFrame()).toContain("AAPL - AUTO");
|
|
281
357
|
|
|
282
358
|
await emitKeypress({ name: "3", sequence: "3" });
|
|
283
359
|
await flushFrames(3);
|
|
360
|
+
await flushDebouncedChartRequests();
|
|
284
361
|
|
|
285
362
|
const frame = testSetup.captureCharFrame();
|
|
363
|
+
expect(frame).not.toContain("AAPL - AUTO");
|
|
286
364
|
expect(frame).toContain("AAPL - 15M");
|
|
287
|
-
expect(frame).
|
|
365
|
+
expect(frame).toContain("May 28");
|
|
366
|
+
expect(frame).not.toContain("view:");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("chooses supported auto detail resolutions as the visible window changes", async () => {
|
|
370
|
+
const symbol = "AAPL";
|
|
371
|
+
const config = makeChartConfig(symbol);
|
|
372
|
+
config.layout.instances = config.layout.instances.map((instance) => (
|
|
373
|
+
instance.instanceId === TEST_PANE_ID
|
|
374
|
+
? {
|
|
375
|
+
...instance,
|
|
376
|
+
settings: {
|
|
377
|
+
chartRangePreset: "1W",
|
|
378
|
+
chartResolution: "auto",
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
: instance
|
|
382
|
+
));
|
|
383
|
+
|
|
384
|
+
const ticker = makeTicker(symbol);
|
|
385
|
+
const history = makeIntradayHistory(30);
|
|
386
|
+
const detailBarSizes: string[] = [];
|
|
387
|
+
const provider = {
|
|
388
|
+
...makeProvider({ [symbol]: history }),
|
|
389
|
+
getDetailedPriceHistory: async (
|
|
390
|
+
requestSymbol: string,
|
|
391
|
+
_exchange: string,
|
|
392
|
+
startDate: Date,
|
|
393
|
+
endDate: Date,
|
|
394
|
+
barSize: string,
|
|
395
|
+
) => {
|
|
396
|
+
detailBarSizes.push(barSize);
|
|
397
|
+
return history.filter((point) => {
|
|
398
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
399
|
+
return date >= startDate && date <= endDate;
|
|
400
|
+
});
|
|
401
|
+
},
|
|
402
|
+
} satisfies DataProvider;
|
|
403
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
404
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
405
|
+
setSharedDataProviderForTests(provider);
|
|
406
|
+
const financials: TickerFinancials = {
|
|
407
|
+
annualStatements: [],
|
|
408
|
+
quarterlyStatements: [],
|
|
409
|
+
priceHistory: history,
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
413
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
414
|
+
root = createRoot(testSetup.renderer);
|
|
415
|
+
act(() => {
|
|
416
|
+
root!.render(
|
|
417
|
+
<ChartHarness
|
|
418
|
+
config={config}
|
|
419
|
+
ticker={ticker}
|
|
420
|
+
financials={financials}
|
|
421
|
+
/>,
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
await flushFrames(6);
|
|
426
|
+
await flushDebouncedChartRequests();
|
|
427
|
+
expect(testSetup.captureCharFrame()).toContain("AAPL - AUTO");
|
|
428
|
+
expect(detailBarSizes).toContain("15m");
|
|
429
|
+
|
|
430
|
+
for (let index = 0; index < 6; index += 1) {
|
|
431
|
+
await emitKeypress({ name: "=", sequence: "=" });
|
|
432
|
+
await flushFrames(3);
|
|
433
|
+
}
|
|
434
|
+
await flushDebouncedChartRequests();
|
|
435
|
+
|
|
436
|
+
expect(detailBarSizes).toContain("1m");
|
|
437
|
+
expect(testSetup.captureCharFrame()).not.toContain("zoom:");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("keeps auto zoom span-based even when the base history is coarse", async () => {
|
|
441
|
+
const symbol = "AAPL";
|
|
442
|
+
const config = makeChartConfig(symbol);
|
|
443
|
+
config.layout.instances = config.layout.instances.map((instance) => (
|
|
444
|
+
instance.instanceId === TEST_PANE_ID
|
|
445
|
+
? {
|
|
446
|
+
...instance,
|
|
447
|
+
settings: {
|
|
448
|
+
chartRangePreset: "1W",
|
|
449
|
+
chartResolution: "auto",
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
: instance
|
|
453
|
+
));
|
|
454
|
+
|
|
455
|
+
const ticker = makeTicker(symbol);
|
|
456
|
+
const baseHistory = makeHistory(120);
|
|
457
|
+
const detailHistory = makeIntradayHistory(35, 1, new Date(Date.UTC(2024, 2, 25)));
|
|
458
|
+
const detailRequests: Array<{ barSize: string; spanMs: number }> = [];
|
|
459
|
+
const provider = {
|
|
460
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
461
|
+
getDetailedPriceHistory: async (
|
|
462
|
+
requestSymbol: string,
|
|
463
|
+
_exchange: string,
|
|
464
|
+
startDate: Date,
|
|
465
|
+
endDate: Date,
|
|
466
|
+
barSize: string,
|
|
467
|
+
) => {
|
|
468
|
+
detailRequests.push({ barSize, spanMs: endDate.getTime() - startDate.getTime() });
|
|
469
|
+
return detailHistory.filter((point) => {
|
|
470
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
471
|
+
return date >= startDate && date <= endDate;
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
} satisfies DataProvider;
|
|
475
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
476
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
477
|
+
setSharedDataProviderForTests(provider);
|
|
478
|
+
const financials: TickerFinancials = {
|
|
479
|
+
annualStatements: [],
|
|
480
|
+
quarterlyStatements: [],
|
|
481
|
+
priceHistory: baseHistory,
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
485
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
486
|
+
root = createRoot(testSetup.renderer);
|
|
487
|
+
act(() => {
|
|
488
|
+
root!.render(
|
|
489
|
+
<ChartHarness
|
|
490
|
+
config={config}
|
|
491
|
+
ticker={ticker}
|
|
492
|
+
financials={financials}
|
|
493
|
+
/>,
|
|
494
|
+
);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
await flushFrames(6);
|
|
498
|
+
await flushDebouncedChartRequests();
|
|
499
|
+
expect(detailRequests.some((request) => request.barSize === "15m")).toBe(true);
|
|
500
|
+
|
|
501
|
+
for (let index = 0; index < 8; index += 1) {
|
|
502
|
+
await emitKeypress({ name: "=", sequence: "=" });
|
|
503
|
+
await flushFrames(3);
|
|
504
|
+
}
|
|
505
|
+
await flushDebouncedChartRequests();
|
|
506
|
+
|
|
507
|
+
expect(detailRequests.some((request) => request.barSize === "1m")).toBe(true);
|
|
508
|
+
expect(
|
|
509
|
+
detailRequests.some((request) => request.barSize === "1m" && request.spanMs <= 24 * 60 * 60_000),
|
|
510
|
+
).toBe(true);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("prefers resolution history when it can satisfy the requested auto window", async () => {
|
|
514
|
+
const symbol = "AAPL";
|
|
515
|
+
const config = makeChartConfig(symbol);
|
|
516
|
+
const ticker = makeTicker(symbol);
|
|
517
|
+
const baseHistory = makeMonthlyHistory(59, new Date(Date.UTC(2021, 5, 1)));
|
|
518
|
+
const resolutionHistory = makeIntradayHistory(1774, 1, new Date(Date.UTC(2021, 5, 1)));
|
|
519
|
+
const resolutionRequests: Array<{ bufferRange: string; resolution: string }> = [];
|
|
520
|
+
const detailRequests: string[] = [];
|
|
521
|
+
const provider = {
|
|
522
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
523
|
+
getPriceHistory: async () => baseHistory,
|
|
524
|
+
getPriceHistoryForResolution: async (
|
|
525
|
+
requestSymbol: string,
|
|
526
|
+
_exchange: string,
|
|
527
|
+
bufferRange: string,
|
|
528
|
+
resolution: string,
|
|
529
|
+
) => {
|
|
530
|
+
resolutionRequests.push({ bufferRange, resolution });
|
|
531
|
+
return resolutionHistory;
|
|
532
|
+
},
|
|
533
|
+
getDetailedPriceHistory: async (
|
|
534
|
+
requestSymbol: string,
|
|
535
|
+
_exchange: string,
|
|
536
|
+
startDate: Date,
|
|
537
|
+
endDate: Date,
|
|
538
|
+
barSize: string,
|
|
539
|
+
) => {
|
|
540
|
+
detailRequests.push(barSize);
|
|
541
|
+
return [];
|
|
542
|
+
},
|
|
543
|
+
} satisfies DataProvider;
|
|
544
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
545
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
546
|
+
setSharedDataProviderForTests(provider);
|
|
547
|
+
const financials: TickerFinancials = {
|
|
548
|
+
annualStatements: [],
|
|
549
|
+
quarterlyStatements: [],
|
|
550
|
+
priceHistory: baseHistory,
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
554
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
555
|
+
root = createRoot(testSetup.renderer);
|
|
556
|
+
act(() => {
|
|
557
|
+
root!.render(
|
|
558
|
+
<ChartHarness
|
|
559
|
+
config={config}
|
|
560
|
+
ticker={ticker}
|
|
561
|
+
financials={financials}
|
|
562
|
+
/>,
|
|
563
|
+
);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await flushFrames(6);
|
|
567
|
+
for (let index = 0; index < 14; index += 1) {
|
|
568
|
+
await emitKeypress({ name: "=", sequence: "=" });
|
|
569
|
+
await flushFrames(4);
|
|
570
|
+
}
|
|
571
|
+
await flushDebouncedChartRequests();
|
|
572
|
+
|
|
573
|
+
const frame = testSetup.captureCharFrame();
|
|
574
|
+
expect(resolutionRequests.some((request) => request.resolution === "1d")).toBe(true);
|
|
575
|
+
expect(detailRequests).toHaveLength(0);
|
|
576
|
+
expect(frame).not.toContain("No price history available.");
|
|
577
|
+
expect(frame).toContain("Apr");
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("mouse scroll at the auto edge does not zoom into narrower detail windows", async () => {
|
|
581
|
+
const symbol = "AAPL";
|
|
582
|
+
const config = makeChartConfig(symbol);
|
|
583
|
+
const ticker = makeTicker(symbol);
|
|
584
|
+
const baseHistory = makeHistory(1774, new Date(Date.UTC(2021, 5, 1)));
|
|
585
|
+
const intradayDetail = makeIntradayHistory(240, 1, new Date(Date.UTC(2025, 8, 1)));
|
|
586
|
+
const detailRequests: string[] = [];
|
|
587
|
+
const provider = {
|
|
588
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
589
|
+
getDetailedPriceHistory: async (
|
|
590
|
+
requestSymbol: string,
|
|
591
|
+
_exchange: string,
|
|
592
|
+
startDate: Date,
|
|
593
|
+
endDate: Date,
|
|
594
|
+
barSize: string,
|
|
595
|
+
) => {
|
|
596
|
+
detailRequests.push(barSize);
|
|
597
|
+
const source = /(m|h)$/i.test(barSize) ? intradayDetail : baseHistory;
|
|
598
|
+
return source.filter((point) => {
|
|
599
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
600
|
+
return date >= startDate && date <= endDate;
|
|
601
|
+
});
|
|
602
|
+
},
|
|
603
|
+
} satisfies DataProvider;
|
|
604
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
605
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
606
|
+
setSharedDataProviderForTests(provider);
|
|
607
|
+
const financials: TickerFinancials = {
|
|
608
|
+
annualStatements: [],
|
|
609
|
+
quarterlyStatements: [],
|
|
610
|
+
priceHistory: baseHistory,
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
614
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
615
|
+
root = createRoot(testSetup.renderer);
|
|
616
|
+
act(() => {
|
|
617
|
+
root!.render(
|
|
618
|
+
<ChartHarness
|
|
619
|
+
config={config}
|
|
620
|
+
ticker={ticker}
|
|
621
|
+
financials={financials}
|
|
622
|
+
/>,
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
await flushFrames(6);
|
|
627
|
+
const initialFrame = testSetup.captureCharFrame();
|
|
628
|
+
const titleRow = getFrameRowContaining(initialFrame, "AAPL - AUTO");
|
|
629
|
+
expect(titleRow).toBeGreaterThanOrEqual(0);
|
|
630
|
+
|
|
631
|
+
await act(async () => {
|
|
632
|
+
await testSetup!.mockMouse.scroll(1, titleRow, "up");
|
|
633
|
+
await testSetup!.renderOnce();
|
|
634
|
+
});
|
|
635
|
+
await flushFrames(2);
|
|
636
|
+
|
|
637
|
+
for (let index = 0; index < 4; index += 1) {
|
|
638
|
+
await act(async () => {
|
|
639
|
+
await testSetup!.mockMouse.scroll(68, titleRow + 3, "up");
|
|
640
|
+
await testSetup!.renderOnce();
|
|
641
|
+
});
|
|
642
|
+
await flushFrames(2);
|
|
643
|
+
}
|
|
644
|
+
await flushDebouncedChartRequests();
|
|
645
|
+
|
|
646
|
+
const pannedFrame = testSetup.captureCharFrame();
|
|
647
|
+
const pannedHeader = getFrameLineContaining(pannedFrame, "AAPL - AUTO");
|
|
648
|
+
expect(pannedHeader).not.toContain("view:");
|
|
649
|
+
expect(detailRequests.some((request) => /(m|h)$/i.test(request))).toBe(false);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("keeps the chart interactive while a wider pan range is loading", async () => {
|
|
653
|
+
const symbol = "AAPL";
|
|
654
|
+
const config = makeChartConfig(symbol);
|
|
655
|
+
config.layout.instances = config.layout.instances.map((instance) => ({
|
|
656
|
+
...instance,
|
|
657
|
+
settings: {
|
|
658
|
+
chartRangePreset: "1Y",
|
|
659
|
+
chartResolution: "1d",
|
|
660
|
+
},
|
|
661
|
+
}));
|
|
662
|
+
config.layouts = [{ name: "Default", layout: cloneLayout(config.layout) }];
|
|
663
|
+
const ticker = makeTicker(symbol);
|
|
664
|
+
const oneYearHistory = makeHistory(252, new Date(Date.UTC(2025, 0, 1)));
|
|
665
|
+
const fiveYearHistory = makeHistory(1260, new Date(Date.UTC(2021, 0, 1)));
|
|
666
|
+
const pendingResolvers: Array<(history: PricePoint[]) => void> = [];
|
|
667
|
+
const provider = {
|
|
668
|
+
...makeProvider({ [symbol]: oneYearHistory }),
|
|
669
|
+
getPriceHistory: async (_requestSymbol, _exchange, range) => {
|
|
670
|
+
if (range === "5Y") {
|
|
671
|
+
return new Promise<PricePoint[]>((resolve) => {
|
|
672
|
+
pendingResolvers.push(resolve);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return oneYearHistory;
|
|
676
|
+
},
|
|
677
|
+
getPriceHistoryForResolution: async (_requestSymbol, _exchange, range) => {
|
|
678
|
+
if (range === "5Y") {
|
|
679
|
+
return new Promise<PricePoint[]>((resolve) => {
|
|
680
|
+
pendingResolvers.push(resolve);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
return oneYearHistory;
|
|
684
|
+
},
|
|
685
|
+
} satisfies DataProvider;
|
|
686
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
687
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
688
|
+
setSharedDataProviderForTests(provider);
|
|
689
|
+
const financials: TickerFinancials = {
|
|
690
|
+
annualStatements: [],
|
|
691
|
+
quarterlyStatements: [],
|
|
692
|
+
priceHistory: oneYearHistory,
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
696
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
697
|
+
root = createRoot(testSetup.renderer);
|
|
698
|
+
act(() => {
|
|
699
|
+
root!.render(
|
|
700
|
+
<ChartHarness
|
|
701
|
+
config={config}
|
|
702
|
+
ticker={ticker}
|
|
703
|
+
financials={financials}
|
|
704
|
+
/>,
|
|
705
|
+
);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
await flushFrames(6);
|
|
709
|
+
expect(testSetup.captureCharFrame()).toContain("AAPL - 1D");
|
|
710
|
+
|
|
711
|
+
await emitKeypress({ name: "a", sequence: "a" });
|
|
712
|
+
await flushFrames(3);
|
|
713
|
+
|
|
714
|
+
const loadingFrame = testSetup.captureCharFrame();
|
|
715
|
+
expect(pendingResolvers.length).toBeGreaterThan(0);
|
|
716
|
+
expect(loadingFrame).toContain("AAPL - 1D");
|
|
717
|
+
expect(loadingFrame).not.toContain("Loading chart...");
|
|
718
|
+
|
|
719
|
+
await emitKeypress({ name: "a", sequence: "a" });
|
|
720
|
+
await flushFrames(3);
|
|
721
|
+
expect(testSetup.captureCharFrame()).not.toContain("Loading chart...");
|
|
722
|
+
|
|
723
|
+
await act(async () => {
|
|
724
|
+
for (const resolve of pendingResolvers) {
|
|
725
|
+
resolve(fiveYearHistory);
|
|
726
|
+
}
|
|
727
|
+
await Promise.resolve();
|
|
728
|
+
});
|
|
729
|
+
await flushFrames(4);
|
|
730
|
+
expect(testSetup.captureCharFrame()).not.toContain("Loading chart...");
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test("keeps the last rendered chart while a manual detail window is loading", async () => {
|
|
734
|
+
const symbol = "AAPL";
|
|
735
|
+
const config = makeChartConfig(symbol);
|
|
736
|
+
config.layout.instances = config.layout.instances.map((instance) => ({
|
|
737
|
+
...instance,
|
|
738
|
+
settings: {
|
|
739
|
+
chartRangePreset: "1Y",
|
|
740
|
+
chartResolution: "1d",
|
|
741
|
+
},
|
|
742
|
+
}));
|
|
743
|
+
config.layouts = [{ name: "Default", layout: cloneLayout(config.layout) }];
|
|
744
|
+
const ticker = makeTicker(symbol);
|
|
745
|
+
const baseHistory = makeHistory(600, new Date(Date.UTC(2024, 0, 1)));
|
|
746
|
+
let holdDetailRequests = false;
|
|
747
|
+
const pendingResolvers: Array<(history: PricePoint[]) => void> = [];
|
|
748
|
+
const provider = {
|
|
749
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
750
|
+
getDetailedPriceHistory: async (
|
|
751
|
+
_requestSymbol,
|
|
752
|
+
_exchange,
|
|
753
|
+
startDate,
|
|
754
|
+
endDate,
|
|
755
|
+
) => {
|
|
756
|
+
if (holdDetailRequests) {
|
|
757
|
+
return new Promise<PricePoint[]>((resolve) => {
|
|
758
|
+
pendingResolvers.push(resolve);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
return baseHistory.filter((point) => {
|
|
762
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
763
|
+
return date >= startDate && date <= endDate;
|
|
764
|
+
});
|
|
765
|
+
},
|
|
766
|
+
} satisfies DataProvider;
|
|
767
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
768
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
769
|
+
setSharedDataProviderForTests(provider);
|
|
770
|
+
const financials: TickerFinancials = {
|
|
771
|
+
annualStatements: [],
|
|
772
|
+
quarterlyStatements: [],
|
|
773
|
+
priceHistory: baseHistory,
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
777
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
778
|
+
root = createRoot(testSetup.renderer);
|
|
779
|
+
act(() => {
|
|
780
|
+
root!.render(
|
|
781
|
+
<ChartHarness
|
|
782
|
+
config={config}
|
|
783
|
+
ticker={ticker}
|
|
784
|
+
financials={financials}
|
|
785
|
+
/>,
|
|
786
|
+
);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
await flushFrames(6);
|
|
790
|
+
expect(testSetup.captureCharFrame()).toContain("AAPL - 1D");
|
|
791
|
+
|
|
792
|
+
holdDetailRequests = true;
|
|
793
|
+
for (let index = 0; index < 5; index += 1) {
|
|
794
|
+
await emitKeypress({ name: "a", sequence: "a" });
|
|
795
|
+
}
|
|
796
|
+
await flushFrames(3);
|
|
797
|
+
await flushDebouncedChartRequests();
|
|
798
|
+
|
|
799
|
+
const loadingFrame = testSetup.captureCharFrame();
|
|
800
|
+
expect(pendingResolvers.length).toBeGreaterThan(0);
|
|
801
|
+
expect(loadingFrame).toContain("AAPL - 1D");
|
|
802
|
+
expect(loadingFrame).not.toContain("Loading chart...");
|
|
803
|
+
|
|
804
|
+
await act(async () => {
|
|
805
|
+
for (const resolve of pendingResolvers) {
|
|
806
|
+
resolve(baseHistory);
|
|
807
|
+
}
|
|
808
|
+
await Promise.resolve();
|
|
809
|
+
});
|
|
810
|
+
await flushFrames(4);
|
|
811
|
+
expect(testSetup.captureCharFrame()).not.toContain("Loading chart...");
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("keeps panning on fallback data while the preferred detail request is loading", async () => {
|
|
815
|
+
const symbol = "AAPL";
|
|
816
|
+
const config = makeChartConfig(symbol);
|
|
817
|
+
config.layout.instances = config.layout.instances.map((instance) => ({
|
|
818
|
+
...instance,
|
|
819
|
+
settings: {
|
|
820
|
+
chartRangePreset: "1Y",
|
|
821
|
+
chartResolution: "1d",
|
|
822
|
+
},
|
|
823
|
+
}));
|
|
824
|
+
config.layouts = [{ name: "Default", layout: cloneLayout(config.layout) }];
|
|
825
|
+
config.chartPreferences.defaultRenderMode = "candles";
|
|
826
|
+
const ticker = makeTicker(symbol);
|
|
827
|
+
const baseHistory = makeMonthlyHistory(84, new Date(Date.UTC(2019, 0, 1)));
|
|
828
|
+
const fallbackDetailHistory = makeHistory(900, new Date(Date.UTC(2023, 8, 1)));
|
|
829
|
+
const pendingOneDayResolvers: Array<(history: PricePoint[]) => void> = [];
|
|
830
|
+
let fallbackRequests = 0;
|
|
831
|
+
const provider = {
|
|
832
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
833
|
+
getDetailedPriceHistory: async (
|
|
834
|
+
_requestSymbol,
|
|
835
|
+
_exchange,
|
|
836
|
+
startDate,
|
|
837
|
+
endDate,
|
|
838
|
+
barSize,
|
|
839
|
+
) => {
|
|
840
|
+
if (barSize === "1d") {
|
|
841
|
+
return new Promise<PricePoint[]>((resolve) => {
|
|
842
|
+
pendingOneDayResolvers.push(resolve);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
if (barSize === "1wk") {
|
|
846
|
+
fallbackRequests += 1;
|
|
847
|
+
return fallbackDetailHistory.filter((point) => {
|
|
848
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
849
|
+
return date >= startDate && date <= endDate;
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
return [];
|
|
853
|
+
},
|
|
854
|
+
} satisfies DataProvider;
|
|
855
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
856
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
857
|
+
setSharedDataProviderForTests(provider);
|
|
858
|
+
const financials: TickerFinancials = {
|
|
859
|
+
annualStatements: [],
|
|
860
|
+
quarterlyStatements: [],
|
|
861
|
+
priceHistory: baseHistory,
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
865
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
866
|
+
root = createRoot(testSetup.renderer);
|
|
867
|
+
act(() => {
|
|
868
|
+
root!.render(
|
|
869
|
+
<ChartHarness
|
|
870
|
+
config={config}
|
|
871
|
+
ticker={ticker}
|
|
872
|
+
financials={financials}
|
|
873
|
+
/>,
|
|
874
|
+
);
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
await flushFrames(6);
|
|
878
|
+
await flushDebouncedChartRequests();
|
|
879
|
+
|
|
880
|
+
const firstFrame = testSetup.captureCharFrame();
|
|
881
|
+
expect(pendingOneDayResolvers.length).toBeGreaterThan(0);
|
|
882
|
+
expect(fallbackRequests).toBeGreaterThan(0);
|
|
883
|
+
expect(firstFrame).toContain("AAPL - 1D");
|
|
884
|
+
expect(firstFrame).toContain("showing 1W");
|
|
885
|
+
expect(firstFrame).toContain("updating");
|
|
886
|
+
expect(firstFrame).not.toContain("Loading chart...");
|
|
887
|
+
|
|
888
|
+
await emitKeypress({ name: "a", sequence: "a" });
|
|
889
|
+
await flushFrames(3);
|
|
890
|
+
await flushDebouncedChartRequests();
|
|
891
|
+
|
|
892
|
+
const pannedFrame = testSetup.captureCharFrame();
|
|
893
|
+
expect(pannedFrame).toContain("showing 1W");
|
|
894
|
+
expect(pannedFrame).not.toContain("Loading chart...");
|
|
895
|
+
expect(pannedFrame).not.toBe(firstFrame);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("falls back manual resolution without repeated input when finer resolutions are empty", async () => {
|
|
899
|
+
const symbol = "AAPL";
|
|
900
|
+
const config = makeChartConfig(symbol);
|
|
901
|
+
const ticker = makeTicker(symbol);
|
|
902
|
+
const baseHistory = makeHistory(400, new Date(Date.UTC(2025, 0, 1)));
|
|
903
|
+
const detailRequests: string[] = [];
|
|
904
|
+
const provider = {
|
|
905
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
906
|
+
getDetailedPriceHistory: async (
|
|
907
|
+
requestSymbol: string,
|
|
908
|
+
_exchange: string,
|
|
909
|
+
startDate: Date,
|
|
910
|
+
endDate: Date,
|
|
911
|
+
barSize: string,
|
|
912
|
+
) => {
|
|
913
|
+
detailRequests.push(barSize);
|
|
914
|
+
if (barSize !== "1d" && barSize !== "1wk" && barSize !== "1mo") {
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
return baseHistory.filter((point) => {
|
|
918
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
919
|
+
return date >= startDate && date <= endDate;
|
|
920
|
+
});
|
|
921
|
+
},
|
|
922
|
+
} satisfies DataProvider;
|
|
923
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
924
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
925
|
+
setSharedDataProviderForTests(provider);
|
|
926
|
+
const financials: TickerFinancials = {
|
|
927
|
+
annualStatements: [],
|
|
928
|
+
quarterlyStatements: [],
|
|
929
|
+
priceHistory: baseHistory,
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
933
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
934
|
+
root = createRoot(testSetup.renderer);
|
|
935
|
+
act(() => {
|
|
936
|
+
root!.render(
|
|
937
|
+
<ChartHarness
|
|
938
|
+
config={config}
|
|
939
|
+
ticker={ticker}
|
|
940
|
+
financials={financials}
|
|
941
|
+
/>,
|
|
942
|
+
);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
await flushFrames(6);
|
|
946
|
+
await emitKeypress({ name: "r", sequence: "r" });
|
|
947
|
+
await flushFrames(12);
|
|
948
|
+
await flushDebouncedChartRequests();
|
|
949
|
+
|
|
950
|
+
const frame = testSetup.captureCharFrame();
|
|
951
|
+
expect(detailRequests).toContain("1m");
|
|
952
|
+
expect(detailRequests).toContain("1d");
|
|
953
|
+
expect(frame).toContain("AAPL - 1M");
|
|
954
|
+
expect(frame).toContain("showing 1D");
|
|
955
|
+
expect(frame).not.toContain("No price history available.");
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
test("keeps updating the visible auto span when coarse resolution data stays sparse", async () => {
|
|
959
|
+
const symbol = "AAPL";
|
|
960
|
+
const config = makeChartConfig(symbol);
|
|
961
|
+
const ticker = makeTicker(symbol);
|
|
962
|
+
const baseHistory = makeMonthlyHistory(59, new Date(Date.UTC(2021, 5, 1)));
|
|
963
|
+
const dailyDetail = makeHistory(260, new Date(Date.UTC(2025, 7, 1)));
|
|
964
|
+
const intradayDetail = makeIntradayHistory(40, 1, new Date(Date.UTC(2026, 2, 1)));
|
|
965
|
+
const detailRequests: string[] = [];
|
|
966
|
+
const provider = {
|
|
967
|
+
...makeProvider({ [symbol]: baseHistory }),
|
|
968
|
+
getPriceHistory: async () => baseHistory,
|
|
969
|
+
getPriceHistoryForResolution: async () => baseHistory,
|
|
970
|
+
getDetailedPriceHistory: async (
|
|
971
|
+
requestSymbol: string,
|
|
972
|
+
_exchange: string,
|
|
973
|
+
startDate: Date,
|
|
974
|
+
endDate: Date,
|
|
975
|
+
barSize: string,
|
|
976
|
+
) => {
|
|
977
|
+
detailRequests.push(barSize);
|
|
978
|
+
const source = /(m|h)$/i.test(barSize) ? intradayDetail : dailyDetail;
|
|
979
|
+
return source.filter((point) => {
|
|
980
|
+
const date = point.date instanceof Date ? point.date : new Date(point.date);
|
|
981
|
+
return date >= startDate && date <= endDate;
|
|
982
|
+
});
|
|
983
|
+
},
|
|
984
|
+
} satisfies DataProvider;
|
|
985
|
+
sharedCoordinator = new MarketDataCoordinator(provider);
|
|
986
|
+
setSharedMarketDataCoordinator(sharedCoordinator);
|
|
987
|
+
setSharedDataProviderForTests(provider);
|
|
988
|
+
const financials: TickerFinancials = {
|
|
989
|
+
annualStatements: [],
|
|
990
|
+
quarterlyStatements: [],
|
|
991
|
+
priceHistory: baseHistory,
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
995
|
+
testSetup = await createTestRenderer({ width: 120, height: 32 });
|
|
996
|
+
root = createRoot(testSetup.renderer);
|
|
997
|
+
act(() => {
|
|
998
|
+
root!.render(
|
|
999
|
+
<ChartHarness
|
|
1000
|
+
config={config}
|
|
1001
|
+
ticker={ticker}
|
|
1002
|
+
financials={financials}
|
|
1003
|
+
/>,
|
|
1004
|
+
);
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
await flushFrames(6);
|
|
1008
|
+
const frames: string[] = [];
|
|
1009
|
+
for (let index = 0; index < 10; index += 1) {
|
|
1010
|
+
await emitKeypress({ name: "=", sequence: "=" });
|
|
1011
|
+
await flushFrames(4);
|
|
1012
|
+
frames.push(testSetup.captureCharFrame());
|
|
1013
|
+
}
|
|
1014
|
+
await flushDebouncedChartRequests();
|
|
1015
|
+
|
|
1016
|
+
expect(detailRequests).toContain("1d");
|
|
1017
|
+
expect(frames[6]).not.toBe(frames[7]);
|
|
1018
|
+
expect(frames[7]).not.toContain("view:");
|
|
288
1019
|
});
|
|
289
1020
|
});
|