Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions apps/emdash-desktop/src/renderer/features/tabs/pane-store.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { observable, runInAction } from 'mobx';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { browserDiagnosticsStore } from '@renderer/features/browser/browser-diagnostics-store';
import { browserSessionStore } from '@renderer/features/browser/browser-session-store';
import { terminalRegistry } from '@renderer/features/tasks/stores/terminal-registry';
import { events } from '@renderer/lib/ipc';
import { browserOpenInNewTabChannel } from '@shared/events/browserEvents';

Expand All @@ -15,6 +17,11 @@ vi.mock('@renderer/lib/ipc', () => ({
browser: {
unregisterSession: vi.fn(),
},
ssh: {
getConnections: vi.fn(async () => []),
getConnectionState: vi.fn(async () => ({})),
getHealthStates: vi.fn(async () => ({})),
},
},
}));

Expand Down Expand Up @@ -51,6 +58,10 @@ vi.mock('@renderer/features/tasks/diff-view/diff-tab-item', () => ({
DiffTabDragPreview: () => null,
diffGroupSuffix: (group: string) => `(${group})`,
}));
vi.mock('@renderer/features/tasks/terminals/terminal-tab-item', () => ({
TerminalTabItem: () => null,
TerminalTabDragPreview: () => null,
}));
vi.mock('@renderer/features/tasks/conversations/conversation-title-utils', () => ({
formatConversationTitleForDisplay: (_providerId: unknown, title: unknown) =>
(title as string) ?? 'Conversation',
Expand All @@ -73,11 +84,43 @@ function createTabManager() {
return new PaneStore(taskTabView.registry, testCtx);
}

function terminalRegistryEntries(): Map<string, unknown> {
return (
terminalRegistry as unknown as {
entries: Map<string, unknown>;
}
).entries;
}

function setTerminalRegistry(ids: string[], renameTerminal = vi.fn()) {
const terminals = observable.map(
ids.map((id) => [
id,
{
data: {
id,
projectId: 'project-1',
taskId: 'task-1',
shellId: 'system',
name: id === 'terminal-1' ? 'Terminal 1' : 'Terminal 2',
},
},
])
);
terminalRegistryEntries().set('task-1', {
terminals,
sessions: observable.map(),
renameTerminal,
});
return { terminals, renameTerminal };
}

describe('PaneStore browser tabs', () => {
beforeEach(() => {
vi.clearAllMocks();
browserDiagnosticsStore.clear();
browserSessionStore.clear();
terminalRegistryEntries().delete('task-1');
});

it('opens browser tabs backed by the default browser profile session', () => {
Expand Down Expand Up @@ -200,4 +243,63 @@ describe('PaneStore browser tabs', () => {
(manager.resolvedTabs[1] as ResolvedTab<BrowserResolvedData> | undefined)?.session.currentUrl
).toBe('https://target.example/path');
});

it('opens terminal tabs backed by task terminal records', () => {
setTerminalRegistry(['terminal-1']);
const manager = createTabManager();

manager.open('terminal', { terminalId: 'terminal-1' });

expect(manager.resolvedTabs[0]).toMatchObject({
kind: 'terminal',
terminalId: 'terminal-1',
isActive: true,
});
expect(manager.snapshot.tabs).toEqual([
expect.objectContaining({
kind: 'terminal',
terminalId: 'terminal-1',
isPreview: false,
}),
]);
});

it('restores terminal tab descriptors through tab manager state', () => {
setTerminalRegistry(['terminal-1']);
const manager = createTabManager();

manager.restoreSnapshot({
tabs: [
{
kind: 'terminal',
tabId: 'tab-terminal-1',
terminalId: 'terminal-1',
isPreview: false,
},
],
activeTabId: 'tab-terminal-1',
});

expect(manager.resolvedTabs).toEqual([
expect.objectContaining({
kind: 'terminal',
tabId: 'tab-terminal-1',
terminalId: 'terminal-1',
isActive: true,
}),
]);
});

it('closes terminal tabs when the backing terminal is deleted', () => {
const { terminals } = setTerminalRegistry(['terminal-1']);
const manager = createTabManager();
manager.open('terminal', { terminalId: 'terminal-1' });

runInAction(() => {
terminals.delete('terminal-1');
});

expect(manager.resolvedTabs).toEqual([]);
expect(manager.tabOrder).toEqual([]);
});
});
47 changes: 46 additions & 1 deletion apps/emdash-desktop/src/renderer/features/tasks/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ describe('createTaskCommandProvider', () => {
isSidebarCollapsed: false,
isTerminalDrawerOpen: false,
openNewTerminal: vi.fn(),
openNewTerminalTab: vi.fn(),
setFocusedRegion: vi.fn(),
setSidebarCollapsed: vi.fn(),
setSidebarTab: vi.fn(),
Expand Down Expand Up @@ -181,12 +182,43 @@ describe('createTaskCommandProvider', () => {
});

const modalOptions = mocks.showModal.mock.calls[0][1];
modalOptions.onSuccess({ conversationId: 'conversation-1' });
modalOptions.onSuccess({
conversationId: 'conversation-1',
openBrowserTab: true,
openTerminalTab: false,
});

expect(taskView.paneLayout.openInRightSplit).toHaveBeenCalledWith('conversation', {
conversationId: 'conversation-1',
preview: false,
});
expect(taskView.paneLayout.open).toHaveBeenCalledWith('browser', {});
expect(taskView.setFocusedRegion).toHaveBeenCalledWith('main');
});

it('opens requested browser and terminal tabs from the create conversation command modal result', () => {
const provider = createTaskCommandProvider('project-1', 'task-1');

const command = provider
.getCommands()
.find((candidate) => candidate.id === 'task.newConversation');
const taskView = mocks.getTaskView.mock.results.at(-1)?.value ?? mocks.getTaskView();

command?.execute();

const modalOptions = mocks.showModal.mock.calls[0][1];
modalOptions.onSuccess({
conversationId: 'conversation-1',
openBrowserTab: true,
openTerminalTab: true,
});

expect(taskView.paneLayout.open).toHaveBeenCalledWith('conversation', {
conversationId: 'conversation-1',
preview: false,
});
expect(taskView.paneLayout.open).toHaveBeenCalledWith('browser', {});
expect(taskView.openNewTerminalTab).toHaveBeenCalledTimes(1);
expect(taskView.setFocusedRegion).toHaveBeenCalledWith('main');
});

Expand Down Expand Up @@ -286,6 +318,19 @@ describe('createTaskCommandProvider', () => {
expect(taskView.setTerminalDrawerOpen).not.toHaveBeenCalled();
});

it('creates a terminal task tab from the new terminal command', () => {
const provider = createTaskCommandProvider('project-1', 'task-1');

const command = provider.getCommands().find((candidate) => candidate.id === 'task.newTerminal');
const taskView = mocks.getTaskView.mock.results.at(-1)?.value ?? mocks.getTaskView();

command?.execute();

expect(taskView.openNewTerminalTab).toHaveBeenCalledTimes(1);
expect(taskView.openNewTerminalTab).toHaveBeenCalledWith();
expect(taskView.openNewTerminal).not.toHaveBeenCalled();
});

it('navigates to the next visible task across project boundaries', () => {
mocks.visibleTaskEntries = [
{ projectId: 'project-1', taskId: 'task-1' },
Expand Down
10 changes: 7 additions & 3 deletions apps/emdash-desktop/src/renderer/features/tasks/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ export function createTaskCommandProvider(projectId: string, taskId: string): Co
showModal('createConversationModal', {
projectId,
taskId,
onSuccess: ({ conversationId }) => {
onSuccess: ({ conversationId, openBrowserTab, openTerminalTab }) => {
taskView?.paneLayout.open('conversation', { conversationId, preview: false });
if (openBrowserTab) taskView?.paneLayout.open('browser', {});
if (openTerminalTab) void taskView?.openNewTerminalTab();
taskView?.setFocusedRegion('main');
},
});
Expand All @@ -106,11 +108,13 @@ export function createTaskCommandProvider(projectId: string, taskId: string): Co
showModal('createConversationModal', {
projectId,
taskId,
onSuccess: ({ conversationId }) => {
onSuccess: ({ conversationId, openBrowserTab, openTerminalTab }) => {
taskView?.paneLayout.openInRightSplit('conversation', {
conversationId,
preview: false,
});
if (openBrowserTab) taskView?.paneLayout.open('browser', {});
if (openTerminalTab) void taskView?.openNewTerminalTab();
taskView?.setFocusedRegion('main');
},
});
Expand Down Expand Up @@ -201,7 +205,7 @@ export function createTaskCommandProvider(projectId: string, taskId: string): Co
shortcutKey: newTerminalDef.shortcutKey,
group: newTerminalDef.group,
execute() {
void taskView?.openNewTerminal();
void taskView?.openNewTerminalTab();
},
},
{
Expand Down
Loading
Loading