test(v3): add workspace store tests (24 tests)
Tests for loadGroups, setActiveGroup, setActiveTab, focusProject, resetWorkspace, derived state (activeGroup, activeProjects), and edge cases. All 138 vitest + 36 cargo tests pass.
This commit is contained in:
parent
ab79dac4b3
commit
a11e7f9d2c
1 changed files with 239 additions and 0 deletions
239
v2/src/lib/stores/workspace.test.ts
Normal file
239
v2/src/lib/stores/workspace.test.ts
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Mock groups-bridge before importing the workspace store
|
||||
function mockGroupsData() {
|
||||
return {
|
||||
version: 1,
|
||||
groups: [
|
||||
{
|
||||
id: 'g1',
|
||||
name: 'Group One',
|
||||
projects: [
|
||||
{ id: 'p1', name: 'Project 1', identifier: 'project-1', description: '', icon: '', cwd: '/tmp/p1', profile: 'default', enabled: true },
|
||||
{ id: 'p2', name: 'Project 2', identifier: 'project-2', description: '', icon: '', cwd: '/tmp/p2', profile: 'default', enabled: true },
|
||||
{ id: 'p3', name: 'Disabled', identifier: 'disabled', description: '', icon: '', cwd: '/tmp/p3', profile: 'default', enabled: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'g2',
|
||||
name: 'Group Two',
|
||||
projects: [
|
||||
{ id: 'p4', name: 'Project 4', identifier: 'project-4', description: '', icon: '', cwd: '/tmp/p4', profile: 'default', enabled: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
activeGroupId: 'g1',
|
||||
};
|
||||
}
|
||||
|
||||
vi.mock('../adapters/groups-bridge', () => ({
|
||||
loadGroups: vi.fn().mockImplementation(() => Promise.resolve(mockGroupsData())),
|
||||
saveGroups: vi.fn().mockResolvedValue(undefined),
|
||||
getCliGroup: vi.fn().mockResolvedValue(null),
|
||||
}));
|
||||
|
||||
import {
|
||||
getGroupsConfig,
|
||||
getActiveGroupId,
|
||||
getActiveTab,
|
||||
getActiveProjectId,
|
||||
getActiveGroup,
|
||||
getEnabledProjects,
|
||||
getAllGroups,
|
||||
setActiveTab,
|
||||
setActiveProject,
|
||||
switchGroup,
|
||||
getTerminalTabs,
|
||||
addTerminalTab,
|
||||
removeTerminalTab,
|
||||
loadWorkspace,
|
||||
addGroup,
|
||||
removeGroup,
|
||||
updateProject,
|
||||
addProject,
|
||||
removeProject,
|
||||
} from './workspace.svelte';
|
||||
|
||||
import { saveGroups, getCliGroup } from '../adapters/groups-bridge';
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
// Reset state by reloading
|
||||
await loadWorkspace();
|
||||
});
|
||||
|
||||
describe('workspace store', () => {
|
||||
describe('loadWorkspace', () => {
|
||||
it('loads groups config and sets active group', async () => {
|
||||
expect(getGroupsConfig()).not.toBeNull();
|
||||
expect(getActiveGroupId()).toBe('g1');
|
||||
});
|
||||
|
||||
it('auto-focuses first enabled project', async () => {
|
||||
expect(getActiveProjectId()).toBe('p1');
|
||||
});
|
||||
|
||||
it('accepts initialGroupId override', async () => {
|
||||
await loadWorkspace('g2');
|
||||
expect(getActiveGroupId()).toBe('g2');
|
||||
expect(getActiveProjectId()).toBe('p4');
|
||||
});
|
||||
|
||||
it('falls back to first group if target not found', async () => {
|
||||
await loadWorkspace('nonexistent');
|
||||
expect(getActiveGroupId()).toBe('g1');
|
||||
});
|
||||
|
||||
it('uses CLI --group flag when no initialGroupId given', async () => {
|
||||
vi.mocked(getCliGroup).mockResolvedValueOnce('Group Two');
|
||||
await loadWorkspace();
|
||||
expect(getActiveGroupId()).toBe('g2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getters', () => {
|
||||
it('getActiveGroup returns the active group config', () => {
|
||||
const group = getActiveGroup();
|
||||
expect(group).toBeDefined();
|
||||
expect(group!.id).toBe('g1');
|
||||
expect(group!.name).toBe('Group One');
|
||||
});
|
||||
|
||||
it('getEnabledProjects filters disabled projects', () => {
|
||||
const projects = getEnabledProjects();
|
||||
expect(projects).toHaveLength(2);
|
||||
expect(projects.map(p => p.id)).toEqual(['p1', 'p2']);
|
||||
});
|
||||
|
||||
it('getAllGroups returns all groups', () => {
|
||||
const groups = getAllGroups();
|
||||
expect(groups).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setters', () => {
|
||||
it('setActiveTab changes the active tab', () => {
|
||||
setActiveTab('docs');
|
||||
expect(getActiveTab()).toBe('docs');
|
||||
setActiveTab('sessions');
|
||||
expect(getActiveTab()).toBe('sessions');
|
||||
});
|
||||
|
||||
it('setActiveProject changes the active project', () => {
|
||||
setActiveProject('p2');
|
||||
expect(getActiveProjectId()).toBe('p2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('switchGroup', () => {
|
||||
it('switches to a different group and auto-focuses first project', async () => {
|
||||
await switchGroup('g2');
|
||||
expect(getActiveGroupId()).toBe('g2');
|
||||
expect(getActiveProjectId()).toBe('p4');
|
||||
});
|
||||
|
||||
it('clears terminal tabs on group switch', async () => {
|
||||
addTerminalTab('p1', { id: 't1', title: 'Shell', type: 'shell' });
|
||||
expect(getTerminalTabs('p1')).toHaveLength(1);
|
||||
|
||||
await switchGroup('g2');
|
||||
expect(getTerminalTabs('p1')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('no-ops when switching to current group', async () => {
|
||||
const projectBefore = getActiveProjectId();
|
||||
vi.mocked(saveGroups).mockClear();
|
||||
await switchGroup('g1');
|
||||
// State should remain unchanged
|
||||
expect(getActiveGroupId()).toBe('g1');
|
||||
expect(getActiveProjectId()).toBe(projectBefore);
|
||||
expect(saveGroups).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('persists active group', async () => {
|
||||
await switchGroup('g2');
|
||||
expect(saveGroups).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('terminal tabs', () => {
|
||||
it('adds and retrieves terminal tabs per project', () => {
|
||||
addTerminalTab('p1', { id: 't1', title: 'Shell 1', type: 'shell' });
|
||||
addTerminalTab('p1', { id: 't2', title: 'Agent', type: 'agent-terminal' });
|
||||
addTerminalTab('p2', { id: 't3', title: 'SSH', type: 'ssh', sshSessionId: 'ssh1' });
|
||||
|
||||
expect(getTerminalTabs('p1')).toHaveLength(2);
|
||||
expect(getTerminalTabs('p2')).toHaveLength(1);
|
||||
expect(getTerminalTabs('p2')[0].sshSessionId).toBe('ssh1');
|
||||
});
|
||||
|
||||
it('removes terminal tabs by id', () => {
|
||||
addTerminalTab('p1', { id: 't1', title: 'Shell', type: 'shell' });
|
||||
addTerminalTab('p1', { id: 't2', title: 'Agent', type: 'agent-terminal' });
|
||||
|
||||
removeTerminalTab('p1', 't1');
|
||||
expect(getTerminalTabs('p1')).toHaveLength(1);
|
||||
expect(getTerminalTabs('p1')[0].id).toBe('t2');
|
||||
});
|
||||
|
||||
it('returns empty array for unknown project', () => {
|
||||
expect(getTerminalTabs('unknown')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('group mutation', () => {
|
||||
it('addGroup adds a new group', () => {
|
||||
addGroup({ id: 'g3', name: 'New Group', projects: [] });
|
||||
expect(getAllGroups()).toHaveLength(3);
|
||||
expect(saveGroups).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('removeGroup removes the group and resets active if needed', () => {
|
||||
removeGroup('g1');
|
||||
expect(getAllGroups()).toHaveLength(1);
|
||||
expect(getActiveGroupId()).toBe('g2');
|
||||
});
|
||||
|
||||
it('removeGroup with non-active group keeps active unchanged', () => {
|
||||
removeGroup('g2');
|
||||
expect(getAllGroups()).toHaveLength(1);
|
||||
expect(getActiveGroupId()).toBe('g1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('project mutation', () => {
|
||||
it('updateProject updates project fields', () => {
|
||||
updateProject('g1', 'p1', { name: 'Renamed' });
|
||||
const group = getActiveGroup()!;
|
||||
expect(group.projects.find(p => p.id === 'p1')!.name).toBe('Renamed');
|
||||
expect(saveGroups).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('addProject adds a project to a group', () => {
|
||||
addProject('g1', {
|
||||
id: 'p5', name: 'New', identifier: 'new', description: '',
|
||||
icon: '', cwd: '/tmp', profile: 'default', enabled: true,
|
||||
});
|
||||
const group = getActiveGroup()!;
|
||||
expect(group.projects).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('addProject respects 5-project limit', () => {
|
||||
// g1 already has 3 projects, add 2 more to reach 5
|
||||
addProject('g1', { id: 'x1', name: 'X1', identifier: 'x1', description: '', icon: '', cwd: '/tmp', profile: 'default', enabled: true });
|
||||
addProject('g1', { id: 'x2', name: 'X2', identifier: 'x2', description: '', icon: '', cwd: '/tmp', profile: 'default', enabled: true });
|
||||
// This 6th should be rejected
|
||||
addProject('g1', { id: 'x3', name: 'X3', identifier: 'x3', description: '', icon: '', cwd: '/tmp', profile: 'default', enabled: true });
|
||||
const group = getActiveGroup()!;
|
||||
expect(group.projects).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('removeProject removes and clears activeProjectId if needed', () => {
|
||||
setActiveProject('p1');
|
||||
removeProject('g1', 'p1');
|
||||
expect(getActiveProjectId()).toBeNull();
|
||||
const group = getActiveGroup()!;
|
||||
expect(group.projects.find(p => p.id === 'p1')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue