/ tests / unit / workspace / transcendent.test.ts
transcendent.test.ts
  1  /**
  2   * Tests for transcendent workspace module
  3   */
  4  import { describe, it, expect } from 'vitest';
  5  import {
  6    getTranscendentWorkspaces,
  7    getNonTranscendentWorkspaces,
  8    countTranscendent,
  9    canBeTranscendent,
 10    validateTranscendentChange,
 11    suggestsTranscendent,
 12    formatWindowTitle,
 13    getTranscendentStats,
 14    sortTranscendentFirst,
 15    groupByTranscendent,
 16  } from '../../../src/lib/workspace/transcendent';
 17  import type { Workspace, ParentWorkspace, ChildWorkspace, StandaloneWorkspace } from '../../../src/lib/types';
 18  
 19  // Helper to create mock workspaces
 20  function createMockWorkspace(
 21    id: string,
 22    type: 'parent' | 'child' | 'standalone',
 23    isTranscendent: boolean,
 24    name = `Workspace ${id}`
 25  ): Workspace {
 26    const base = {
 27      id,
 28      name,
 29      isTranscendent,
 30      metadata: {
 31        created: new Date().toISOString(),
 32        lastModified: new Date().toISOString(),
 33        lastAccessed: new Date().toISOString(),
 34        switchCount: 0,
 35        totalTimeActive: 0,
 36      },
 37    };
 38  
 39    if (type === 'parent') {
 40      return { ...base, type: 'parent', parentId: null, children: [] } as ParentWorkspace;
 41    } else if (type === 'child') {
 42      return { ...base, type: 'child', parentId: 'parent-1', tabs: [] } as ChildWorkspace;
 43    } else {
 44      return { ...base, type: 'standalone', parentId: null, tabs: [] } as StandaloneWorkspace;
 45    }
 46  }
 47  
 48  describe('getTranscendentWorkspaces', () => {
 49    it('returns only transcendent workspaces', () => {
 50      const workspaces = [
 51        createMockWorkspace('1', 'standalone', true),
 52        createMockWorkspace('2', 'child', false),
 53        createMockWorkspace('3', 'standalone', true),
 54      ];
 55  
 56      const result = getTranscendentWorkspaces(workspaces);
 57  
 58      expect(result).toHaveLength(2);
 59      expect(result.every((ws) => ws.isTranscendent)).toBe(true);
 60    });
 61  
 62    it('returns empty array when no transcendent workspaces', () => {
 63      const workspaces = [
 64        createMockWorkspace('1', 'standalone', false),
 65        createMockWorkspace('2', 'parent', false),
 66      ];
 67  
 68      const result = getTranscendentWorkspaces(workspaces);
 69  
 70      expect(result).toHaveLength(0);
 71    });
 72  });
 73  
 74  describe('getNonTranscendentWorkspaces', () => {
 75    it('returns only non-transcendent workspaces', () => {
 76      const workspaces = [
 77        createMockWorkspace('1', 'standalone', true),
 78        createMockWorkspace('2', 'child', false),
 79        createMockWorkspace('3', 'parent', false),
 80      ];
 81  
 82      const result = getNonTranscendentWorkspaces(workspaces);
 83  
 84      expect(result).toHaveLength(2);
 85      expect(result.every((ws) => !ws.isTranscendent)).toBe(true);
 86    });
 87  });
 88  
 89  describe('countTranscendent', () => {
 90    it('counts transcendent workspaces', () => {
 91      const workspaces = [
 92        createMockWorkspace('1', 'standalone', true),
 93        createMockWorkspace('2', 'child', false),
 94        createMockWorkspace('3', 'standalone', true),
 95        createMockWorkspace('4', 'parent', false),
 96      ];
 97  
 98      expect(countTranscendent(workspaces)).toBe(2);
 99    });
100  
101    it('returns 0 for empty array', () => {
102      expect(countTranscendent([])).toBe(0);
103    });
104  });
105  
106  describe('canBeTranscendent', () => {
107    it('returns false for parent workspaces', () => {
108      const parent = createMockWorkspace('1', 'parent', false);
109      expect(canBeTranscendent(parent)).toBe(false);
110    });
111  
112    it('returns true for child workspaces', () => {
113      const child = createMockWorkspace('1', 'child', false);
114      expect(canBeTranscendent(child)).toBe(true);
115    });
116  
117    it('returns true for standalone workspaces', () => {
118      const standalone = createMockWorkspace('1', 'standalone', false);
119      expect(canBeTranscendent(standalone)).toBe(true);
120    });
121  });
122  
123  describe('validateTranscendentChange', () => {
124    it('rejects making parent workspace transcendent', () => {
125      const parent = createMockWorkspace('1', 'parent', false);
126      const result = validateTranscendentChange(parent, true);
127  
128      expect(result.valid).toBe(false);
129      expect(result.reason).toBeTruthy();
130    });
131  
132    it('allows making child workspace transcendent', () => {
133      const child = createMockWorkspace('1', 'child', false);
134      const result = validateTranscendentChange(child, true);
135  
136      expect(result.valid).toBe(true);
137    });
138  
139    it('allows making standalone workspace transcendent', () => {
140      const standalone = createMockWorkspace('1', 'standalone', false);
141      const result = validateTranscendentChange(standalone, true);
142  
143      expect(result.valid).toBe(true);
144    });
145  
146    it('allows removing transcendent status from any workspace', () => {
147      const parent = createMockWorkspace('1', 'parent', true);
148      const result = validateTranscendentChange(parent, false);
149  
150      expect(result.valid).toBe(true);
151    });
152  });
153  
154  describe('suggestsTranscendent', () => {
155    it('returns true for email-related names', () => {
156      expect(suggestsTranscendent('Email')).toBe(true);
157      expect(suggestsTranscendent('Gmail')).toBe(true); // Contains 'mail'
158      expect(suggestsTranscendent('My Email Workspace')).toBe(true);
159    });
160  
161    it('returns true for chat-related names', () => {
162      expect(suggestsTranscendent('Chat')).toBe(true);
163      expect(suggestsTranscendent('Slack')).toBe(true);
164      expect(suggestsTranscendent('Discord Server')).toBe(true);
165    });
166  
167    it('returns true for music-related names', () => {
168      expect(suggestsTranscendent('Music')).toBe(true);
169      expect(suggestsTranscendent('Spotify')).toBe(true);
170    });
171  
172    it('returns false for unrelated names', () => {
173      expect(suggestsTranscendent('Project Alpha')).toBe(false);
174      expect(suggestsTranscendent('Development')).toBe(false);
175      expect(suggestsTranscendent('Research')).toBe(false);
176    });
177  });
178  
179  describe('formatWindowTitle', () => {
180    it('includes infinity symbol for transcendent workspace', () => {
181      const ws = createMockWorkspace('1', 'standalone', true, 'My Workspace');
182      const title = formatWindowTitle(ws);
183  
184      expect(title).toContain('\u221E'); // Infinity symbol
185      expect(title).toContain('My Workspace');
186      expect(title).toContain('Mnemonic');
187    });
188  
189    it('excludes infinity symbol for non-transcendent workspace', () => {
190      const ws = createMockWorkspace('1', 'standalone', false, 'My Workspace');
191      const title = formatWindowTitle(ws);
192  
193      expect(title).not.toContain('\u221E');
194    });
195  
196    it('includes type indicator', () => {
197      const parent = createMockWorkspace('1', 'parent', false);
198      const child = createMockWorkspace('2', 'child', false);
199      const standalone = createMockWorkspace('3', 'standalone', false);
200  
201      expect(formatWindowTitle(parent)).toContain('[+]');
202      expect(formatWindowTitle(child)).toContain('[-]');
203      expect(formatWindowTitle(standalone)).toContain('[*]');
204    });
205  });
206  
207  describe('getTranscendentStats', () => {
208    it('returns correct statistics', () => {
209      const workspaces: Workspace[] = [
210        { ...createMockWorkspace('1', 'child', true), tabs: [{ url: 'a', title: 'a', pinned: false }] } as ChildWorkspace,
211        { ...createMockWorkspace('2', 'standalone', true), tabs: [{ url: 'b', title: 'b', pinned: false }, { url: 'c', title: 'c', pinned: false }] } as StandaloneWorkspace,
212        createMockWorkspace('3', 'parent', false),
213      ];
214  
215      const stats = getTranscendentStats(workspaces);
216  
217      expect(stats.total).toBe(2);
218      expect(stats.byType.child).toBe(1);
219      expect(stats.byType.standalone).toBe(1);
220      expect(stats.totalTabs).toBe(3);
221    });
222  });
223  
224  describe('sortTranscendentFirst', () => {
225    it('sorts transcendent workspaces before non-transcendent', () => {
226      const workspaces = [
227        createMockWorkspace('1', 'standalone', false, 'Zebra'),
228        createMockWorkspace('2', 'child', true, 'Alpha'),
229        createMockWorkspace('3', 'standalone', false, 'Beta'),
230      ];
231  
232      const sorted = sortTranscendentFirst(workspaces);
233  
234      expect(sorted[0].id).toBe('2'); // Transcendent first
235      expect(sorted[0].isTranscendent).toBe(true);
236    });
237  
238    it('maintains alphabetical order within groups', () => {
239      const workspaces = [
240        createMockWorkspace('1', 'standalone', true, 'Zebra'),
241        createMockWorkspace('2', 'child', true, 'Alpha'),
242        createMockWorkspace('3', 'standalone', false, 'Beta'),
243      ];
244  
245      const sorted = sortTranscendentFirst(workspaces);
246  
247      expect(sorted[0].name).toBe('Alpha');
248      expect(sorted[1].name).toBe('Zebra');
249      expect(sorted[2].name).toBe('Beta');
250    });
251  });
252  
253  describe('groupByTranscendent', () => {
254    it('groups workspaces by transcendent status', () => {
255      const workspaces = [
256        createMockWorkspace('1', 'standalone', true),
257        createMockWorkspace('2', 'child', false),
258        createMockWorkspace('3', 'standalone', true),
259        createMockWorkspace('4', 'parent', false),
260      ];
261  
262      const { transcendent, regular } = groupByTranscendent(workspaces);
263  
264      expect(transcendent).toHaveLength(2);
265      expect(regular).toHaveLength(2);
266      expect(transcendent.every((ws) => ws.isTranscendent)).toBe(true);
267      expect(regular.every((ws) => !ws.isTranscendent)).toBe(true);
268    });
269  });