import assert from "node:assert/strict"; import fs from "node:fs"; import test from "node:test"; import { clampCropCenter, cropCenterBounds, cropGeometry } from "../app/static/media-crop-geometry.js"; import { shouldReuseMediaJobCard } from "../app/static/media-import-ui-model.js"; const html = fs.readFileSync(new URL("../app/static/index.html", import.meta.url), "utf8"); const app = fs.readFileSync(new URL("../app/static/app.js", import.meta.url), "utf8"); const view = fs.readFileSync(new URL("../app/static/views/media-import.js", import.meta.url), "utf8"); const copyCatalog = JSON.parse(fs.readFileSync(new URL("../app/static/ui-copy.json", import.meta.url), "utf8")); test("media import workspace exposes streaming upload, crop controls, and queue polling", () => { assert.match(app, /views\/media-import\.js/); assert.match(html, /id="workspace-media-import"/); assert.match(html, /id="media-source-file"/); assert.doesNotMatch(html, /id="media-output-name"/); assert.match(html, /class="media-source-field"/); assert.match(html, /id="media-upload-progress"/); assert.match(view, /XMLHttpRequest/); assert.match(view, /pointermove/); assert.match(view, /transparency_color/); assert.match(view, /padding_color/); assert.match(view, /document\.hidden/); assert.match(view, /setInterval\(refreshJobs, 2000\)/); assert.match(view, /id: "media-import"/); assert.doesNotMatch(view, /nameInput/); assert.doesNotMatch(view, /&name=/); assert.doesNotMatch(view, /裁切填满/); assert.doesNotMatch(fs.readFileSync(new URL("../app/static/style.css", import.meta.url), "utf8"), /object-fit:\s*cover/); }); test("free framing starts from the complete source and keeps one pixel visible", () => { const centered = cropGeometry({ viewportWidth: 400, viewportHeight: 400, sourceWidth: 800, sourceHeight: 400, zoom: 1, centerX: 0.5, centerY: 0.5, }); assert.deepEqual(centered, { width: 400, height: 200, left: 0, top: 100 }); const bounds = cropCenterBounds({ viewportWidth: 400, viewportHeight: 400, sourceWidth: 800, sourceHeight: 400, zoom: 1, }); const corner = cropGeometry({ viewportWidth: 400, viewportHeight: 400, sourceWidth: 800, sourceHeight: 400, zoom: 1, centerX: bounds.minX, centerY: bounds.minY, }); assert.equal(corner.left, 393.75); assert.equal(corner.top, 393.75); assert.deepEqual( clampCropCenter({ centerX: -100, centerY: 100 }, bounds), { centerX: bounds.minX, centerY: bounds.maxY }, ); }); test("free framing copy is stable and catalogued", () => { assert.match(view, /copy\.dynamic\.media\.mode_crop", "自由取景"/); assert.equal(copyCatalog.items["copy.dynamic.media.mode_crop"]?.text, "自由取景"); assert.equal(copyCatalog.items["copy.dynamic.media.crop_zoom"]?.text, "取景缩放"); }); test("upload action has visible ordinary-build copy", () => { assert.match(html, /id="media-upload-button"[^>]*>上传文件<\/button>/); const item = Object.values(copyCatalog.items).find( (candidate) => candidate.render?.selector === "#media-upload-button", ); assert.equal(item?.text, "上传文件"); assert.equal(Boolean(item?.render?.default_hidden), false); }); test("media naming happens after analysis and conflicts keep the draft", () => { assert.match(view, /copy\.dynamic\.media\.name_conflict", "保存名称已存在,请更换名称。"/); assert.match(view, /error\.message === "output name already exists"/); assert.match(view, /const file = fileInput\.files\[0\]/); assert.doesNotMatch(html, />保存名称<\/span>[\s\S]*media-upload-button/); assert.equal(copyCatalog.items["copy.dynamic.media.name_conflict"]?.text, "保存名称已存在,请更换名称。"); }); test("awaiting-settings cards survive polling without hiding server transitions", () => { const rendered = { id: "job-1", state: "awaiting_settings", updatedAt: "2026-08-15T10:00:00Z", dirty: false, }; const unchanged = { id: "job-1", state: "awaiting_settings", updated_at: "2026-08-15T10:00:00Z", }; assert.equal(shouldReuseMediaJobCard(rendered, unchanged), true); assert.equal(shouldReuseMediaJobCard( { ...rendered, dirty: true }, { ...unchanged, updated_at: "2026-08-15T10:01:00Z" }, ), true); assert.equal(shouldReuseMediaJobCard( rendered, { ...unchanged, updated_at: "2026-08-15T10:01:00Z" }, ), false); assert.equal(shouldReuseMediaJobCard( rendered, { ...unchanged, state: "queued" }, ), false); });