refactor: add promoteDraftToAgent store action with tests
This commit is contained in:
@@ -411,6 +411,7 @@ function WorkspaceScreenContent({
|
|||||||
const openOrFocusTab = useWorkspaceTabsStore((state) => state.openOrFocusTab);
|
const openOrFocusTab = useWorkspaceTabsStore((state) => state.openOrFocusTab);
|
||||||
const focusTab = useWorkspaceTabsStore((state) => state.focusTab);
|
const focusTab = useWorkspaceTabsStore((state) => state.focusTab);
|
||||||
const closeWorkspaceTab = useWorkspaceTabsStore((state) => state.closeTab);
|
const closeWorkspaceTab = useWorkspaceTabsStore((state) => state.closeTab);
|
||||||
|
const promoteDraftToAgent = useWorkspaceTabsStore((state) => state.promoteDraftToAgent);
|
||||||
const reorderWorkspaceTabs = useWorkspaceTabsStore((state) => state.reorderTabs);
|
const reorderWorkspaceTabs = useWorkspaceTabsStore((state) => state.reorderTabs);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -971,15 +972,11 @@ function WorkspaceScreenContent({
|
|||||||
draftId={target.draftId}
|
draftId={target.draftId}
|
||||||
onCreated={(agentSnapshot) => {
|
onCreated={(agentSnapshot) => {
|
||||||
const tabId = activeTabId ?? target.draftId;
|
const tabId = activeTabId ?? target.draftId;
|
||||||
const nextAgentTabId = openOrFocusTab({
|
const nextAgentTabId = promoteDraftToAgent({
|
||||||
serverId: normalizedServerId,
|
serverId: normalizedServerId,
|
||||||
workspaceId: normalizedWorkspaceId,
|
workspaceId: normalizedWorkspaceId,
|
||||||
target: { kind: "agent", agentId: agentSnapshot.id },
|
draftTabId: tabId,
|
||||||
});
|
agentId: agentSnapshot.id,
|
||||||
closeWorkspaceTab({
|
|
||||||
serverId: normalizedServerId,
|
|
||||||
workspaceId: normalizedWorkspaceId,
|
|
||||||
tabId,
|
|
||||||
});
|
});
|
||||||
if (nextAgentTabId) {
|
if (nextAgentTabId) {
|
||||||
navigateToTabId(nextAgentTabId);
|
navigateToTabId(nextAgentTabId);
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("@react-native-async-storage/async-storage", () => {
|
||||||
|
const storage = new Map<string, string>();
|
||||||
|
return {
|
||||||
|
default: {
|
||||||
|
getItem: vi.fn(async (key: string) => storage.get(key) ?? null),
|
||||||
|
setItem: vi.fn(async (key: string, value: string) => {
|
||||||
|
storage.set(key, value);
|
||||||
|
}),
|
||||||
|
removeItem: vi.fn(async (key: string) => {
|
||||||
|
storage.delete(key);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { buildWorkspaceTabPersistenceKey, useWorkspaceTabsStore } from "@/stores/workspace-tabs-store";
|
||||||
|
|
||||||
|
const SERVER_ID = "server-1";
|
||||||
|
const WORKSPACE_ID = "/repo/worktree";
|
||||||
|
|
||||||
|
describe("workspace-tabs-store promoteDraftToAgent", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useWorkspaceTabsStore.setState({
|
||||||
|
uiTabsByWorkspace: {},
|
||||||
|
tabOrderByWorkspace: {},
|
||||||
|
focusedTabIdByWorkspace: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces draft tab id in order with agent tab id, preserves tab count, and removes draft UI tab", () => {
|
||||||
|
const draftTabId = "draft_123";
|
||||||
|
const agentId = "agent-1";
|
||||||
|
const expectedAgentTabId = `agent_${agentId}`;
|
||||||
|
const key = buildWorkspaceTabPersistenceKey({ serverId: SERVER_ID, workspaceId: WORKSPACE_ID });
|
||||||
|
expect(key).toBeTruthy();
|
||||||
|
const workspaceKey = key as string;
|
||||||
|
|
||||||
|
useWorkspaceTabsStore.getState().openDraftTab({
|
||||||
|
serverId: SERVER_ID,
|
||||||
|
workspaceId: WORKSPACE_ID,
|
||||||
|
draftId: draftTabId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const beforeOrder = useWorkspaceTabsStore.getState().tabOrderByWorkspace[workspaceKey] ?? [];
|
||||||
|
const promoted = useWorkspaceTabsStore.getState().promoteDraftToAgent({
|
||||||
|
serverId: SERVER_ID,
|
||||||
|
workspaceId: WORKSPACE_ID,
|
||||||
|
draftTabId,
|
||||||
|
agentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = useWorkspaceTabsStore.getState();
|
||||||
|
const afterOrder = state.tabOrderByWorkspace[workspaceKey] ?? [];
|
||||||
|
|
||||||
|
expect(promoted).toBe(expectedAgentTabId);
|
||||||
|
expect(afterOrder).toEqual([expectedAgentTabId]);
|
||||||
|
expect(afterOrder).toHaveLength(beforeOrder.length);
|
||||||
|
expect(state.uiTabsByWorkspace[workspaceKey]).toBeUndefined();
|
||||||
|
expect(state.focusedTabIdByWorkspace[workspaceKey]).toBe(expectedAgentTabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not create duplicate agent tab ids when agent tab already exists", () => {
|
||||||
|
const draftTabId = "draft_456";
|
||||||
|
const agentId = "agent-dup";
|
||||||
|
const expectedAgentTabId = `agent_${agentId}`;
|
||||||
|
const key = buildWorkspaceTabPersistenceKey({ serverId: SERVER_ID, workspaceId: WORKSPACE_ID });
|
||||||
|
expect(key).toBeTruthy();
|
||||||
|
const workspaceKey = key as string;
|
||||||
|
|
||||||
|
useWorkspaceTabsStore.getState().openDraftTab({
|
||||||
|
serverId: SERVER_ID,
|
||||||
|
workspaceId: WORKSPACE_ID,
|
||||||
|
draftId: draftTabId,
|
||||||
|
});
|
||||||
|
useWorkspaceTabsStore.getState().openOrFocusTab({
|
||||||
|
serverId: SERVER_ID,
|
||||||
|
workspaceId: WORKSPACE_ID,
|
||||||
|
target: { kind: "agent", agentId },
|
||||||
|
});
|
||||||
|
useWorkspaceTabsStore.getState().focusTab({
|
||||||
|
serverId: SERVER_ID,
|
||||||
|
workspaceId: WORKSPACE_ID,
|
||||||
|
tabId: draftTabId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const beforeOrder = useWorkspaceTabsStore.getState().tabOrderByWorkspace[workspaceKey] ?? [];
|
||||||
|
const promoted = useWorkspaceTabsStore.getState().promoteDraftToAgent({
|
||||||
|
serverId: SERVER_ID,
|
||||||
|
workspaceId: WORKSPACE_ID,
|
||||||
|
draftTabId,
|
||||||
|
agentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = useWorkspaceTabsStore.getState();
|
||||||
|
const afterOrder = state.tabOrderByWorkspace[workspaceKey] ?? [];
|
||||||
|
const duplicateCount = afterOrder.filter((tabId) => tabId === expectedAgentTabId).length;
|
||||||
|
|
||||||
|
expect(promoted).toBe(expectedAgentTabId);
|
||||||
|
expect(duplicateCount).toBe(1);
|
||||||
|
expect(afterOrder).toEqual([expectedAgentTabId]);
|
||||||
|
expect(afterOrder.length).toBeLessThanOrEqual(beforeOrder.length);
|
||||||
|
expect(state.focusedTabIdByWorkspace[workspaceKey]).toBe(expectedAgentTabId);
|
||||||
|
expect(state.uiTabsByWorkspace[workspaceKey]).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -114,6 +114,12 @@ type WorkspaceTabsState = {
|
|||||||
}) => string | null;
|
}) => string | null;
|
||||||
focusTab: (input: { serverId: string; workspaceId: string; tabId: string }) => void;
|
focusTab: (input: { serverId: string; workspaceId: string; tabId: string }) => void;
|
||||||
closeTab: (input: { serverId: string; workspaceId: string; tabId: string }) => void;
|
closeTab: (input: { serverId: string; workspaceId: string; tabId: string }) => void;
|
||||||
|
promoteDraftToAgent: (input: {
|
||||||
|
serverId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
draftTabId: string;
|
||||||
|
agentId: string;
|
||||||
|
}) => string | null;
|
||||||
reorderTabs: (input: { serverId: string; workspaceId: string; tabIds: string[] }) => void;
|
reorderTabs: (input: { serverId: string; workspaceId: string; tabIds: string[] }) => void;
|
||||||
getWorkspaceTabs: (input: { serverId: string; workspaceId: string }) => WorkspaceTab[];
|
getWorkspaceTabs: (input: { serverId: string; workspaceId: string }) => WorkspaceTab[];
|
||||||
};
|
};
|
||||||
@@ -266,6 +272,82 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
promoteDraftToAgent: ({ serverId, workspaceId, draftTabId, agentId }) => {
|
||||||
|
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
|
||||||
|
const normalizedDraftTabId = trimNonEmpty(draftTabId);
|
||||||
|
const normalizedAgentId = trimNonEmpty(agentId);
|
||||||
|
if (!key || !normalizedDraftTabId || !normalizedAgentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextTabId = `agent_${normalizedAgentId}`;
|
||||||
|
let promotedTabId: string | null = null;
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
const currentOrder = state.tabOrderByWorkspace[key] ?? [];
|
||||||
|
const currentTabs = state.uiTabsByWorkspace[key] ?? [];
|
||||||
|
const hasDraftInOrder = currentOrder.includes(normalizedDraftTabId);
|
||||||
|
const hasDraftInUiTabs = currentTabs.some((tab) => tab.tabId === normalizedDraftTabId);
|
||||||
|
if (!hasDraftInOrder && !hasDraftInUiTabs) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAgentTabInOrder = currentOrder.includes(nextTabId);
|
||||||
|
const nextOrder = hasAgentTabInOrder
|
||||||
|
? currentOrder.filter((tabId) => tabId !== normalizedDraftTabId)
|
||||||
|
: currentOrder
|
||||||
|
.map((tabId) => (tabId === normalizedDraftTabId ? nextTabId : tabId))
|
||||||
|
.filter((tabId, index, arr) => arr.indexOf(tabId) === index);
|
||||||
|
|
||||||
|
const nextTabs = currentTabs.filter((tab) => tab.tabId !== normalizedDraftTabId);
|
||||||
|
const nextUiTabsByWorkspace =
|
||||||
|
nextTabs.length === 0
|
||||||
|
? (() => {
|
||||||
|
const { [key]: _removed, ...rest } = state.uiTabsByWorkspace;
|
||||||
|
return rest;
|
||||||
|
})()
|
||||||
|
: nextTabs.length === currentTabs.length
|
||||||
|
? state.uiTabsByWorkspace
|
||||||
|
: { ...state.uiTabsByWorkspace, [key]: nextTabs };
|
||||||
|
|
||||||
|
const nextTabOrderByWorkspace =
|
||||||
|
nextOrder.length === 0
|
||||||
|
? (() => {
|
||||||
|
const { [key]: _removed, ...rest } = state.tabOrderByWorkspace;
|
||||||
|
return rest;
|
||||||
|
})()
|
||||||
|
: nextOrder.length === currentOrder.length &&
|
||||||
|
nextOrder.every((tabId, index) => tabId === currentOrder[index])
|
||||||
|
? state.tabOrderByWorkspace
|
||||||
|
: { ...state.tabOrderByWorkspace, [key]: nextOrder };
|
||||||
|
|
||||||
|
const currentFocused = trimNonEmpty(state.focusedTabIdByWorkspace[key]);
|
||||||
|
const nextFocused = !currentFocused || currentFocused === normalizedDraftTabId ? nextTabId : currentFocused;
|
||||||
|
const nextFocusedByWorkspace =
|
||||||
|
nextFocused === currentFocused
|
||||||
|
? state.focusedTabIdByWorkspace
|
||||||
|
: { ...state.focusedTabIdByWorkspace, [key]: nextFocused };
|
||||||
|
|
||||||
|
const tabsChanged = nextTabs.length !== currentTabs.length;
|
||||||
|
const orderChanged =
|
||||||
|
nextOrder.length !== currentOrder.length ||
|
||||||
|
nextOrder.some((tabId, index) => tabId !== currentOrder[index]);
|
||||||
|
const focusChanged = nextFocused !== currentFocused;
|
||||||
|
|
||||||
|
if (!tabsChanged && !orderChanged && !focusChanged) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
promotedTabId = nextTabId;
|
||||||
|
return {
|
||||||
|
uiTabsByWorkspace: nextUiTabsByWorkspace,
|
||||||
|
tabOrderByWorkspace: nextTabOrderByWorkspace,
|
||||||
|
focusedTabIdByWorkspace: nextFocusedByWorkspace,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return promotedTabId;
|
||||||
|
},
|
||||||
reorderTabs: ({ serverId, workspaceId, tabIds }) => {
|
reorderTabs: ({ serverId, workspaceId, tabIds }) => {
|
||||||
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
|
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
|
||||||
if (!key) {
|
if (!key) {
|
||||||
|
|||||||
Reference in New Issue
Block a user