fix: explain import blockers and recompute selected identities
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user