gloomberb 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +0 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +0 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +0 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/index.test.ts +0 -10
- package/src/market-data/coordinator/index.ts +1 -18
- package/src/market-data/coordinator/quotes.ts +0 -6
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/resolution.ts +2 -2
- package/src/market-data/request-types.ts +0 -5
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/form-components.tsx +0 -120
- package/src/plugins/builtin/account-management/model.ts +3 -27
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +2 -1
- package/src/plugins/builtin/chat/content/index.tsx +1 -2
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -1
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/sidebar.test.tsx +15 -5
- package/src/plugins/builtin/chat/test-harness.tsx +2 -2
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +1 -24
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +44 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/financial-routes.ts +25 -3
- package/src/sources/provider-router/index.test.ts +167 -0
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +13 -8
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +21 -17
- package/src/test-support/data-provider.ts +0 -1
- package/src/ui/host.tsx +2 -1
- package/src/ui/index.tsx +1 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -3,7 +3,6 @@ import { TextAttributes } from "../../../../../ui";
|
|
|
3
3
|
import {
|
|
4
4
|
DataTableStackView,
|
|
5
5
|
TickerBadgeList,
|
|
6
|
-
activeStackIndex,
|
|
7
6
|
sortStackItems,
|
|
8
7
|
type DataTableCell,
|
|
9
8
|
type DataTableColumn,
|
|
@@ -165,23 +164,13 @@ export function NewsArticleStackView({
|
|
|
165
164
|
[articles, sortPreference],
|
|
166
165
|
);
|
|
167
166
|
const selectedIdx = sortedArticles.findIndex((article) => article.id === selectedArticleId);
|
|
168
|
-
const activeIdx = activeStackIndex(sortedArticles.length, selectedIdx);
|
|
169
167
|
const columns = useMemo(() => buildColumns(width, columnIds), [columnIds, width]);
|
|
170
168
|
|
|
171
|
-
const selectIndex = useCallback((index: number) => {
|
|
172
|
-
setSelectedArticleId(sortedArticles[index]?.id ?? null);
|
|
173
|
-
}, [setSelectedArticleId, sortedArticles]);
|
|
174
|
-
|
|
175
169
|
const openArticle = useCallback((article: MarketNewsItem) => {
|
|
176
170
|
onArticleRead?.(article.id);
|
|
177
171
|
onOpenArticle(article);
|
|
178
172
|
}, [onArticleRead, onOpenArticle]);
|
|
179
173
|
|
|
180
|
-
const openIndex = useCallback((index: number) => {
|
|
181
|
-
const article = sortedArticles[index];
|
|
182
|
-
if (article) openArticle(article);
|
|
183
|
-
}, [openArticle, sortedArticles]);
|
|
184
|
-
|
|
185
174
|
useEffect(() => {
|
|
186
175
|
if (sortedArticles.length === 0) {
|
|
187
176
|
if (selectedArticleId !== null) setSelectedArticleId(null);
|
|
@@ -123,8 +123,8 @@ function OptionsHarness({
|
|
|
123
123
|
async function renderSettled() {
|
|
124
124
|
for (let i = 0; i < 4; i += 1) {
|
|
125
125
|
await act(async () => {
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
126
127
|
await testSetup!.renderOnce();
|
|
127
|
-
await Promise.resolve();
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -139,43 +139,6 @@ afterEach(async () => {
|
|
|
139
139
|
setSharedMarketDataCoordinator(null);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
test("renders the options chain with the shared table columns", async () => {
|
|
143
|
-
const provider = createTestDataProvider({
|
|
144
|
-
getOptionsChain: async () => makeChain(),
|
|
145
|
-
});
|
|
146
|
-
setSharedMarketDataCoordinator(new MarketDataCoordinator(provider));
|
|
147
|
-
|
|
148
|
-
await act(async () => {
|
|
149
|
-
testSetup = await testRender(<OptionsHarness ticker={makeTicker("AAPL")} />, {
|
|
150
|
-
width: 124,
|
|
151
|
-
height: 16,
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await renderSettled();
|
|
156
|
-
|
|
157
|
-
const frame = testSetup!.captureCharFrame();
|
|
158
|
-
expect(frame).toContain("C LAST");
|
|
159
|
-
expect(frame).toContain("C IV");
|
|
160
|
-
expect(frame).toContain("STRIKE");
|
|
161
|
-
expect(frame).toContain("P IV");
|
|
162
|
-
expect(frame).toContain("P LAST");
|
|
163
|
-
expect(frame).toContain("100");
|
|
164
|
-
expect(frame).toContain("101");
|
|
165
|
-
|
|
166
|
-
const headerLine = frame.split("\n").find((line) => line.includes("C OI") && line.includes("STRIKE")) ?? "";
|
|
167
|
-
expect(headerLine.indexOf("C OI")).toBeLessThan(headerLine.indexOf("C VOL"));
|
|
168
|
-
expect(headerLine.indexOf("C VOL")).toBeLessThan(headerLine.indexOf("C LAST"));
|
|
169
|
-
expect(headerLine.indexOf("C LAST")).toBeLessThan(headerLine.indexOf("C IV"));
|
|
170
|
-
expect(headerLine.indexOf("C IV")).toBeLessThan(headerLine.indexOf("C BID"));
|
|
171
|
-
expect(headerLine.indexOf("C BID")).toBeLessThan(headerLine.indexOf("C ASK"));
|
|
172
|
-
expect(headerLine.indexOf("P BID")).toBeLessThan(headerLine.indexOf("P ASK"));
|
|
173
|
-
expect(headerLine.indexOf("P ASK")).toBeLessThan(headerLine.indexOf("P IV"));
|
|
174
|
-
expect(headerLine.indexOf("P IV")).toBeLessThan(headerLine.indexOf("P LAST"));
|
|
175
|
-
expect(headerLine.indexOf("P LAST")).toBeLessThan(headerLine.indexOf("P VOL"));
|
|
176
|
-
expect(headerLine.indexOf("P VOL")).toBeLessThan(headerLine.indexOf("P OI"));
|
|
177
|
-
});
|
|
178
|
-
|
|
179
142
|
test("defaults the table around the nearest strike to the current quote", async () => {
|
|
180
143
|
const strikes = Array.from({ length: 25 }, (_, index) => 50 + index * 5);
|
|
181
144
|
const provider = createTestDataProvider({
|
|
@@ -29,6 +29,7 @@ type PluginGroupOptions = Pick<
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
function createPluginGroup(options: PluginGroupOptions): GloomPlugin {
|
|
32
|
+
let startedPlugins: GloomPlugin[] = [];
|
|
32
33
|
return {
|
|
33
34
|
id: options.id,
|
|
34
35
|
name: options.name,
|
|
@@ -42,17 +43,30 @@ function createPluginGroup(options: PluginGroupOptions): GloomPlugin {
|
|
|
42
43
|
capabilities: options.plugins.flatMap((plugin) => plugin.capabilities ?? []),
|
|
43
44
|
|
|
44
45
|
async setup(ctx) {
|
|
46
|
+
startedPlugins = [];
|
|
45
47
|
await options.setup?.(ctx);
|
|
46
48
|
for (const plugin of options.plugins) {
|
|
49
|
+
startedPlugins.push(plugin);
|
|
47
50
|
await plugin.setup?.(ctx);
|
|
48
51
|
}
|
|
49
52
|
},
|
|
50
53
|
|
|
51
54
|
dispose() {
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
let firstError: unknown;
|
|
56
|
+
for (const plugin of startedPlugins.reverse()) {
|
|
57
|
+
try {
|
|
58
|
+
plugin.dispose?.();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
firstError ??= error;
|
|
61
|
+
}
|
|
54
62
|
}
|
|
55
|
-
|
|
63
|
+
startedPlugins = [];
|
|
64
|
+
try {
|
|
65
|
+
options.dispose?.();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
firstError ??= error;
|
|
68
|
+
}
|
|
69
|
+
if (firstError) throw firstError;
|
|
56
70
|
},
|
|
57
71
|
};
|
|
58
72
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ColumnConfig } from "../../../types/config";
|
|
2
|
-
import type { AnalystResearchData, CorporateActionsData, TickerFinancials } from "../../../types/financials";
|
|
2
|
+
import type { AnalystResearchData, CorporateActionsData, MarketState, TickerFinancials } from "../../../types/financials";
|
|
3
3
|
import type { EarningsEvent } from "../../../types/data-provider";
|
|
4
4
|
import type { TickerRecord } from "../../../types/ticker";
|
|
5
5
|
import { priceColor } from "../../../theme/colors";
|
|
@@ -13,7 +13,13 @@ import {
|
|
|
13
13
|
formatSignedMarketPrice,
|
|
14
14
|
type MarketFormatOptions,
|
|
15
15
|
} from "../../../market-data/market/format";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
getActiveQuoteDisplay,
|
|
18
|
+
marketChangeColor,
|
|
19
|
+
marketPriceColor,
|
|
20
|
+
marketStateDot,
|
|
21
|
+
type ActiveQuoteDisplay,
|
|
22
|
+
} from "../../../market-data/market/status";
|
|
17
23
|
import { formatOptionTicker } from "../../../utils/options";
|
|
18
24
|
import { PRICE_SPARKLINE_COLUMN_ID } from "../../../components/price-sparkline/view";
|
|
19
25
|
import {
|
|
@@ -159,13 +165,14 @@ function getActiveMarketValue(
|
|
|
159
165
|
export function resolvePortfolioPriceValue(
|
|
160
166
|
activeQuote: ActiveQuoteDisplay | null,
|
|
161
167
|
brokerMarkPrice: number | undefined,
|
|
162
|
-
formatOptions: MarketFormatOptions,
|
|
168
|
+
formatOptions: MarketFormatOptions = {},
|
|
163
169
|
maxWidth?: number,
|
|
170
|
+
marketState?: MarketState,
|
|
164
171
|
): { text: string; color?: string } {
|
|
165
172
|
if (activeQuote) {
|
|
166
173
|
return {
|
|
167
174
|
text: formatMarketPrice(activeQuote.price, { ...formatOptions, maxWidth }),
|
|
168
|
-
color:
|
|
175
|
+
color: marketPriceColor(activeQuote.change, marketState),
|
|
169
176
|
};
|
|
170
177
|
}
|
|
171
178
|
if (brokerMarkPrice != null) {
|
|
@@ -229,12 +236,12 @@ export function getColumnValue(
|
|
|
229
236
|
case "tags":
|
|
230
237
|
return { text: ticker.metadata.tags.length > 0 ? ticker.metadata.tags.join(",") : "—" };
|
|
231
238
|
case "price":
|
|
232
|
-
return resolvePortfolioPriceValue(activeQuote, brokerMarkPrice, formatOptions, col.width);
|
|
239
|
+
return resolvePortfolioPriceValue(activeQuote, brokerMarkPrice, formatOptions, col.width, quote?.marketState);
|
|
233
240
|
case "change":
|
|
234
241
|
if (!activeQuote) return { text: "—" };
|
|
235
242
|
return {
|
|
236
243
|
text: formatSignedMarketPrice(activeQuote.change, { ...formatOptions, maxWidth: col.width }),
|
|
237
|
-
color:
|
|
244
|
+
color: marketChangeColor(activeQuote.change, quote?.marketState),
|
|
238
245
|
};
|
|
239
246
|
case "bid":
|
|
240
247
|
return { text: quote?.bid != null ? formatMarketPrice(quote.bid, { ...formatOptions, maxWidth: col.width }) : "—" };
|
|
@@ -258,8 +265,8 @@ export function getColumnValue(
|
|
|
258
265
|
}
|
|
259
266
|
case "change_pct":
|
|
260
267
|
return activeQuote
|
|
261
|
-
? { text: formatPercentRaw(activeQuote.changePercent), color:
|
|
262
|
-
: { text: quote ? formatPercentRaw(quote.changePercent) : "—", color: quote ?
|
|
268
|
+
? { text: formatPercentRaw(activeQuote.changePercent), color: marketChangeColor(activeQuote.changePercent, quote?.marketState) }
|
|
269
|
+
: { text: quote ? formatPercentRaw(quote.changePercent) : "—", color: quote ? marketChangeColor(quote.changePercent, quote.marketState) : undefined };
|
|
263
270
|
case "volume":
|
|
264
271
|
return { text: finiteNumber(quote?.volume) ? formatCompact(quote.volume) : "—" };
|
|
265
272
|
case "dollar_volume": {
|
|
@@ -5,7 +5,7 @@ import type { BrokerAccount } from "../../../types/trading";
|
|
|
5
5
|
import type { TickerRecord } from "../../../types/ticker";
|
|
6
6
|
import type { PortfolioSummaryTotals } from "./metrics";
|
|
7
7
|
import { buildPortfolioSummarySegments } from "./summary";
|
|
8
|
-
import {
|
|
8
|
+
import { shouldToggleCashMarginDrawer } from ".";
|
|
9
9
|
import { needsVisibleQuoteWatchdogRefresh, selectQuoteWarmupTickers, selectStreamTickers } from "./pane/data";
|
|
10
10
|
import { buildPortfolioPaneSettingsDef, getPortfolioPaneSettings } from "./settings";
|
|
11
11
|
|
|
@@ -221,29 +221,6 @@ describe("visible quote refresh predicates", () => {
|
|
|
221
221
|
});
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
-
describe("portfolio list pane templates", () => {
|
|
225
|
-
test("create panes with default all-collection settings", async () => {
|
|
226
|
-
const config = createDefaultConfig("/tmp/gloomberb-portfolio-list-template");
|
|
227
|
-
const context = {
|
|
228
|
-
config,
|
|
229
|
-
layout: config.layout,
|
|
230
|
-
focusedPaneId: "portfolio-list:main",
|
|
231
|
-
activeTicker: null,
|
|
232
|
-
activeCollectionId: "watchlist",
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
for (const templateId of ["new-collection-pane", "new-watchlist-pane"]) {
|
|
236
|
-
const template = portfolioListPlugin.paneTemplates?.find((entry) => entry.id === templateId);
|
|
237
|
-
const instance = await template?.createInstance?.(context);
|
|
238
|
-
expect(instance).toEqual({ params: { collectionId: "watchlist" } });
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const portfolioTemplate = portfolioListPlugin.paneTemplates?.find((entry) => entry.id === "new-portfolio-pane");
|
|
242
|
-
const portfolioInstance = await portfolioTemplate?.createInstance?.(context);
|
|
243
|
-
expect(portfolioInstance).toEqual({ params: { collectionId: "main" } });
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
|
|
247
224
|
describe("portfolio list pane settings", () => {
|
|
248
225
|
test("exposes view mode only for portfolio collections", () => {
|
|
249
226
|
const config = createDefaultConfig("/tmp/gloomberb-portfolio-list-settings");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { CollectionSortPreference } from "../../../state/app/context";
|
|
3
3
|
import type { ColumnConfig } from "../../../types/config";
|
|
4
|
-
import type { TickerFinancials } from "../../../types/financials";
|
|
4
|
+
import type { Quote, TickerFinancials } from "../../../types/financials";
|
|
5
5
|
import type { TickerRecord } from "../../../types/ticker";
|
|
6
|
+
import { blendHex, colors } from "../../../theme/colors";
|
|
6
7
|
import {
|
|
7
8
|
calculatePortfolioSummaryTotals,
|
|
8
9
|
getColumnValue,
|
|
@@ -29,8 +30,15 @@ function createTicker(overrides: Partial<TickerRecord["metadata"]> = {}): Ticker
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function createFinancials(
|
|
33
|
+
function createFinancials(
|
|
34
|
+
overrides: Omit<Partial<TickerFinancials>, "quote"> & { quote?: Partial<Quote> } = {},
|
|
35
|
+
): TickerFinancials {
|
|
36
|
+
const { quote: quoteOverrides, ...financialOverrides } = overrides;
|
|
33
37
|
return {
|
|
38
|
+
annualStatements: [],
|
|
39
|
+
quarterlyStatements: [],
|
|
40
|
+
priceHistory: [],
|
|
41
|
+
...financialOverrides,
|
|
34
42
|
quote: {
|
|
35
43
|
symbol: "AAPL",
|
|
36
44
|
price: 120,
|
|
@@ -39,12 +47,8 @@ function createFinancials(overrides: Partial<TickerFinancials> = {}): TickerFina
|
|
|
39
47
|
changePercent: 4.35,
|
|
40
48
|
previousClose: 115,
|
|
41
49
|
lastUpdated: 1_700_000_000_000,
|
|
42
|
-
...
|
|
50
|
+
...quoteOverrides,
|
|
43
51
|
},
|
|
44
|
-
annualStatements: [],
|
|
45
|
-
quarterlyStatements: [],
|
|
46
|
-
priceHistory: [],
|
|
47
|
-
...overrides,
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -82,6 +86,39 @@ describe("portfolio-metrics", () => {
|
|
|
82
86
|
});
|
|
83
87
|
});
|
|
84
88
|
|
|
89
|
+
test("mutes completed-session prices and changes without discarding their direction", () => {
|
|
90
|
+
const ticker = createTicker({
|
|
91
|
+
positions: [{ portfolio: "main", shares: 10, avgCost: 100, broker: "manual" }],
|
|
92
|
+
});
|
|
93
|
+
const priceColumn: ColumnConfig = { id: "price", label: "LAST", width: 10, align: "right" };
|
|
94
|
+
const changeColumn: ColumnConfig = { id: "change", label: "CHG", width: 8, align: "right" };
|
|
95
|
+
const changePctColumn: ColumnConfig = { id: "change_pct", label: "CHG%", width: 8, align: "right" };
|
|
96
|
+
const financials = createFinancials({ quote: { marketState: "CLOSED" } });
|
|
97
|
+
const mutedPositive = blendHex(colors.positive, colors.textDim, 0.55);
|
|
98
|
+
|
|
99
|
+
expect(getColumnValue(priceColumn, ticker, financials, defaultColumnContext)).toEqual({
|
|
100
|
+
text: "120",
|
|
101
|
+
color: colors.textDim,
|
|
102
|
+
});
|
|
103
|
+
expect(getColumnValue(changeColumn, ticker, financials, defaultColumnContext)).toEqual({
|
|
104
|
+
text: "+5",
|
|
105
|
+
color: mutedPositive,
|
|
106
|
+
});
|
|
107
|
+
expect(getColumnValue(changePctColumn, ticker, financials, defaultColumnContext)).toEqual({
|
|
108
|
+
text: "+4.35%",
|
|
109
|
+
color: mutedPositive,
|
|
110
|
+
});
|
|
111
|
+
const unchanged = createFinancials({ quote: { change: 0, changePercent: 0, previousClose: 120, marketState: "CLOSED" } });
|
|
112
|
+
expect(getColumnValue(changePctColumn, ticker, unchanged, defaultColumnContext)).toEqual({
|
|
113
|
+
text: "0.00%",
|
|
114
|
+
color: colors.neutral,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const open = createFinancials({ quote: { marketState: "REGULAR" } });
|
|
118
|
+
expect(getColumnValue(priceColumn, ticker, open, defaultColumnContext).color).toBe(colors.positive);
|
|
119
|
+
expect(getColumnValue(changePctColumn, ticker, open, defaultColumnContext).color).toBe(colors.positive);
|
|
120
|
+
});
|
|
121
|
+
|
|
85
122
|
test("calculates portfolio totals from live quotes", () => {
|
|
86
123
|
const ticker = createTicker({
|
|
87
124
|
positions: [{ portfolio: "main", shares: 10, avgCost: 100, broker: "manual" }],
|
|
@@ -21,17 +21,6 @@ export function documentContentTarget(filing: SecFilingItem, document: SecFiling
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function formatDocumentLabel(document: SecFilingDocument): string {
|
|
25
|
-
const label = document.isPrimary ? "PRIMARY" : document.type || "DOCUMENT";
|
|
26
|
-
const description = document.description
|
|
27
|
-
&& document.description !== document.document
|
|
28
|
-
&& document.description !== document.type
|
|
29
|
-
? ` | ${document.description}`
|
|
30
|
-
: "";
|
|
31
|
-
const size = document.size ? ` | ${document.size}` : "";
|
|
32
|
-
return `${label.padEnd(8)} ${document.document}${description}${size}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
24
|
export function formatCompactDocumentLabel(document: SecFilingDocument): string {
|
|
36
25
|
const label = document.isPrimary ? "PRIMARY" : document.type || "DOCUMENT";
|
|
37
26
|
const description = document.description
|
|
@@ -229,8 +229,6 @@ function toFeedItems(
|
|
|
229
229
|
eyebrow: filing.form,
|
|
230
230
|
title: form4Preview ? `${displayTitle} | ${form4Preview}` : enrichedTitle,
|
|
231
231
|
timestamp: filing.filingDate,
|
|
232
|
-
preview: form4Preview
|
|
233
|
-
?? (filing.items ? `Items ${filing.items}` : (formDesc || filing.primaryDocument)),
|
|
234
232
|
detailTitle: enrichedTitle,
|
|
235
233
|
detailMeta: [
|
|
236
234
|
`Filed ${formatFiledAt(filing)}`,
|
|
@@ -43,7 +43,7 @@ const ARTICLE_IMAGE_MAX_HEIGHT = 26;
|
|
|
43
43
|
const ARTICLE_IMAGE_MIN_HEIGHT = 6;
|
|
44
44
|
const ARTICLE_IMAGE_LEGACY_HEIGHT_CAP = 14;
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
function resolveArticleImageSize(lineWidth: number) {
|
|
47
47
|
const safeLineWidth = Math.max(1, Math.floor(lineWidth));
|
|
48
48
|
const width = Math.min(safeLineWidth, ARTICLE_IMAGE_MAX_WIDTH);
|
|
49
49
|
const legacyHeight = Math.min(ARTICLE_IMAGE_LEGACY_HEIGHT_CAP, Math.floor(width * 0.32));
|
|
@@ -226,21 +226,6 @@ export async function searchThirteenFFunds(
|
|
|
226
226
|
return arrayResponse(raw).map(mapFund).filter((fund): fund is ThirteenFFund => !!fund);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
export async function listThirteenFFilers(
|
|
230
|
-
limit = 50,
|
|
231
|
-
signal?: AbortSignal,
|
|
232
|
-
options: { forceRefresh?: boolean; offset?: number } = {},
|
|
233
|
-
): Promise<ThirteenFFund[]> {
|
|
234
|
-
const raw = await fetchForms13F<unknown>("/filers", {
|
|
235
|
-
offset: options.offset ?? 0,
|
|
236
|
-
limit,
|
|
237
|
-
}, { signal, forceRefresh: options.forceRefresh });
|
|
238
|
-
return arrayResponse(raw).map((entry) => mapFund({
|
|
239
|
-
cik: entry.cik,
|
|
240
|
-
name: Array.isArray(entry.company_names) ? entry.company_names[0] : entry.company_name,
|
|
241
|
-
})).filter((fund): fund is ThirteenFFund => !!fund);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
229
|
export async function listTopThirteenFFunds(
|
|
245
230
|
quarter: string,
|
|
246
231
|
limit = 50,
|
|
@@ -235,7 +235,6 @@ export function ThirteenFPane({ focused, width, height }: PaneProps) {
|
|
|
235
235
|
}, { allowEditable: true });
|
|
236
236
|
|
|
237
237
|
const sortedRows = useMemo(() => sortBrowserRows(rows, sortPreference), [rows, sortPreference]);
|
|
238
|
-
const selectedIndex = selectedIndexById(sortedRows, selectedId);
|
|
239
238
|
const columns = useMemo(() => buildBrowserColumns(width), [width]);
|
|
240
239
|
|
|
241
240
|
useEffect(() => {
|
|
@@ -643,7 +642,7 @@ function FundDetailView({
|
|
|
643
642
|
getId: (row) => row.id,
|
|
644
643
|
onChange: (id) => setFilingSelectedId(id),
|
|
645
644
|
}}
|
|
646
|
-
onActivate={openFilingInPane}
|
|
645
|
+
onActivate={(row) => openFilingInPane(row)}
|
|
647
646
|
onDetailKeyDown={(event) => {
|
|
648
647
|
if (event.name !== "o" || !openFiling?.url) return false;
|
|
649
648
|
event.preventDefault?.();
|
|
@@ -755,7 +754,6 @@ function FilingDetailView({
|
|
|
755
754
|
sortPreference,
|
|
756
755
|
)
|
|
757
756
|
), [filing.tableValueTotal, holdings, sortPreference]);
|
|
758
|
-
const selectedPositionIndex = selectedIndexById(positionRows, selectedPositionId);
|
|
759
757
|
const columns = useMemo(() => buildFilingPositionColumns(width), [width]);
|
|
760
758
|
|
|
761
759
|
useEffect(() => {
|
|
@@ -267,7 +267,7 @@ export function FundamentalGraphContent({
|
|
|
267
267
|
header={metricHeader}
|
|
268
268
|
formatValue={definition.format}
|
|
269
269
|
onHoverChange={setHoveredBar}
|
|
270
|
-
onMouseDown={
|
|
270
|
+
onMouseDown={focusMetricTabs}
|
|
271
271
|
/>
|
|
272
272
|
{tableHeight > 0 ? (
|
|
273
273
|
<DataTableView<FundamentalGraphRow, FundamentalColumn>
|
|
@@ -274,13 +274,6 @@ export function ResolvedFinancialsTab({
|
|
|
274
274
|
})),
|
|
275
275
|
];
|
|
276
276
|
const rows = buildFinancialRows(subTab.rows, displayStatements, collapsedGroups);
|
|
277
|
-
const selectedIndex = selectedRowId
|
|
278
|
-
? rows.findIndex((row) => row.id === selectedRowId)
|
|
279
|
-
: rows.length > 0 ? 0 : -1;
|
|
280
|
-
const effectiveSelectedIndex = selectedIndex >= 0
|
|
281
|
-
? selectedIndex
|
|
282
|
-
: rows.length > 0 ? 0 : -1;
|
|
283
|
-
|
|
284
277
|
useEffect(() => {
|
|
285
278
|
if (rows.length === 0) {
|
|
286
279
|
if (selectedRowId !== null) setSelectedRowId(null);
|
|
@@ -13,10 +13,12 @@ import type { PinTickerOptions } from "../../../../types/plugin";
|
|
|
13
13
|
|
|
14
14
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
15
15
|
|
|
16
|
-
afterEach(() => {
|
|
16
|
+
afterEach(async () => {
|
|
17
17
|
setSharedMarketDataCoordinator(null);
|
|
18
18
|
if (testSetup) {
|
|
19
|
-
|
|
19
|
+
await act(async () => {
|
|
20
|
+
testSetup!.renderer.destroy();
|
|
21
|
+
});
|
|
20
22
|
testSetup = undefined;
|
|
21
23
|
}
|
|
22
24
|
});
|
|
@@ -181,44 +183,13 @@ async function renderOnce() {
|
|
|
181
183
|
async function flushFrames(count: number) {
|
|
182
184
|
for (let index = 0; index < count; index += 1) {
|
|
183
185
|
await act(async () => {
|
|
184
|
-
await Promise
|
|
186
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
185
187
|
await testSetup!.renderOnce();
|
|
186
188
|
});
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
describe("QuoteMonitorPane", () => {
|
|
191
|
-
test("renders a compact quote card for the bound ticker", async () => {
|
|
192
|
-
await renderHarness(createQuoteMonitorHarness(), {
|
|
193
|
-
width: 72,
|
|
194
|
-
height: 7,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
await renderOnce();
|
|
198
|
-
|
|
199
|
-
const frame = testSetup.captureCharFrame();
|
|
200
|
-
expect(frame).toContain("MSFT");
|
|
201
|
-
expect(frame).toContain("$356.15");
|
|
202
|
-
expect(frame).toContain("-2.68%");
|
|
203
|
-
expect(frame).toContain("-9.82");
|
|
204
|
-
expect(frame).toMatch(/[⠁-⣿]/);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("renders multiple configured tickers", async () => {
|
|
208
|
-
await renderHarness(createQuoteMonitorHarness({ symbols: ["MSFT", "AAPL"] }), {
|
|
209
|
-
width: 72,
|
|
210
|
-
height: 8,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
await renderOnce();
|
|
214
|
-
|
|
215
|
-
const frame = testSetup.captureCharFrame();
|
|
216
|
-
expect(frame).toContain("MSFT");
|
|
217
|
-
expect(frame).toContain("AAPL");
|
|
218
|
-
expect(frame).toContain("$356.15");
|
|
219
|
-
expect(frame).toContain("$202.12");
|
|
220
|
-
});
|
|
221
|
-
|
|
222
193
|
test("opens a Ticker Research pane on the second card click", async () => {
|
|
223
194
|
const pinCalls: PinTickerCall[] = [];
|
|
224
195
|
await renderHarness(createQuoteMonitorHarness({ pinCalls }), {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
buildFlatRows,
|
|
10
10
|
DEFAULT_SORT_PREFERENCE,
|
|
11
11
|
nextSortPreference,
|
|
12
|
-
rowIndicesOf,
|
|
13
12
|
type IndexQuoteState,
|
|
14
13
|
type QuoteMap,
|
|
15
14
|
type WorldIndexSortPreference,
|
|
@@ -36,12 +35,9 @@ function WorldIndicesPane({ focused, width, height }: PaneProps) {
|
|
|
36
35
|
() => buildFlatRows(indicesByRegion, sortPreference, quotes),
|
|
37
36
|
[indicesByRegion, quotes, sortPreference],
|
|
38
37
|
);
|
|
39
|
-
const navigableIndices = useMemo(() => rowIndicesOf(flatRows), [flatRows]);
|
|
40
38
|
const selectedFlatIdx = selectedSymbol
|
|
41
39
|
? flatRows.findIndex((row) => row.type === "row" && row.entry.symbol === selectedSymbol)
|
|
42
40
|
: -1;
|
|
43
|
-
const activeFlatIdx = selectedFlatIdx >= 0 ? selectedFlatIdx : (navigableIndices[0] ?? -1);
|
|
44
|
-
|
|
45
41
|
useEffect(() => {
|
|
46
42
|
if (selectedSymbol && selectedFlatIdx >= 0) return;
|
|
47
43
|
const firstRow = flatRows.find((row) => row.type === "row");
|
|
@@ -87,13 +87,6 @@ export function buildFlatRows(
|
|
|
87
87
|
return rows;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export function rowIndicesOf(flatRows: WorldIndexTableRow[]): number[] {
|
|
91
|
-
return flatRows.reduce<number[]>((acc, row, i) => {
|
|
92
|
-
if (row.type === "row") acc.push(i);
|
|
93
|
-
return acc;
|
|
94
|
-
}, []);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
90
|
export function nextSortPreference(
|
|
98
91
|
current: WorldIndexSortPreference,
|
|
99
92
|
columnId: string,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BrokerInstanceConfig } from "../../types/config";
|
|
2
2
|
import type { CachePolicy } from "../../types/persistence";
|
|
3
3
|
import { normalizeIbkrConfig } from "./config";
|
|
4
|
-
import { fnv1aHashString } from "
|
|
4
|
+
import { fnv1aHashString } from "../../utils/hash";
|
|
5
5
|
|
|
6
6
|
const FLEX_ACCOUNT_CACHE_POLICY = {
|
|
7
7
|
staleMs: 6 * 60 * 60 * 1000,
|
|
@@ -5,7 +5,7 @@ import type { BrokerPortfolioPerformance } from "../../types/trading";
|
|
|
5
5
|
import { debugLog } from "../../utils/debug-log";
|
|
6
6
|
import { loadFlexStatement, parseFlexPortfolioPerformance } from "./flex";
|
|
7
7
|
import { normalizeIbkrConfig } from "./config";
|
|
8
|
-
import { fnv1aHashString } from "
|
|
8
|
+
import { fnv1aHashString } from "../../utils/hash";
|
|
9
9
|
|
|
10
10
|
const PERFORMANCE_LOG = debugLog.createLogger("ibkr-performance");
|
|
11
11
|
const PERFORMANCE_CACHE_KIND = "portfolio-performance";
|
|
@@ -5,6 +5,7 @@ import type { TickerRecord } from "../../../types/ticker";
|
|
|
5
5
|
import type { BrokerAccount, BrokerOrderPreview, BrokerOrderType } from "../../../types/trading";
|
|
6
6
|
import { colors } from "../../../theme/colors";
|
|
7
7
|
import { formatCompact, formatCurrency } from "../../../utils/format";
|
|
8
|
+
export { truncateWithEllipsis as truncateTradeText } from "../../../utils/text-wrap";
|
|
8
9
|
import { formatMarketPrice, formatMarketPriceWithCurrency, formatSignedMarketPrice, type AssetDisplayContext } from "../../../market-data/market/format";
|
|
9
10
|
import { getConfiguredIbkrGatewayInstances } from "../instance-selection";
|
|
10
11
|
|
|
@@ -108,13 +109,6 @@ export function formatPreviewMetric(before?: number, after?: number): string {
|
|
|
108
109
|
return `${formatCompact(before || 0)} → ${formatCompact(after || 0)}`;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
export function truncateTradeText(value: string, maxWidth: number): string {
|
|
112
|
-
if (maxWidth <= 0) return "";
|
|
113
|
-
if (value.length <= maxWidth) return value;
|
|
114
|
-
if (maxWidth <= 3) return value.slice(0, maxWidth);
|
|
115
|
-
return `${value.slice(0, maxWidth - 3)}...`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
112
|
export function findTickerForOrder(
|
|
119
113
|
order: { contract: BrokerContractRef },
|
|
120
114
|
tickers: Map<string, TickerRecord>,
|
package/src/plugins/loader.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { readdir } from "fs/promises";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
4
5
|
import type { GloomPlugin } from "../types/plugin";
|
|
5
6
|
import { debugLog } from "../utils/debug-log";
|
|
6
7
|
|
|
7
8
|
const loaderLog = debugLog.createLogger("plugin-loader");
|
|
8
9
|
|
|
9
|
-
const PLUGINS_DIR = join(process.env.HOME ||
|
|
10
|
+
const PLUGINS_DIR = join(process.env.HOME || homedir(), ".gloomberb", "plugins");
|
|
10
11
|
|
|
11
12
|
export interface LoadedExternalPlugin {
|
|
12
13
|
plugin: GloomPlugin;
|
|
@@ -64,38 +64,4 @@ describe("prediction markets detail views", () => {
|
|
|
64
64
|
expect(frame).not.toContain("Loading market detail...");
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
test("renders book levels in the shared data table", async () => {
|
|
68
|
-
testSetup = await testRender(<GroupedDetailHarness detailTab="book" />, {
|
|
69
|
-
width: 64,
|
|
70
|
-
height: 24,
|
|
71
|
-
});
|
|
72
|
-
await flushFrames(testSetup);
|
|
73
|
-
|
|
74
|
-
const frame = testSetup.captureCharFrame();
|
|
75
|
-
expect(frame).toContain("OUT");
|
|
76
|
-
expect(frame).toContain("SIDE");
|
|
77
|
-
expect(frame).toContain("PRICE");
|
|
78
|
-
expect(frame).toContain("SIZE");
|
|
79
|
-
expect(frame).toContain("YES");
|
|
80
|
-
expect(frame).toContain("BID");
|
|
81
|
-
expect(frame).toContain("47c");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("renders recent trades in the shared data table", async () => {
|
|
85
|
-
testSetup = await testRender(<GroupedDetailHarness detailTab="trades" />, {
|
|
86
|
-
width: 64,
|
|
87
|
-
height: 24,
|
|
88
|
-
});
|
|
89
|
-
await flushFrames(testSetup);
|
|
90
|
-
|
|
91
|
-
const frame = testSetup.captureCharFrame();
|
|
92
|
-
expect(frame).toContain("TIME");
|
|
93
|
-
expect(frame).toContain("SIDE");
|
|
94
|
-
expect(frame).toContain("OUT");
|
|
95
|
-
expect(frame).toContain("PRICE");
|
|
96
|
-
expect(frame).toContain("SIZE");
|
|
97
|
-
expect(frame).toContain("BUY");
|
|
98
|
-
expect(frame).toContain("YES");
|
|
99
|
-
expect(frame).toContain("48c");
|
|
100
|
-
});
|
|
101
67
|
});
|