Makelore 2.0 initial clean snapshot

This commit is contained in:
inman
2026-07-29 17:22:35 +08:00
commit b8ca3f8eea
694 changed files with 139782 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createProjectTemplateSnapshot } from '../../shared/project-template';
import { useProjectTemplatesStore } from '@/stores/project-templates';
const hostApiFetchMock = vi.fn();
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
ensureHostApiToken: vi.fn().mockResolvedValue('test-token'),
}));
describe('project template store', () => {
beforeEach(async () => {
vi.clearAllMocks();
const persistOptions = useProjectTemplatesStore.persist.getOptions();
await persistOptions.storage?.removeItem(persistOptions.name);
useProjectTemplatesStore.setState({
snapshotsByProjectId: {},
templateErrorsByProjectId: {},
templatesByProjectId: {},
});
});
it('returns missing results without caching snapshots or errors', async () => {
hostApiFetchMock.mockResolvedValue({ status: 'missing' });
const loaded = await useProjectTemplatesStore.getState().loadProjectTemplate('prj_missing');
expect(loaded).toEqual({ status: 'missing' });
expect(useProjectTemplatesStore.getState().snapshotsByProjectId.prj_missing).toBeUndefined();
expect(useProjectTemplatesStore.getState().templateErrorsByProjectId.prj_missing).toBeUndefined();
});
it('loads a valid snapshot into the renderer cache', async () => {
const snapshot = createProjectTemplateSnapshot('standard-dev', '2026-07-01T00:00:00.000Z');
hostApiFetchMock.mockResolvedValue({ status: 'valid', snapshot });
const loaded = await useProjectTemplatesStore.getState().loadProjectTemplate('prj_1');
expect(loaded).toEqual({ status: 'valid', snapshot });
expect(useProjectTemplatesStore.getState().snapshotsByProjectId.prj_1).toEqual(snapshot);
expect(useProjectTemplatesStore.getState().templateErrorsByProjectId.prj_1).toBeUndefined();
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/template?projectId=prj_1');
});
it('stores invalid snapshot errors and does not cache a snapshot', async () => {
hostApiFetchMock.mockResolvedValue({ status: 'invalid', error: '.niancode/project-template.json: invalid JSON' });
const loaded = await useProjectTemplatesStore.getState().loadProjectTemplate('prj_bad');
expect(loaded).toEqual({ status: 'invalid', error: '.niancode/project-template.json: invalid JSON' });
expect(useProjectTemplatesStore.getState().snapshotsByProjectId.prj_bad).toBeUndefined();
expect(useProjectTemplatesStore.getState().templateErrorsByProjectId.prj_bad).toContain('invalid JSON');
});
it('writes a snapshot through the Host API and caches the returned snapshot', async () => {
const snapshot = createProjectTemplateSnapshot('youth-ai-course', '2026-07-01T00:00:00.000Z');
hostApiFetchMock.mockResolvedValue({
success: true,
snapshot,
scaffold: { status: 'disabled' },
});
const written = await useProjectTemplatesStore.getState().writeProjectTemplate('prj_course', snapshot);
expect(written).toEqual({
snapshot,
scaffold: { status: 'disabled' },
});
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/template', {
method: 'PUT',
body: JSON.stringify({ projectId: 'prj_course', snapshot }),
});
expect(useProjectTemplatesStore.getState().snapshotsByProjectId.prj_course).toEqual(snapshot);
expect(useProjectTemplatesStore.getState().templateErrorsByProjectId.prj_course).toBeUndefined();
});
it('returns the scaffold result from the Host API while caching the returned snapshot', async () => {
const snapshot = createProjectTemplateSnapshot('web-mini-game-course', '2026-07-01T00:00:00.000Z');
hostApiFetchMock.mockResolvedValue({
success: true,
snapshot,
scaffold: {
status: 'generated',
files: ['package.json', 'src/main.ts'],
},
});
const written = await useProjectTemplatesStore.getState().writeProjectTemplate('prj_game', snapshot);
expect(written).toEqual({
snapshot,
scaffold: {
status: 'generated',
files: ['package.json', 'src/main.ts'],
},
});
expect(useProjectTemplatesStore.getState().snapshotsByProjectId.prj_game).toEqual(snapshot);
});
it('forwards scaffold write options through the store to the Host API layer', async () => {
const snapshot = createProjectTemplateSnapshot('web-mini-game-course', '2026-07-01T00:00:00.000Z');
hostApiFetchMock.mockResolvedValue({
success: true,
snapshot,
scaffold: { status: 'disabled' },
});
const written = await useProjectTemplatesStore.getState().writeProjectTemplate(
'prj_game',
snapshot,
{ scaffold: 'never' },
);
expect(written.scaffold).toEqual({ status: 'disabled' });
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/template', {
method: 'PUT',
body: JSON.stringify({
projectId: 'prj_game',
snapshot,
options: { scaffold: 'never' },
}),
});
});
it('does not allow legacy template ids to fabricate authoritative snapshots locally', () => {
const beforeState = useProjectTemplatesStore.getState();
expect(() => useProjectTemplatesStore.getState().setProjectTemplate('prj_legacy', 'standard-dev')).toThrow(
/deprecated/i,
);
expect(useProjectTemplatesStore.getState().snapshotsByProjectId).toEqual(beforeState.snapshotsByProjectId);
expect(useProjectTemplatesStore.getState().templateErrorsByProjectId).toEqual(beforeState.templateErrorsByProjectId);
expect(useProjectTemplatesStore.getState().templatesByProjectId).toEqual(beforeState.templatesByProjectId);
});
it('ignores legacy persisted template ids during rehydration', async () => {
const snapshot = createProjectTemplateSnapshot('standard-dev', '2026-07-01T00:00:00.000Z');
const persistOptions = useProjectTemplatesStore.persist.getOptions();
const mergedState = persistOptions.merge?.(
{
snapshotsByProjectId: { prj_snapshot: snapshot },
templateErrorsByProjectId: { prj_snapshot: 'stale error' },
templatesByProjectId: {
prj_snapshot: 'standard-dev',
prj_legacy_only: 'youth-ai-course',
},
},
{
...useProjectTemplatesStore.getState(),
snapshotsByProjectId: {},
templateErrorsByProjectId: { prj_dirty: 'dirty error' },
templatesByProjectId: { prj_dirty: 'youth-ai-course' },
},
);
expect(mergedState).toBeDefined();
expect(mergedState?.snapshotsByProjectId).toEqual({ prj_snapshot: snapshot });
expect(mergedState?.templateErrorsByProjectId).toEqual({});
expect(mergedState?.templatesByProjectId).toEqual({ prj_snapshot: 'standard-dev' });
});
it('clears cached snapshots and errors when removing a project template', () => {
const snapshot = createProjectTemplateSnapshot('standard-dev', '2026-07-01T00:00:00.000Z');
useProjectTemplatesStore.setState({
snapshotsByProjectId: { prj_remove: snapshot },
templateErrorsByProjectId: { prj_remove: 'bad snapshot' },
});
useProjectTemplatesStore.getState().removeProjectTemplate('prj_remove');
expect(useProjectTemplatesStore.getState().snapshotsByProjectId.prj_remove).toBeUndefined();
expect(useProjectTemplatesStore.getState().templateErrorsByProjectId.prj_remove).toBeUndefined();
});
});