Files
cms-client/tests/e2e/events.spec.ts
2026-04-18 12:14:30 +08:00

304 lines
10 KiB
TypeScript

import { expect } from '@playwright/test';
import { test } from './helpers/fixtures';
import { mock } from './helpers/mock';
// ─── Shared test data ───────────────────────────────────────────────────────
const baseEvent = {
event_id: 'e1',
name: '2026 年春季技术峰会',
subtitle: 'Building the future together',
type: 'official',
enable_kyc: false,
is_joined: false,
is_checked_in: false,
start_time: '2026-06-01T09:00:00Z',
end_time: '2026-06-02T18:00:00Z'
};
const kycEvent = {
...baseEvent,
event_id: 'e2',
name: 'KYC 认证活动',
enable_kyc: true
};
// Helper: override the event list with the given items.
async function overrideEventList(items: (typeof baseEvent)[]) {
await mock.override('GET', '/event/list', {
status: 200,
body: { status: 200, data: { items, total: items.length } }
});
}
// Helper: override event info.
async function overrideEventInfo(ev: typeof baseEvent) {
await mock.override('GET', '/event/info', {
status: 200,
body: { status: 200, data: ev }
});
}
// Helper: register a no-op guide override (needed when is_joined: true triggers getEventGuide).
async function overrideGuideEmpty(eventId: string = 'e1') {
void eventId; // mock matches by path, not query param
await mock.override('GET', '/event/guide', {
status: 200,
body: { status: 200, data: { attendance_guide: null } }
});
}
// ─── Tests ──────────────────────────────────────────────────────────────────
test('event list renders event names and tab labels', async ({ page, loggedInUser }) => {
void loggedInUser;
await overrideEventList([baseEvent, kycEvent]);
await page.goto('/app/events');
await page.waitForLoadState('networkidle');
await expect(page.getByText('2026 年春季技术峰会')).toBeVisible();
await expect(page.getByText('KYC 认证活动')).toBeVisible();
await expect(page.getByRole('button', { name: '全部活动' })).toBeVisible();
await expect(page.getByRole('button', { name: /已加入/ })).toBeVisible();
});
test('"已加入" tab shows only joined events', async ({ page, loggedInUser }) => {
void loggedInUser;
const joinedEvent = { ...baseEvent, event_id: 'ej', name: '已报名活动', is_joined: true };
await overrideEventList([baseEvent, joinedEvent]);
await page.goto('/app/events');
await page.waitForLoadState('networkidle');
// Switch to joined tab
await page.getByRole('button', { name: /已加入/ }).click();
await expect(page.getByText('已报名活动')).toBeVisible();
await expect(page.getByText('2026 年春季技术峰会')).not.toBeVisible();
});
test('event detail renders title, badges, and attendance guide when joined', async ({
page,
loggedInUser
}) => {
void loggedInUser;
const guideMd = Buffer.from('# 参会须知\n请携带身份证。').toString('base64');
await overrideEventInfo({ ...baseEvent, is_joined: true });
await mock.override('GET', '/event/guide', {
status: 200,
body: { status: 200, data: { attendance_guide: guideMd } }
});
await page.goto('/app/events/e1');
await page.waitForLoadState('networkidle');
await expect(page.getByRole('heading', { name: '2026 年春季技术峰会' })).toBeVisible();
await expect(page.getByText('已报名')).toBeVisible();
// Guide card appears
await expect(page.getByText('参会指南')).toBeVisible();
await expect(page.getByText('参会须知')).toBeVisible();
});
test('simple join success shows 已报名 badge', async ({ page, loggedInUser }) => {
void loggedInUser;
await overrideEventInfo(baseEvent);
await mock.override('POST', '/event/join', {
status: 200,
body: { status: 200, data: { attendance_id: 'att-1' } }
});
// After invalidateAll the load re-runs; return is_joined: true.
// is_joined: true triggers getEventGuide — register that override too.
await overrideGuideEmpty();
await page.goto('/app/events/e1');
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: '立即加入' }).click();
// Join dialog appears
await expect(page.getByText('是否确认要加入活动')).toBeVisible();
// Register the post-join event info override just before clicking 加入.
await mock.override('GET', '/event/info', {
status: 200,
body: { status: 200, data: { ...baseEvent, is_joined: true } }
});
await page.getByRole('button', { name: '加入', exact: true }).click();
await expect(page.getByText('已报名')).toBeVisible();
});
test('simple join error shows inline error message', async ({ page, loggedInUser }) => {
void loggedInUser;
await overrideEventInfo(baseEvent);
await mock.override('POST', '/event/join', {
status: 400,
body: { status: 400, msg: '活动已满' }
});
await page.goto('/app/events/e1');
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: '立即加入' }).click();
await expect(page.getByText('是否确认要加入活动')).toBeVisible();
await page.getByRole('button', { name: '加入', exact: true }).click();
await expect(page.getByText('活动已满')).toBeVisible();
// Dialog remains open
await expect(page.getByText('是否确认要加入活动')).toBeVisible();
});
test('KYC prompt → method selection navigation', async ({ page, loggedInUser }) => {
void loggedInUser;
await overrideEventInfo(kycEvent);
await page.goto('/app/events/e2');
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: '立即加入' }).click();
// Prompt stage
await expect(page.getByRole('heading', { name: '需要身份认证' })).toBeVisible();
await expect(page.getByText('AES-256 加密存储')).toBeVisible();
await page.getByRole('button', { name: '下一步' }).click();
// Method selection stage
await expect(page.getByRole('heading', { name: '选择身份认证模式' })).toBeVisible();
await expect(page.getByText('身份证')).toBeVisible();
await expect(page.getByText('护照')).toBeVisible();
});
test('KYC synchronous success (cnrid) joins and shows 已报名', async ({ page, loggedInUser }) => {
void loggedInUser;
await overrideEventInfo(kycEvent);
await mock.override('POST', '/kyc/session', {
status: 200,
body: { status: 200, data: { status: 'success', kyc_id: 'k1' } }
});
await mock.override('POST', '/event/join', {
status: 200,
body: { status: 200, data: { attendance_id: 'att-1' } }
});
await page.goto('/app/events/e2');
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: '立即加入' }).click();
await page.getByRole('button', { name: '下一步' }).click();
await page.getByText('身份证').click();
await page.locator('#kyc-name').fill('张三');
await page.locator('#kyc-cnrid').fill('110101199003070034');
await page.getByRole('button', { name: '开始认证' }).click();
// KYC passed synchronously — dialog transitions to success
await expect(page.getByRole('heading', { name: '成功' })).toBeVisible();
// Register post-join overrides before invalidateAll fires on 完成.
await mock.override('GET', '/event/info', {
status: 200,
body: { status: 200, data: { ...kycEvent, is_joined: true } }
});
await overrideGuideEmpty();
await page.getByRole('button', { name: '完成' }).click();
await expect(page.getByText('已报名')).toBeVisible();
});
test('KYC processing → polling → success shows 已报名', async ({ page, loggedInUser }) => {
void loggedInUser;
// Suppress window.open so the redirect URL does not open a real tab.
await page.addInitScript(() => {
window.open = () => null;
});
await overrideEventInfo(kycEvent);
await mock.override('POST', '/kyc/session', {
status: 200,
body: {
status: 200,
data: { status: 'processing', kyc_id: 'k1', redirect_uri: 'https://example.com/kyc' }
}
});
// The SvelteKit +server.ts polls POST /kyc/query (backend path).
await mock.override('POST', '/kyc/query', {
status: 200,
body: { status: 200, data: { status: 'success' } }
});
await mock.override('POST', '/event/join', {
status: 200,
body: { status: 200, data: { attendance_id: 'att-1' } }
});
await page.goto('/app/events/e2');
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: '立即加入' }).click();
await page.getByRole('button', { name: '下一步' }).click();
await page.getByText('身份证').click();
await page.locator('#kyc-name').fill('李四');
await page.locator('#kyc-cnrid').fill('110101199003070034');
await page.getByRole('button', { name: '开始认证' }).click();
// Stage → pending (window.open was suppressed)
await expect(page.getByRole('heading', { name: '等待身份认证结果' })).toBeVisible();
// Polling resolves and join completes — dialog transitions to success
await expect(
page.getByRole('heading', { name: '成功' }),
'polling resolves within 5s'
).toBeVisible({
timeout: 5000
});
// Register post-join overrides before invalidateAll fires on 完成.
await mock.override('GET', '/event/info', {
status: 200,
body: { status: 200, data: { ...kycEvent, is_joined: true } }
});
await overrideGuideEmpty();
await page.getByRole('button', { name: '完成' }).click();
await expect(page.getByText('已报名')).toBeVisible();
});
test('KYC failed shows 失败 stage', async ({ page, loggedInUser }) => {
void loggedInUser;
await page.addInitScript(() => {
window.open = () => null;
});
await overrideEventInfo(kycEvent);
await mock.override('POST', '/kyc/session', {
status: 200,
body: {
status: 200,
data: { status: 'processing', kyc_id: 'k1', redirect_uri: 'https://example.com/kyc' }
}
});
// Poll returns failed immediately.
await mock.override('POST', '/kyc/query', {
status: 200,
body: { status: 200, data: { status: 'failed' } }
});
await page.goto('/app/events/e2');
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: '立即加入' }).click();
await page.getByRole('button', { name: '下一步' }).click();
await page.getByText('身份证').click();
await page.locator('#kyc-name').fill('王五');
await page.locator('#kyc-cnrid').fill('110101199003070034');
await page.getByRole('button', { name: '开始认证' }).click();
await expect(page.getByRole('heading', { name: '等待身份认证结果' })).toBeVisible();
await expect(page.getByRole('heading', { name: '失败' }), 'poll fails within 5s').toBeVisible({
timeout: 5000
});
});