import { test, expect } from './helpers/fixtures'; import { mock } from './helpers/mock'; import type { ServiceEventEventListItems } from '$lib/api'; // Minimal event factory matching the SDK type function mkEvent(overrides: Partial = {}): ServiceEventEventListItems { return { event_id: 'evt-1', name: '测试活动', subtitle: '测试地点', start_time: new Date(Date.now() + 3_600_000).toISOString(), end_time: new Date(Date.now() + 7_200_000).toISOString(), is_joined: true, is_checked_in: false, is_agenda_published: false, enable_kyc: false, type: 'party', ...overrides }; } function eventListResponse(items: ServiceEventEventListItems[]) { return { status: 200, body: { status: 200, data: { items } } }; } // ── Workbench structure ────────────────────────────────────────────────────── test('empty state: no events shows zero count and empty cards', async ({ page, loggedInUser }) => { void loggedInUser; await mock.override('GET', '/event/list', eventListResponse([])); await page.goto('/app/'); // Welcome card: 0 joined events await expect(page.getByText('0', { exact: true })).toBeVisible(); // Current event empty state await expect(page.getByText('暂无进行中或即将到来的活动')).toBeVisible(); // Upcoming: three dashed placeholders await expect(page.getByText('暂无更多活动')).toHaveCount(3); // No GET /event/list unexpected requests const reqs = await mock.requests({ method: 'GET', path: '/event/list' }); expect(reqs.length).toBeGreaterThan(0); }); test('welcome card shows nickname and joined count', async ({ page, loggedInUser }) => { void loggedInUser; await mock.override( 'GET', '/event/list', eventListResponse([ mkEvent({ event_id: 'a', is_joined: true }), mkEvent({ event_id: 'b', is_joined: false }) ]) ); await page.goto('/app/'); // nickname 'Alice' from loggedInUser fixture — scope to the element await expect(page.locator('em').filter({ hasText: 'Alice' })).toBeVisible(); // 1 joined (the non-joined one is excluded) await expect(page.getByText('1', { exact: true })).toBeVisible(); }); // ── Current event card ─────────────────────────────────────────────────────── test('joined ongoing event shows "进行中" badge', async ({ page, loggedInUser }) => { void loggedInUser; const start = new Date(Date.now() - 3_600_000).toISOString(); const end = new Date(Date.now() + 3_600_000).toISOString(); await mock.override( 'GET', '/event/list', eventListResponse([mkEvent({ event_id: 'live', start_time: start, end_time: end })]) ); await page.goto('/app/'); await expect(page.getByText('进行中')).toBeVisible(); await expect(page.getByText('暂无进行中或即将到来的活动')).toHaveCount(0); }); test('joined upcoming event shows "待开始" badge in current event card', async ({ page, loggedInUser }) => { void loggedInUser; const start = new Date(Date.now() + 3_600_000).toISOString(); const end = new Date(Date.now() + 7_200_000).toISOString(); await mock.override( 'GET', '/event/list', eventListResponse([mkEvent({ event_id: 'soon', start_time: start, end_time: end })]) ); await page.goto('/app/'); // Current event card shows "待开始" await expect(page.getByText('待开始').first()).toBeVisible(); }); test('is_checked_in shows "已签到" badge', async ({ page, loggedInUser }) => { void loggedInUser; const start = new Date(Date.now() - 3_600_000).toISOString(); const end = new Date(Date.now() + 3_600_000).toISOString(); await mock.override( 'GET', '/event/list', eventListResponse([mkEvent({ start_time: start, end_time: end, is_checked_in: true })]) ); await page.goto('/app/'); await expect(page.getByText('已签到')).toBeVisible(); }); // ── Checkin button ─────────────────────────────────────────────────────────── test('attendee sees "立即签到" button during ongoing event', async ({ page, loggedInUser }) => { void loggedInUser; const start = new Date(Date.now() - 3_600_000).toISOString(); const end = new Date(Date.now() + 3_600_000).toISOString(); await mock.override( 'GET', '/event/list', eventListResponse([mkEvent({ start_time: start, end_time: end })]) ); await page.goto('/app/'); await expect(page.getByRole('button', { name: /立即签到/ })).toBeVisible(); }); test('"立即签到" button is hidden when already checked in', async ({ page, loggedInUser }) => { void loggedInUser; const start = new Date(Date.now() - 3_600_000).toISOString(); const end = new Date(Date.now() + 3_600_000).toISOString(); await mock.override( 'GET', '/event/list', eventListResponse([mkEvent({ start_time: start, end_time: end, is_checked_in: true })]) ); await page.goto('/app/'); await expect(page.getByRole('button', { name: /立即签到/ })).toHaveCount(0); }); test('"立即签到" button is hidden for an upcoming (not yet started) event', async ({ page, loggedInUser }) => { void loggedInUser; const start = new Date(Date.now() + 3_600_000).toISOString(); const end = new Date(Date.now() + 7_200_000).toISOString(); await mock.override( 'GET', '/event/list', eventListResponse([mkEvent({ start_time: start, end_time: end })]) ); await page.goto('/app/'); await expect(page.getByRole('button', { name: /立即签到/ })).toHaveCount(0); }); // ── Upcoming schedule ──────────────────────────────────────────────────────── test('upcoming card shows next 3 events, current event excluded', async ({ page, loggedInUser }) => { void loggedInUser; const t = (offset: number) => new Date(Date.now() + offset).toISOString(); const events = [ mkEvent({ event_id: 'a', name: '活动A', start_time: t(1_000), end_time: t(3_600_000) }), mkEvent({ event_id: 'b', name: '活动B', start_time: t(3_600_001), end_time: t(7_200_000) }), mkEvent({ event_id: 'c', name: '活动C', start_time: t(7_200_001), end_time: t(10_800_000) }), mkEvent({ event_id: 'd', name: '活动D', start_time: t(10_800_001), end_time: t(14_400_000) }) ]; await mock.override('GET', '/event/list', eventListResponse(events)); await page.goto('/app/'); // 'A' is currentEvent; B, C, D are upcoming (D should be excluded — only 3 slots) await expect(page.getByText('活动B')).toBeVisible(); await expect(page.getByText('活动C')).toBeVisible(); await expect(page.getByText('活动D')).toBeVisible(); // Only 1 empty slot (3 events fill 3 slots) await expect(page.getByText('暂无更多活动')).toHaveCount(0); }); // ── Profile completeness card ──────────────────────────────────────────────── test('profile ring shows 100% when all fields set', async ({ page, loggedInUser }) => { // loggedInUser already has nickname and email; override to add subtitle, avatar, bio await mock.override('GET', '/user/info', { status: 200, body: { status: 200, data: { ...loggedInUser, subtitle: 'Developer', avatar: 'https://example.com/a.jpg', bio: Buffer.from('简介').toString('base64') } } }); await mock.override('GET', '/event/list', eventListResponse([])); await page.goto('/app/'); await expect(page.getByText('100%')).toBeVisible(); }); test('profile ring shows 75% when bio is missing', async ({ page, loggedInUser }) => { // loggedInUser has nickname; add subtitle + avatar but no bio await mock.override('GET', '/user/info', { status: 200, body: { status: 200, data: { ...loggedInUser, subtitle: 'Developer', avatar: 'https://example.com/a.jpg' } } }); await mock.override('GET', '/event/list', eventListResponse([])); await page.goto('/app/'); await expect(page.getByText('75%')).toBeVisible(); // Bio checklist item shows as incomplete (dashed circle, no checkmark sibling) await expect(page.getByText('个人简介')).toBeVisible(); });