One button whose label flipped on "is every photo cut out?" could not serve a partly cut-out item, which is not a hypothetical state: it is what a partial removal leaves behind, and it is also what happens when REMBG_URL goes away after some photos were already done. In that state the single button read "Remove backgrounds", so the cut-out photos the item already had could never be restored from this screen. Remove and Restore are now separately gated and can appear together, which is correct — Remove finishes the job on what is left, Restore undoes what is already done. Restore is deliberately not gated on the backgroundRemoval config flag. Gating it would strand cut-out photos with no way back in exactly the environment that most needs the undo. Remove stays gated, so an unconfigured environment shows no button rather than one that reports zero of four done every time. The emptiness check moves from `!== null` to `!= null`: original_image_path is optional on the shared Item type because the public storefront response omits it, so a stray undefined has to count as "not cut out" — `undefined !== null` is true, which would misread a public-shaped item as fully cut out. The modal now refreshes on a non-ok response too. A restore that fails partway can still have swapped some files back before it failed, so returning early left the thumbnails showing files that are no longer on the server. The warning text is now driven off whichever count the action reports, so a partial restore says how far it got the same way a partial removal already did. The e2e spec seeds its item into a category of its own and filters the table down to it. The inventory table paginates at 10 and the suite runs fullyParallel, so an unfiltered page one was never a reliable place to find the fixture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
import { test, expect, createAdminContext, createCategory, uniqueSuffix } from './fixtures';
|
|
|
|
/**
|
|
* The per-item background control in the inventory editor (#293).
|
|
*
|
|
* Asserts the control is offered, not that a cut-out happens. Pressing it needs
|
|
* a live rembg sidecar, which takes forty seconds to start and which no test
|
|
* should depend on — the swap itself is covered in the integration suite
|
|
* against a stub.
|
|
*
|
|
* "Does not appear when the feature is unconfigured" is not written here as an
|
|
* e2e case: unsetting REMBG_URL means restarting the backend mid-suite, which
|
|
* this run has no way to do and should not gain one. GET /api/admin/config
|
|
* answering `backgroundRemoval: false` is covered by Task 2's integration
|
|
* tests, and the render condition that gates the button on it is plain enough
|
|
* to read in Admin.tsx.
|
|
*/
|
|
const RUN = uniqueSuffix();
|
|
const NAME = `Vase ${RUN}`;
|
|
// A category of its own, not shared: it exists only so filterByCategory below
|
|
// has something unique to narrow the table down to, the same way
|
|
// admin-inventory-filters.spec.ts uses one per run.
|
|
const CATEGORY = `Backgrounds ${RUN}`;
|
|
|
|
// The 1x1 PNG the other upload specs use, so this goes through the real
|
|
// validated upload path rather than a buffer that merely starts correctly.
|
|
const PNG = Buffer.from(
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
|
|
'base64'
|
|
);
|
|
|
|
test.beforeAll(async ({ playwright }) => {
|
|
const api = await createAdminContext(playwright);
|
|
const categoryId = await createCategory(api, CATEGORY);
|
|
// Seeded with an image directly: createItem cannot attach one, and the
|
|
// control does not render for an item with no photos.
|
|
const res = await api.post('/api/admin/items', {
|
|
multipart: {
|
|
name: NAME,
|
|
description: '',
|
|
price: '50',
|
|
category_id: String(categoryId),
|
|
tags: '[]',
|
|
images: { name: `${RUN}.png`, mimeType: 'image/png', buffer: PNG }
|
|
}
|
|
});
|
|
expect(res.ok(), `seeding ${NAME}`).toBeTruthy();
|
|
await api.dispose();
|
|
});
|
|
|
|
test.describe('Removing backgrounds from the item editor', () => {
|
|
test('offers the control on an item that has photos', async ({ page, admin, adminInventory }) => {
|
|
await admin.open('Inventory');
|
|
|
|
// The table paginates at 10 and this suite runs fullyParallel, so an
|
|
// unfiltered page 1 is not a reliable place to find this fixture — see
|
|
// AdminInventory.filterByCategory. Filtering to this item's own category
|
|
// narrows the table down to just it, the way admin-inventory-filters.spec.ts
|
|
// does.
|
|
await adminInventory.filterByCategory(CATEGORY, NAME);
|
|
|
|
await adminInventory.row(NAME).getByRole('button', { name: 'Edit' }).click();
|
|
|
|
await expect(page.getByRole('button', { name: 'Remove backgrounds' })).toBeVisible();
|
|
});
|
|
});
|