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() })