107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
import path from 'node:path';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
createProjectStorageFromStore,
|
|
createMemoryProjectStorage,
|
|
createProjectStore,
|
|
getProjectIdForPath,
|
|
} from '@electron/opencode/project-store';
|
|
|
|
function expectedNormalizedPath(input: string): string {
|
|
const normalized = path.normalize(path.resolve(input.trim()));
|
|
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
}
|
|
|
|
describe('opencode project store', () => {
|
|
it('normalizes equivalent paths to the same project id', () => {
|
|
const base = path.join('D:', 'repo', 'packages', 'ui');
|
|
const equivalent = path.join('D:', 'repo', 'packages', 'ui', '..', 'ui', '.');
|
|
|
|
const id = getProjectIdForPath(base);
|
|
|
|
expect(id).toMatch(/^prj_[0-9a-f]{16}$/);
|
|
expect(getProjectIdForPath(equivalent)).toBe(id);
|
|
});
|
|
|
|
it('opens the exact normalized folder without expanding to the git root', async () => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-05-12T01:02:03.000Z'));
|
|
|
|
try {
|
|
const store = createProjectStore(createMemoryProjectStorage());
|
|
const input = 'D:/repo/packages/ui';
|
|
|
|
const project = await store.openFolder(input);
|
|
|
|
expect(project.path).toBe(expectedNormalizedPath(input));
|
|
expect(project.path).not.toBe(expectedNormalizedPath('D:/repo'));
|
|
expect(project.name).toBe('ui');
|
|
await expect(store.getActiveProject()).resolves.toEqual(project);
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
|
|
it('keeps subfolders in the same repo separate and lists newest first', async () => {
|
|
vi.useFakeTimers();
|
|
|
|
try {
|
|
const store = createProjectStore(createMemoryProjectStorage());
|
|
|
|
vi.setSystemTime(new Date('2026-05-12T00:00:00.000Z'));
|
|
const api = await store.openFolder('D:/repo/packages/api');
|
|
|
|
vi.setSystemTime(new Date('2026-05-12T00:01:00.000Z'));
|
|
const ui = await store.openFolder('D:/repo/packages/ui');
|
|
|
|
vi.setSystemTime(new Date('2026-05-12T00:02:00.000Z'));
|
|
const reopenedApi = await store.openFolder('D:/repo/packages/api');
|
|
|
|
expect(reopenedApi.id).toBe(api.id);
|
|
expect(reopenedApi.createdAt).toBe(api.createdAt);
|
|
expect(reopenedApi.updatedAt).toBe('2026-05-12T00:02:00.000Z');
|
|
expect(reopenedApi.lastOpenedAt).toBe('2026-05-12T00:02:00.000Z');
|
|
|
|
const projects = await store.listProjects();
|
|
|
|
expect(ui.id).not.toBe(api.id);
|
|
expect(projects.map((project) => project.id)).toEqual([api.id, ui.id]);
|
|
expect(projects.map((project) => project.path)).toEqual([
|
|
expectedNormalizedPath('D:/repo/packages/api'),
|
|
expectedNormalizedPath('D:/repo/packages/ui'),
|
|
]);
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
|
|
it('removes a project from the list without touching other projects', async () => {
|
|
const store = createProjectStore(createMemoryProjectStorage());
|
|
const api = await store.openFolder('D:/repo/packages/api');
|
|
const ui = await store.openFolder('D:/repo/packages/ui');
|
|
|
|
await expect(store.getActiveProject()).resolves.toEqual(ui);
|
|
|
|
await store.removeProject(ui.id);
|
|
|
|
await expect(store.listProjects()).resolves.toEqual([api]);
|
|
await expect(store.getActiveProject()).resolves.toBeNull();
|
|
});
|
|
|
|
it('persists project data through a key-value backing store', async () => {
|
|
const backing = new Map<string, unknown>();
|
|
const storage = createProjectStorageFromStore({
|
|
get: (key) => backing.get(key),
|
|
set: (key, value) => backing.set(key, value),
|
|
});
|
|
const firstStore = createProjectStore(storage);
|
|
|
|
const project = await firstStore.openFolder('D:/repo/packages/ui');
|
|
const secondStore = createProjectStore(storage);
|
|
|
|
await expect(secondStore.getActiveProject()).resolves.toEqual(project);
|
|
await expect(secondStore.listProjects()).resolves.toEqual([project]);
|
|
});
|
|
});
|