import { readFileSync } from 'node:fs';
|
|
import { unref } from 'vue';
|
|
import ts from 'typescript';
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { createUploadEditCoordinator } from '#/components/Jnpf/upload/src/uploadEditCoordinator';
|
|
// Run the actual SFC handlers together, without mounting unrelated designer controls.
|
function loadHandlers(path: string, names: string[], indent: string, dependencies: Record<string, any>) {
|
const source = readFileSync(path, 'utf8');
|
const functions = names.map((name) => {
|
const match = source.match(new RegExp(`${indent}(?:async )?function ${name}\\([^]*?\\n${indent}\\}`));
|
if (!match) throw new Error(`Missing handler ${name}`);
|
return match[0];
|
});
|
const code = ts.transpileModule(`const { ${Object.keys(dependencies).join(', ')} } = deps;\n${functions.join('\n')}\nreturn { ${names.join(', ')} };`, {
|
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.None },
|
}).outputText;
|
return new Function('deps', code)(dependencies);
|
}
|
|
function harness() {
|
const rows = [{ id: 'existing', name: '' }, { name: 'unfinished' }];
|
const tableState = {
|
dataSource: rows,
|
tableFormData: rows.map(() => [{ __vModel__: 'name', required: true, regList: [], config: { __config__: {} }, valid: true }]),
|
};
|
const checkRegData = vi.fn(() => true);
|
const table = loadHandlers(
|
'apps/jnpf-web-apps-main/src/components/Jnpf/InputTable/src/InputTable.vue',
|
['submit', 'getDataSource', 'checkData', 'clearCellValidation'],
|
'',
|
{ props: { config: {} }, state: tableState, checkRegData },
|
);
|
const state = { formData: { title: '', items: [] }, tableRefs: { items: { tableRef: table } } };
|
const validate = vi.fn().mockRejectedValue(new Error('title is required'));
|
const prepare = vi.fn().mockResolvedValue({ draftId: 'file-draft', revision: 3 });
|
const beforeSubmit = vi.fn();
|
const confirmSign = vi.fn().mockResolvedValue({ approved: true, bizSign: 'signature' });
|
const submit = vi.fn();
|
const toast = vi.fn();
|
const claimDisplayedBillNumbers = vi.fn().mockResolvedValue(true);
|
const parser = loadHandlers(
|
'apps/jnpf-web-apps-main/src/components/FormGenerator/src/components/Parser.vue',
|
['checkTableData', 'handleValidate', 'handleSubmit'],
|
' ',
|
{
|
state,
|
props: { requireReview: false, deferBillNumberClaim: true },
|
unref,
|
isTableValid: { value: false },
|
formElRef: { value: { validate } },
|
isUnreachedControlledStepField: () => false,
|
isVirtualTable: () => false,
|
getFieldByVModel: () => ({}),
|
isReviewRequired: () => false,
|
reviewPassed: { value: true },
|
uploadEditors: new Set([{ prepare }]),
|
uploadCoordinator: createUploadEditCoordinator(vi.fn()),
|
onlineUtils: { toast },
|
claimDisplayedBillNumbers,
|
beforeSubmit,
|
confirmSign,
|
submit,
|
},
|
);
|
return { ...parser, state, rows, tableState, checkRegData, validate, prepare, beforeSubmit, confirmSign, submit, toast };
|
}
|
|
describe('workflow draft collection', () => {
|
it('saves incomplete main and child fields, retaining current rows and prepared attachments', async () => {
|
const h = harness();
|
// Include edits and a new row beyond the initially loaded data.
|
h.rows[1]!.name = 'latest edit';
|
h.rows.push({ name: '' });
|
|
expect(await h.handleSubmit(true)).toBe(true);
|
|
expect(h.state.formData.items).toEqual(h.rows);
|
expect(h.state.formData).toHaveProperty('__jnpfUploadEdit', [{ draftId: 'file-draft', revision: 3 }]);
|
expect(h.validate).not.toHaveBeenCalled();
|
expect(h.checkRegData).not.toHaveBeenCalled();
|
expect(h.beforeSubmit).not.toHaveBeenCalled();
|
expect(h.confirmSign).not.toHaveBeenCalled();
|
expect(h.submit).toHaveBeenCalledOnce();
|
});
|
|
it('still rejects missing required child values on formal submission after a successful draft', async () => {
|
const h = harness();
|
await h.handleSubmit(true);
|
h.submit.mockClear();
|
|
expect(await h.handleSubmit()).toBe(false);
|
|
expect(h.tableState.tableFormData[0]![0]!.valid).toBe(false);
|
expect(h.submit).not.toHaveBeenCalled();
|
});
|
|
it('still rejects missing required main values on formal submission', async () => {
|
const h = harness();
|
h.rows.forEach((row) => (row.name = 'complete child'));
|
|
expect(await h.handleSubmit()).toBe(false);
|
|
expect(h.validate).toHaveBeenCalledOnce();
|
expect(h.beforeSubmit).not.toHaveBeenCalled();
|
expect(h.submit).not.toHaveBeenCalled();
|
});
|
|
it('defers child business rules until formal submission', async () => {
|
const h = harness();
|
h.rows.forEach((row) => (row.name = 'invalid business value'));
|
h.tableState.tableFormData[0]![0]!.regList = [{}] as any;
|
h.checkRegData.mockReturnValue(false);
|
|
expect(await h.handleSubmit(true)).toBe(true);
|
expect(h.checkRegData).not.toHaveBeenCalled();
|
h.submit.mockClear();
|
|
expect(await h.handleSubmit()).toBe(false);
|
expect(h.checkRegData).toHaveBeenCalledOnce();
|
expect(h.submit).not.toHaveBeenCalled();
|
});
|
|
it.each([true, false])('blocks saving when attachment preparation fails (draft=%s)', async (isSave) => {
|
const h = harness();
|
h.prepare.mockRejectedValue(new Error('附件仍在编辑'));
|
|
expect(await h.handleSubmit(isSave)).toBe(false);
|
|
expect(h.toast).toHaveBeenCalledWith('附件仍在编辑');
|
expect(h.submit).not.toHaveBeenCalled();
|
});
|
|
it('preserves validation, pre-submit, approval check and signature for formal submission', async () => {
|
const h = harness();
|
h.rows.forEach((row) => (row.name = 'complete'));
|
h.validate.mockResolvedValue({ title: 'complete' });
|
const approvalCheck = vi.fn(async () => true);
|
|
expect(await h.handleSubmit(false, approvalCheck)).toBe(true);
|
|
expect(h.validate).toHaveBeenCalledOnce();
|
expect(h.beforeSubmit).toHaveBeenCalledOnce();
|
expect(approvalCheck).toHaveBeenCalledOnce();
|
expect(h.confirmSign).toHaveBeenCalledOnce();
|
expect(h.submit).toHaveBeenCalledExactlyOnceWith('signature');
|
});
|
});
|