134 lines
4.7 KiB
TypeScript
134 lines
4.7 KiB
TypeScript
|
|
/**
|
||
|
|
* Phase-3 acceptance tests: 视角/perspective switcher (D2 — frontend view-presets only).
|
||
|
|
*
|
||
|
|
* Roles are NOT access control: the switcher only changes the default landing page +
|
||
|
|
* granularity/filter presets. This suite verifies the switcher renders, selecting a role
|
||
|
|
* navigates to that role's default landing URL (with its query params), and the choice
|
||
|
|
* survives a reload (persisted to localStorage['cbpoa_role']).
|
||
|
|
*
|
||
|
|
* Auth + API mocking mirror e2e/user-flows.spec.ts so the suite runs hermetically.
|
||
|
|
*/
|
||
|
|
import { test, expect, Page } from '@playwright/test';
|
||
|
|
import { TESTIDS } from '../src/utils/testids';
|
||
|
|
|
||
|
|
/** Seed auth token and mock all /api/** calls (shapes copied from user-flows.spec.ts). */
|
||
|
|
async function seedAuthAndMockApi(page: Page) {
|
||
|
|
await page.addInitScript(() => {
|
||
|
|
localStorage.setItem('cbpoa_token', 'e2e-test-token');
|
||
|
|
});
|
||
|
|
|
||
|
|
await page.route('/api/**', (route) => {
|
||
|
|
const url = route.request().url();
|
||
|
|
|
||
|
|
if (url.includes('/alerts')) {
|
||
|
|
route.fulfill({
|
||
|
|
status: 200,
|
||
|
|
contentType: 'application/json',
|
||
|
|
body: JSON.stringify({ alerts: [], total: 0 }),
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (url.includes('/history/aggregated')) {
|
||
|
|
route.fulfill({
|
||
|
|
status: 200,
|
||
|
|
contentType: 'application/json',
|
||
|
|
body: JSON.stringify({ aggregations: [], total_records: 0 }),
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (url.includes('/grids')) {
|
||
|
|
route.fulfill({
|
||
|
|
status: 200,
|
||
|
|
contentType: 'application/json',
|
||
|
|
body: JSON.stringify({ type: 'FeatureCollection', features: [] }),
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (url.includes('/cases/demographics')) {
|
||
|
|
route.fulfill({
|
||
|
|
status: 200,
|
||
|
|
contentType: 'application/json',
|
||
|
|
body: JSON.stringify({
|
||
|
|
age_distribution: [],
|
||
|
|
gender_split: { male: { outpatient: 0, inpatient: 0 }, female: { outpatient: 0, inpatient: 0 } },
|
||
|
|
age_diagnosis_matrix: [],
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (url.includes('/cases/diagnosis-distribution') || url.includes('/cases/disease-seasonality')) {
|
||
|
|
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (url.includes('/cases/districts') || url.includes('/districts')) {
|
||
|
|
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (url.includes('/cases/trend') || url.includes('/cases')) {
|
||
|
|
route.fulfill({
|
||
|
|
status: 200,
|
||
|
|
contentType: 'application/json',
|
||
|
|
body: JSON.stringify({ data: [], total: 0 }),
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
route.fulfill({
|
||
|
|
status: 200,
|
||
|
|
contentType: 'application/json',
|
||
|
|
body: JSON.stringify({ data: [], items: [], total: 0 }),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
test.describe('视角/perspective switcher (Phase 3)', () => {
|
||
|
|
// Use a desktop viewport so the top bar renders the switcher inline.
|
||
|
|
test.use({ viewport: { width: 1280, height: 800 } });
|
||
|
|
|
||
|
|
test.beforeEach(async ({ page }) => {
|
||
|
|
await seedAuthAndMockApi(page);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('perspective-switcher is visible in the top bar', async ({ page }) => {
|
||
|
|
await page.goto('/monitoring');
|
||
|
|
await expect(page.locator(`[data-testid="${TESTIDS.perspectiveSwitcher}"]`)).toBeVisible();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('selecting 厅领导 (official) navigates to /overview?granularity=district', async ({ page }) => {
|
||
|
|
await page.goto('/monitoring');
|
||
|
|
const switcher = page.locator(`[data-testid="${TESTIDS.perspectiveSwitcher}"]`);
|
||
|
|
await expect(switcher).toBeVisible();
|
||
|
|
|
||
|
|
await switcher.selectOption('official');
|
||
|
|
|
||
|
|
await expect(page).toHaveURL(/\/overview/);
|
||
|
|
await expect(page).toHaveURL(/granularity=district/);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('selecting 医生 (doctor) navigates to /alerts?view=cluster', async ({ page }) => {
|
||
|
|
await page.goto('/monitoring');
|
||
|
|
const switcher = page.locator(`[data-testid="${TESTIDS.perspectiveSwitcher}"]`);
|
||
|
|
await expect(switcher).toBeVisible();
|
||
|
|
|
||
|
|
await switcher.selectOption('doctor');
|
||
|
|
|
||
|
|
await expect(page).toHaveURL(/\/alerts/);
|
||
|
|
await expect(page).toHaveURL(/view=cluster/);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('selected role persists across reload (localStorage cbpoa_role)', async ({ page }) => {
|
||
|
|
await page.goto('/monitoring');
|
||
|
|
const switcher = page.locator(`[data-testid="${TESTIDS.perspectiveSwitcher}"]`);
|
||
|
|
await switcher.selectOption('doctor');
|
||
|
|
await expect(page).toHaveURL(/\/alerts/);
|
||
|
|
|
||
|
|
// localStorage should now hold the chosen role.
|
||
|
|
const stored = await page.evaluate(() => localStorage.getItem('cbpoa_role'));
|
||
|
|
expect(stored).toBe('doctor');
|
||
|
|
|
||
|
|
await page.reload();
|
||
|
|
|
||
|
|
// After reload the switcher reflects the persisted role.
|
||
|
|
await expect(page.locator(`[data-testid="${TESTIDS.perspectiveSwitcher}"]`)).toHaveValue('doctor');
|
||
|
|
});
|
||
|
|
});
|