87 lines
5.6 KiB
TypeScript
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'");
|
|
});
|
|
});
|