gloomberb 0.4.1 → 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.
Files changed (207) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/app.tsx +168 -65
  4. package/src/cli/commands/ticker.ts +45 -15
  5. package/src/cli/helpers.ts +4 -2
  6. package/src/cli/registry.test.ts +42 -35
  7. package/src/cli.test.ts +4 -2
  8. package/src/components/chart/chart-control-hint.tsx +39 -0
  9. package/src/components/chart/chart-controller.test.ts +110 -0
  10. package/src/components/chart/chart-controller.ts +364 -2
  11. package/src/components/chart/chart-data.ts +177 -29
  12. package/src/components/chart/chart-indicator-selector.tsx +53 -0
  13. package/src/components/chart/chart-pane-settings.ts +20 -0
  14. package/src/components/chart/chart-renderer.ts +220 -30
  15. package/src/components/chart/chart-resolution.test.ts +67 -0
  16. package/src/components/chart/chart-resolution.ts +74 -4
  17. package/src/components/chart/chart-scroll.test.ts +69 -0
  18. package/src/components/chart/chart-scroll.ts +104 -0
  19. package/src/components/chart/chart-types.ts +12 -0
  20. package/src/components/chart/chart-viewport.test.ts +5 -1
  21. package/src/components/chart/chart-viewport.ts +1 -1
  22. package/src/components/chart/chart.test.ts +249 -10
  23. package/src/components/chart/comparison-chart-renderer.test.ts +71 -1
  24. package/src/components/chart/comparison-chart-renderer.ts +40 -3
  25. package/src/components/chart/comparison-stock-chart.tsx +218 -123
  26. package/src/components/chart/indicators/bands.test.ts +50 -0
  27. package/src/components/chart/indicators/bands.ts +29 -0
  28. package/src/components/chart/indicators/moving-averages.test.ts +54 -0
  29. package/src/components/chart/indicators/moving-averages.ts +42 -0
  30. package/src/components/chart/indicators/options.test.ts +32 -0
  31. package/src/components/chart/indicators/options.ts +107 -0
  32. package/src/components/chart/indicators/oscillators.test.ts +79 -0
  33. package/src/components/chart/indicators/oscillators.ts +92 -0
  34. package/src/components/chart/indicators/types.ts +36 -0
  35. package/src/components/chart/native/chart-rasterizer.ts +51 -0
  36. package/src/components/chart/native/kitty.test.ts +68 -0
  37. package/src/components/chart/stock-chart-renderer-switch.test.tsx +740 -9
  38. package/src/components/chart/stock-chart.test.ts +239 -0
  39. package/src/components/chart/stock-chart.tsx +2257 -372
  40. package/src/components/command-bar/command-bar.test.tsx +337 -5
  41. package/src/components/command-bar/command-bar.tsx +292 -120
  42. package/src/components/command-bar/helpers.ts +1 -1
  43. package/src/components/command-bar/root-shortcuts.ts +33 -4
  44. package/src/components/command-bar/view-model.test.ts +3 -3
  45. package/src/components/command-bar/view-model.ts +4 -4
  46. package/src/components/command-bar/workflow-ops.ts +10 -0
  47. package/src/components/layout/header.tsx +2 -1
  48. package/src/components/layout/shell.test.tsx +186 -27
  49. package/src/components/layout/shell.tsx +5 -4
  50. package/src/components/layout/status-bar.test.tsx +16 -7
  51. package/src/components/layout/status-bar.tsx +4 -1
  52. package/src/components/markdown-editor.tsx +1 -0
  53. package/src/components/pane-settings-dialog.tsx +13 -152
  54. package/src/components/pane-template-wizard.tsx +1 -0
  55. package/src/components/price-selector-dialog.tsx +12 -7
  56. package/src/components/ticker-badge-text.test.tsx +50 -0
  57. package/src/components/ticker-badge-text.tsx +17 -2
  58. package/src/components/ticker-badge.tsx +2 -2
  59. package/src/components/ticker-list-table.tsx +54 -1
  60. package/src/components/toggle-list.tsx +15 -2
  61. package/src/components/ui/data-table.tsx +60 -3
  62. package/src/components/ui/external-link.tsx +48 -11
  63. package/src/components/ui/fields.tsx +2 -1
  64. package/src/components/ui/index.ts +8 -1
  65. package/src/components/ui/multi-select-chips.tsx +69 -0
  66. package/src/components/ui/multi-select-dialog.tsx +267 -0
  67. package/src/components/ui/multi-select.ts +78 -0
  68. package/src/components/ui/ui.test.tsx +343 -1
  69. package/src/data/plugin-state-store.ts +30 -19
  70. package/src/data/resource-store.ts +133 -103
  71. package/src/data/session-store.ts +19 -12
  72. package/src/data/sqlite-cache.test.ts +37 -2
  73. package/src/data/sqlite-database.ts +8 -2
  74. package/src/data/sqlite-retry.test.ts +63 -0
  75. package/src/data/sqlite-retry.ts +66 -0
  76. package/src/data/ticker-store.ts +29 -18
  77. package/src/index.tsx +6 -1
  78. package/src/market-data/coordinator.test.ts +114 -5
  79. package/src/market-data/coordinator.ts +70 -1
  80. package/src/market-data/hooks.test.tsx +73 -2
  81. package/src/market-data/hooks.tsx +16 -4
  82. package/src/notifications/app-notifier.test.ts +177 -0
  83. package/src/notifications/app-notifier.ts +241 -0
  84. package/src/pane-settings.test.ts +2 -0
  85. package/src/plugins/builtin/ai/index.test.tsx +1 -1
  86. package/src/plugins/builtin/ai/screener-pane.test.tsx +2 -0
  87. package/src/plugins/builtin/ai/screener-pane.tsx +1 -0
  88. package/src/plugins/builtin/ai/ticker-context.ts +3 -2
  89. package/src/plugins/builtin/alerts/alert-engine.test.ts +90 -0
  90. package/src/plugins/builtin/alerts/alert-engine.ts +55 -0
  91. package/src/plugins/builtin/alerts/index.test.tsx +357 -0
  92. package/src/plugins/builtin/alerts/index.tsx +519 -0
  93. package/src/plugins/builtin/alerts/types.ts +14 -0
  94. package/src/plugins/builtin/analytics/index.test.tsx +300 -0
  95. package/src/plugins/builtin/analytics/index.tsx +775 -0
  96. package/src/plugins/builtin/analytics/metrics.test.ts +125 -0
  97. package/src/plugins/builtin/analytics/metrics.ts +155 -0
  98. package/src/plugins/builtin/ask-ai.test.tsx +2 -0
  99. package/src/plugins/builtin/chat-controller.test.ts +62 -18
  100. package/src/plugins/builtin/chat-controller.ts +23 -12
  101. package/src/plugins/builtin/chat.test.tsx +715 -4
  102. package/src/plugins/builtin/chat.tsx +415 -119
  103. package/src/plugins/builtin/comparison-chart.test.tsx +97 -57
  104. package/src/plugins/builtin/comparison-chart.tsx +1 -2
  105. package/src/plugins/builtin/correlation/compute.test.ts +147 -0
  106. package/src/plugins/builtin/correlation/compute.ts +115 -0
  107. package/src/plugins/builtin/correlation/index.tsx +385 -0
  108. package/src/plugins/builtin/correlation/plugin.test.ts +44 -0
  109. package/src/plugins/builtin/correlation/settings.test.ts +34 -0
  110. package/src/plugins/builtin/correlation/settings.ts +95 -0
  111. package/src/plugins/builtin/debug.tsx +36 -50
  112. package/src/plugins/builtin/earnings/earnings-cache.test.ts +181 -0
  113. package/src/plugins/builtin/earnings/earnings-cache.ts +136 -0
  114. package/src/plugins/builtin/earnings/earnings-data.test.ts +59 -0
  115. package/src/plugins/builtin/earnings/earnings-data.ts +50 -0
  116. package/src/plugins/builtin/earnings/index.tsx +337 -0
  117. package/src/plugins/builtin/econ/calendar-source.test.ts +219 -0
  118. package/src/plugins/builtin/econ/calendar-source.ts +121 -0
  119. package/src/plugins/builtin/econ/fred-client.test.ts +63 -0
  120. package/src/plugins/builtin/econ/fred-client.ts +79 -0
  121. package/src/plugins/builtin/econ/fred-series-map.ts +88 -0
  122. package/src/plugins/builtin/econ/index.tsx +999 -0
  123. package/src/plugins/builtin/econ/types.ts +13 -0
  124. package/src/plugins/builtin/fred-settings.ts +10 -0
  125. package/src/plugins/builtin/fx-matrix/index.tsx +178 -0
  126. package/src/plugins/builtin/fx-matrix/pairs.ts +30 -0
  127. package/src/plugins/builtin/help.test.tsx +1 -5
  128. package/src/plugins/builtin/help.tsx +1 -16
  129. package/src/plugins/builtin/insider/index.tsx +417 -0
  130. package/src/plugins/builtin/insider/insider-data.test.ts +55 -0
  131. package/src/plugins/builtin/insider/insider-data.ts +49 -0
  132. package/src/plugins/builtin/layout-manager.tsx +24 -16
  133. package/src/plugins/builtin/market-movers/index.tsx +573 -0
  134. package/src/plugins/builtin/market-movers/screener.test.ts +143 -0
  135. package/src/plugins/builtin/market-movers/screener.ts +105 -0
  136. package/src/plugins/builtin/options.tsx +4 -3
  137. package/src/plugins/builtin/portfolio-list/cli/commands.ts +11 -6
  138. package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -3
  139. package/src/plugins/builtin/portfolio-list/header.tsx +3 -2
  140. package/src/plugins/builtin/portfolio-list/metrics.ts +22 -11
  141. package/src/plugins/builtin/portfolio-list/pane.tsx +3 -9
  142. package/src/plugins/builtin/portfolio-list/settings.ts +9 -0
  143. package/src/plugins/builtin/portfolio-list/table.tsx +3 -0
  144. package/src/plugins/builtin/portfolio-list.test.tsx +3 -1
  145. package/src/plugins/builtin/portfolio-metrics.test.ts +13 -1
  146. package/src/plugins/builtin/sec.tsx +114 -7
  147. package/src/plugins/builtin/sectors/index.tsx +359 -0
  148. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  149. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +75 -13
  150. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +316 -80
  151. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +4 -6
  152. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +17 -12
  153. package/src/plugins/builtin/ticker-detail.test.tsx +3 -3
  154. package/src/plugins/builtin/world-indices/index.tsx +410 -0
  155. package/src/plugins/builtin/world-indices/indices.ts +52 -0
  156. package/src/plugins/builtin/yield-curve/index.tsx +242 -0
  157. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +212 -0
  158. package/src/plugins/builtin/yield-curve/treasury-data.ts +107 -0
  159. package/src/plugins/catalog.ts +22 -0
  160. package/src/plugins/gridlock-notification.ts +15 -0
  161. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  162. package/src/plugins/ibkr/index.test.tsx +1 -1
  163. package/src/plugins/ibkr/index.tsx +1 -1
  164. package/src/plugins/ibkr/trade-tab.tsx +23 -5
  165. package/src/plugins/ibkr/trade-utils.test.ts +1 -1
  166. package/src/plugins/ibkr/trade-utils.ts +7 -6
  167. package/src/plugins/ibkr/trading-pane.tsx +8 -7
  168. package/src/plugins/plugin-runtime.test.tsx +2 -0
  169. package/src/plugins/plugin-runtime.tsx +10 -0
  170. package/src/plugins/prediction-markets/controller-data.ts +8 -0
  171. package/src/plugins/prediction-markets/detail/shared.tsx +7 -6
  172. package/src/plugins/prediction-markets/plugin.test.tsx +48 -1
  173. package/src/plugins/prediction-markets/services/fetch.ts +13 -7
  174. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  175. package/src/plugins/prediction-markets/services/polymarket-ws.ts +20 -0
  176. package/src/plugins/prediction-markets/test-helpers.tsx +2 -0
  177. package/src/plugins/registry.ts +22 -13
  178. package/src/sources/provider-router.test.ts +3 -2
  179. package/src/sources/sec-edgar.ts +13 -1
  180. package/src/sources/yahoo-finance.ts +75 -1
  181. package/src/state/app-context-selector.test.tsx +17 -1
  182. package/src/state/app-context.test.tsx +24 -0
  183. package/src/state/app-context.tsx +29 -6
  184. package/src/theme/colors.ts +5 -0
  185. package/src/theme/themes.ts +25 -0
  186. package/src/types/config.test.ts +107 -0
  187. package/src/types/config.ts +30 -1
  188. package/src/types/data-provider.ts +13 -0
  189. package/src/types/plugin.ts +35 -1
  190. package/src/utils/format.test.ts +11 -1
  191. package/src/utils/format.ts +86 -8
  192. package/src/utils/inline-content-tokenizer.test.ts +22 -0
  193. package/src/utils/inline-content-tokenizer.ts +76 -0
  194. package/src/utils/link-tokenizer.test.ts +27 -0
  195. package/src/utils/link-tokenizer.ts +93 -0
  196. package/src/utils/market-format.test.ts +109 -0
  197. package/src/utils/market-format.ts +299 -0
  198. package/src/utils/market-status.ts +3 -2
  199. package/src/utils/selection-clipboard.test.ts +43 -0
  200. package/src/utils/selection-clipboard.ts +88 -0
  201. package/src/utils/terminal-input-reset.test.ts +50 -0
  202. package/src/utils/terminal-input-reset.ts +24 -0
  203. package/src/utils/throttled-fetch.test.ts +145 -0
  204. package/src/utils/throttled-fetch.ts +212 -0
  205. package/src/utils/ticker-tokenizer.test.ts +31 -0
  206. package/src/utils/ticker-tokenizer.ts +7 -42
  207. 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(length: number): PricePoint[] {
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(Date.UTC(2024, 0, index + 1)),
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 - 1W");
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).not.toContain("AAPL - 1W");
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
  });