feat(erase-object): add freeform lasso selection mode (#503)

Adds a Brush | Lasso toggle to the object eraser. Lasso lets the user drag a freeform loop that auto-closes and fills into the mask, so they select around a subject instead of painting every pixel. Frontend-only; the mask contract is unchanged. Also un-skips the erase-object e2e suite via a shared mockAiFeaturesInstalled helper (7 tests now run; 2 multi-file tests fixme'd for a pre-existing tool-page remount bug). Closes #492.
This commit is contained in:
SnapOtter
2026-07-11 22:27:51 +08:00
committed by GitHub
parent 380419dd06
commit 601557edae
27 changed files with 408 additions and 124 deletions
+37
View File
@@ -128,6 +128,43 @@ export async function uploadTestImage(page: Page): Promise<void> {
await page.waitForTimeout(500);
}
// ---------------------------------------------------------------------------
// mockAiFeaturesInstalled() — make AI-tool bundles report as installed.
//
// AI tools (erase-object, upscale, ...) render a FeatureInstallPrompt instead of
// the tool UI when their bundle is missing, so their e2e specs otherwise skip in
// CI and on any box without the bundle. Mock the feature-status endpoint so the
// tool UI renders. Call BEFORE navigating to the tool page, then goto (a full
// load resets the in-memory features store, which re-fetches through this mock).
// These specs exercise client-side UI up to mask generation, not the real AI
// backend / processing.
// ---------------------------------------------------------------------------
export async function mockAiFeaturesInstalled(
page: Page,
bundles: Array<{ id: string; enablesTools: string[]; name?: string }>,
): Promise<void> {
await page.route("**/api/v1/features", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
bundles: bundles.map((b) => ({
id: b.id,
name: b.name ?? b.id,
description: "",
status: "installed",
installedVersion: "1.0.0",
estimatedSize: "",
downloadBytes: null,
installedBytes: null,
enablesTools: b.enablesTools,
progress: null,
})),
}),
}),
);
}
// ---------------------------------------------------------------------------
// waitForProcessing() — wait for processing to complete
// ---------------------------------------------------------------------------