mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-14 17:41:22 +08:00
Add comprehensive tests for CLI functionality and CodexLens compatibility
- Introduced tests for stale running fallback in CLI watch functionality to ensure proper handling of saved conversations. - Added compatibility tests for CodexLens CLI to verify index initialization despite compatibility conflicts. - Implemented tests for Smart Search MCP usage to validate default settings and path handling. - Created tests for UV Manager to ensure Python preference handling works as expected. - Added a detailed guide for CCW/Codex commands and skills, covering core commands, execution modes, and templates.
This commit is contained in:
63
ccw/frontend/src/stores/cliStreamStore.test.ts
Normal file
63
ccw/frontend/src/stores/cliStreamStore.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// ========================================
|
||||
// CLI Stream Store Tests
|
||||
// ========================================
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useCliStreamStore, selectActiveExecutionCount } from './cliStreamStore';
|
||||
|
||||
describe('cliStreamStore', () => {
|
||||
beforeEach(() => {
|
||||
useCliStreamStore.getState().resetState();
|
||||
});
|
||||
|
||||
it('removeExecution clears outputs and execution state together', () => {
|
||||
const store = useCliStreamStore.getState();
|
||||
|
||||
store.upsertExecution('exec-1', {
|
||||
tool: 'codex',
|
||||
mode: 'analysis',
|
||||
status: 'running',
|
||||
output: [],
|
||||
startTime: 1_741_400_000_000,
|
||||
});
|
||||
store.addOutput('exec-1', {
|
||||
type: 'stdout',
|
||||
content: 'hello',
|
||||
timestamp: 1_741_400_000_100,
|
||||
});
|
||||
|
||||
expect(useCliStreamStore.getState().outputs['exec-1']).toHaveLength(1);
|
||||
expect(useCliStreamStore.getState().executions['exec-1']).toBeDefined();
|
||||
|
||||
store.removeExecution('exec-1');
|
||||
|
||||
expect(useCliStreamStore.getState().outputs['exec-1']).toBeUndefined();
|
||||
expect(useCliStreamStore.getState().executions['exec-1']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('resetState clears execution badge state for workspace switches', () => {
|
||||
const store = useCliStreamStore.getState();
|
||||
|
||||
store.upsertExecution('exec-running', {
|
||||
tool: 'codex',
|
||||
mode: 'analysis',
|
||||
status: 'running',
|
||||
output: [],
|
||||
startTime: 1_741_401_000_000,
|
||||
});
|
||||
store.setCurrentExecution('exec-running');
|
||||
store.markExecutionClosedByUser('exec-running');
|
||||
|
||||
expect(selectActiveExecutionCount(useCliStreamStore.getState() as any)).toBe(1);
|
||||
expect(useCliStreamStore.getState().currentExecutionId).toBe('exec-running');
|
||||
|
||||
store.resetState();
|
||||
|
||||
const nextState = useCliStreamStore.getState();
|
||||
expect(selectActiveExecutionCount(nextState as any)).toBe(0);
|
||||
expect(nextState.currentExecutionId).toBeNull();
|
||||
expect(Object.keys(nextState.executions)).toEqual([]);
|
||||
expect(Object.keys(nextState.outputs)).toEqual([]);
|
||||
expect(nextState.userClosedExecutions.size).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -93,6 +93,7 @@ interface CliStreamState extends BlockCacheState {
|
||||
isExecutionClosedByUser: (executionId: string) => boolean;
|
||||
cleanupUserClosedExecutions: (serverIds: Set<string>) => void;
|
||||
setCurrentExecution: (executionId: string | null) => void;
|
||||
resetState: () => void;
|
||||
|
||||
// Block cache methods
|
||||
getBlocks: (executionId: string) => LogBlockData[];
|
||||
@@ -462,15 +463,18 @@ export const useCliStreamStore = create<CliStreamState>()(
|
||||
|
||||
removeExecution: (executionId: string) => {
|
||||
set((state) => {
|
||||
const newOutputs = { ...state.outputs };
|
||||
const newExecutions = { ...state.executions };
|
||||
const newBlocks = { ...state.blocks };
|
||||
const newLastUpdate = { ...state.lastUpdate };
|
||||
const newDeduplicationWindows = { ...state.deduplicationWindows };
|
||||
delete newOutputs[executionId];
|
||||
delete newExecutions[executionId];
|
||||
delete newBlocks[executionId];
|
||||
delete newLastUpdate[executionId];
|
||||
delete newDeduplicationWindows[executionId];
|
||||
return {
|
||||
outputs: newOutputs,
|
||||
executions: newExecutions,
|
||||
blocks: newBlocks,
|
||||
lastUpdate: newLastUpdate,
|
||||
@@ -513,6 +517,18 @@ export const useCliStreamStore = create<CliStreamState>()(
|
||||
set({ currentExecutionId: executionId }, false, 'cliStream/setCurrentExecution');
|
||||
},
|
||||
|
||||
resetState: () => {
|
||||
set({
|
||||
outputs: {},
|
||||
executions: {},
|
||||
currentExecutionId: null,
|
||||
userClosedExecutions: new Set<string>(),
|
||||
deduplicationWindows: {},
|
||||
blocks: {},
|
||||
lastUpdate: {},
|
||||
}, false, 'cliStream/resetState');
|
||||
},
|
||||
|
||||
// Block cache methods
|
||||
getBlocks: (executionId: string) => {
|
||||
const state = get();
|
||||
|
||||
46
ccw/frontend/src/stores/executionMonitorStore.test.ts
Normal file
46
ccw/frontend/src/stores/executionMonitorStore.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
// ========================================
|
||||
// Execution Monitor Store Tests
|
||||
// ========================================
|
||||
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
useExecutionMonitorStore,
|
||||
selectActiveExecutionCount,
|
||||
type ExecutionWSMessage,
|
||||
} from './executionMonitorStore';
|
||||
|
||||
describe('executionMonitorStore', () => {
|
||||
beforeEach(() => {
|
||||
useExecutionMonitorStore.getState().resetState();
|
||||
});
|
||||
|
||||
it('resetState clears workspace-scoped execution monitor state', () => {
|
||||
const store = useExecutionMonitorStore.getState();
|
||||
const startMessage: ExecutionWSMessage = {
|
||||
type: 'EXECUTION_STARTED',
|
||||
payload: {
|
||||
executionId: 'exec-running',
|
||||
flowId: 'flow-1',
|
||||
sessionKey: 'session-1',
|
||||
stepName: 'Workspace Flow',
|
||||
totalSteps: 3,
|
||||
timestamp: '2026-03-08T12:00:00.000Z',
|
||||
},
|
||||
};
|
||||
|
||||
store.handleExecutionMessage(startMessage);
|
||||
|
||||
const activeState = useExecutionMonitorStore.getState();
|
||||
expect(selectActiveExecutionCount(activeState as any)).toBe(1);
|
||||
expect(activeState.currentExecutionId).toBe('exec-running');
|
||||
expect(activeState.isPanelOpen).toBe(true);
|
||||
|
||||
store.resetState();
|
||||
|
||||
const nextState = useExecutionMonitorStore.getState();
|
||||
expect(selectActiveExecutionCount(nextState as any)).toBe(0);
|
||||
expect(nextState.activeExecutions).toEqual({});
|
||||
expect(nextState.currentExecutionId).toBeNull();
|
||||
expect(nextState.isPanelOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -81,6 +81,7 @@ interface ExecutionMonitorActions {
|
||||
setPanelOpen: (open: boolean) => void;
|
||||
clearExecution: (executionId: string) => void;
|
||||
clearAllExecutions: () => void;
|
||||
resetState: () => void;
|
||||
}
|
||||
|
||||
type ExecutionMonitorStore = ExecutionMonitorState & ExecutionMonitorActions;
|
||||
@@ -318,6 +319,10 @@ export const useExecutionMonitorStore = create<ExecutionMonitorStore>()(
|
||||
clearAllExecutions: () => {
|
||||
set({ activeExecutions: {}, currentExecutionId: null }, false, 'clearAllExecutions');
|
||||
},
|
||||
|
||||
resetState: () => {
|
||||
set({ ...initialState }, false, 'resetState');
|
||||
},
|
||||
}),
|
||||
{ name: 'ExecutionMonitorStore' }
|
||||
)
|
||||
|
||||
35
ccw/frontend/src/stores/terminalPanelStore.test.ts
Normal file
35
ccw/frontend/src/stores/terminalPanelStore.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// ========================================
|
||||
// Terminal Panel Store Tests
|
||||
// ========================================
|
||||
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { useTerminalPanelStore, selectTerminalCount } from './terminalPanelStore';
|
||||
|
||||
describe('terminalPanelStore', () => {
|
||||
beforeEach(() => {
|
||||
useTerminalPanelStore.getState().resetState();
|
||||
});
|
||||
|
||||
it('resetState clears workspace-scoped terminal tabs and selection', () => {
|
||||
const store = useTerminalPanelStore.getState();
|
||||
|
||||
store.openTerminal('session-a');
|
||||
store.addTerminal('session-b');
|
||||
store.setPanelView('queue');
|
||||
|
||||
const activeState = useTerminalPanelStore.getState();
|
||||
expect(selectTerminalCount(activeState as any)).toBe(2);
|
||||
expect(activeState.activeTerminalId).toBe('session-a');
|
||||
expect(activeState.panelView).toBe('queue');
|
||||
expect(activeState.isPanelOpen).toBe(true);
|
||||
|
||||
store.resetState();
|
||||
|
||||
const nextState = useTerminalPanelStore.getState();
|
||||
expect(selectTerminalCount(nextState as any)).toBe(0);
|
||||
expect(nextState.terminalOrder).toEqual([]);
|
||||
expect(nextState.activeTerminalId).toBeNull();
|
||||
expect(nextState.panelView).toBe('terminal');
|
||||
expect(nextState.isPanelOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,8 @@ export interface TerminalPanelActions {
|
||||
addTerminal: (sessionKey: string) => void;
|
||||
/** Remove a terminal from the order list and adjust active if needed */
|
||||
removeTerminal: (sessionKey: string) => void;
|
||||
/** Reset workspace-scoped terminal panel UI state */
|
||||
resetState: () => void;
|
||||
}
|
||||
|
||||
export type TerminalPanelStore = TerminalPanelState & TerminalPanelActions;
|
||||
@@ -153,6 +155,10 @@ export const useTerminalPanelStore = create<TerminalPanelStore>()(
|
||||
'removeTerminal'
|
||||
);
|
||||
},
|
||||
|
||||
resetState: () => {
|
||||
set({ ...initialState }, false, 'resetState');
|
||||
},
|
||||
}),
|
||||
{ name: 'TerminalPanelStore' }
|
||||
)
|
||||
|
||||
@@ -112,8 +112,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
sessionDataStore,
|
||||
},
|
||||
false,
|
||||
'setSessions'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -131,8 +130,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
[key]: session,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
'addSession'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -140,7 +138,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const key = sessionKey(sessionId);
|
||||
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
const session = state.sessionDataStore[key];
|
||||
if (!session) return state;
|
||||
|
||||
@@ -163,8 +161,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
};
|
||||
},
|
||||
false,
|
||||
'updateSession'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -172,7 +169,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const key = sessionKey(sessionId);
|
||||
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
const { [key]: removed, ...remainingStore } = state.sessionDataStore;
|
||||
|
||||
return {
|
||||
@@ -187,8 +184,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
};
|
||||
},
|
||||
false,
|
||||
'removeSession'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -196,7 +192,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const key = sessionKey(sessionId);
|
||||
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
const session = state.sessionDataStore[key];
|
||||
if (!session || session.location === 'archived') return state;
|
||||
|
||||
@@ -220,8 +216,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
};
|
||||
},
|
||||
false,
|
||||
'archiveSession'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -231,7 +226,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const key = sessionKey(sessionId);
|
||||
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
const session = state.sessionDataStore[key];
|
||||
if (!session) return state;
|
||||
|
||||
@@ -252,8 +247,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
};
|
||||
},
|
||||
false,
|
||||
'addTask'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -261,7 +255,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const key = sessionKey(sessionId);
|
||||
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
const session = state.sessionDataStore[key];
|
||||
if (!session?.tasks) return state;
|
||||
|
||||
@@ -284,8 +278,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
};
|
||||
},
|
||||
false,
|
||||
'updateTask'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -293,7 +286,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const key = sessionKey(sessionId);
|
||||
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
const session = state.sessionDataStore[key];
|
||||
if (!session?.tasks) return state;
|
||||
|
||||
@@ -310,8 +303,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
};
|
||||
},
|
||||
false,
|
||||
'removeTask'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -325,8 +317,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
[key]: session,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
'setLiteTaskSession'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -336,8 +327,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const { [key]: removed, ...remaining } = state.liteTaskDataStore;
|
||||
return { liteTaskDataStore: remaining };
|
||||
},
|
||||
false,
|
||||
'removeLiteTaskSession'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -351,8 +341,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
[key]: data,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
'setTaskJson'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -362,38 +351,36 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const { [key]: removed, ...remaining } = state.taskJsonStore;
|
||||
return { taskJsonStore: remaining };
|
||||
},
|
||||
false,
|
||||
'removeTaskJson'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
// ========== Active Session ==========
|
||||
|
||||
setActiveSessionId: (sessionId: string | null) => {
|
||||
set({ activeSessionId: sessionId }, false, 'setActiveSessionId');
|
||||
set({ activeSessionId: sessionId }, false);
|
||||
},
|
||||
|
||||
// ========== Project Path ==========
|
||||
|
||||
setProjectPath: (path: string) => {
|
||||
set({ projectPath: path }, false, 'setProjectPath');
|
||||
set({ projectPath: path }, false);
|
||||
},
|
||||
|
||||
addRecentPath: (path: string) => {
|
||||
set(
|
||||
(state) => {
|
||||
(state: WorkflowState) => {
|
||||
// Remove if exists, add to front
|
||||
const filtered = state.recentPaths.filter((p) => p !== path);
|
||||
const updated = [path, ...filtered].slice(0, 10); // Keep max 10
|
||||
return { recentPaths: updated };
|
||||
},
|
||||
false,
|
||||
'addRecentPath'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
setServerPlatform: (platform: 'win32' | 'darwin' | 'linux') => {
|
||||
set({ serverPlatform: platform }, false, 'setServerPlatform');
|
||||
set({ serverPlatform: platform }, false);
|
||||
},
|
||||
|
||||
// ========== Workspace Actions ==========
|
||||
@@ -418,8 +405,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
},
|
||||
sessionDataStore,
|
||||
},
|
||||
false,
|
||||
'switchWorkspace'
|
||||
false
|
||||
);
|
||||
|
||||
// Persist projectPath to localStorage manually
|
||||
@@ -434,16 +420,16 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
|
||||
removeRecentPath: async (path: string) => {
|
||||
const updatedPaths = await apiRemoveRecentPath(path);
|
||||
set({ recentPaths: updatedPaths }, false, 'removeRecentPath');
|
||||
set({ recentPaths: updatedPaths }, false);
|
||||
},
|
||||
|
||||
refreshRecentPaths: async () => {
|
||||
const paths = await fetchRecentPaths();
|
||||
set({ recentPaths: paths }, false, 'refreshRecentPaths');
|
||||
set({ recentPaths: paths }, false);
|
||||
},
|
||||
|
||||
registerQueryInvalidator: (callback: () => void) => {
|
||||
set({ _invalidateQueriesCallback: callback }, false, 'registerQueryInvalidator');
|
||||
set({ _invalidateQueriesCallback: callback }, false);
|
||||
},
|
||||
|
||||
// ========== Filters and Sorting ==========
|
||||
@@ -453,8 +439,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
(state) => ({
|
||||
filters: { ...state.filters, ...filters },
|
||||
}),
|
||||
false,
|
||||
'setFilters'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -463,13 +448,12 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
(state) => ({
|
||||
sorting: { ...state.sorting, ...sorting },
|
||||
}),
|
||||
false,
|
||||
'setSorting'
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
resetFilters: () => {
|
||||
set({ filters: defaultFilters, sorting: defaultSorting }, false, 'resetFilters');
|
||||
set({ filters: defaultFilters, sorting: defaultSorting }, false);
|
||||
},
|
||||
|
||||
// ========== Computed Selectors ==========
|
||||
|
||||
Reference in New Issue
Block a user