243 lines
7.8 KiB
JavaScript
243 lines
7.8 KiB
JavaScript
|
|
const { test } = require('node:test')
|
|||
|
|
const assert = require('node:assert/strict')
|
|||
|
|
const fs = require('node:fs')
|
|||
|
|
const path = require('node:path')
|
|||
|
|
const vm = require('node:vm')
|
|||
|
|
const { createRequire } = require('node:module')
|
|||
|
|
const vue = require('vue')
|
|||
|
|
const esbuild = createRequire(require.resolve('vite'))('esbuild')
|
|||
|
|
const flowModule = { exports: {} }
|
|||
|
|
vm.runInNewContext(
|
|||
|
|
esbuild.transformSync(
|
|||
|
|
fs.readFileSync(path.join(__dirname, '../src/views/health/identity/collectionFlow.ts'), 'utf8'),
|
|||
|
|
{ loader: 'ts', format: 'cjs' }
|
|||
|
|
).code,
|
|||
|
|
{ module: flowModule, exports: flowModule.exports }
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 加载实际 composable,仅替换 HTTP 与组件卸载回调,无浏览器或 DOM 依赖。
|
|||
|
|
function fixture(initialDraft = null) {
|
|||
|
|
const calls = []
|
|||
|
|
const backend = {
|
|||
|
|
getCollectionDraft: async () => initialDraft,
|
|||
|
|
saveCollectionDraft: async (payload) => {
|
|||
|
|
calls.push(JSON.parse(JSON.stringify(payload)))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const module = { exports: {} }
|
|||
|
|
const source = fs.readFileSync(
|
|||
|
|
path.join(__dirname, '../src/views/health/identity/useCollectionDraft.ts'),
|
|||
|
|
'utf8'
|
|||
|
|
)
|
|||
|
|
const code = esbuild.transformSync(source, { loader: 'ts', format: 'cjs' }).code
|
|||
|
|
vm.runInNewContext(code, {
|
|||
|
|
module,
|
|||
|
|
exports: module.exports,
|
|||
|
|
require: (id) =>
|
|||
|
|
id === 'vue'
|
|||
|
|
? { ...vue, onBeforeUnmount: () => {} }
|
|||
|
|
: id === './collectionFlow'
|
|||
|
|
? flowModule.exports
|
|||
|
|
: backend,
|
|||
|
|
// 测试手动触发保存,避免真实的一秒定时器影响执行和结果。
|
|||
|
|
setTimeout: () => 1,
|
|||
|
|
clearTimeout: () => {}
|
|||
|
|
})
|
|||
|
|
const data = vue.ref({
|
|||
|
|
identityType: 10,
|
|||
|
|
realName: '',
|
|||
|
|
gender: 3,
|
|||
|
|
consentConfirmed: true,
|
|||
|
|
password: 'never-store-this'
|
|||
|
|
})
|
|||
|
|
const accountMode = vue.ref('new')
|
|||
|
|
const activeStep = vue.ref(1)
|
|||
|
|
const visible = vue.ref(true)
|
|||
|
|
const readOnly = vue.ref(false)
|
|||
|
|
const draft = module.exports.useCollectionDraft(data, accountMode, activeStep, visible, readOnly)
|
|||
|
|
return { data, accountMode, activeStep, visible, readOnly, draft, calls, backend }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test('restores incomplete fields and step without restoring password', async () => {
|
|||
|
|
const f = fixture({
|
|||
|
|
currentStep: 2,
|
|||
|
|
fields: { realName: '测试老人', educationLevel: null, accountMode: 'existing' }
|
|||
|
|
})
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
assert.equal(f.data.value.realName, '测试老人')
|
|||
|
|
assert.equal(f.activeStep.value, 2)
|
|||
|
|
assert.equal(f.accountMode.value, 'existing')
|
|||
|
|
assert.equal(f.data.value.password, undefined)
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('normalizes cleared options while preserving zero and false; excludes password', async () => {
|
|||
|
|
const f = fixture()
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
Object.assign(f.data.value, {
|
|||
|
|
maritalStatus: 0,
|
|||
|
|
elderlyUniversity: false,
|
|||
|
|
residenceType: '',
|
|||
|
|
educationLevel: undefined
|
|||
|
|
})
|
|||
|
|
await f.draft.saveDraftNow()
|
|||
|
|
const fields = f.calls[0].fields
|
|||
|
|
assert.equal(fields.maritalStatus, 0)
|
|||
|
|
assert.equal(fields.elderlyUniversity, false)
|
|||
|
|
assert.equal(fields.residenceType, null)
|
|||
|
|
assert.equal(fields.educationLevel, null)
|
|||
|
|
assert.equal('password' in fields, false)
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('serializes saves so delayed earlier requests cannot overwrite newer input', async () => {
|
|||
|
|
const f = fixture()
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
let release
|
|||
|
|
f.backend.saveCollectionDraft = async (payload) => {
|
|||
|
|
f.calls.push(JSON.parse(JSON.stringify(payload)))
|
|||
|
|
if (f.calls.length === 1)
|
|||
|
|
await new Promise((resolve) => {
|
|||
|
|
release = resolve
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
f.data.value.realName = '较早输入'
|
|||
|
|
const first = f.draft.saveDraftNow()
|
|||
|
|
await Promise.resolve()
|
|||
|
|
await Promise.resolve()
|
|||
|
|
f.data.value.realName = '最新输入'
|
|||
|
|
const second = f.draft.saveDraftNow()
|
|||
|
|
assert.equal(f.calls.length, 1)
|
|||
|
|
release()
|
|||
|
|
await first
|
|||
|
|
await second
|
|||
|
|
assert.equal(f.calls[1].fields.realName, '最新输入')
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('pausing before a formal submission prevents close from recreating the old draft', async () => {
|
|||
|
|
const f = fixture()
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
f.data.value.realName = '正式提交'
|
|||
|
|
await f.draft.saveDraftNow()
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
f.visible.value = false
|
|||
|
|
await vue.nextTick()
|
|||
|
|
await Promise.resolve()
|
|||
|
|
assert.equal(f.calls.length, 1)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('restores seventh-step nested drafts and excludes server metadata from subsequent saves', async () => {
|
|||
|
|
const f = fixture({
|
|||
|
|
currentStep: 7,
|
|||
|
|
fields: {
|
|||
|
|
consentConfirmed: true,
|
|||
|
|
chronicDiseaseStatus: 1,
|
|||
|
|
chronicDiseases: [{ code: 'diabetes', currentDisease: false }],
|
|||
|
|
emergencyContacts: [
|
|||
|
|
{
|
|||
|
|
name: '联系人',
|
|||
|
|
relationship: 'child',
|
|||
|
|
phone: '13900000001',
|
|||
|
|
primary: true,
|
|||
|
|
contactOnly: true
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
assert.equal(f.activeStep.value, 7)
|
|||
|
|
assert.equal(f.data.value.chronicDiseases[0].currentDisease, false)
|
|||
|
|
Object.assign(f.data.value, {
|
|||
|
|
collectorId: 999,
|
|||
|
|
collectionCompletedTime: 'forged',
|
|||
|
|
incomeRange: 0,
|
|||
|
|
disabled: false
|
|||
|
|
})
|
|||
|
|
await f.draft.saveDraftNow()
|
|||
|
|
assert.equal(f.calls[0].currentStep, 7)
|
|||
|
|
assert.equal(f.calls[0].fields.incomeRange, 0)
|
|||
|
|
assert.equal(f.calls[0].fields.disabled, false)
|
|||
|
|
assert.equal('collectorId' in f.calls[0].fields, false)
|
|||
|
|
assert.equal('collectionCompletedTime' in f.calls[0].fields, false)
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('queued snapshots keep the original nested contacts while later edits save separately', async () => {
|
|||
|
|
const f = fixture()
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
let release
|
|||
|
|
f.backend.saveCollectionDraft = async (payload) => {
|
|||
|
|
if (f.calls.length === 0)
|
|||
|
|
await new Promise((resolve) => {
|
|||
|
|
release = resolve
|
|||
|
|
})
|
|||
|
|
f.calls.push(JSON.parse(JSON.stringify(payload)))
|
|||
|
|
}
|
|||
|
|
f.data.value.emergencyContacts = [
|
|||
|
|
{
|
|||
|
|
name: '原联系人',
|
|||
|
|
relationship: 'child',
|
|||
|
|
phone: '13900000001',
|
|||
|
|
primary: true,
|
|||
|
|
contactOnly: true
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
const first = f.draft.saveDraftNow()
|
|||
|
|
await Promise.resolve()
|
|||
|
|
await Promise.resolve()
|
|||
|
|
f.data.value.emergencyContacts[0].name = '修改联系人'
|
|||
|
|
const second = f.draft.saveDraftNow()
|
|||
|
|
release()
|
|||
|
|
await first
|
|||
|
|
await second
|
|||
|
|
assert.equal(f.calls[0].fields.emergencyContacts[0].name, '原联系人')
|
|||
|
|
assert.equal(f.calls[1].fields.emergencyContacts[0].name, '修改联系人')
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('unconfirmed consent limits autosave to basic identity and contact fields', async () => {
|
|||
|
|
const f = fixture()
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
Object.assign(f.data.value, {
|
|||
|
|
consentConfirmed: false,
|
|||
|
|
realName: '基础姓名',
|
|||
|
|
contactPhone: '13800000001',
|
|||
|
|
idCard: 'must-not-upload',
|
|||
|
|
birthDate: '1949-01-01',
|
|||
|
|
chronicDiseases: [{ code: 'diabetes' }],
|
|||
|
|
medicalInsuranceNumber: 'must-not-upload',
|
|||
|
|
emergencyContacts: [{ name: '敏感联系人' }]
|
|||
|
|
})
|
|||
|
|
await f.draft.saveDraftNow()
|
|||
|
|
const fields = f.calls[0].fields
|
|||
|
|
assert.equal(fields.realName, '基础姓名')
|
|||
|
|
assert.equal(fields.contactPhone, '13800000001')
|
|||
|
|
assert.equal(fields.consentConfirmed, false)
|
|||
|
|
for (const key of [
|
|||
|
|
'idCard',
|
|||
|
|
'birthDate',
|
|||
|
|
'medicalInsuranceNumber',
|
|||
|
|
'chronicDiseases',
|
|||
|
|
'emergencyContacts'
|
|||
|
|
])
|
|||
|
|
assert.equal(key in fields, false)
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('failed draft initialization can be retried without losing current user input', async () => {
|
|||
|
|
const f = fixture()
|
|||
|
|
f.backend.getCollectionDraft = async () => {
|
|||
|
|
throw new Error('offline')
|
|||
|
|
}
|
|||
|
|
await f.draft.initializeDraft()
|
|||
|
|
assert.equal(f.draft.draftFailed.value, true)
|
|||
|
|
f.data.value.realName = '当前输入'
|
|||
|
|
f.backend.getCollectionDraft = async () => ({ currentStep: 1, fields: { realName: '较早草稿' } })
|
|||
|
|
await f.draft.saveDraftNow()
|
|||
|
|
assert.equal(f.calls[0].fields.realName, '当前输入')
|
|||
|
|
assert.equal(f.data.value.realName, '当前输入')
|
|||
|
|
assert.equal(f.draft.draftFailed.value, false)
|
|||
|
|
await f.draft.pauseDraft()
|
|||
|
|
})
|