Files
WiseGrade/frontend/tests/import.spec.ts
T

99 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from '@playwright/test';
const identity = { student_no: '9000000001', name: '学生甲', topic: '机器人项目' };
const mixed = () => ({ relative_dir: 'test/student', student_no: '', name: '', topic: '',
status: 'needs_confirmation', folder_identity: {}, detected_identity: {}, missing: [],
warnings: ['不同材料封面的学号不一致,请核对后填写'], files: [
{ relative_path: 'test/student/最终报告.pdf', category: 'final_report', status: 'recognized',
sha256: 'a'.repeat(64), detected_identity: identity },
{ relative_path: 'test/student/汇报PPT.pdf', category: 'presentation', status: 'needs_confirmation',
sha256: 'b'.repeat(64), detected_identity: { student_no: '9000000002', name: '学生乙', topic: '另一项目' } },
] });
async function setup(page: Page, project: any) {
const imports: any[] = [];
await page.route('**/api/**', async route => {
const request = route.request();
const path = new URL(request.url()).pathname.replace(/^\/api/, '');
let data: any = [];
if (path === '/courses') data = [{ id: 1, name: '课程甲' }];
if (path === '/courses/1/versions') data = [{ id: 1, published: true, version: 1, title: '初始版本' }];
if (path === '/import/scan') data = { projects: [project] };
if (path === '/import/confirm') { imports.push(request.postDataJSON()); data = { project_ids: [12] }; }
await route.fulfill({ json: data });
});
await page.goto('/?page=import');
await page.getByRole('button', { name: '扫描此目录', exact: true }).click();
await choose(page, '导入课程', '课程甲');
await choose(page, '导入课程版本', /初始版本/);
return imports;
}
async function choose(page: Page, label: string, option: string | RegExp) {
await page.keyboard.press('Escape');
await expect(page.locator('.ant-select-dropdown:visible')).toHaveCount(0);
const input = page.getByRole('combobox', { name: label, exact: true });
await input.focus(); await input.press('ArrowDown');
const listId = await input.getAttribute('aria-controls');
const dropdown = page.locator('.ant-select-dropdown').filter({ has: page.locator(`[id="${listId}"]`) });
await expect(dropdown).toBeVisible();
await dropdown.getByText(option, { exact: typeof option === 'string' }).click();
}
test('invalid student number explains why confirm is disabled and recovers after editing', async ({ page }) => {
const project = { ...mixed(), student_no: '1', name: '1', topic: '1' };
delete (project as any).folder_identity;
for (const file of project.files) delete (file as any).detected_identity;
await setup(page, project);
await expect(page.getByText(/学号必须为5–20位数字/)).toBeVisible();
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeDisabled();
await page.getByRole('textbox', { name: '学号 test/student', exact: true }).fill('9000000001');
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeEnabled();
});
test('ignoring a foreign PPT recalculates identity and submits the intended project', async ({ page }) => {
const imports = await setup(page, mixed());
await expect(page.getByText('学生乙(9000000002)', { exact: true })).toBeVisible();
await expect(page.getByText(/学号冲突:/)).toBeVisible();
await choose(page, '处理 test/student/汇报PPT.pdf', '忽略');
await expect(page.getByRole('textbox', { name: '学号 test/student', exact: true })).toHaveValue(identity.student_no);
await expect(page.getByRole('textbox', { name: '姓名 test/student', exact: true })).toHaveValue(identity.name);
await expect(page.getByText(/学号冲突:/)).toHaveCount(0);
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeEnabled();
await page.getByRole('button', { name: '确认导入', exact: true }).click();
await expect.poll(() => imports.length).toBe(1);
expect(imports[0].projects[0].student_no).toBe(identity.student_no);
expect(imports[0].projects[0].files[1].status).toBe('ignored');
});
test('file selection preserves manual corrections until the user chooses cover values', async ({ page }) => {
await setup(page, mixed());
const name = page.getByRole('textbox', { name: '姓名 test/student', exact: true });
await name.fill('人工修正姓名');
await choose(page, '处理 test/student/汇报PPT.pdf', '忽略');
await expect(name).toHaveValue('人工修正姓名');
await page.getByRole('button', { name: '使用所选资料的封面信息', exact: true }).click();
await expect(name).toHaveValue(identity.name);
});
test('ignoring all files gives an explicit selected-material error', async ({ page }) => {
await setup(page, mixed());
await choose(page, '处理 test/student/汇报PPT.pdf', '忽略');
await choose(page, '处理 test/student/最终报告.pdf', '忽略');
await expect(page.getByText(/至少选择1份资料/)).toBeVisible();
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeDisabled();
});
test('unknown and duplicate material types explain the block before sending a request', async ({ page }) => {
const project = mixed();
project.files[1].category = 'unknown';
project.files[1].detected_identity = identity;
await setup(page, project);
await expect(page.getByText(/请选择材料类型/)).toBeVisible();
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeDisabled();
await choose(page, '材料类型 test/student/汇报PPT.pdf', '最终报告');
await expect(page.getByText(/最终报告选了2份/)).toBeVisible();
await choose(page, '处理 test/student/汇报PPT.pdf', '忽略');
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeEnabled();
});