Files
cms-client/tests/e2e/workbench.spec.ts
Noa Virellia 13a9413446 test(e2e): fix workbench checkin selector link→button
The "立即签到" button is rendered by bits-ui Dialog.Trigger as a
<button>, not <a>. Change getByRole('link') to getByRole('button')
on lines 120, 133, and 149 for selector consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 13:11:37 +08:00

219 lines
8.2 KiB
TypeScript

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> = {}): 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 <em> 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();
});