mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(web): queue chat sends * feat(web): render code comment directives * feat(web): add preview comments and manual edits * fix(web): polish shared chrome controls * fix(web): align queued send loading state * feat(web): open primary project artifacts * fix(web): keep queued sends and tests aligned * fix(web): restore docked comment tools layout * fix(web): align preview comment toolbar * fix(web): place local cli beside handoff * fix(web): move agent menu beside handoff * fix(web): make project instructions a direct header action * fix(web): compact handoff and toolbar labels * fix(web): clarify handoff menu and annotation label * fix(web): restore compact cursor handoff trigger * fix(web): align agent menu trigger with handoff * fix(web): add draw toolbar close action * fix(web): move inspect editing into edit mode * fix(web): avoid reserving comment sidebar in annotation mode * fix(web): float preview comments panel * fix(web): keep edit canvas full width * fix(web): polish preview annotation tools * fix(web): highlight active preview comments * fix(web): open comments panel after annotation save * fix(web): polish comment handoff controls * fix(web): remove palette preview tool * fix(web): simplify draw annotation toolbar * fix(web): restore queued tasks into composer * fix(web): restore queued send strip styling * fix(web): hide internal comment target ids * fix(web): align manual edit panel header * test(web): cover visual interaction contracts * fix(web): address PR feedback regressions * fix(web): preserve artifact chrome state * fix(daemon): restore project raw file routes --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> Co-authored-by: mrcfps <mrc@powerformer.com>
135 lines
4 KiB
TypeScript
135 lines
4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
artifactManifestNameFor,
|
|
createHtmlArtifactManifest,
|
|
inferLegacyManifest,
|
|
parseArtifactManifest,
|
|
} from '../../src/artifacts/manifest';
|
|
|
|
describe('parseArtifactManifest', () => {
|
|
it('returns null for malformed json', () => {
|
|
expect(parseArtifactManifest('{"version":1')).toBeNull();
|
|
});
|
|
|
|
it('returns null when required fields are missing', () => {
|
|
expect(parseArtifactManifest(JSON.stringify({ version: 1, kind: 'html' }))).toBeNull();
|
|
});
|
|
|
|
it('returns null for wrong version', () => {
|
|
const raw = JSON.stringify({
|
|
version: 2,
|
|
kind: 'html',
|
|
title: 'x',
|
|
entry: 'index.html',
|
|
renderer: 'html',
|
|
exports: ['html'],
|
|
});
|
|
expect(parseArtifactManifest(raw)).toBeNull();
|
|
});
|
|
|
|
it('defaults status to complete when missing', () => {
|
|
const raw = JSON.stringify({
|
|
version: 1,
|
|
kind: 'html',
|
|
title: 'x',
|
|
entry: 'index.html',
|
|
renderer: 'html',
|
|
exports: ['html'],
|
|
});
|
|
const out = parseArtifactManifest(raw);
|
|
expect(out?.status).toBe('complete');
|
|
});
|
|
|
|
it('preserves valid status when provided', () => {
|
|
const raw = JSON.stringify({
|
|
version: 1,
|
|
kind: 'html',
|
|
title: 'x',
|
|
entry: 'index.html',
|
|
renderer: 'html',
|
|
status: 'streaming',
|
|
exports: ['html'],
|
|
});
|
|
const out = parseArtifactManifest(raw);
|
|
expect(out?.status).toBe('streaming');
|
|
});
|
|
|
|
it('preserves primary file hints', () => {
|
|
const raw = JSON.stringify({
|
|
version: 1,
|
|
kind: 'html',
|
|
title: 'x',
|
|
entry: 'index.html',
|
|
renderer: 'html',
|
|
exports: ['html'],
|
|
primary: 'index.html',
|
|
});
|
|
const out = parseArtifactManifest(raw);
|
|
expect(out?.primary).toBe('index.html');
|
|
});
|
|
});
|
|
|
|
describe('inferLegacyManifest', () => {
|
|
it('infers markdown manifests for .md files', () => {
|
|
const out = inferLegacyManifest({ entry: 'README.md' });
|
|
expect(out?.kind).toBe('markdown-document');
|
|
expect(out?.renderer).toBe('markdown');
|
|
expect(out?.status).toBe('complete');
|
|
});
|
|
|
|
it('infers svg manifests for .svg files', () => {
|
|
const out = inferLegacyManifest({ entry: 'logo.svg' });
|
|
expect(out?.kind).toBe('svg');
|
|
expect(out?.renderer).toBe('svg');
|
|
expect(out?.status).toBe('complete');
|
|
});
|
|
|
|
it('returns null for non-artifact file types', () => {
|
|
expect(inferLegacyManifest({ entry: 'photo.png' })).toBeNull();
|
|
expect(inferLegacyManifest({ entry: 'archive.bin' })).toBeNull();
|
|
});
|
|
|
|
it('infers React component artifacts from JSX and TSX entries', () => {
|
|
expect(inferLegacyManifest({ entry: 'Card.jsx' })).toMatchObject({
|
|
kind: 'react-component',
|
|
renderer: 'react-component',
|
|
exports: ['jsx', 'html', 'zip'],
|
|
});
|
|
expect(inferLegacyManifest({ entry: 'Card.tsx' })).toMatchObject({
|
|
kind: 'react-component',
|
|
renderer: 'react-component',
|
|
exports: ['jsx', 'html', 'zip'],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('artifactManifestNameFor', () => {
|
|
it('handles names without extension', () => {
|
|
expect(artifactManifestNameFor('README')).toBe('README.artifact.json');
|
|
});
|
|
|
|
it('handles names with multiple dots', () => {
|
|
expect(artifactManifestNameFor('page.v2.final.html')).toBe('page.v2.final.html.artifact.json');
|
|
});
|
|
|
|
it('avoids collisions between different extensions', () => {
|
|
expect(artifactManifestNameFor('foo.html')).not.toBe(artifactManifestNameFor('foo.md'));
|
|
});
|
|
});
|
|
|
|
describe('createHtmlArtifactManifest', () => {
|
|
it('creates expected default html manifest shape', () => {
|
|
const out = createHtmlArtifactManifest({ entry: 'index.html', title: 'Landing' });
|
|
expect(out.version).toBe(1);
|
|
expect(out.kind).toBe('html');
|
|
expect(out.renderer).toBe('html');
|
|
expect(out.status).toBe('complete');
|
|
expect(out.exports).toEqual(['html', 'pdf', 'zip']);
|
|
expect(out.primary).toBe(true);
|
|
expect(out.entry).toBe('index.html');
|
|
expect(out.title).toBe('Landing');
|
|
expect(typeof out.createdAt).toBe('string');
|
|
expect(typeof out.updatedAt).toBe('string');
|
|
});
|
|
});
|