gloomberb 0.7.2 → 0.7.3
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 +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +49 -15
- package/src/components/chart/chart-types.ts +2 -2
- package/src/components/chart/chart.test.ts +27 -8
- package/src/components/chart/comparison-stock-chart.tsx +84 -121
- package/src/components/chart/native/chart-rasterizer.ts +43 -17
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +1 -1
- package/src/components/chart/stock-chart.tsx +59 -41
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +528 -383
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +1 -0
- package/src/components/command-bar/view-model.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +11 -51
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator.ts +8 -1
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +258 -44
- package/src/plugins/builtin/chat.test.tsx +257 -6
- package/src/plugins/builtin/chat.tsx +225 -60
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/news-wire/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +11 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +117 -18
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +282 -2
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +252 -41
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +0 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +442 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/state/app-bootstrap.ts +3 -3
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +128 -0
- package/src/utils/api-client.ts +112 -7
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { act, useCallback, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { PaneFooterBar, PaneFooterProvider } from "../../components/layout/pane-footer";
|
|
4
4
|
import { testRender } from "../../renderers/opentui/test-utils";
|
|
@@ -8,7 +8,7 @@ import { colors } from "../../theme/colors";
|
|
|
8
8
|
import { createDefaultConfig, findPaneInstance, type PaneInstanceConfig } from "../../types/config";
|
|
9
9
|
import type { PersistedResourceValue } from "../../types/persistence";
|
|
10
10
|
import type { PluginPersistence } from "../../types/plugin";
|
|
11
|
-
import { Box } from "../../ui";
|
|
11
|
+
import { Box, TextAttributes } from "../../ui";
|
|
12
12
|
import { apiClient, type ChatChannel, type ChatMessage } from "../../utils/api-client";
|
|
13
13
|
import { PluginRenderProvider, type PluginRuntimeAccess } from "../plugin-runtime";
|
|
14
14
|
import { setSharedMarketDataForTests, setSharedRegistryForTests } from "../registry";
|
|
@@ -21,6 +21,8 @@ const TRANSCRIPT_SOURCE = "server";
|
|
|
21
21
|
const TRANSCRIPT_SCHEMA_VERSION = 2;
|
|
22
22
|
const originalConnectChannel = apiClient.connectChannel.bind(apiClient);
|
|
23
23
|
const originalGetChannels = apiClient.getChannels.bind(apiClient);
|
|
24
|
+
const originalGetChatPresence = apiClient.getChatPresence.bind(apiClient);
|
|
25
|
+
const originalUpdateChatChannelState = apiClient.updateChatChannelState.bind(apiClient);
|
|
24
26
|
|
|
25
27
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
26
28
|
|
|
@@ -150,6 +152,8 @@ function createController(options: {
|
|
|
150
152
|
controller.attachPersistence(persistence);
|
|
151
153
|
controller.refreshSession = async () => {};
|
|
152
154
|
controller.refreshMessages = async () => {};
|
|
155
|
+
controller.refreshPresence = async () => {};
|
|
156
|
+
controller.refreshChatState = async () => {};
|
|
153
157
|
return controller;
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -248,11 +252,23 @@ function hexToRgbaInts(hex: string) {
|
|
|
248
252
|
].join(",");
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
apiClient.getChatPresence = async () => ({ onlineCount: 0 });
|
|
257
|
+
apiClient.updateChatChannelState = async (channelId, body) => ({
|
|
258
|
+
channelId,
|
|
259
|
+
notificationsEnabled: body.notificationsEnabled ?? false,
|
|
260
|
+
lastReadMessageId: body.readThroughMessageId ?? null,
|
|
261
|
+
unreadCount: 0,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
251
265
|
afterEach(async () => {
|
|
252
266
|
setSharedRegistryForTests(undefined);
|
|
253
267
|
setSharedMarketDataForTests(undefined);
|
|
254
268
|
apiClient.connectChannel = originalConnectChannel;
|
|
255
269
|
apiClient.getChannels = originalGetChannels;
|
|
270
|
+
apiClient.getChatPresence = originalGetChatPresence;
|
|
271
|
+
apiClient.updateChatChannelState = originalUpdateChatChannelState;
|
|
256
272
|
apiClient.setSessionToken(null);
|
|
257
273
|
|
|
258
274
|
if (testSetup) {
|
|
@@ -468,6 +484,136 @@ describe("ChatContent", () => {
|
|
|
468
484
|
expect(testSetup.captureCharFrame()).toContain("#options");
|
|
469
485
|
});
|
|
470
486
|
|
|
487
|
+
test("renders unread sidebar channels in bold and clears them when opened", async () => {
|
|
488
|
+
const controller = createController({ sessionToken: "token-123" });
|
|
489
|
+
installServerChannels(controller);
|
|
490
|
+
controller.refreshChannels = async () => {};
|
|
491
|
+
controller.refreshChannelMessages = async () => {};
|
|
492
|
+
const optionsState = (controller as any).ensureChannelState("options");
|
|
493
|
+
optionsState.unreadCount = 2;
|
|
494
|
+
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-chat"));
|
|
495
|
+
|
|
496
|
+
function ChannelPane() {
|
|
497
|
+
const [channelId, setChannelId] = useState("equities");
|
|
498
|
+
return (
|
|
499
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
500
|
+
<PluginRenderProvider pluginId="gloomberb-cloud" runtime={createTestPluginRuntime()}>
|
|
501
|
+
<ChatContent
|
|
502
|
+
controller={controller}
|
|
503
|
+
width={90}
|
|
504
|
+
height={12}
|
|
505
|
+
focused
|
|
506
|
+
channelId={channelId}
|
|
507
|
+
onChannelChange={setChannelId}
|
|
508
|
+
/>
|
|
509
|
+
</PluginRenderProvider>
|
|
510
|
+
</AppContext>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
await act(async () => {
|
|
515
|
+
testSetup = await testRender(<ChannelPane />, {
|
|
516
|
+
width: 90,
|
|
517
|
+
height: 12,
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await flushFrame();
|
|
522
|
+
const unreadLine = testSetup.captureSpans().lines.find((line) => lineText(line).includes("options"));
|
|
523
|
+
const unreadSpan = unreadLine?.spans.find((span) => span.text.includes("options"));
|
|
524
|
+
expect((unreadSpan?.attributes ?? 0) & TextAttributes.BOLD).toBe(TextAttributes.BOLD);
|
|
525
|
+
|
|
526
|
+
const lines = testSetup.captureCharFrame().split("\n");
|
|
527
|
+
const row = lines.findIndex((line) => line.includes("options"));
|
|
528
|
+
const col = lines[row]?.indexOf("options") ?? -1;
|
|
529
|
+
|
|
530
|
+
await act(async () => {
|
|
531
|
+
await testSetup!.mockMouse.click(col + 1, row);
|
|
532
|
+
await testSetup!.renderOnce();
|
|
533
|
+
await testSetup!.renderOnce();
|
|
534
|
+
});
|
|
535
|
+
await flushFrame();
|
|
536
|
+
|
|
537
|
+
const readLine = testSetup.captureSpans().lines.find((line) => lineText(line).includes("#options"));
|
|
538
|
+
const readSpan = readLine?.spans.find((span) => span.text.includes("options"));
|
|
539
|
+
expect((readSpan?.attributes ?? 0) & TextAttributes.BOLD).toBe(0);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("toggles sidebar channel notifications without selecting the channel", async () => {
|
|
543
|
+
const controller = createController({ sessionToken: "token-123" });
|
|
544
|
+
installServerChannels(controller);
|
|
545
|
+
controller.refreshChannels = async () => {};
|
|
546
|
+
controller.refreshChannelMessages = async () => {};
|
|
547
|
+
const toggles: Array<{ channelId: string; enabled: boolean }> = [];
|
|
548
|
+
controller.setChannelNotificationsEnabled = (channelId: string, enabled: boolean) => {
|
|
549
|
+
toggles.push({ channelId, enabled });
|
|
550
|
+
};
|
|
551
|
+
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-chat"));
|
|
552
|
+
|
|
553
|
+
function ChannelPane() {
|
|
554
|
+
const [channelId, setChannelId] = useState("equities");
|
|
555
|
+
return (
|
|
556
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
557
|
+
<PluginRenderProvider pluginId="gloomberb-cloud" runtime={createTestPluginRuntime()}>
|
|
558
|
+
<ChatContent
|
|
559
|
+
controller={controller}
|
|
560
|
+
width={90}
|
|
561
|
+
height={12}
|
|
562
|
+
focused
|
|
563
|
+
channelId={channelId}
|
|
564
|
+
onChannelChange={setChannelId}
|
|
565
|
+
/>
|
|
566
|
+
</PluginRenderProvider>
|
|
567
|
+
</AppContext>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
await act(async () => {
|
|
572
|
+
testSetup = await testRender(<ChannelPane />, {
|
|
573
|
+
width: 90,
|
|
574
|
+
height: 12,
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
await flushFrame();
|
|
579
|
+
const frame = testSetup.captureCharFrame();
|
|
580
|
+
const lines = frame.split("\n");
|
|
581
|
+
const row = lines.findIndex((line) => line.includes("options"));
|
|
582
|
+
const col = lines[row]?.lastIndexOf("·") ?? -1;
|
|
583
|
+
expect(row).toBeGreaterThanOrEqual(0);
|
|
584
|
+
expect(col).toBeGreaterThanOrEqual(0);
|
|
585
|
+
|
|
586
|
+
await act(async () => {
|
|
587
|
+
await testSetup!.mockMouse.click(col, row);
|
|
588
|
+
await testSetup!.renderOnce();
|
|
589
|
+
await testSetup!.renderOnce();
|
|
590
|
+
});
|
|
591
|
+
await flushFrame();
|
|
592
|
+
|
|
593
|
+
expect(toggles).toEqual([{ channelId: "options", enabled: true }]);
|
|
594
|
+
expect(testSetup.captureCharFrame()).toContain("#equities");
|
|
595
|
+
expect(testSetup.captureCharFrame()).not.toContain("#options");
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("shows the sidebar online count footer", async () => {
|
|
599
|
+
const controller = createController({ sessionToken: "token-123" });
|
|
600
|
+
installServerChannels(controller);
|
|
601
|
+
controller.refreshChannels = async () => {};
|
|
602
|
+
controller.refreshChannelMessages = async () => {};
|
|
603
|
+
(controller as any).onlineCount = 6;
|
|
604
|
+
|
|
605
|
+
await act(async () => {
|
|
606
|
+
testSetup = await testRender(createHarness(controller, { width: 90, height: 12 }), {
|
|
607
|
+
width: 90,
|
|
608
|
+
height: 12,
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
await flushFrame();
|
|
613
|
+
|
|
614
|
+
expect(testSetup.captureCharFrame()).toContain("● 6 online");
|
|
615
|
+
});
|
|
616
|
+
|
|
471
617
|
test("uses arrows to move between channel sidebar and chat content", async () => {
|
|
472
618
|
const controller = createController({ sessionToken: "token-123" });
|
|
473
619
|
installServerChannels(controller);
|
|
@@ -818,6 +964,34 @@ describe("ChatContent", () => {
|
|
|
818
964
|
expect(frame).toContain("Reply to @user2...");
|
|
819
965
|
});
|
|
820
966
|
|
|
967
|
+
test("modified k does not move message selection", async () => {
|
|
968
|
+
const controller = createController({
|
|
969
|
+
messages: [makeMessage(1), makeMessage(2)],
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
await act(async () => {
|
|
973
|
+
testSetup = await testRender(createHarness(controller, {
|
|
974
|
+
width: 72,
|
|
975
|
+
height: 12,
|
|
976
|
+
}), {
|
|
977
|
+
width: 72,
|
|
978
|
+
height: 12,
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
await flushFrame();
|
|
983
|
+
|
|
984
|
+
await emitKeypress({ name: "up", sequence: "\u001b[A" });
|
|
985
|
+
await emitKeypress({ name: "k", sequence: "k", meta: true });
|
|
986
|
+
await emitKeypress({ name: "return", sequence: "\r" });
|
|
987
|
+
await flushFrame();
|
|
988
|
+
|
|
989
|
+
const frame = testSetup.captureCharFrame();
|
|
990
|
+
expect(frame).toContain("replying to @user2");
|
|
991
|
+
expect(frame).toContain("Reply to @user2...");
|
|
992
|
+
expect(frame).not.toContain("replying to @user1");
|
|
993
|
+
});
|
|
994
|
+
|
|
821
995
|
test("shows the reply action next to the selected message timestamp", async () => {
|
|
822
996
|
const controller = createController({
|
|
823
997
|
messages: [makeMessage(1), makeMessage(2)],
|
|
@@ -1448,7 +1622,7 @@ describe("ChatContent", () => {
|
|
|
1448
1622
|
expect(frame).not.toContain("message 1");
|
|
1449
1623
|
});
|
|
1450
1624
|
|
|
1451
|
-
test("
|
|
1625
|
+
test("preserves a manually scrolled transcript across focus and snapshot refreshes", async () => {
|
|
1452
1626
|
const controller = createController({
|
|
1453
1627
|
messages: [
|
|
1454
1628
|
makeMessage(1),
|
|
@@ -1506,13 +1680,90 @@ describe("ChatContent", () => {
|
|
|
1506
1680
|
|
|
1507
1681
|
await act(async () => {
|
|
1508
1682
|
setFocused?.(true);
|
|
1683
|
+
(controller as any).emit("everyone");
|
|
1684
|
+
await testSetup!.renderOnce();
|
|
1685
|
+
});
|
|
1686
|
+
await flushFrame();
|
|
1687
|
+
|
|
1688
|
+
const refreshedFrame = testSetup.captureCharFrame();
|
|
1689
|
+
expect(refreshedFrame).toContain("message 2");
|
|
1690
|
+
expect(refreshedFrame).not.toContain("message 8");
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
test("opens a newly selected channel at the latest message", async () => {
|
|
1694
|
+
const controller = createController({
|
|
1695
|
+
messages: Array.from({ length: 8 }, (_, index) => makeMessage(index + 1)),
|
|
1696
|
+
});
|
|
1697
|
+
installServerChannels(controller, TEST_CHAT_CHANNELS.slice(0, 2));
|
|
1698
|
+
controller.refreshChannels = async () => {};
|
|
1699
|
+
controller.refreshChannelMessages = async () => {};
|
|
1700
|
+
|
|
1701
|
+
const equitiesMessages = Array.from({ length: 8 }, (_, index) => ({
|
|
1702
|
+
...makeMessage(index + 1),
|
|
1703
|
+
id: `o${index + 1}`,
|
|
1704
|
+
channelId: "equities",
|
|
1705
|
+
content: `equities message ${index + 1}`,
|
|
1706
|
+
}));
|
|
1707
|
+
const equitiesState = (controller as any).ensureChannelState("equities");
|
|
1708
|
+
equitiesState.messages = equitiesMessages;
|
|
1709
|
+
equitiesState.lastCursor = "o8";
|
|
1710
|
+
|
|
1711
|
+
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-chat-channel-scroll"));
|
|
1712
|
+
|
|
1713
|
+
function ChannelPane() {
|
|
1714
|
+
const [channelId, setChannelId] = useState("everyone");
|
|
1715
|
+
return (
|
|
1716
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
1717
|
+
<PluginRenderProvider pluginId="gloomberb-cloud" runtime={createTestPluginRuntime()}>
|
|
1718
|
+
<ChatContent
|
|
1719
|
+
controller={controller}
|
|
1720
|
+
width={90}
|
|
1721
|
+
height={13}
|
|
1722
|
+
focused
|
|
1723
|
+
channelId={channelId}
|
|
1724
|
+
onChannelChange={setChannelId}
|
|
1725
|
+
/>
|
|
1726
|
+
</PluginRenderProvider>
|
|
1727
|
+
</AppContext>
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
await act(async () => {
|
|
1732
|
+
testSetup = await testRender(<ChannelPane />, {
|
|
1733
|
+
width: 90,
|
|
1734
|
+
height: 13,
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
await flushFrame();
|
|
1739
|
+
expect(testSetup.captureCharFrame()).toContain("message 8");
|
|
1740
|
+
|
|
1741
|
+
await act(async () => {
|
|
1742
|
+
await testSetup!.mockMouse.scroll(24, 4, "up");
|
|
1743
|
+
await testSetup!.mockMouse.scroll(24, 4, "up");
|
|
1744
|
+
await testSetup!.mockMouse.scroll(24, 4, "up");
|
|
1745
|
+
await testSetup!.renderOnce();
|
|
1746
|
+
await testSetup!.renderOnce();
|
|
1747
|
+
});
|
|
1748
|
+
await flushFrame();
|
|
1749
|
+
expect(testSetup.captureCharFrame()).toContain("message 2");
|
|
1750
|
+
|
|
1751
|
+
const lines = testSetup.captureCharFrame().split("\n");
|
|
1752
|
+
const row = lines.findIndex((line) => line.includes("equities"));
|
|
1753
|
+
const col = lines[row]?.indexOf("equities") ?? -1;
|
|
1754
|
+
expect(row).toBeGreaterThanOrEqual(0);
|
|
1755
|
+
expect(col).toBeGreaterThanOrEqual(0);
|
|
1756
|
+
|
|
1757
|
+
await act(async () => {
|
|
1758
|
+
await testSetup!.mockMouse.click(col + 1, row);
|
|
1759
|
+
await testSetup!.renderOnce();
|
|
1509
1760
|
await testSetup!.renderOnce();
|
|
1510
1761
|
});
|
|
1511
1762
|
await flushFrame();
|
|
1512
1763
|
|
|
1513
|
-
const
|
|
1514
|
-
expect(
|
|
1515
|
-
expect(
|
|
1764
|
+
const channelFrame = testSetup.captureCharFrame();
|
|
1765
|
+
expect(channelFrame).toContain("equities message 8");
|
|
1766
|
+
expect(channelFrame).not.toContain("equities message 1");
|
|
1516
1767
|
});
|
|
1517
1768
|
|
|
1518
1769
|
test("keeps the bottom reachable after narrowing the chat pane", async () => {
|