mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
test: cli and studio-server tests remove the temp folders they create (#4566)
* test: cli and studio-server tests remove the temp folders they create * test(cli): each temp folder is removed where it is made, even when a test fails * test(cli): the one-liner helper relies on its project's own cleanup
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { describe, expect, it, onTestFinished, vi } from "vitest";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { TimeoutError } from "puppeteer-core";
|
||||
@@ -9,6 +9,7 @@ import { captureScrollScreenshots } from "./screenshotCapture.js";
|
||||
describe("captureScrollScreenshots degradation", () => {
|
||||
it("rethrows protocol evaluate timeouts for the caller warning path", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-scroll-degrade-"));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
const page = {
|
||||
evaluate: vi.fn(async () => {
|
||||
throw new TimeoutError(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { existsSync, mkdtempSync, readFileSync } from "node:fs";
|
||||
import { describe, expect, it, onTestFinished, vi } from "vitest";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Page } from "puppeteer-core";
|
||||
@@ -10,6 +10,12 @@ import {
|
||||
pngHeight,
|
||||
} from "./screenshotCapture.js";
|
||||
|
||||
function tempDir(prefix: string): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), prefix));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
return dir;
|
||||
}
|
||||
|
||||
// A real 1920x800 PNG header, so the produced-height guard sees something valid.
|
||||
function pngBuffer(height: number, width = 1920): Buffer {
|
||||
const buf = Buffer.alloc(24);
|
||||
@@ -38,7 +44,7 @@ function fakePage(
|
||||
|
||||
describe("captureFullPagePlate — the scroll shot's plate", () => {
|
||||
it("writes one full-page png and returns its relative path", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
const { page, screenshot } = fakePage({ docHeight: 10962, plateHeight: 10962 });
|
||||
|
||||
const out = await captureFullPagePlate(page, dir);
|
||||
@@ -49,7 +55,7 @@ describe("captureFullPagePlate — the scroll shot's plate", () => {
|
||||
});
|
||||
|
||||
it("stays 1x: it never touches the viewport's deviceScaleFactor", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
const setViewport = vi.fn(async () => undefined);
|
||||
const { page } = fakePage({}, { setViewport });
|
||||
|
||||
@@ -60,7 +66,7 @@ describe("captureFullPagePlate — the scroll shot's plate", () => {
|
||||
});
|
||||
|
||||
it("skips a page taller than Chrome can capture, instead of writing a clipped plate", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
const { page, screenshot } = fakePage({ docHeight: MAX_PLATE_HEIGHT_PX + 1 });
|
||||
|
||||
const out = await captureFullPagePlate(page, dir);
|
||||
@@ -71,7 +77,7 @@ describe("captureFullPagePlate — the scroll shot's plate", () => {
|
||||
});
|
||||
|
||||
it("neutralises sticky/fixed chrome for the shot and restores it afterwards", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
const { page, evaluate, screenshot } = fakePage();
|
||||
|
||||
await captureFullPagePlate(page, dir);
|
||||
@@ -96,7 +102,7 @@ describe("captureFullPagePlate — the scroll shot's plate", () => {
|
||||
});
|
||||
|
||||
it("restores the page even when the screenshot throws", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
const screenshot = vi.fn(async (_opts?: unknown) => {
|
||||
throw new Error("capture failed");
|
||||
});
|
||||
@@ -111,7 +117,7 @@ describe("captureFullPagePlate — the scroll shot's plate", () => {
|
||||
|
||||
describe("captureScrollScreenshots — capture budget", () => {
|
||||
it("does not begin page work when the post-navigation budget is exhausted", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-scroll-budget-"));
|
||||
const dir = tempDir("hf-scroll-budget-");
|
||||
const evaluate = vi.fn(async () => 1080);
|
||||
const screenshot = vi.fn(async () => pngBuffer(1080));
|
||||
const page = { evaluate, screenshot } as unknown as Page;
|
||||
@@ -126,7 +132,7 @@ describe("captureScrollScreenshots — capture budget", () => {
|
||||
it("re-checks the budget after settling and before each viewport screenshot", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0);
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-scroll-expiring-budget-"));
|
||||
const dir = tempDir("hf-scroll-expiring-budget-");
|
||||
const evaluate = vi.fn(async (expression: unknown) => {
|
||||
const source = String(expression);
|
||||
if (source.includes("Math.max(document.body.scrollHeight")) return 1080;
|
||||
@@ -155,7 +161,7 @@ describe("captureFullPagePlate — capture budget", () => {
|
||||
it("re-checks the budget immediately before the full-page screenshot", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0);
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-expiring-budget-"));
|
||||
const dir = tempDir("hf-plate-expiring-budget-");
|
||||
const screenshot = vi.fn(async () => pngBuffer(8000));
|
||||
const evaluate = vi.fn(async (expression: unknown) => {
|
||||
if (String(expression).includes("scrollHeight")) {
|
||||
@@ -181,7 +187,7 @@ describe("captureFullPagePlate — capture budget", () => {
|
||||
|
||||
describe("captureFullPagePlate — guards against a silently clipped plate", () => {
|
||||
it("measures the height itself, after lazy content has grown the page", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
// A page that measured 9000 before scrolling but is 20000 once lazy images land: the
|
||||
// pre-scroll number would have passed the guard and emitted a clipped plate.
|
||||
const { page, screenshot } = fakePage({ docHeight: 20000 });
|
||||
@@ -191,7 +197,7 @@ describe("captureFullPagePlate — guards against a silently clipped plate", ()
|
||||
});
|
||||
|
||||
it("discards a plate Chrome clipped, even when the measurement passed", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
// Measurement said 16000, but the capture itself triggered more loading and came back
|
||||
// over the cap. Emitting it would be undetectable downstream.
|
||||
const { page } = fakePage({ docHeight: 16000, plateHeight: MAX_PLATE_HEIGHT_PX + 500 });
|
||||
@@ -201,7 +207,7 @@ describe("captureFullPagePlate — guards against a silently clipped plate", ()
|
||||
});
|
||||
|
||||
it("survives a restore that throws — the real error is what propagates", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
let call = 0;
|
||||
const evaluate = vi.fn(async (script?: unknown) => {
|
||||
call++;
|
||||
@@ -233,7 +239,7 @@ describe("pngHeight", () => {
|
||||
|
||||
describe("captureFullPagePlate — the guard sees the post-neutralisation page (Magi's case)", () => {
|
||||
it("skips when the initial height is under the cap but the final height is over it", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-plate-"));
|
||||
const dir = tempDir("hf-plate-");
|
||||
// Pre-traversal the page measured 9000. Lazy content and un-fixing the sticky header push
|
||||
// it over the cap by the time the plate would be shot. Probing before either step would
|
||||
// have passed the guard and emitted a clipped plate.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from "vitest";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
@@ -26,6 +26,7 @@ afterEach(() => {
|
||||
|
||||
function writeComposition(width: number, height: number): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-cloud-render-test-"));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
writeFileSync(
|
||||
join(dir, "index.html"),
|
||||
`<!doctype html><html><body><div data-composition-id="main" data-width="${width}" data-height="${height}"></div></body></html>`,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { existsSync, linkSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
linkSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { beforeAll, describe, expect, it, onTestFinished } from "vitest";
|
||||
import type { ArgsDef } from "citty";
|
||||
import { ensureDOMParser } from "../utils/dom.js";
|
||||
import keyframesCommand from "./keyframes.js";
|
||||
@@ -28,6 +36,7 @@ const wrap = (script: string) =>
|
||||
describe("keyframes direct composition scope", () => {
|
||||
it("keeps the project root and passes the nested HTML entry to --shot", () => {
|
||||
const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-target-"));
|
||||
onTestFinished(() => rmSync(projectDir, { recursive: true, force: true }));
|
||||
const compositionsDir = join(projectDir, "compositions");
|
||||
mkdirSync(compositionsDir);
|
||||
writeFileSync(join(projectDir, "index.html"), wrap(""));
|
||||
@@ -44,6 +53,7 @@ describe("keyframes direct composition scope", () => {
|
||||
describe("keyframes shot output", () => {
|
||||
it("rejects an output path that would overwrite the composition source", () => {
|
||||
const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-shot-source-"));
|
||||
onTestFinished(() => rmSync(projectDir, { recursive: true, force: true }));
|
||||
const sourcePath = join(projectDir, "index.html");
|
||||
writeFileSync(sourcePath, wrap(""));
|
||||
|
||||
@@ -55,6 +65,7 @@ describe("keyframes shot output", () => {
|
||||
|
||||
it("rejects an existing output alias that refers to the composition source", () => {
|
||||
const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-shot-alias-"));
|
||||
onTestFinished(() => rmSync(projectDir, { recursive: true, force: true }));
|
||||
const sourcePath = join(projectDir, "index.html");
|
||||
const aliasPath = join(projectDir, "shot.png");
|
||||
writeFileSync(sourcePath, wrap(""));
|
||||
@@ -67,6 +78,7 @@ describe("keyframes shot output", () => {
|
||||
|
||||
it("creates a missing parent directory before writing --shot", () => {
|
||||
const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-shot-dir-"));
|
||||
onTestFinished(() => rmSync(projectDir, { recursive: true, force: true }));
|
||||
const outputDir = join(projectDir, "nested", "proofs");
|
||||
ensureShotOutputDir(join(outputDir, "shot.png"));
|
||||
expect(existsSync(outputDir)).toBe(true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, mkdtempSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, onTestFinished, vi } from "vitest";
|
||||
import type { ActiveServer } from "../server/portUtils.js";
|
||||
import {
|
||||
buildBackgroundPreviewArgs,
|
||||
@@ -22,6 +22,12 @@ const server: ActiveServer = {
|
||||
pid: "4321",
|
||||
};
|
||||
|
||||
function tempDir(prefix: string): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), prefix));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
return dir;
|
||||
}
|
||||
|
||||
function savePreviewSession(stateHome: string): void {
|
||||
writePreviewSession(
|
||||
{ pid: 4321, port: 3210, projectDir, logPath: "/tmp/preview.log" },
|
||||
@@ -42,7 +48,7 @@ async function expectStaleSessionRemoved(stateHome: string): Promise<void> {
|
||||
describe("background preview lifecycle", () => {
|
||||
it("keeps case-distinct project paths separate on case-sensitive platforms", () => {
|
||||
if (process.platform === "win32") return;
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
|
||||
expect(previewSessionPath("/tmp/Project", stateHome)).not.toBe(
|
||||
previewSessionPath("/tmp/project", stateHome),
|
||||
@@ -71,7 +77,7 @@ describe("background preview lifecycle", () => {
|
||||
execPath: "/usr/bin/node",
|
||||
scan,
|
||||
spawn,
|
||||
stateHome: mkdtempSync(join(tmpdir(), "hf-preview-state-")),
|
||||
stateHome: tempDir("hf-preview-state-"),
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ type: "reused", port: 3210 });
|
||||
@@ -79,7 +85,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("reuses a saved managed preview on a custom port without repeating --port", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, port: 41402, projectDir, logPath: "/tmp/custom.log" },
|
||||
stateHome,
|
||||
@@ -100,7 +106,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("discovers managed previews outside the default port scan and removes stale records", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
const otherProjectDir = resolve("/tmp/hyperframes-preview-managed-custom-port");
|
||||
const staleProjectDir = resolve("/tmp/hyperframes-preview-managed-stale");
|
||||
writePreviewSession(
|
||||
@@ -160,7 +166,7 @@ describe("background preview lifecycle", () => {
|
||||
scan,
|
||||
spawn,
|
||||
sleep: async () => {},
|
||||
stateHome: mkdtempSync(join(tmpdir(), "hf-preview-state-")),
|
||||
stateHome: tempDir("hf-preview-state-"),
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ type: "started", port: 3211, pid: 5432 });
|
||||
@@ -173,7 +179,7 @@ describe("background preview lifecycle", () => {
|
||||
])(
|
||||
"%s replaces a previously managed server instead of orphaning it",
|
||||
async (_label, forceNew) => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
const oldServer = { ...server, port: 41490, browserGpuMode: "hardware" as const };
|
||||
writePreviewSession(
|
||||
{ pid: 4321, port: 41490, projectDir, logPath: "/tmp/preview.log" },
|
||||
@@ -218,7 +224,7 @@ describe("background preview lifecycle", () => {
|
||||
);
|
||||
|
||||
it("replaces the owned preview instead of reusing an unmanaged policy-matching sibling", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
const owned = { ...server, port: 41490, browserGpuMode: "hardware" as const };
|
||||
const sibling = {
|
||||
...server,
|
||||
@@ -270,7 +276,7 @@ describe("background preview lifecycle", () => {
|
||||
// one inside the stop. "Nothing left to stop" is the goal state for a
|
||||
// replacement; treating it as fatal refused to start any preview at all
|
||||
// until the session record was deleted by hand.
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
const owned = { ...server, port: 41490 };
|
||||
const replacement = { ...server, port: 41491, pid: "5432" };
|
||||
writePreviewSession(
|
||||
@@ -327,7 +333,7 @@ describe("background preview lifecycle", () => {
|
||||
scan,
|
||||
spawn,
|
||||
sleep: async () => {},
|
||||
stateHome: mkdtempSync(join(tmpdir(), "hf-preview-state-")),
|
||||
stateHome: tempDir("hf-preview-state-"),
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ type: "started", port: 3211, pid: 5432 });
|
||||
@@ -342,7 +348,7 @@ describe("background preview lifecycle", () => {
|
||||
spawned = true;
|
||||
return { pid: 4321, unref };
|
||||
});
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
|
||||
const result = await startBackgroundPreview(projectDir, 3002, {
|
||||
argv: ["/opt/hyperframes/cli.js", "preview", projectDir, "--background"],
|
||||
@@ -362,7 +368,7 @@ describe("background preview lifecycle", () => {
|
||||
const liveServer = { ...server, pid: "9876" };
|
||||
let spawned = false;
|
||||
const scan = vi.fn(async () => (spawned ? [liveServer] : []));
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
|
||||
const result = await startBackgroundPreview(projectDir, 3002, {
|
||||
scan,
|
||||
@@ -380,7 +386,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("reaps a detached child that never becomes reachable without recording ownership", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
const kill = vi.fn();
|
||||
|
||||
await expect(
|
||||
@@ -399,7 +405,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("removes a stale session when no matching server or process survives", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 999_999, port: 3210, projectDir, logPath: "/tmp/missing.log" },
|
||||
stateHome,
|
||||
@@ -409,7 +415,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("removes stale session metadata when its PID is alive but no server proves ownership", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
savePreviewSession(stateHome);
|
||||
|
||||
await expectStaleSessionRemoved(stateHome);
|
||||
@@ -420,7 +426,7 @@ describe("background preview lifecycle", () => {
|
||||
// capture will do it) answers nothing for a second or two. Retiring the
|
||||
// record on that destroys the wrapperIdentity that is the only PID-reuse
|
||||
// guard `--stop` has, and it never comes back.
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, wrapperIdentity: "posix:birth", port: 3210, projectDir, logPath: "/tmp/p.log" },
|
||||
stateHome,
|
||||
@@ -442,7 +448,7 @@ describe("background preview lifecycle", () => {
|
||||
// macOS that failure is a subprocess timeout on a LIVE process — under the
|
||||
// same load that made the HTTP probe miss. Treating no-answer as
|
||||
// "recycled" destroyed the only PID-reuse guard `--stop` has.
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, wrapperIdentity: "posix:birth", port: 3210, projectDir, logPath: "/tmp/p.log" },
|
||||
stateHome,
|
||||
@@ -461,7 +467,7 @@ describe("background preview lifecycle", () => {
|
||||
|
||||
it("retires the record when the PID cannot be signalled at all", async () => {
|
||||
// Gone is gone: no birth token needed, and no subprocess spawned for it.
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, wrapperIdentity: "posix:birth", port: 3210, projectDir, logPath: "/tmp/p.log" },
|
||||
stateHome,
|
||||
@@ -481,7 +487,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("retires the record once the wrapper PID has been recycled", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, wrapperIdentity: "posix:birth", port: 3210, projectDir, logPath: "/tmp/p.log" },
|
||||
stateHome,
|
||||
@@ -499,7 +505,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("never leaves a partial session record for a concurrent reader", () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
savePreviewSession(stateHome);
|
||||
const path = previewSessionPath(projectDir, stateHome);
|
||||
|
||||
@@ -514,7 +520,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("uses the recorded custom port when status is called without repeating --port", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
savePreviewSession(stateHome);
|
||||
const scan = vi.fn(async () => [server]);
|
||||
|
||||
@@ -538,7 +544,7 @@ describe("background preview lifecycle", () => {
|
||||
scan,
|
||||
kill,
|
||||
sleep: async () => {},
|
||||
stateHome: mkdtempSync(join(tmpdir(), "hf-preview-state-")),
|
||||
stateHome: tempDir("hf-preview-state-"),
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
@@ -547,7 +553,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("does not kill an unmatched saved PID that may have been reused", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
savePreviewSession(stateHome);
|
||||
const kill = vi.fn();
|
||||
|
||||
@@ -563,7 +569,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("refuses to stop when the live server cannot prove its own PID", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, port: 3210, projectDir, logPath: "/tmp/preview.log" },
|
||||
stateHome,
|
||||
@@ -584,7 +590,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("reaps the saved wrapper when the live server is proven to be its descendant", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{
|
||||
pid: 4321,
|
||||
@@ -615,7 +621,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("kills only the live server when the saved wrapper birth identity has changed", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{
|
||||
pid: 4321,
|
||||
@@ -646,7 +652,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("fails loudly when the server remains reachable after stop", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
writePreviewSession(
|
||||
{ pid: 4321, port: 3210, projectDir, logPath: "/tmp/preview.log" },
|
||||
stateHome,
|
||||
@@ -664,7 +670,7 @@ describe("background preview lifecycle", () => {
|
||||
});
|
||||
|
||||
it("verifies the owned port stopped even when another server serves the same project", async () => {
|
||||
const stateHome = mkdtempSync(join(tmpdir(), "hf-preview-state-"));
|
||||
const stateHome = tempDir("hf-preview-state-");
|
||||
const owned = { ...server, port: 41490 };
|
||||
const sibling = { ...server, port: 41491, pid: "8765" };
|
||||
writePreviewSession(
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, onTestFinished, vi } from "vitest";
|
||||
import type { RegistryItem } from "@hyperframes/core";
|
||||
|
||||
// The installer fetches over the network; the point of these tests is what it
|
||||
@@ -24,7 +24,9 @@ vi.mock("./remote.js", () => ({
|
||||
const { hasLocalEdits, installItem, prepareItem, publishItem } = await import("./installer.js");
|
||||
|
||||
function project(): string {
|
||||
return mkdtempSync(join(tmpdir(), "hf-installer-"));
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-installer-"));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
return dir;
|
||||
}
|
||||
|
||||
const item = {
|
||||
@@ -62,29 +64,16 @@ describe("installItem", () => {
|
||||
it("rejects a target directory symlink that escapes the project even with force", async () => {
|
||||
const dir = project();
|
||||
const outside = project();
|
||||
try {
|
||||
symlinkSync(outside, join(dir, "components"), "junction");
|
||||
await expect(installItem(item, { destDir: dir, force: true })).rejects.toThrow(
|
||||
/Unsafe target/,
|
||||
);
|
||||
expect(existsSync(join(outside, "data-chart.html"))).toBe(false);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
rmSync(outside, { recursive: true, force: true });
|
||||
}
|
||||
symlinkSync(outside, join(dir, "components"), "junction");
|
||||
await expect(installItem(item, { destDir: dir, force: true })).rejects.toThrow(/Unsafe target/);
|
||||
expect(existsSync(join(outside, "data-chart.html"))).toBe(false);
|
||||
});
|
||||
it("preserves directory symlinks whose destination stays inside the project", async () => {
|
||||
const dir = project();
|
||||
try {
|
||||
mkdirSync(join(dir, "actual"));
|
||||
symlinkSync(join(dir, "actual"), join(dir, "components"), "junction");
|
||||
await installItem(item, { destDir: dir, force: true });
|
||||
expect(readFileSync(join(dir, "actual/data-chart.html"), "utf8")).toContain(
|
||||
"REGISTRY VERSION",
|
||||
);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
mkdirSync(join(dir, "actual"));
|
||||
symlinkSync(join(dir, "actual"), join(dir, "components"), "junction");
|
||||
await installItem(item, { destDir: dir, force: true });
|
||||
expect(readFileSync(join(dir, "actual/data-chart.html"), "utf8")).toContain("REGISTRY VERSION");
|
||||
});
|
||||
it.each(["darwin", "win32"] as const)(
|
||||
"rejects absent case and Unicode aliases on %s before downloads",
|
||||
@@ -96,18 +85,14 @@ describe("installItem", () => {
|
||||
["Café.html", "Cafe\u0301.html"],
|
||||
]) {
|
||||
const dir = project();
|
||||
try {
|
||||
const conflicting = {
|
||||
...item,
|
||||
files: names.map((name) => ({ ...item.files[0]!, target: `components/${name}` })),
|
||||
};
|
||||
await expect(installItem(conflicting, { destDir: dir, force: true })).rejects.toThrow(
|
||||
/duplicate/,
|
||||
);
|
||||
expect(existsSync(join(dir, "components"))).toBe(false);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
const conflicting = {
|
||||
...item,
|
||||
files: names.map((name) => ({ ...item.files[0]!, target: `components/${name}` })),
|
||||
};
|
||||
await expect(installItem(conflicting, { destDir: dir, force: true })).rejects.toThrow(
|
||||
/duplicate/,
|
||||
);
|
||||
expect(existsSync(join(dir, "components"))).toBe(false);
|
||||
}
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
@@ -285,7 +270,6 @@ describe("installing with --vars the item cannot take", () => {
|
||||
expect(existsSync(join(dir, file))).toBe(false);
|
||||
} finally {
|
||||
remote.contents = "REGISTRY VERSION\n";
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
// payloads. Unit tests cover the streak arithmetic; this proves the wiring
|
||||
// and pins the event shape a dashboard will be built against.
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterAll } from "vitest";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const HOME = mkdtempSync(join(tmpdir(), "hf-lintrun-"));
|
||||
afterAll(() => rmSync(HOME, { recursive: true, force: true }));
|
||||
vi.mock("node:os", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:os")>();
|
||||
return { ...actual, homedir: () => HOME };
|
||||
|
||||
@@ -12,7 +12,7 @@ import { tmpdir } from "node:os";
|
||||
import { basename, dirname, join } from "node:path";
|
||||
import { MEDIA_DURATION_FIXTURES } from "@hyperframes/parsers/media-duration-fixtures";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import { beforeAll, describe, expect, it, onTestFinished } from "vitest";
|
||||
import { ensureDOMParser } from "../utils/dom.js";
|
||||
import {
|
||||
createProbeGate,
|
||||
@@ -47,6 +47,11 @@ const childrenOf = (t: ProjectTimeline, row: TimelineRow) =>
|
||||
allRows(t).filter((r) => row.children.some((c) => c.kind === r.trackKind && c.index === r.index));
|
||||
|
||||
let dir = "";
|
||||
const tempDir = (prefix: string) => {
|
||||
const made = mkdtempSync(join(tmpdir(), prefix));
|
||||
onTestFinished(() => rmSync(made, { recursive: true, force: true }));
|
||||
return made;
|
||||
};
|
||||
const TINY_PNG = Buffer.from(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==",
|
||||
"base64",
|
||||
@@ -67,15 +72,14 @@ const rowsOf = async (
|
||||
};
|
||||
|
||||
const project = () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-"));
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
dir = join(tempDir("hf-timeline-"), "project");
|
||||
mkdirSync(join(dir, "compositions"), { recursive: true });
|
||||
writeFileSync(join(dir, "index.html"), INDEX);
|
||||
writeFileSync(join(dir, "compositions", "title.html"), TITLE);
|
||||
return join(dir, "index.html");
|
||||
};
|
||||
|
||||
beforeAll(ensureDOMParser);
|
||||
afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
||||
|
||||
describe("describeProject", () => {
|
||||
it("groups rows into tracks by kind with resolved timing and clip facts", async () => {
|
||||
@@ -133,7 +137,6 @@ describe("describeProject", () => {
|
||||
["o", 0],
|
||||
["d", 0],
|
||||
]);
|
||||
rmSync(join(dir, "..", "hf-outside.html"));
|
||||
});
|
||||
|
||||
it("reads a sub-composition whose folder name starts with two dots", async () => {
|
||||
@@ -150,7 +153,7 @@ describe("describeProject", () => {
|
||||
|
||||
it("does not follow a symlink out of the project", async () => {
|
||||
const index = project();
|
||||
const outside = mkdtempSync(join(tmpdir(), "hf-outside-"));
|
||||
const outside = tempDir("hf-outside-");
|
||||
writeFileSync(join(outside, "secret.html"), TITLE);
|
||||
symlinkSync(join(outside, "secret.html"), join(dir, "compositions", "link.html"));
|
||||
writeFileSync(
|
||||
@@ -159,7 +162,6 @@ describe("describeProject", () => {
|
||||
);
|
||||
const [row] = (await describeProject(index)).tracks.flatMap((t) => t.rows);
|
||||
expect(row!.children).toEqual([]);
|
||||
rmSync(outside, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("claims no duration source for a leaf with nothing authored and no children", async () => {
|
||||
@@ -172,7 +174,7 @@ describe("describeProject", () => {
|
||||
|
||||
it("does not probe a remote, absolute or parent-relative src and says why", async () => {
|
||||
const index = project();
|
||||
const outside = mkdtempSync(join(tmpdir(), "hf-outside-media-"));
|
||||
const outside = tempDir("hf-outside-media-");
|
||||
copyFileSync(REAL_AUDIO, join(outside, "out.mp3"));
|
||||
copyFileSync(REAL_AUDIO, join(dir, "..", "hf-parent-media.mp3"));
|
||||
writeFileSync(
|
||||
@@ -196,8 +198,6 @@ describe("describeProject", () => {
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
rmSync(outside, { recursive: true, force: true });
|
||||
rmSync(join(dir, "..", "hf-parent-media.mp3"));
|
||||
});
|
||||
|
||||
it("reports a still image used as a video source as pending, not a measured zero", async () => {
|
||||
@@ -215,22 +215,18 @@ describe("describeProject", () => {
|
||||
});
|
||||
|
||||
it("does not probe a media src that is a symlink out of the project", async () => {
|
||||
const outside = mkdtempSync(join(tmpdir(), "hf-outside-"));
|
||||
try {
|
||||
copyFileSync(REAL_AUDIO, join(outside, "secret.mp3"));
|
||||
const { rows } = await rowsOf(
|
||||
`<div data-composition-id="m"><audio id="a" src="link.mp3" data-start="0"></audio></div>`,
|
||||
false,
|
||||
(root) => symlinkSync(join(outside, "secret.mp3"), join(root, "link.mp3")),
|
||||
);
|
||||
expect(rows[0]).toMatchObject({
|
||||
durationSource: "pending",
|
||||
pendingReason: "source file not found",
|
||||
duration: 0,
|
||||
});
|
||||
} finally {
|
||||
rmSync(outside, { recursive: true, force: true });
|
||||
}
|
||||
const outside = tempDir("hf-outside-");
|
||||
copyFileSync(REAL_AUDIO, join(outside, "secret.mp3"));
|
||||
const { rows } = await rowsOf(
|
||||
`<div data-composition-id="m"><audio id="a" src="link.mp3" data-start="0"></audio></div>`,
|
||||
false,
|
||||
(root) => symlinkSync(join(outside, "secret.mp3"), join(root, "link.mp3")),
|
||||
);
|
||||
expect(rows[0]).toMatchObject({
|
||||
durationSource: "pending",
|
||||
pendingReason: "source file not found",
|
||||
duration: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("gives an image with no authored duration the resolver's default length", async () => {
|
||||
@@ -328,7 +324,7 @@ const INVERSION_INDEX = `<div data-composition-id="main" data-duration="30">
|
||||
const INVERSION_SCENE = `<template><div data-composition-id="scene"><video id="nested" src="n.mp4" data-start="1" data-duration="2" data-track-index="0"></video></div></template>`;
|
||||
|
||||
const inversionProject = () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-abs-"));
|
||||
dir = tempDir("hf-timeline-abs-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(join(dir, "index.html"), INVERSION_INDEX);
|
||||
writeFileSync(join(dir, "compositions", "scene.html"), INVERSION_SCENE);
|
||||
@@ -369,7 +365,7 @@ describe("absolute main-timeline time", () => {
|
||||
});
|
||||
|
||||
it("places a nested media clip with a negative start where the runtime plays it", async () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-neg-"));
|
||||
dir = tempDir("hf-timeline-neg-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(join(dir, "index.html"), INVERSION_INDEX);
|
||||
writeFileSync(
|
||||
@@ -382,7 +378,7 @@ describe("absolute main-timeline time", () => {
|
||||
});
|
||||
|
||||
it("resolves a media start given as an expression like any other clip, not as a literal", async () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-expr-"));
|
||||
dir = tempDir("hf-timeline-expr-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(join(dir, "index.html"), INVERSION_INDEX);
|
||||
writeFileSync(
|
||||
@@ -398,7 +394,7 @@ describe("absolute main-timeline time", () => {
|
||||
});
|
||||
|
||||
it("clamps a negative media start to 0 when the host starts at 0, as the runtime does", async () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-neg0-"));
|
||||
dir = tempDir("hf-timeline-neg0-");
|
||||
writeFileSync(
|
||||
join(dir, "index.html"),
|
||||
`<div data-composition-id="main" data-duration="10"><video id="v" src="v.mp4" data-start="-3" data-duration="2" data-track-index="0"></video></div>`,
|
||||
@@ -412,7 +408,7 @@ describe("absolute main-timeline time", () => {
|
||||
// hosted at main-timeline 5.2s too, and a video in an EARLIER host ends at
|
||||
// 5.27s — a 0.07s overlap only visible once both are on the same clock.
|
||||
it("carries enough absolute time to detect a sub-second overlap across two different sub-compositions", async () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-overlap-"));
|
||||
dir = tempDir("hf-timeline-overlap-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(
|
||||
join(dir, "index.html"),
|
||||
@@ -442,7 +438,7 @@ describe("absolute main-timeline time", () => {
|
||||
// clip" has to be found by comparing videos nested in DIFFERENT
|
||||
// sub-compositions, each printed with its own local 0-based start.
|
||||
it("orders videos nested in different sub-compositions by absolute start, not local start", async () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-order-"));
|
||||
dir = tempDir("hf-timeline-order-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(
|
||||
join(dir, "index.html"),
|
||||
@@ -534,7 +530,7 @@ const EVAL_MUSIC = `<template><div data-composition-id="terminal-music">
|
||||
</div></template>`;
|
||||
|
||||
const evalTimeline = () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-eval-"));
|
||||
dir = tempDir("hf-timeline-eval-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(join(dir, "index.html"), EVAL_INDEX);
|
||||
writeFileSync(join(dir, "compositions", "terminal-sfx.html"), EVAL_SFX);
|
||||
@@ -544,7 +540,7 @@ const evalTimeline = () => {
|
||||
|
||||
/** Ids, absent ids, and same ids across kinds and hosts; every clip has a distinct src. */
|
||||
const collidingTimeline = () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-collide-"));
|
||||
dir = tempDir("hf-timeline-collide-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(
|
||||
join(dir, "index.html"),
|
||||
@@ -635,7 +631,7 @@ describe("kind tracks list every clip once", () => {
|
||||
});
|
||||
|
||||
it("does not count a sub-composition inside a sub-composition", async () => {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-timeline-deep-"));
|
||||
dir = tempDir("hf-timeline-deep-");
|
||||
mkdirSync(join(dir, "compositions"));
|
||||
writeFileSync(join(dir, "index.html"), EVAL_INDEX);
|
||||
writeFileSync(
|
||||
@@ -674,17 +670,13 @@ const hasJq = (() => {
|
||||
|
||||
const runOneLiner = async (line: string, timeline?: ProjectTimeline): Promise<string> => {
|
||||
const own = timeline ? null : inversionProject();
|
||||
try {
|
||||
return execFileSync("bash", ["-c", line], {
|
||||
env: {
|
||||
...process.env,
|
||||
TL: JSON.stringify({ timeline: timeline ?? (await describeProject(own!)) }),
|
||||
},
|
||||
encoding: "utf8",
|
||||
});
|
||||
} finally {
|
||||
if (own) rmSync(dirname(own), { recursive: true, force: true });
|
||||
}
|
||||
return execFileSync("bash", ["-c", line], {
|
||||
env: {
|
||||
...process.env,
|
||||
TL: JSON.stringify({ timeline: timeline ?? (await describeProject(own!)) }),
|
||||
},
|
||||
encoding: "utf8",
|
||||
});
|
||||
};
|
||||
const parseStream = (out: string) => JSON.parse(`[${out.replace(/}\s*{/g, "},{")}]`);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, onTestFinished } from "vitest";
|
||||
import { findMotionSpec, parseMotionSpec, readMotionSpec, type MotionSpec } from "./motionSpec.js";
|
||||
|
||||
const RFC_SPEC = {
|
||||
@@ -19,6 +19,12 @@ function expectOk(result: ReturnType<typeof parseMotionSpec>): MotionSpec {
|
||||
return result.spec;
|
||||
}
|
||||
|
||||
function tempDir(prefix: string): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), prefix));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
return dir;
|
||||
}
|
||||
|
||||
describe("parseMotionSpec", () => {
|
||||
it("parses the RFC four-assertion spec", () => {
|
||||
const spec = expectOk(parseMotionSpec(RFC_SPEC));
|
||||
@@ -100,19 +106,19 @@ describe("parseMotionSpec", () => {
|
||||
|
||||
describe("findMotionSpec", () => {
|
||||
it("returns null when no sidecar is present", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-none-"));
|
||||
const dir = tempDir("motion-none-");
|
||||
writeFileSync(join(dir, "main.html"), "<div></div>");
|
||||
expect(findMotionSpec(dir)).toBeNull();
|
||||
});
|
||||
|
||||
it("finds the single sidecar", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-one-"));
|
||||
const dir = tempDir("motion-one-");
|
||||
writeFileSync(join(dir, "anything.motion.json"), "{}");
|
||||
expect(findMotionSpec(dir)).toBe(join(dir, "anything.motion.json"));
|
||||
});
|
||||
|
||||
it("prefers the sidecar matching a composition html basename", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-many-"));
|
||||
const dir = tempDir("motion-many-");
|
||||
writeFileSync(join(dir, "aaa.motion.json"), "{}");
|
||||
writeFileSync(join(dir, "main.motion.json"), "{}");
|
||||
writeFileSync(join(dir, "main.html"), "<div></div>");
|
||||
@@ -120,7 +126,7 @@ describe("findMotionSpec", () => {
|
||||
});
|
||||
|
||||
it("throws when multiple sidecars each match a different composition", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-ambig-"));
|
||||
const dir = tempDir("motion-ambig-");
|
||||
writeFileSync(join(dir, "hero.motion.json"), "{}");
|
||||
writeFileSync(join(dir, "landing.motion.json"), "{}");
|
||||
writeFileSync(join(dir, "hero.html"), "<div></div>");
|
||||
@@ -137,7 +143,7 @@ describe("readMotionSpec", () => {
|
||||
});
|
||||
|
||||
it("returns error for a file with invalid JSON", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-bad-"));
|
||||
const dir = tempDir("motion-bad-");
|
||||
const path = join(dir, "bad.motion.json");
|
||||
writeFileSync(path, "not json {{");
|
||||
const result = readMotionSpec(path);
|
||||
@@ -146,7 +152,7 @@ describe("readMotionSpec", () => {
|
||||
});
|
||||
|
||||
it("returns error for a file with a valid JSON but invalid spec", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-invalid-"));
|
||||
const dir = tempDir("motion-invalid-");
|
||||
const path = join(dir, "invalid.motion.json");
|
||||
writeFileSync(path, JSON.stringify({ assertions: [] }));
|
||||
const result = readMotionSpec(path);
|
||||
@@ -155,7 +161,7 @@ describe("readMotionSpec", () => {
|
||||
});
|
||||
|
||||
it("parses a valid sidecar file", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "motion-valid-"));
|
||||
const dir = tempDir("motion-valid-");
|
||||
const path = join(dir, "main.motion.json");
|
||||
writeFileSync(
|
||||
path,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// @vitest-environment node
|
||||
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, onTestFinished } from "vitest";
|
||||
import { buildSubCompositionHtml, hasBaseElement } from "./subComposition";
|
||||
|
||||
function makeTempProject(files: Record<string, string>): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-subcomp-preview-"));
|
||||
onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
const full = join(dir, rel);
|
||||
mkdirSync(join(full, ".."), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user