gloomberb 0.10.2 → 0.10.3

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.
@@ -2,10 +2,14 @@ import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act, useState } from "react";
3
3
  import { testRender } from "../../renderers/opentui/test-utils";
4
4
  import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../market-data/coordinator";
5
- import { useQuoteStreaming } from "./quote-streaming";
5
+ import { createTestDataProvider } from "../../test-support/data-provider";
6
+ import { useLiveQuoteEntries, useQuoteStreaming } from "./quote-streaming";
6
7
 
7
8
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
8
9
  let bumpHarness: (() => void) | null = null;
10
+ let updateLiveTargets: ((symbol: string) => void) | null = null;
11
+ let updateFreshnessScope: ((scope: string) => void) | null = null;
12
+ let observedSubscriptionStartedAt = 0;
9
13
 
10
14
  function QuoteStreamingHarness() {
11
15
  const [tick, setTick] = useState(0);
@@ -19,6 +23,18 @@ function QuoteStreamingHarness() {
19
23
  return <text>{String(tick)}</text>;
20
24
  }
21
25
 
26
+ function LiveQuoteFreshnessHarness() {
27
+ const [symbol, setSymbol] = useState("AAPL");
28
+ const [freshnessScopeKey, setFreshnessScopeKey] = useState("expiration-1");
29
+ updateLiveTargets = setSymbol;
30
+ updateFreshnessScope = setFreshnessScopeKey;
31
+ observedSubscriptionStartedAt = useLiveQuoteEntries(
32
+ [{ symbol, exchange: "OPTIONS" }],
33
+ { freshnessScopeKey },
34
+ ).subscriptionStartedAt;
35
+ return <text>{symbol}</text>;
36
+ }
37
+
22
38
  afterEach(async () => {
23
39
  if (testSetup) {
24
40
  await act(async () => {
@@ -27,6 +43,9 @@ afterEach(async () => {
27
43
  testSetup = undefined;
28
44
  }
29
45
  bumpHarness = null;
46
+ updateLiveTargets = null;
47
+ updateFreshnessScope = null;
48
+ observedSubscriptionStartedAt = 0;
30
49
  setSharedMarketDataCoordinator(null);
31
50
  });
32
51
 
@@ -67,4 +86,42 @@ describe("useQuoteStreaming", () => {
67
86
  expect(subscribeCalls).toBe(1);
68
87
  expect(unsubscribeCalls).toBe(0);
69
88
  });
89
+
90
+ test("keeps quote freshness stable while targets move within one surface", async () => {
91
+ const originalDateNow = Date.now;
92
+ let now = 100;
93
+ Date.now = () => now;
94
+ setSharedMarketDataCoordinator(new MarketDataCoordinator(createTestDataProvider({
95
+ subscribeQuotes: () => () => {},
96
+ })));
97
+
98
+ try {
99
+ testSetup = await testRender(<LiveQuoteFreshnessHarness />, {
100
+ width: 20,
101
+ height: 1,
102
+ });
103
+ await act(async () => {
104
+ await testSetup!.renderOnce();
105
+ });
106
+ expect(observedSubscriptionStartedAt).toBe(100);
107
+
108
+ now = 200;
109
+ await act(async () => {
110
+ updateLiveTargets?.("MSFT");
111
+ await Promise.resolve();
112
+ await testSetup!.renderOnce();
113
+ });
114
+ expect(observedSubscriptionStartedAt).toBe(100);
115
+
116
+ now = 300;
117
+ await act(async () => {
118
+ updateFreshnessScope?.("expiration-2");
119
+ await Promise.resolve();
120
+ await testSetup!.renderOnce();
121
+ });
122
+ expect(observedSubscriptionStartedAt).toBe(300);
123
+ } finally {
124
+ Date.now = originalDateNow;
125
+ }
126
+ });
70
127
  });
@@ -101,7 +101,10 @@ export function useQuoteStreaming(targets: QuoteSubscriptionTarget[]): void {
101
101
  * Subscribe to a bounded target set and observe the coordinator entries filled
102
102
  * by that stream. This intentionally avoids per-symbol HTTP quote loads.
103
103
  */
104
- export function useLiveQuoteEntries(targets: QuoteSubscriptionTarget[]): {
104
+ export function useLiveQuoteEntries(
105
+ targets: QuoteSubscriptionTarget[],
106
+ options: { freshnessScopeKey?: string } = {},
107
+ ): {
105
108
  entries: Map<string, QueryEntry<Quote>>;
106
109
  freshnessNow: number;
107
110
  subscriptionStartedAt: number;
@@ -115,7 +118,7 @@ export function useLiveQuoteEntries(targets: QuoteSubscriptionTarget[]): {
115
118
  .map((target) => buildQuoteStreamSubscriptionKey(target))
116
119
  .sort()
117
120
  .join("\u001f");
118
- const subscriptionKey = `${appActive ? "active" : "inactive"}\u001f${targetKey}`;
121
+ const subscriptionKey = `${appActive ? "active" : "inactive"}\u001f${options.freshnessScopeKey ?? targetKey}`;
119
122
  const subscriptionRef = useRef({
120
123
  key: subscriptionKey,
121
124
  startedAt: Date.now(),