Adds a checkbox to the public submission page that lets a sender opt out of background removal, ticked by default because most items look better cut out and the reverse default would mean almost nobody got it. It only renders when the server reports the sidecar is configured, matching the intake link's new backgroundRemoval flag from Task 5 — an unconfigured environment gets no checkbox rather than one that would do nothing. submitItem now takes removeBackground as a required fourth parameter, sent as the multipart string 'true' or 'false' to match the backend's exact-string opt-out contract. Making the parameter required rather than optional was deliberate, so the compiler would catch any call site left unupdated; the frontend build (which also type-checks tests/ via tsconfig.test.json) confirmed the only call site, in Submit.tsx, was updated. scripts/start-local.ps1 now sets REMBG_URL for the local backend so the checkbox is visible during local and e2e runs; the value need not resolve, since no e2e submission reaches the sidecar without a configured drafting step. Adds two e2e cases to intake-submit.spec.ts: the checkbox appears ticked by default, and a sender can uncheck it and still submit successfully. Both are written per the task-7 brief but not run in this session, since running Playwright requires the full local stack (database, backend, frontend dev server) which was not started. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
import { test, expect, createAdminContext, uniqueSuffix } from './fixtures';
|
|
|
|
/**
|
|
* The public submission page (#222).
|
|
*
|
|
* Every fixture here carries a run id and every assertion names only what this
|
|
* run created. The suite is fullyParallel against one shared database, so a
|
|
* spec that asserts on anything catalogue-wide is asserting on other specs too
|
|
* (#241).
|
|
*/
|
|
const RUN = `i${uniqueSuffix()}`;
|
|
|
|
let token = '';
|
|
|
|
test.beforeAll(async ({ playwright }) => {
|
|
const api = await createAdminContext(playwright);
|
|
|
|
const res = await api.post('/api/admin/upload-links', {
|
|
data: { label: `Intake spec ${RUN}` }
|
|
});
|
|
expect(res.status(), 'creating the upload link').toBe(201);
|
|
token = (await res.json()).token;
|
|
|
|
await api.dispose();
|
|
});
|
|
|
|
test.describe('Sending in an item through a link', () => {
|
|
test('shows the form for a link that works', async ({ page }) => {
|
|
await page.goto(`/submit/${token}`);
|
|
|
|
await expect(page.getByRole('heading', { name: 'Send in an item' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Choose photos' })).toBeVisible();
|
|
});
|
|
|
|
// Nothing to send is not a submission, and the server would refuse it — but
|
|
// the sender should not have to find that out by pressing the button.
|
|
test('will not send until a photo is chosen', async ({ page }) => {
|
|
await page.goto(`/submit/${token}`);
|
|
|
|
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled();
|
|
});
|
|
|
|
// One state for every refusal, matching the server's single 404. Saying which
|
|
// kind of dead a link is would tell a stranger whether one they guessed at
|
|
// exists.
|
|
test('explains an unusable link without saying which kind', async ({ page }) => {
|
|
await page.goto('/submit/not-a-real-token');
|
|
|
|
await expect(page.getByRole('heading', { name: 'This link is not active' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Choose photos' })).toHaveCount(0);
|
|
});
|
|
|
|
test('accepts a photo and says it arrived', async ({ page }) => {
|
|
await page.goto(`/submit/${token}`);
|
|
|
|
// A real 1x1 PNG, so the server's magic-byte check sees what it expects
|
|
// rather than a buffer that merely starts correctly.
|
|
const png = Buffer.from(
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
|
|
'base64'
|
|
);
|
|
await page.setInputFiles('input[type="file"]', {
|
|
name: `${RUN}.png`,
|
|
mimeType: 'image/png',
|
|
buffer: png
|
|
});
|
|
await page.getByLabel('Anything you know about this item').fill(`Stoneware ${RUN}`);
|
|
|
|
await page.getByRole('button', { name: 'Send' }).click();
|
|
|
|
await expect(page.getByRole('heading', { name: 'Thank you — it arrived' })).toBeVisible();
|
|
// The wording matters: it is what stops a sender wondering why their item
|
|
// is not on the site.
|
|
await expect(page.getByText(/Nothing is listed for sale until/)).toBeVisible();
|
|
});
|
|
|
|
// Ticked by default, because that is the decision: most items look better
|
|
// cut out, and the reverse default would mean almost nobody got it.
|
|
test('offers to remove the background, already ticked', async ({ page }) => {
|
|
await page.goto(`/submit/${token}`);
|
|
|
|
const control = page.getByRole('checkbox', { name: /remove the background/i });
|
|
await expect(control).toBeVisible();
|
|
await expect(control).toBeChecked();
|
|
});
|
|
|
|
test('lets a sender turn it off and still send', async ({ page }) => {
|
|
await page.goto(`/submit/${token}`);
|
|
|
|
const png = Buffer.from(
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
|
|
'base64'
|
|
);
|
|
await page.setInputFiles('input[type="file"]', {
|
|
name: `${RUN}-nobg.png`,
|
|
mimeType: 'image/png',
|
|
buffer: png
|
|
});
|
|
await page.getByRole('checkbox', { name: /remove the background/i }).uncheck();
|
|
await page.getByRole('button', { name: 'Send' }).click();
|
|
|
|
await expect(page.getByRole('heading', { name: 'Thank you — it arrived' })).toBeVisible();
|
|
});
|
|
});
|