Files
kaidi/frontend/tests/forms-projects-workflow.test.ts
T
2026-08-17 14:08:40 +08:00

87 lines
5.6 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
const sourceApi = readFileSync(resolve(process.cwd(), 'src/api/source.ts'), 'utf8');
const projectApi = readFileSync(resolve(process.cwd(), 'src/api/project.ts'), 'utf8');
const formCenter = readFileSync(resolve(process.cwd(), 'src/pages/forms/FormCenterPage.vue'), 'utf8');
const formEditor = readFileSync(resolve(process.cwd(), 'src/pages/forms/FormEditorPage.vue'), 'utf8');
const taskCenter = readFileSync(resolve(process.cwd(), 'src/pages/tasks/TaskCenterPage.vue'), 'utf8');
const projectDetail = readFileSync(resolve(process.cwd(), 'src/pages/projects/ProjectDetailPage.vue'), 'utf8');
const accountingPage = readFileSync(resolve(process.cwd(), 'src/pages/finance/AccountingPage.vue'), 'utf8');
const projectArchives = readFileSync(resolve(process.cwd(), 'src/pages/archives/ProjectArchivesPage.vue'), 'utf8');
const archiveFiles = readFileSync(resolve(process.cwd(), 'src/pages/archives/ArchiveFilesPage.vue'), 'utf8');
const projectSubresourcePanel = readFileSync(
resolve(process.cwd(), 'src/components/business/project/ProjectSubresourcePanel.vue'),
'utf8',
);
describe('pAGE-07 through PAGE-11 command controls', () => {
it('adds command idempotency headers to source and project writes', () => {
expect(sourceApi).toContain("commandHeaders('source-form-create', idempotencyKey)");
expect(sourceApi).toContain("commandHeaders('source-form-submit', idempotencyKey)");
expect(sourceApi).toContain('commandHeaders(`workflow-task-' + '$' + '{action}`, idempotencyKey)');
expect(sourceApi).toContain("commandHeaders('workflow-instance-withdraw', idempotencyKey)");
expect(sourceApi).toContain("commandHeaders('workflow-instance-void', idempotencyKey)");
expect(sourceApi).toContain("commandHeaders('source-import-create', idempotencyKey)");
expect(sourceApi).toContain("commandHeaders('source-import-confirm', idempotencyKey)");
expect(projectApi).toContain("commandHeaders('project-risk-create', idempotencyKey)");
expect(projectApi).toContain("commandHeaders('project-risk-resolve', idempotencyKey)");
});
it('gates mutations with server allowedActions and re-reads server state', () => {
expect(formCenter).toContain("!row.allowedActions.includes('VALIDATE')");
expect(formCenter).toContain('await loadForms();');
expect(formEditor).toContain("detail.value.summary.allowedActions.includes('SUBMIT')");
expect(formEditor).toContain('await loadDetail();');
expect(taskCenter).toContain('if (!canRunAction(task, nextAction)) return;');
expect(taskCenter).toContain('await getWorkflowTask(task.publicId);');
expect(taskCenter).toContain('task.form.allowedActions.includes(code)');
expect(taskCenter).toContain('await withdrawSourceForm(');
expect(taskCenter).toContain('await voidSourceForm(');
expect(taskCenter).toContain('const detail = await getWorkflowTask(task.publicId);');
expect(taskCenter).not.toContain("opinion: '审批通过'");
expect(projectDetail).toContain("!project.value.allowedActions.includes('CREATE_RISK_FLAG')");
expect(projectDetail).toContain('await loadProject();');
});
it('uses active source template metadata and TDesign controls for projection fields', () => {
expect(sourceApi).toContain('suppliers: Array.isArray(references?.suppliers)');
expect(formEditor).toContain('const active = (await getSourceTemplates()).find');
expect(formEditor).not.toContain('getSourceTemplate(formType.value, 1)');
expect(formEditor).toContain("field.code.toLowerCase().includes('supplier')");
expect(formEditor).toContain("column.dataType === 'ENUM'");
expect(formEditor).toContain("column.dataType === 'DATE'");
expect(formEditor).toContain('detail.value?.fields || template.value?.fields');
expect(formEditor).toContain('<t-select');
expect(formEditor).toContain('<t-date-picker');
});
it('keeps project drill-down rows traceable to their unique source', () => {
expect(projectSubresourcePanel).toContain('#businessNo="{ row }"');
expect(projectSubresourcePanel).toContain("$emit('open-row', row)");
expect(projectDetail).toContain('@open-row="openDrillRow(tab.value, $event)"');
expect(projectDetail).toContain("name: 'FormEditor'");
expect(projectDetail).toContain('query: { from: route.fullPath }');
expect(projectDetail).toContain("openLinkedPage('/finance/payments'");
expect(projectDetail).toContain("openLinkedPage('/finance/accounting'");
expect(projectDetail).toContain("openLinkedPage('/archives/files'");
expect(formEditor).toContain("from.startsWith('/projects/')");
});
it('keeps project accounting and archive drill-down bidirectional across lifecycle states', () => {
expect(projectDetail).toContain("voucherStatus: textValue(row.voucherStatus, '')");
expect(projectDetail).toContain("sortAt: textValue(isVoucher ? row.updatedAt : row.createdAt, '')");
expect(projectDetail).toContain('return rightTime - leftTime;');
expect(projectDetail).toContain('from: route.fullPath');
expect(accountingPage).toContain("name: 'ProjectDetail'");
expect(accountingPage).toContain('function openEventSource(event: AccountingEvent)');
expect(accountingPage).toContain("route.name !== 'Accounting'");
expect(formCenter).toContain("name: 'ProjectDetail'");
expect(projectArchives).toContain('function openSnapshot(snapshot: ArchivePackageObjectSnapshot)');
expect(projectArchives).toContain("ACCOUNTING_VOUCHER: { path: '/finance/accounting'");
expect(archiveFiles).toContain("name: 'ProjectDetail'");
});
});