gloomberb 0.3.7 → 0.4.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 +19 -8
- package/package.json +1 -1
- package/src/app.tsx +29 -65
- package/src/cli/base-converter.ts +31 -0
- package/src/cli/commands/search.ts +11 -3
- package/src/cli/commands/ticker.ts +22 -11
- package/src/cli/context.ts +27 -32
- package/src/cli/helpers.ts +1 -4
- package/src/cli/index.ts +140 -123
- package/src/cli/registry.test.ts +226 -0
- package/src/cli/registry.ts +201 -0
- package/src/cli.test.ts +219 -0
- package/src/components/chart/chart-controller.ts +309 -0
- package/src/components/chart/chart-data.ts +5 -6
- package/src/components/chart/chart-pane-settings.ts +27 -0
- package/src/components/chart/chart-resolution.test.ts +56 -0
- package/src/components/chart/chart-resolution.ts +362 -0
- package/src/components/chart/chart-types.ts +12 -4
- package/src/components/chart/chart-viewport.test.ts +25 -0
- package/src/components/chart/chart-viewport.ts +28 -0
- package/src/components/chart/comparison-chart-data.ts +20 -27
- package/src/components/chart/comparison-stock-chart.tsx +374 -99
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
- package/src/components/chart/stock-chart.tsx +455 -106
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +650 -5
- package/src/components/command-bar/command-bar.tsx +359 -63
- package/src/components/command-bar/command-registry.ts +8 -0
- package/src/components/command-bar/helpers.test.ts +23 -1
- package/src/components/command-bar/helpers.ts +14 -5
- package/src/components/command-bar/pane-template-display.ts +12 -0
- package/src/components/command-bar/root-shortcuts.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +4 -2
- package/src/components/detail-feed-view.test.tsx +124 -49
- package/src/components/detail-feed-view.tsx +396 -160
- package/src/components/layout/floating-pane.tsx +1 -1
- package/src/components/layout/pane-header.tsx +26 -2
- package/src/components/layout/shell.tsx +62 -50
- package/src/components/layout/status-bar.test.tsx +66 -0
- package/src/components/layout/status-bar.tsx +26 -5
- package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
- package/src/components/onboarding/onboarding-wizard.tsx +5 -6
- package/src/components/ticker-list-table.test.tsx +83 -0
- package/src/components/ticker-list-table.tsx +174 -97
- package/src/components/ui/data-table.tsx +27 -2
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/page-stack-view.tsx +60 -0
- package/src/components/use-double-click-activation.ts +31 -0
- package/src/index.tsx +10 -10
- package/src/market-data/coordinator.test.ts +92 -4
- package/src/market-data/coordinator.ts +32 -5
- package/src/market-data/hooks.test.tsx +120 -0
- package/src/market-data/hooks.tsx +53 -16
- package/src/market-data/request-types.ts +4 -2
- package/src/market-data/selectors.ts +24 -58
- package/src/plugins/builtin/chat-controller.test.ts +370 -3
- package/src/plugins/builtin/chat-controller.ts +310 -35
- package/src/plugins/builtin/chat.test.tsx +118 -6
- package/src/plugins/builtin/chat.tsx +175 -116
- package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
- package/src/plugins/builtin/comparison-chart.tsx +21 -1
- package/src/plugins/builtin/news.tsx +1 -11
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
- package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
- package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
- package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
- package/src/plugins/builtin/sec.tsx +1 -11
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
- package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
- package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
- package/src/plugins/catalog.ts +65 -0
- package/src/plugins/ibkr/broker-adapter.ts +28 -0
- package/src/plugins/ibkr/gateway-service.test.ts +45 -1
- package/src/plugins/ibkr/gateway-service.ts +141 -7
- package/src/plugins/pane-manager.test.ts +1 -1
- package/src/plugins/plugin-runtime.tsx +27 -7
- package/src/plugins/prediction-markets/cli.ts +28 -0
- package/src/plugins/prediction-markets/controller-data.ts +11 -3
- package/src/plugins/prediction-markets/controller.ts +141 -121
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/prediction-markets/launch.ts +51 -16
- package/src/plugins/prediction-markets/metrics.ts +9 -5
- package/src/plugins/prediction-markets/pane.test.tsx +119 -57
- package/src/plugins/prediction-markets/pane.tsx +74 -153
- package/src/plugins/prediction-markets/table.tsx +6 -3
- package/src/plugins/prediction-markets/types.ts +0 -1
- package/src/sources/gloomberb-cloud.test.ts +29 -0
- package/src/sources/gloomberb-cloud.ts +89 -1
- package/src/sources/provider-router.test.ts +265 -3
- package/src/sources/provider-router.ts +239 -11
- package/src/sources/yahoo-finance.test.ts +26 -0
- package/src/sources/yahoo-finance.ts +54 -3
- package/src/state/app-context-selector.test.tsx +99 -0
- package/src/state/app-context.test.ts +18 -0
- package/src/state/app-context.tsx +170 -65
- package/src/state/selectors.ts +25 -0
- package/src/theme/colors.ts +8 -8
- package/src/theme/themes.test.ts +2 -0
- package/src/theme/themes.ts +22 -10
- package/src/types/broker.ts +21 -0
- package/src/types/config.ts +17 -1
- package/src/types/data-provider.ts +18 -0
- package/src/types/financials.ts +31 -1
- package/src/types/plugin.ts +70 -0
- package/src/utils/api-client.test.ts +57 -0
- package/src/utils/api-client.ts +35 -12
- package/src/utils/back-navigation.ts +27 -0
- package/src/utils/format.test.ts +14 -0
- package/src/utils/format.ts +3 -3
- package/src/utils/market-status.ts +46 -1
- package/src/utils/quote-freshness.ts +127 -0
- package/src/utils/quote-resolution.test.ts +277 -0
- package/src/utils/quote-resolution.ts +641 -0
- package/src/utils/slugify.ts +3 -0
- package/src/utils/timestamp.ts +25 -0
- package/src/version.ts +1 -1
- package/src/cli/commands/portfolio.ts +0 -210
- package/src/cli/commands/watchlist.ts +0 -210
- package/src/plugins/builtin/data-providers.ts +0 -16
- package/src/sources/exchange-rates.ts +0 -18
- package/src/utils/ascii-chart.ts +0 -172
- package/src/utils/ticker-tokenizer.test.ts +0 -48
|
@@ -82,6 +82,14 @@ export const commands: Command[] = [
|
|
|
82
82
|
category: "Portfolio",
|
|
83
83
|
execute: () => {},
|
|
84
84
|
},
|
|
85
|
+
{
|
|
86
|
+
id: "set-portfolio-position",
|
|
87
|
+
prefix: "",
|
|
88
|
+
label: "Set Portfolio Position",
|
|
89
|
+
description: "Create or update a manual position in a portfolio",
|
|
90
|
+
category: "Portfolio",
|
|
91
|
+
execute: () => {},
|
|
92
|
+
},
|
|
85
93
|
|
|
86
94
|
// Create / Delete
|
|
87
95
|
{
|
|
@@ -188,10 +188,32 @@ describe("command-bar helpers", () => {
|
|
|
188
188
|
options: [],
|
|
189
189
|
payload: { fieldType: "ordered-multi-select" },
|
|
190
190
|
};
|
|
191
|
+
const filteredPickerRoute: CommandBarRoute = {
|
|
192
|
+
...orderedPickerRoute,
|
|
193
|
+
query: "vol",
|
|
194
|
+
};
|
|
195
|
+
const workflowRoute: CommandBarRoute = {
|
|
196
|
+
kind: "workflow",
|
|
197
|
+
title: "Create Pane",
|
|
198
|
+
fields: [],
|
|
199
|
+
values: {},
|
|
200
|
+
activeFieldId: null,
|
|
201
|
+
};
|
|
202
|
+
const confirmRoute: CommandBarRoute = {
|
|
203
|
+
kind: "confirm",
|
|
204
|
+
title: "Delete",
|
|
205
|
+
body: ["Delete item?"],
|
|
206
|
+
confirmLabel: "Delete",
|
|
207
|
+
cancelLabel: "Back",
|
|
208
|
+
tone: "danger",
|
|
209
|
+
};
|
|
191
210
|
|
|
192
211
|
expect(getScreenFooterLeft(null)).toBe("up/down move enter select");
|
|
193
212
|
expect(getScreenFooterLeft(orderedPickerRoute)).toBe("up/down move space toggle [ ] reorder enter done");
|
|
194
|
-
expect(getScreenFooterRight(orderedPickerRoute)).toBe("esc back");
|
|
213
|
+
expect(getScreenFooterRight(orderedPickerRoute)).toBe("backspace/esc back");
|
|
214
|
+
expect(getScreenFooterRight(filteredPickerRoute)).toBe("backspace delete esc back");
|
|
215
|
+
expect(getScreenFooterRight(workflowRoute)).toBe("esc back");
|
|
216
|
+
expect(getScreenFooterRight(confirmRoute)).toBe("backspace/esc back");
|
|
195
217
|
});
|
|
196
218
|
|
|
197
219
|
test("maps route and collection command ids", () => {
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
PaneTemplateDef,
|
|
5
5
|
WizardStep,
|
|
6
6
|
} from "../../types/plugin";
|
|
7
|
+
export { slugifyName } from "../../utils/slugify";
|
|
7
8
|
import type {
|
|
8
9
|
CommandBarFieldOption,
|
|
9
10
|
CommandBarFieldValue,
|
|
@@ -251,10 +252,6 @@ export function routeCommandIdToScreen(commandId: RouteCommandId): "ticker-searc
|
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
export function slugifyName(name: string, fallbackPrefix: string): string {
|
|
255
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || `${fallbackPrefix}-${Date.now()}`;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
255
|
export function getFirstVisibleFieldId(
|
|
259
256
|
fields: CommandBarWorkflowField[],
|
|
260
257
|
values: Record<string, CommandBarFieldValue>,
|
|
@@ -328,7 +325,19 @@ export function getScreenFooterLeft(route: CommandBarRoute | null): string {
|
|
|
328
325
|
}
|
|
329
326
|
|
|
330
327
|
export function getScreenFooterRight(route: CommandBarRoute | null): string {
|
|
331
|
-
|
|
328
|
+
if (!route) return "esc close";
|
|
329
|
+
|
|
330
|
+
if (route.kind === "workflow") return "esc back";
|
|
331
|
+
if (route.kind === "confirm") return "backspace/esc back";
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
(route.kind === "mode" || route.kind === "picker" || route.kind === "pane-settings")
|
|
335
|
+
&& route.query.trim().length > 0
|
|
336
|
+
) {
|
|
337
|
+
return "backspace delete esc back";
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return "backspace/esc back";
|
|
332
341
|
}
|
|
333
342
|
|
|
334
343
|
export function isRootParsedCommand(commandId: string): boolean {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PaneTemplateDef } from "../../types/plugin";
|
|
2
|
+
|
|
3
|
+
export function formatPaneTemplateLabel(label: string): string {
|
|
4
|
+
let normalized = label.trim();
|
|
5
|
+
if (normalized.startsWith("New ")) normalized = normalized.slice(4);
|
|
6
|
+
if (normalized.endsWith(" Pane")) normalized = normalized.slice(0, -5);
|
|
7
|
+
return normalized;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getPaneTemplateDisplayLabel(template: Pick<PaneTemplateDef, "label">): string {
|
|
11
|
+
return formatPaneTemplateLabel(template.label);
|
|
12
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PaneTemplateDef } from "../../types/plugin";
|
|
2
2
|
import type { Command } from "./command-registry";
|
|
3
|
+
import { getPaneTemplateDisplayLabel } from "./pane-template-display";
|
|
3
4
|
|
|
4
5
|
export type RootShortcutArgKind = "text" | "ticker" | "ticker-list";
|
|
5
6
|
export type ShortcutIntentKind = "none" | "complete" | "inferred-complete" | "partial" | "ambiguous";
|
|
@@ -88,7 +89,7 @@ function buildShortcutCandidates(
|
|
|
88
89
|
.filter((template) => template.shortcut?.prefix)
|
|
89
90
|
.map((template) => ({
|
|
90
91
|
prefix: normalizeShortcutPrefix(template.shortcut!.prefix),
|
|
91
|
-
label: template
|
|
92
|
+
label: getPaneTemplateDisplayLabel(template),
|
|
92
93
|
description: template.description,
|
|
93
94
|
argKind: getPaneShortcutArgKind(template),
|
|
94
95
|
argPlaceholder: template.shortcut?.argPlaceholder,
|
|
@@ -9,10 +9,12 @@ import type { TickerRepository } from "../../data/ticker-repository";
|
|
|
9
9
|
import { resolveTickerSearch, upsertTickerFromSearchResult } from "../../utils/ticker-search";
|
|
10
10
|
import { formatTickerListInput, parseTickerListInput } from "../../utils/ticker-list";
|
|
11
11
|
import { updatePaneInstance, setPaneSettings } from "../../pane-settings";
|
|
12
|
+
import { isManualPortfolio } from "../../plugins/builtin/portfolio-list/mutations";
|
|
12
13
|
import {
|
|
13
14
|
buildComparisonChartPaneTitle,
|
|
14
15
|
COMPARISON_CHART_PANE_ID,
|
|
15
16
|
} from "../../plugins/builtin/comparison-chart";
|
|
17
|
+
import { getPaneTemplateDisplayLabel } from "./pane-template-display";
|
|
16
18
|
|
|
17
19
|
export interface SharedWorkflowDeps {
|
|
18
20
|
dataProvider: DataProvider;
|
|
@@ -109,7 +111,7 @@ function getCollectionOptions(
|
|
|
109
111
|
if (kind === "watchlist") {
|
|
110
112
|
return state.config.watchlists;
|
|
111
113
|
}
|
|
112
|
-
return state.config.portfolios.filter(
|
|
114
|
+
return state.config.portfolios.filter(isManualPortfolio);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export function getCollectionTargetOptions(
|
|
@@ -302,7 +304,7 @@ export async function createPaneTemplateOrThrow(
|
|
|
302
304
|
const { context, resolvedOptions } = await resolvePaneTemplateOptions(template, options, deps);
|
|
303
305
|
|
|
304
306
|
if (template.canCreate && !template.canCreate(context, resolvedOptions)) {
|
|
305
|
-
throw new Error(`Can't create ${template.
|
|
307
|
+
throw new Error(`Can't create ${getPaneTemplateDisplayLabel(template).toLowerCase()} right now.`);
|
|
306
308
|
}
|
|
307
309
|
|
|
308
310
|
const createInstanceResult = await template.createInstance?.(context, resolvedOptions);
|
|
@@ -1,26 +1,55 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { act, useState } from "react";
|
|
2
3
|
import { testRender } from "@opentui/react/test-utils";
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DetailFeedView,
|
|
6
|
+
type DetailFeedItem,
|
|
7
|
+
} from "./detail-feed-view";
|
|
4
8
|
|
|
5
9
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
10
|
+
let setHarnessItems:
|
|
11
|
+
| ((items: DetailFeedItem[]) => void)
|
|
12
|
+
| undefined;
|
|
13
|
+
let harnessSelectedIdx = 0;
|
|
6
14
|
|
|
7
|
-
afterEach(() => {
|
|
15
|
+
afterEach(async () => {
|
|
8
16
|
if (testSetup) {
|
|
9
|
-
|
|
17
|
+
await act(async () => {
|
|
18
|
+
testSetup!.renderer.destroy();
|
|
19
|
+
});
|
|
10
20
|
testSetup = undefined;
|
|
11
21
|
}
|
|
22
|
+
setHarnessItems = undefined;
|
|
23
|
+
harnessSelectedIdx = 0;
|
|
12
24
|
});
|
|
13
25
|
|
|
14
|
-
|
|
26
|
+
async function renderSettled() {
|
|
27
|
+
await act(async () => {
|
|
28
|
+
await testSetup!.renderOnce();
|
|
29
|
+
await testSetup!.renderOnce();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function clickAt(x: number, y: number) {
|
|
34
|
+
await act(async () => {
|
|
35
|
+
await testSetup!.mockMouse.click(x, y);
|
|
36
|
+
await testSetup!.renderOnce();
|
|
37
|
+
await testSetup!.renderOnce();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const items: DetailFeedItem[] = [
|
|
15
42
|
{
|
|
16
43
|
id: "1",
|
|
17
44
|
eyebrow: "Reuters",
|
|
18
|
-
title:
|
|
45
|
+
title:
|
|
46
|
+
"Long headline with enough words to prove the list wraps instead of collapsing into fixed columns",
|
|
19
47
|
timestamp: new Date("2024-08-01T12:00:00Z"),
|
|
20
48
|
preview: "Preview text with a little more color.",
|
|
21
49
|
detailTitle: "Long headline with enough words to prove the list wraps",
|
|
22
50
|
detailMeta: ["Reuters", "Published Aug 1, 2024"],
|
|
23
|
-
detailBody:
|
|
51
|
+
detailBody:
|
|
52
|
+
"This body should stay readable when the pane is narrow and also when the pane is wide enough for a split layout.",
|
|
24
53
|
detailNote: "https://example.com/story",
|
|
25
54
|
},
|
|
26
55
|
{
|
|
@@ -35,68 +64,114 @@ const items = [
|
|
|
35
64
|
},
|
|
36
65
|
];
|
|
37
66
|
|
|
67
|
+
function Harness({
|
|
68
|
+
width,
|
|
69
|
+
height,
|
|
70
|
+
listVariant = "comfortable",
|
|
71
|
+
}: {
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
listVariant?: "comfortable" | "compact" | "single-line";
|
|
75
|
+
}) {
|
|
76
|
+
const [activeItems, setActiveItems] = useState(items);
|
|
77
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
78
|
+
const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
|
|
79
|
+
|
|
80
|
+
setHarnessItems = setActiveItems;
|
|
81
|
+
harnessSelectedIdx = selectedIdx;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<DetailFeedView
|
|
85
|
+
width={width}
|
|
86
|
+
height={height}
|
|
87
|
+
focused
|
|
88
|
+
items={activeItems}
|
|
89
|
+
selectedIdx={selectedIdx}
|
|
90
|
+
hoveredIdx={hoveredIdx}
|
|
91
|
+
onSelect={setSelectedIdx}
|
|
92
|
+
onHover={setHoveredIdx}
|
|
93
|
+
listVariant={listVariant}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
38
98
|
describe("DetailFeedView", () => {
|
|
39
|
-
test("renders
|
|
40
|
-
testSetup = await testRender(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
items={items}
|
|
45
|
-
selectedIdx={0}
|
|
46
|
-
hoveredIdx={null}
|
|
47
|
-
onSelect={() => {}}
|
|
48
|
-
onHover={() => {}}
|
|
49
|
-
/>,
|
|
50
|
-
{ width: 60, height: 16 },
|
|
51
|
-
);
|
|
99
|
+
test("renders the browse list by default", async () => {
|
|
100
|
+
testSetup = await testRender(<Harness width={60} height={16} />, {
|
|
101
|
+
width: 60,
|
|
102
|
+
height: 16,
|
|
103
|
+
});
|
|
52
104
|
|
|
53
|
-
await
|
|
105
|
+
await renderSettled();
|
|
54
106
|
const frame = testSetup.captureCharFrame();
|
|
55
107
|
|
|
56
108
|
expect(frame).toContain("Long headline with enough words");
|
|
57
|
-
expect(frame).toContain("
|
|
58
|
-
expect(frame).toContain("
|
|
109
|
+
expect(frame).toContain("j/k move enter or click selected row");
|
|
110
|
+
expect(frame).not.toContain("This body should stay readable");
|
|
59
111
|
});
|
|
60
112
|
|
|
61
|
-
test("
|
|
113
|
+
test("selects an item on single click without opening detail", async () => {
|
|
62
114
|
testSetup = await testRender(
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
hoveredIdx={1}
|
|
69
|
-
onSelect={() => {}}
|
|
70
|
-
onHover={() => {}}
|
|
71
|
-
/>,
|
|
72
|
-
{ width: 110, height: 16 },
|
|
115
|
+
<Harness width={90} height={12} listVariant="single-line" />,
|
|
116
|
+
{
|
|
117
|
+
width: 90,
|
|
118
|
+
height: 12,
|
|
119
|
+
},
|
|
73
120
|
);
|
|
74
121
|
|
|
75
|
-
await
|
|
122
|
+
await renderSettled();
|
|
123
|
+
|
|
76
124
|
const frame = testSetup.captureCharFrame();
|
|
125
|
+
const lines = frame.split("\n");
|
|
126
|
+
const filingRow = lines.findIndex((line) => line.includes("10-Q filing"));
|
|
127
|
+
const filingCol = lines[filingRow]?.indexOf("10-Q filing") ?? -1;
|
|
128
|
+
|
|
129
|
+
await clickAt(filingCol + 1, filingRow);
|
|
130
|
+
|
|
131
|
+
const nextFrame = testSetup.captureCharFrame();
|
|
132
|
+
expect(harnessSelectedIdx).toBe(1);
|
|
133
|
+
expect(nextFrame).not.toContain("<- Back");
|
|
134
|
+
expect(nextFrame).not.toContain("Quarterly report details.");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("closes the open detail page when the feed items change", async () => {
|
|
138
|
+
testSetup = await testRender(<Harness width={90} height={16} />, {
|
|
139
|
+
width: 90,
|
|
140
|
+
height: 16,
|
|
141
|
+
});
|
|
77
142
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
143
|
+
await renderSettled();
|
|
144
|
+
|
|
145
|
+
await act(async () => {
|
|
146
|
+
testSetup!.mockInput.pressEnter();
|
|
147
|
+
await testSetup!.renderOnce();
|
|
148
|
+
await testSetup!.renderOnce();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await act(async () => {
|
|
152
|
+
setHarnessItems?.([
|
|
153
|
+
{
|
|
154
|
+
id: "9",
|
|
155
|
+
title: "Fresh item after source change",
|
|
156
|
+
detailBody: "Replacement detail",
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
});
|
|
160
|
+
await renderSettled();
|
|
161
|
+
|
|
162
|
+
const frame = testSetup.captureCharFrame();
|
|
163
|
+
expect(frame).toContain("Fresh item after source change");
|
|
164
|
+
expect(frame).not.toContain("<- Back");
|
|
165
|
+
expect(frame).not.toContain("Replacement detail");
|
|
81
166
|
});
|
|
82
167
|
|
|
83
168
|
test("renders a single-line list variant", async () => {
|
|
84
169
|
testSetup = await testRender(
|
|
85
|
-
<
|
|
86
|
-
width={110}
|
|
87
|
-
height={12}
|
|
88
|
-
items={items}
|
|
89
|
-
selectedIdx={0}
|
|
90
|
-
hoveredIdx={null}
|
|
91
|
-
onSelect={() => {}}
|
|
92
|
-
onHover={() => {}}
|
|
93
|
-
listVariant="single-line"
|
|
94
|
-
splitListWidthRatio={0.33}
|
|
95
|
-
/>,
|
|
170
|
+
<Harness width={110} height={12} listVariant="single-line" />,
|
|
96
171
|
{ width: 110, height: 12 },
|
|
97
172
|
);
|
|
98
173
|
|
|
99
|
-
await
|
|
174
|
+
await renderSettled();
|
|
100
175
|
const frame = testSetup.captureCharFrame();
|
|
101
176
|
|
|
102
177
|
expect(frame).toContain("Long headline with enough words");
|