刘光辉
6 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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, /读取失败/);
});