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