gloomberb 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/app.tsx +184 -69
- package/src/cli/commands/ticker.ts +45 -15
- package/src/cli/helpers.ts +4 -2
- package/src/cli/registry.test.ts +42 -35
- package/src/cli.test.ts +4 -2
- package/src/components/chart/chart-control-hint.tsx +39 -0
- package/src/components/chart/chart-controller.test.ts +110 -0
- package/src/components/chart/chart-controller.ts +364 -2
- package/src/components/chart/chart-data.ts +177 -29
- package/src/components/chart/chart-indicator-selector.tsx +53 -0
- package/src/components/chart/chart-pane-settings.ts +20 -0
- package/src/components/chart/chart-renderer.ts +220 -30
- package/src/components/chart/chart-resolution.test.ts +67 -0
- package/src/components/chart/chart-resolution.ts +74 -4
- package/src/components/chart/chart-scroll.test.ts +69 -0
- package/src/components/chart/chart-scroll.ts +104 -0
- package/src/components/chart/chart-types.ts +12 -0
- package/src/components/chart/chart-viewport.test.ts +5 -1
- package/src/components/chart/chart-viewport.ts +1 -1
- package/src/components/chart/chart.test.ts +249 -10
- package/src/components/chart/comparison-chart-renderer.test.ts +71 -1
- package/src/components/chart/comparison-chart-renderer.ts +40 -3
- package/src/components/chart/comparison-stock-chart.tsx +218 -123
- package/src/components/chart/indicators/bands.test.ts +50 -0
- package/src/components/chart/indicators/bands.ts +29 -0
- package/src/components/chart/indicators/moving-averages.test.ts +54 -0
- package/src/components/chart/indicators/moving-averages.ts +42 -0
- package/src/components/chart/indicators/options.test.ts +32 -0
- package/src/components/chart/indicators/options.ts +107 -0
- package/src/components/chart/indicators/oscillators.test.ts +79 -0
- package/src/components/chart/indicators/oscillators.ts +92 -0
- package/src/components/chart/indicators/types.ts +36 -0
- package/src/components/chart/native/chart-rasterizer.ts +51 -0
- package/src/components/chart/native/kitty.test.ts +68 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +740 -9
- package/src/components/chart/stock-chart.test.ts +239 -0
- package/src/components/chart/stock-chart.tsx +2257 -372
- package/src/components/command-bar/command-bar.test.tsx +337 -5
- package/src/components/command-bar/command-bar.tsx +296 -120
- package/src/components/command-bar/helpers.ts +1 -1
- package/src/components/command-bar/root-shortcuts.ts +33 -4
- package/src/components/command-bar/view-model.test.ts +3 -3
- package/src/components/command-bar/view-model.ts +4 -4
- package/src/components/command-bar/workflow-ops.ts +10 -0
- package/src/components/layout/floating-pane.tsx +3 -1
- package/src/components/layout/header.tsx +5 -4
- package/src/components/layout/pane-sizing.ts +6 -0
- package/src/components/layout/pane.tsx +5 -1
- package/src/components/layout/shell.test.tsx +263 -32
- package/src/components/layout/shell.tsx +10 -6
- package/src/components/layout/status-bar.test.tsx +16 -7
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-editor.tsx +1 -0
- package/src/components/pane-settings-dialog.tsx +13 -152
- package/src/components/pane-template-wizard.tsx +1 -0
- package/src/components/price-selector-dialog.tsx +12 -7
- package/src/components/ticker-badge-text.test.tsx +50 -0
- package/src/components/ticker-badge-text.tsx +17 -2
- package/src/components/ticker-badge.tsx +2 -2
- package/src/components/ticker-list-table.tsx +62 -2
- package/src/components/toggle-list.tsx +15 -2
- package/src/components/ui/data-table.tsx +82 -5
- package/src/components/ui/external-link.tsx +48 -11
- package/src/components/ui/fields.tsx +2 -1
- package/src/components/ui/index.ts +8 -1
- package/src/components/ui/multi-select-chips.tsx +69 -0
- package/src/components/ui/multi-select-dialog.tsx +267 -0
- package/src/components/ui/multi-select.ts +78 -0
- package/src/components/ui/ui.test.tsx +343 -1
- package/src/data/plugin-state-store.ts +30 -19
- package/src/data/resource-store.ts +133 -103
- package/src/data/session-store.ts +19 -12
- package/src/data/sqlite-cache.test.ts +37 -2
- package/src/data/sqlite-database.ts +8 -2
- package/src/data/sqlite-retry.test.ts +63 -0
- package/src/data/sqlite-retry.ts +66 -0
- package/src/data/ticker-store.ts +29 -18
- package/src/index.tsx +6 -1
- package/src/market-data/coordinator.test.ts +114 -5
- package/src/market-data/coordinator.ts +70 -1
- package/src/market-data/hooks.test.tsx +73 -2
- package/src/market-data/hooks.tsx +16 -4
- package/src/notifications/app-notifier.test.ts +177 -0
- package/src/notifications/app-notifier.ts +241 -0
- package/src/pane-settings.test.ts +2 -0
- package/src/plugins/builtin/ai/index.test.tsx +1 -1
- package/src/plugins/builtin/ai/screener-pane.test.tsx +2 -0
- package/src/plugins/builtin/ai/screener-pane.tsx +1 -0
- package/src/plugins/builtin/ai/ticker-context.ts +3 -2
- package/src/plugins/builtin/alerts/alert-engine.test.ts +90 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +55 -0
- package/src/plugins/builtin/alerts/index.test.tsx +357 -0
- package/src/plugins/builtin/alerts/index.tsx +519 -0
- package/src/plugins/builtin/alerts/types.ts +14 -0
- package/src/plugins/builtin/analytics/index.test.tsx +300 -0
- package/src/plugins/builtin/analytics/index.tsx +775 -0
- package/src/plugins/builtin/analytics/metrics.test.ts +125 -0
- package/src/plugins/builtin/analytics/metrics.ts +155 -0
- package/src/plugins/builtin/ask-ai.test.tsx +2 -0
- package/src/plugins/builtin/chat-controller.test.ts +92 -18
- package/src/plugins/builtin/chat-controller.ts +40 -11
- package/src/plugins/builtin/chat.test.tsx +715 -4
- package/src/plugins/builtin/chat.tsx +420 -119
- package/src/plugins/builtin/comparison-chart.test.tsx +97 -57
- package/src/plugins/builtin/comparison-chart.tsx +1 -2
- package/src/plugins/builtin/correlation/compute.test.ts +147 -0
- package/src/plugins/builtin/correlation/compute.ts +115 -0
- package/src/plugins/builtin/correlation/index.tsx +385 -0
- package/src/plugins/builtin/correlation/plugin.test.ts +44 -0
- package/src/plugins/builtin/correlation/settings.test.ts +34 -0
- package/src/plugins/builtin/correlation/settings.ts +95 -0
- package/src/plugins/builtin/debug.tsx +36 -50
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +181 -0
- package/src/plugins/builtin/earnings/earnings-cache.ts +136 -0
- package/src/plugins/builtin/earnings/earnings-data.test.ts +59 -0
- package/src/plugins/builtin/earnings/earnings-data.ts +50 -0
- package/src/plugins/builtin/earnings/index.tsx +337 -0
- package/src/plugins/builtin/econ/calendar-source.test.ts +219 -0
- package/src/plugins/builtin/econ/calendar-source.ts +121 -0
- package/src/plugins/builtin/econ/fred-client.test.ts +63 -0
- package/src/plugins/builtin/econ/fred-client.ts +79 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +88 -0
- package/src/plugins/builtin/econ/index.tsx +999 -0
- package/src/plugins/builtin/econ/types.ts +13 -0
- package/src/plugins/builtin/fred-settings.ts +10 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +178 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +30 -0
- package/src/plugins/builtin/help.test.tsx +1 -5
- package/src/plugins/builtin/help.tsx +1 -16
- package/src/plugins/builtin/insider/index.tsx +417 -0
- package/src/plugins/builtin/insider/insider-data.test.ts +55 -0
- package/src/plugins/builtin/insider/insider-data.ts +49 -0
- package/src/plugins/builtin/layout-manager.tsx +33 -16
- package/src/plugins/builtin/market-movers/index.tsx +573 -0
- package/src/plugins/builtin/market-movers/screener.test.ts +143 -0
- package/src/plugins/builtin/market-movers/screener.ts +105 -0
- package/src/plugins/builtin/options.tsx +4 -3
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +11 -6
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -3
- package/src/plugins/builtin/portfolio-list/header.tsx +3 -2
- package/src/plugins/builtin/portfolio-list/metrics.ts +22 -11
- package/src/plugins/builtin/portfolio-list/pane.tsx +3 -9
- package/src/plugins/builtin/portfolio-list/settings.ts +9 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -0
- package/src/plugins/builtin/portfolio-list.test.tsx +3 -1
- package/src/plugins/builtin/portfolio-metrics.test.ts +13 -1
- package/src/plugins/builtin/sec.tsx +114 -7
- package/src/plugins/builtin/sectors/index.tsx +359 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +75 -13
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +316 -80
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +4 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +17 -12
- package/src/plugins/builtin/ticker-detail.test.tsx +3 -3
- package/src/plugins/builtin/world-indices/index.tsx +410 -0
- package/src/plugins/builtin/world-indices/indices.ts +52 -0
- package/src/plugins/builtin/yield-curve/index.tsx +242 -0
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +212 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +107 -0
- package/src/plugins/catalog.ts +22 -0
- package/src/plugins/gridlock-notification.ts +15 -0
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/gateway-service.test.ts +22 -0
- package/src/plugins/ibkr/gateway-service.ts +6 -0
- package/src/plugins/ibkr/index.test.tsx +1 -1
- package/src/plugins/ibkr/index.tsx +8 -2
- package/src/plugins/ibkr/trade-tab.tsx +23 -5
- package/src/plugins/ibkr/trade-utils.test.ts +1 -1
- package/src/plugins/ibkr/trade-utils.ts +7 -6
- package/src/plugins/ibkr/trading-pane.tsx +8 -7
- package/src/plugins/plugin-runtime.test.tsx +2 -0
- package/src/plugins/plugin-runtime.tsx +10 -0
- package/src/plugins/prediction-markets/controller-data.ts +129 -14
- package/src/plugins/prediction-markets/controller.ts +1 -1
- package/src/plugins/prediction-markets/detail/shared.tsx +7 -6
- package/src/plugins/prediction-markets/index.tsx +5 -1
- package/src/plugins/prediction-markets/pane.test.tsx +78 -0
- package/src/plugins/prediction-markets/pane.tsx +7 -3
- package/src/plugins/prediction-markets/plugin.test.tsx +48 -1
- package/src/plugins/prediction-markets/services/fetch.ts +17 -7
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +20 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +22 -5
- package/src/plugins/registry.ts +45 -13
- package/src/sources/provider-router.test.ts +3 -2
- package/src/sources/sec-edgar.ts +13 -1
- package/src/sources/yahoo-finance.ts +75 -1
- package/src/state/app-context-selector.test.tsx +17 -1
- package/src/state/app-context.test.tsx +24 -0
- package/src/state/app-context.tsx +29 -6
- package/src/theme/colors.ts +5 -0
- package/src/theme/themes.ts +25 -0
- package/src/types/config.test.ts +107 -0
- package/src/types/config.ts +30 -1
- package/src/types/data-provider.ts +13 -0
- package/src/types/plugin.ts +35 -1
- package/src/updater.test.ts +2 -0
- package/src/utils/api-client.ts +12 -0
- package/src/utils/format.test.ts +11 -1
- package/src/utils/format.ts +86 -8
- package/src/utils/inline-content-tokenizer.test.ts +22 -0
- package/src/utils/inline-content-tokenizer.ts +76 -0
- package/src/utils/link-tokenizer.test.ts +27 -0
- package/src/utils/link-tokenizer.ts +93 -0
- package/src/utils/market-format.test.ts +109 -0
- package/src/utils/market-format.ts +299 -0
- package/src/utils/market-status.ts +3 -2
- package/src/utils/selection-clipboard.test.ts +43 -0
- package/src/utils/selection-clipboard.ts +88 -0
- package/src/utils/terminal-input-reset.test.ts +50 -0
- package/src/utils/terminal-input-reset.ts +24 -0
- package/src/utils/throttled-fetch.test.ts +145 -0
- package/src/utils/throttled-fetch.ts +212 -0
- package/src/utils/ticker-tokenizer.test.ts +31 -0
- package/src/utils/ticker-tokenizer.ts +7 -42
- package/src/version.ts +1 -1
|
@@ -325,7 +325,7 @@ export function getScreenFooterLeft(route: CommandBarRoute | null): string {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
export function getScreenFooterRight(route: CommandBarRoute | null): string {
|
|
328
|
-
if (!route) return "esc
|
|
328
|
+
if (!route) return "esc cancel";
|
|
329
329
|
|
|
330
330
|
if (route.kind === "workflow") return "esc back";
|
|
331
331
|
if (route.kind === "confirm") return "backspace/esc back";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PaneTemplateDef } from "../../types/plugin";
|
|
1
|
+
import type { CommandDef, PaneTemplateDef } from "../../types/plugin";
|
|
2
2
|
import type { Command } from "./command-registry";
|
|
3
3
|
import { getPaneTemplateDisplayLabel } from "./pane-template-display";
|
|
4
4
|
|
|
@@ -26,10 +26,16 @@ export interface PaneTemplateShortcutIntent extends ShortcutIntentBase {
|
|
|
26
26
|
template: PaneTemplateDef;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export interface PluginCommandShortcutIntent extends ShortcutIntentBase {
|
|
30
|
+
source: "plugin-command";
|
|
31
|
+
command: CommandDef;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export type ShortcutIntent =
|
|
30
35
|
| { kind: "none" }
|
|
31
36
|
| CommandShortcutIntent
|
|
32
|
-
| PaneTemplateShortcutIntent
|
|
37
|
+
| PaneTemplateShortcutIntent
|
|
38
|
+
| PluginCommandShortcutIntent;
|
|
33
39
|
|
|
34
40
|
interface ShortcutParseCandidate {
|
|
35
41
|
prefix: string;
|
|
@@ -37,8 +43,9 @@ interface ShortcutParseCandidate {
|
|
|
37
43
|
description: string;
|
|
38
44
|
argKind: RootShortcutArgKind | null;
|
|
39
45
|
argPlaceholder?: string;
|
|
40
|
-
source: "command" | "pane-template";
|
|
46
|
+
source: "command" | "pane-template" | "plugin-command";
|
|
41
47
|
command?: Command;
|
|
48
|
+
pluginCommand?: CommandDef;
|
|
42
49
|
template?: PaneTemplateDef;
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -71,6 +78,7 @@ function inferShortcutArg(argKind: RootShortcutArgKind | null, activeTicker: str
|
|
|
71
78
|
|
|
72
79
|
function buildShortcutCandidates(
|
|
73
80
|
commands: Command[],
|
|
81
|
+
pluginCommands: CommandDef[],
|
|
74
82
|
paneTemplates: PaneTemplateDef[],
|
|
75
83
|
): ShortcutParseCandidate[] {
|
|
76
84
|
return [
|
|
@@ -85,6 +93,17 @@ function buildShortcutCandidates(
|
|
|
85
93
|
source: "command" as const,
|
|
86
94
|
command,
|
|
87
95
|
})),
|
|
96
|
+
...pluginCommands
|
|
97
|
+
.filter((command) => command.shortcut?.trim().length)
|
|
98
|
+
.map((command) => ({
|
|
99
|
+
prefix: normalizeShortcutPrefix(command.shortcut!),
|
|
100
|
+
label: command.label,
|
|
101
|
+
description: command.description ?? "",
|
|
102
|
+
argKind: command.shortcutArg?.kind ?? (command.shortcutArg ? "text" : null),
|
|
103
|
+
argPlaceholder: command.shortcutArg?.placeholder,
|
|
104
|
+
source: "plugin-command" as const,
|
|
105
|
+
pluginCommand: command,
|
|
106
|
+
})),
|
|
88
107
|
...paneTemplates
|
|
89
108
|
.filter((template) => template.shortcut?.prefix)
|
|
90
109
|
.map((template) => ({
|
|
@@ -102,11 +121,13 @@ function buildShortcutCandidates(
|
|
|
102
121
|
export function parseRootShortcutIntent({
|
|
103
122
|
query,
|
|
104
123
|
commands,
|
|
124
|
+
pluginCommands = [],
|
|
105
125
|
paneTemplates,
|
|
106
126
|
activeTicker,
|
|
107
127
|
}: {
|
|
108
128
|
query: string;
|
|
109
129
|
commands: Command[];
|
|
130
|
+
pluginCommands?: CommandDef[];
|
|
110
131
|
paneTemplates: PaneTemplateDef[];
|
|
111
132
|
activeTicker: string | null;
|
|
112
133
|
}): ShortcutIntent {
|
|
@@ -114,7 +135,7 @@ export function parseRootShortcutIntent({
|
|
|
114
135
|
if (!trimmed) return { kind: "none" };
|
|
115
136
|
|
|
116
137
|
const upper = trimmed.toUpperCase();
|
|
117
|
-
const match = buildShortcutCandidates(commands, paneTemplates).find((candidate) => (
|
|
138
|
+
const match = buildShortcutCandidates(commands, pluginCommands, paneTemplates).find((candidate) => (
|
|
118
139
|
upper === candidate.prefix || upper.startsWith(`${candidate.prefix} `)
|
|
119
140
|
));
|
|
120
141
|
if (!match) return { kind: "none" };
|
|
@@ -151,6 +172,14 @@ export function parseRootShortcutIntent({
|
|
|
151
172
|
};
|
|
152
173
|
}
|
|
153
174
|
|
|
175
|
+
if (match.source === "plugin-command" && match.pluginCommand) {
|
|
176
|
+
return {
|
|
177
|
+
...base,
|
|
178
|
+
source: "plugin-command",
|
|
179
|
+
command: match.pluginCommand,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
154
183
|
return {
|
|
155
184
|
...base,
|
|
156
185
|
source: "pane-template",
|
|
@@ -45,15 +45,15 @@ describe("command bar view model helpers", () => {
|
|
|
45
45
|
test("returns footer hints for plugin toggles", () => {
|
|
46
46
|
expect(getFooterHints("plugins", false)).toEqual({
|
|
47
47
|
left: "up/down move enter select space toggle",
|
|
48
|
-
right: "esc
|
|
48
|
+
right: "esc cancel",
|
|
49
49
|
});
|
|
50
50
|
expect(getFooterHints("layout", false)).toEqual({
|
|
51
51
|
left: "up/down move enter select",
|
|
52
|
-
right: "esc
|
|
52
|
+
right: "esc cancel",
|
|
53
53
|
});
|
|
54
54
|
expect(getFooterHints("new-pane", false)).toEqual({
|
|
55
55
|
left: "up/down move enter select",
|
|
56
|
-
right: "esc
|
|
56
|
+
right: "esc cancel",
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
|
|
@@ -111,16 +111,16 @@ export function getFooterHints(mode: CommandBarMode, isNarrow: boolean): Command
|
|
|
111
111
|
if (mode === "plugins") {
|
|
112
112
|
return {
|
|
113
113
|
left: isNarrow ? "space toggle" : `${moveAndSelect} space toggle`,
|
|
114
|
-
right: "esc
|
|
114
|
+
right: "esc cancel",
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
if (mode === "layout") {
|
|
118
|
-
return { left: moveAndSelect, right: "esc
|
|
118
|
+
return { left: moveAndSelect, right: "esc cancel" };
|
|
119
119
|
}
|
|
120
120
|
if (mode === "new-pane") {
|
|
121
|
-
return { left: moveAndSelect, right: "esc
|
|
121
|
+
return { left: moveAndSelect, right: "esc cancel" };
|
|
122
122
|
}
|
|
123
|
-
return { left: moveAndSelect, right: "esc
|
|
123
|
+
return { left: moveAndSelect, right: "esc cancel" };
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
export function getEmptyState(mode: CommandBarMode, query: string, searchQuery?: string): { label: string; detail: string } {
|
|
@@ -152,6 +152,16 @@ export function resolvePreferredCollectionTarget(
|
|
|
152
152
|
return options.some((option) => option.id === activeCollectionId) ? activeCollectionId : null;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
export function resolveSoleCollectionTarget(
|
|
156
|
+
state: AppState,
|
|
157
|
+
kind: CollectionKind,
|
|
158
|
+
action: CollectionMembershipAction,
|
|
159
|
+
ticker: TickerRecord | null,
|
|
160
|
+
): string | null {
|
|
161
|
+
const options = getCollectionTargetOptions(state, kind, action, ticker);
|
|
162
|
+
return options.length === 1 ? options[0]!.id : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
export async function resolveTickerInput(
|
|
156
166
|
rawInput: string | undefined,
|
|
157
167
|
activeTicker: string | null,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { colors, floatingPaneBg } from "../../theme/colors";
|
|
3
3
|
import { PaneHeader } from "./pane-header";
|
|
4
|
+
import { getPaneBodyHeight } from "./pane-sizing";
|
|
4
5
|
|
|
5
6
|
interface FloatingPaneWrapperProps {
|
|
6
7
|
title: string;
|
|
@@ -20,6 +21,7 @@ export function FloatingPaneWrapper({
|
|
|
20
21
|
title, x, y, width, height, zIndex, focused, showActions = false, onMouseMove, children,
|
|
21
22
|
}: FloatingPaneWrapperProps) {
|
|
22
23
|
const bg = floatingPaneBg(focused);
|
|
24
|
+
const bodyHeight = getPaneBodyHeight(height);
|
|
23
25
|
|
|
24
26
|
return (
|
|
25
27
|
<box
|
|
@@ -36,7 +38,7 @@ export function FloatingPaneWrapper({
|
|
|
36
38
|
<PaneHeader title={title} width={width} focused={focused} floating showActions={showActions} />
|
|
37
39
|
|
|
38
40
|
{/* Content */}
|
|
39
|
-
<box
|
|
41
|
+
<box height={bodyHeight} overflow="hidden">
|
|
40
42
|
{children}
|
|
41
43
|
</box>
|
|
42
44
|
|
|
@@ -7,6 +7,7 @@ import { useAppState } from "../../state/app-context";
|
|
|
7
7
|
import { getSharedMarketDataCoordinator } from "../../market-data/coordinator";
|
|
8
8
|
import { useQuoteEntry, useResolvedEntryValue } from "../../market-data/hooks";
|
|
9
9
|
import { formatPercentRaw } from "../../utils/format";
|
|
10
|
+
import { formatMarketPrice } from "../../utils/market-format";
|
|
10
11
|
import { marketStateLabel, marketStateColor, getExtendedHoursInfo } from "../../utils/market-status";
|
|
11
12
|
import { VERSION } from "../../version";
|
|
12
13
|
|
|
@@ -31,7 +32,7 @@ function UpdateStatus() {
|
|
|
31
32
|
<box flexDirection="row" gap={1}>
|
|
32
33
|
<spinner name="dots" color={colors.headerText} />
|
|
33
34
|
<text fg={colors.headerText}>
|
|
34
|
-
Downloading v{updateAvailable?.version}
|
|
35
|
+
Downloading v{updateAvailable?.version}: {updateProgress.percent ?? 0}%
|
|
35
36
|
</text>
|
|
36
37
|
</box>
|
|
37
38
|
);
|
|
@@ -45,7 +46,7 @@ function UpdateStatus() {
|
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
48
|
if (updateProgress.phase === "done") {
|
|
48
|
-
return <text fg={colors.headerText}>Update installed
|
|
49
|
+
return <text fg={colors.headerText}>Update installed, restart to apply</text>;
|
|
49
50
|
}
|
|
50
51
|
if (updateProgress.phase === "error") {
|
|
51
52
|
return <text fg={colors.headerText}>Update failed: {updateProgress.error}</text>;
|
|
@@ -71,7 +72,7 @@ function UpdateStatus() {
|
|
|
71
72
|
}
|
|
72
73
|
return (
|
|
73
74
|
<text fg={colors.headerText}>
|
|
74
|
-
v{updateAvailable.version} available —
|
|
75
|
+
v{updateAvailable.version} available — starting download...
|
|
75
76
|
</text>
|
|
76
77
|
);
|
|
77
78
|
}
|
|
@@ -103,7 +104,7 @@ export function Header() {
|
|
|
103
104
|
|
|
104
105
|
const spyColor = spyQuote ? priceColor(spyQuote.change) : colors.headerText;
|
|
105
106
|
const spyText = spyQuote
|
|
106
|
-
? `SPY ${spyQuote.price
|
|
107
|
+
? `SPY ${formatMarketPrice(spyQuote.price, { assetCategory: "ETF" })} ${formatPercentRaw(spyQuote.changePercent)}`
|
|
107
108
|
: "SPY —";
|
|
108
109
|
|
|
109
110
|
// Extended hours info
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { paneBg } from "../../theme/colors";
|
|
3
3
|
import { PaneHeader } from "./pane-header";
|
|
4
|
+
import { getPaneBodyHeight } from "./pane-sizing";
|
|
4
5
|
|
|
5
6
|
interface PaneWrapperProps {
|
|
6
7
|
title?: string;
|
|
@@ -26,6 +27,9 @@ export function PaneWrapper({
|
|
|
26
27
|
children,
|
|
27
28
|
}: PaneWrapperProps) {
|
|
28
29
|
const bg = paneBg(focused);
|
|
30
|
+
const bodyHeight = typeof height === "number"
|
|
31
|
+
? title ? getPaneBodyHeight(height) : height
|
|
32
|
+
: undefined;
|
|
29
33
|
|
|
30
34
|
return (
|
|
31
35
|
<box
|
|
@@ -41,7 +45,7 @@ export function PaneWrapper({
|
|
|
41
45
|
{title && (
|
|
42
46
|
<PaneHeader title={title} width={width} focused={focused} showActions={showActions} />
|
|
43
47
|
)}
|
|
44
|
-
<box flexGrow={1} overflow="hidden">
|
|
48
|
+
<box height={bodyHeight} flexGrow={bodyHeight == null ? 1 : 0} overflow="hidden">
|
|
45
49
|
{children}
|
|
46
50
|
</box>
|
|
47
51
|
</box>
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { TextAttributes } from "@opentui/core";
|
|
2
3
|
import { testRender } from "@opentui/react/test-utils";
|
|
3
4
|
import { DialogProvider } from "@opentui-ui/dialog/react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
4
6
|
import { act, useReducer } from "react";
|
|
5
7
|
import { AppContext, appReducer, createInitialState } from "../../state/app-context";
|
|
8
|
+
import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../market-data/coordinator";
|
|
9
|
+
import { setSharedDataProviderForTests } from "../../plugins/registry";
|
|
6
10
|
import { cloneLayout, createDefaultConfig } from "../../types/config";
|
|
7
11
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
8
12
|
import { setSharedRegistryForTests } from "../../plugins/registry";
|
|
9
13
|
import { portfolioListPlugin } from "../../plugins/builtin/portfolio-list";
|
|
14
|
+
import type { PaneProps } from "../../types/plugin";
|
|
15
|
+
import { StockChart } from "../chart/stock-chart";
|
|
10
16
|
import { StatusBar } from "./status-bar";
|
|
11
17
|
import { Header } from "./header";
|
|
12
18
|
import { buildNativeWindowState, finalizePaneDragRelease, resolveNativeDockDividers, Shell } from "./shell";
|
|
13
19
|
import type { DataProvider } from "../../types/data-provider";
|
|
14
|
-
import type { Quote } from "../../types/financials";
|
|
20
|
+
import type { PricePoint, Quote, TickerFinancials } from "../../types/financials";
|
|
15
21
|
import type { TickerRecord } from "../../types/ticker";
|
|
16
22
|
|
|
17
23
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
@@ -23,10 +29,14 @@ afterEach(() => {
|
|
|
23
29
|
testSetup = undefined;
|
|
24
30
|
}
|
|
25
31
|
harnessState = null;
|
|
32
|
+
setSharedMarketDataCoordinator(null);
|
|
33
|
+
setSharedDataProviderForTests(undefined);
|
|
26
34
|
setSharedRegistryForTests(undefined);
|
|
27
35
|
});
|
|
28
36
|
|
|
29
|
-
function createShellPluginRegistry(
|
|
37
|
+
function createShellPluginRegistry(options?: {
|
|
38
|
+
tickerDetailComponent?: (props: PaneProps) => ReactNode;
|
|
39
|
+
}): PluginRegistry {
|
|
30
40
|
return {
|
|
31
41
|
panes: new Map([
|
|
32
42
|
["portfolio-list", {
|
|
@@ -38,7 +48,7 @@ function createShellPluginRegistry(): PluginRegistry {
|
|
|
38
48
|
["ticker-detail", {
|
|
39
49
|
id: "ticker-detail",
|
|
40
50
|
name: "Ticker Detail",
|
|
41
|
-
component: () => <text>Ticker Detail Body</text
|
|
51
|
+
component: options?.tickerDetailComponent ?? (() => <text>Ticker Detail Body</text>),
|
|
42
52
|
defaultPosition: "right",
|
|
43
53
|
defaultMode: "floating",
|
|
44
54
|
}],
|
|
@@ -130,45 +140,59 @@ function makeTicker(overrides: Partial<TickerRecord["metadata"]> = {}): TickerRe
|
|
|
130
140
|
};
|
|
131
141
|
}
|
|
132
142
|
|
|
133
|
-
function
|
|
143
|
+
function makePriceHistory(
|
|
144
|
+
length: number,
|
|
145
|
+
startDate = new Date(Date.UTC(2025, 0, 1)),
|
|
146
|
+
): PricePoint[] {
|
|
147
|
+
return Array.from({ length }, (_, index) => ({
|
|
148
|
+
date: new Date(startDate.getTime() + index * 24 * 3600_000),
|
|
149
|
+
open: 100 + index * 0.2,
|
|
150
|
+
high: 101 + index * 0.2,
|
|
151
|
+
low: 99 + index * 0.2,
|
|
152
|
+
close: 100.5 + index * 0.2,
|
|
153
|
+
volume: 1_000 + index * 10,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function createChartShellDataProvider(historyBySymbol: Record<string, PricePoint[]>): DataProvider {
|
|
134
158
|
return {
|
|
135
|
-
id: "test",
|
|
136
|
-
name: "Test",
|
|
137
|
-
async
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
async
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
async getPriceHistory() {
|
|
156
|
-
return [];
|
|
157
|
-
},
|
|
159
|
+
id: "shell-test-provider",
|
|
160
|
+
name: "Shell Test Provider",
|
|
161
|
+
getTickerFinancials: async (symbol) => ({
|
|
162
|
+
annualStatements: [],
|
|
163
|
+
quarterlyStatements: [],
|
|
164
|
+
priceHistory: historyBySymbol[symbol] ?? [],
|
|
165
|
+
}),
|
|
166
|
+
getQuote: async (symbol) => ({
|
|
167
|
+
symbol,
|
|
168
|
+
price: historyBySymbol[symbol]?.[historyBySymbol[symbol]!.length - 1]?.close ?? 0,
|
|
169
|
+
currency: "USD",
|
|
170
|
+
change: 0,
|
|
171
|
+
changePercent: 0,
|
|
172
|
+
lastUpdated: Date.now(),
|
|
173
|
+
}),
|
|
174
|
+
getExchangeRate: async () => 1,
|
|
175
|
+
search: async () => [],
|
|
176
|
+
getNews: async () => [],
|
|
177
|
+
getArticleSummary: async () => null,
|
|
178
|
+
getPriceHistory: async (symbol) => historyBySymbol[symbol] ?? [],
|
|
158
179
|
};
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
function HeaderHarness({
|
|
162
183
|
updateAvailable = null,
|
|
184
|
+
updateProgress = null,
|
|
163
185
|
updateCheckInProgress = false,
|
|
164
186
|
updateNotice = null,
|
|
165
187
|
}: {
|
|
166
188
|
updateAvailable?: ReturnType<typeof createInitialState>["updateAvailable"];
|
|
189
|
+
updateProgress?: ReturnType<typeof createInitialState>["updateProgress"];
|
|
167
190
|
updateCheckInProgress?: boolean;
|
|
168
191
|
updateNotice?: string | null;
|
|
169
192
|
}) {
|
|
170
193
|
const initialState = createInitialState(createDefaultConfig("/tmp/gloomberb-header-test"));
|
|
171
194
|
initialState.updateAvailable = updateAvailable;
|
|
195
|
+
initialState.updateProgress = updateProgress;
|
|
172
196
|
initialState.updateCheckInProgress = updateCheckInProgress;
|
|
173
197
|
initialState.updateNotice = updateNotice;
|
|
174
198
|
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
@@ -249,7 +273,7 @@ function BrokerShellHarness({ pluginRegistry }: { pluginRegistry: PluginRegistry
|
|
|
249
273
|
return (
|
|
250
274
|
<AppContext value={{ state, dispatch }}>
|
|
251
275
|
<box flexDirection="column">
|
|
252
|
-
<Header
|
|
276
|
+
<Header />
|
|
253
277
|
<Shell pluginRegistry={pluginRegistry} />
|
|
254
278
|
<StatusBar />
|
|
255
279
|
</box>
|
|
@@ -257,8 +281,38 @@ function BrokerShellHarness({ pluginRegistry }: { pluginRegistry: PluginRegistry
|
|
|
257
281
|
);
|
|
258
282
|
}
|
|
259
283
|
|
|
284
|
+
function ChartShellHarness({
|
|
285
|
+
pluginRegistry,
|
|
286
|
+
config,
|
|
287
|
+
ticker,
|
|
288
|
+
financials,
|
|
289
|
+
focusedPaneId,
|
|
290
|
+
}: {
|
|
291
|
+
pluginRegistry: PluginRegistry;
|
|
292
|
+
config: ReturnType<typeof createDefaultConfig>;
|
|
293
|
+
ticker: TickerRecord;
|
|
294
|
+
financials: TickerFinancials;
|
|
295
|
+
focusedPaneId: string | null;
|
|
296
|
+
}) {
|
|
297
|
+
const initialState = createInitialState(config);
|
|
298
|
+
initialState.focusedPaneId = focusedPaneId;
|
|
299
|
+
initialState.tickers = new Map([[ticker.metadata.ticker, ticker]]);
|
|
300
|
+
initialState.financials = new Map([[ticker.metadata.ticker, financials]]);
|
|
301
|
+
|
|
302
|
+
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
303
|
+
harnessState = state;
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<AppContext value={{ state, dispatch }}>
|
|
307
|
+
<DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
|
|
308
|
+
<Shell pluginRegistry={pluginRegistry} />
|
|
309
|
+
</DialogProvider>
|
|
310
|
+
</AppContext>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
260
314
|
describe("Header", () => {
|
|
261
|
-
test("shows
|
|
315
|
+
test("shows automatic self-update status for standalone binaries", async () => {
|
|
262
316
|
testSetup = await testRender(
|
|
263
317
|
<HeaderHarness updateAvailable={{
|
|
264
318
|
version: "0.3.0",
|
|
@@ -274,7 +328,8 @@ describe("Header", () => {
|
|
|
274
328
|
const frame = testSetup.captureCharFrame();
|
|
275
329
|
|
|
276
330
|
expect(frame).toContain("v0.3.0 available");
|
|
277
|
-
expect(frame).toContain("
|
|
331
|
+
expect(frame).toContain("starting download");
|
|
332
|
+
expect(frame).not.toContain("press u to update");
|
|
278
333
|
});
|
|
279
334
|
|
|
280
335
|
test("shows the manual npm command when self-update is disabled", async () => {
|
|
@@ -297,7 +352,26 @@ describe("Header", () => {
|
|
|
297
352
|
expect(frame).not.toContain("press u to update");
|
|
298
353
|
});
|
|
299
354
|
|
|
300
|
-
test("shows update
|
|
355
|
+
test("shows update download progress and notices", async () => {
|
|
356
|
+
testSetup = await testRender(
|
|
357
|
+
<HeaderHarness
|
|
358
|
+
updateAvailable={{
|
|
359
|
+
version: "0.3.0",
|
|
360
|
+
tagName: "v0.3.0",
|
|
361
|
+
downloadUrl: "https://example.com/gloomberb",
|
|
362
|
+
publishedAt: "2026-04-01T00:00:00.000Z",
|
|
363
|
+
updateAction: { kind: "self" },
|
|
364
|
+
}}
|
|
365
|
+
updateProgress={{ phase: "downloading", percent: 42 }}
|
|
366
|
+
/>,
|
|
367
|
+
{ width: 120, height: 2 },
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
await testSetup.renderOnce();
|
|
371
|
+
expect(testSetup.captureCharFrame()).toContain("Downloading v0.3.0: 42%");
|
|
372
|
+
|
|
373
|
+
testSetup.renderer.destroy();
|
|
374
|
+
|
|
301
375
|
testSetup = await testRender(
|
|
302
376
|
<HeaderHarness updateCheckInProgress />,
|
|
303
377
|
{ width: 120, height: 2 },
|
|
@@ -497,13 +571,98 @@ describe("Shell", () => {
|
|
|
497
571
|
expect(testSetup.captureCharFrame()).toContain("Settings");
|
|
498
572
|
});
|
|
499
573
|
|
|
574
|
+
test("applies a chart resolution chip on the first click even when the chart pane is unfocused", async () => {
|
|
575
|
+
const symbol = "AAPL";
|
|
576
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-chart-test");
|
|
577
|
+
config.layout = cloneLayout(config.layout);
|
|
578
|
+
config.layouts = [{ name: "Default", layout: cloneLayout(config.layout) }];
|
|
579
|
+
config.layout.instances = config.layout.instances.map((instance) => (
|
|
580
|
+
instance.instanceId === "ticker-detail:main"
|
|
581
|
+
? {
|
|
582
|
+
...instance,
|
|
583
|
+
binding: { kind: "fixed" as const, symbol },
|
|
584
|
+
settings: {
|
|
585
|
+
...(instance.settings ?? {}),
|
|
586
|
+
chartResolution: "auto",
|
|
587
|
+
chartRangePreset: "5Y",
|
|
588
|
+
},
|
|
589
|
+
}
|
|
590
|
+
: instance
|
|
591
|
+
));
|
|
592
|
+
|
|
593
|
+
const ticker = makeTicker({ ticker: symbol, name: symbol });
|
|
594
|
+
const history = makePriceHistory(260);
|
|
595
|
+
const financials: TickerFinancials = {
|
|
596
|
+
annualStatements: [],
|
|
597
|
+
quarterlyStatements: [],
|
|
598
|
+
priceHistory: history,
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const provider = createChartShellDataProvider({ [symbol]: history });
|
|
602
|
+
setSharedDataProviderForTests(provider);
|
|
603
|
+
setSharedMarketDataCoordinator(new MarketDataCoordinator(provider));
|
|
604
|
+
|
|
605
|
+
const pluginRegistry = createShellPluginRegistry({
|
|
606
|
+
tickerDetailComponent: (props) => (
|
|
607
|
+
<StockChart width={props.width} height={props.height} focused={props.focused} />
|
|
608
|
+
),
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
testSetup = await testRender(
|
|
612
|
+
<ChartShellHarness
|
|
613
|
+
pluginRegistry={pluginRegistry}
|
|
614
|
+
config={config}
|
|
615
|
+
ticker={ticker}
|
|
616
|
+
financials={financials}
|
|
617
|
+
focusedPaneId="portfolio-list:main"
|
|
618
|
+
/>,
|
|
619
|
+
{ width: 120, height: 32 },
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
await testSetup.renderOnce();
|
|
623
|
+
await testSetup.renderOnce();
|
|
624
|
+
|
|
625
|
+
const initialLines = testSetup.captureCharFrame().split("\n");
|
|
626
|
+
const resolutionRow = initialLines.findIndex((line) => line.includes("AUTO 1M 5M 15M"));
|
|
627
|
+
const targetResolution = "1W";
|
|
628
|
+
expect(resolutionRow).toBeGreaterThanOrEqual(0);
|
|
629
|
+
const resolutionSpansBefore = testSetup.captureSpans().lines[resolutionRow]?.spans ?? [];
|
|
630
|
+
let resolutionCol = -1;
|
|
631
|
+
let spanColumn = 0;
|
|
632
|
+
for (const span of resolutionSpansBefore) {
|
|
633
|
+
const chipOffset = span.text.indexOf(targetResolution);
|
|
634
|
+
if (chipOffset >= 0) {
|
|
635
|
+
resolutionCol = spanColumn + chipOffset + Math.floor(targetResolution.length / 2);
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
spanColumn += span.width;
|
|
639
|
+
}
|
|
640
|
+
expect(resolutionCol).toBeGreaterThanOrEqual(0);
|
|
641
|
+
expect(testSetup.captureCharFrame()).toContain("AAPL - AUTO");
|
|
642
|
+
|
|
643
|
+
await act(async () => {
|
|
644
|
+
await testSetup!.mockMouse.click(resolutionCol, resolutionRow);
|
|
645
|
+
await testSetup!.renderOnce();
|
|
646
|
+
});
|
|
647
|
+
await testSetup.renderOnce();
|
|
648
|
+
await testSetup.renderOnce();
|
|
649
|
+
|
|
650
|
+
expect(
|
|
651
|
+
harnessState?.config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main")?.settings?.chartResolution,
|
|
652
|
+
).toBe("1wk");
|
|
653
|
+
const resolutionSpans = testSetup.captureSpans().lines[resolutionRow]?.spans ?? [];
|
|
654
|
+
expect(
|
|
655
|
+
resolutionSpans.some((span) => span.text === targetResolution && (span.attributes & TextAttributes.BOLD) !== 0),
|
|
656
|
+
).toBe(true);
|
|
657
|
+
});
|
|
658
|
+
|
|
500
659
|
test("keeps the cash drawer and gridlock tip on distinct click rows in the full app layout", async () => {
|
|
501
660
|
const pluginRegistry = createBrokerPortfolioRegistry();
|
|
502
661
|
const layoutUpdates: unknown[] = [];
|
|
503
662
|
const toasts: string[] = [];
|
|
504
663
|
pluginRegistry.getLayoutFn = () => harnessState?.config.layout ?? { dockRoot: null, instances: [], floating: [] };
|
|
505
664
|
pluginRegistry.updateLayoutFn = (layout) => { layoutUpdates.push(layout); };
|
|
506
|
-
pluginRegistry.
|
|
665
|
+
pluginRegistry.notify = ({ body }: { body: string }) => { toasts.push(body); };
|
|
507
666
|
setSharedRegistryForTests(pluginRegistry);
|
|
508
667
|
|
|
509
668
|
testSetup = await testRender(
|
|
@@ -648,4 +807,76 @@ describe("Shell", () => {
|
|
|
648
807
|
expect(updateLayout?.layout.floating).toEqual([]);
|
|
649
808
|
expect(updateLayout?.layout.dockRoot).toEqual({ kind: "pane", instanceId: "portfolio-list:main" });
|
|
650
809
|
});
|
|
810
|
+
|
|
811
|
+
test("keeps a floating pane at the preview rect after a free drag release", () => {
|
|
812
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-drag-test");
|
|
813
|
+
const detailPane = config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main");
|
|
814
|
+
if (!detailPane) throw new Error("missing detail pane");
|
|
815
|
+
const floatingOnlyLayout = {
|
|
816
|
+
dockRoot: null,
|
|
817
|
+
instances: [{ ...detailPane, binding: { kind: "fixed" as const, symbol: "AAPL" } }],
|
|
818
|
+
floating: [{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8, zIndex: 75 }],
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const result = finalizePaneDragRelease(
|
|
822
|
+
floatingOnlyLayout,
|
|
823
|
+
"ticker-detail:main",
|
|
824
|
+
{ x: 14, y: 4, width: 30, height: 8 },
|
|
825
|
+
null,
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
expect(result.shouldShowGridlockTip).toBe(false);
|
|
829
|
+
expect(result.nextLayout.floating[0]).toEqual(expect.objectContaining({
|
|
830
|
+
instanceId: "ticker-detail:main",
|
|
831
|
+
x: 14,
|
|
832
|
+
y: 4,
|
|
833
|
+
width: 30,
|
|
834
|
+
height: 8,
|
|
835
|
+
}));
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
test("keeps the last floating pane body row visible above the border", async () => {
|
|
839
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-footer-test");
|
|
840
|
+
const detailPane = config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main");
|
|
841
|
+
if (!detailPane) throw new Error("missing detail pane");
|
|
842
|
+
|
|
843
|
+
const floatingOnlyLayout = {
|
|
844
|
+
dockRoot: null,
|
|
845
|
+
instances: [{ ...detailPane }],
|
|
846
|
+
floating: [{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8, zIndex: 75 }],
|
|
847
|
+
};
|
|
848
|
+
const state = {
|
|
849
|
+
...createInitialState({
|
|
850
|
+
...config,
|
|
851
|
+
layout: cloneLayout(floatingOnlyLayout),
|
|
852
|
+
layouts: [{ name: "Default", layout: cloneLayout(floatingOnlyLayout) }],
|
|
853
|
+
}),
|
|
854
|
+
focusedPaneId: "ticker-detail:main",
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
testSetup = await testRender(
|
|
858
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
859
|
+
<DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
|
|
860
|
+
<Shell pluginRegistry={createShellPluginRegistry({
|
|
861
|
+
tickerDetailComponent: ({ width, height }) => (
|
|
862
|
+
<box flexDirection="column" width={width} height={height}>
|
|
863
|
+
<box flexGrow={1} />
|
|
864
|
+
<box paddingLeft={1}>
|
|
865
|
+
<text>Footer Probe</text>
|
|
866
|
+
</box>
|
|
867
|
+
</box>
|
|
868
|
+
),
|
|
869
|
+
})} />
|
|
870
|
+
</DialogProvider>
|
|
871
|
+
</AppContext>,
|
|
872
|
+
{ width: 40, height: 12 },
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
await act(async () => {
|
|
876
|
+
await testSetup!.renderOnce();
|
|
877
|
+
await testSetup!.renderOnce();
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
expect(testSetup.captureCharFrame()).toContain("Footer Probe");
|
|
881
|
+
});
|
|
651
882
|
});
|