import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import * as fs from 'node:fs/promises' import * as os from 'node:os' import * as path from 'node:path' import { handleDesktopUiApi } from '../api/desktop-ui.js' import { DesktopUiPreferencesService } from '../services/desktopUiPreferencesService.js' let tmpDir: string let originalConfigDir: string | undefined async function setup() { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'desktop-ui-preferences-')) originalConfigDir = process.env.CLAUDE_CONFIG_DIR process.env.CLAUDE_CONFIG_DIR = tmpDir } async function teardown() { if (originalConfigDir !== undefined) { process.env.CLAUDE_CONFIG_DIR = originalConfigDir } else { delete process.env.CLAUDE_CONFIG_DIR } await fs.rm(tmpDir, { recursive: true, force: true }) } function makeRequest( method: string, urlStr: string, body?: Record, ): { req: Request; url: URL; segments: string[] } { const url = new URL(urlStr, 'http://localhost:3456') const init: RequestInit = { method } if (body !== undefined) { init.headers = { 'Content-Type': 'application/json' } init.body = JSON.stringify(body) } const req = new Request(url.toString(), init) const segments = url.pathname.split('/').filter(Boolean) return { req, url, segments } } async function readDesktopUiFile(): Promise> { const raw = await fs.readFile(path.join(tmpDir, 'cc-haha', 'desktop-ui.json'), 'utf-8') return JSON.parse(raw) as Record } describe('DesktopUiPreferencesService', () => { beforeEach(setup) afterEach(teardown) test('returns defaults when desktop-ui.json does not exist', async () => { const service = new DesktopUiPreferencesService() const result = await service.readPreferences() expect(result.exists).toBe(false) expect(result.preferences).toEqual({ schemaVersion: 1, sidebar: { projectOrder: [], pinnedProjects: [], hiddenProjects: [], projectOrganization: 'recentProject', projectSortBy: 'updatedAt', }, }) }) test('normalizes old schema files and preserves unknown fields when updating sidebar preferences', async () => { await fs.mkdir(path.join(tmpDir, 'cc-haha'), { recursive: true }) await fs.writeFile( path.join(tmpDir, 'cc-haha', 'desktop-ui.json'), JSON.stringify({ futureField: { keep: true }, sidebar: { projectOrder: ['/workspace/alpha', 42, '/workspace/alpha', '/workspace/beta'], pinnedProjects: ['/workspace/beta'], hiddenProjects: [null, '/workspace/gamma'], }, }), 'utf-8', ) const service = new DesktopUiPreferencesService() const before = await service.readPreferences() const after = await service.updateSidebarPreferences({ projectOrder: ['/workspace/gamma'], pinnedProjects: [], hiddenProjects: ['/workspace/beta'], }) expect(before.exists).toBe(true) expect(before.preferences).toEqual({ schemaVersion: 1, futureField: { keep: true }, sidebar: { projectOrder: ['/workspace/alpha', '/workspace/beta'], pinnedProjects: ['/workspace/beta'], hiddenProjects: ['/workspace/gamma'], projectOrganization: 'recentProject', projectSortBy: 'updatedAt', }, }) expect(after).toEqual({ schemaVersion: 1, futureField: { keep: true }, sidebar: { projectOrder: ['/workspace/gamma'], pinnedProjects: [], hiddenProjects: ['/workspace/beta'], projectOrganization: 'recentProject', projectSortBy: 'updatedAt', }, }) expect(await readDesktopUiFile()).toEqual(after) }) test('quarantines corrupt desktop-ui.json and reports defaults as missing', async () => { await fs.mkdir(path.join(tmpDir, 'cc-haha'), { recursive: true }) await fs.writeFile(path.join(tmpDir, 'cc-haha', 'desktop-ui.json'), '{bad json', 'utf-8') const service = new DesktopUiPreferencesService() const result = await service.readPreferences() const files = await fs.readdir(path.join(tmpDir, 'cc-haha')) expect(result.exists).toBe(false) expect(result.preferences.sidebar.hiddenProjects).toEqual([]) expect(files.some((name) => name.startsWith('desktop-ui.json.invalid-'))).toBe(true) }) }) describe('desktop UI preferences API', () => { beforeEach(setup) afterEach(teardown) test('persists sidebar preferences under cc-haha desktop-ui.json', async () => { const putReq = makeRequest('PUT', '/api/desktop-ui/preferences/sidebar', { projectOrder: ['/workspace/beta', '/workspace/alpha'], pinnedProjects: ['/workspace/beta'], hiddenProjects: ['/workspace/old'], projectOrganization: 'project', projectSortBy: 'createdAt', }) const putRes = await handleDesktopUiApi(putReq.req, putReq.url, putReq.segments) const putBody = await putRes.json() as Record expect(putRes.status).toBe(200) expect(putBody).toEqual({ ok: true, preferences: { schemaVersion: 1, sidebar: { projectOrder: ['/workspace/beta', '/workspace/alpha'], pinnedProjects: ['/workspace/beta'], hiddenProjects: ['/workspace/old'], projectOrganization: 'project', projectSortBy: 'createdAt', }, }, }) const getReq = makeRequest('GET', '/api/desktop-ui/preferences') const getRes = await handleDesktopUiApi(getReq.req, getReq.url, getReq.segments) const getBody = await getRes.json() as Record expect(getRes.status).toBe(200) expect(getBody).toEqual({ exists: true, preferences: { schemaVersion: 1, sidebar: { projectOrder: ['/workspace/beta', '/workspace/alpha'], pinnedProjects: ['/workspace/beta'], hiddenProjects: ['/workspace/old'], projectOrganization: 'project', projectSortBy: 'createdAt', }, }, }) }) })