gloomberb 0.7.4 → 0.8.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 +27 -6
- package/package.json +2 -3
- package/src/api-client/auth.ts +167 -0
- package/src/api-client/chat.ts +116 -0
- package/src/api-client/data.ts +177 -0
- package/src/api-client/errors.ts +28 -0
- package/src/api-client/index.test.ts +675 -0
- package/src/api-client/index.ts +384 -0
- package/src/api-client/normalizers.ts +61 -0
- package/src/api-client/paths.ts +173 -0
- package/src/api-client/request.ts +124 -0
- package/src/api-client/socket.ts +483 -0
- package/src/api-client/types.ts +490 -0
- package/src/app/app-bootstrap-state.test.ts +157 -0
- package/src/app/app-bootstrap-state.ts +134 -0
- package/src/app/global-shortcuts.test.tsx +166 -0
- package/src/app/global-shortcuts.ts +126 -0
- package/src/app/pane-runtime/index.tsx +297 -0
- package/src/app/pane-runtime/layout-placement.ts +73 -0
- package/src/app/pane-runtime/pane-settings-runtime.tsx +67 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +110 -0
- package/src/app/pane-runtime/plugin-bindings.ts +231 -0
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +167 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +144 -0
- package/src/app/pane-template-dialog-wizard.tsx +52 -0
- package/src/app/runtime/broker-import.ts +109 -0
- package/src/app/runtime/desktop-menu.ts +74 -0
- package/src/app/runtime/plugin-bindings.ts +246 -0
- package/src/app/runtime/startup.ts +126 -0
- package/src/app/runtime/ticker-refresh.ts +249 -0
- package/src/app/runtime/update.ts +86 -0
- package/src/app.tsx +110 -1461
- package/src/brokers/broker-portfolio-sync.ts +186 -0
- package/src/brokers/broker-ticker-sync.ts +141 -0
- package/src/brokers/profile-form.ts +2 -2
- package/src/brokers/remote-broker-adapter.ts +3 -2
- package/src/brokers/sync-broker-instance.test.ts +140 -0
- package/src/brokers/sync-broker-instance.ts +60 -181
- package/src/capabilities/types.ts +4 -4
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/ticker.ts +3 -3
- package/src/cli/context.ts +1 -1
- package/src/cli/desktop-pane-shot.ts +305 -0
- package/src/cli/helpers.ts +1 -1
- package/src/cli/index.ts +34 -0
- package/src/cli/pane-functions/catalog.ts +243 -0
- package/src/cli/pane-functions/data.ts +78 -0
- package/src/cli/pane-functions/discovery.ts +113 -0
- package/src/cli/pane-functions/index.test.ts +114 -0
- package/src/cli/pane-functions/index.ts +112 -0
- package/src/cli/pane-functions/options.ts +249 -0
- package/src/cli/pane-functions/report.ts +180 -0
- package/src/cli/pane-functions/resolver.ts +96 -0
- package/src/cli/pane-functions/screenshot.ts +113 -0
- package/src/cli/registry.ts +3 -2
- package/src/cli/ticker-resolution.ts +1 -1
- package/src/cli.test.ts +1 -1
- package/src/components/chart/bar-chart-renderer.test.ts +53 -0
- package/src/components/chart/bar-chart-renderer.ts +190 -0
- package/src/components/chart/comparison/data.test.ts +105 -0
- package/src/components/chart/comparison/data.ts +279 -0
- package/src/components/chart/comparison/renderer.test.ts +187 -0
- package/src/components/chart/comparison/renderer.ts +283 -0
- package/src/components/chart/comparison/scene.ts +178 -0
- package/src/components/chart/comparison-stock-chart/axis-width.ts +99 -0
- package/src/components/chart/comparison-stock-chart/canvas.ts +148 -0
- package/src/components/chart/comparison-stock-chart/controls.ts +252 -0
- package/src/components/chart/comparison-stock-chart/helpers.ts +133 -0
- package/src/components/chart/comparison-stock-chart/interactions.ts +202 -0
- package/src/components/chart/comparison-stock-chart/layout-metrics.ts +62 -0
- package/src/components/chart/comparison-stock-chart/layout.tsx +155 -0
- package/src/components/chart/comparison-stock-chart/legend.tsx +73 -0
- package/src/components/chart/comparison-stock-chart/native-surfaces.ts +89 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +198 -0
- package/src/components/chart/comparison-stock-chart/render-output.ts +145 -0
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +111 -0
- package/src/components/chart/comparison-stock-chart/sources.ts +47 -0
- package/src/components/chart/comparison-stock-chart/surface-runtime.ts +159 -0
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +107 -0
- package/src/components/chart/comparison-stock-chart/types.ts +22 -0
- package/src/components/chart/comparison-stock-chart/view.tsx +524 -0
- package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +239 -0
- package/src/components/chart/comparison-stock-chart.tsx +12 -2028
- package/src/components/chart/core/axis-format.ts +147 -0
- package/src/components/chart/core/axis-measure.ts +33 -0
- package/src/components/chart/core/controller.test.ts +157 -0
- package/src/components/chart/core/controller.ts +349 -0
- package/src/components/chart/core/data.ts +94 -0
- package/src/components/chart/core/date-window.ts +392 -0
- package/src/components/chart/core/index.test.ts +1007 -0
- package/src/components/chart/core/indicator-selector.tsx +75 -0
- package/src/components/chart/core/keyboard.ts +15 -0
- package/src/components/chart/core/palette.ts +60 -0
- package/src/components/chart/core/pane-settings.test.ts +25 -0
- package/src/components/chart/core/pane-settings.ts +82 -0
- package/src/components/chart/core/pointer.ts +292 -0
- package/src/components/chart/core/projection.ts +333 -0
- package/src/components/chart/core/render-utils.ts +4 -0
- package/src/components/chart/core/renderer.ts +249 -0
- package/src/components/chart/core/resolution.test.ts +99 -0
- package/src/components/chart/core/resolution.ts +380 -0
- package/src/components/chart/core/scene.ts +180 -0
- package/src/components/chart/core/scroll.test.ts +70 -0
- package/src/components/chart/core/scroll.ts +104 -0
- package/src/components/chart/core/time-axis.ts +330 -0
- package/src/components/chart/core/types.ts +124 -0
- package/src/components/chart/core/viewport.test.ts +29 -0
- package/src/components/chart/core/viewport.ts +51 -0
- package/src/components/chart/core/window-viewport.test.ts +69 -0
- package/src/components/chart/core/window-viewport.ts +162 -0
- package/src/components/chart/display-cursor.ts +153 -0
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/multi-line/renderer.test.ts +83 -0
- package/src/components/chart/multi-line/renderer.ts +160 -0
- package/src/components/chart/multi-line/scene.ts +188 -0
- package/src/components/chart/native/canvas-bitmaps.ts +140 -0
- package/src/components/chart/native/chart-rasterizer.ts +18 -865
- package/src/components/chart/native/kitty/adapter.ts +9 -0
- package/src/components/chart/native/kitty/index.test.ts +752 -0
- package/src/components/chart/native/kitty/manager.ts +142 -0
- package/src/components/chart/native/kitty/support.ts +70 -0
- package/src/components/chart/native/raster/comparison-chart.ts +162 -0
- package/src/components/chart/native/raster/crosshair.ts +51 -0
- package/src/components/chart/native/raster/layout.ts +136 -0
- package/src/components/chart/native/raster/placement.ts +132 -0
- package/src/components/chart/native/raster/price-chart.ts +244 -0
- package/src/components/chart/native/raster/primitives.ts +154 -0
- package/src/components/chart/native/raster/types.ts +41 -0
- package/src/components/chart/native/renderer-selection.ts +81 -1
- package/src/components/chart/native/surface/hooks.ts +137 -0
- package/src/components/chart/native/surface/manager.ts +225 -0
- package/src/components/chart/native/surface/rendering.ts +211 -0
- package/src/components/chart/native/surface/sync.ts +34 -0
- package/src/components/chart/native/surface/visibility.ts +48 -0
- package/src/components/chart/price-axis-labels.tsx +4 -4
- package/src/components/chart/rendering/chart-draw.ts +270 -0
- package/src/components/chart/rendering/geometry.ts +95 -0
- package/src/components/chart/rendering/pixel-buffer.ts +267 -0
- package/src/components/chart/scatter-chart-renderer.test.ts +31 -0
- package/src/components/chart/scatter-chart-renderer.ts +223 -0
- package/src/components/chart/static/bar-chart-surface.tsx +85 -0
- package/src/components/chart/static/chart/bitmap.ts +50 -0
- package/src/components/chart/static/chart/surface.test.tsx +51 -0
- package/src/components/chart/static/chart/surface.tsx +181 -0
- package/src/components/chart/static/multi-line-chart-surface.tsx +180 -0
- package/src/components/chart/static/scatter-chart-surface.tsx +83 -0
- package/src/components/chart/stock-chart/auto.ts +216 -0
- package/src/components/chart/stock-chart/axis-width.ts +146 -0
- package/src/components/chart/stock-chart/bitmaps.ts +22 -0
- package/src/components/chart/stock-chart/canvas.ts +202 -0
- package/src/components/chart/stock-chart/controls.tsx +149 -0
- package/src/components/chart/stock-chart/cursor.ts +60 -0
- package/src/components/chart/stock-chart/data-runtime.ts +129 -0
- package/src/components/chart/stock-chart/footer.ts +248 -0
- package/src/components/chart/stock-chart/geometry.ts +160 -0
- package/src/components/chart/stock-chart/index.test.ts +500 -0
- package/src/components/chart/stock-chart/index.tsx +29 -0
- package/src/components/chart/stock-chart/indicators.ts +226 -0
- package/src/components/chart/stock-chart/interaction-runtime.ts +44 -0
- package/src/components/chart/stock-chart/interactions.ts +300 -0
- package/src/components/chart/stock-chart/keyboard.ts +373 -0
- package/src/components/chart/stock-chart/layout-metrics.ts +62 -0
- package/src/components/chart/stock-chart/layout.tsx +177 -0
- package/src/components/chart/stock-chart/native-surfaces.ts +121 -0
- package/src/components/chart/stock-chart/presentation.ts +67 -0
- package/src/components/chart/stock-chart/renderer-switch.test.tsx +263 -0
- package/src/components/chart/stock-chart/rendering/bounds.ts +116 -0
- package/src/components/chart/stock-chart/rendering/candidates.ts +145 -0
- package/src/components/chart/stock-chart/rendering/data.ts +370 -0
- package/src/components/chart/stock-chart/rendering/invalidation.ts +68 -0
- package/src/components/chart/stock-chart/rendering/output.ts +304 -0
- package/src/components/chart/stock-chart/rendering/planning.ts +122 -0
- package/src/components/chart/stock-chart/rendering/projection.test.tsx +98 -0
- package/src/components/chart/stock-chart/rendering/projection.ts +112 -0
- package/src/components/chart/stock-chart/rendering/state.test.ts +70 -0
- package/src/components/chart/stock-chart/rendering/state.ts +283 -0
- package/src/components/chart/stock-chart/requests.ts +337 -0
- package/src/components/chart/stock-chart/resolution-support.ts +96 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +289 -0
- package/src/components/chart/stock-chart/resolved.tsx +345 -0
- package/src/components/chart/stock-chart/settings.ts +77 -0
- package/src/components/chart/stock-chart/surface-runtime.ts +62 -0
- package/src/components/chart/stock-chart/types.ts +25 -0
- package/src/components/chart/stock-chart/view.tsx +155 -0
- package/src/components/chart/stock-chart/viewport/effects.ts +296 -0
- package/src/components/chart/stock-chart/viewport/index.ts +144 -0
- package/src/components/chart/stock-chart/viewport/runtime.ts +375 -0
- package/src/components/chart/time-axis-label.tsx +4 -3
- package/src/components/command-bar/action-runtime.ts +326 -0
- package/src/components/command-bar/commands/collection.ts +214 -0
- package/src/components/command-bar/commands/direct/index.ts +266 -0
- package/src/components/command-bar/commands/direct/runtime.ts +168 -0
- package/src/components/command-bar/commands/plugin/actions.ts +171 -0
- package/src/components/command-bar/commands/plugin/items.ts +166 -0
- package/src/components/command-bar/commands/registry.ts +263 -0
- package/src/components/command-bar/helpers.test.ts +13 -21
- package/src/components/command-bar/helpers.ts +51 -272
- package/src/components/command-bar/keyboard-handlers.ts +306 -0
- package/src/components/command-bar/keyboard-shortcuts.ts +204 -0
- package/src/components/command-bar/layout-items/current-layout.ts +120 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +143 -0
- package/src/components/command-bar/layout-items/types.ts +25 -0
- package/src/components/command-bar/layout-items/window-mode.ts +72 -0
- package/src/components/command-bar/layout-items.ts +93 -0
- package/src/components/command-bar/list/model.ts +81 -0
- package/src/components/command-bar/list/navigation.ts +125 -0
- package/src/components/command-bar/list/view.tsx +316 -0
- package/src/components/command-bar/multi-select-picker.tsx +219 -0
- package/src/components/command-bar/multi-select-runtime.ts +91 -0
- package/src/components/command-bar/pane-actions.ts +124 -0
- package/src/components/command-bar/pane-settings.ts +227 -0
- package/src/components/command-bar/pane-templates/items.ts +203 -0
- package/src/components/command-bar/pane-templates/workflow-route.ts +81 -0
- package/src/components/command-bar/pane-templates/workflow.ts +282 -0
- package/src/components/command-bar/panel/body.tsx +311 -0
- package/src/components/command-bar/panel/index.tsx +196 -0
- package/src/components/command-bar/panel/layout.ts +117 -0
- package/src/components/command-bar/panel/runtime.ts +280 -0
- package/src/components/command-bar/panel/state.ts +138 -0
- package/src/components/command-bar/panel/types.ts +78 -0
- package/src/components/command-bar/picker-activation.ts +126 -0
- package/src/components/command-bar/routes/root/command-items.ts +176 -0
- package/src/components/command-bar/routes/root/provider-search.ts +200 -0
- package/src/components/command-bar/routes/root/results.ts +233 -0
- package/src/components/command-bar/routes/root/runtime.ts +315 -0
- package/src/components/command-bar/routes/root/selection.ts +228 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +108 -0
- package/src/components/command-bar/routes/root/shortcut-items.ts +159 -0
- package/src/components/command-bar/routes/root/shortcuts.test.ts +151 -0
- package/src/components/command-bar/routes/root/shortcuts.ts +188 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +142 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +94 -0
- package/src/components/command-bar/routes/ticker-search/route.ts +151 -0
- package/src/components/command-bar/routing/actions.ts +299 -0
- package/src/components/command-bar/routing/confirm.ts +91 -0
- package/src/components/command-bar/routing/effects.ts +82 -0
- package/src/components/command-bar/routing/launch-request.ts +59 -0
- package/src/components/command-bar/routing/list-state.ts +242 -0
- package/src/components/command-bar/routing/navigation-state.ts +147 -0
- package/src/components/command-bar/selection-runtime.ts +291 -0
- package/src/components/command-bar/surface/environment.ts +124 -0
- package/src/components/command-bar/surface/index.test.tsx +864 -0
- package/src/components/command-bar/surface/index.tsx +344 -0
- package/src/components/command-bar/surface/pane.test.tsx +703 -0
- package/src/components/command-bar/surface/portfolio.test.tsx +452 -0
- package/src/components/command-bar/surface/test-harness.tsx +403 -0
- package/src/components/command-bar/theme-preview.ts +84 -0
- package/src/components/command-bar/view-model.test.ts +3 -3
- package/src/components/command-bar/view-model.ts +7 -6
- package/src/components/command-bar/workflow/alert.ts +164 -0
- package/src/components/command-bar/workflow/body.tsx +179 -0
- package/src/components/command-bar/workflow/broker.ts +158 -0
- package/src/components/command-bar/workflow/builtin.ts +134 -0
- package/src/components/command-bar/workflow/collection-actions.ts +257 -0
- package/src/components/command-bar/workflow/coordinator.ts +193 -0
- package/src/components/command-bar/workflow/field-row.tsx +214 -0
- package/src/components/command-bar/workflow/fields.ts +230 -0
- package/src/components/command-bar/workflow/ops.test.ts +197 -0
- package/src/components/command-bar/workflow/ops.ts +268 -0
- package/src/components/command-bar/workflow/route-actions.ts +232 -0
- package/src/components/command-bar/workflow/route-builder.ts +56 -0
- package/src/components/command-bar/workflow/runtime.ts +234 -0
- package/src/components/command-bar/workflow/submit.ts +213 -0
- package/src/components/command-bar/workflow/tickers.ts +214 -0
- package/src/components/command-bar/workflow/types.ts +127 -0
- package/src/components/data-table/stack-view.test.tsx +218 -0
- package/src/components/data-table/stack-view.tsx +59 -0
- package/src/components/data-table/view.test.tsx +254 -0
- package/src/components/data-table/view.tsx +306 -0
- package/src/components/feed-data-table/stack-view.test.tsx +148 -0
- package/src/components/feed-data-table/stack-view.tsx +359 -0
- package/src/components/index.ts +14 -14
- package/src/components/input-search-bar.tsx +94 -0
- package/src/components/layout/detached-pane-shell.tsx +22 -16
- package/src/components/layout/floating-pane.tsx +19 -24
- package/src/components/layout/header.tsx +9 -21
- package/src/components/layout/pane/content.test.tsx +64 -0
- package/src/components/layout/pane/content.tsx +44 -0
- package/src/components/layout/pane/cycle-order.test.ts +30 -0
- package/src/components/layout/pane/cycle-order.ts +32 -0
- package/src/components/layout/pane/footer/index.test.tsx +131 -0
- package/src/components/layout/pane/footer/index.tsx +246 -0
- package/src/components/layout/pane/footer/model.ts +99 -0
- package/src/components/layout/pane/footer/registration.tsx +114 -0
- package/src/components/layout/pane/frame.tsx +55 -0
- package/src/components/layout/pane/header.test.tsx +39 -0
- package/src/components/layout/pane/header.tsx +252 -0
- package/src/components/layout/pane/index.tsx +105 -0
- package/src/components/layout/pane/keyboard-scroll.test.tsx +146 -0
- package/src/components/layout/pane/sizing.ts +66 -0
- package/src/components/layout/pane/title.ts +38 -0
- package/src/components/layout/shell/action-menu-overlay.tsx +72 -0
- package/src/components/layout/shell/active-drag.ts +221 -0
- package/src/components/layout/shell/chrome.ts +8 -0
- package/src/components/layout/shell/drag/index.ts +432 -0
- package/src/components/layout/shell/drag/overlays.tsx +78 -0
- package/src/components/layout/shell/drag/runtime.ts +264 -0
- package/src/components/layout/shell/index.test.tsx +788 -0
- package/src/components/layout/shell/index.tsx +451 -0
- package/src/components/layout/shell/layout-state.ts +97 -0
- package/src/components/layout/shell/menu.ts +152 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +184 -0
- package/src/components/layout/shell/native/surfaces.ts +127 -0
- package/src/components/layout/shell/native/window-state.ts +208 -0
- package/src/components/layout/shell/pane/actions.ts +129 -0
- package/src/components/layout/shell/pane/layers.tsx +234 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +114 -0
- package/src/components/layout/shell/shortcuts.ts +47 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +326 -0
- package/src/components/layout/shell/window-mode/index.ts +253 -0
- package/src/components/layout/shell/window-mode/overlays.tsx +208 -0
- package/src/components/layout/status-bar.test.tsx +1 -101
- package/src/components/layout/status-bar.tsx +1 -1
- package/src/components/layout/window-edit/mode.test.ts +55 -0
- package/src/components/layout/window-edit/mode.ts +422 -0
- package/src/components/layout/window-edit/presentation.ts +123 -0
- package/src/components/layout/window-edit/status.tsx +101 -0
- package/src/components/markdown-text.tsx +3 -3
- package/src/components/onboarding/onboarding-steps.tsx +248 -0
- package/src/components/onboarding/onboarding-wizard.tsx +97 -1035
- package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +144 -0
- package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +97 -0
- package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +42 -0
- package/src/components/onboarding/portfolio-step/choose-panel.tsx +42 -0
- package/src/components/onboarding/portfolio-step/index.tsx +70 -0
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +62 -0
- package/src/components/onboarding/portfolio-step/types.ts +25 -0
- package/src/components/onboarding/portfolio-step/utils.ts +15 -0
- package/src/components/onboarding/wizard-broker-sync.ts +230 -0
- package/src/components/onboarding/wizard-keyboard.ts +264 -0
- package/src/components/onboarding/wizard-model.ts +21 -0
- package/src/components/pane-settings-dialog/desktop.tsx +315 -0
- package/src/components/pane-settings-dialog/field-dialogs.tsx +213 -0
- package/src/components/pane-settings-dialog/tui.tsx +73 -0
- package/src/components/pane-settings-dialog/value.ts +36 -0
- package/src/components/pane-settings-dialog.tsx +18 -622
- package/src/components/pane-template-wizard.tsx +1 -1
- package/src/components/price-selector-dialog.tsx +2 -2
- package/src/components/price-sparkline/bitmap.ts +150 -0
- package/src/components/price-sparkline/index.ts +46 -0
- package/src/components/price-sparkline/model.ts +119 -0
- package/src/components/price-sparkline/view.tsx +214 -0
- package/src/components/quote-flash.ts +103 -0
- package/src/components/speedometer-gauge/desktop.tsx +151 -0
- package/src/components/speedometer-gauge/index.tsx +44 -0
- package/src/components/speedometer-gauge/model.ts +60 -0
- package/src/components/speedometer-gauge/terminal.tsx +261 -0
- package/src/components/ticker/badge/format.ts +42 -0
- package/src/components/ticker/badge/index.tsx +100 -0
- package/src/components/ticker/badge/list.tsx +57 -0
- package/src/components/ticker/badge/text.test.tsx +223 -0
- package/src/components/ticker/badge/text.tsx +156 -0
- package/src/components/ticker/list-table-view.tsx +297 -0
- package/src/components/ticker/list-table.test.tsx +177 -0
- package/src/components/toggle-list.tsx +1 -1
- package/src/components/ui/data-table/index.tsx +26 -0
- package/src/components/ui/data-table/opentui/index.tsx +467 -0
- package/src/components/ui/data-table/opentui/model.test.ts +47 -0
- package/src/components/ui/data-table/opentui/model.ts +66 -0
- package/src/components/ui/data-table/types.ts +81 -0
- package/src/components/ui/index.ts +1 -3
- package/src/components/ui/multi-select/dialog.tsx +336 -0
- package/src/components/ui/native-select.tsx +1 -1
- package/src/components/ui/shortcut-hint.tsx +1 -1
- package/src/components/ui/tabs.tsx +11 -5
- package/src/components/ui/toggle.tsx +1 -1
- package/src/components/ui/ui.test.tsx +3 -75
- package/src/components/use-external-link-footer.ts +1 -1
- package/src/core/app-services.ts +3 -0
- package/src/core/state/app/layout-reducer.ts +196 -0
- package/src/core/state/app/layout.ts +406 -0
- package/src/core/state/app/state.ts +340 -0
- package/src/core/state/app/types.ts +116 -0
- package/src/core/state/session-persistence.ts +1 -1
- package/src/data/app-persistence.ts +1 -1
- package/src/data/config/layout.ts +227 -0
- package/src/data/config/store/index.test.ts +500 -0
- package/src/data/config/store/index.ts +64 -0
- package/src/data/config/store/node.ts +101 -0
- package/src/data/config/store/normalize.ts +403 -0
- package/src/data/plugin-state-store.ts +3 -3
- package/src/data/resource-store.ts +3 -3
- package/src/data/session-store.ts +3 -3
- package/src/data/sqlite/cache.test.ts +151 -0
- package/src/data/sqlite/database.ts +68 -0
- package/src/data/sqlite/json.ts +16 -0
- package/src/data/sqlite/retry.test.ts +63 -0
- package/src/data/ticker-repository.ts +1 -1
- package/src/data/ticker-store.ts +2 -2
- package/src/market-data/coordinator/auxiliary.ts +252 -0
- package/src/market-data/coordinator/chart.ts +141 -0
- package/src/market-data/coordinator/entries.ts +129 -0
- package/src/market-data/coordinator/events.ts +74 -0
- package/src/market-data/coordinator/financials.ts +281 -0
- package/src/market-data/coordinator/index.test.ts +840 -0
- package/src/market-data/coordinator/index.ts +388 -0
- package/src/market-data/coordinator/quotes.ts +329 -0
- package/src/market-data/coordinator/subscriptions.test.ts +209 -0
- package/src/market-data/hooks.test.tsx +42 -1
- package/src/market-data/hooks.tsx +83 -18
- package/src/market-data/market/format.test.ts +109 -0
- package/src/market-data/market/format.ts +293 -0
- package/src/market-data/market/freshness.ts +140 -0
- package/src/market-data/market/status.ts +112 -0
- package/src/market-data/quotes/contributions.ts +194 -0
- package/src/market-data/quotes/freshness.test.ts +150 -0
- package/src/market-data/quotes/freshness.ts +36 -0
- package/src/market-data/quotes/resolution.test.ts +277 -0
- package/src/market-data/quotes/resolution.ts +468 -0
- package/src/market-data/quotes/time.test.ts +34 -0
- package/src/market-data/quotes/time.ts +59 -0
- package/src/market-data/request-types.ts +10 -3
- package/src/market-data/result-types.ts +3 -3
- package/src/market-data/selectors.ts +2 -2
- package/src/news/aggregator.ts +29 -219
- package/src/news/news-model.ts +208 -0
- package/src/news/ticker-symbols.ts +1 -1
- package/src/news/types.ts +3 -3
- package/src/plugins/builtin/account-management/footer.ts +39 -0
- package/src/plugins/builtin/account-management/form-components.tsx +162 -0
- package/src/plugins/builtin/account-management/keyboard.ts +90 -0
- package/src/plugins/builtin/account-management/model.ts +97 -0
- package/src/plugins/builtin/account-management/pane.tsx +389 -0
- package/src/plugins/builtin/account-management/password-dialog.tsx +125 -0
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +6 -6
- package/src/plugins/builtin/ai/ask-ai.test.tsx +305 -0
- package/src/plugins/builtin/ai/index.tsx +4 -4
- package/src/plugins/builtin/ai/providers.ts +1 -1
- package/src/plugins/builtin/ai/runner.ts +1 -0
- package/src/plugins/builtin/ai/screener/action-bar.tsx +58 -0
- package/src/plugins/builtin/ai/screener/contract.test.ts +117 -0
- package/src/plugins/builtin/ai/screener/contract.ts +174 -0
- package/src/plugins/builtin/ai/screener/editor-runtime.ts +110 -0
- package/src/plugins/builtin/ai/screener/editor.tsx +125 -0
- package/src/plugins/builtin/ai/screener/footer.ts +93 -0
- package/src/plugins/builtin/ai/screener/keyboard.ts +90 -0
- package/src/plugins/builtin/ai/screener/market-runtime.ts +99 -0
- package/src/plugins/builtin/ai/screener/model.ts +107 -0
- package/src/plugins/builtin/ai/screener/pane.test.tsx +348 -0
- package/src/plugins/builtin/ai/screener/pane.tsx +412 -0
- package/src/plugins/builtin/ai/screener/results-view.tsx +169 -0
- package/src/plugins/builtin/ai/screener/results.ts +139 -0
- package/src/plugins/builtin/ai/screener/runner.ts +144 -0
- package/src/plugins/builtin/ai/screener/tabs-bar.tsx +65 -0
- package/src/plugins/builtin/ai/settings.ts +3 -3
- package/src/plugins/builtin/ai/ticker-context.ts +1 -1
- package/src/plugins/builtin/alerts/command.ts +86 -0
- package/src/plugins/builtin/alerts/constants.ts +3 -0
- package/src/plugins/builtin/alerts/format.ts +68 -0
- package/src/plugins/builtin/alerts/index.test.tsx +5 -4
- package/src/plugins/builtin/alerts/index.tsx +18 -473
- package/src/plugins/builtin/alerts/pane.tsx +304 -0
- package/src/plugins/builtin/alerts/quotes.ts +57 -0
- package/src/plugins/builtin/alerts/storage.ts +20 -0
- package/src/plugins/builtin/analytics/broker-performance.ts +142 -0
- package/src/plugins/builtin/analytics/display.ts +44 -0
- package/src/plugins/builtin/analytics/index.test.tsx +144 -5
- package/src/plugins/builtin/analytics/index.tsx +108 -468
- package/src/plugins/builtin/analytics/metrics.ts +1 -1
- package/src/plugins/builtin/analytics/pane-model.ts +297 -0
- package/src/plugins/builtin/analytics/portfolio-selection.ts +10 -0
- package/src/plugins/builtin/analytics/sector-model.ts +172 -0
- package/src/plugins/builtin/analytics/view.tsx +207 -0
- package/src/plugins/builtin/broker-manager/detail.tsx +232 -0
- package/src/plugins/builtin/broker-manager/footer.ts +62 -0
- package/src/plugins/builtin/broker-manager/index.test.tsx +3 -3
- package/src/plugins/builtin/broker-manager/index.tsx +125 -527
- package/src/plugins/builtin/broker-manager/keyboard.ts +104 -0
- package/src/plugins/builtin/broker-manager/model.test.ts +39 -1
- package/src/plugins/builtin/broker-manager/model.ts +14 -8
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +181 -0
- package/src/plugins/builtin/broker-manager/table.ts +86 -0
- package/src/plugins/builtin/buildout/cells.tsx +203 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +238 -0
- package/src/plugins/builtin/buildout/detail/company.tsx +219 -0
- package/src/plugins/builtin/buildout/detail/index.tsx +105 -0
- package/src/plugins/builtin/buildout/detail/intel.tsx +61 -0
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +80 -0
- package/src/plugins/builtin/buildout/detail/site.tsx +142 -0
- package/src/plugins/builtin/buildout/detail/ui.tsx +355 -0
- package/src/plugins/builtin/buildout/detail/values.ts +67 -0
- package/src/plugins/builtin/buildout/format.ts +163 -0
- package/src/plugins/builtin/buildout/model/index.ts +135 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +396 -0
- package/src/plugins/builtin/buildout/model/types.ts +318 -0
- package/src/plugins/builtin/buildout/pane/header.tsx +55 -0
- package/src/plugins/builtin/buildout/pane/index.tsx +405 -0
- package/src/plugins/builtin/buildout/pane/status.tsx +84 -0
- package/src/plugins/builtin/buildout/table-model.ts +367 -0
- package/src/plugins/builtin/changelog/index.tsx +370 -0
- package/src/plugins/builtin/chat/channels.ts +157 -0
- package/src/plugins/builtin/chat/chat.test.tsx +1048 -0
- package/src/plugins/builtin/chat/composer-commands.test.ts +30 -0
- package/src/plugins/builtin/chat/composer-commands.ts +34 -0
- package/src/plugins/builtin/chat/content/channel-navigation.ts +157 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +251 -0
- package/src/plugins/builtin/chat/content/composer.tsx +148 -0
- package/src/plugins/builtin/chat/content/index.tsx +407 -0
- package/src/plugins/builtin/chat/content/layout-metrics.ts +72 -0
- package/src/plugins/builtin/chat/content/scroll.ts +278 -0
- package/src/plugins/builtin/chat/content/selection-runtime.ts +66 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +212 -0
- package/src/plugins/builtin/chat/content/snapshot.ts +154 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +155 -0
- package/src/plugins/builtin/chat/content/types.ts +26 -0
- package/src/plugins/builtin/chat/content/user-map.ts +21 -0
- package/src/plugins/builtin/chat/controller/channel-actions.ts +87 -0
- package/src/plugins/builtin/chat/controller/channels.ts +159 -0
- package/src/plugins/builtin/chat/controller/connections.ts +90 -0
- package/src/plugins/builtin/chat/controller/draft-sync.ts +41 -0
- package/src/plugins/builtin/chat/controller/fetch.ts +83 -0
- package/src/plugins/builtin/chat/controller/index.ts +488 -0
- package/src/plugins/builtin/chat/controller/lifecycle.ts +141 -0
- package/src/plugins/builtin/chat/controller/message-loading.ts +97 -0
- package/src/plugins/builtin/chat/controller/messages.ts +173 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +56 -0
- package/src/plugins/builtin/chat/controller/persistence.ts +166 -0
- package/src/plugins/builtin/chat/controller/realtime.ts +91 -0
- package/src/plugins/builtin/chat/controller/send.ts +89 -0
- package/src/plugins/builtin/chat/controller/session-runtime.ts +156 -0
- package/src/plugins/builtin/chat/controller/session.ts +39 -0
- package/src/plugins/builtin/chat/controller/state.ts +162 -0
- package/src/plugins/builtin/chat/controller/storage.ts +136 -0
- package/src/plugins/builtin/chat/controller/utils.ts +75 -0
- package/src/plugins/builtin/chat/controller/view.ts +96 -0
- package/src/plugins/builtin/chat/controller.test.ts +1302 -0
- package/src/plugins/builtin/chat/index.tsx +4 -0
- package/src/plugins/builtin/chat/layout.ts +330 -0
- package/src/plugins/builtin/chat/message/action-chip.tsx +38 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +158 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +118 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +72 -0
- package/src/plugins/builtin/chat/message/render-state.ts +67 -0
- package/src/plugins/builtin/chat/message/terminal.tsx +132 -0
- package/src/plugins/builtin/chat/message/types.ts +18 -0
- package/src/plugins/builtin/chat/pane.tsx +100 -0
- package/src/plugins/builtin/chat/profile-popover.ts +49 -0
- package/src/plugins/builtin/chat/sidebar.test.tsx +455 -0
- package/src/plugins/builtin/chat/sidebar.tsx +296 -0
- package/src/plugins/builtin/chat/status-widget.tsx +103 -0
- package/src/plugins/builtin/chat/test-harness.tsx +212 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +72 -0
- package/src/plugins/builtin/cloud/auth-commands.ts +115 -0
- package/src/plugins/builtin/cloud/index.tsx +11 -0
- package/src/plugins/builtin/cloud/plugin.tsx +185 -0
- package/src/plugins/builtin/cloud-tweets/footer.ts +40 -0
- package/src/plugins/builtin/cloud-tweets/index.ts +1 -0
- package/src/plugins/builtin/cloud-tweets/keyboard.ts +65 -0
- package/src/plugins/builtin/cloud-tweets/model.ts +257 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +318 -0
- package/src/plugins/builtin/cloud-tweets/registration.ts +87 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +49 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +331 -0
- package/src/plugins/builtin/comparison-chart/index.test.tsx +645 -0
- package/src/plugins/builtin/comparison-chart/index.tsx +194 -0
- package/src/plugins/builtin/congress-trades/detail.tsx +154 -0
- package/src/plugins/builtin/congress-trades/footer.ts +67 -0
- package/src/plugins/builtin/congress-trades/keyboard.ts +112 -0
- package/src/plugins/builtin/congress-trades/model.ts +224 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +331 -0
- package/src/plugins/builtin/congress-trades/table.tsx +84 -0
- package/src/plugins/builtin/correlation/compute.ts +0 -7
- package/src/plugins/builtin/correlation/index.test.tsx +5 -5
- package/src/plugins/builtin/correlation/index.tsx +36 -208
- package/src/plugins/builtin/correlation/matrix/model.ts +155 -0
- package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +56 -0
- package/src/plugins/builtin/correlation/relationship/controls.tsx +53 -0
- package/src/plugins/builtin/correlation/relationship/history.ts +72 -0
- package/src/plugins/builtin/correlation/relationship/model.ts +279 -0
- package/src/plugins/builtin/correlation/relationship/pane.tsx +359 -0
- package/src/plugins/builtin/correlation/relationship/view-model.ts +120 -0
- package/src/plugins/builtin/correlation/settings.ts +6 -6
- package/src/plugins/builtin/debug/index.tsx +365 -0
- package/src/plugins/builtin/earnings/data/cache.test.ts +94 -0
- package/src/plugins/builtin/earnings/data/cache.ts +145 -0
- package/src/plugins/builtin/earnings/data/data.test.ts +90 -0
- package/src/plugins/builtin/earnings/data/data.ts +115 -0
- package/src/plugins/builtin/earnings/index.test.ts +12 -0
- package/src/plugins/builtin/earnings/index.tsx +74 -266
- package/src/plugins/builtin/earnings/model.ts +82 -0
- package/src/plugins/builtin/earnings/table.ts +206 -0
- package/src/plugins/builtin/econ/calendar-model.test.ts +48 -0
- package/src/plugins/builtin/econ/calendar-model.ts +217 -0
- package/src/plugins/builtin/econ/calendar-source.ts +1 -1
- package/src/plugins/builtin/econ/detail-view.tsx +319 -0
- package/src/plugins/builtin/econ/fred-cache.test.ts +58 -0
- package/src/plugins/builtin/econ/fred-cache.ts +106 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -4
- package/src/plugins/builtin/econ/index.tsx +46 -474
- package/src/plugins/builtin/fear-greed/cache.test.ts +75 -0
- package/src/plugins/builtin/fear-greed/cache.ts +142 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +305 -0
- package/src/plugins/builtin/fear-greed/data.test.ts +72 -0
- package/src/plugins/builtin/fear-greed/data.ts +370 -0
- package/src/plugins/builtin/fear-greed/format.ts +96 -0
- package/src/plugins/builtin/fear-greed/index.tsx +13 -555
- package/src/plugins/builtin/fear-greed/pane.tsx +169 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +0 -22
- package/src/plugins/builtin/help/components.tsx +92 -0
- package/src/plugins/builtin/help/index.test.tsx +167 -0
- package/src/plugins/builtin/help/index.tsx +256 -0
- package/src/plugins/builtin/help/shortcut-model.ts +145 -0
- package/src/plugins/builtin/holders/format.ts +44 -0
- package/src/plugins/builtin/holders/index.test.ts +1 -46
- package/src/plugins/builtin/holders/index.tsx +3 -955
- package/src/plugins/builtin/holders/pane.tsx +331 -0
- package/src/plugins/builtin/holders/table-model.ts +84 -0
- package/src/plugins/builtin/holders/thirteenf-match.ts +76 -0
- package/src/plugins/builtin/holders/treemap-layout.ts +231 -0
- package/src/plugins/builtin/holders/treemap.tsx +318 -0
- package/src/plugins/builtin/holders/types.ts +51 -0
- package/src/plugins/builtin/insider/index.tsx +5 -5
- package/src/plugins/builtin/layout-manager/index.tsx +247 -0
- package/src/plugins/builtin/market-movers/index.tsx +44 -282
- package/src/plugins/builtin/market-movers/model.ts +146 -0
- package/src/plugins/builtin/market-movers/screener.test.ts +31 -1
- package/src/plugins/builtin/market-movers/screener.ts +106 -12
- package/src/plugins/builtin/market-movers/table.ts +95 -0
- package/src/plugins/builtin/news/index.tsx +194 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +98 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.ts +110 -0
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +156 -0
- package/src/plugins/builtin/news/wire/breaking/pane.tsx +68 -0
- package/src/plugins/builtin/news/wire/categories.ts +114 -0
- package/src/plugins/builtin/news/wire/default-feeds.ts +3 -0
- package/src/plugins/builtin/news/wire/feed-config.test.ts +90 -0
- package/src/plugins/builtin/news/wire/feed-config.ts +216 -0
- package/src/plugins/builtin/news/wire/index.tsx +129 -0
- package/src/plugins/builtin/news/wire/industry-pane.tsx +126 -0
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +306 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +77 -0
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +98 -0
- package/src/plugins/builtin/news/wire/news/query-presets.ts +40 -0
- package/src/plugins/builtin/news/wire/news/table.test.tsx +157 -0
- package/src/plugins/builtin/news/wire/news/table.tsx +270 -0
- package/src/plugins/builtin/news/wire/persisted-articles.ts +98 -0
- package/src/plugins/builtin/news/wire/read-state.ts +76 -0
- package/src/plugins/builtin/news/wire/rss/parser.test.ts +116 -0
- package/src/plugins/builtin/news/wire/rss/parser.ts +213 -0
- package/src/plugins/builtin/news/wire/rss/source.test.ts +104 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +181 -0
- package/src/plugins/builtin/notes/index.tsx +49 -0
- package/src/plugins/builtin/notes/markdown-note-preview.tsx +39 -0
- package/src/plugins/builtin/notes/model.ts +36 -0
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +334 -0
- package/src/plugins/builtin/notes/text-state.ts +11 -0
- package/src/plugins/builtin/notes/ticker-notes-tab.tsx +112 -0
- package/src/plugins/builtin/options/index.tsx +44 -0
- package/src/plugins/builtin/options/table.ts +209 -0
- package/src/plugins/builtin/options/types.ts +33 -0
- package/src/plugins/builtin/options/view.test.tsx +185 -0
- package/src/plugins/builtin/options/view.tsx +264 -0
- package/src/plugins/builtin/plugin-groups.ts +25 -5
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +50 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +46 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +224 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +203 -0
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +21 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +224 -0
- package/src/plugins/builtin/portfolio-list/column-values.ts +284 -0
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/header.tsx +7 -98
- package/src/plugins/builtin/portfolio-list/index.test.ts +133 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +2 -1
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +242 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +5 -462
- package/src/plugins/builtin/portfolio-list/pane/data.ts +171 -0
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1267 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +385 -0
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +159 -0
- package/src/plugins/builtin/portfolio-list/position-metrics.ts +123 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +326 -0
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +135 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +126 -0
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +392 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +86 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
- package/src/plugins/builtin/research/analyst-pane.tsx +383 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +413 -0
- package/src/plugins/builtin/research/index.test.ts +248 -2
- package/src/plugins/builtin/research/index.tsx +30 -760
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +189 -0
- package/src/plugins/builtin/sec/filing-display.tsx +84 -0
- package/src/plugins/builtin/sec/index.tsx +342 -0
- package/src/plugins/builtin/sectors/index.tsx +27 -234
- package/src/plugins/builtin/sectors/sector-data.ts +0 -2
- package/src/plugins/builtin/sectors/sector-model.ts +221 -0
- package/src/plugins/builtin/shared/table-pane.ts +31 -0
- package/src/plugins/builtin/shared/ticker-request.ts +70 -0
- package/src/plugins/builtin/shared/ticker-surface.test.ts +57 -0
- package/src/plugins/builtin/shared/ticker-surface.ts +113 -0
- package/src/plugins/builtin/thirteenf/api.test.ts +58 -0
- package/src/plugins/builtin/thirteenf/api.ts +375 -0
- package/src/plugins/builtin/thirteenf/data.test.ts +95 -0
- package/src/plugins/builtin/thirteenf/data.ts +210 -0
- package/src/plugins/builtin/thirteenf/format.ts +55 -0
- package/src/plugins/builtin/thirteenf/index.tsx +80 -0
- package/src/plugins/builtin/thirteenf/model.test.ts +156 -0
- package/src/plugins/builtin/thirteenf/model.ts +618 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +328 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +826 -0
- package/src/plugins/builtin/thirteenf/table.ts +212 -0
- package/src/plugins/builtin/thirteenf/types.ts +187 -0
- package/src/plugins/builtin/ticker-detail/chart-pane.tsx +91 -0
- package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +282 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +158 -0
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +234 -0
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +44 -0
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +51 -0
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +135 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +192 -0
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +172 -0
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +110 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +203 -0
- package/src/plugins/builtin/ticker-detail/financials/model.test.ts +84 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +364 -0
- package/src/plugins/builtin/ticker-detail/financials/pane.tsx +27 -0
- package/src/plugins/builtin/ticker-detail/financials/schema.ts +392 -0
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +438 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +780 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +174 -17
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +240 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +168 -0
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +14 -0
- package/src/plugins/builtin/ticker-detail/overview/types.ts +17 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +27 -392
- package/src/plugins/builtin/ticker-detail/pane.tsx +24 -136
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +330 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +292 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +180 -0
- package/src/plugins/builtin/ticker-detail/settings.ts +39 -33
- package/src/plugins/builtin/world-indices/footer.ts +28 -0
- package/src/plugins/builtin/world-indices/index.tsx +25 -218
- package/src/plugins/builtin/world-indices/model.ts +120 -0
- package/src/plugins/builtin/world-indices/table.tsx +96 -0
- package/src/plugins/builtin/yahoo/index.ts +72 -0
- package/src/plugins/builtin/yield-curve/index.tsx +5 -5
- package/src/plugins/builtin/yield-curve/treasury-data.ts +1 -1
- package/src/plugins/catalog-ui.ts +7 -9
- package/src/plugins/catalog.ts +1 -1
- package/src/plugins/ibkr/account-cache.ts +2 -10
- package/src/plugins/ibkr/broker-adapter.ts +10 -4
- package/src/plugins/ibkr/config.ts +5 -24
- package/src/plugins/ibkr/dialogs.tsx +0 -1
- package/src/plugins/ibkr/flex/index.test.ts +236 -0
- package/src/plugins/ibkr/flex/index.ts +337 -0
- package/src/plugins/ibkr/flex/statement-client.ts +181 -0
- package/src/plugins/ibkr/gateway/account-loaders.ts +266 -0
- package/src/plugins/ibkr/gateway/account-summary.ts +163 -0
- package/src/plugins/ibkr/gateway/client-lock.ts +154 -0
- package/src/plugins/ibkr/gateway/connection-events.ts +114 -0
- package/src/plugins/ibkr/gateway/connection.ts +152 -0
- package/src/plugins/ibkr/gateway/contracts.ts +132 -0
- package/src/plugins/ibkr/gateway/fundamentals.ts +83 -0
- package/src/plugins/ibkr/gateway/helpers.ts +33 -0
- package/src/plugins/ibkr/gateway/history-requests.ts +158 -0
- package/src/plugins/ibkr/gateway/history.ts +120 -0
- package/src/plugins/ibkr/gateway/instrument-lookup.ts +117 -0
- package/src/plugins/ibkr/gateway/market-data.ts +338 -0
- package/src/plugins/ibkr/gateway/order-execution.ts +218 -0
- package/src/plugins/ibkr/gateway/orders.ts +63 -0
- package/src/plugins/ibkr/gateway/price-normalization.test.ts +44 -0
- package/src/plugins/ibkr/gateway/price-normalization.ts +50 -0
- package/src/plugins/ibkr/gateway/quote-stream-controller.ts +203 -0
- package/src/plugins/ibkr/gateway/quote-stream.ts +171 -0
- package/src/plugins/ibkr/gateway/selection.ts +51 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +614 -0
- package/src/plugins/ibkr/gateway/service/index.ts +432 -0
- package/src/plugins/ibkr/gateway/service/lifecycle.ts +191 -0
- package/src/plugins/ibkr/gateway/service/manager.ts +52 -0
- package/src/plugins/ibkr/gateway/service/market-data.ts +48 -0
- package/src/plugins/ibkr/gateway/service/native.ts +543 -0
- package/src/plugins/ibkr/gateway/timeouts.ts +13 -0
- package/src/plugins/ibkr/gateway/types.ts +27 -0
- package/src/plugins/ibkr/hash.ts +8 -0
- package/src/plugins/ibkr/index.test.tsx +1 -12
- package/src/plugins/ibkr/index.tsx +8 -9
- package/src/plugins/ibkr/instance-selection.test.ts +2 -2
- package/src/plugins/ibkr/instance-selection.ts +1 -1
- package/src/plugins/ibkr/portfolio-performance.test.ts +32 -0
- package/src/plugins/ibkr/portfolio-performance.ts +129 -0
- package/src/plugins/ibkr/trade/badge.tsx +28 -0
- package/src/plugins/ibkr/trade/dialogs.tsx +44 -0
- package/src/plugins/ibkr/trade/preview-panel.tsx +130 -0
- package/src/plugins/ibkr/trade/tab/actions.tsx +443 -0
- package/src/plugins/ibkr/trade/tab/field-editors.tsx +79 -0
- package/src/plugins/ibkr/trade/tab/footer.ts +67 -0
- package/src/plugins/ibkr/trade/tab/header.tsx +167 -0
- package/src/plugins/ibkr/trade/tab/index.test.tsx +178 -0
- package/src/plugins/ibkr/trade/tab/index.tsx +302 -0
- package/src/plugins/ibkr/trade/tab/model.ts +159 -0
- package/src/plugins/ibkr/trade/tab/order-request.ts +64 -0
- package/src/plugins/ibkr/trade/tab/shortcuts.ts +89 -0
- package/src/plugins/ibkr/trade/tab/state-sync.ts +86 -0
- package/src/plugins/ibkr/trade/ticket-panel.tsx +301 -0
- package/src/plugins/ibkr/trade/utils.test.ts +175 -0
- package/src/plugins/ibkr/trade/utils.ts +150 -0
- package/src/plugins/ibkr/trading/pane-view.tsx +175 -0
- package/src/plugins/ibkr/trading/pane.tsx +332 -0
- package/src/plugins/ibkr/trading/state.test.ts +109 -0
- package/src/plugins/ibkr/trading/state.ts +335 -0
- package/src/plugins/pane-manager/dock-tree.ts +282 -0
- package/src/plugins/pane-manager/docking.ts +202 -0
- package/src/plugins/pane-manager/floating-actions.ts +158 -0
- package/src/plugins/pane-manager/floating.ts +76 -0
- package/src/plugins/pane-manager/gridlock-inference.ts +126 -0
- package/src/plugins/pane-manager/gridlock.ts +34 -0
- package/src/plugins/pane-manager/layout-state.ts +160 -0
- package/src/plugins/pane-manager/queries.ts +75 -0
- package/src/plugins/pane-manager/types.ts +24 -0
- package/src/plugins/pane-manager.test.ts +110 -4
- package/src/plugins/pane-manager.ts +57 -1035
- package/src/plugins/prediction-markets/chart.test.tsx +2 -2
- package/src/plugins/prediction-markets/controller/catalog.ts +317 -0
- package/src/plugins/prediction-markets/controller/data.ts +171 -0
- package/src/plugins/prediction-markets/controller/detail.ts +243 -0
- package/src/plugins/prediction-markets/controller/effects.ts +253 -0
- package/src/plugins/prediction-markets/controller/index.ts +374 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +220 -0
- package/src/plugins/prediction-markets/controller/live-updates.ts +85 -0
- package/src/plugins/prediction-markets/controller/selection.ts +59 -0
- package/src/plugins/prediction-markets/controller/status.ts +104 -0
- package/src/plugins/prediction-markets/detail/outcomes.tsx +1 -1
- package/src/plugins/prediction-markets/metrics.ts +4 -14
- package/src/plugins/prediction-markets/navigation.ts +0 -13
- package/src/plugins/prediction-markets/pane.test.tsx +2 -198
- package/src/plugins/prediction-markets/plugin.test.tsx +2 -2
- package/src/plugins/prediction-markets/services/fetch.ts +1 -5
- package/src/plugins/prediction-markets/services/kalshi/adapter.ts +299 -0
- package/src/plugins/prediction-markets/services/kalshi/normalize.ts +209 -0
- package/src/plugins/prediction-markets/services/kalshi/types.ts +89 -0
- package/src/plugins/prediction-markets/services/polymarket/adapter.ts +134 -0
- package/src/plugins/prediction-markets/services/polymarket/detail.ts +307 -0
- package/src/plugins/prediction-markets/services/polymarket/normalize.ts +239 -0
- package/src/plugins/prediction-markets/services/polymarket/types.ts +66 -0
- package/src/plugins/prediction-markets/services/polymarket/ws.ts +128 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +12 -76
- package/src/plugins/prediction-markets/types.ts +2 -2
- package/src/plugins/registry/context-menu.ts +36 -0
- package/src/plugins/registry/context.ts +196 -0
- package/src/plugins/registry/contributions.ts +174 -0
- package/src/plugins/registry/index.test.ts +362 -0
- package/src/plugins/registry/index.ts +474 -0
- package/src/plugins/registry/pane-settings.ts +105 -0
- package/src/plugins/registry/plugin-state.ts +123 -0
- package/src/plugins/registry/shared.ts +34 -0
- package/src/plugins/registry/slots.ts +70 -0
- package/src/plugins/runtime/context.tsx +115 -0
- package/src/plugins/runtime/index.test.tsx +328 -0
- package/src/plugins/runtime/index.tsx +111 -0
- package/src/plugins/runtime/state.ts +275 -0
- package/src/plugins/ticker-navigation.test.ts +23 -2
- package/src/plugins/ticker-navigation.ts +9 -3
- package/src/react/input.test.ts +29 -0
- package/src/react/input.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +50 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +58 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +123 -0
- package/src/renderers/electrobun/bun/context-menu/click.test.ts +67 -0
- package/src/renderers/electrobun/bun/context-menu/normalize.test.ts +52 -0
- package/src/renderers/electrobun/bun/context-menu/normalize.ts +67 -0
- package/src/renderers/electrobun/bun/core-capabilities.ts +3 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +163 -0
- package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +93 -0
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +263 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +114 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +56 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +139 -0
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +92 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +48 -0
- package/src/renderers/electrobun/bun/desktop/rpc-registry.ts +43 -0
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +76 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +162 -0
- package/src/renderers/electrobun/bun/desktop/window-events.ts +49 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +63 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +180 -0
- package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +97 -0
- package/src/renderers/electrobun/bun/index.ts +191 -947
- package/src/renderers/electrobun/bun/tui-entry.ts +6 -0
- package/src/renderers/electrobun/bun/window/focus.ts +37 -0
- package/src/renderers/electrobun/bun/window/frame.test.ts +40 -0
- package/src/renderers/electrobun/shared/protocol.ts +9 -2
- package/src/renderers/electrobun/view/app-services.ts +4 -375
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -0
- package/src/renderers/electrobun/view/build-assets.ts +117 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +398 -0
- package/src/renderers/electrobun/view/config-host.ts +1 -1
- package/src/renderers/electrobun/view/context-menu.test.ts +10 -4
- package/src/renderers/electrobun/view/context-menu.ts +1 -1
- package/src/renderers/electrobun/view/data-table/dom.ts +211 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +358 -0
- package/src/renderers/electrobun/view/data-table/row.tsx +335 -0
- package/src/renderers/electrobun/view/desktop/control-styles.ts +77 -0
- package/src/renderers/electrobun/view/desktop/controls.tsx +421 -0
- package/src/renderers/electrobun/view/desktop/list-view.tsx +200 -0
- package/src/renderers/electrobun/view/desktop/window/bridge.ts +70 -0
- package/src/renderers/electrobun/view/desktop/window/snapshot.test.ts +57 -0
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +41 -0
- package/src/renderers/electrobun/view/fatal-screen.tsx +118 -0
- package/src/renderers/electrobun/view/host/box.tsx +160 -0
- package/src/renderers/electrobun/view/host/chart-surface.tsx +142 -0
- package/src/renderers/electrobun/view/host/input.tsx +294 -0
- package/src/renderers/electrobun/view/host/mouse.ts +139 -0
- package/src/renderers/electrobun/view/host/native.ts +49 -0
- package/src/renderers/electrobun/view/host/scroll-box.tsx +276 -0
- package/src/renderers/electrobun/view/host/style.ts +124 -0
- package/src/renderers/electrobun/view/host/tabs.tsx +255 -0
- package/src/renderers/electrobun/view/host/text.tsx +121 -0
- package/src/renderers/electrobun/view/http-fetch.ts +1 -1
- package/src/renderers/electrobun/view/input-host.tsx +12 -1
- package/src/renderers/electrobun/view/main.tsx +45 -26
- package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +59 -0
- package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +9 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +201 -0
- package/src/renderers/electrobun/view/remote/persistence.ts +158 -0
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +26 -0
- package/src/renderers/electrobun/view/styles.css +91 -4
- package/src/renderers/electrobun/view/ui-host.tsx +22 -1469
- package/src/renderers/opentui/chart-surface.tsx +4 -5
- package/src/renderers/opentui/host.tsx +30 -4
- package/src/renderers/opentui/image/loader.test.ts +25 -0
- package/src/renderers/opentui/image/loader.ts +105 -0
- package/src/renderers/opentui/image/surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +12 -1
- package/src/renderers/opentui/start.tsx +2 -2
- package/src/renderers/opentui/test-utils.tsx +2 -2
- package/src/renderers/opentui/ui-host.tsx +1 -1
- package/src/sources/gloomberb-cloud/index.test.ts +660 -0
- package/src/sources/gloomberb-cloud/index.ts +444 -0
- package/src/sources/gloomberb-cloud/news.ts +127 -0
- package/src/sources/gloomberb-cloud/normalizers.ts +354 -0
- package/src/sources/provider-errors.ts +8 -2
- package/src/sources/provider-router/batches.ts +170 -0
- package/src/sources/provider-router/brokers.ts +114 -0
- package/src/sources/provider-router/cache.ts +171 -0
- package/src/sources/provider-router/cached-financials.test.ts +498 -0
- package/src/sources/provider-router/document-routes.ts +153 -0
- package/src/sources/provider-router/financial-routes.ts +225 -0
- package/src/sources/provider-router/financials.ts +259 -0
- package/src/sources/provider-router/history.test.ts +383 -0
- package/src/sources/provider-router/history.ts +448 -0
- package/src/sources/provider-router/index.test.ts +1438 -0
- package/src/sources/provider-router/index.ts +389 -0
- package/src/sources/provider-router/news.ts +36 -0
- package/src/sources/provider-router/primary.ts +164 -0
- package/src/sources/provider-router/route-types.ts +34 -0
- package/src/sources/provider-router/routing.ts +58 -0
- package/src/sources/provider-router/search.ts +183 -0
- package/src/sources/provider-router/sources.ts +55 -0
- package/src/sources/provider-router/streaming.ts +92 -0
- package/src/sources/provider-router/supplemental.ts +373 -0
- package/src/sources/provider-router/test-support.ts +136 -0
- package/src/sources/sec-edgar/content.ts +211 -0
- package/src/sources/sec-edgar.ts +9 -217
- package/src/sources/yahoo-finance/financials.ts +339 -0
- package/src/sources/yahoo-finance/history.ts +106 -0
- package/src/sources/yahoo-finance/http.ts +100 -0
- package/src/sources/yahoo-finance/mappers.ts +409 -0
- package/src/sources/yahoo-finance/options.ts +134 -0
- package/src/sources/yahoo-finance/quote-summary.ts +210 -0
- package/src/sources/yahoo-finance/requests.ts +192 -0
- package/src/sources/yahoo-finance/snapshots.ts +236 -0
- package/src/sources/yahoo-finance/symbols.ts +102 -0
- package/src/sources/yahoo-finance/types.ts +167 -0
- package/src/sources/yahoo-finance.test.ts +116 -12
- package/src/sources/yahoo-finance.ts +115 -1408
- package/src/state/app/activity.test.ts +41 -0
- package/src/state/app/activity.ts +106 -0
- package/src/state/app/bootstrap.test.ts +498 -0
- package/src/state/app/bootstrap.ts +412 -0
- package/src/state/app/context/index.test.ts +564 -0
- package/src/state/app/context/index.test.tsx +193 -0
- package/src/state/app/context/index.tsx +476 -0
- package/src/state/app/context/selector.test.tsx +291 -0
- package/src/state/config-save-scheduler.ts +2 -6
- package/src/state/hooks/inline-tickers.test.tsx +123 -0
- package/src/state/hooks/inline-tickers.ts +212 -0
- package/src/state/hooks/quote-streaming.test.ts +31 -0
- package/src/state/hooks/quote-streaming.test.tsx +70 -0
- package/src/state/hooks/quote-streaming.ts +92 -0
- package/src/state/hooks/session-snapshot.ts +99 -0
- package/src/state/pane-scroll-registry.tsx +1 -1
- package/src/state/selectors-ui.ts +1 -1
- package/src/state/selectors.ts +1 -1
- package/src/test-support/plugin-persistence.ts +112 -0
- package/src/test-support/plugin-runtime.ts +2 -1
- package/src/theme/colors.ts +23 -9
- package/src/theme/themes.test.ts +15 -0
- package/src/tickers/list.test.ts +16 -0
- package/src/tickers/open-target.ts +77 -0
- package/src/tickers/search/index.test.ts +252 -0
- package/src/tickers/search/index.ts +367 -0
- package/src/tickers/search/ranking.ts +387 -0
- package/src/tickers/search/result.ts +27 -0
- package/src/tickers/search/types.ts +41 -0
- package/src/tickers/search/upsert.ts +91 -0
- package/src/tickers/tokenizer.test.ts +31 -0
- package/src/tickers/tokenizer.ts +37 -0
- package/src/types/broker.ts +12 -4
- package/src/types/config.ts +42 -23
- package/src/types/context-menu.ts +1 -1
- package/src/types/data-provider.ts +2 -2
- package/src/types/desktop-window.ts +1 -1
- package/src/types/financials.ts +109 -4
- package/src/types/news-source.ts +0 -1
- package/src/types/persistence.ts +1 -1
- package/src/types/plugin.ts +37 -21
- package/src/types/ticker.ts +1 -0
- package/src/types/trading.ts +23 -1
- package/src/ui/context-menu.tsx +6 -5
- package/src/ui/dialog.tsx +6 -3
- package/src/ui/host.tsx +10 -10
- package/src/ui/index.tsx +14 -33
- package/src/updater/github-releases.test.ts +41 -0
- package/src/updater/github-releases.ts +73 -0
- package/src/updater.test.ts +21 -0
- package/src/updater.ts +10 -3
- package/src/utils/back-navigation.ts +2 -2
- package/src/utils/cli-output.ts +4 -4
- package/src/utils/currency-units.test.ts +23 -1
- package/src/utils/currency-units.ts +56 -0
- package/src/utils/datetime-format.ts +40 -0
- package/src/utils/dom-screenshot.ts +2 -2
- package/src/utils/double-escape-close.ts +1 -1
- package/src/utils/exchanges.ts +2 -2
- package/src/utils/format.ts +0 -7
- package/src/utils/fuzzy-search.ts +1 -1
- package/src/utils/inline-content-tokenizer.test.ts +11 -2
- package/src/utils/inline-content-tokenizer.ts +33 -12
- package/src/utils/keyboard.ts +1 -1
- package/src/utils/link-tokenizer.ts +2 -2
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +20 -0
- package/src/utils/price-history.ts +24 -2
- package/src/utils/relative-time.ts +8 -0
- package/src/utils/selection-clipboard.ts +1 -1
- package/src/utils/shortcut-labels.ts +2 -2
- package/src/utils/sort-values.ts +17 -0
- package/src/utils/terminal-input-reset.ts +1 -1
- package/src/utils/text-wrap.ts +20 -0
- package/src/version.ts +1 -1
- package/src/components/chart/chart-controller.test.ts +0 -110
- package/src/components/chart/chart-controller.ts +0 -650
- package/src/components/chart/chart-data.ts +0 -350
- package/src/components/chart/chart-indicator-selector.tsx +0 -75
- package/src/components/chart/chart-pane-settings.ts +0 -37
- package/src/components/chart/chart-renderer.ts +0 -1562
- package/src/components/chart/chart-resolution.test.ts +0 -87
- package/src/components/chart/chart-resolution.ts +0 -405
- package/src/components/chart/chart-scroll.test.ts +0 -70
- package/src/components/chart/chart-scroll.ts +0 -104
- package/src/components/chart/chart-types.ts +0 -119
- package/src/components/chart/chart-viewport.test.ts +0 -29
- package/src/components/chart/chart-viewport.ts +0 -50
- package/src/components/chart/chart.test.ts +0 -922
- package/src/components/chart/comparison-chart-data.test.ts +0 -105
- package/src/components/chart/comparison-chart-data.ts +0 -292
- package/src/components/chart/comparison-chart-renderer.test.ts +0 -187
- package/src/components/chart/comparison-chart-renderer.ts +0 -437
- package/src/components/chart/native/kitty-adapter.ts +0 -9
- package/src/components/chart/native/kitty-manager.ts +0 -142
- package/src/components/chart/native/kitty-support.ts +0 -71
- package/src/components/chart/native/kitty.test.ts +0 -728
- package/src/components/chart/native/surface-manager.ts +0 -225
- package/src/components/chart/native/surface-sync.ts +0 -34
- package/src/components/chart/native/surface-visibility.ts +0 -48
- package/src/components/chart/static-chart-surface.test.tsx +0 -51
- package/src/components/chart/static-chart-surface.tsx +0 -217
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +0 -1323
- package/src/components/chart/stock-chart.test.ts +0 -330
- package/src/components/chart/stock-chart.tsx +0 -4148
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -109
- package/src/components/command-bar/command-bar.test.tsx +0 -3244
- package/src/components/command-bar/command-bar.tsx +0 -5868
- package/src/components/command-bar/command-registry.ts +0 -267
- package/src/components/command-bar/confirm-body.tsx +0 -51
- package/src/components/command-bar/list-model.ts +0 -80
- package/src/components/command-bar/pane-template-display.ts +0 -8
- package/src/components/command-bar/root-shortcuts.ts +0 -188
- package/src/components/command-bar/workflow-ops.test.ts +0 -144
- package/src/components/command-bar/workflow-ops.ts +0 -459
- package/src/components/command-bar/workflow-types.ts +0 -127
- package/src/components/data-table-stack-view.test.tsx +0 -241
- package/src/components/data-table-stack-view.tsx +0 -59
- package/src/components/data-table-view.test.tsx +0 -211
- package/src/components/data-table-view.tsx +0 -244
- package/src/components/feed-data-table-stack-view.test.tsx +0 -225
- package/src/components/feed-data-table-stack-view.tsx +0 -359
- package/src/components/layout/pane-content.test.tsx +0 -64
- package/src/components/layout/pane-content.tsx +0 -44
- package/src/components/layout/pane-cycle-order.test.ts +0 -30
- package/src/components/layout/pane-cycle-order.ts +0 -32
- package/src/components/layout/pane-footer.test.tsx +0 -195
- package/src/components/layout/pane-footer.tsx +0 -432
- package/src/components/layout/pane-header.test.tsx +0 -39
- package/src/components/layout/pane-header.tsx +0 -248
- package/src/components/layout/pane-keyboard-scroll.test.tsx +0 -226
- package/src/components/layout/pane-sizing.ts +0 -34
- package/src/components/layout/pane-title.ts +0 -38
- package/src/components/layout/pane.tsx +0 -129
- package/src/components/layout/shell.test.tsx +0 -1427
- package/src/components/layout/shell.tsx +0 -1912
- package/src/components/price-sparkline-view.tsx +0 -463
- package/src/components/price-sparkline.ts +0 -46
- package/src/components/speedometer-gauge.test.tsx +0 -45
- package/src/components/speedometer-gauge.tsx +0 -562
- package/src/components/ticker-badge-list.tsx +0 -53
- package/src/components/ticker-badge-text.test.tsx +0 -253
- package/src/components/ticker-badge-text.tsx +0 -113
- package/src/components/ticker-badge.tsx +0 -109
- package/src/components/ticker-list-table-view.tsx +0 -233
- package/src/components/ticker-list-table.test.tsx +0 -210
- package/src/components/ticker-list-table.tsx +0 -458
- package/src/components/ui/data-table.tsx +0 -522
- package/src/components/ui/multi-select-dialog.tsx +0 -336
- package/src/core/state/app-state.ts +0 -1009
- package/src/data/config-layout.ts +0 -225
- package/src/data/config-store-node.ts +0 -478
- package/src/data/config-store.test.ts +0 -481
- package/src/data/config-store.ts +0 -64
- package/src/data/sqlite-cache.test.ts +0 -151
- package/src/data/sqlite-database.ts +0 -68
- package/src/data/sqlite-json.ts +0 -16
- package/src/data/sqlite-retry.test.ts +0 -63
- package/src/market-data/coordinator-subscriptions.test.ts +0 -209
- package/src/market-data/coordinator.test.ts +0 -830
- package/src/market-data/coordinator.ts +0 -1155
- package/src/plugins/builtin/ai/screener-contract.test.ts +0 -117
- package/src/plugins/builtin/ai/screener-contract.ts +0 -174
- package/src/plugins/builtin/ai/screener-pane.test.tsx +0 -348
- package/src/plugins/builtin/ai/screener-pane.tsx +0 -1118
- package/src/plugins/builtin/ai/utils.ts +0 -1
- package/src/plugins/builtin/ask-ai.test.tsx +0 -348
- package/src/plugins/builtin/chat-controller.test.ts +0 -1376
- package/src/plugins/builtin/chat-controller.ts +0 -1248
- package/src/plugins/builtin/chat.test.tsx +0 -2245
- package/src/plugins/builtin/chat.tsx +0 -2267
- package/src/plugins/builtin/cloud-auth-actions.tsx +0 -72
- package/src/plugins/builtin/cloud-tweets.tsx +0 -896
- package/src/plugins/builtin/comparison-chart.test.tsx +0 -532
- package/src/plugins/builtin/comparison-chart.tsx +0 -194
- package/src/plugins/builtin/debug.tsx +0 -365
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +0 -181
- package/src/plugins/builtin/earnings/earnings-cache.ts +0 -145
- package/src/plugins/builtin/earnings/earnings-data.test.ts +0 -90
- package/src/plugins/builtin/earnings/earnings-data.ts +0 -115
- package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +0 -72
- package/src/plugins/builtin/fear-greed/fear-greed-data.ts +0 -366
- package/src/plugins/builtin/help.test.tsx +0 -166
- package/src/plugins/builtin/help.tsx +0 -450
- package/src/plugins/builtin/layout-manager.tsx +0 -247
- package/src/plugins/builtin/manual-entry.tsx +0 -25
- package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +0 -98
- package/src/plugins/builtin/news-wire/breaking-notifications.ts +0 -110
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +0 -156
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -68
- package/src/plugins/builtin/news-wire/categories.ts +0 -114
- package/src/plugins/builtin/news-wire/default-feeds.ts +0 -3
- package/src/plugins/builtin/news-wire/feed-config.test.ts +0 -90
- package/src/plugins/builtin/news-wire/feed-config.ts +0 -216
- package/src/plugins/builtin/news-wire/index.tsx +0 -129
- package/src/plugins/builtin/news-wire/industry-pane.tsx +0 -126
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +0 -299
- package/src/plugins/builtin/news-wire/news-footer.ts +0 -28
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +0 -98
- package/src/plugins/builtin/news-wire/news-query-presets.ts +0 -40
- package/src/plugins/builtin/news-wire/news-table.test.tsx +0 -206
- package/src/plugins/builtin/news-wire/news-table.tsx +0 -296
- package/src/plugins/builtin/news-wire/persisted-articles.ts +0 -98
- package/src/plugins/builtin/news-wire/read-state.ts +0 -76
- package/src/plugins/builtin/news-wire/rss-parser.test.ts +0 -184
- package/src/plugins/builtin/news-wire/rss-parser.ts +0 -213
- package/src/plugins/builtin/news-wire/rss-source.test.ts +0 -159
- package/src/plugins/builtin/news-wire/rss-source.ts +0 -181
- package/src/plugins/builtin/news.tsx +0 -194
- package/src/plugins/builtin/notes.tsx +0 -561
- package/src/plugins/builtin/options.test.tsx +0 -241
- package/src/plugins/builtin/options.tsx +0 -542
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +0 -657
- package/src/plugins/builtin/portfolio-list/pane.tsx +0 -724
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +0 -422
- package/src/plugins/builtin/portfolio-list/summary.tsx +0 -275
- package/src/plugins/builtin/portfolio-list.test.ts +0 -133
- package/src/plugins/builtin/portfolio-list.test.tsx +0 -1535
- package/src/plugins/builtin/portfolio-metrics.test.ts +0 -236
- package/src/plugins/builtin/quote-monitor.test.tsx +0 -222
- package/src/plugins/builtin/sec-filing-display.tsx +0 -84
- package/src/plugins/builtin/sec.tsx +0 -342
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +0 -461
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +0 -479
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +0 -269
- package/src/plugins/builtin/ticker-detail.test.tsx +0 -888
- package/src/plugins/builtin/ticker-surface.ts +0 -68
- package/src/plugins/builtin/yahoo.ts +0 -72
- package/src/plugins/catalog-ui.test.ts +0 -24
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +0 -35
- package/src/plugins/ibkr/flex.test.ts +0 -162
- package/src/plugins/ibkr/flex.ts +0 -371
- package/src/plugins/ibkr/gateway-helpers.ts +0 -33
- package/src/plugins/ibkr/gateway-selection.ts +0 -51
- package/src/plugins/ibkr/gateway-service-native.ts +0 -2377
- package/src/plugins/ibkr/gateway-service.test.ts +0 -584
- package/src/plugins/ibkr/gateway-service.ts +0 -433
- package/src/plugins/ibkr/gateway-types.ts +0 -27
- package/src/plugins/ibkr/price-normalization.test.ts +0 -33
- package/src/plugins/ibkr/price-normalization.ts +0 -60
- package/src/plugins/ibkr/trade-badge.tsx +0 -28
- package/src/plugins/ibkr/trade-tab.test.tsx +0 -276
- package/src/plugins/ibkr/trade-tab.tsx +0 -1117
- package/src/plugins/ibkr/trade-utils.test.ts +0 -175
- package/src/plugins/ibkr/trade-utils.ts +0 -150
- package/src/plugins/ibkr/trading-pane.tsx +0 -474
- package/src/plugins/ibkr/trading-state.test.ts +0 -109
- package/src/plugins/ibkr/trading-state.ts +0 -339
- package/src/plugins/plugin-runtime.test.tsx +0 -323
- package/src/plugins/plugin-runtime.tsx +0 -433
- package/src/plugins/prediction-markets/controller-data.ts +0 -785
- package/src/plugins/prediction-markets/controller.ts +0 -659
- package/src/plugins/prediction-markets/services/kalshi-adapter.ts +0 -572
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +0 -696
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +0 -128
- package/src/plugins/registry.test.ts +0 -362
- package/src/plugins/registry.ts +0 -854
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +0 -50
- package/src/renderers/electrobun/bun/application-menu-click.ts +0 -58
- package/src/renderers/electrobun/bun/application-menu.ts +0 -123
- package/src/renderers/electrobun/bun/context-menu-click.test.ts +0 -67
- package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +0 -52
- package/src/renderers/electrobun/bun/context-menu-normalize.ts +0 -67
- package/src/renderers/electrobun/bun/desktop-workspace.test.ts +0 -63
- package/src/renderers/electrobun/bun/desktop-workspace.ts +0 -180
- package/src/renderers/electrobun/bun/window-focus.ts +0 -37
- package/src/renderers/electrobun/bun/window-frame.test.ts +0 -40
- package/src/renderers/electrobun/view/data-table.tsx +0 -810
- package/src/renderers/electrobun/view/desktop-controls.tsx +0 -673
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +0 -70
- package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +0 -57
- package/src/renderers/electrobun/view/desktop-window-snapshot.ts +0 -41
- package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +0 -9
- package/src/renderers/opentui/image-loader.test.ts +0 -25
- package/src/renderers/opentui/image-loader.ts +0 -105
- package/src/renderers/opentui/image-surface.tsx +0 -266
- package/src/sources/gloomberb-cloud.test.ts +0 -637
- package/src/sources/gloomberb-cloud.ts +0 -828
- package/src/sources/provider-router.test.ts +0 -2600
- package/src/sources/provider-router.ts +0 -2231
- package/src/state/app-activity.test.ts +0 -41
- package/src/state/app-activity.ts +0 -106
- package/src/state/app-bootstrap.test.ts +0 -498
- package/src/state/app-bootstrap.ts +0 -411
- package/src/state/app-context-selector.test.tsx +0 -369
- package/src/state/app-context.test.ts +0 -670
- package/src/state/app-context.test.tsx +0 -193
- package/src/state/app-context.tsx +0 -485
- package/src/state/use-inline-tickers.test.tsx +0 -73
- package/src/state/use-inline-tickers.ts +0 -188
- package/src/state/use-quote-streaming.test.ts +0 -31
- package/src/state/use-quote-streaming.test.tsx +0 -70
- package/src/state/use-quote-streaming.ts +0 -92
- package/src/state/use-session-snapshot.ts +0 -99
- package/src/utils/api-client.test.ts +0 -603
- package/src/utils/api-client.ts +0 -1379
- package/src/utils/market-format.test.ts +0 -109
- package/src/utils/market-format.ts +0 -299
- package/src/utils/market-status.ts +0 -141
- package/src/utils/quote-freshness.test.ts +0 -150
- package/src/utils/quote-freshness.ts +0 -145
- package/src/utils/quote-resolution.test.ts +0 -277
- package/src/utils/quote-resolution.ts +0 -642
- package/src/utils/quote-time.test.ts +0 -34
- package/src/utils/quote-time.ts +0 -59
- package/src/utils/ticker-list.test.ts +0 -16
- package/src/utils/ticker-search.test.ts +0 -227
- package/src/utils/ticker-search.ts +0 -855
- package/src/utils/ticker-tokenizer.test.ts +0 -31
- package/src/utils/ticker-tokenizer.ts +0 -37
- /package/src/components/chart/native/{kitty-protocol.ts → kitty/protocol.ts} +0 -0
- /package/src/components/ui/{multi-select.ts → multi-select/index.ts} +0 -0
- /package/src/data/{sqlite-retry.ts → sqlite/retry.ts} +0 -0
- /package/src/plugins/builtin/{news-wire → news/wire}/hash.ts +0 -0
- /package/src/plugins/builtin/{news-wire → news/wire}/read-state.test.ts +0 -0
- /package/src/plugins/builtin/{notes-files.ts → notes/files.ts} +0 -0
- /package/src/renderers/electrobun/bun/{context-menu-click.ts → context-menu/click.ts} +0 -0
- /package/src/renderers/electrobun/bun/{window-frame.ts → window/frame.ts} +0 -0
- /package/src/renderers/electrobun/view/native-stubs/{chart-surface-manager.ts → chart/surface-manager.ts} +0 -0
- /package/src/renderers/electrobun/view/native-stubs/{chart-surface-sync.ts → chart/surface-sync.ts} +0 -0
- /package/src/{utils/ticker-list.ts → tickers/list.ts} +0 -0
- /package/src/{utils/ticker-metadata.ts → tickers/metadata.ts} +0 -0
|
@@ -1,3244 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { act, useReducer } from "react";
|
|
3
|
-
import { TestDialogProvider, testRender } from "../../renderers/opentui/test-utils";
|
|
4
|
-
import { CommandBar } from "./command-bar";
|
|
5
|
-
import { AppContext, type AppAction, type AppState, appReducer, createInitialState, getEffectiveThemeId } from "../../state/app-context";
|
|
6
|
-
import { createTestDataProvider } from "../../test-support/data-provider";
|
|
7
|
-
import { ThemeProvider, useThemeId } from "../../theme/theme-context";
|
|
8
|
-
import { cloneLayout, createDefaultConfig, type AppConfig } from "../../types/config";
|
|
9
|
-
import type { DataProvider } from "../../types/data-provider";
|
|
10
|
-
import type { TickerRecord } from "../../types/ticker";
|
|
11
|
-
import type { PluginRegistry } from "../../plugins/registry";
|
|
12
|
-
import type { PaneSettingField, PaneTemplateCreateOptions } from "../../types/plugin";
|
|
13
|
-
import { useShortcut } from "../../react/input";
|
|
14
|
-
|
|
15
|
-
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
if (testSetup) {
|
|
19
|
-
testSetup.renderer.destroy();
|
|
20
|
-
testSetup = undefined;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
async function waitForFrameToContain(text: string, attempts = 12, delayMs = 50): Promise<string> {
|
|
25
|
-
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
26
|
-
const frame = testSetup!.captureCharFrame();
|
|
27
|
-
if (frame.includes(text)) {
|
|
28
|
-
return frame;
|
|
29
|
-
}
|
|
30
|
-
await Bun.sleep(delayMs);
|
|
31
|
-
await testSetup!.renderOnce();
|
|
32
|
-
}
|
|
33
|
-
throw new Error(`Timed out waiting for frame to contain "${text}".`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function clickFrameText(text: string): Promise<void> {
|
|
37
|
-
const frame = testSetup!.captureCharFrame();
|
|
38
|
-
const rows = frame.split("\n");
|
|
39
|
-
const row = rows.findIndex((line) => line.includes(text));
|
|
40
|
-
const col = row >= 0 ? rows[row]!.indexOf(text) : -1;
|
|
41
|
-
|
|
42
|
-
expect(row).toBeGreaterThanOrEqual(0);
|
|
43
|
-
expect(col).toBeGreaterThanOrEqual(0);
|
|
44
|
-
|
|
45
|
-
await act(async () => {
|
|
46
|
-
await testSetup!.mockMouse.click(col + 1, row);
|
|
47
|
-
await testSetup!.renderOnce();
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function emitKeypress(
|
|
52
|
-
renderer: Awaited<ReturnType<typeof testRender>>,
|
|
53
|
-
event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; shift?: boolean; option?: boolean },
|
|
54
|
-
): Promise<void> {
|
|
55
|
-
await act(async () => {
|
|
56
|
-
renderer.renderer.keyInput.emit("keypress", {
|
|
57
|
-
ctrl: false,
|
|
58
|
-
meta: false,
|
|
59
|
-
option: false,
|
|
60
|
-
shift: false,
|
|
61
|
-
eventType: "press",
|
|
62
|
-
repeated: false,
|
|
63
|
-
stopPropagation: () => {},
|
|
64
|
-
preventDefault: () => {},
|
|
65
|
-
...event,
|
|
66
|
-
} as any);
|
|
67
|
-
await renderer.renderOnce();
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function renderFrames(count = 2): Promise<void> {
|
|
72
|
-
for (let index = 0; index < count; index += 1) {
|
|
73
|
-
await testSetup!.renderOnce();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function expectSingleBackControl(frame: string): void {
|
|
78
|
-
expect(frame.match(/\bBack\b/g)?.length ?? 0).toBe(1);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function makeTicker(symbol: string, name: string, overrides: Partial<TickerRecord["metadata"]> = {}): TickerRecord {
|
|
82
|
-
return {
|
|
83
|
-
metadata: {
|
|
84
|
-
ticker: symbol,
|
|
85
|
-
exchange: "NASDAQ",
|
|
86
|
-
currency: "USD",
|
|
87
|
-
name,
|
|
88
|
-
portfolios: [],
|
|
89
|
-
watchlists: [],
|
|
90
|
-
positions: [],
|
|
91
|
-
custom: {},
|
|
92
|
-
tags: [],
|
|
93
|
-
...overrides,
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function makeDataProvider(searchImpl: DataProvider["search"] = async () => []): DataProvider {
|
|
99
|
-
return createTestDataProvider({
|
|
100
|
-
id: "test",
|
|
101
|
-
search: searchImpl,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () => false): PluginRegistry {
|
|
106
|
-
return {
|
|
107
|
-
panes: new Map<string, any>([
|
|
108
|
-
["portfolio-list", {
|
|
109
|
-
id: "portfolio-list",
|
|
110
|
-
name: "Portfolio List",
|
|
111
|
-
component: () => null,
|
|
112
|
-
defaultPosition: "left",
|
|
113
|
-
}],
|
|
114
|
-
["ticker-detail", {
|
|
115
|
-
id: "ticker-detail",
|
|
116
|
-
name: "Ticker Detail",
|
|
117
|
-
component: () => null,
|
|
118
|
-
defaultPosition: "right",
|
|
119
|
-
}],
|
|
120
|
-
["chat", {
|
|
121
|
-
id: "chat",
|
|
122
|
-
name: "Chat",
|
|
123
|
-
component: () => null,
|
|
124
|
-
defaultPosition: "right",
|
|
125
|
-
defaultMode: "floating",
|
|
126
|
-
}],
|
|
127
|
-
["quote-monitor", {
|
|
128
|
-
id: "quote-monitor",
|
|
129
|
-
name: "Quote Monitor",
|
|
130
|
-
component: () => null,
|
|
131
|
-
defaultPosition: "right",
|
|
132
|
-
defaultMode: "floating",
|
|
133
|
-
}],
|
|
134
|
-
["ibkr-trading", {
|
|
135
|
-
id: "ibkr-trading",
|
|
136
|
-
name: "IBKR Console",
|
|
137
|
-
component: () => null,
|
|
138
|
-
defaultPosition: "right",
|
|
139
|
-
defaultMode: "floating",
|
|
140
|
-
}],
|
|
141
|
-
]),
|
|
142
|
-
paneTemplates: new Map<string, any>([
|
|
143
|
-
["new-portfolio-pane", {
|
|
144
|
-
id: "new-portfolio-pane",
|
|
145
|
-
paneId: "portfolio-list",
|
|
146
|
-
label: "New Portfolio Pane",
|
|
147
|
-
description: "Open another portfolio list pane",
|
|
148
|
-
shortcut: { prefix: "PF" },
|
|
149
|
-
}],
|
|
150
|
-
["new-watchlist-pane", {
|
|
151
|
-
id: "new-watchlist-pane",
|
|
152
|
-
paneId: "portfolio-list",
|
|
153
|
-
label: "New Watchlist Pane",
|
|
154
|
-
description: "Open another watchlist pane",
|
|
155
|
-
}],
|
|
156
|
-
["new-ticker-detail-pane", {
|
|
157
|
-
id: "new-ticker-detail-pane",
|
|
158
|
-
paneId: "ticker-detail",
|
|
159
|
-
label: "New Ticker Detail Pane",
|
|
160
|
-
description: "Open another detail pane",
|
|
161
|
-
}],
|
|
162
|
-
["new-chat-pane", {
|
|
163
|
-
id: "new-chat-pane",
|
|
164
|
-
paneId: "chat",
|
|
165
|
-
label: "New Chat Pane",
|
|
166
|
-
description: "Open another floating chat window",
|
|
167
|
-
shortcut: { prefix: "CHAT", argPlaceholder: "channel", argKind: "text" },
|
|
168
|
-
}],
|
|
169
|
-
["quote-monitor-pane", {
|
|
170
|
-
id: "quote-monitor-pane",
|
|
171
|
-
paneId: "quote-monitor",
|
|
172
|
-
label: "Quote Monitor",
|
|
173
|
-
description: "Open a compact quote monitor for one or more tickers",
|
|
174
|
-
shortcut: { prefix: "QQ", argPlaceholder: "tickers", argKind: "ticker-list" },
|
|
175
|
-
wizard: [{ key: "tickers", label: "Quote Tickers", type: "text" }],
|
|
176
|
-
}],
|
|
177
|
-
["new-ibkr-trading-pane", {
|
|
178
|
-
id: "new-ibkr-trading-pane",
|
|
179
|
-
paneId: "ibkr-trading",
|
|
180
|
-
label: "New IBKR Trading Pane",
|
|
181
|
-
description: "Open another floating IBKR trading console",
|
|
182
|
-
shortcut: { prefix: "IBKR" },
|
|
183
|
-
canCreate: (context: any) => context.config.brokerInstances.some((instance: any) => (
|
|
184
|
-
instance.brokerType === "ibkr"
|
|
185
|
-
&& instance.connectionMode === "gateway"
|
|
186
|
-
&& instance.enabled !== false
|
|
187
|
-
)),
|
|
188
|
-
}],
|
|
189
|
-
]),
|
|
190
|
-
commands: new Map<string, any>([
|
|
191
|
-
["plugin:scan", {
|
|
192
|
-
id: "plugin:scan",
|
|
193
|
-
label: "Scan Movers",
|
|
194
|
-
description: "Run a quick movers scan",
|
|
195
|
-
category: "plugins",
|
|
196
|
-
execute: async () => {},
|
|
197
|
-
}],
|
|
198
|
-
]),
|
|
199
|
-
tickerActions: new Map<string, any>([
|
|
200
|
-
["pin", {
|
|
201
|
-
id: "pin",
|
|
202
|
-
label: "Pin Ticker",
|
|
203
|
-
execute: async () => {},
|
|
204
|
-
}],
|
|
205
|
-
]),
|
|
206
|
-
brokers: new Map(),
|
|
207
|
-
allPlugins: new Map([
|
|
208
|
-
["news", { id: "news", name: "News", version: "1.0.0", description: "Latest headlines", toggleable: true }],
|
|
209
|
-
["notes", { id: "notes", name: "Notes", version: "1.0.0", description: "Ticker notes", toggleable: true }],
|
|
210
|
-
]),
|
|
211
|
-
getCommandPluginId: () => "news",
|
|
212
|
-
getPaneTemplatePluginId: (templateId: string) => (
|
|
213
|
-
templateId === "new-chat-pane" ? "news" : "notes"
|
|
214
|
-
),
|
|
215
|
-
getPluginPaneIds: () => [],
|
|
216
|
-
getPluginPaneTemplateIds: () => [],
|
|
217
|
-
hasPaneSettings,
|
|
218
|
-
events: { emit: () => {} },
|
|
219
|
-
hidePane: () => {},
|
|
220
|
-
pinTicker: () => {},
|
|
221
|
-
showPane: () => {},
|
|
222
|
-
updateLayoutFn: () => {},
|
|
223
|
-
getTermSizeFn: () => ({ width: 80, height: 24 }),
|
|
224
|
-
notify: () => {},
|
|
225
|
-
createPaneFromTemplateFn: () => {},
|
|
226
|
-
createPaneFromTemplateAsyncFn: async () => {},
|
|
227
|
-
openPaneSettingsFn: () => {},
|
|
228
|
-
applyPaneSettingValueFn: async () => {},
|
|
229
|
-
resolvePaneSettings: () => null,
|
|
230
|
-
createBrokerInstanceFn: async () => { throw new Error("unused"); },
|
|
231
|
-
syncBrokerInstanceFn: async () => {},
|
|
232
|
-
removeBrokerInstanceFn: async () => {},
|
|
233
|
-
getConfigFn: () => createDefaultConfig("/tmp/gloomberb-test"),
|
|
234
|
-
} as unknown as PluginRegistry;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function makeQuoteMonitorPaneSettingsDescriptor(
|
|
238
|
-
pluginRegistry: PluginRegistry,
|
|
239
|
-
fields: PaneSettingField[],
|
|
240
|
-
settings: Record<string, unknown> = {},
|
|
241
|
-
) {
|
|
242
|
-
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
243
|
-
const pane = {
|
|
244
|
-
instanceId: "quote-monitor:main",
|
|
245
|
-
paneId: "quote-monitor",
|
|
246
|
-
title: "Quote Monitor",
|
|
247
|
-
settings,
|
|
248
|
-
};
|
|
249
|
-
return {
|
|
250
|
-
paneId: "quote-monitor:main",
|
|
251
|
-
pane,
|
|
252
|
-
paneDef: pluginRegistry.panes.get("quote-monitor")!,
|
|
253
|
-
settingsDef: {
|
|
254
|
-
title: "Quote Monitor Settings",
|
|
255
|
-
fields,
|
|
256
|
-
},
|
|
257
|
-
context: {
|
|
258
|
-
config,
|
|
259
|
-
layout: cloneLayout(config.layout),
|
|
260
|
-
paneId: "quote-monitor:main",
|
|
261
|
-
paneType: "quote-monitor",
|
|
262
|
-
pane,
|
|
263
|
-
settings,
|
|
264
|
-
paneState: {},
|
|
265
|
-
activeTicker: "AAPL",
|
|
266
|
-
activeCollectionId: "main",
|
|
267
|
-
},
|
|
268
|
-
} as any;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function ThemeProbe() {
|
|
272
|
-
return <text>{`theme:${useThemeId()}`}</text>;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function UnhandledEnterProbe({ onEnter }: { onEnter: () => void }) {
|
|
276
|
-
useShortcut((event) => {
|
|
277
|
-
if (event.name === "enter" || event.name === "return") {
|
|
278
|
-
onEnter();
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function CommandBarHarness({
|
|
285
|
-
query,
|
|
286
|
-
disabledPlugins = [],
|
|
287
|
-
selectedTicker,
|
|
288
|
-
live = false,
|
|
289
|
-
extraTickers = [],
|
|
290
|
-
showQueryState = false,
|
|
291
|
-
onSaveTicker,
|
|
292
|
-
configureConfig,
|
|
293
|
-
configureState,
|
|
294
|
-
configurePluginRegistry,
|
|
295
|
-
dataProvider = makeDataProvider(),
|
|
296
|
-
hasPaneSettings,
|
|
297
|
-
onCheckForUpdates,
|
|
298
|
-
onAction,
|
|
299
|
-
onUnhandledEnter,
|
|
300
|
-
}: {
|
|
301
|
-
query: string;
|
|
302
|
-
disabledPlugins?: string[];
|
|
303
|
-
selectedTicker?: string;
|
|
304
|
-
live?: boolean;
|
|
305
|
-
extraTickers?: TickerRecord[];
|
|
306
|
-
showQueryState?: boolean;
|
|
307
|
-
onSaveTicker?: (ticker: TickerRecord) => void;
|
|
308
|
-
configureConfig?: (config: AppConfig) => AppConfig;
|
|
309
|
-
configureState?: (state: AppState) => AppState;
|
|
310
|
-
configurePluginRegistry?: (pluginRegistry: PluginRegistry) => void;
|
|
311
|
-
dataProvider?: DataProvider;
|
|
312
|
-
hasPaneSettings?: (paneId: string) => boolean;
|
|
313
|
-
onCheckForUpdates?: () => void | Promise<void>;
|
|
314
|
-
onAction?: (action: AppAction) => void;
|
|
315
|
-
onUnhandledEnter?: () => void;
|
|
316
|
-
}) {
|
|
317
|
-
let config = {
|
|
318
|
-
...createDefaultConfig("/tmp/gloomberb-test"),
|
|
319
|
-
recentTickers: ["AAPL", "MSFT"],
|
|
320
|
-
disabledPlugins,
|
|
321
|
-
};
|
|
322
|
-
if (configureConfig) {
|
|
323
|
-
config = configureConfig(config);
|
|
324
|
-
}
|
|
325
|
-
const tickers = [makeTicker("AAPL", "Apple Inc."), makeTicker("MSFT", "Microsoft Corp."), ...extraTickers];
|
|
326
|
-
let state: AppState = {
|
|
327
|
-
...createInitialState(config),
|
|
328
|
-
commandBarOpen: true,
|
|
329
|
-
commandBarQuery: query,
|
|
330
|
-
focusedPaneId: "portfolio-list:main",
|
|
331
|
-
tickers: new Map(tickers.map((ticker) => [ticker.metadata.ticker, ticker])),
|
|
332
|
-
config: { ...config, disabledPlugins },
|
|
333
|
-
paneState: selectedTicker
|
|
334
|
-
? {
|
|
335
|
-
"portfolio-list:main": {
|
|
336
|
-
collectionId: "main",
|
|
337
|
-
cursorSymbol: selectedTicker,
|
|
338
|
-
},
|
|
339
|
-
}
|
|
340
|
-
: createInitialState(config).paneState,
|
|
341
|
-
};
|
|
342
|
-
if (configureState) {
|
|
343
|
-
state = configureState(state);
|
|
344
|
-
}
|
|
345
|
-
const tickerRepository = {
|
|
346
|
-
loadTicker: async () => null,
|
|
347
|
-
createTicker: async (metadata: TickerRecord["metadata"]) => ({
|
|
348
|
-
metadata,
|
|
349
|
-
}),
|
|
350
|
-
saveTicker: async (ticker: TickerRecord) => {
|
|
351
|
-
onSaveTicker?.(ticker);
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
const pluginRegistry = makePluginRegistry(hasPaneSettings);
|
|
355
|
-
configurePluginRegistry?.(pluginRegistry);
|
|
356
|
-
const [liveState, dispatch] = useReducer(appReducer, state);
|
|
357
|
-
const currentState = live ? liveState : state;
|
|
358
|
-
const currentDispatch = live
|
|
359
|
-
? (action: AppAction) => {
|
|
360
|
-
onAction?.(action);
|
|
361
|
-
dispatch(action);
|
|
362
|
-
}
|
|
363
|
-
: (_action: AppAction) => {};
|
|
364
|
-
|
|
365
|
-
return (
|
|
366
|
-
<ThemeProvider themeId={getEffectiveThemeId(currentState)}>
|
|
367
|
-
<AppContext value={{ state: currentState, dispatch: currentDispatch }}>
|
|
368
|
-
<TestDialogProvider>
|
|
369
|
-
{live && <ThemeProbe />}
|
|
370
|
-
{showQueryState && <text>{`query:${currentState.commandBarQuery}`}</text>}
|
|
371
|
-
{currentState.commandBarOpen && (
|
|
372
|
-
<CommandBar
|
|
373
|
-
dataProvider={dataProvider}
|
|
374
|
-
tickerRepository={tickerRepository as any}
|
|
375
|
-
pluginRegistry={pluginRegistry}
|
|
376
|
-
quitApp={() => {}}
|
|
377
|
-
onCheckForUpdates={onCheckForUpdates}
|
|
378
|
-
/>
|
|
379
|
-
)}
|
|
380
|
-
{onUnhandledEnter && <UnhandledEnterProbe onEnter={onUnhandledEnter} />}
|
|
381
|
-
</TestDialogProvider>
|
|
382
|
-
</AppContext>
|
|
383
|
-
</ThemeProvider>
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
describe("CommandBar", () => {
|
|
388
|
-
test("renders the default layout with opencode-style chrome", async () => {
|
|
389
|
-
testSetup = await testRender(<CommandBarHarness query="" selectedTicker="AAPL" />, {
|
|
390
|
-
width: 80,
|
|
391
|
-
height: 24,
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
await testSetup.renderOnce();
|
|
395
|
-
|
|
396
|
-
expect(testSetup.captureCharFrame()).toMatchSnapshot();
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("runs check for updates from the command bar", async () => {
|
|
400
|
-
const calls: number[] = [];
|
|
401
|
-
|
|
402
|
-
testSetup = await testRender(<CommandBarHarness query="check for updates" live onCheckForUpdates={() => { calls.push(Date.now()); }} />, {
|
|
403
|
-
width: 80,
|
|
404
|
-
height: 24,
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
await testSetup.renderOnce();
|
|
408
|
-
expect(testSetup.captureCharFrame()).toContain("Check for Updates");
|
|
409
|
-
|
|
410
|
-
await clickFrameText("Check for Updates");
|
|
411
|
-
await Bun.sleep(0);
|
|
412
|
-
await testSetup.renderOnce();
|
|
413
|
-
|
|
414
|
-
expect(calls).toHaveLength(1);
|
|
415
|
-
expect(testSetup.captureCharFrame()).not.toContain("Commands");
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
test("runs plugin command shortcuts from the root query", async () => {
|
|
419
|
-
const calls: string[] = [];
|
|
420
|
-
|
|
421
|
-
testSetup = await testRender(<CommandBarHarness
|
|
422
|
-
query="GL"
|
|
423
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
424
|
-
(pluginRegistry.commands as Map<string, any>).set("gridlock-all", {
|
|
425
|
-
id: "gridlock-all",
|
|
426
|
-
label: "Gridlock All Windows",
|
|
427
|
-
description: "Arrange all visible panes into a tiled grid",
|
|
428
|
-
keywords: ["grid", "gridlock", "tile", "arrange", "windows", "layout"],
|
|
429
|
-
shortcut: "GL",
|
|
430
|
-
category: "config",
|
|
431
|
-
execute: async () => {
|
|
432
|
-
calls.push("gridlock-all");
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
(pluginRegistry.allPlugins as Map<string, any>).set("layout-manager", {
|
|
436
|
-
id: "layout-manager",
|
|
437
|
-
name: "Layout Manager",
|
|
438
|
-
version: "1.0.0",
|
|
439
|
-
description: "Pane layout management commands",
|
|
440
|
-
});
|
|
441
|
-
const getCommandPluginId = pluginRegistry.getCommandPluginId;
|
|
442
|
-
pluginRegistry.getCommandPluginId = (commandId: string) => (
|
|
443
|
-
commandId === "gridlock-all" ? "layout-manager" : getCommandPluginId(commandId)
|
|
444
|
-
);
|
|
445
|
-
}}
|
|
446
|
-
/>, {
|
|
447
|
-
width: 80,
|
|
448
|
-
height: 24,
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
await testSetup.renderOnce();
|
|
452
|
-
|
|
453
|
-
const frame = testSetup.captureCharFrame();
|
|
454
|
-
expect(frame).toContain("Gridlock All Windows");
|
|
455
|
-
expect(frame).toContain("GL");
|
|
456
|
-
|
|
457
|
-
await act(async () => {
|
|
458
|
-
testSetup!.mockInput.pressEnter();
|
|
459
|
-
await Bun.sleep(0);
|
|
460
|
-
await testSetup!.renderOnce();
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
expect(calls).toEqual(["gridlock-all"]);
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
test("surfaces plugin commands by add-style search terms", async () => {
|
|
467
|
-
testSetup = await testRender(<CommandBarHarness
|
|
468
|
-
query="add alert"
|
|
469
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
470
|
-
(pluginRegistry.commands as Map<string, any>).set("set-alert", {
|
|
471
|
-
id: "set-alert",
|
|
472
|
-
label: "Add Alert",
|
|
473
|
-
description: "Create a price alert from a symbol, condition, and target price",
|
|
474
|
-
keywords: ["add", "set", "alert", "price", "trigger"],
|
|
475
|
-
shortcut: "SA",
|
|
476
|
-
category: "data",
|
|
477
|
-
execute: async () => {},
|
|
478
|
-
});
|
|
479
|
-
}}
|
|
480
|
-
/>, {
|
|
481
|
-
width: 80,
|
|
482
|
-
height: 24,
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
await testSetup.renderOnce();
|
|
486
|
-
|
|
487
|
-
const frame = testSetup.captureCharFrame();
|
|
488
|
-
expect(frame).toContain("Add Alert");
|
|
489
|
-
expect(frame).toContain("SA");
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
test("opens plugin command shortcut arguments in the wizard for confirmation", async () => {
|
|
493
|
-
const calls: Array<Record<string, string> | undefined> = [];
|
|
494
|
-
|
|
495
|
-
testSetup = await testRender(<CommandBarHarness
|
|
496
|
-
query="SA AAPL above 200"
|
|
497
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
498
|
-
(pluginRegistry.commands as Map<string, any>).set("set-alert", {
|
|
499
|
-
id: "set-alert",
|
|
500
|
-
label: "Add Alert",
|
|
501
|
-
description: "Create a price alert from a symbol, condition, and target price",
|
|
502
|
-
keywords: ["add", "set", "alert", "price", "trigger"],
|
|
503
|
-
shortcut: "SA",
|
|
504
|
-
shortcutArg: {
|
|
505
|
-
placeholder: "symbol condition price",
|
|
506
|
-
kind: "text",
|
|
507
|
-
parse: (arg: string) => {
|
|
508
|
-
const [symbol, condition, price] = arg.split(/\s+/);
|
|
509
|
-
return { symbol, condition, price };
|
|
510
|
-
},
|
|
511
|
-
},
|
|
512
|
-
category: "data",
|
|
513
|
-
wizardLayout: "form",
|
|
514
|
-
wizard: [
|
|
515
|
-
{ key: "symbol", label: "Symbol", type: "text" },
|
|
516
|
-
{
|
|
517
|
-
key: "condition",
|
|
518
|
-
label: "Condition",
|
|
519
|
-
type: "select",
|
|
520
|
-
options: [
|
|
521
|
-
{ label: "Above", value: "above" },
|
|
522
|
-
{ label: "Below", value: "below" },
|
|
523
|
-
],
|
|
524
|
-
},
|
|
525
|
-
{ key: "price", label: "Target Price", type: "number" },
|
|
526
|
-
],
|
|
527
|
-
execute: async (values?: Record<string, string>) => {
|
|
528
|
-
calls.push(values);
|
|
529
|
-
},
|
|
530
|
-
});
|
|
531
|
-
}}
|
|
532
|
-
/>, {
|
|
533
|
-
width: 80,
|
|
534
|
-
height: 24,
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
await testSetup.renderOnce();
|
|
538
|
-
|
|
539
|
-
const frame = testSetup.captureCharFrame();
|
|
540
|
-
expect(frame).toContain("Add Alert");
|
|
541
|
-
expect(frame).toContain("AAPL above 200");
|
|
542
|
-
|
|
543
|
-
await act(async () => {
|
|
544
|
-
testSetup!.mockInput.pressEnter();
|
|
545
|
-
await Bun.sleep(0);
|
|
546
|
-
await testSetup!.renderOnce();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
const workflowFrame = await waitForFrameToContain("Target Price");
|
|
550
|
-
expect(workflowFrame).toContain("AAPL");
|
|
551
|
-
expect(workflowFrame).toContain("Above");
|
|
552
|
-
expect(workflowFrame).toContain("200");
|
|
553
|
-
expect(calls).toEqual([]);
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
test("opens partial plugin command shortcut arguments in the wizard", async () => {
|
|
557
|
-
testSetup = await testRender(<CommandBarHarness
|
|
558
|
-
query="SA AMD"
|
|
559
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
560
|
-
(pluginRegistry.commands as Map<string, any>).set("set-alert", {
|
|
561
|
-
id: "set-alert",
|
|
562
|
-
label: "Add Alert",
|
|
563
|
-
description: "Create a price alert from a symbol, condition, and target price",
|
|
564
|
-
keywords: ["add", "set", "alert", "price", "trigger"],
|
|
565
|
-
shortcut: "SA",
|
|
566
|
-
shortcutArg: {
|
|
567
|
-
placeholder: "symbol condition price",
|
|
568
|
-
kind: "text",
|
|
569
|
-
parse: (arg: string) => ({ symbol: arg.trim().toUpperCase() }),
|
|
570
|
-
},
|
|
571
|
-
category: "data",
|
|
572
|
-
wizardLayout: "form",
|
|
573
|
-
wizard: [
|
|
574
|
-
{ key: "symbol", label: "Symbol", type: "text" },
|
|
575
|
-
{
|
|
576
|
-
key: "condition",
|
|
577
|
-
label: "Condition",
|
|
578
|
-
type: "select",
|
|
579
|
-
options: [{ label: "Above", value: "above" }],
|
|
580
|
-
},
|
|
581
|
-
{ key: "price", label: "Target Price", type: "number" },
|
|
582
|
-
],
|
|
583
|
-
execute: async () => {},
|
|
584
|
-
});
|
|
585
|
-
}}
|
|
586
|
-
/>, {
|
|
587
|
-
width: 80,
|
|
588
|
-
height: 24,
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
await testSetup.renderOnce();
|
|
592
|
-
|
|
593
|
-
await act(async () => {
|
|
594
|
-
testSetup!.mockInput.pressEnter();
|
|
595
|
-
await Bun.sleep(0);
|
|
596
|
-
await testSetup!.renderOnce();
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
const workflowFrame = await waitForFrameToContain("Target Price");
|
|
600
|
-
expect(workflowFrame).toContain("AMD");
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
test("prefills alert command targets from the resolved quote", async () => {
|
|
604
|
-
const quoteProvider = createTestDataProvider({
|
|
605
|
-
id: "test",
|
|
606
|
-
search: async () => [],
|
|
607
|
-
getQuote: async (symbol: string) => ({
|
|
608
|
-
symbol,
|
|
609
|
-
price: 201.5,
|
|
610
|
-
currency: "USD",
|
|
611
|
-
change: 1,
|
|
612
|
-
changePercent: 0.5,
|
|
613
|
-
name: "Advanced Micro Devices",
|
|
614
|
-
exchangeName: "NASDAQ",
|
|
615
|
-
lastUpdated: Date.now(),
|
|
616
|
-
dataSource: "live",
|
|
617
|
-
}),
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
testSetup = await testRender(<CommandBarHarness
|
|
621
|
-
query="SA AMD"
|
|
622
|
-
dataProvider={quoteProvider}
|
|
623
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
624
|
-
(pluginRegistry.commands as Map<string, any>).set("set-alert", {
|
|
625
|
-
id: "set-alert",
|
|
626
|
-
label: "Add Alert",
|
|
627
|
-
description: "Create a price alert from a symbol, condition, and target price",
|
|
628
|
-
keywords: ["add", "set", "alert", "price", "trigger"],
|
|
629
|
-
shortcut: "SA",
|
|
630
|
-
shortcutArg: {
|
|
631
|
-
placeholder: "symbol condition price",
|
|
632
|
-
kind: "ticker",
|
|
633
|
-
parse: (arg: string) => ({ symbol: arg.trim().toUpperCase() }),
|
|
634
|
-
},
|
|
635
|
-
category: "data",
|
|
636
|
-
wizardLayout: "form",
|
|
637
|
-
wizard: [
|
|
638
|
-
{ key: "symbol", label: "Symbol", type: "text" },
|
|
639
|
-
{
|
|
640
|
-
key: "condition",
|
|
641
|
-
label: "Condition",
|
|
642
|
-
type: "select",
|
|
643
|
-
options: [{ label: "Above", value: "above" }],
|
|
644
|
-
},
|
|
645
|
-
{ key: "price", label: "Target Price", type: "number" },
|
|
646
|
-
],
|
|
647
|
-
execute: async () => {},
|
|
648
|
-
});
|
|
649
|
-
}}
|
|
650
|
-
/>, {
|
|
651
|
-
width: 90,
|
|
652
|
-
height: 24,
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
await testSetup.renderOnce();
|
|
656
|
-
|
|
657
|
-
await act(async () => {
|
|
658
|
-
testSetup!.mockInput.pressEnter();
|
|
659
|
-
await Bun.sleep(0);
|
|
660
|
-
await testSetup!.renderOnce();
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
const workflowFrame = await waitForFrameToContain("Advanced Micro Devices");
|
|
664
|
-
expect(workflowFrame).toContain("201.5");
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
test("updates workflow select fields from the option picker", async () => {
|
|
668
|
-
testSetup = await testRender(<CommandBarHarness
|
|
669
|
-
query="SA AMD"
|
|
670
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
671
|
-
(pluginRegistry.commands as Map<string, any>).set("set-alert", {
|
|
672
|
-
id: "set-alert",
|
|
673
|
-
label: "Add Alert",
|
|
674
|
-
description: "Create a price alert from a symbol, condition, and target price",
|
|
675
|
-
keywords: ["add", "set", "alert", "price", "trigger"],
|
|
676
|
-
shortcut: "SA",
|
|
677
|
-
shortcutArg: {
|
|
678
|
-
placeholder: "symbol condition price",
|
|
679
|
-
kind: "text",
|
|
680
|
-
parse: (arg: string) => ({ symbol: arg.trim().toUpperCase() }),
|
|
681
|
-
},
|
|
682
|
-
category: "data",
|
|
683
|
-
wizardLayout: "form",
|
|
684
|
-
wizard: [
|
|
685
|
-
{ key: "symbol", label: "Symbol", type: "text" },
|
|
686
|
-
{
|
|
687
|
-
key: "condition",
|
|
688
|
-
label: "Condition",
|
|
689
|
-
type: "select",
|
|
690
|
-
options: [
|
|
691
|
-
{ label: "Above", value: "above" },
|
|
692
|
-
{ label: "Below", value: "below" },
|
|
693
|
-
{ label: "Crosses", value: "crosses" },
|
|
694
|
-
],
|
|
695
|
-
},
|
|
696
|
-
{ key: "price", label: "Target Price", type: "number" },
|
|
697
|
-
],
|
|
698
|
-
execute: async () => {},
|
|
699
|
-
});
|
|
700
|
-
}}
|
|
701
|
-
/>, {
|
|
702
|
-
width: 80,
|
|
703
|
-
height: 24,
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
await testSetup.renderOnce();
|
|
707
|
-
await act(async () => {
|
|
708
|
-
testSetup!.mockInput.pressEnter();
|
|
709
|
-
await Bun.sleep(0);
|
|
710
|
-
await testSetup!.renderOnce();
|
|
711
|
-
});
|
|
712
|
-
await waitForFrameToContain("Target Price");
|
|
713
|
-
|
|
714
|
-
await act(async () => {
|
|
715
|
-
testSetup!.mockInput.pressTab();
|
|
716
|
-
await testSetup!.renderOnce();
|
|
717
|
-
});
|
|
718
|
-
await act(async () => {
|
|
719
|
-
testSetup!.mockInput.pressEnter();
|
|
720
|
-
await testSetup!.renderOnce();
|
|
721
|
-
});
|
|
722
|
-
let frame = await waitForFrameToContain("Below");
|
|
723
|
-
expect(frame).toContain("Crosses");
|
|
724
|
-
|
|
725
|
-
await act(async () => {
|
|
726
|
-
testSetup!.mockInput.pressArrow("down");
|
|
727
|
-
await testSetup!.renderOnce();
|
|
728
|
-
});
|
|
729
|
-
await act(async () => {
|
|
730
|
-
testSetup!.mockInput.pressEnter();
|
|
731
|
-
await testSetup!.renderOnce();
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
frame = await waitForFrameToContain("Target Price");
|
|
735
|
-
expect(frame).toContain("Below");
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
test("opens plugin command workflows from a launch request", async () => {
|
|
739
|
-
testSetup = await testRender(<CommandBarHarness
|
|
740
|
-
query=""
|
|
741
|
-
selectedTicker="AMD"
|
|
742
|
-
extraTickers={[makeTicker("AMD", "Advanced Micro Devices")]}
|
|
743
|
-
configureState={(state) => ({
|
|
744
|
-
...state,
|
|
745
|
-
commandBarLaunchRequest: {
|
|
746
|
-
kind: "plugin-command",
|
|
747
|
-
commandId: "set-alert",
|
|
748
|
-
sequence: 1,
|
|
749
|
-
},
|
|
750
|
-
})}
|
|
751
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
752
|
-
(pluginRegistry.commands as Map<string, any>).set("set-alert", {
|
|
753
|
-
id: "set-alert",
|
|
754
|
-
label: "Set Alert",
|
|
755
|
-
keywords: ["alert", "price", "trigger"],
|
|
756
|
-
shortcut: "SA",
|
|
757
|
-
shortcutArg: {
|
|
758
|
-
placeholder: "symbol condition price",
|
|
759
|
-
kind: "ticker",
|
|
760
|
-
parse: (_arg: string, context?: { activeTicker: string | null }) => (
|
|
761
|
-
context?.activeTicker ? { symbol: context.activeTicker } : {}
|
|
762
|
-
),
|
|
763
|
-
},
|
|
764
|
-
category: "data",
|
|
765
|
-
wizardLayout: "form",
|
|
766
|
-
wizard: [
|
|
767
|
-
{ key: "symbol", label: "Symbol", type: "text" },
|
|
768
|
-
{
|
|
769
|
-
key: "condition",
|
|
770
|
-
label: "Condition",
|
|
771
|
-
type: "select",
|
|
772
|
-
options: [
|
|
773
|
-
{ label: "Above", value: "above" },
|
|
774
|
-
{ label: "Below", value: "below" },
|
|
775
|
-
],
|
|
776
|
-
},
|
|
777
|
-
{ key: "price", label: "Target Price", type: "number" },
|
|
778
|
-
],
|
|
779
|
-
execute: async () => {},
|
|
780
|
-
});
|
|
781
|
-
}}
|
|
782
|
-
/>, {
|
|
783
|
-
width: 80,
|
|
784
|
-
height: 24,
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
const frame = await waitForFrameToContain("Target Price");
|
|
788
|
-
expect(frame).toContain("Set Alert");
|
|
789
|
-
expect(frame).toContain("Symbol");
|
|
790
|
-
expect(frame).toContain("Condition");
|
|
791
|
-
expect(frame).toContain("AMD");
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
test("renders theme prefix results in the root query", async () => {
|
|
795
|
-
testSetup = await testRender(<CommandBarHarness query="TH " />, {
|
|
796
|
-
width: 80,
|
|
797
|
-
height: 24,
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
await testSetup.renderOnce();
|
|
801
|
-
|
|
802
|
-
const frame = testSetup.captureCharFrame();
|
|
803
|
-
expect(frame).toContain("Commands");
|
|
804
|
-
expect(frame).toContain("TH");
|
|
805
|
-
expect(frame).toContain("Amber");
|
|
806
|
-
expect(frame).toContain("Paper");
|
|
807
|
-
expect(frame).toContain("GitHub Light");
|
|
808
|
-
expect(frame).toContain("Solarized Light");
|
|
809
|
-
expect(frame).toContain("Gruvbox Light");
|
|
810
|
-
expect(frame).toContain("Nord Light");
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
test("renders plugin toggle state", async () => {
|
|
814
|
-
testSetup = await testRender(<CommandBarHarness query="PL " disabledPlugins={["notes"]} />, {
|
|
815
|
-
width: 80,
|
|
816
|
-
height: 24,
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
await testSetup.renderOnce();
|
|
820
|
-
|
|
821
|
-
expect(testSetup.captureCharFrame()).toMatchSnapshot();
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
test("only surfaces PS when the focused pane exposes settings", async () => {
|
|
825
|
-
testSetup = await testRender(
|
|
826
|
-
<CommandBarHarness query="PS" hasPaneSettings={(paneId) => paneId === "portfolio-list:main"} />,
|
|
827
|
-
{ width: 80, height: 24 },
|
|
828
|
-
);
|
|
829
|
-
|
|
830
|
-
await testSetup.renderOnce();
|
|
831
|
-
expect(testSetup.captureCharFrame()).toContain("Pane Settings");
|
|
832
|
-
|
|
833
|
-
testSetup.renderer.destroy();
|
|
834
|
-
testSetup = await testRender(<CommandBarHarness query="PS" hasPaneSettings={() => false} />, {
|
|
835
|
-
width: 80,
|
|
836
|
-
height: 24,
|
|
837
|
-
});
|
|
838
|
-
|
|
839
|
-
await testSetup.renderOnce();
|
|
840
|
-
expect(testSetup.captureCharFrame()).not.toContain("Pane Settings");
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
test("collapses the trailing column on narrow terminals", async () => {
|
|
844
|
-
testSetup = await testRender(<CommandBarHarness query="" selectedTicker="AAPL" />, {
|
|
845
|
-
width: 42,
|
|
846
|
-
height: 20,
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
await testSetup.renderOnce();
|
|
850
|
-
|
|
851
|
-
expect(testSetup.captureCharFrame()).toMatchSnapshot();
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
test("moves the theme selector with arrow keys", async () => {
|
|
855
|
-
testSetup = await testRender(<CommandBarHarness query="TH " live />, {
|
|
856
|
-
width: 80,
|
|
857
|
-
height: 24,
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
await testSetup.renderOnce();
|
|
861
|
-
await act(async () => {
|
|
862
|
-
testSetup!.mockInput.pressArrow("down");
|
|
863
|
-
await Bun.sleep(220);
|
|
864
|
-
await testSetup!.renderOnce();
|
|
865
|
-
});
|
|
866
|
-
await testSetup.renderOnce();
|
|
867
|
-
|
|
868
|
-
const frame = testSetup.captureCharFrame();
|
|
869
|
-
expect(frame).toContain("theme:green");
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
test("debounces rapid keyboard theme previews", async () => {
|
|
873
|
-
const actions: AppAction[] = [];
|
|
874
|
-
testSetup = await testRender(
|
|
875
|
-
<CommandBarHarness query="TH " live onAction={(action) => actions.push(action)} />,
|
|
876
|
-
{ width: 80, height: 24 },
|
|
877
|
-
);
|
|
878
|
-
|
|
879
|
-
await testSetup.renderOnce();
|
|
880
|
-
await act(async () => {
|
|
881
|
-
for (let index = 0; index < 6; index++) {
|
|
882
|
-
testSetup!.mockInput.pressArrow("down");
|
|
883
|
-
}
|
|
884
|
-
await testSetup!.renderOnce();
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
expect(actions.filter((action) => action.type === "PREVIEW_THEME")).toHaveLength(0);
|
|
888
|
-
|
|
889
|
-
await act(async () => {
|
|
890
|
-
await Bun.sleep(100);
|
|
891
|
-
await testSetup!.renderOnce();
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
expect(actions.filter((action) => action.type === "PREVIEW_THEME")).toHaveLength(0);
|
|
895
|
-
|
|
896
|
-
await act(async () => {
|
|
897
|
-
await Bun.sleep(150);
|
|
898
|
-
await testSetup!.renderOnce();
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
expect(actions.filter((action) => action.type === "PREVIEW_THEME")).toHaveLength(1);
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
test("does not preview a theme from mouse hover alone", async () => {
|
|
905
|
-
testSetup = await testRender(<CommandBarHarness query="TH " live />, {
|
|
906
|
-
width: 80,
|
|
907
|
-
height: 24,
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
await testSetup.renderOnce();
|
|
911
|
-
const frame = testSetup.captureCharFrame();
|
|
912
|
-
const rows = frame.split("\n");
|
|
913
|
-
const hoveredRow = rows.findIndex((line) => line.includes("Green"));
|
|
914
|
-
const hoveredCol = rows[hoveredRow]?.indexOf("Green") ?? -1;
|
|
915
|
-
|
|
916
|
-
expect(hoveredRow).toBeGreaterThanOrEqual(0);
|
|
917
|
-
expect(hoveredCol).toBeGreaterThanOrEqual(0);
|
|
918
|
-
|
|
919
|
-
await act(async () => {
|
|
920
|
-
await testSetup!.mockMouse.moveTo(hoveredCol + 1, hoveredRow);
|
|
921
|
-
await testSetup!.renderOnce();
|
|
922
|
-
});
|
|
923
|
-
await testSetup.renderOnce();
|
|
924
|
-
|
|
925
|
-
const hoveredFrame = testSetup.captureCharFrame();
|
|
926
|
-
expect(hoveredFrame).toContain("theme:amber");
|
|
927
|
-
expect(hoveredFrame).not.toContain("theme:green");
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
test("keeps the selected theme after pressing enter in root theme mode", async () => {
|
|
931
|
-
testSetup = await testRender(<CommandBarHarness query="TH " live />, {
|
|
932
|
-
width: 80,
|
|
933
|
-
height: 24,
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
await testSetup.renderOnce();
|
|
937
|
-
await act(async () => {
|
|
938
|
-
testSetup!.mockInput.pressArrow("down");
|
|
939
|
-
await Bun.sleep(220);
|
|
940
|
-
await testSetup!.renderOnce();
|
|
941
|
-
});
|
|
942
|
-
await testSetup.renderOnce();
|
|
943
|
-
|
|
944
|
-
await act(async () => {
|
|
945
|
-
testSetup!.mockInput.pressEnter();
|
|
946
|
-
await Bun.sleep(0);
|
|
947
|
-
await testSetup!.renderOnce();
|
|
948
|
-
});
|
|
949
|
-
await testSetup.renderOnce();
|
|
950
|
-
|
|
951
|
-
const frame = testSetup.captureCharFrame();
|
|
952
|
-
expect(frame).toContain("theme:green");
|
|
953
|
-
expect(frame).not.toContain("theme:amber");
|
|
954
|
-
expect(frame).not.toContain("Commands");
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
test("uses pending keyboard theme selection when enter follows before a repaint", async () => {
|
|
958
|
-
testSetup = await testRender(<CommandBarHarness query="TH " live />, {
|
|
959
|
-
width: 80,
|
|
960
|
-
height: 24,
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
await testSetup.renderOnce();
|
|
964
|
-
await act(async () => {
|
|
965
|
-
testSetup!.mockInput.pressArrow("down");
|
|
966
|
-
testSetup!.mockInput.pressEnter();
|
|
967
|
-
await Bun.sleep(0);
|
|
968
|
-
await testSetup!.renderOnce();
|
|
969
|
-
});
|
|
970
|
-
await testSetup.renderOnce();
|
|
971
|
-
|
|
972
|
-
const frame = testSetup.captureCharFrame();
|
|
973
|
-
expect(frame).toContain("theme:green");
|
|
974
|
-
expect(frame).not.toContain("theme:amber");
|
|
975
|
-
expect(frame).not.toContain("Commands");
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
test("clears theme preview when leaving root theme mode", async () => {
|
|
979
|
-
testSetup = await testRender(<CommandBarHarness query="TH " live />, {
|
|
980
|
-
width: 80,
|
|
981
|
-
height: 24,
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
await testSetup.renderOnce();
|
|
985
|
-
await act(async () => {
|
|
986
|
-
testSetup!.mockInput.pressArrow("down");
|
|
987
|
-
await Bun.sleep(220);
|
|
988
|
-
await testSetup!.renderOnce();
|
|
989
|
-
});
|
|
990
|
-
await testSetup.renderOnce();
|
|
991
|
-
expect(testSetup.captureCharFrame()).toContain("theme:green");
|
|
992
|
-
|
|
993
|
-
await emitKeypress(testSetup, { name: "u", ctrl: true });
|
|
994
|
-
await testSetup.renderOnce();
|
|
995
|
-
|
|
996
|
-
const frame = testSetup.captureCharFrame();
|
|
997
|
-
expect(frame).toContain("theme:amber");
|
|
998
|
-
expect(frame).not.toContain("theme:green");
|
|
999
|
-
expect(frame).toContain("Commands");
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
test("keeps typed prefixes in the root query until a result is activated", async () => {
|
|
1003
|
-
testSetup = await testRender(<CommandBarHarness query="DES " />, {
|
|
1004
|
-
width: 80,
|
|
1005
|
-
height: 24,
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
await testSetup.renderOnce();
|
|
1009
|
-
|
|
1010
|
-
let frame = testSetup.captureCharFrame();
|
|
1011
|
-
expect(frame).toContain("Commands");
|
|
1012
|
-
expect(frame).toContain("DES");
|
|
1013
|
-
expect(frame).toContain("Type a ticker symbol");
|
|
1014
|
-
expect(frame).not.toContain("Back");
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
test("QQ without an active ticker opens inline ticker-list entry on enter", async () => {
|
|
1018
|
-
testSetup = await testRender(<CommandBarHarness query="QQ" />, {
|
|
1019
|
-
width: 100,
|
|
1020
|
-
height: 20,
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
await testSetup.renderOnce();
|
|
1024
|
-
expect(testSetup.captureCharFrame()).toContain("Quote Monitor");
|
|
1025
|
-
expect(testSetup.captureCharFrame()).not.toContain("Back");
|
|
1026
|
-
|
|
1027
|
-
await act(async () => {
|
|
1028
|
-
testSetup!.mockInput.pressEnter();
|
|
1029
|
-
await testSetup!.renderOnce();
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
const frame = testSetup.captureCharFrame();
|
|
1033
|
-
expect(frame).toContain("Back");
|
|
1034
|
-
expect(frame).toContain("Quote Tickers");
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
test("T without an active ticker opens ticker search on enter", async () => {
|
|
1038
|
-
testSetup = await testRender(<CommandBarHarness query="T" />, {
|
|
1039
|
-
width: 100,
|
|
1040
|
-
height: 20,
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
await testSetup.renderOnce();
|
|
1044
|
-
expect(testSetup.captureCharFrame()).toContain("Description");
|
|
1045
|
-
|
|
1046
|
-
await act(async () => {
|
|
1047
|
-
testSetup!.mockInput.pressEnter();
|
|
1048
|
-
await testSetup!.renderOnce();
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
const frame = testSetup.captureCharFrame();
|
|
1052
|
-
expect(frame).toContain("Back");
|
|
1053
|
-
expect(frame).toContain("Security Description");
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
test("QQ with an active ticker shows ghost completion and tab inserts the symbol", async () => {
|
|
1057
|
-
testSetup = await testRender(<CommandBarHarness query="QQ" live selectedTicker="AAPL" showQueryState />, {
|
|
1058
|
-
width: 100,
|
|
1059
|
-
height: 20,
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
await testSetup.renderOnce();
|
|
1063
|
-
expect(testSetup.captureCharFrame()).toContain("QQ AAPL");
|
|
1064
|
-
expect(testSetup.captureCharFrame()).toContain("Shortcut: Quote Monitor for AAPL");
|
|
1065
|
-
expect(testSetup.captureCharFrame()).toContain("query:QQ");
|
|
1066
|
-
|
|
1067
|
-
await act(async () => {
|
|
1068
|
-
testSetup!.mockInput.pressTab();
|
|
1069
|
-
await testSetup!.renderOnce();
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
const frame = testSetup.captureCharFrame();
|
|
1073
|
-
expect(frame).toContain("query:QQ AAPL");
|
|
1074
|
-
});
|
|
1075
|
-
|
|
1076
|
-
test("typing a shorthand and pressing enter executes the inferred quote monitor shortcut", async () => {
|
|
1077
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
1078
|
-
|
|
1079
|
-
testSetup = await testRender(<CommandBarHarness
|
|
1080
|
-
query=""
|
|
1081
|
-
live
|
|
1082
|
-
selectedTicker="AAPL"
|
|
1083
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1084
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
1085
|
-
created.push({ templateId, options });
|
|
1086
|
-
};
|
|
1087
|
-
}}
|
|
1088
|
-
/>, {
|
|
1089
|
-
width: 100,
|
|
1090
|
-
height: 20,
|
|
1091
|
-
});
|
|
1092
|
-
|
|
1093
|
-
await testSetup.renderOnce();
|
|
1094
|
-
|
|
1095
|
-
await act(async () => {
|
|
1096
|
-
await testSetup!.mockInput.typeText("QQ");
|
|
1097
|
-
testSetup!.mockInput.pressEnter();
|
|
1098
|
-
await Bun.sleep(0);
|
|
1099
|
-
await testSetup!.renderOnce();
|
|
1100
|
-
});
|
|
1101
|
-
|
|
1102
|
-
expect(created).toEqual([{
|
|
1103
|
-
templateId: "quote-monitor-pane",
|
|
1104
|
-
options: {
|
|
1105
|
-
arg: "AAPL",
|
|
1106
|
-
symbols: ["AAPL"],
|
|
1107
|
-
},
|
|
1108
|
-
}]);
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
test("consumes enter before focused pane shortcuts when executing a pane shortcut", async () => {
|
|
1112
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
1113
|
-
let leakedEnterCount = 0;
|
|
1114
|
-
|
|
1115
|
-
testSetup = await testRender(<CommandBarHarness
|
|
1116
|
-
query="PF"
|
|
1117
|
-
live
|
|
1118
|
-
onUnhandledEnter={() => {
|
|
1119
|
-
leakedEnterCount += 1;
|
|
1120
|
-
}}
|
|
1121
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1122
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
1123
|
-
created.push({ templateId, options });
|
|
1124
|
-
};
|
|
1125
|
-
}}
|
|
1126
|
-
/>, {
|
|
1127
|
-
width: 100,
|
|
1128
|
-
height: 20,
|
|
1129
|
-
});
|
|
1130
|
-
|
|
1131
|
-
await testSetup.renderOnce();
|
|
1132
|
-
|
|
1133
|
-
await act(async () => {
|
|
1134
|
-
testSetup!.mockInput.pressEnter();
|
|
1135
|
-
await Bun.sleep(0);
|
|
1136
|
-
await testSetup!.renderOnce();
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
expect(created).toEqual([{ templateId: "new-portfolio-pane", options: undefined }]);
|
|
1140
|
-
expect(leakedEnterCount).toBe(0);
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
test("typing a chat channel shortcut opens that channel directly", async () => {
|
|
1144
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
1145
|
-
|
|
1146
|
-
testSetup = await testRender(<CommandBarHarness
|
|
1147
|
-
query=""
|
|
1148
|
-
live
|
|
1149
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1150
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
1151
|
-
created.push({ templateId, options });
|
|
1152
|
-
};
|
|
1153
|
-
}}
|
|
1154
|
-
/>, {
|
|
1155
|
-
width: 100,
|
|
1156
|
-
height: 20,
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
await testSetup.renderOnce();
|
|
1160
|
-
|
|
1161
|
-
await act(async () => {
|
|
1162
|
-
await testSetup!.mockInput.typeText("CHAT help");
|
|
1163
|
-
testSetup!.mockInput.pressEnter();
|
|
1164
|
-
await Bun.sleep(0);
|
|
1165
|
-
await testSetup!.renderOnce();
|
|
1166
|
-
});
|
|
1167
|
-
|
|
1168
|
-
expect(created).toEqual([{
|
|
1169
|
-
templateId: "new-chat-pane",
|
|
1170
|
-
options: {
|
|
1171
|
-
arg: "help",
|
|
1172
|
-
},
|
|
1173
|
-
}]);
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
test("clears the root query with cmd-backspace", async () => {
|
|
1177
|
-
testSetup = await testRender(<CommandBarHarness query="DES AMD" />, {
|
|
1178
|
-
width: 80,
|
|
1179
|
-
height: 24,
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
await testSetup.renderOnce();
|
|
1183
|
-
expect(testSetup.captureCharFrame()).toContain("DES AMD");
|
|
1184
|
-
|
|
1185
|
-
await act(async () => {
|
|
1186
|
-
testSetup!.mockInput.pressKey("backspace", { meta: true });
|
|
1187
|
-
await testSetup!.renderOnce();
|
|
1188
|
-
});
|
|
1189
|
-
|
|
1190
|
-
const frame = testSetup.captureCharFrame();
|
|
1191
|
-
expect(frame).toContain("Commands");
|
|
1192
|
-
expect(frame).toContain("Search");
|
|
1193
|
-
expect(frame).not.toContain("DES AMD");
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
test("pressing the close shortcut at the root closes the command bar", async () => {
|
|
1197
|
-
testSetup = await testRender(<CommandBarHarness query="" live />, {
|
|
1198
|
-
width: 80,
|
|
1199
|
-
height: 24,
|
|
1200
|
-
});
|
|
1201
|
-
|
|
1202
|
-
await testSetup.renderOnce();
|
|
1203
|
-
|
|
1204
|
-
await act(async () => {
|
|
1205
|
-
testSetup!.mockInput.pressKey("`");
|
|
1206
|
-
await testSetup!.renderOnce();
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
expect(testSetup.captureCharFrame()).not.toContain("Commands");
|
|
1210
|
-
});
|
|
1211
|
-
|
|
1212
|
-
test("loads provider-backed ticker search results in the root command results", async () => {
|
|
1213
|
-
testSetup = await testRender(
|
|
1214
|
-
<CommandBarHarness
|
|
1215
|
-
query="DES appl"
|
|
1216
|
-
dataProvider={makeDataProvider(async () => [
|
|
1217
|
-
{ providerId: "yahoo", symbol: "IVSX", name: "Invsivx Holdings", exchange: "NYSE", type: "ETF" },
|
|
1218
|
-
{ providerId: "yahoo", symbol: "AAPL", name: "Apple Inc", exchange: "NASDAQ", type: "EQUITY" },
|
|
1219
|
-
{ providerId: "yahoo", symbol: "AMAT", name: "Applied Materials", exchange: "NASDAQ", type: "EQUITY" },
|
|
1220
|
-
{ providerId: "yahoo", symbol: "AAOI", name: "Applied Optoelectronics", exchange: "NASDAQ", type: "EQUITY" },
|
|
1221
|
-
{ providerId: "yahoo", symbol: "APP", name: "AppLovin Corp", exchange: "NASDAQ", type: "EQUITY" },
|
|
1222
|
-
])}
|
|
1223
|
-
/>,
|
|
1224
|
-
{ width: 80, height: 24 },
|
|
1225
|
-
);
|
|
1226
|
-
|
|
1227
|
-
await testSetup.renderOnce();
|
|
1228
|
-
const frame = await waitForFrameToContain("AAOI");
|
|
1229
|
-
expect(frame).toContain("Saved");
|
|
1230
|
-
expect(frame).toContain("Other Listings");
|
|
1231
|
-
expect(frame).toContain("APP");
|
|
1232
|
-
expect(frame).toContain("AAOI");
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
test("keeps exact T ticker matches above noisy provider matches", async () => {
|
|
1236
|
-
testSetup = await testRender(
|
|
1237
|
-
<CommandBarHarness
|
|
1238
|
-
query="T AMD"
|
|
1239
|
-
extraTickers={[
|
|
1240
|
-
makeTicker("AMDL", "GraniteShares 2x Long AMD Daily"),
|
|
1241
|
-
makeTicker("AMUU", "Direxion Daily AMD Bull 2X ETF"),
|
|
1242
|
-
]}
|
|
1243
|
-
dataProvider={makeDataProvider(async () => [
|
|
1244
|
-
{ providerId: "yahoo", symbol: "DES", name: "AMD-themed synthetic listing", exchange: "ASX", type: "EQUITY" },
|
|
1245
|
-
{ providerId: "yahoo", symbol: "DES2", name: "AMD-themed secondary listing", exchange: "LSE", type: "EQUITY" },
|
|
1246
|
-
{ providerId: "yahoo", symbol: "AMD", name: "Advanced Micro Devices", exchange: "NASDAQ", type: "EQUITY" },
|
|
1247
|
-
])}
|
|
1248
|
-
/>,
|
|
1249
|
-
{ width: 100, height: 24 },
|
|
1250
|
-
);
|
|
1251
|
-
|
|
1252
|
-
await testSetup.renderOnce();
|
|
1253
|
-
const frame = await waitForFrameToContain("DES2");
|
|
1254
|
-
const resultLines = frame
|
|
1255
|
-
.split("\n")
|
|
1256
|
-
.filter((line) => /\b(AMD|DES2?|DES)\b/.test(line) && /\b(NASDAQ|ASX|LSE)\b/.test(line));
|
|
1257
|
-
|
|
1258
|
-
expect(resultLines[0]).toContain("AMD");
|
|
1259
|
-
expect(resultLines[0]).toContain("NASDAQ");
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
test("loads exact provider matches for plain ticker-shaped root queries", async () => {
|
|
1263
|
-
testSetup = await testRender(
|
|
1264
|
-
<CommandBarHarness
|
|
1265
|
-
query="amd"
|
|
1266
|
-
extraTickers={[
|
|
1267
|
-
makeTicker("AMDL", "GraniteShares 2x Long AMD Daily"),
|
|
1268
|
-
makeTicker("AMUU", "Direxion Daily AMD Bull 2X ETF"),
|
|
1269
|
-
]}
|
|
1270
|
-
dataProvider={makeDataProvider(async () => [
|
|
1271
|
-
{ providerId: "yahoo", symbol: "DES", name: "AMD-themed synthetic listing", exchange: "ASX", type: "EQUITY" },
|
|
1272
|
-
{ providerId: "yahoo", symbol: "AMD", name: "Advanced Micro Devices", exchange: "NASDAQ", type: "EQUITY" },
|
|
1273
|
-
])}
|
|
1274
|
-
/>,
|
|
1275
|
-
{ width: 100, height: 24 },
|
|
1276
|
-
);
|
|
1277
|
-
|
|
1278
|
-
await testSetup.renderOnce();
|
|
1279
|
-
const frame = await waitForFrameToContain("Exact Match");
|
|
1280
|
-
const resultLines = frame
|
|
1281
|
-
.split("\n")
|
|
1282
|
-
.filter((line) => /\b(AMD|AMDL|AMUU|DES)\b/.test(line) && /\b(NASDAQ|ASX)\b/.test(line));
|
|
1283
|
-
|
|
1284
|
-
expect(resultLines[0]).toContain("AMD");
|
|
1285
|
-
expect(resultLines[0]).toContain("NASDAQ");
|
|
1286
|
-
});
|
|
1287
|
-
|
|
1288
|
-
test("keeps pane matches visible alongside plain ticker provider results", async () => {
|
|
1289
|
-
testSetup = await testRender(
|
|
1290
|
-
<CommandBarHarness
|
|
1291
|
-
query="opti"
|
|
1292
|
-
dataProvider={makeDataProvider(async () => [
|
|
1293
|
-
{ providerId: "yahoo", symbol: "OPTI", name: "Optimi Health Corp", exchange: "CSE", type: "EQUITY" },
|
|
1294
|
-
{ providerId: "yahoo", symbol: "OPTI", name: "Optimi Health Corp", exchange: "NEO", type: "EQUITY" },
|
|
1295
|
-
{ providerId: "yahoo", symbol: "OPTI", name: "Optimi Health Corp", exchange: "LSE", type: "EQUITY" },
|
|
1296
|
-
])}
|
|
1297
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1298
|
-
const paneTemplates = pluginRegistry.paneTemplates as Map<string, any>;
|
|
1299
|
-
paneTemplates.set("options-pane", {
|
|
1300
|
-
id: "options-pane",
|
|
1301
|
-
paneId: "options",
|
|
1302
|
-
label: "Options",
|
|
1303
|
-
description: "Options chain for the selected ticker",
|
|
1304
|
-
shortcut: { prefix: "OMON", argPlaceholder: "ticker", argKind: "ticker" },
|
|
1305
|
-
canCreate: (context: any, options?: PaneTemplateCreateOptions) => (
|
|
1306
|
-
context.activeTicker !== null || !!options?.arg
|
|
1307
|
-
),
|
|
1308
|
-
});
|
|
1309
|
-
}}
|
|
1310
|
-
/>,
|
|
1311
|
-
{ width: 100, height: 24 },
|
|
1312
|
-
);
|
|
1313
|
-
|
|
1314
|
-
await testSetup.renderOnce();
|
|
1315
|
-
const frame = await waitForFrameToContain("OPTI");
|
|
1316
|
-
expect(frame).toContain("Options");
|
|
1317
|
-
expect(frame).toContain("OMON");
|
|
1318
|
-
});
|
|
1319
|
-
|
|
1320
|
-
test("clears stale provider rows after a plain ticker root search is cleared", async () => {
|
|
1321
|
-
testSetup = await testRender(
|
|
1322
|
-
<CommandBarHarness
|
|
1323
|
-
query=""
|
|
1324
|
-
live
|
|
1325
|
-
dataProvider={makeDataProvider(async () => [
|
|
1326
|
-
{ providerId: "yahoo", symbol: "OPTI", name: "Optimi Health Corp", exchange: "CSE", type: "EQUITY" },
|
|
1327
|
-
{ providerId: "yahoo", symbol: "OPTI", name: "Optimi Health Corp", exchange: "NEO", type: "EQUITY" },
|
|
1328
|
-
{ providerId: "yahoo", symbol: "OPTI", name: "Optimi Health Corp", exchange: "LSE", type: "EQUITY" },
|
|
1329
|
-
])}
|
|
1330
|
-
/>,
|
|
1331
|
-
{ width: 100, height: 24 },
|
|
1332
|
-
);
|
|
1333
|
-
|
|
1334
|
-
await testSetup.renderOnce();
|
|
1335
|
-
await act(async () => {
|
|
1336
|
-
await testSetup!.mockInput.typeText("opti");
|
|
1337
|
-
await testSetup!.renderOnce();
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
await waitForFrameToContain("OPTI");
|
|
1341
|
-
await emitKeypress(testSetup, { name: "u", ctrl: true });
|
|
1342
|
-
await renderFrames(2);
|
|
1343
|
-
|
|
1344
|
-
const frame = testSetup.captureCharFrame();
|
|
1345
|
-
expect(frame).toContain("Search");
|
|
1346
|
-
expect(frame).toContain("Tickers");
|
|
1347
|
-
expect(frame).not.toContain("OPTI");
|
|
1348
|
-
});
|
|
1349
|
-
|
|
1350
|
-
test("DES MSFT opens an exact ticker directly", async () => {
|
|
1351
|
-
const pinned: string[] = [];
|
|
1352
|
-
|
|
1353
|
-
testSetup = await testRender(
|
|
1354
|
-
<CommandBarHarness
|
|
1355
|
-
query="DES MSFT"
|
|
1356
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1357
|
-
pluginRegistry.pinTicker = (symbol) => {
|
|
1358
|
-
pinned.push(symbol);
|
|
1359
|
-
};
|
|
1360
|
-
}}
|
|
1361
|
-
/>,
|
|
1362
|
-
{ width: 100, height: 20 },
|
|
1363
|
-
);
|
|
1364
|
-
|
|
1365
|
-
await testSetup.renderOnce();
|
|
1366
|
-
await act(async () => {
|
|
1367
|
-
testSetup!.mockInput.pressEnter();
|
|
1368
|
-
await Bun.sleep(0);
|
|
1369
|
-
await testSetup!.renderOnce();
|
|
1370
|
-
await testSetup!.renderOnce();
|
|
1371
|
-
});
|
|
1372
|
-
|
|
1373
|
-
expect(pinned).toEqual(["MSFT"]);
|
|
1374
|
-
});
|
|
1375
|
-
|
|
1376
|
-
test("T AMD opens an exact ticker directly", async () => {
|
|
1377
|
-
const pinned: string[] = [];
|
|
1378
|
-
|
|
1379
|
-
testSetup = await testRender(
|
|
1380
|
-
<CommandBarHarness
|
|
1381
|
-
query="T AMD"
|
|
1382
|
-
extraTickers={[makeTicker("AMD", "Advanced Micro Devices")]}
|
|
1383
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1384
|
-
pluginRegistry.pinTicker = (symbol) => {
|
|
1385
|
-
pinned.push(symbol);
|
|
1386
|
-
};
|
|
1387
|
-
}}
|
|
1388
|
-
/>,
|
|
1389
|
-
{ width: 100, height: 20 },
|
|
1390
|
-
);
|
|
1391
|
-
|
|
1392
|
-
await testSetup.renderOnce();
|
|
1393
|
-
await act(async () => {
|
|
1394
|
-
testSetup!.mockInput.pressEnter();
|
|
1395
|
-
await Bun.sleep(0);
|
|
1396
|
-
await testSetup!.renderOnce();
|
|
1397
|
-
await testSetup!.renderOnce();
|
|
1398
|
-
});
|
|
1399
|
-
|
|
1400
|
-
expect(pinned).toEqual(["AMD"]);
|
|
1401
|
-
});
|
|
1402
|
-
|
|
1403
|
-
test("AW AAPL uses the active watchlist target by default", async () => {
|
|
1404
|
-
const saved: TickerRecord[] = [];
|
|
1405
|
-
|
|
1406
|
-
testSetup = await testRender(
|
|
1407
|
-
<CommandBarHarness
|
|
1408
|
-
query="AW AAPL"
|
|
1409
|
-
onSaveTicker={(ticker) => {
|
|
1410
|
-
saved.push(ticker);
|
|
1411
|
-
}}
|
|
1412
|
-
configureState={(state) => ({
|
|
1413
|
-
...state,
|
|
1414
|
-
paneState: {
|
|
1415
|
-
...state.paneState,
|
|
1416
|
-
"portfolio-list:main": {
|
|
1417
|
-
collectionId: "watchlist",
|
|
1418
|
-
},
|
|
1419
|
-
},
|
|
1420
|
-
})}
|
|
1421
|
-
/>,
|
|
1422
|
-
{ width: 100, height: 20 },
|
|
1423
|
-
);
|
|
1424
|
-
|
|
1425
|
-
await testSetup.renderOnce();
|
|
1426
|
-
await act(async () => {
|
|
1427
|
-
testSetup!.mockInput.pressEnter();
|
|
1428
|
-
await Bun.sleep(0);
|
|
1429
|
-
await testSetup!.renderOnce();
|
|
1430
|
-
});
|
|
1431
|
-
|
|
1432
|
-
expect(saved.at(-1)?.metadata.watchlists).toEqual(["watchlist"]);
|
|
1433
|
-
});
|
|
1434
|
-
|
|
1435
|
-
test("bare AW without a compatible active target adds to the sole watchlist directly", async () => {
|
|
1436
|
-
const saved: TickerRecord[] = [];
|
|
1437
|
-
|
|
1438
|
-
testSetup = await testRender(
|
|
1439
|
-
<CommandBarHarness
|
|
1440
|
-
query="AW"
|
|
1441
|
-
selectedTicker="AAPL"
|
|
1442
|
-
onSaveTicker={(ticker) => {
|
|
1443
|
-
saved.push(ticker);
|
|
1444
|
-
}}
|
|
1445
|
-
/>,
|
|
1446
|
-
{ width: 100, height: 20 },
|
|
1447
|
-
);
|
|
1448
|
-
|
|
1449
|
-
await testSetup.renderOnce();
|
|
1450
|
-
await act(async () => {
|
|
1451
|
-
testSetup!.mockInput.pressEnter();
|
|
1452
|
-
await Bun.sleep(0);
|
|
1453
|
-
await testSetup!.renderOnce();
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1456
|
-
expect(saved.at(-1)?.metadata.watchlists).toEqual(["watchlist"]);
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
|
-
test("bare AW without a compatible active target opens inline target selection when multiple watchlists exist", async () => {
|
|
1460
|
-
testSetup = await testRender(
|
|
1461
|
-
<CommandBarHarness
|
|
1462
|
-
query="AW"
|
|
1463
|
-
selectedTicker="AAPL"
|
|
1464
|
-
configureConfig={(config) => ({
|
|
1465
|
-
...config,
|
|
1466
|
-
watchlists: [
|
|
1467
|
-
{ id: "watchlist", name: "Watchlist" },
|
|
1468
|
-
{ id: "growth", name: "Growth Radar" },
|
|
1469
|
-
],
|
|
1470
|
-
})}
|
|
1471
|
-
/>,
|
|
1472
|
-
{ width: 100, height: 20 },
|
|
1473
|
-
);
|
|
1474
|
-
|
|
1475
|
-
await testSetup.renderOnce();
|
|
1476
|
-
await act(async () => {
|
|
1477
|
-
testSetup!.mockInput.pressEnter();
|
|
1478
|
-
await testSetup!.renderOnce();
|
|
1479
|
-
});
|
|
1480
|
-
|
|
1481
|
-
const frame = testSetup.captureCharFrame();
|
|
1482
|
-
expect(frame).toContain("Add AAPL to Watchlist");
|
|
1483
|
-
expect(frame).toContain("Watchlist");
|
|
1484
|
-
expect(frame).toContain("Back");
|
|
1485
|
-
});
|
|
1486
|
-
|
|
1487
|
-
test("typing add still surfaces Add to Portfolio for a ticker already in the active manual portfolio", async () => {
|
|
1488
|
-
testSetup = await testRender(
|
|
1489
|
-
<CommandBarHarness
|
|
1490
|
-
query="add"
|
|
1491
|
-
selectedTicker="AAPL"
|
|
1492
|
-
configureConfig={(config) => ({
|
|
1493
|
-
...config,
|
|
1494
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1495
|
-
})}
|
|
1496
|
-
configureState={(state) => ({
|
|
1497
|
-
...state,
|
|
1498
|
-
paneState: {
|
|
1499
|
-
...state.paneState,
|
|
1500
|
-
"portfolio-list:main": {
|
|
1501
|
-
collectionId: "research",
|
|
1502
|
-
cursorSymbol: "AAPL",
|
|
1503
|
-
},
|
|
1504
|
-
},
|
|
1505
|
-
})}
|
|
1506
|
-
extraTickers={[makeTicker("AAPL", "Apple Inc.", {
|
|
1507
|
-
portfolios: ["research"],
|
|
1508
|
-
})]}
|
|
1509
|
-
/>,
|
|
1510
|
-
{ width: 100, height: 20 },
|
|
1511
|
-
);
|
|
1512
|
-
|
|
1513
|
-
await testSetup.renderOnce();
|
|
1514
|
-
|
|
1515
|
-
const frame = testSetup.captureCharFrame();
|
|
1516
|
-
expect(frame).toContain("Add AAPL to Portfolio");
|
|
1517
|
-
});
|
|
1518
|
-
|
|
1519
|
-
test("AP opens the add-to-portfolio workflow and prefills avg cost from the current price", async () => {
|
|
1520
|
-
testSetup = await testRender(
|
|
1521
|
-
<CommandBarHarness
|
|
1522
|
-
query="AP AAPL"
|
|
1523
|
-
selectedTicker="AAPL"
|
|
1524
|
-
configureConfig={(config) => ({
|
|
1525
|
-
...config,
|
|
1526
|
-
portfolios: [{ id: "research", name: "Only Manual Portfolio", currency: "USD" }],
|
|
1527
|
-
})}
|
|
1528
|
-
configureState={(state) => ({
|
|
1529
|
-
...state,
|
|
1530
|
-
paneState: {
|
|
1531
|
-
...state.paneState,
|
|
1532
|
-
"portfolio-list:main": {
|
|
1533
|
-
collectionId: "research",
|
|
1534
|
-
cursorSymbol: "AAPL",
|
|
1535
|
-
},
|
|
1536
|
-
},
|
|
1537
|
-
financials: new Map([["AAPL", {
|
|
1538
|
-
annualStatements: [],
|
|
1539
|
-
quarterlyStatements: [],
|
|
1540
|
-
priceHistory: [],
|
|
1541
|
-
quote: {
|
|
1542
|
-
symbol: "AAPL",
|
|
1543
|
-
price: 205.5,
|
|
1544
|
-
currency: "USD",
|
|
1545
|
-
change: 1.25,
|
|
1546
|
-
changePercent: 0.61,
|
|
1547
|
-
lastUpdated: Date.now(),
|
|
1548
|
-
},
|
|
1549
|
-
}]]),
|
|
1550
|
-
})}
|
|
1551
|
-
extraTickers={[makeTicker("AAPL", "Apple Inc.", {
|
|
1552
|
-
portfolios: ["research"],
|
|
1553
|
-
})]}
|
|
1554
|
-
/>,
|
|
1555
|
-
{ width: 100, height: 30 },
|
|
1556
|
-
);
|
|
1557
|
-
|
|
1558
|
-
await testSetup.renderOnce();
|
|
1559
|
-
await act(async () => {
|
|
1560
|
-
testSetup!.mockInput.pressEnter();
|
|
1561
|
-
await testSetup!.renderOnce();
|
|
1562
|
-
});
|
|
1563
|
-
|
|
1564
|
-
const frame = await waitForFrameToContain("Avg Cost");
|
|
1565
|
-
expect(frame).toContain("Shares");
|
|
1566
|
-
expect(frame).toContain("Avg Cost");
|
|
1567
|
-
expect(frame).toContain("205.5");
|
|
1568
|
-
expect(frame).not.toContain("Only Manual Portfolio");
|
|
1569
|
-
expectSingleBackControl(frame);
|
|
1570
|
-
});
|
|
1571
|
-
|
|
1572
|
-
test("add-to-portfolio can still add membership without entering a position", async () => {
|
|
1573
|
-
const saved: TickerRecord[] = [];
|
|
1574
|
-
|
|
1575
|
-
testSetup = await testRender(
|
|
1576
|
-
<CommandBarHarness
|
|
1577
|
-
query="AP AAPL"
|
|
1578
|
-
selectedTicker="AAPL"
|
|
1579
|
-
onSaveTicker={(ticker) => {
|
|
1580
|
-
saved.push(ticker);
|
|
1581
|
-
}}
|
|
1582
|
-
configureConfig={(config) => ({
|
|
1583
|
-
...config,
|
|
1584
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1585
|
-
})}
|
|
1586
|
-
configureState={(state) => ({
|
|
1587
|
-
...state,
|
|
1588
|
-
paneState: {
|
|
1589
|
-
...state.paneState,
|
|
1590
|
-
"portfolio-list:main": {
|
|
1591
|
-
collectionId: "research",
|
|
1592
|
-
cursorSymbol: "AAPL",
|
|
1593
|
-
},
|
|
1594
|
-
},
|
|
1595
|
-
})}
|
|
1596
|
-
/>,
|
|
1597
|
-
{ width: 100, height: 30 },
|
|
1598
|
-
);
|
|
1599
|
-
|
|
1600
|
-
await testSetup.renderOnce();
|
|
1601
|
-
await act(async () => {
|
|
1602
|
-
testSetup!.mockInput.pressEnter();
|
|
1603
|
-
await testSetup!.renderOnce();
|
|
1604
|
-
});
|
|
1605
|
-
|
|
1606
|
-
const frame = await waitForFrameToContain("Avg Cost");
|
|
1607
|
-
expect(frame).toContain("Shares");
|
|
1608
|
-
expect(frame).toContain("Avg Cost");
|
|
1609
|
-
|
|
1610
|
-
await clickFrameText("Add to Portfolio");
|
|
1611
|
-
await act(async () => {
|
|
1612
|
-
await Bun.sleep(0);
|
|
1613
|
-
await testSetup!.renderOnce();
|
|
1614
|
-
});
|
|
1615
|
-
|
|
1616
|
-
expect(saved.at(-1)?.metadata.portfolios).toEqual(["research"]);
|
|
1617
|
-
expect(saved.at(-1)?.metadata.positions).toEqual([]);
|
|
1618
|
-
});
|
|
1619
|
-
|
|
1620
|
-
test("only surfaces Set Portfolio Position when a manual portfolio exists", async () => {
|
|
1621
|
-
testSetup = await testRender(
|
|
1622
|
-
<CommandBarHarness query="Set Portfolio Position" />,
|
|
1623
|
-
{ width: 100, height: 20 },
|
|
1624
|
-
);
|
|
1625
|
-
|
|
1626
|
-
await testSetup.renderOnce();
|
|
1627
|
-
expect(testSetup.captureCharFrame()).toContain("Set Portfolio Position");
|
|
1628
|
-
|
|
1629
|
-
testSetup.renderer.destroy();
|
|
1630
|
-
testSetup = await testRender(
|
|
1631
|
-
<CommandBarHarness
|
|
1632
|
-
query="Set Portfolio Position"
|
|
1633
|
-
configureConfig={(config) => ({
|
|
1634
|
-
...config,
|
|
1635
|
-
portfolios: [{
|
|
1636
|
-
id: "broker:ibkr",
|
|
1637
|
-
name: "IBKR Account",
|
|
1638
|
-
currency: "USD",
|
|
1639
|
-
brokerId: "ibkr",
|
|
1640
|
-
brokerInstanceId: "ibkr-live",
|
|
1641
|
-
}],
|
|
1642
|
-
})}
|
|
1643
|
-
/>,
|
|
1644
|
-
{ width: 100, height: 20 },
|
|
1645
|
-
);
|
|
1646
|
-
|
|
1647
|
-
await testSetup.renderOnce();
|
|
1648
|
-
const frame = testSetup.captureCharFrame();
|
|
1649
|
-
expect(frame).toContain('No matches for "Set Portfolio Position"');
|
|
1650
|
-
expect(frame).not.toContain("Create or update a manual position in a portfolio");
|
|
1651
|
-
});
|
|
1652
|
-
|
|
1653
|
-
test("matches set portfolio position when searching edit position", async () => {
|
|
1654
|
-
testSetup = await testRender(
|
|
1655
|
-
<CommandBarHarness
|
|
1656
|
-
query="edit position"
|
|
1657
|
-
selectedTicker="AAPL"
|
|
1658
|
-
configureConfig={(config) => ({
|
|
1659
|
-
...config,
|
|
1660
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1661
|
-
})}
|
|
1662
|
-
configureState={(state) => ({
|
|
1663
|
-
...state,
|
|
1664
|
-
paneState: {
|
|
1665
|
-
...state.paneState,
|
|
1666
|
-
"portfolio-list:main": {
|
|
1667
|
-
collectionId: "research",
|
|
1668
|
-
cursorSymbol: "AAPL",
|
|
1669
|
-
},
|
|
1670
|
-
},
|
|
1671
|
-
})}
|
|
1672
|
-
/>,
|
|
1673
|
-
{ width: 100, height: 20 },
|
|
1674
|
-
);
|
|
1675
|
-
|
|
1676
|
-
await testSetup.renderOnce();
|
|
1677
|
-
|
|
1678
|
-
const frame = testSetup.captureCharFrame();
|
|
1679
|
-
expect(frame).toContain("Set Position for AAPL");
|
|
1680
|
-
});
|
|
1681
|
-
|
|
1682
|
-
test("prefills the portfolio position workflow from the active manual portfolio and ticker", async () => {
|
|
1683
|
-
testSetup = await testRender(
|
|
1684
|
-
<CommandBarHarness
|
|
1685
|
-
query="Set Position for AAPL"
|
|
1686
|
-
selectedTicker="AAPL"
|
|
1687
|
-
configureConfig={(config) => ({
|
|
1688
|
-
...config,
|
|
1689
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1690
|
-
})}
|
|
1691
|
-
configureState={(state) => ({
|
|
1692
|
-
...state,
|
|
1693
|
-
paneState: {
|
|
1694
|
-
...state.paneState,
|
|
1695
|
-
"portfolio-list:main": {
|
|
1696
|
-
collectionId: "research",
|
|
1697
|
-
cursorSymbol: "AAPL",
|
|
1698
|
-
},
|
|
1699
|
-
},
|
|
1700
|
-
})}
|
|
1701
|
-
extraTickers={[makeTicker("AAPL", "Apple Inc.", {
|
|
1702
|
-
portfolios: ["research"],
|
|
1703
|
-
positions: [{
|
|
1704
|
-
portfolio: "research",
|
|
1705
|
-
shares: 10,
|
|
1706
|
-
avgCost: 180,
|
|
1707
|
-
currency: "USD",
|
|
1708
|
-
broker: "manual",
|
|
1709
|
-
}],
|
|
1710
|
-
})]}
|
|
1711
|
-
/>,
|
|
1712
|
-
{ width: 100, height: 30 },
|
|
1713
|
-
);
|
|
1714
|
-
|
|
1715
|
-
await testSetup.renderOnce();
|
|
1716
|
-
await act(async () => {
|
|
1717
|
-
testSetup!.mockInput.pressEnter();
|
|
1718
|
-
await testSetup!.renderOnce();
|
|
1719
|
-
});
|
|
1720
|
-
|
|
1721
|
-
const frame = await waitForFrameToContain("Avg Cost");
|
|
1722
|
-
expect(frame).toContain("Research");
|
|
1723
|
-
expect(frame).toContain("AAPL");
|
|
1724
|
-
expect(frame).toContain("10");
|
|
1725
|
-
expect(frame).toContain("180");
|
|
1726
|
-
expectSingleBackControl(frame);
|
|
1727
|
-
});
|
|
1728
|
-
|
|
1729
|
-
test("submits the portfolio position workflow and persists a manual position", async () => {
|
|
1730
|
-
const saved: TickerRecord[] = [];
|
|
1731
|
-
|
|
1732
|
-
testSetup = await testRender(
|
|
1733
|
-
<CommandBarHarness
|
|
1734
|
-
query="Set Position for AAPL"
|
|
1735
|
-
selectedTicker="AAPL"
|
|
1736
|
-
onSaveTicker={(ticker) => {
|
|
1737
|
-
saved.push(ticker);
|
|
1738
|
-
}}
|
|
1739
|
-
configureConfig={(config) => ({
|
|
1740
|
-
...config,
|
|
1741
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1742
|
-
})}
|
|
1743
|
-
configureState={(state) => ({
|
|
1744
|
-
...state,
|
|
1745
|
-
paneState: {
|
|
1746
|
-
...state.paneState,
|
|
1747
|
-
"portfolio-list:main": {
|
|
1748
|
-
collectionId: "research",
|
|
1749
|
-
cursorSymbol: "AAPL",
|
|
1750
|
-
},
|
|
1751
|
-
},
|
|
1752
|
-
})}
|
|
1753
|
-
/>,
|
|
1754
|
-
{ width: 100, height: 30 },
|
|
1755
|
-
);
|
|
1756
|
-
|
|
1757
|
-
await testSetup.renderOnce();
|
|
1758
|
-
await act(async () => {
|
|
1759
|
-
testSetup!.mockInput.pressEnter();
|
|
1760
|
-
await testSetup!.renderOnce();
|
|
1761
|
-
});
|
|
1762
|
-
|
|
1763
|
-
await act(async () => {
|
|
1764
|
-
testSetup!.mockInput.pressTab();
|
|
1765
|
-
await testSetup!.renderOnce();
|
|
1766
|
-
});
|
|
1767
|
-
await act(async () => {
|
|
1768
|
-
testSetup!.mockInput.pressTab();
|
|
1769
|
-
await testSetup!.renderOnce();
|
|
1770
|
-
});
|
|
1771
|
-
await act(async () => {
|
|
1772
|
-
await testSetup!.mockInput.typeText("10");
|
|
1773
|
-
await testSetup!.renderOnce();
|
|
1774
|
-
});
|
|
1775
|
-
await act(async () => {
|
|
1776
|
-
testSetup!.mockInput.pressTab();
|
|
1777
|
-
await testSetup!.renderOnce();
|
|
1778
|
-
});
|
|
1779
|
-
await act(async () => {
|
|
1780
|
-
await testSetup!.mockInput.typeText("180");
|
|
1781
|
-
await testSetup!.renderOnce();
|
|
1782
|
-
});
|
|
1783
|
-
await act(async () => {
|
|
1784
|
-
testSetup!.mockInput.pressTab();
|
|
1785
|
-
await testSetup!.renderOnce();
|
|
1786
|
-
});
|
|
1787
|
-
await act(async () => {
|
|
1788
|
-
await testSetup!.mockInput.typeText("EUR");
|
|
1789
|
-
await testSetup!.renderOnce();
|
|
1790
|
-
});
|
|
1791
|
-
await act(async () => {
|
|
1792
|
-
testSetup!.mockInput.pressEnter();
|
|
1793
|
-
await Bun.sleep(0);
|
|
1794
|
-
await testSetup!.renderOnce();
|
|
1795
|
-
});
|
|
1796
|
-
|
|
1797
|
-
expect(saved.at(-1)?.metadata.portfolios).toEqual(["research"]);
|
|
1798
|
-
expect(saved.at(-1)?.metadata.positions).toEqual([{
|
|
1799
|
-
portfolio: "research",
|
|
1800
|
-
shares: 10,
|
|
1801
|
-
avgCost: 180,
|
|
1802
|
-
currency: "EUR",
|
|
1803
|
-
broker: "manual",
|
|
1804
|
-
}]);
|
|
1805
|
-
});
|
|
1806
|
-
|
|
1807
|
-
test("removing a ticker from a manual portfolio also removes its position", async () => {
|
|
1808
|
-
const saved: TickerRecord[] = [];
|
|
1809
|
-
|
|
1810
|
-
testSetup = await testRender(
|
|
1811
|
-
<CommandBarHarness
|
|
1812
|
-
query="RP AAPL"
|
|
1813
|
-
selectedTicker="AAPL"
|
|
1814
|
-
onSaveTicker={(ticker) => {
|
|
1815
|
-
saved.push(ticker);
|
|
1816
|
-
}}
|
|
1817
|
-
configureConfig={(config) => ({
|
|
1818
|
-
...config,
|
|
1819
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1820
|
-
})}
|
|
1821
|
-
configureState={(state) => ({
|
|
1822
|
-
...state,
|
|
1823
|
-
paneState: {
|
|
1824
|
-
...state.paneState,
|
|
1825
|
-
"portfolio-list:main": {
|
|
1826
|
-
collectionId: "research",
|
|
1827
|
-
cursorSymbol: "AAPL",
|
|
1828
|
-
},
|
|
1829
|
-
},
|
|
1830
|
-
})}
|
|
1831
|
-
extraTickers={[makeTicker("AAPL", "Apple Inc.", {
|
|
1832
|
-
portfolios: ["research"],
|
|
1833
|
-
positions: [{
|
|
1834
|
-
portfolio: "research",
|
|
1835
|
-
shares: 4,
|
|
1836
|
-
avgCost: 175,
|
|
1837
|
-
currency: "USD",
|
|
1838
|
-
broker: "manual",
|
|
1839
|
-
}],
|
|
1840
|
-
})]}
|
|
1841
|
-
/>,
|
|
1842
|
-
{ width: 100, height: 20 },
|
|
1843
|
-
);
|
|
1844
|
-
|
|
1845
|
-
await testSetup.renderOnce();
|
|
1846
|
-
await act(async () => {
|
|
1847
|
-
testSetup!.mockInput.pressEnter();
|
|
1848
|
-
await Bun.sleep(0);
|
|
1849
|
-
await testSetup!.renderOnce();
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
expect(saved.at(-1)?.metadata.portfolios).toEqual([]);
|
|
1853
|
-
expect(saved.at(-1)?.metadata.positions).toEqual([]);
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
|
-
test("deleting a manual portfolio also cleans saved ticker positions", async () => {
|
|
1857
|
-
const saved: TickerRecord[] = [];
|
|
1858
|
-
|
|
1859
|
-
testSetup = await testRender(
|
|
1860
|
-
<CommandBarHarness
|
|
1861
|
-
query="Delete Portfolio"
|
|
1862
|
-
onSaveTicker={(ticker) => {
|
|
1863
|
-
saved.push(ticker);
|
|
1864
|
-
}}
|
|
1865
|
-
configureConfig={(config) => ({
|
|
1866
|
-
...config,
|
|
1867
|
-
portfolios: [{ id: "research", name: "Research", currency: "USD" }],
|
|
1868
|
-
})}
|
|
1869
|
-
configureState={(state) => ({
|
|
1870
|
-
...state,
|
|
1871
|
-
paneState: {
|
|
1872
|
-
...state.paneState,
|
|
1873
|
-
"portfolio-list:main": {
|
|
1874
|
-
collectionId: "research",
|
|
1875
|
-
cursorSymbol: "AAPL",
|
|
1876
|
-
},
|
|
1877
|
-
},
|
|
1878
|
-
})}
|
|
1879
|
-
extraTickers={[makeTicker("AAPL", "Apple Inc.", {
|
|
1880
|
-
portfolios: ["research"],
|
|
1881
|
-
positions: [{
|
|
1882
|
-
portfolio: "research",
|
|
1883
|
-
shares: 2,
|
|
1884
|
-
avgCost: 160,
|
|
1885
|
-
currency: "USD",
|
|
1886
|
-
broker: "manual",
|
|
1887
|
-
}],
|
|
1888
|
-
})]}
|
|
1889
|
-
/>,
|
|
1890
|
-
{ width: 100, height: 20 },
|
|
1891
|
-
);
|
|
1892
|
-
|
|
1893
|
-
await testSetup.renderOnce();
|
|
1894
|
-
await act(async () => {
|
|
1895
|
-
testSetup!.mockInput.pressEnter();
|
|
1896
|
-
await testSetup!.renderOnce();
|
|
1897
|
-
});
|
|
1898
|
-
await act(async () => {
|
|
1899
|
-
testSetup!.mockInput.pressEnter();
|
|
1900
|
-
await testSetup!.renderOnce();
|
|
1901
|
-
});
|
|
1902
|
-
await act(async () => {
|
|
1903
|
-
testSetup!.mockInput.pressEnter();
|
|
1904
|
-
await Bun.sleep(0);
|
|
1905
|
-
await testSetup!.renderOnce();
|
|
1906
|
-
});
|
|
1907
|
-
|
|
1908
|
-
expect(saved.at(-1)?.metadata.portfolios).toEqual([]);
|
|
1909
|
-
expect(saved.at(-1)?.metadata.positions).toEqual([]);
|
|
1910
|
-
});
|
|
1911
|
-
|
|
1912
|
-
test("pressing enter after arrow navigation ignores stale mouse hover", async () => {
|
|
1913
|
-
const pinned: string[] = [];
|
|
1914
|
-
|
|
1915
|
-
testSetup = await testRender(
|
|
1916
|
-
<CommandBarHarness
|
|
1917
|
-
query=""
|
|
1918
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1919
|
-
pluginRegistry.pinTicker = (symbol) => {
|
|
1920
|
-
pinned.push(symbol);
|
|
1921
|
-
};
|
|
1922
|
-
}}
|
|
1923
|
-
/>,
|
|
1924
|
-
{ width: 80, height: 24 },
|
|
1925
|
-
);
|
|
1926
|
-
|
|
1927
|
-
await testSetup.renderOnce();
|
|
1928
|
-
|
|
1929
|
-
const frame = testSetup.captureCharFrame();
|
|
1930
|
-
const rows = frame.split("\n");
|
|
1931
|
-
const hoveredRow = rows.findIndex((line) => line.includes("AAPL"));
|
|
1932
|
-
const hoveredCol = rows[hoveredRow]?.indexOf("AAPL") ?? -1;
|
|
1933
|
-
|
|
1934
|
-
expect(hoveredRow).toBeGreaterThanOrEqual(0);
|
|
1935
|
-
expect(hoveredCol).toBeGreaterThanOrEqual(0);
|
|
1936
|
-
|
|
1937
|
-
await act(async () => {
|
|
1938
|
-
await testSetup!.mockMouse.moveTo(hoveredCol + 1, hoveredRow);
|
|
1939
|
-
await testSetup!.renderOnce();
|
|
1940
|
-
});
|
|
1941
|
-
|
|
1942
|
-
await act(async () => {
|
|
1943
|
-
testSetup!.mockInput.pressArrow("down");
|
|
1944
|
-
await testSetup!.renderOnce();
|
|
1945
|
-
});
|
|
1946
|
-
|
|
1947
|
-
await act(async () => {
|
|
1948
|
-
testSetup!.mockInput.pressEnter();
|
|
1949
|
-
await Bun.sleep(0);
|
|
1950
|
-
await testSetup!.renderOnce();
|
|
1951
|
-
});
|
|
1952
|
-
|
|
1953
|
-
expect(pinned).toEqual(["MSFT"]);
|
|
1954
|
-
});
|
|
1955
|
-
|
|
1956
|
-
test("pressing enter follows the displayed grouped order and opens confirm for the highlighted destructive item", async () => {
|
|
1957
|
-
const executed: string[] = [];
|
|
1958
|
-
|
|
1959
|
-
testSetup = await testRender(
|
|
1960
|
-
<CommandBarHarness
|
|
1961
|
-
query="delete"
|
|
1962
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
1963
|
-
(pluginRegistry.commands as Map<string, any>).set("delete-layout", {
|
|
1964
|
-
id: "delete-layout",
|
|
1965
|
-
label: "Delete Layout",
|
|
1966
|
-
description: "Delete the current layout preset",
|
|
1967
|
-
category: "Layout Manager",
|
|
1968
|
-
execute: async () => {
|
|
1969
|
-
executed.push("delete-layout");
|
|
1970
|
-
},
|
|
1971
|
-
});
|
|
1972
|
-
}}
|
|
1973
|
-
/>,
|
|
1974
|
-
{ width: 100, height: 20 },
|
|
1975
|
-
);
|
|
1976
|
-
|
|
1977
|
-
await testSetup.renderOnce();
|
|
1978
|
-
const initialFrame = testSetup.captureCharFrame();
|
|
1979
|
-
expect(initialFrame).toContain("Delete Layout");
|
|
1980
|
-
|
|
1981
|
-
await act(async () => {
|
|
1982
|
-
testSetup!.mockInput.pressEnter();
|
|
1983
|
-
await testSetup!.renderOnce();
|
|
1984
|
-
});
|
|
1985
|
-
|
|
1986
|
-
let frame = testSetup.captureCharFrame();
|
|
1987
|
-
expect(frame).toContain("Delete Layout");
|
|
1988
|
-
expect(frame).toContain("Back");
|
|
1989
|
-
expectSingleBackControl(frame);
|
|
1990
|
-
expect(executed).toEqual([]);
|
|
1991
|
-
|
|
1992
|
-
await act(async () => {
|
|
1993
|
-
testSetup!.mockInput.pressEnter();
|
|
1994
|
-
await Bun.sleep(0);
|
|
1995
|
-
await testSetup!.renderOnce();
|
|
1996
|
-
});
|
|
1997
|
-
|
|
1998
|
-
expect(executed).toEqual(["delete-layout"]);
|
|
1999
|
-
});
|
|
2000
|
-
|
|
2001
|
-
test("uses backspace to leave confirm routes", async () => {
|
|
2002
|
-
testSetup = await testRender(
|
|
2003
|
-
<CommandBarHarness
|
|
2004
|
-
query="delete"
|
|
2005
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2006
|
-
(pluginRegistry.commands as Map<string, any>).set("delete-layout", {
|
|
2007
|
-
id: "delete-layout",
|
|
2008
|
-
label: "Delete Layout",
|
|
2009
|
-
description: "Delete the current layout preset",
|
|
2010
|
-
category: "Layout Manager",
|
|
2011
|
-
execute: async () => {},
|
|
2012
|
-
});
|
|
2013
|
-
}}
|
|
2014
|
-
/>,
|
|
2015
|
-
{ width: 100, height: 20 },
|
|
2016
|
-
);
|
|
2017
|
-
|
|
2018
|
-
await testSetup.renderOnce();
|
|
2019
|
-
await act(async () => {
|
|
2020
|
-
testSetup!.mockInput.pressEnter();
|
|
2021
|
-
await testSetup!.renderOnce();
|
|
2022
|
-
});
|
|
2023
|
-
await renderFrames();
|
|
2024
|
-
|
|
2025
|
-
await act(async () => {
|
|
2026
|
-
testSetup!.mockInput.pressBackspace();
|
|
2027
|
-
await testSetup!.renderOnce();
|
|
2028
|
-
});
|
|
2029
|
-
await renderFrames();
|
|
2030
|
-
|
|
2031
|
-
const frame = testSetup.captureCharFrame();
|
|
2032
|
-
expect(frame).toContain("Delete Layout");
|
|
2033
|
-
expect(frame).not.toContain("Back Delete Layout");
|
|
2034
|
-
});
|
|
2035
|
-
|
|
2036
|
-
test("moves through long result lists with the mouse wheel", async () => {
|
|
2037
|
-
testSetup = await testRender(
|
|
2038
|
-
<CommandBarHarness
|
|
2039
|
-
query="scratch"
|
|
2040
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2041
|
-
const paneTemplates = pluginRegistry.paneTemplates as Map<string, any>;
|
|
2042
|
-
for (let index = 0; index < 20; index++) {
|
|
2043
|
-
const suffix = String(index).padStart(2, "0");
|
|
2044
|
-
paneTemplates.set(`scratch-${suffix}`, {
|
|
2045
|
-
id: `scratch-${suffix}`,
|
|
2046
|
-
paneId: "chat",
|
|
2047
|
-
label: `Scratch Pane ${suffix}`,
|
|
2048
|
-
description: `Open scratch pane ${suffix}`,
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
}}
|
|
2052
|
-
/>,
|
|
2053
|
-
{ width: 100, height: 18 },
|
|
2054
|
-
);
|
|
2055
|
-
|
|
2056
|
-
await testSetup.renderOnce();
|
|
2057
|
-
|
|
2058
|
-
const initialFrame = testSetup.captureCharFrame();
|
|
2059
|
-
expect(initialFrame).toContain("Scratch Pane 00");
|
|
2060
|
-
expect(initialFrame).not.toContain("Scratch Pane 12");
|
|
2061
|
-
|
|
2062
|
-
const rows = initialFrame.split("\n");
|
|
2063
|
-
const scrollRow = rows.findIndex((line) => line.includes("Scratch Pane 00"));
|
|
2064
|
-
const scrollCol = rows[scrollRow]?.indexOf("Scratch Pane 00") ?? -1;
|
|
2065
|
-
|
|
2066
|
-
expect(scrollRow).toBeGreaterThanOrEqual(0);
|
|
2067
|
-
expect(scrollCol).toBeGreaterThanOrEqual(0);
|
|
2068
|
-
|
|
2069
|
-
await act(async () => {
|
|
2070
|
-
for (let index = 0; index < 12; index++) {
|
|
2071
|
-
await testSetup!.mockMouse.scroll(scrollCol + 1, scrollRow, "down");
|
|
2072
|
-
await testSetup!.renderOnce();
|
|
2073
|
-
}
|
|
2074
|
-
});
|
|
2075
|
-
|
|
2076
|
-
const scrolledFrame = testSetup.captureCharFrame();
|
|
2077
|
-
expect(scrolledFrame).not.toContain("Scratch Pane 00");
|
|
2078
|
-
expect(scrolledFrame).toContain("Scratch Pane 12");
|
|
2079
|
-
});
|
|
2080
|
-
|
|
2081
|
-
test("closes when clicking outside the command bar", async () => {
|
|
2082
|
-
testSetup = await testRender(<CommandBarHarness query="" live />, {
|
|
2083
|
-
width: 80,
|
|
2084
|
-
height: 24,
|
|
2085
|
-
});
|
|
2086
|
-
|
|
2087
|
-
await testSetup.renderOnce();
|
|
2088
|
-
|
|
2089
|
-
await act(async () => {
|
|
2090
|
-
await testSetup!.mockMouse.click(0, 0);
|
|
2091
|
-
await testSetup!.renderOnce();
|
|
2092
|
-
});
|
|
2093
|
-
await testSetup.renderOnce();
|
|
2094
|
-
|
|
2095
|
-
expect(testSetup.captureCharFrame()).not.toContain("Commands");
|
|
2096
|
-
});
|
|
2097
|
-
|
|
2098
|
-
const layoutModeConfig = (config: AppConfig): AppConfig => {
|
|
2099
|
-
const research = cloneLayout(config.layout);
|
|
2100
|
-
research.dockRoot = { kind: "pane", instanceId: "portfolio-list:main" };
|
|
2101
|
-
research.floating = [{ instanceId: "ticker-detail:main", x: 8, y: 2, width: 36, height: 12 }];
|
|
2102
|
-
return {
|
|
2103
|
-
...config,
|
|
2104
|
-
layouts: [
|
|
2105
|
-
{ name: "Default", layout: cloneLayout(config.layout) },
|
|
2106
|
-
{ name: "Research", layout: research },
|
|
2107
|
-
],
|
|
2108
|
-
};
|
|
2109
|
-
};
|
|
2110
|
-
|
|
2111
|
-
const layoutModeState = (state: AppState): AppState => ({
|
|
2112
|
-
...state,
|
|
2113
|
-
layoutHistory: {
|
|
2114
|
-
0: {
|
|
2115
|
-
past: [cloneLayout(state.config.layout)],
|
|
2116
|
-
future: [],
|
|
2117
|
-
},
|
|
2118
|
-
},
|
|
2119
|
-
});
|
|
2120
|
-
|
|
2121
|
-
test("renders layout mode with focused pane actions", async () => {
|
|
2122
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2123
|
-
query="LAY "
|
|
2124
|
-
configureConfig={layoutModeConfig}
|
|
2125
|
-
configureState={layoutModeState}
|
|
2126
|
-
/>, {
|
|
2127
|
-
width: 90,
|
|
2128
|
-
height: 28,
|
|
2129
|
-
});
|
|
2130
|
-
|
|
2131
|
-
await testSetup.renderOnce();
|
|
2132
|
-
|
|
2133
|
-
const frame = testSetup.captureCharFrame();
|
|
2134
|
-
expect(frame).toContain("Focused Pane");
|
|
2135
|
-
expect(frame).toContain("Float Pane");
|
|
2136
|
-
expect(frame).toContain("Undo Layout Change");
|
|
2137
|
-
expect(frame).toContain("Current Layout");
|
|
2138
|
-
});
|
|
2139
|
-
|
|
2140
|
-
test("runs layout actions directly from root search", async () => {
|
|
2141
|
-
const actions: AppAction[] = [];
|
|
2142
|
-
|
|
2143
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2144
|
-
query="undo layout change"
|
|
2145
|
-
live
|
|
2146
|
-
configureConfig={layoutModeConfig}
|
|
2147
|
-
configureState={layoutModeState}
|
|
2148
|
-
onAction={(action) => actions.push(action)}
|
|
2149
|
-
/>, {
|
|
2150
|
-
width: 90,
|
|
2151
|
-
height: 24,
|
|
2152
|
-
});
|
|
2153
|
-
|
|
2154
|
-
await testSetup.renderOnce();
|
|
2155
|
-
|
|
2156
|
-
expect(testSetup.captureCharFrame()).toContain("Undo Layout Change");
|
|
2157
|
-
|
|
2158
|
-
await act(async () => {
|
|
2159
|
-
testSetup!.mockInput.pressEnter();
|
|
2160
|
-
await testSetup!.renderOnce();
|
|
2161
|
-
});
|
|
2162
|
-
|
|
2163
|
-
expect(actions.some((action) => action.type === "UNDO_LAYOUT")).toBe(true);
|
|
2164
|
-
});
|
|
2165
|
-
|
|
2166
|
-
test("renders filtered saved layouts with textual previews", async () => {
|
|
2167
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2168
|
-
query="LAY Research"
|
|
2169
|
-
configureConfig={layoutModeConfig}
|
|
2170
|
-
configureState={layoutModeState}
|
|
2171
|
-
/>, {
|
|
2172
|
-
width: 120,
|
|
2173
|
-
height: 18,
|
|
2174
|
-
});
|
|
2175
|
-
|
|
2176
|
-
await testSetup.renderOnce();
|
|
2177
|
-
|
|
2178
|
-
const frame = testSetup.captureCharFrame();
|
|
2179
|
-
expect(frame).toContain("Research");
|
|
2180
|
-
expect(frame).toContain("1c / 1d");
|
|
2181
|
-
});
|
|
2182
|
-
|
|
2183
|
-
test("filters plugin-defined pane templates directly from the root query", async () => {
|
|
2184
|
-
testSetup = await testRender(<CommandBarHarness query="chat" />, {
|
|
2185
|
-
width: 100,
|
|
2186
|
-
height: 18,
|
|
2187
|
-
});
|
|
2188
|
-
|
|
2189
|
-
await testSetup.renderOnce();
|
|
2190
|
-
|
|
2191
|
-
const frame = testSetup.captureCharFrame();
|
|
2192
|
-
expect(frame).toContain("Chat");
|
|
2193
|
-
expect(frame).not.toContain("float");
|
|
2194
|
-
});
|
|
2195
|
-
|
|
2196
|
-
test("shows pane shortcuts in the default browse results", async () => {
|
|
2197
|
-
testSetup = await testRender(<CommandBarHarness query="" selectedTicker="AAPL" />, {
|
|
2198
|
-
width: 100,
|
|
2199
|
-
height: 24,
|
|
2200
|
-
});
|
|
2201
|
-
|
|
2202
|
-
await testSetup.renderOnce();
|
|
2203
|
-
|
|
2204
|
-
const frame = testSetup.captureCharFrame();
|
|
2205
|
-
expect(frame).toContain("Panes");
|
|
2206
|
-
expect(frame).toContain("Quote Monitor");
|
|
2207
|
-
expect(frame).toContain("QQ");
|
|
2208
|
-
});
|
|
2209
|
-
|
|
2210
|
-
test("matches direct pane shortcut queries", async () => {
|
|
2211
|
-
testSetup = await testRender(<CommandBarHarness query="QQ MSFT" selectedTicker="AAPL" />, {
|
|
2212
|
-
width: 100,
|
|
2213
|
-
height: 18,
|
|
2214
|
-
});
|
|
2215
|
-
|
|
2216
|
-
await testSetup.renderOnce();
|
|
2217
|
-
|
|
2218
|
-
const frame = testSetup.captureCharFrame();
|
|
2219
|
-
expect(frame).toContain("Quote Monitor");
|
|
2220
|
-
expect(frame).toContain("QQ");
|
|
2221
|
-
});
|
|
2222
|
-
|
|
2223
|
-
test("shows pane templates that share the same shortcut", async () => {
|
|
2224
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2225
|
-
query="DUP"
|
|
2226
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2227
|
-
const paneTemplates = pluginRegistry.paneTemplates as Map<string, any>;
|
|
2228
|
-
paneTemplates.set("first-duplicate-pane", {
|
|
2229
|
-
id: "first-duplicate-pane",
|
|
2230
|
-
paneId: "first-duplicate",
|
|
2231
|
-
label: "First Duplicate",
|
|
2232
|
-
description: "First pane using a shared shortcut",
|
|
2233
|
-
shortcut: { prefix: "DUP" },
|
|
2234
|
-
});
|
|
2235
|
-
paneTemplates.set("second-duplicate-pane", {
|
|
2236
|
-
id: "second-duplicate-pane",
|
|
2237
|
-
paneId: "second-duplicate",
|
|
2238
|
-
label: "Second Duplicate",
|
|
2239
|
-
description: "Second pane using a shared shortcut",
|
|
2240
|
-
shortcut: { prefix: "DUP" },
|
|
2241
|
-
});
|
|
2242
|
-
}}
|
|
2243
|
-
/>, {
|
|
2244
|
-
width: 100,
|
|
2245
|
-
height: 18,
|
|
2246
|
-
});
|
|
2247
|
-
|
|
2248
|
-
await testSetup.renderOnce();
|
|
2249
|
-
|
|
2250
|
-
const frame = testSetup.captureCharFrame();
|
|
2251
|
-
expect(frame).toContain("First Duplicate");
|
|
2252
|
-
expect(frame).toContain("Second Duplicate");
|
|
2253
|
-
});
|
|
2254
|
-
|
|
2255
|
-
test("does not treat no-argument shortcut prefixes as typed pane names", async () => {
|
|
2256
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2257
|
-
|
|
2258
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2259
|
-
query="Top News"
|
|
2260
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2261
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2262
|
-
created.push({ templateId, options });
|
|
2263
|
-
};
|
|
2264
|
-
const paneTemplates = pluginRegistry.paneTemplates as Map<string, any>;
|
|
2265
|
-
paneTemplates.set("market-movers-pane", {
|
|
2266
|
-
id: "market-movers-pane",
|
|
2267
|
-
paneId: "market-movers",
|
|
2268
|
-
label: "Market Movers",
|
|
2269
|
-
description: "Track market gainers and losers",
|
|
2270
|
-
shortcut: { prefix: "MOST" },
|
|
2271
|
-
});
|
|
2272
|
-
paneTemplates.set("news-top-pane", {
|
|
2273
|
-
id: "news-top-pane",
|
|
2274
|
-
paneId: "news-top",
|
|
2275
|
-
label: "Top News",
|
|
2276
|
-
description: "Curated top market stories ranked by importance",
|
|
2277
|
-
shortcut: { prefix: "TOP" },
|
|
2278
|
-
});
|
|
2279
|
-
}}
|
|
2280
|
-
/>, {
|
|
2281
|
-
width: 100,
|
|
2282
|
-
height: 18,
|
|
2283
|
-
});
|
|
2284
|
-
|
|
2285
|
-
await testSetup.renderOnce();
|
|
2286
|
-
expect(testSetup.captureCharFrame()).toContain("Top News");
|
|
2287
|
-
|
|
2288
|
-
await act(async () => {
|
|
2289
|
-
testSetup!.mockInput.pressEnter();
|
|
2290
|
-
await Bun.sleep(0);
|
|
2291
|
-
await testSetup!.renderOnce();
|
|
2292
|
-
});
|
|
2293
|
-
|
|
2294
|
-
expect(created).toEqual([{ templateId: "news-top-pane", options: undefined }]);
|
|
2295
|
-
});
|
|
2296
|
-
|
|
2297
|
-
test("surfaces ticker-required pane templates by name without an active ticker", async () => {
|
|
2298
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2299
|
-
query="Options"
|
|
2300
|
-
extraTickers={[makeTicker("^RUT", "Chicago Board Options Exchange Russell 2000")]}
|
|
2301
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2302
|
-
const paneTemplates = pluginRegistry.paneTemplates as Map<string, any>;
|
|
2303
|
-
paneTemplates.set("options-pane", {
|
|
2304
|
-
id: "options-pane",
|
|
2305
|
-
paneId: "options",
|
|
2306
|
-
label: "Options",
|
|
2307
|
-
description: "Options chain for the selected ticker",
|
|
2308
|
-
keywords: ["options", "chain", "calls", "puts"],
|
|
2309
|
-
shortcut: { prefix: "OMON", argPlaceholder: "ticker", argKind: "ticker" },
|
|
2310
|
-
canCreate: (context: any, options?: PaneTemplateCreateOptions) => (
|
|
2311
|
-
context.activeTicker !== null || !!options?.arg
|
|
2312
|
-
),
|
|
2313
|
-
});
|
|
2314
|
-
}}
|
|
2315
|
-
/>, {
|
|
2316
|
-
width: 100,
|
|
2317
|
-
height: 18,
|
|
2318
|
-
});
|
|
2319
|
-
|
|
2320
|
-
await testSetup.renderOnce();
|
|
2321
|
-
|
|
2322
|
-
let frame = testSetup.captureCharFrame();
|
|
2323
|
-
expect(frame).toContain("Options");
|
|
2324
|
-
expect(frame).toContain("OMON");
|
|
2325
|
-
|
|
2326
|
-
await act(async () => {
|
|
2327
|
-
testSetup!.mockInput.pressEnter();
|
|
2328
|
-
await testSetup!.renderOnce();
|
|
2329
|
-
});
|
|
2330
|
-
|
|
2331
|
-
frame = testSetup.captureCharFrame();
|
|
2332
|
-
expect(frame).toContain("Back");
|
|
2333
|
-
expect(frame).toContain("Ticker");
|
|
2334
|
-
});
|
|
2335
|
-
|
|
2336
|
-
test("ranks exact ticker symbols before longer ticker prefixes in root search", async () => {
|
|
2337
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2338
|
-
query="amd"
|
|
2339
|
-
extraTickers={[
|
|
2340
|
-
makeTicker("AMDL", "GraniteShares 2x Long AMD Daily ETF"),
|
|
2341
|
-
makeTicker("AMD", "Advanced Micro Devices"),
|
|
2342
|
-
]}
|
|
2343
|
-
/>, {
|
|
2344
|
-
width: 100,
|
|
2345
|
-
height: 18,
|
|
2346
|
-
});
|
|
2347
|
-
|
|
2348
|
-
await testSetup.renderOnce();
|
|
2349
|
-
|
|
2350
|
-
const tickerLines = testSetup.captureCharFrame()
|
|
2351
|
-
.split("\n")
|
|
2352
|
-
.filter((line) => /\bAMD\b|\bAMDL\b/.test(line));
|
|
2353
|
-
expect(tickerLines[0]).toMatch(/\bAMD\b/);
|
|
2354
|
-
expect(tickerLines[0]).not.toMatch(/\bAMDL\b/);
|
|
2355
|
-
});
|
|
2356
|
-
|
|
2357
|
-
test("opens plugin command wizards inline inside the command bar", async () => {
|
|
2358
|
-
let submittedValues: Record<string, string> | undefined;
|
|
2359
|
-
|
|
2360
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2361
|
-
query="Plugin Login"
|
|
2362
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2363
|
-
(pluginRegistry.commands as Map<string, any>).set("plugin:login", {
|
|
2364
|
-
id: "plugin:login",
|
|
2365
|
-
label: "Plugin Login",
|
|
2366
|
-
description: "Authenticate without leaving the command bar",
|
|
2367
|
-
category: "config",
|
|
2368
|
-
wizard: [
|
|
2369
|
-
{ key: "username", label: "Username", type: "text", placeholder: "vince" },
|
|
2370
|
-
{ key: "password", label: "Password", type: "password", placeholder: "secret" },
|
|
2371
|
-
{ key: "_validate", label: "Validating", type: "info", body: ["Validating…", "Connected."] },
|
|
2372
|
-
],
|
|
2373
|
-
execute: async (values?: Record<string, string>) => {
|
|
2374
|
-
submittedValues = values;
|
|
2375
|
-
},
|
|
2376
|
-
} as any);
|
|
2377
|
-
pluginRegistry.getCommandPluginId = (commandId: string) => (
|
|
2378
|
-
commandId === "plugin:login" ? "notes" : "news"
|
|
2379
|
-
);
|
|
2380
|
-
}}
|
|
2381
|
-
/>, {
|
|
2382
|
-
width: 100,
|
|
2383
|
-
height: 24,
|
|
2384
|
-
});
|
|
2385
|
-
|
|
2386
|
-
await testSetup.renderOnce();
|
|
2387
|
-
await act(async () => {
|
|
2388
|
-
testSetup!.mockInput.pressEnter();
|
|
2389
|
-
await testSetup!.renderOnce();
|
|
2390
|
-
});
|
|
2391
|
-
|
|
2392
|
-
let frame = testSetup.captureCharFrame();
|
|
2393
|
-
expect(frame).toContain("Plugin Login");
|
|
2394
|
-
expect(frame).toContain("Username");
|
|
2395
|
-
expect(frame).toContain("Password");
|
|
2396
|
-
|
|
2397
|
-
await act(async () => {
|
|
2398
|
-
await testSetup!.mockInput.typeText("vince");
|
|
2399
|
-
await testSetup!.renderOnce();
|
|
2400
|
-
});
|
|
2401
|
-
await act(async () => {
|
|
2402
|
-
testSetup!.mockInput.pressTab();
|
|
2403
|
-
await testSetup!.renderOnce();
|
|
2404
|
-
});
|
|
2405
|
-
await act(async () => {
|
|
2406
|
-
await testSetup!.mockInput.typeText("secret");
|
|
2407
|
-
await testSetup!.renderOnce();
|
|
2408
|
-
});
|
|
2409
|
-
|
|
2410
|
-
frame = await waitForFrameToContain("******");
|
|
2411
|
-
expect(frame).toContain("******");
|
|
2412
|
-
expect(frame).not.toContain("secret");
|
|
2413
|
-
expectSingleBackControl(frame);
|
|
2414
|
-
|
|
2415
|
-
await act(async () => {
|
|
2416
|
-
testSetup!.mockInput.pressEnter();
|
|
2417
|
-
await Bun.sleep(0);
|
|
2418
|
-
await testSetup!.renderOnce();
|
|
2419
|
-
});
|
|
2420
|
-
|
|
2421
|
-
expect(submittedValues).toEqual({
|
|
2422
|
-
username: "vince",
|
|
2423
|
-
password: "secret",
|
|
2424
|
-
});
|
|
2425
|
-
});
|
|
2426
|
-
|
|
2427
|
-
test("does not duplicate placeholder text as workflow field help", async () => {
|
|
2428
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2429
|
-
query="Duplicate Form"
|
|
2430
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2431
|
-
(pluginRegistry.commands as Map<string, any>).set("plugin:duplicate-form", {
|
|
2432
|
-
id: "plugin:duplicate-form",
|
|
2433
|
-
label: "Duplicate Form",
|
|
2434
|
-
description: "Check workflow field copy",
|
|
2435
|
-
category: "config",
|
|
2436
|
-
wizard: [
|
|
2437
|
-
{ key: "name", label: "Name", type: "text", placeholder: "Profile name" },
|
|
2438
|
-
{ key: "token", label: "Token", type: "text", placeholder: "repeat marker", body: ["repeat marker"] },
|
|
2439
|
-
],
|
|
2440
|
-
execute: async () => {},
|
|
2441
|
-
} as any);
|
|
2442
|
-
pluginRegistry.getCommandPluginId = () => "notes";
|
|
2443
|
-
}}
|
|
2444
|
-
/>, {
|
|
2445
|
-
width: 100,
|
|
2446
|
-
height: 24,
|
|
2447
|
-
});
|
|
2448
|
-
|
|
2449
|
-
await testSetup.renderOnce();
|
|
2450
|
-
await act(async () => {
|
|
2451
|
-
testSetup!.mockInput.pressEnter();
|
|
2452
|
-
await testSetup!.renderOnce();
|
|
2453
|
-
});
|
|
2454
|
-
|
|
2455
|
-
const frame = testSetup.captureCharFrame();
|
|
2456
|
-
expect(frame.match(/repeat marker/g)?.length ?? 0).toBe(1);
|
|
2457
|
-
});
|
|
2458
|
-
|
|
2459
|
-
test("does not treat backspace as Back inside workflow text fields", async () => {
|
|
2460
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2461
|
-
query="Plugin Login"
|
|
2462
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2463
|
-
(pluginRegistry.commands as Map<string, any>).set("plugin:login", {
|
|
2464
|
-
id: "plugin:login",
|
|
2465
|
-
label: "Plugin Login",
|
|
2466
|
-
description: "Authenticate without leaving the command bar",
|
|
2467
|
-
category: "config",
|
|
2468
|
-
wizard: [
|
|
2469
|
-
{ key: "username", label: "Username", type: "text" },
|
|
2470
|
-
],
|
|
2471
|
-
execute: async () => {},
|
|
2472
|
-
} as any);
|
|
2473
|
-
pluginRegistry.getCommandPluginId = () => "notes";
|
|
2474
|
-
}}
|
|
2475
|
-
/>, {
|
|
2476
|
-
width: 100,
|
|
2477
|
-
height: 24,
|
|
2478
|
-
});
|
|
2479
|
-
|
|
2480
|
-
await testSetup.renderOnce();
|
|
2481
|
-
await act(async () => {
|
|
2482
|
-
testSetup!.mockInput.pressEnter();
|
|
2483
|
-
await testSetup!.renderOnce();
|
|
2484
|
-
});
|
|
2485
|
-
|
|
2486
|
-
await act(async () => {
|
|
2487
|
-
await testSetup!.mockInput.typeText("vince");
|
|
2488
|
-
await testSetup!.renderOnce();
|
|
2489
|
-
});
|
|
2490
|
-
await act(async () => {
|
|
2491
|
-
testSetup!.mockInput.pressBackspace();
|
|
2492
|
-
await testSetup!.renderOnce();
|
|
2493
|
-
});
|
|
2494
|
-
|
|
2495
|
-
const frame = testSetup.captureCharFrame();
|
|
2496
|
-
expect(frame).toContain("Plugin Login");
|
|
2497
|
-
expect(frame).toContain("vinc");
|
|
2498
|
-
expect(frame).toContain("Back");
|
|
2499
|
-
});
|
|
2500
|
-
|
|
2501
|
-
test("QQ MSFT executes directly without opening a secondary workflow", async () => {
|
|
2502
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2503
|
-
|
|
2504
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2505
|
-
query="QQ MSFT"
|
|
2506
|
-
selectedTicker="AAPL"
|
|
2507
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2508
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2509
|
-
created.push({ templateId, options });
|
|
2510
|
-
};
|
|
2511
|
-
}}
|
|
2512
|
-
/>, {
|
|
2513
|
-
width: 100,
|
|
2514
|
-
height: 20,
|
|
2515
|
-
});
|
|
2516
|
-
|
|
2517
|
-
await testSetup.renderOnce();
|
|
2518
|
-
await act(async () => {
|
|
2519
|
-
testSetup!.mockInput.pressEnter();
|
|
2520
|
-
await Bun.sleep(0);
|
|
2521
|
-
await testSetup!.renderOnce();
|
|
2522
|
-
});
|
|
2523
|
-
|
|
2524
|
-
expect(created).toEqual([{
|
|
2525
|
-
templateId: "quote-monitor-pane",
|
|
2526
|
-
options: {
|
|
2527
|
-
arg: "MSFT",
|
|
2528
|
-
symbols: ["MSFT"],
|
|
2529
|
-
},
|
|
2530
|
-
}]);
|
|
2531
|
-
});
|
|
2532
|
-
|
|
2533
|
-
test("QQ AAPL,MSFT creates a multi-symbol quote monitor directly", async () => {
|
|
2534
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2535
|
-
|
|
2536
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2537
|
-
query="QQ AAPL,MSFT"
|
|
2538
|
-
selectedTicker="AAPL"
|
|
2539
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2540
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2541
|
-
created.push({ templateId, options });
|
|
2542
|
-
};
|
|
2543
|
-
}}
|
|
2544
|
-
/>, {
|
|
2545
|
-
width: 100,
|
|
2546
|
-
height: 20,
|
|
2547
|
-
});
|
|
2548
|
-
|
|
2549
|
-
await testSetup.renderOnce();
|
|
2550
|
-
await act(async () => {
|
|
2551
|
-
testSetup!.mockInput.pressEnter();
|
|
2552
|
-
await Bun.sleep(0);
|
|
2553
|
-
await testSetup!.renderOnce();
|
|
2554
|
-
});
|
|
2555
|
-
|
|
2556
|
-
expect(created).toEqual([{
|
|
2557
|
-
templateId: "quote-monitor-pane",
|
|
2558
|
-
options: {
|
|
2559
|
-
arg: "AAPL,MSFT",
|
|
2560
|
-
symbols: ["AAPL", "MSFT"],
|
|
2561
|
-
},
|
|
2562
|
-
}]);
|
|
2563
|
-
});
|
|
2564
|
-
|
|
2565
|
-
test("CMP AAPL,MSFT creates the comparison chart directly", async () => {
|
|
2566
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2567
|
-
|
|
2568
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2569
|
-
query="CMP AAPL,MSFT"
|
|
2570
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2571
|
-
(pluginRegistry.panes as Map<string, any>).set("comparison-chart", {
|
|
2572
|
-
id: "comparison-chart",
|
|
2573
|
-
name: "Comparison Chart",
|
|
2574
|
-
component: () => null,
|
|
2575
|
-
defaultPosition: "right",
|
|
2576
|
-
});
|
|
2577
|
-
(pluginRegistry.paneTemplates as Map<string, any>).set("comparison-chart-pane", {
|
|
2578
|
-
id: "comparison-chart-pane",
|
|
2579
|
-
paneId: "comparison-chart",
|
|
2580
|
-
label: "Comparison Chart",
|
|
2581
|
-
description: "Compare multiple symbols in one pane",
|
|
2582
|
-
shortcut: { prefix: "CMP", argPlaceholder: "tickers", argKind: "ticker-list" },
|
|
2583
|
-
});
|
|
2584
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2585
|
-
created.push({ templateId, options });
|
|
2586
|
-
};
|
|
2587
|
-
}}
|
|
2588
|
-
/>, {
|
|
2589
|
-
width: 100,
|
|
2590
|
-
height: 20,
|
|
2591
|
-
});
|
|
2592
|
-
|
|
2593
|
-
await testSetup.renderOnce();
|
|
2594
|
-
await act(async () => {
|
|
2595
|
-
testSetup!.mockInput.pressEnter();
|
|
2596
|
-
await Bun.sleep(0);
|
|
2597
|
-
await testSetup!.renderOnce();
|
|
2598
|
-
});
|
|
2599
|
-
|
|
2600
|
-
expect(created).toEqual([{
|
|
2601
|
-
templateId: "comparison-chart-pane",
|
|
2602
|
-
options: {
|
|
2603
|
-
arg: "AAPL,MSFT",
|
|
2604
|
-
symbols: ["AAPL", "MSFT"],
|
|
2605
|
-
},
|
|
2606
|
-
}]);
|
|
2607
|
-
});
|
|
2608
|
-
|
|
2609
|
-
test("CMP AAPL, opens inline completion when the ticker list is incomplete", async () => {
|
|
2610
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2611
|
-
query="CMP AAPL,"
|
|
2612
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2613
|
-
(pluginRegistry.panes as Map<string, any>).set("comparison-chart", {
|
|
2614
|
-
id: "comparison-chart",
|
|
2615
|
-
name: "Comparison Chart",
|
|
2616
|
-
component: () => null,
|
|
2617
|
-
defaultPosition: "right",
|
|
2618
|
-
});
|
|
2619
|
-
(pluginRegistry.paneTemplates as Map<string, any>).set("comparison-chart-pane", {
|
|
2620
|
-
id: "comparison-chart-pane",
|
|
2621
|
-
paneId: "comparison-chart",
|
|
2622
|
-
label: "Comparison Chart",
|
|
2623
|
-
description: "Compare multiple symbols in one pane",
|
|
2624
|
-
shortcut: { prefix: "CMP", argPlaceholder: "tickers", argKind: "ticker-list" },
|
|
2625
|
-
});
|
|
2626
|
-
}}
|
|
2627
|
-
/>, {
|
|
2628
|
-
width: 100,
|
|
2629
|
-
height: 20,
|
|
2630
|
-
});
|
|
2631
|
-
|
|
2632
|
-
await testSetup.renderOnce();
|
|
2633
|
-
await act(async () => {
|
|
2634
|
-
testSetup!.mockInput.pressEnter();
|
|
2635
|
-
await testSetup!.renderOnce();
|
|
2636
|
-
});
|
|
2637
|
-
|
|
2638
|
-
const frame = testSetup.captureCharFrame();
|
|
2639
|
-
expect(frame).toContain("Comparison Chart");
|
|
2640
|
-
expect(frame).toContain("Tickers");
|
|
2641
|
-
expect(frame).toContain("AAPL,");
|
|
2642
|
-
});
|
|
2643
|
-
|
|
2644
|
-
test("CMP with one resolved ticker opens inline completion instead of creating a one-symbol chart", async () => {
|
|
2645
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2646
|
-
|
|
2647
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2648
|
-
query="CMP AMD"
|
|
2649
|
-
extraTickers={[makeTicker("AMD", "Advanced Micro Devices")]}
|
|
2650
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2651
|
-
(pluginRegistry.panes as Map<string, any>).set("comparison-chart", {
|
|
2652
|
-
id: "comparison-chart",
|
|
2653
|
-
name: "Comparison Chart",
|
|
2654
|
-
component: () => null,
|
|
2655
|
-
defaultPosition: "right",
|
|
2656
|
-
});
|
|
2657
|
-
(pluginRegistry.paneTemplates as Map<string, any>).set("comparison-chart-pane", {
|
|
2658
|
-
id: "comparison-chart-pane",
|
|
2659
|
-
paneId: "comparison-chart",
|
|
2660
|
-
label: "Comparison Chart",
|
|
2661
|
-
description: "Compare multiple symbols in one pane",
|
|
2662
|
-
shortcut: { prefix: "CMP", argPlaceholder: "tickers", argKind: "ticker-list" },
|
|
2663
|
-
wizard: [{ key: "tickers", label: "Tickers", type: "text" }],
|
|
2664
|
-
canCreate: (_context: unknown, options?: { symbols?: string[] | null }) => !options?.symbols || options.symbols.length >= 2,
|
|
2665
|
-
});
|
|
2666
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2667
|
-
created.push({ templateId, options });
|
|
2668
|
-
};
|
|
2669
|
-
}}
|
|
2670
|
-
/>, {
|
|
2671
|
-
width: 100,
|
|
2672
|
-
height: 20,
|
|
2673
|
-
});
|
|
2674
|
-
|
|
2675
|
-
await testSetup.renderOnce();
|
|
2676
|
-
await act(async () => {
|
|
2677
|
-
testSetup!.mockInput.pressEnter();
|
|
2678
|
-
await Bun.sleep(0);
|
|
2679
|
-
await testSetup!.renderOnce();
|
|
2680
|
-
});
|
|
2681
|
-
|
|
2682
|
-
const frame = await waitForFrameToContain("Tickers");
|
|
2683
|
-
expect(created).toEqual([]);
|
|
2684
|
-
expect(frame).toContain("Comparison Chart");
|
|
2685
|
-
expect(frame).toContain("AMD");
|
|
2686
|
-
});
|
|
2687
|
-
|
|
2688
|
-
test("AI <prompt> opens the inline workflow and prefills the textarea prompt", async () => {
|
|
2689
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2690
|
-
|
|
2691
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2692
|
-
query="AI quality compounders"
|
|
2693
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2694
|
-
(pluginRegistry.panes as Map<string, any>).set("ai-screener", {
|
|
2695
|
-
id: "ai-screener",
|
|
2696
|
-
name: "AI Screener",
|
|
2697
|
-
component: () => null,
|
|
2698
|
-
defaultPosition: "right",
|
|
2699
|
-
defaultMode: "floating",
|
|
2700
|
-
});
|
|
2701
|
-
(pluginRegistry.paneTemplates as Map<string, any>).set("new-ai-screener-pane", {
|
|
2702
|
-
id: "new-ai-screener-pane",
|
|
2703
|
-
paneId: "ai-screener",
|
|
2704
|
-
label: "AI Screener",
|
|
2705
|
-
description: "Create a prompt-driven screener pane.",
|
|
2706
|
-
shortcut: { prefix: "AI", argPlaceholder: "prompt", argKind: "text" },
|
|
2707
|
-
wizard: [
|
|
2708
|
-
{
|
|
2709
|
-
key: "providerId",
|
|
2710
|
-
label: "AI Provider",
|
|
2711
|
-
type: "select",
|
|
2712
|
-
defaultValue: "claude",
|
|
2713
|
-
options: [{ label: "Claude", value: "claude" }],
|
|
2714
|
-
},
|
|
2715
|
-
{
|
|
2716
|
-
key: "prompt",
|
|
2717
|
-
label: "Screener Prompt",
|
|
2718
|
-
type: "textarea",
|
|
2719
|
-
},
|
|
2720
|
-
],
|
|
2721
|
-
});
|
|
2722
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2723
|
-
created.push({ templateId, options });
|
|
2724
|
-
};
|
|
2725
|
-
}}
|
|
2726
|
-
/>, {
|
|
2727
|
-
width: 100,
|
|
2728
|
-
height: 24,
|
|
2729
|
-
});
|
|
2730
|
-
|
|
2731
|
-
await testSetup.renderOnce();
|
|
2732
|
-
await act(async () => {
|
|
2733
|
-
testSetup!.mockInput.pressEnter();
|
|
2734
|
-
await testSetup!.renderOnce();
|
|
2735
|
-
});
|
|
2736
|
-
|
|
2737
|
-
const frame = testSetup.captureCharFrame();
|
|
2738
|
-
expect(frame).toContain("AI Screener");
|
|
2739
|
-
expect(frame).toContain("AI Provider");
|
|
2740
|
-
expect(frame).toContain("quality compounders");
|
|
2741
|
-
expect(created).toEqual([]);
|
|
2742
|
-
});
|
|
2743
|
-
|
|
2744
|
-
test("submits typed AI screener prompts from the textarea field", async () => {
|
|
2745
|
-
const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
|
|
2746
|
-
|
|
2747
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2748
|
-
query="AI"
|
|
2749
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2750
|
-
(pluginRegistry.panes as Map<string, any>).set("ai-screener", {
|
|
2751
|
-
id: "ai-screener",
|
|
2752
|
-
name: "AI Screener",
|
|
2753
|
-
component: () => null,
|
|
2754
|
-
defaultPosition: "right",
|
|
2755
|
-
defaultMode: "floating",
|
|
2756
|
-
});
|
|
2757
|
-
(pluginRegistry.paneTemplates as Map<string, any>).set("new-ai-screener-pane", {
|
|
2758
|
-
id: "new-ai-screener-pane",
|
|
2759
|
-
paneId: "ai-screener",
|
|
2760
|
-
label: "AI Screener",
|
|
2761
|
-
description: "Create a prompt-driven screener pane.",
|
|
2762
|
-
shortcut: { prefix: "AI", argPlaceholder: "prompt", argKind: "text" },
|
|
2763
|
-
wizard: [
|
|
2764
|
-
{
|
|
2765
|
-
key: "providerId",
|
|
2766
|
-
label: "AI Provider",
|
|
2767
|
-
type: "select",
|
|
2768
|
-
defaultValue: "claude",
|
|
2769
|
-
options: [{ label: "Claude", value: "claude" }],
|
|
2770
|
-
},
|
|
2771
|
-
{
|
|
2772
|
-
key: "prompt",
|
|
2773
|
-
label: "Screener Prompt",
|
|
2774
|
-
type: "textarea",
|
|
2775
|
-
},
|
|
2776
|
-
],
|
|
2777
|
-
});
|
|
2778
|
-
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2779
|
-
created.push({ templateId, options });
|
|
2780
|
-
};
|
|
2781
|
-
}}
|
|
2782
|
-
/>, {
|
|
2783
|
-
width: 100,
|
|
2784
|
-
height: 24,
|
|
2785
|
-
});
|
|
2786
|
-
|
|
2787
|
-
await testSetup.renderOnce();
|
|
2788
|
-
await act(async () => {
|
|
2789
|
-
testSetup!.mockInput.pressEnter();
|
|
2790
|
-
await testSetup!.renderOnce();
|
|
2791
|
-
});
|
|
2792
|
-
await act(async () => {
|
|
2793
|
-
testSetup!.mockInput.pressTab();
|
|
2794
|
-
await testSetup!.renderOnce();
|
|
2795
|
-
});
|
|
2796
|
-
await act(async () => {
|
|
2797
|
-
await testSetup!.mockInput.typeText("humanoid robot suppliers");
|
|
2798
|
-
await testSetup!.renderOnce();
|
|
2799
|
-
});
|
|
2800
|
-
await clickFrameText("Create Pane");
|
|
2801
|
-
await act(async () => {
|
|
2802
|
-
await Bun.sleep(0);
|
|
2803
|
-
await testSetup!.renderOnce();
|
|
2804
|
-
});
|
|
2805
|
-
|
|
2806
|
-
expect(created).toEqual([{
|
|
2807
|
-
templateId: "new-ai-screener-pane",
|
|
2808
|
-
options: {
|
|
2809
|
-
arg: "humanoid robot suppliers",
|
|
2810
|
-
values: {
|
|
2811
|
-
providerId: "claude",
|
|
2812
|
-
prompt: "humanoid robot suppliers",
|
|
2813
|
-
},
|
|
2814
|
-
},
|
|
2815
|
-
}]);
|
|
2816
|
-
});
|
|
2817
|
-
|
|
2818
|
-
test("edits pane settings inline inside the command bar", async () => {
|
|
2819
|
-
const appliedValues: Array<{ paneId: string; key: string; value: unknown }> = [];
|
|
2820
|
-
|
|
2821
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2822
|
-
query="PS"
|
|
2823
|
-
configureState={(state) => ({
|
|
2824
|
-
...state,
|
|
2825
|
-
focusedPaneId: "quote-monitor:main",
|
|
2826
|
-
})}
|
|
2827
|
-
hasPaneSettings={(paneId) => paneId === "quote-monitor:main"}
|
|
2828
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2829
|
-
pluginRegistry.resolvePaneSettings = () => makeQuoteMonitorPaneSettingsDescriptor(pluginRegistry, [{
|
|
2830
|
-
key: "symbol",
|
|
2831
|
-
label: "Symbol",
|
|
2832
|
-
type: "text",
|
|
2833
|
-
description: "Ticker symbol to track",
|
|
2834
|
-
}]);
|
|
2835
|
-
pluginRegistry.applyPaneSettingValueFn = async (paneId, field, value) => {
|
|
2836
|
-
appliedValues.push({ paneId, key: field.key, value });
|
|
2837
|
-
};
|
|
2838
|
-
}}
|
|
2839
|
-
/>, {
|
|
2840
|
-
width: 100,
|
|
2841
|
-
height: 20,
|
|
2842
|
-
});
|
|
2843
|
-
|
|
2844
|
-
await testSetup.renderOnce();
|
|
2845
|
-
|
|
2846
|
-
await act(async () => {
|
|
2847
|
-
testSetup!.mockInput.pressEnter();
|
|
2848
|
-
await testSetup!.renderOnce();
|
|
2849
|
-
});
|
|
2850
|
-
let frame = testSetup.captureCharFrame();
|
|
2851
|
-
expect(frame).toContain("Quote Monitor Settings");
|
|
2852
|
-
expect(frame).toContain("Symbol");
|
|
2853
|
-
|
|
2854
|
-
await act(async () => {
|
|
2855
|
-
testSetup!.mockInput.pressEnter();
|
|
2856
|
-
await testSetup!.renderOnce();
|
|
2857
|
-
});
|
|
2858
|
-
frame = testSetup.captureCharFrame();
|
|
2859
|
-
await clickFrameText("Symbol");
|
|
2860
|
-
frame = testSetup.captureCharFrame();
|
|
2861
|
-
expect(frame).toContain("Apply");
|
|
2862
|
-
expect(frame).toContain("Symbol");
|
|
2863
|
-
|
|
2864
|
-
await act(async () => {
|
|
2865
|
-
await testSetup!.mockInput.typeText("MSFT");
|
|
2866
|
-
await testSetup!.renderOnce();
|
|
2867
|
-
});
|
|
2868
|
-
await act(async () => {
|
|
2869
|
-
testSetup!.mockInput.pressEnter();
|
|
2870
|
-
await Bun.sleep(0);
|
|
2871
|
-
await testSetup!.renderOnce();
|
|
2872
|
-
});
|
|
2873
|
-
|
|
2874
|
-
expect(appliedValues).toEqual([{
|
|
2875
|
-
paneId: "quote-monitor:main",
|
|
2876
|
-
key: "symbol",
|
|
2877
|
-
value: "MSFT",
|
|
2878
|
-
}]);
|
|
2879
|
-
});
|
|
2880
|
-
|
|
2881
|
-
test("opens focused pane settings directly from root search", async () => {
|
|
2882
|
-
const appliedValues: Array<{ paneId: string; key: string; value: unknown }> = [];
|
|
2883
|
-
|
|
2884
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2885
|
-
query="ticker symbol"
|
|
2886
|
-
configureState={(state) => ({
|
|
2887
|
-
...state,
|
|
2888
|
-
focusedPaneId: "quote-monitor:main",
|
|
2889
|
-
})}
|
|
2890
|
-
hasPaneSettings={(paneId) => paneId === "quote-monitor:main"}
|
|
2891
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2892
|
-
pluginRegistry.resolvePaneSettings = () => makeQuoteMonitorPaneSettingsDescriptor(pluginRegistry, [{
|
|
2893
|
-
key: "symbol",
|
|
2894
|
-
label: "Symbol",
|
|
2895
|
-
type: "text",
|
|
2896
|
-
description: "Ticker symbol to track",
|
|
2897
|
-
}]);
|
|
2898
|
-
pluginRegistry.applyPaneSettingValueFn = async (paneId, field, value) => {
|
|
2899
|
-
appliedValues.push({ paneId, key: field.key, value });
|
|
2900
|
-
};
|
|
2901
|
-
}}
|
|
2902
|
-
/>, {
|
|
2903
|
-
width: 100,
|
|
2904
|
-
height: 20,
|
|
2905
|
-
});
|
|
2906
|
-
|
|
2907
|
-
await testSetup.renderOnce();
|
|
2908
|
-
|
|
2909
|
-
let frame = testSetup.captureCharFrame();
|
|
2910
|
-
expect(frame).toContain("Quote Monitor Settings");
|
|
2911
|
-
expect(frame).toContain("Symbol");
|
|
2912
|
-
|
|
2913
|
-
await act(async () => {
|
|
2914
|
-
testSetup!.mockInput.pressEnter();
|
|
2915
|
-
await testSetup!.renderOnce();
|
|
2916
|
-
});
|
|
2917
|
-
|
|
2918
|
-
frame = testSetup.captureCharFrame();
|
|
2919
|
-
expect(frame).toContain("Apply");
|
|
2920
|
-
expect(frame).toContain("Symbol");
|
|
2921
|
-
|
|
2922
|
-
await act(async () => {
|
|
2923
|
-
await testSetup!.mockInput.typeText("MSFT");
|
|
2924
|
-
await testSetup!.renderOnce();
|
|
2925
|
-
});
|
|
2926
|
-
await act(async () => {
|
|
2927
|
-
testSetup!.mockInput.pressEnter();
|
|
2928
|
-
await Bun.sleep(0);
|
|
2929
|
-
await testSetup!.renderOnce();
|
|
2930
|
-
});
|
|
2931
|
-
|
|
2932
|
-
expect(appliedValues).toEqual([{
|
|
2933
|
-
paneId: "quote-monitor:main",
|
|
2934
|
-
key: "symbol",
|
|
2935
|
-
value: "MSFT",
|
|
2936
|
-
}]);
|
|
2937
|
-
});
|
|
2938
|
-
|
|
2939
|
-
test("uses backspace as back only when a pane-settings route query is empty", async () => {
|
|
2940
|
-
testSetup = await testRender(<CommandBarHarness
|
|
2941
|
-
query="PS"
|
|
2942
|
-
configureState={(state) => ({
|
|
2943
|
-
...state,
|
|
2944
|
-
focusedPaneId: "quote-monitor:main",
|
|
2945
|
-
})}
|
|
2946
|
-
hasPaneSettings={(paneId) => paneId === "quote-monitor:main"}
|
|
2947
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
2948
|
-
pluginRegistry.resolvePaneSettings = () => makeQuoteMonitorPaneSettingsDescriptor(pluginRegistry, [{
|
|
2949
|
-
key: "symbol",
|
|
2950
|
-
label: "Symbol",
|
|
2951
|
-
type: "text",
|
|
2952
|
-
description: "Ticker symbol to track",
|
|
2953
|
-
}]);
|
|
2954
|
-
}}
|
|
2955
|
-
/>, {
|
|
2956
|
-
width: 100,
|
|
2957
|
-
height: 20,
|
|
2958
|
-
});
|
|
2959
|
-
|
|
2960
|
-
await testSetup.renderOnce();
|
|
2961
|
-
|
|
2962
|
-
await act(async () => {
|
|
2963
|
-
testSetup!.mockInput.pressEnter();
|
|
2964
|
-
await testSetup!.renderOnce();
|
|
2965
|
-
});
|
|
2966
|
-
await renderFrames();
|
|
2967
|
-
|
|
2968
|
-
let frame = testSetup.captureCharFrame();
|
|
2969
|
-
expect(frame).toContain("Quote Monitor Settings");
|
|
2970
|
-
|
|
2971
|
-
await act(async () => {
|
|
2972
|
-
await testSetup!.mockInput.typeText("s");
|
|
2973
|
-
await testSetup!.renderOnce();
|
|
2974
|
-
});
|
|
2975
|
-
|
|
2976
|
-
frame = testSetup.captureCharFrame();
|
|
2977
|
-
expect(frame).toContain("Quote Monitor Settings");
|
|
2978
|
-
|
|
2979
|
-
await emitKeypress(testSetup, { name: "backspace", sequence: "\b" });
|
|
2980
|
-
|
|
2981
|
-
frame = testSetup.captureCharFrame();
|
|
2982
|
-
expect(frame).toContain("Quote Monitor Settings");
|
|
2983
|
-
|
|
2984
|
-
await act(async () => {
|
|
2985
|
-
testSetup!.mockInput.pressBackspace();
|
|
2986
|
-
await testSetup!.renderOnce();
|
|
2987
|
-
});
|
|
2988
|
-
await renderFrames();
|
|
2989
|
-
await act(async () => {
|
|
2990
|
-
testSetup!.mockInput.pressBackspace();
|
|
2991
|
-
await testSetup!.renderOnce();
|
|
2992
|
-
});
|
|
2993
|
-
await renderFrames();
|
|
2994
|
-
|
|
2995
|
-
frame = testSetup.captureCharFrame();
|
|
2996
|
-
expect(frame).not.toContain("Quote Monitor Settings");
|
|
2997
|
-
expect(frame).not.toContain("Back Pane Settings");
|
|
2998
|
-
});
|
|
2999
|
-
|
|
3000
|
-
test("renders pane-setting multi-select pickers only once inside the command bar", async () => {
|
|
3001
|
-
testSetup = await testRender(<CommandBarHarness
|
|
3002
|
-
query="PS"
|
|
3003
|
-
configureState={(state) => ({
|
|
3004
|
-
...state,
|
|
3005
|
-
focusedPaneId: "quote-monitor:main",
|
|
3006
|
-
})}
|
|
3007
|
-
hasPaneSettings={(paneId) => paneId === "quote-monitor:main"}
|
|
3008
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
3009
|
-
pluginRegistry.resolvePaneSettings = () => makeQuoteMonitorPaneSettingsDescriptor(pluginRegistry, [{
|
|
3010
|
-
key: "columns",
|
|
3011
|
-
label: "Columns",
|
|
3012
|
-
type: "ordered-multi-select",
|
|
3013
|
-
description: "Visible columns",
|
|
3014
|
-
options: [
|
|
3015
|
-
{ value: "volume", label: "AAA", description: "Volume column" },
|
|
3016
|
-
{ value: "spread", label: "BBB", description: "Spread column" },
|
|
3017
|
-
{ value: "beta", label: "CCC", description: "Beta column" },
|
|
3018
|
-
],
|
|
3019
|
-
}], { columns: ["volume", "spread"] });
|
|
3020
|
-
}}
|
|
3021
|
-
/>, {
|
|
3022
|
-
width: 100,
|
|
3023
|
-
height: 20,
|
|
3024
|
-
});
|
|
3025
|
-
|
|
3026
|
-
await testSetup.renderOnce();
|
|
3027
|
-
|
|
3028
|
-
await act(async () => {
|
|
3029
|
-
testSetup!.mockInput.pressEnter();
|
|
3030
|
-
await testSetup!.renderOnce();
|
|
3031
|
-
});
|
|
3032
|
-
await clickFrameText("Columns");
|
|
3033
|
-
|
|
3034
|
-
const frame = await waitForFrameToContain("Done");
|
|
3035
|
-
expect(frame).toContain("Columns");
|
|
3036
|
-
expect(frame).toContain("Done");
|
|
3037
|
-
expect(frame).not.toContain("Options");
|
|
3038
|
-
expectSingleBackControl(frame);
|
|
3039
|
-
});
|
|
3040
|
-
|
|
3041
|
-
test("matches the IBKR trading shortcut when a gateway profile exists", async () => {
|
|
3042
|
-
testSetup = await testRender(<CommandBarHarness
|
|
3043
|
-
query="IBKR"
|
|
3044
|
-
configureConfig={(config) => ({
|
|
3045
|
-
...config,
|
|
3046
|
-
brokerInstances: [{
|
|
3047
|
-
id: "ibkr-paper",
|
|
3048
|
-
brokerType: "ibkr",
|
|
3049
|
-
label: "Paper",
|
|
3050
|
-
connectionMode: "gateway",
|
|
3051
|
-
config: { connectionMode: "gateway", gateway: { host: "127.0.0.1", port: 4002, clientId: 1 } },
|
|
3052
|
-
enabled: true,
|
|
3053
|
-
}],
|
|
3054
|
-
})}
|
|
3055
|
-
/>, {
|
|
3056
|
-
width: 100,
|
|
3057
|
-
height: 18,
|
|
3058
|
-
});
|
|
3059
|
-
|
|
3060
|
-
await testSetup.renderOnce();
|
|
3061
|
-
|
|
3062
|
-
const frame = testSetup.captureCharFrame();
|
|
3063
|
-
expect(frame).toContain("IBKR Trading");
|
|
3064
|
-
expect(frame).toContain("IBKR");
|
|
3065
|
-
});
|
|
3066
|
-
|
|
3067
|
-
test("matches the direct help command", async () => {
|
|
3068
|
-
const openedPanes: string[] = [];
|
|
3069
|
-
testSetup = await testRender(<CommandBarHarness
|
|
3070
|
-
query="help"
|
|
3071
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
3072
|
-
pluginRegistry.showPane = (paneId: string) => {
|
|
3073
|
-
openedPanes.push(paneId);
|
|
3074
|
-
};
|
|
3075
|
-
}}
|
|
3076
|
-
/>, {
|
|
3077
|
-
width: 80,
|
|
3078
|
-
height: 18,
|
|
3079
|
-
});
|
|
3080
|
-
|
|
3081
|
-
await testSetup.renderOnce();
|
|
3082
|
-
|
|
3083
|
-
const frame = testSetup.captureCharFrame();
|
|
3084
|
-
expect(frame).toContain("Help");
|
|
3085
|
-
expect(frame).toContain("Navigation");
|
|
3086
|
-
|
|
3087
|
-
await act(async () => {
|
|
3088
|
-
testSetup!.mockInput.pressEnter();
|
|
3089
|
-
await testSetup!.renderOnce();
|
|
3090
|
-
});
|
|
3091
|
-
|
|
3092
|
-
expect(openedPanes).toEqual(["help"]);
|
|
3093
|
-
});
|
|
3094
|
-
|
|
3095
|
-
test("skips pane templates whose canCreate throws", async () => {
|
|
3096
|
-
testSetup = await testRender(<CommandBarHarness
|
|
3097
|
-
query=""
|
|
3098
|
-
selectedTicker="AAPL"
|
|
3099
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
3100
|
-
(pluginRegistry.paneTemplates as Map<string, any>).set("broken-pane", {
|
|
3101
|
-
id: "broken-pane",
|
|
3102
|
-
paneId: "chat",
|
|
3103
|
-
label: "Broken Pane",
|
|
3104
|
-
description: "Should not crash the command bar",
|
|
3105
|
-
shortcut: { prefix: "IBKR" },
|
|
3106
|
-
canCreate: () => {
|
|
3107
|
-
throw new ReferenceError("getIbkrInstances is not defined");
|
|
3108
|
-
},
|
|
3109
|
-
});
|
|
3110
|
-
}}
|
|
3111
|
-
/>, {
|
|
3112
|
-
width: 100,
|
|
3113
|
-
height: 24,
|
|
3114
|
-
});
|
|
3115
|
-
|
|
3116
|
-
await testSetup.renderOnce();
|
|
3117
|
-
|
|
3118
|
-
const frame = testSetup.captureCharFrame();
|
|
3119
|
-
expect(frame).toContain("Quote Monitor");
|
|
3120
|
-
expect(frame).not.toContain("Broken Pane");
|
|
3121
|
-
});
|
|
3122
|
-
|
|
3123
|
-
test("groups ticker search sections and keeps saved matches above looser provider results", async () => {
|
|
3124
|
-
testSetup = await testRender(
|
|
3125
|
-
<CommandBarHarness
|
|
3126
|
-
query="DES appl"
|
|
3127
|
-
dataProvider={makeDataProvider(async () => [
|
|
3128
|
-
{ providerId: "yahoo", symbol: "IVSX", name: "Invsivx Holdings", exchange: "NYSE", type: "ETF" },
|
|
3129
|
-
{ providerId: "yahoo", symbol: "AAPL", name: "Apple Inc", exchange: "NASDAQ", type: "EQUITY" },
|
|
3130
|
-
{ providerId: "yahoo", symbol: "AMAT", name: "Applied Materials", exchange: "NASDAQ", type: "EQUITY" },
|
|
3131
|
-
{ providerId: "yahoo", symbol: "AAOI", name: "Applied Optoelectronics", exchange: "NASDAQ", type: "EQUITY" },
|
|
3132
|
-
{ providerId: "yahoo", symbol: "APP", name: "AppLovin Corp", exchange: "NASDAQ", type: "EQUITY" },
|
|
3133
|
-
])}
|
|
3134
|
-
/>,
|
|
3135
|
-
{ width: 80, height: 24 },
|
|
3136
|
-
);
|
|
3137
|
-
|
|
3138
|
-
await testSetup.renderOnce();
|
|
3139
|
-
await waitForFrameToContain("AAOI");
|
|
3140
|
-
|
|
3141
|
-
const frame = testSetup.captureCharFrame();
|
|
3142
|
-
const rows = frame.split("\n");
|
|
3143
|
-
const savedHeadings = frame.split("\n").filter((line) => line.trim() === "Saved");
|
|
3144
|
-
const otherListingsHeadings = frame.split("\n").filter((line) => line.trim() === "Other Listings");
|
|
3145
|
-
const aaplRow = rows.findIndex((line) => line.trimStart().startsWith("AAPL") && line.includes("NASDAQ"));
|
|
3146
|
-
const appRow = rows.findIndex((line) => line.trimStart().startsWith("APP") && line.includes("NASDAQ"));
|
|
3147
|
-
expect(savedHeadings).toHaveLength(1);
|
|
3148
|
-
expect(otherListingsHeadings).toHaveLength(1);
|
|
3149
|
-
expect(aaplRow).toBeGreaterThanOrEqual(0);
|
|
3150
|
-
expect(appRow).toBeGreaterThanOrEqual(0);
|
|
3151
|
-
expect(aaplRow).toBeLessThan(appRow);
|
|
3152
|
-
expect(frame).toMatchSnapshot();
|
|
3153
|
-
});
|
|
3154
|
-
|
|
3155
|
-
test("renders form-layout wizard fields together on one screen", async () => {
|
|
3156
|
-
testSetup = await testRender(
|
|
3157
|
-
<CommandBarHarness
|
|
3158
|
-
query="auth login"
|
|
3159
|
-
live
|
|
3160
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
3161
|
-
(pluginRegistry.commands as Map<string, any>).set("auth-login", {
|
|
3162
|
-
id: "auth-login",
|
|
3163
|
-
label: "Auth Login",
|
|
3164
|
-
description: "Log in to your account",
|
|
3165
|
-
keywords: ["login", "auth"],
|
|
3166
|
-
category: "config",
|
|
3167
|
-
wizardLayout: "form",
|
|
3168
|
-
wizard: [
|
|
3169
|
-
{ key: "email", label: "Email", type: "text", placeholder: "email@example.com" },
|
|
3170
|
-
{ key: "password", label: "Password", type: "password", placeholder: "Your password" },
|
|
3171
|
-
],
|
|
3172
|
-
execute: async () => {},
|
|
3173
|
-
} as any);
|
|
3174
|
-
}}
|
|
3175
|
-
/>,
|
|
3176
|
-
{ width: 80, height: 24 },
|
|
3177
|
-
);
|
|
3178
|
-
|
|
3179
|
-
await testSetup.renderOnce();
|
|
3180
|
-
await clickFrameText("Auth Login");
|
|
3181
|
-
await act(async () => {
|
|
3182
|
-
await testSetup!.renderOnce();
|
|
3183
|
-
});
|
|
3184
|
-
|
|
3185
|
-
let frame = testSetup.captureCharFrame();
|
|
3186
|
-
expect(frame).toContain("Back");
|
|
3187
|
-
expect(frame).toContain("Email");
|
|
3188
|
-
expect(frame).toContain("Password");
|
|
3189
|
-
expect(frame).toContain("Your password");
|
|
3190
|
-
expectSingleBackControl(frame);
|
|
3191
|
-
});
|
|
3192
|
-
|
|
3193
|
-
test("submits single-field form-layout wizards", async () => {
|
|
3194
|
-
const submitted: Array<Record<string, string> | undefined> = [];
|
|
3195
|
-
|
|
3196
|
-
testSetup = await testRender(
|
|
3197
|
-
<CommandBarHarness
|
|
3198
|
-
query="workspace"
|
|
3199
|
-
live
|
|
3200
|
-
configurePluginRegistry={(pluginRegistry) => {
|
|
3201
|
-
(pluginRegistry.commands as Map<string, any>).set("new-workspace", {
|
|
3202
|
-
id: "new-workspace",
|
|
3203
|
-
label: "Workspace",
|
|
3204
|
-
description: "Create a workspace",
|
|
3205
|
-
keywords: ["workspace"],
|
|
3206
|
-
category: "config",
|
|
3207
|
-
wizardLayout: "form",
|
|
3208
|
-
wizard: [
|
|
3209
|
-
{ key: "name", label: "Name", type: "text", placeholder: "Research" },
|
|
3210
|
-
],
|
|
3211
|
-
execute: async (values?: Record<string, string>) => {
|
|
3212
|
-
submitted.push(values);
|
|
3213
|
-
},
|
|
3214
|
-
} as any);
|
|
3215
|
-
}}
|
|
3216
|
-
/>,
|
|
3217
|
-
{ width: 80, height: 24 },
|
|
3218
|
-
);
|
|
3219
|
-
|
|
3220
|
-
await testSetup.renderOnce();
|
|
3221
|
-
|
|
3222
|
-
await act(async () => {
|
|
3223
|
-
testSetup!.mockInput.pressEnter();
|
|
3224
|
-
await Bun.sleep(0);
|
|
3225
|
-
await testSetup!.renderOnce();
|
|
3226
|
-
await testSetup!.renderOnce();
|
|
3227
|
-
});
|
|
3228
|
-
|
|
3229
|
-
await act(async () => {
|
|
3230
|
-
await testSetup!.mockInput.typeText("Research");
|
|
3231
|
-
await testSetup!.renderOnce();
|
|
3232
|
-
await testSetup!.renderOnce();
|
|
3233
|
-
});
|
|
3234
|
-
|
|
3235
|
-
await act(async () => {
|
|
3236
|
-
testSetup!.mockInput.pressEnter();
|
|
3237
|
-
await Bun.sleep(0);
|
|
3238
|
-
await testSetup!.renderOnce();
|
|
3239
|
-
await testSetup!.renderOnce();
|
|
3240
|
-
});
|
|
3241
|
-
|
|
3242
|
-
expect(submitted).toEqual([{ name: "Research" }]);
|
|
3243
|
-
});
|
|
3244
|
-
});
|