import { test, expect, Page } from './fixtures'; const PASSWORD = 'supersecret123'; const RUN = `f${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`; const ITEM = `Favoritable ${RUN}`; const uniqueEmail = () => `fav-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}@example.com`; test.beforeAll(async ({ playwright }) => { const api = await playwright.request.newContext({ baseURL: 'http://localhost:5173' }); const res = await api.post('/api/admin/items', { multipart: { name: ITEM, description: '', price: '60', category_id: '', tags: '[]' } }); expect(res.ok()).toBeTruthy(); await api.dispose(); }); async function register(page: Page, email: string) { await page.goto('/register'); await page.getByRole('textbox', { name: 'Email' }).fill(email); await page.getByLabel('Password').fill(PASSWORD); await page.getByRole('button', { name: 'Create account' }).click(); // Registering now closes the auth modal and returns to the page behind it, so // the header rather than the URL is what proves the session exists. The wait // is generous because this is a bcrypt round-trip rather than a render. await expect(page.getByRole('button', { name: 'My Account' })).toBeVisible({ timeout: 20000 }); } // Going to the storefront remounts the app, so the session is briefly still // resolving. The heart deliberately ignores clicks in that window rather than // wrongly prompting a signed-in customer to sign in, so wait for the header to // show the account link — which is exactly what a real customer sees settle. async function gotoStorefrontSignedIn(page: Page) { await page.goto('/'); await expect(page.getByRole('button', { name: 'My Account' })).toBeVisible(); } // The storefront paginates as items accumulate, so find the card by name. function heart(page: Page, itemName: string) { return page.getByRole('button', { name: new RegExp(`(Add|Remove) ${itemName}`) }); } test.describe('Favoriting items', () => { test('a signed-out visitor is prompted to sign in, and the favorite completes', async ({ page }) => { await page.goto('/'); await heart(page, ITEM).first().click(); // Same inline prompt Add to Cart already uses. await expect(page.getByRole('dialog')).toBeVisible(); const email = uniqueEmail(); await page.getByRole('dialog').getByRole('textbox', { name: 'Email' }).fill(email); await page.getByRole('dialog').getByLabel('Password').fill(PASSWORD); await page.getByRole('dialog').getByRole('button', { name: 'Create account' }).click(); // The favorite the visitor originally asked for is applied on success. await expect(page.getByRole('button', { name: `Remove ${ITEM} from favorites` })).toBeVisible(); }); test('offers the alert opt-in with a reason, and it is not the marketing consent', async ({ page }) => { const email = uniqueEmail(); await register(page, email); await gotoStorefrontSignedIn(page); await heart(page, ITEM).first().click(); const prompt = page.getByRole('dialog'); await expect(prompt).toBeVisible(); await expect(prompt).toContainText('Want to know if this sells?'); // The reason for asking has to be given, not just the ask. await expect(prompt).toContainText(/one of a kind/i); await expect(prompt).toContainText(/separate from any marketing/i); await prompt.getByRole('button', { name: 'Yes, email me' }).click(); await expect(page.getByText('We will let you know')).toBeVisible(); const me = await (await page.request.get('/api/customers/me')).json(); expect(me.favorite_alerts).toBe(true); // Accepting item alerts must not sign anyone up for marketing. expect(me.marketing_consent).toBe(false); }); test('declining the opt-in still keeps the favorite', async ({ page }) => { const email = uniqueEmail(); await register(page, email); await gotoStorefrontSignedIn(page); await heart(page, ITEM).first().click(); await page.getByRole('dialog').getByRole('button', { name: 'No thanks' }).click(); await expect(page.getByRole('button', { name: `Remove ${ITEM} from favorites` })).toBeVisible(); const favorites = await (await page.request.get('/api/customers/me/favorites')).json(); expect(favorites).toHaveLength(1); const me = await (await page.request.get('/api/customers/me')).json(); expect(me.favorite_alerts).toBe(false); }); test('unfavoriting removes it', async ({ page }) => { const email = uniqueEmail(); await register(page, email); await gotoStorefrontSignedIn(page); await heart(page, ITEM).first().click(); await page.getByRole('dialog').getByRole('button', { name: 'No thanks' }).click(); await expect(page.getByRole('button', { name: `Remove ${ITEM} from favorites` })).toBeVisible(); await page.getByRole('button', { name: `Remove ${ITEM} from favorites` }).click(); await expect(page.getByRole('button', { name: `Add ${ITEM} to favorites` })).toBeVisible(); const favorites = await (await page.request.get('/api/customers/me/favorites')).json(); expect(favorites).toEqual([]); }); test('favorites survive a reload', async ({ page }) => { const email = uniqueEmail(); await register(page, email); await gotoStorefrontSignedIn(page); await heart(page, ITEM).first().click(); await page.getByRole('dialog').getByRole('button', { name: 'No thanks' }).click(); await expect(page.getByRole('button', { name: `Remove ${ITEM} from favorites` })).toBeVisible(); // Proves it was stored server-side rather than held in component state. await page.reload(); await expect(page.getByRole('button', { name: `Remove ${ITEM} from favorites` })).toBeVisible(); }); test('the account page can turn the alerts off again', async ({ page }) => { const email = uniqueEmail(); await register(page, email); await page.request.put('/api/customers/me/favorite-alerts', { data: { enabled: true } }); await page.goto('/account'); // Scoped to the account modal. The storefront renders behind it and has a // theme switch of its own, so an unscoped switch locator only picks the // right control by DOM accident. const account = page.getByRole('dialog', { name: 'My Account' }); await expect(account.getByText('Email me when an item I favorited is sold')).toBeVisible(); await account.getByRole('switch').last().click(); await expect(page.getByText('Turned off')).toBeVisible(); const me = await (await page.request.get('/api/customers/me')).json(); expect(me.favorite_alerts).toBe(false); }); });