const { test } = require('node:test');
|
const assert = require('node:assert/strict');
|
const fs = require('node:fs');
|
const vm = require('node:vm');
|
const path = require('node:path');
|
const runtime = vm.runInThisContext(fs.readFileSync(path.join(__dirname, '../../main/resources/forms/errata-revision/runtime.js'), 'utf8'))();
|
const raw = (id = 'e1') => ({ kanwu_zixiang_id: id, kanwu_shenqing_id: 'a1', wenjian_id: 'file', banben_id: 'v1', wenjian_bianhao: 'SOP-1', kanwu_neirong: '修正温度', fujian: [] });
|
const source = (rows = [raw()]) => ({ xiuding_shenqing_zixiang_id: 'request1', wenjian_id: 'file', yuan_wenjian_banben_id: 'v1', kanwu_mingxi: JSON.stringify(rows) });
|
function args(rows = [raw()]) {
|
const formData = { xiuding_shenqing_zixiang_id: 'request1', wenjian_id: 'file', yuan_wenjian_banben_id: 'v1', kanwu_chuli_mingxi: runtime.parseRows(rows) };
|
return { formData, onlineUtils: { request: async () => ({ data: [source(rows)] }), getUserInfo: () => ({ userId: 'drafter' }), toast: () => {} },
|
setFormData(key, value, i) { const [table, field] = key.split('.'); if (field) formData[table][i][field] = value; else formData[key] = value; } };
|
}
|
function handled(a, conclusion = 'yixiugai_queren') {
|
a.formData.kanwu_chuli_mingxi.forEach(row => Object.assign(row, { chuli_jielun: conclusion, chuli_yuanyin: '已在第 2 节修订', chuli_ren_id: 'drafter', chuli_shijian: 123 }));
|
}
|
test('accept arrays and JSON text, reject malformed and PGobject responses', () => {
|
assert.equal(runtime.parseRows([raw()]).length, 1);
|
assert.equal(runtime.parseRows(JSON.stringify([raw()]))[0].kanwu_zixiang_id, 'e1');
|
for (const invalid of [null, undefined, {}, '', '{', { type: 'jsonb', value: '[]' }, [raw(), raw()], [{ ...raw(), fujian: '[]' }]]) assert.throws(() => runtime.parseRows(invalid));
|
});
|
test('fresh rows have no default conclusion or inherited native child id', () => {
|
const row = runtime.mergeRows([], runtime.parseRows([{ ...raw(), f_id: 'source-id' }]))[0];
|
assert.equal(row.chuli_jielun, ''); assert.equal(row.chuli_ren_id, ''); assert.equal(row.f_id, undefined);
|
});
|
test('preserve draft decisions and native id only for unchanged source content', () => {
|
const a = args(); handled(a); a.formData.kanwu_chuli_mingxi[0].id = 'native-row';
|
const same = runtime.mergeRows(a.formData.kanwu_chuli_mingxi, runtime.parseRows([raw()]));
|
assert.equal(same[0].chuli_jielun, 'yixiugai_queren'); assert.equal(same[0].id, 'native-row');
|
const changed = runtime.mergeRows(same, runtime.parseRows([{ ...raw(), kanwu_neirong: 'changed' }]));
|
assert.equal(changed[0].chuli_jielun, ''); assert.equal(changed[0].chuli_yuanyin, ''); assert.equal(changed[0].id, 'native-row');
|
});
|
test('both decisions require a reason and actor/time', async () => {
|
for (const conclusion of ['yixiugai_queren', 'bucaina']) { const a = args(); handled(a, conclusion); await runtime.beforeAuditSubmit(a); }
|
for (const override of [{ chuli_jielun: 'yes' }, { chuli_yuanyin: ' ' }, { chuli_ren_id: '' }, { chuli_shijian: null }]) {
|
const a = args(); handled(a); Object.assign(a.formData.kanwu_chuli_mingxi[0], override); await assert.rejects(runtime.beforeAuditSubmit(a));
|
}
|
});
|
test('deleted or newly approved notes are restored and block the current submit', async () => {
|
const a = args([raw(), raw('e2')]); handled(a); a.formData.kanwu_chuli_mingxi.pop();
|
await assert.rejects(runtime.beforeAuditSubmit(a), /刷新/);
|
assert.equal(a.formData.kanwu_chuli_mingxi.length, 2);
|
assert.equal(a.formData.kanwu_chuli_mingxi[0].chuli_jielun, 'yixiugai_queren');
|
assert.equal(a.formData.kanwu_chuli_mingxi[1].chuli_jielun, '');
|
});
|
test('source edits invalidate the old decision and restore the source snapshot', async () => {
|
const a = args(); handled(a); a.formData.kanwu_chuli_mingxi[0].kanwu_neirong = 'tampered';
|
await assert.rejects(runtime.beforeAuditSubmit(a), /刷新/);
|
assert.equal(a.formData.kanwu_chuli_mingxi[0].kanwu_neirong, raw().kanwu_neirong);
|
assert.equal(a.formData.kanwu_chuli_mingxi[0].chuli_jielun, '');
|
});
|
test('query errors, missing source and unknown arrays never become an empty success', async () => {
|
for (const response of [{}, { data: [] }, { data: [{ ...source(), kanwu_mingxi: {} }] }]) {
|
const a = args(); handled(a); a.onlineUtils.request = async () => response; await assert.rejects(runtime.beforeAuditSubmit(a));
|
assert.equal(a.formData.kanwu_chuli_mingxi.length, 1);
|
}
|
const a = args(); a.onlineUtils.request = async () => { throw new Error('offline'); }; await assert.rejects(runtime.beforeAuditSubmit(a), /offline/);
|
});
|
test('source switching during async query does not overwrite the new selection', async () => {
|
const a = args(); a.onlineUtils.request = async () => { a.formData.xiuding_shenqing_zixiang_id = 'other'; return { data: [source()] }; };
|
await assert.rejects(runtime.beforeAuditSubmit(a), /切换/);
|
assert.equal(a.formData.kanwu_chuli_mingxi.length, 1);
|
});
|
test('history onLoad does not query or rewrite saved handling', async () => {
|
const a = args(); a.formData.id = 'saved'; handled(a); a.onlineUtils.request = async () => assert.fail('history queried');
|
await runtime.auditOnLoad(a); assert.equal(a.formData.kanwu_chuli_mingxi[0].chuli_shijian, 123);
|
});
|
test('decision edits stamp the operator and time; opening and submitting preserve them', async () => {
|
const a = args(); handled(a); a.rowIndex = 0; runtime.decisionChange(a);
|
const stamp = a.formData.kanwu_chuli_mingxi[0].chuli_shijian; assert.ok(stamp > 123);
|
await runtime.beforeAuditSubmit(a); assert.equal(a.formData.kanwu_chuli_mingxi[0].chuli_shijian, stamp);
|
});
|
test('no approved notes is a valid empty result', async () => { await runtime.beforeAuditSubmit(args([])); });
|
test('request reminder refresh blocks once, then submits after review', async () => {
|
const a = args(); a.formData.xiuding_shenqing_zixiang = [{ wenjian_id: 'file', yuan_wenjian_banben_id: 'v1', kanwu_tixing: '' }];
|
await assert.rejects(runtime.beforeRequestSubmit(a), /更新/);
|
assert.equal(a.formData.xiuding_shenqing_zixiang[0].kanwu_tixing, '是');
|
await runtime.beforeRequestSubmit(a);
|
});
|
test('request version or selection changes during query block submit', async () => {
|
const a = args(); a.formData.xiuding_shenqing_zixiang = [{ wenjian_id: 'file', yuan_wenjian_banben_id: 'old' }];
|
await assert.rejects(runtime.beforeRequestSubmit(a), /版本/);
|
a.formData.xiuding_shenqing_zixiang[0].yuan_wenjian_banben_id = 'v1';
|
a.onlineUtils.request = async () => { a.formData.xiuding_shenqing_zixiang = []; return { data: [source()] }; };
|
await assert.rejects(runtime.beforeRequestSubmit(a), /清单/);
|
});
|
test('request indicator distinguishes approved notes, empty results, cleared selection and errors', () => {
|
const a = args(); a.rowIndex = 0;
|
a.formData.xiuding_shenqing_zixiang = [{}];
|
const row = a.formData.xiuding_shenqing_zixiang[0];
|
for (const [data, expected] of [[source(), '是'], [source([]), '否'], [null, '']]) {
|
a.data = data; runtime.requestSourceChange(a); assert.equal(row.kanwu_tixing, expected);
|
}
|
a.data = { ...source(), kanwu_mingxi: null };
|
runtime.requestSourceChange(a); assert.match(row.kanwu_tixing, /读取失败/);
|
});
|