import { test, expect } from '@playwright/test' const FAKE_TOKEN = 'fake-invite-token-for-e2e' const FAKE_PREREG_ID = '000000000000000000000001' async function mockVerifyOk(page, overrides = {}) { await page.route('**/api/invite/verify', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ preRegistrationId: FAKE_PREREG_ID, name: overrides.name ?? 'Pre Registered User', email: overrides.email ?? `prereg-${Date.now()}@example.com`, city: overrides.city ?? 'Vancouver, BC', }), }) }) } async function mockAcceptFree(page) { await page.route('**/api/invite/accept', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, requiresPayment: false, redirectUrl: '/member/dashboard', member: { id: 'mem-1', email: 'prereg@example.com', name: 'Pre Registered User', circle: 'community', contributionAmount: 0, status: 'active', }, }), }) }) await page.route('**/api/auth/status', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ authenticated: true, member: { id: 'mem-1', name: 'Pre Registered User', status: 'active' }, status: 'active', }), }) }) } async function gotoAcceptInvite(page) { await page.goto(`/accept-invite#${FAKE_TOKEN}`) } test.describe('Accept Invite — pre-registrant signup', () => { test('verifies invitation and shows form fields', async ({ page }) => { await mockVerifyOk(page, { name: 'Ada Lovelace', email: 'ada@example.com' }) await gotoAcceptInvite(page) await expect(page.locator('#accept-name')).toBeVisible() await expect(page.locator('#accept-name')).toHaveValue('Ada Lovelace') await expect(page.locator('#accept-email')).toHaveValue('ada@example.com') await expect(page.locator('#circle-community')).toBeAttached() await expect(page.locator('#circle-founder')).toBeAttached() await expect(page.locator('#circle-practitioner')).toBeAttached() await expect(page.locator('#accept-cadence-monthly')).toBeAttached() await expect(page.locator('#accept-cadence-annual')).toBeAttached() await expect(page.locator('#accept-contribution')).toBeVisible() await expect(page.locator('.contribution-preset-chip').first()).toBeVisible() await expect(page.locator('.form-submit')).toBeVisible() }) test('shows error when no token in URL hash', async ({ page }) => { await page.goto('/accept-invite') await expect(page.getByRole('heading', { name: 'Invitation Error' })).toBeVisible() await expect(page.locator('.error-box')).toContainText(/No invitation token/) }) test('shows error when token verification fails', async ({ page }) => { await page.route('**/api/invite/verify', async (route) => { await route.fulfill({ status: 401, contentType: 'application/json', body: JSON.stringify({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' }), }) }) await gotoAcceptInvite(page) await expect(page.getByRole('heading', { name: 'Invitation Error' })).toBeVisible() await expect(page.locator('.error-box')).toContainText(/Invalid or expired/) }) test('submit disabled until name + agreement filled', async ({ page }) => { await mockVerifyOk(page, { name: '' }) await gotoAcceptInvite(page) await expect(page.locator('#accept-name')).toBeVisible() await expect(page.locator('.form-submit')).toBeDisabled() await page.locator('#accept-name').fill('New Member') await expect(page.locator('.form-submit')).toBeDisabled() await page.getByRole('checkbox', { name: /Community Guidelines/ }).check() await expect(page.locator('.form-submit')).toBeEnabled() }) test('cadence toggle updates billing summary total', async ({ page }) => { await mockVerifyOk(page) await gotoAcceptInvite(page) await expect(page.locator('#accept-contribution')).toBeVisible() await page.locator('#accept-contribution').fill('10') await page.locator('label[for="accept-cadence-monthly"]').click() await expect(page.locator('.billing-summary')).toContainText('$10 today') await page.locator('label[for="accept-cadence-annual"]').click() await expect(page.locator('.billing-summary')).toContainText('$120 today') await expect(page.locator('.billing-summary')).toContainText('$10/month') }) test('preset chip sets contribution amount', async ({ page }) => { await mockVerifyOk(page) await gotoAcceptInvite(page) await expect(page.locator('.contribution-preset-chip').first()).toBeVisible() const chip = page.locator('.contribution-preset-chip').nth(1) const chipText = await chip.textContent() const expected = chipText.replace(/[^0-9]/g, '') await chip.click() await expect(page.locator('#accept-contribution')).toHaveValue(expected) }) test('free tier happy path shows welcome state', async ({ page }) => { await mockVerifyOk(page, { name: 'Free Tester', email: `free-${Date.now()}@example.com` }) await mockAcceptFree(page) await gotoAcceptInvite(page) await expect(page.locator('#accept-name')).toHaveValue('Free Tester') await page.locator('#circle-community').check({ force: true }) await page.locator('#accept-contribution').fill('0') await page.getByRole('checkbox', { name: /Community Guidelines/ }).check() await expect(page.locator('.form-submit')).toBeEnabled() await expect(page.locator('.form-submit')).toContainText(/Accept Invitation/) await page.locator('.form-submit').click() await expect( page.getByRole('heading', { name: 'Welcome to Ghost Guild!' }) ).toBeVisible({ timeout: 15000 }) }) test('paid tier submit button copy switches to Continue to Payment', async ({ page }) => { await mockVerifyOk(page) await gotoAcceptInvite(page) await page.locator('#accept-contribution').fill('10') await page.getByRole('checkbox', { name: /Community Guidelines/ }).check() await expect(page.locator('.form-submit')).toContainText(/Continue to Payment/) }) // Skipped: full paid-tier submission requires intercepting HelcimPay.js modal // (external script loads an iframe and posts a message back to verifyPayment). // Feasible but out of scope for this initial coverage pass. test.skip('paid tier full flow with mocked HelcimPay', async () => {}) })