修复了2个bug

This commit is contained in:
andy committed 2026-09-02 17:02:02 +08:00
1 parent ce8372e878
commit 2a64dc7395
64 files changed
+1050 -39

No files matched your search

+14
View File
@@ -1,7 +1,21 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { hasCompleteSeedreamLayerMetadata } from "@/lib/seedream/layer-composition";
describe("Seedream layer composition persistence contract", () => {
it("only enables the editable layer project for complete provider layer metadata", () => {
expect(hasCompleteSeedreamLayerMetadata([
{ z_index: 0, size: "2048x2048" },
{ z_index: 1, size: "600x300" }
], 2)).toBe(true);
expect(hasCompleteSeedreamLayerMetadata([
{ size: "2048x2048" },
{ size: "600x300" }
], 2)).toBe(false);
expect(hasCompleteSeedreamLayerMetadata([{ z_index: 0 }], 2)).toBe(false);
expect(hasCompleteSeedreamLayerMetadata([{ z_index: 0 }], 1)).toBe(false);
});
it("ships a versioned PostgreSQL document table that follows generation-job deletion", async () => {
const migration = await readFile(new URL("../database/migrations/0005_seedream_layer_compositions.sql", import.meta.url), "utf8");
+28 -6
View File
@@ -38,6 +38,28 @@ describe("Seedream layer edit result synchronization", () => {
vi.unstubAllGlobals();
});
it("uses the supported asset collection when production rejects platform item GET", async () => {
vi.useFakeTimers();
vi.stubGlobal("window", { setTimeout, clearTimeout });
const job = { ...succeededBeforeOutputSync, outputAssetIds: [resultAsset.id] };
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/generations/image/job-edit-1") return Response.json({ job });
if (url === "/api/assets/asset-result") return new Response(null, { status: 405 });
if (url === "/api/assets") return Response.json({ assets: [resultAsset] });
return Response.json({ error: "unexpected request" }, { status: 404 });
});
vi.stubGlobal("fetch", fetchMock);
const pending = waitForJobOutputAssets(job, new AbortController().signal);
const assertion = expect(pending).resolves.toEqual([resultAsset]);
await vi.runAllTimersAsync();
await assertion;
expect(fetchMock).toHaveBeenCalledWith("/api/assets", expect.objectContaining({ cache: "no-store" }));
expect(fetchMock).not.toHaveBeenCalledWith("/api/assets/asset-result", expect.anything());
});
it("refreshes a succeeded job whose output IDs arrive after the terminal status", async () => {
vi.useFakeTimers();
vi.stubGlobal("window", { setTimeout, clearTimeout });
@@ -46,7 +68,7 @@ describe("Seedream layer edit result synchronization", () => {
if (url === "/api/generations/image/job-edit-1") {
return Response.json({ job: { ...succeededBeforeOutputSync, outputAssetIds: [resultAsset.id] } });
}
if (url === "/api/assets/asset-result") return Response.json({ asset: resultAsset });
if (url === "/api/assets") return Response.json({ assets: [resultAsset] });
return Response.json({ error: "unexpected request" }, { status: 404 });
});
vi.stubGlobal("fetch", fetchMock);
@@ -56,10 +78,10 @@ describe("Seedream layer edit result synchronization", () => {
await expect(pending).resolves.toEqual([resultAsset]);
expect(fetchMock).toHaveBeenCalledWith("/api/generations/image/job-edit-1", expect.objectContaining({ cache: "no-store" }));
expect(fetchMock).toHaveBeenCalledWith("/api/assets/asset-result", expect.objectContaining({ cache: "no-store" }));
expect(fetchMock).toHaveBeenCalledWith("/api/assets", expect.objectContaining({ cache: "no-store" }));
});
it("retries the direct asset lookup while the registered result is not readable yet", async () => {
it("retries the asset collection while the registered result is not readable yet", async () => {
vi.useFakeTimers();
vi.stubGlobal("window", { setTimeout, clearTimeout });
let assetReads = 0;
@@ -67,11 +89,11 @@ describe("Seedream layer edit result synchronization", () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/generations/image/job-edit-1") return Response.json({ job });
if (url === "/api/assets/asset-result") {
if (url === "/api/assets") {
assetReads += 1;
return assetReads === 1
? Response.json({ error: "资产不存在" }, { status: 404 })
: Response.json({ asset: resultAsset });
? Response.json({ assets: [] })
: Response.json({ assets: [resultAsset] });
}
return Response.json({ error: "unexpected request" }, { status: 404 });
});
@@ -0,0 +1,45 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const styles = read("../app/globals.css");
const workspaceSource = read("../components/seedream-workspace.tsx");
function read(relativePath: string): string {
return readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), "utf8").replace(/\r\n/g, "\n");
}
function cssRule(selector: string): string {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const match = styles.match(new RegExp(`${escapedSelector}\\s*\\{([^}]*)\\}`));
if (!match) throw new Error(`CSS rule not found: ${selector}`);
return match[1];
}
describe("Seedream workspace layout contract", () => {
it("keeps vertical scrolling on the workspace instead of nesting it inside the canvas", () => {
const workspace = cssRule(".seedream-workspace");
const viewport = cssRule(".seedream-canvas-viewport");
expect(workspace).toMatch(/overflow-x\s*:\s*hidden/);
expect(workspace).toMatch(/overflow-y\s*:\s*auto/);
expect(workspace).not.toMatch(/overflow\s*:\s*auto/);
expect(viewport).toMatch(/overflow\s*:\s*hidden/);
expect(viewport).not.toMatch(/overflow\s*:\s*(?:auto|scroll)/);
});
it("caps the annotation canvas without making it consume the remaining column height", () => {
const viewport = cssRule(".seedream-canvas-viewport");
const image = cssRule(".seedream-canvas-stage > img");
expect(viewport).toMatch(/--seedream-canvas-height\s*:\s*clamp\(280px,\s*38vh,\s*460px\)/);
expect(viewport).toMatch(/height\s*:\s*var\(--seedream-canvas-height\)/);
expect(viewport).toMatch(/flex\s*:\s*0\s+0\s+auto/);
expect(image).toMatch(/max-height\s*:\s*calc\(var\(--seedream-canvas-height\)\s*-\s*22px\)/);
});
it("does not render the redundant source picker for single-image layer decomposition", () => {
expect(workspaceSource).toMatch(/\{mode === "interactive" && sources\.length \? \(\s*<div className="seedream-source-picker">/);
});
});