feat(film): capture manifest + validation
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"viewport": { "width": 1920, "height": 1080, "deviceScaleFactor": 2 },
|
||||
"theme": "dark",
|
||||
"baseUrl": "http://localhost:5173",
|
||||
"captures": [
|
||||
{ "id": "storm-intel", "role": "owner", "route": "/owner/storm-intel", "waitMs": 2000 },
|
||||
{ "id": "territory-map", "role": "owner", "route": "/owner/maps", "waitMs": 2500 },
|
||||
{ "id": "pro-canvas", "role": "owner", "route": "/owner/pro-canvas", "waitMs": 1800 },
|
||||
{ "id": "lead-verification", "role": "owner", "route": "/lead-verification", "waitMs": 1800 },
|
||||
{ "id": "dispatch", "role": "owner", "route": "/owner/dispatch", "waitMs": 2500 },
|
||||
{ "id": "people-skills", "role": "owner", "route": "/owner/people", "waitMs": 1800 },
|
||||
{ "id": "kanban", "role": "owner", "route": "/owner/kanban", "waitMs": 1800 },
|
||||
{ "id": "settings-access", "role": "owner", "route": "/owner/settings", "waitMs": 1800 },
|
||||
{ "id": "projects", "role": "owner", "route": "/owner/projects", "waitMs": 1800 },
|
||||
{ "id": "estimates", "role": "owner", "route": "/owner/estimates", "waitMs": 1800 },
|
||||
{ "id": "sub-tasks-owner", "role": "owner", "route": "/owner/subcontractor-tasks", "waitMs": 1800 },
|
||||
{ "id": "owner-snapshot", "role": "owner", "route": "/owner/snapshot", "waitMs": 2000 },
|
||||
{ "id": "leaderboard", "role": "owner", "route": "/admin/leaderboard", "waitMs": 1800 },
|
||||
{ "id": "rep-leads", "role": "agentCody", "route": "/emp/fa/leads", "waitMs": 1800 },
|
||||
{ "id": "rep-dashboard", "role": "agentCody", "route": "/emp/fa/dashboard", "waitMs": 1800 },
|
||||
{ "id": "sub-dashboard", "role": "subCarlos", "route": "/subcontractor/dashboard", "waitMs": 1800 },
|
||||
{ "id": "sub-projects", "role": "subCarlos", "route": "/subcontractor/projects", "waitMs": 1800 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import manifest from "./captures.manifest.json";
|
||||
import { validateManifest, type Manifest } from "./captures";
|
||||
|
||||
describe("captures manifest", () => {
|
||||
it("passes validation", () => {
|
||||
expect(validateManifest(manifest as Manifest)).toEqual([]);
|
||||
});
|
||||
it("has unique ids", () => {
|
||||
const ids = (manifest as Manifest).captures.map((c) => c.id);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
});
|
||||
it("captures in dark theme", () => {
|
||||
expect((manifest as Manifest).theme).toBe("dark");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
export type CaptureRole = "owner" | "agentCody" | "subCarlos";
|
||||
|
||||
export interface Capture {
|
||||
id: string;
|
||||
role: CaptureRole;
|
||||
route: string;
|
||||
waitMs?: number;
|
||||
}
|
||||
|
||||
export interface Manifest {
|
||||
viewport: { width: number; height: number; deviceScaleFactor: number };
|
||||
theme: "dark" | "light";
|
||||
baseUrl: string;
|
||||
captures: Capture[];
|
||||
}
|
||||
|
||||
const VALID_ROLES: CaptureRole[] = ["owner", "agentCody", "subCarlos"];
|
||||
|
||||
export function validateManifest(m: Manifest): string[] {
|
||||
const errors: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
if (!m.baseUrl?.startsWith("http")) errors.push("baseUrl must be an http(s) URL");
|
||||
if (m.theme !== "dark" && m.theme !== "light") errors.push("theme must be dark|light");
|
||||
for (const c of m.captures) {
|
||||
if (!c.id) errors.push("capture missing id");
|
||||
if (seen.has(c.id)) errors.push(`duplicate id: ${c.id}`);
|
||||
seen.add(c.id);
|
||||
if (!c.route?.startsWith("/")) errors.push(`${c.id}: route must start with "/"`);
|
||||
if (!VALID_ROLES.includes(c.role)) errors.push(`${c.id}: unknown role "${c.role}"`);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
Reference in New Issue
Block a user