Vendored Augani/openreel-video (MIT) into services/editor and wired it to the MAM. Editor runs as its own container on port 47435. Library assets pull in via ?asset=<uuid>; render exports route back via POST /api/v1/upload/simple. Sidebar Editor link on every page; Edit button on every preview modal. See services/editor/INTEGRATION.md for the patch map.
184 lines
7 KiB
TypeScript
184 lines
7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import { parseProject } from './services/project-schema';
|
||
import { migrateProject, CURRENT_VERSION } from './services/project-migration';
|
||
|
||
// ── App smoke tests ──────────────────────────────────────────────────────────
|
||
//
|
||
// These tests exercise the integration seam between the project schema,
|
||
// migration utilities, and the store to confirm the whole pipeline is wired up
|
||
// and importing correctly.
|
||
|
||
describe('OpenReel Image – baseline smoke tests', () => {
|
||
// Schema is importable.
|
||
it('project schema module is importable', () => {
|
||
expect(typeof parseProject).toBe('function');
|
||
});
|
||
|
||
// Migration is importable and exposes the current version constant.
|
||
it('migration module exposes CURRENT_VERSION', () => {
|
||
expect(typeof CURRENT_VERSION).toBe('number');
|
||
expect(CURRENT_VERSION).toBeGreaterThanOrEqual(1);
|
||
});
|
||
|
||
// A minimal valid project document passes schema validation.
|
||
it('validates a minimal valid project', () => {
|
||
const baseLayer = {
|
||
id: 'l1',
|
||
name: 'Layer',
|
||
type: 'text' as const,
|
||
visible: true,
|
||
locked: false,
|
||
transform: {
|
||
x: 0, y: 0, width: 200, height: 50, rotation: 0,
|
||
scaleX: 1, scaleY: 1, skewX: 0, skewY: 0, opacity: 1,
|
||
},
|
||
blendMode: { mode: 'normal' as const },
|
||
shadow: { enabled: false, color: 'rgba(0,0,0,0.5)', blur: 10, offsetX: 0, offsetY: 4 },
|
||
innerShadow: { enabled: false, color: 'rgba(0,0,0,0.5)', blur: 10, offsetX: 2, offsetY: 2 },
|
||
stroke: { enabled: false, color: '#000000', width: 1, style: 'solid' as const },
|
||
glow: { enabled: false, color: '#ffffff', blur: 20, intensity: 1 },
|
||
filters: {
|
||
brightness: 100, contrast: 100, saturation: 100, hue: 0, exposure: 0,
|
||
vibrance: 0, highlights: 0, shadows: 0, clarity: 0, blur: 0,
|
||
blurType: 'gaussian' as const, blurAngle: 0, sharpen: 0, vignette: 0,
|
||
grain: 0, sepia: 0, invert: 0,
|
||
},
|
||
parentId: null,
|
||
flipHorizontal: false,
|
||
flipVertical: false,
|
||
mask: null,
|
||
clippingMask: false,
|
||
levels: {
|
||
enabled: false,
|
||
master: { inputBlack: 0, inputWhite: 255, gamma: 1, outputBlack: 0, outputWhite: 255 },
|
||
red: { inputBlack: 0, inputWhite: 255, gamma: 1, outputBlack: 0, outputWhite: 255 },
|
||
green: { inputBlack: 0, inputWhite: 255, gamma: 1, outputBlack: 0, outputWhite: 255 },
|
||
blue: { inputBlack: 0, inputWhite: 255, gamma: 1, outputBlack: 0, outputWhite: 255 },
|
||
},
|
||
curves: {
|
||
enabled: false,
|
||
master: { points: [{ input: 0, output: 0 }, { input: 255, output: 255 }] },
|
||
red: { points: [{ input: 0, output: 0 }, { input: 255, output: 255 }] },
|
||
green: { points: [{ input: 0, output: 0 }, { input: 255, output: 255 }] },
|
||
blue: { points: [{ input: 0, output: 0 }, { input: 255, output: 255 }] },
|
||
},
|
||
colorBalance: {
|
||
enabled: false,
|
||
shadows: { cyanRed: 0, magentaGreen: 0, yellowBlue: 0 },
|
||
midtones: { cyanRed: 0, magentaGreen: 0, yellowBlue: 0 },
|
||
highlights: { cyanRed: 0, magentaGreen: 0, yellowBlue: 0 },
|
||
preserveLuminosity: true,
|
||
},
|
||
selectiveColor: {
|
||
enabled: false, method: 'relative' as const,
|
||
reds: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
yellows: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
greens: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
cyans: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
blues: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
magentas: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
whites: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
neutrals: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
blacks: { cyan: 0, magenta: 0, yellow: 0, black: 0 },
|
||
},
|
||
blackWhite: {
|
||
enabled: false, reds: 40, yellows: 60, greens: 40, cyans: 60, blues: 20,
|
||
magentas: 80, tintEnabled: false, tintHue: 35, tintSaturation: 25,
|
||
},
|
||
photoFilter: {
|
||
enabled: false, filter: 'warming-85' as const, color: '#ec8a00',
|
||
density: 25, preserveLuminosity: true,
|
||
},
|
||
channelMixer: {
|
||
enabled: false, monochrome: false,
|
||
red: { red: 100, green: 0, blue: 0, constant: 0 },
|
||
green: { red: 0, green: 100, blue: 0, constant: 0 },
|
||
blue: { red: 0, green: 0, blue: 100, constant: 0 },
|
||
},
|
||
gradientMap: {
|
||
enabled: false,
|
||
stops: [{ position: 0, color: '#000000' }, { position: 1, color: '#ffffff' }],
|
||
reverse: false, dither: false,
|
||
},
|
||
posterize: { enabled: false, levels: 4 },
|
||
threshold: { enabled: false, level: 128 },
|
||
content: 'Hello',
|
||
style: {
|
||
fontFamily: 'Inter', fontSize: 24, fontWeight: 400,
|
||
fontStyle: 'normal' as const, textDecoration: 'none' as const,
|
||
textAlign: 'left' as const, verticalAlign: 'top' as const,
|
||
lineHeight: 1.4, letterSpacing: 0, fillType: 'solid' as const,
|
||
color: '#ffffff', gradient: null, strokeColor: null, strokeWidth: 0,
|
||
backgroundColor: null, backgroundPadding: 8, backgroundRadius: 4,
|
||
textShadow: { enabled: false, color: 'rgba(0,0,0,0.5)', blur: 4, offsetX: 0, offsetY: 2 },
|
||
},
|
||
autoSize: true,
|
||
};
|
||
|
||
const validProject = {
|
||
id: 'p1',
|
||
name: 'Smoke Test',
|
||
createdAt: Date.now(),
|
||
updatedAt: Date.now(),
|
||
version: 1,
|
||
artboards: [
|
||
{
|
||
id: 'ab1',
|
||
name: 'Artboard 1',
|
||
size: { width: 1080, height: 1080 },
|
||
background: { type: 'color', color: '#ffffff' },
|
||
layerIds: ['l1'],
|
||
position: { x: 0, y: 0 },
|
||
},
|
||
],
|
||
layers: { l1: baseLayer },
|
||
assets: {},
|
||
activeArtboardId: 'ab1',
|
||
};
|
||
|
||
const result = parseProject(validProject);
|
||
expect(result.success).toBe(true);
|
||
});
|
||
|
||
// An invalid document is rejected.
|
||
it('rejects an invalid project document', () => {
|
||
const result = parseProject({ id: 42, broken: true });
|
||
expect(result.success).toBe(false);
|
||
});
|
||
|
||
// Migration promotes a v0 document to v1.
|
||
it('migrates a v0 project to v1', () => {
|
||
const v0 = {
|
||
id: 'old',
|
||
name: 'Legacy',
|
||
createdAt: 0,
|
||
updatedAt: 0,
|
||
artboards: [{ id: 'ab-old', name: 'Page 1' }],
|
||
layers: {},
|
||
assets: {},
|
||
};
|
||
|
||
const migrated = migrateProject(v0 as Record<string, unknown>);
|
||
expect(migrated.version).toBe(1);
|
||
expect(migrated.activeArtboardId).toBe('ab-old');
|
||
});
|
||
|
||
// A project that already has version 1 is returned unchanged.
|
||
it('does not re-migrate a current-version project', () => {
|
||
const v1 = {
|
||
id: 'current',
|
||
name: 'New',
|
||
createdAt: 0,
|
||
updatedAt: 0,
|
||
version: 1,
|
||
artboards: [],
|
||
layers: {},
|
||
assets: {},
|
||
activeArtboardId: null,
|
||
};
|
||
|
||
const migrated = migrateProject(v1 as Record<string, unknown>);
|
||
expect(migrated.version).toBe(1);
|
||
});
|
||
});
|
||
|