gloomberb 0.9.2 → 0.9.4
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 +24 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +20 -8
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +34 -6
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +40 -4
- package/src/components/layout/pane/index.tsx +3 -1
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/cursor-occlusion.ts +196 -0
- package/src/components/layout/shell/drag/runtime.ts +28 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +487 -4
- package/src/components/layout/shell/index.tsx +165 -4
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +34 -16
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +111 -1
- package/src/components/layout/status-bar.tsx +37 -7
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/price-performance.tsx +9 -1
- package/src/components/table-view-shared.tsx +12 -4
- package/src/components/ui/data-table/opentui/index.tsx +25 -12
- package/src/components/ui/data-table/types.ts +4 -0
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +26 -10
- package/src/components/ui/ui.test.tsx +87 -0
- package/src/core/app-services.ts +7 -4
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +75 -0
- package/src/plugins/prediction-markets/pane.tsx +71 -23
- package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/index.ts +13 -0
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
- package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/styles.css +11 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/plugin.ts +28 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -2,11 +2,18 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { TestDialogProvider, testRender } from "../../../renderers/opentui/test-utils";
|
|
3
3
|
import { openTuiUiHost } from "../../../renderers/opentui/ui-host";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
|
-
import { act, useReducer } from "react";
|
|
6
|
-
import {
|
|
5
|
+
import { act, useReducer, useState } from "react";
|
|
6
|
+
import {
|
|
7
|
+
AppContext,
|
|
8
|
+
appReducer,
|
|
9
|
+
createInitialState,
|
|
10
|
+
resolveTickerForPane,
|
|
11
|
+
usePaneTicker,
|
|
12
|
+
} from "../../../state/app/context";
|
|
7
13
|
import { cloneLayout, createDefaultConfig, TICKER_RESEARCH_PANE_ID, type LayoutConfig } from "../../../types/config";
|
|
8
14
|
import type { PluginRegistry } from "../../../plugins/registry";
|
|
9
15
|
import type { PaneProps } from "../../../types/plugin";
|
|
16
|
+
import { Textarea } from "../../../ui";
|
|
10
17
|
import { Header } from "../header";
|
|
11
18
|
import {
|
|
12
19
|
buildNativeWindowState,
|
|
@@ -14,6 +21,8 @@ import {
|
|
|
14
21
|
resolveAppHeaderHeightCells,
|
|
15
22
|
Shell,
|
|
16
23
|
} from "./index";
|
|
24
|
+
import { resolvePaneFocusSourceLayout } from "./fullscreen";
|
|
25
|
+
import { TransientLayoutProvider, useTransientLayout, type TransientLayoutState } from "../transient-layout";
|
|
17
26
|
|
|
18
27
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
19
28
|
|
|
@@ -133,6 +142,44 @@ async function renderShellForWindowModeTest(
|
|
|
133
142
|
return { actions, registry };
|
|
134
143
|
}
|
|
135
144
|
|
|
145
|
+
function CaptureTransientLayout({
|
|
146
|
+
controls,
|
|
147
|
+
}: {
|
|
148
|
+
controls: { transientLayout: TransientLayoutState | null };
|
|
149
|
+
}) {
|
|
150
|
+
const { transientLayout } = useTransientLayout();
|
|
151
|
+
controls.transientLayout = transientLayout;
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function ShellTransientHarness({
|
|
156
|
+
initialState,
|
|
157
|
+
registry,
|
|
158
|
+
controls,
|
|
159
|
+
}: {
|
|
160
|
+
initialState: ReturnType<typeof createInitialState>;
|
|
161
|
+
registry: PluginRegistry;
|
|
162
|
+
controls: {
|
|
163
|
+
dispatch?: (action: ShellTestAction) => void;
|
|
164
|
+
state?: ReturnType<typeof createInitialState>;
|
|
165
|
+
transientLayout: TransientLayoutState | null;
|
|
166
|
+
};
|
|
167
|
+
}) {
|
|
168
|
+
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
169
|
+
controls.dispatch = dispatch;
|
|
170
|
+
controls.state = state;
|
|
171
|
+
return (
|
|
172
|
+
<AppContext value={{ state, dispatch }}>
|
|
173
|
+
<TransientLayoutProvider>
|
|
174
|
+
<TestDialogProvider>
|
|
175
|
+
<Shell pluginRegistry={registry} />
|
|
176
|
+
</TestDialogProvider>
|
|
177
|
+
<CaptureTransientLayout controls={controls} />
|
|
178
|
+
</TransientLayoutProvider>
|
|
179
|
+
</AppContext>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
136
183
|
function findUpdateLayout(actions: ShellTestAction[]) {
|
|
137
184
|
return actions.find((action) => action.type === "UPDATE_LAYOUT");
|
|
138
185
|
}
|
|
@@ -176,7 +223,7 @@ describe("Header", () => {
|
|
|
176
223
|
);
|
|
177
224
|
|
|
178
225
|
await testSetup.renderOnce();
|
|
179
|
-
const frame = testSetup
|
|
226
|
+
const frame = testSetup!.captureCharFrame();
|
|
180
227
|
|
|
181
228
|
expect(frame).toContain("v0.3.0 available");
|
|
182
229
|
expect(frame).toContain("starting download");
|
|
@@ -196,7 +243,7 @@ describe("Header", () => {
|
|
|
196
243
|
);
|
|
197
244
|
|
|
198
245
|
await testSetup.renderOnce();
|
|
199
|
-
const frame = testSetup
|
|
246
|
+
const frame = testSetup!.captureCharFrame();
|
|
200
247
|
|
|
201
248
|
expect(frame).toContain("v0.3.0 available");
|
|
202
249
|
expect(frame).toContain("run npm install -g gloomberb@latest");
|
|
@@ -417,6 +464,7 @@ describe("Shell", () => {
|
|
|
417
464
|
expect(resolvePaneManagementShortcut({ ...base, name: "o", key: "o" })).toBe("pop-out");
|
|
418
465
|
expect(resolvePaneManagementShortcut({ ...base, name: "c", key: "c" })).toBe("copy-screenshot");
|
|
419
466
|
expect(resolvePaneManagementShortcut({ ...base, name: "l", key: "l" })).toBe("layout-actions");
|
|
467
|
+
expect(resolvePaneManagementShortcut({ ...base, name: "f", key: "f" })).toBe("toggle-fullscreen");
|
|
420
468
|
expect(resolvePaneManagementShortcut({ ...base, name: "g", key: "g" })).toBe("gridlock-all");
|
|
421
469
|
expect(resolvePaneManagementShortcut({ ...base, name: "m", key: "m" })).toBe("window-mode");
|
|
422
470
|
expect(resolvePaneManagementShortcut({ ...base, name: "r", key: "r" })).toBe("window-resize-mode");
|
|
@@ -425,6 +473,441 @@ describe("Shell", () => {
|
|
|
425
473
|
expect(resolvePaneManagementShortcut({ ...base, name: "d", key: "d", meta: false, super: false })).toBeNull();
|
|
426
474
|
});
|
|
427
475
|
|
|
476
|
+
test("toggles the focused pane fullscreen without persisting layout", async () => {
|
|
477
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-shortcut-test");
|
|
478
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
479
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
480
|
+
const dockedLayout = {
|
|
481
|
+
dockRoot: {
|
|
482
|
+
kind: "split" as const,
|
|
483
|
+
axis: "horizontal" as const,
|
|
484
|
+
ratio: 0.5,
|
|
485
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
486
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
487
|
+
},
|
|
488
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
489
|
+
floating: [],
|
|
490
|
+
detached: [],
|
|
491
|
+
};
|
|
492
|
+
const { actions } = await renderShellForWindowModeTest(
|
|
493
|
+
createShellStateWithLayout(config, dockedLayout, "portfolio-list:main"),
|
|
494
|
+
{ width: 80, height: 18 },
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
expect(testSetup.captureCharFrame()).toContain("Main Portfolio");
|
|
498
|
+
expect(testSetup.captureCharFrame()).toContain("Ticker Research Body");
|
|
499
|
+
|
|
500
|
+
await emitKeypress({ name: "f", ctrl: true, shift: true });
|
|
501
|
+
let frame = testSetup.captureCharFrame();
|
|
502
|
+
expect(frame).toContain("Main Portfolio");
|
|
503
|
+
expect(frame).not.toContain("Ticker Research Body");
|
|
504
|
+
expect(actions.some((action) => action.type === "UPDATE_LAYOUT")).toBe(false);
|
|
505
|
+
|
|
506
|
+
await emitKeypress({ name: "f", ctrl: true, shift: true });
|
|
507
|
+
await act(async () => {
|
|
508
|
+
await testSetup!.renderOnce();
|
|
509
|
+
});
|
|
510
|
+
frame = testSetup.captureCharFrame();
|
|
511
|
+
expect(frame).toContain("Main Portfolio");
|
|
512
|
+
expect(frame).toContain("Ticker Research Body");
|
|
513
|
+
expect(actions.some((action) => action.type === "UPDATE_LAYOUT")).toBe(false);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("captures the source layout for transient pane focus", () => {
|
|
517
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-layout-test");
|
|
518
|
+
const layout = cloneLayout(config.layout);
|
|
519
|
+
layout.dockRoot = { kind: "pane", instanceId: "portfolio-list:main" };
|
|
520
|
+
layout.floating = [{ instanceId: "ticker-detail:main", x: 8, y: 2, width: 32, height: 10, zIndex: 75 }];
|
|
521
|
+
|
|
522
|
+
const focusedLayout = resolvePaneFocusSourceLayout(layout, "ticker-detail:main");
|
|
523
|
+
|
|
524
|
+
expect(focusedLayout).toMatchObject({
|
|
525
|
+
dockRoot: { kind: "pane", instanceId: "portfolio-list:main" },
|
|
526
|
+
floating: [{ instanceId: "ticker-detail:main", x: 8, y: 2, width: 32, height: 10, zIndex: 75 }],
|
|
527
|
+
});
|
|
528
|
+
expect(focusedLayout).not.toBe(layout);
|
|
529
|
+
expect(focusedLayout?.instances.map((instance) => instance.instanceId)).toEqual(
|
|
530
|
+
layout.instances.map((instance) => instance.instanceId),
|
|
531
|
+
);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("locks layout-changing mouse drags while fullscreen is active", async () => {
|
|
535
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-drag-test");
|
|
536
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
537
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
538
|
+
const dockedLayout = {
|
|
539
|
+
dockRoot: {
|
|
540
|
+
kind: "split" as const,
|
|
541
|
+
axis: "horizontal" as const,
|
|
542
|
+
ratio: 0.5,
|
|
543
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
544
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
545
|
+
},
|
|
546
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
547
|
+
floating: [],
|
|
548
|
+
detached: [],
|
|
549
|
+
};
|
|
550
|
+
const { actions } = await renderShellForWindowModeTest(
|
|
551
|
+
createShellStateWithLayout(config, dockedLayout, "portfolio-list:main"),
|
|
552
|
+
{ width: 80, height: 18 },
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
await emitKeypress({ name: "f", ctrl: true, shift: true });
|
|
556
|
+
await act(async () => {
|
|
557
|
+
await testSetup!.mockMouse.drag(4, 0, 32, 4);
|
|
558
|
+
await testSetup!.renderOnce();
|
|
559
|
+
await testSetup!.renderOnce();
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const frame = testSetup.captureCharFrame();
|
|
563
|
+
expect(frame).toContain("Main Portfolio");
|
|
564
|
+
expect(frame).not.toContain("Ticker Research Body");
|
|
565
|
+
expect(actions.some((action) => action.type === "UPDATE_LAYOUT")).toBe(false);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("updates the floating pane preview before mouse release", async () => {
|
|
569
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-live-floating-drag-test");
|
|
570
|
+
const floatingLayout = cloneLayout(config.layout);
|
|
571
|
+
floatingLayout.dockRoot = { kind: "pane", instanceId: "portfolio-list:main" };
|
|
572
|
+
floatingLayout.floating = [{ instanceId: "ticker-detail:main", x: 8, y: 2, width: 32, height: 10, zIndex: 75 }];
|
|
573
|
+
await renderShellForWindowModeTest(
|
|
574
|
+
createShellStateWithLayout(config, floatingLayout, "ticker-detail:main"),
|
|
575
|
+
{ width: 80, height: 18 },
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
await act(async () => {
|
|
579
|
+
await testSetup!.mockMouse.pressDown(10, 3);
|
|
580
|
+
await testSetup!.renderOnce();
|
|
581
|
+
await testSetup!.mockMouse.moveTo(16, 6);
|
|
582
|
+
await testSetup!.renderOnce();
|
|
583
|
+
await testSetup!.renderOnce();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const frame = testSetup!.captureCharFrame();
|
|
587
|
+
const rows = frame.split("\n");
|
|
588
|
+
expect(rows[2]?.indexOf("┌─:: Main Portfolio") ?? -1).toBeLessThan(0);
|
|
589
|
+
expect(rows[5]?.indexOf("┌─:: Main Portfolio")).toBeGreaterThanOrEqual(14);
|
|
590
|
+
|
|
591
|
+
await act(async () => {
|
|
592
|
+
await testSetup!.mockMouse.release(16, 6);
|
|
593
|
+
await testSetup!.renderOnce();
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("keeps the focused textarea cursor visible when it is not covered", async () => {
|
|
598
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-cursor-visible-test");
|
|
599
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
600
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
601
|
+
const registry = createShellPluginRegistry({
|
|
602
|
+
portfolioListComponent: ({ focused, width, height }) => (
|
|
603
|
+
<Textarea
|
|
604
|
+
initialValue=""
|
|
605
|
+
focused={focused}
|
|
606
|
+
width={width}
|
|
607
|
+
height={Math.max(1, height)}
|
|
608
|
+
/>
|
|
609
|
+
),
|
|
610
|
+
tickerDetailComponent: () => <text>Chart Body</text>,
|
|
611
|
+
});
|
|
612
|
+
const layout = {
|
|
613
|
+
dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
614
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
615
|
+
floating: [{ instanceId: "ticker-detail:main", x: 30, y: 2, width: 24, height: 8, zIndex: 75 }],
|
|
616
|
+
detached: [],
|
|
617
|
+
};
|
|
618
|
+
await renderShellForWindowModeTest(
|
|
619
|
+
createShellStateWithLayout(config, layout, "portfolio-list:main"),
|
|
620
|
+
{ registry, width: 80, height: 18 },
|
|
621
|
+
);
|
|
622
|
+
await testSetup!.renderOnce();
|
|
623
|
+
|
|
624
|
+
expect(testSetup!.renderer.getCursorState().visible).toBe(true);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test("hides the textarea cursor when a higher floating pane covers it", async () => {
|
|
628
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-cursor-occlusion-test");
|
|
629
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
630
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
631
|
+
const registry = createShellPluginRegistry({
|
|
632
|
+
portfolioListComponent: ({ focused, width, height }) => (
|
|
633
|
+
<Textarea
|
|
634
|
+
initialValue=""
|
|
635
|
+
focused={focused}
|
|
636
|
+
width={width}
|
|
637
|
+
height={Math.max(1, height)}
|
|
638
|
+
/>
|
|
639
|
+
),
|
|
640
|
+
tickerDetailComponent: () => <text>Chart Body</text>,
|
|
641
|
+
});
|
|
642
|
+
const layout = {
|
|
643
|
+
dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
644
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
645
|
+
floating: [{ instanceId: "ticker-detail:main", x: 0, y: 0, width: 40, height: 8, zIndex: 75 }],
|
|
646
|
+
detached: [],
|
|
647
|
+
};
|
|
648
|
+
await renderShellForWindowModeTest(
|
|
649
|
+
createShellStateWithLayout(config, layout, "portfolio-list:main"),
|
|
650
|
+
{ registry, width: 80, height: 18 },
|
|
651
|
+
);
|
|
652
|
+
await testSetup!.renderOnce();
|
|
653
|
+
|
|
654
|
+
expect(testSetup!.renderer.getCursorState().visible).toBe(false);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("keeps focused pane local detail state when maximizing a floating pane", async () => {
|
|
658
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-local-state-test");
|
|
659
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
660
|
+
let openDetail: (() => void) | null = null;
|
|
661
|
+
const registry = createShellPluginRegistry({
|
|
662
|
+
tickerDetailComponent: () => {
|
|
663
|
+
const [detailOpen, setDetailOpen] = useState(false);
|
|
664
|
+
openDetail = () => setDetailOpen(true);
|
|
665
|
+
return <text>{detailOpen ? "Detail State" : "Table State"}</text>;
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
const floatingLayout = {
|
|
669
|
+
dockRoot: null,
|
|
670
|
+
instances: [{ ...detailPane }],
|
|
671
|
+
floating: [{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 40, height: 10, zIndex: 50 }],
|
|
672
|
+
detached: [],
|
|
673
|
+
};
|
|
674
|
+
await renderShellForWindowModeTest(
|
|
675
|
+
createShellStateWithLayout(config, floatingLayout, "ticker-detail:main"),
|
|
676
|
+
{ registry, width: 80, height: 18 },
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
await act(async () => {
|
|
680
|
+
openDetail?.();
|
|
681
|
+
await testSetup!.renderOnce();
|
|
682
|
+
});
|
|
683
|
+
expect(testSetup.captureCharFrame()).toContain("etail State");
|
|
684
|
+
|
|
685
|
+
await emitKeypress({ name: "f", ctrl: true, shift: true });
|
|
686
|
+
|
|
687
|
+
const frame = testSetup.captureCharFrame();
|
|
688
|
+
expect(frame).toContain("etail State");
|
|
689
|
+
expect(frame).not.toContain("able State");
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("allows pane fullscreen shortcut while text input is captured on desktop", async () => {
|
|
693
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-fullscreen-captured-input-test");
|
|
694
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
695
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
696
|
+
const dockedLayout = {
|
|
697
|
+
dockRoot: {
|
|
698
|
+
kind: "split" as const,
|
|
699
|
+
axis: "horizontal" as const,
|
|
700
|
+
ratio: 0.5,
|
|
701
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
702
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
703
|
+
},
|
|
704
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
705
|
+
floating: [],
|
|
706
|
+
detached: [],
|
|
707
|
+
};
|
|
708
|
+
const { actions } = await renderShellForWindowModeTest(
|
|
709
|
+
{
|
|
710
|
+
...createShellStateWithLayout(config, dockedLayout, "portfolio-list:main"),
|
|
711
|
+
inputCaptured: true,
|
|
712
|
+
},
|
|
713
|
+
{ width: 80, height: 18 },
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
await emitKeypress({
|
|
717
|
+
name: "f",
|
|
718
|
+
key: "f",
|
|
719
|
+
meta: true,
|
|
720
|
+
super: true,
|
|
721
|
+
shift: true,
|
|
722
|
+
targetEditable: true,
|
|
723
|
+
} as any);
|
|
724
|
+
|
|
725
|
+
const frame = testSetup.captureCharFrame();
|
|
726
|
+
expect(frame).toContain("Main Portfolio");
|
|
727
|
+
expect(frame).not.toContain("Ticker Research Body");
|
|
728
|
+
expect(actions.some((action) => action.type === "UPDATE_LAYOUT")).toBe(false);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("keeps the transient focus layout available after switching saved layouts", async () => {
|
|
732
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-transient-focus-layout-tab-test");
|
|
733
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
734
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
735
|
+
const defaultLayout = {
|
|
736
|
+
dockRoot: {
|
|
737
|
+
kind: "split" as const,
|
|
738
|
+
axis: "horizontal" as const,
|
|
739
|
+
ratio: 0.5,
|
|
740
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
741
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
742
|
+
},
|
|
743
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
744
|
+
floating: [],
|
|
745
|
+
detached: [],
|
|
746
|
+
};
|
|
747
|
+
const monitorLayout = {
|
|
748
|
+
dockRoot: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
749
|
+
instances: [{ ...detailPane }],
|
|
750
|
+
floating: [],
|
|
751
|
+
detached: [],
|
|
752
|
+
};
|
|
753
|
+
config.layout = cloneLayout(defaultLayout);
|
|
754
|
+
config.layouts = [
|
|
755
|
+
{ name: "Default", layout: cloneLayout(defaultLayout), focusedPaneId: "portfolio-list:main" },
|
|
756
|
+
{ name: "Monitor", layout: cloneLayout(monitorLayout), focusedPaneId: "ticker-detail:main" },
|
|
757
|
+
];
|
|
758
|
+
config.activeLayoutIndex = 0;
|
|
759
|
+
|
|
760
|
+
const initialState = {
|
|
761
|
+
...createInitialState(config),
|
|
762
|
+
focusedPaneId: "portfolio-list:main",
|
|
763
|
+
statusBarVisible: true,
|
|
764
|
+
};
|
|
765
|
+
const registry = createShellPluginRegistry();
|
|
766
|
+
const controls: {
|
|
767
|
+
dispatch?: (action: ShellTestAction) => void;
|
|
768
|
+
state?: ReturnType<typeof createInitialState>;
|
|
769
|
+
transientLayout: TransientLayoutState | null;
|
|
770
|
+
} = { transientLayout: null };
|
|
771
|
+
|
|
772
|
+
testSetup = await testRender(
|
|
773
|
+
<ShellTransientHarness initialState={initialState} registry={registry} controls={controls} />,
|
|
774
|
+
{ width: 100, height: 18 },
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
await testSetup.renderOnce();
|
|
778
|
+
await emitKeypress({ name: "f", ctrl: true, shift: true });
|
|
779
|
+
|
|
780
|
+
let frame = testSetup.captureCharFrame();
|
|
781
|
+
expect(frame).toContain("Main Portfolio");
|
|
782
|
+
expect(frame).not.toContain("Ticker Research Body");
|
|
783
|
+
expect(controls.transientLayout).toMatchObject({
|
|
784
|
+
id: "pane-focus",
|
|
785
|
+
active: true,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
await act(async () => {
|
|
789
|
+
controls.transientLayout?.onDeactivate?.();
|
|
790
|
+
controls.dispatch?.({ type: "SWITCH_LAYOUT", index: 1 });
|
|
791
|
+
await testSetup!.renderOnce();
|
|
792
|
+
await testSetup!.renderOnce();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
expect(controls.transientLayout).toMatchObject({
|
|
796
|
+
id: "pane-focus",
|
|
797
|
+
active: false,
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
await act(async () => {
|
|
801
|
+
controls.transientLayout?.onActivate?.();
|
|
802
|
+
await testSetup!.renderOnce();
|
|
803
|
+
await testSetup!.renderOnce();
|
|
804
|
+
await testSetup!.renderOnce();
|
|
805
|
+
await testSetup!.renderOnce();
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
expect(controls.transientLayout).toMatchObject({
|
|
809
|
+
id: "pane-focus",
|
|
810
|
+
active: true,
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("restores the focus source layout state when reactivating transient focus", async () => {
|
|
815
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-transient-focus-state-test");
|
|
816
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
817
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
818
|
+
const defaultLayout = {
|
|
819
|
+
dockRoot: {
|
|
820
|
+
kind: "split" as const,
|
|
821
|
+
axis: "horizontal" as const,
|
|
822
|
+
ratio: 0.5,
|
|
823
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
824
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
825
|
+
},
|
|
826
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
827
|
+
floating: [],
|
|
828
|
+
detached: [],
|
|
829
|
+
};
|
|
830
|
+
const monitorLayout = cloneLayout(defaultLayout);
|
|
831
|
+
config.layout = cloneLayout(defaultLayout);
|
|
832
|
+
config.layouts = [
|
|
833
|
+
{
|
|
834
|
+
name: "Default",
|
|
835
|
+
layout: cloneLayout(defaultLayout),
|
|
836
|
+
focusedPaneId: "ticker-detail:main",
|
|
837
|
+
paneState: {
|
|
838
|
+
"portfolio-list:main": { collectionId: "main", cursorSymbol: "MSTR" },
|
|
839
|
+
"ticker-detail:main": { activeTabId: "overview" },
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "Monitor",
|
|
844
|
+
layout: monitorLayout,
|
|
845
|
+
focusedPaneId: "ticker-detail:main",
|
|
846
|
+
paneState: {
|
|
847
|
+
"portfolio-list:main": { collectionId: "main", cursorSymbol: null },
|
|
848
|
+
"ticker-detail:main": { activeTabId: "overview" },
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
];
|
|
852
|
+
config.activeLayoutIndex = 0;
|
|
853
|
+
const registry = createShellPluginRegistry({
|
|
854
|
+
tickerDetailComponent: () => {
|
|
855
|
+
const { symbol } = usePaneTicker();
|
|
856
|
+
return <text>{`Ticker:${symbol ?? "none"}`}</text>;
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
const controls: {
|
|
860
|
+
dispatch?: (action: ShellTestAction) => void;
|
|
861
|
+
state?: ReturnType<typeof createInitialState>;
|
|
862
|
+
transientLayout: TransientLayoutState | null;
|
|
863
|
+
} = { transientLayout: null };
|
|
864
|
+
|
|
865
|
+
testSetup = await testRender(
|
|
866
|
+
<ShellTransientHarness
|
|
867
|
+
initialState={createInitialState(config)}
|
|
868
|
+
registry={registry}
|
|
869
|
+
controls={controls}
|
|
870
|
+
/>,
|
|
871
|
+
{ width: 100, height: 18 },
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
await testSetup.renderOnce();
|
|
875
|
+
expect(testSetup.captureCharFrame()).toContain("icker:MSTR");
|
|
876
|
+
|
|
877
|
+
await emitKeypress({ name: "f", ctrl: true, shift: true });
|
|
878
|
+
expect(testSetup.captureCharFrame()).toContain("icker:MSTR");
|
|
879
|
+
|
|
880
|
+
await act(async () => {
|
|
881
|
+
controls.transientLayout?.onDeactivate?.();
|
|
882
|
+
controls.dispatch?.({ type: "SWITCH_LAYOUT", index: 1 });
|
|
883
|
+
await testSetup!.renderOnce();
|
|
884
|
+
await testSetup!.renderOnce();
|
|
885
|
+
});
|
|
886
|
+
expect(controls.transientLayout).toMatchObject({
|
|
887
|
+
id: "pane-focus",
|
|
888
|
+
active: false,
|
|
889
|
+
});
|
|
890
|
+
expect(controls.state?.config.activeLayoutIndex).toBe(1);
|
|
891
|
+
expect(controls.state?.paneState["portfolio-list:main"]?.cursorSymbol).toBeNull();
|
|
892
|
+
expect(controls.state ? resolveTickerForPane(controls.state, "ticker-detail:main") : null).toBeNull();
|
|
893
|
+
|
|
894
|
+
await act(async () => {
|
|
895
|
+
controls.transientLayout?.onActivate?.();
|
|
896
|
+
await testSetup!.renderOnce();
|
|
897
|
+
await testSetup!.renderOnce();
|
|
898
|
+
await testSetup!.renderOnce();
|
|
899
|
+
await testSetup!.renderOnce();
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
expect(controls.transientLayout).toMatchObject({
|
|
903
|
+
id: "pane-focus",
|
|
904
|
+
active: true,
|
|
905
|
+
});
|
|
906
|
+
expect(controls.state?.config.activeLayoutIndex).toBe(0);
|
|
907
|
+
expect(controls.state?.paneState["portfolio-list:main"]?.cursorSymbol).toBe("MSTR");
|
|
908
|
+
expect(controls.state ? resolveTickerForPane(controls.state, "ticker-detail:main") : null).toBe("MSTR");
|
|
909
|
+
});
|
|
910
|
+
|
|
428
911
|
test("exits window mode on Enter without changes", async () => {
|
|
429
912
|
const config = createDefaultConfig("/tmp/gloomberb-shell-window-mode-resize-shortcut-test");
|
|
430
913
|
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|