elder-yudao-pro/yudao-ui-admin-vue3/tests/collection-wizard.test.cjs

466 lines
16 KiB
JavaScript
Raw Normal View History

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 { parse, compileScript } = require('vue/compiler-sfc')
const esbuild = createRequire(require.resolve('vite'))('esbuild')
const Schema = createRequire(require.resolve('element-plus'))('async-validator').default
const privacyModule = { exports: {} }
vm.runInNewContext(
esbuild.transformSync(
fs.readFileSync(path.join(__dirname, '../src/utils/healthPrivacy.ts'), 'utf8'),
{ loader: 'ts', format: 'cjs' }
).code,
{ module: privacyModule, exports: privacyModule.exports }
)
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 }
)
const formPath = path.join(__dirname, '../src/views/health/identity/IdentityForm.vue')
const { descriptor } = parse(fs.readFileSync(formPath, 'utf8'), { filename: formPath })
const script = compileScript(descriptor, { id: 'collection-wizard-test' })
const compiled = esbuild.transformSync(script.content, { loader: 'ts', format: 'cjs' }).code
const identityModule = { exports: {} }
vm.runInNewContext(
esbuild.transformSync(fs.readFileSync(path.join(__dirname, '../src/utils/idCard.ts'), 'utf8'), {
loader: 'ts',
format: 'cjs'
}).code,
{ module: identityModule, exports: identityModule.exports }
)
// 执行实际 Vue 表单逻辑,用真实 async-validator 校验规则,仅替换 HTTP 与渲染组件。
function fixture({ identity, draft } = {}) {
const requests = []
const warnings = []
const draftTransport = { saveError: null }
const api = {
identityTypes: [],
genderOptions: [],
ethnicities: [],
residenceTypeOptions: [],
livingStatusOptions: [],
householdTypeOptions: [],
educationLevelOptions: [],
getIdentityAreaOptions: async () => [],
getIdentity: async () => ({ ...identity }),
createIdentity: async (payload) => {
requests.push({ method: 'create', payload: { ...payload } })
return 123
},
updateIdentity: async (payload) => requests.push({ method: 'update', payload: { ...payload } })
}
const module = { exports: {} }
vm.runInNewContext(compiled, {
module,
exports: module.exports,
RegExp,
require: (id) => {
if (id === 'vue') return vue
if (id === '@/api/health') return api
if (id === '@/utils/idCard') return identityModule.exports
if (id === '@/utils/healthPrivacy') return privacyModule.exports
if (id === '@/hooks/web/useMessage')
return {
useMessage: () => ({ warning: (value) => warnings.push(value), success: () => {} })
}
if (id === './useCollectionDraft')
return {
useCollectionDraft: (data, accountMode, activeStep, _visible, readOnly) => ({
draftMessage: vue.ref('填写过程中会自动保存草稿,密码需要重新输入。'),
draftSaving: vue.ref(false),
draftFailed: vue.ref(false),
initializeDraft: async () => {
if (draft && !readOnly.value) {
data.value = { ...data.value, ...draft.fields, password: undefined }
accountMode.value = draft.fields.accountMode || 'new'
activeStep.value = draft.currentStep
}
},
saveDraftNow: async () => {
if (draftTransport.saveError) throw draftTransport.saveError
},
pauseDraft: async () => {},
resumeDraft: () => {}
})
}
if (
[
'./SocialSupport.vue',
'./CollectionHealthSteps.vue',
'./CollectionFamilySteps.vue'
].includes(id)
)
return {}
if (id === './collectionFlow') return flowModule.exports
throw new Error(`Unexpected import: ${id}`)
}
})
const scope = vue.effectScope()
const form = scope.run(() =>
module.exports.default.setup({}, { expose: () => {}, emit: () => {} })
)
const validateFields = async (fields) => {
const keys = fields || Object.keys(form.rules.value)
const errors = {}
await Promise.all(
keys.map(async (key) => {
const rules = (form.rules.value[key] || []).map(({ trigger: _trigger, ...rule }) => rule)
const value = key.split('.').reduce((data, part) => data?.[part], form.data.value)
try {
await new Schema({ [key]: rules }).validate({ [key]: value })
} catch (error) {
Object.assign(errors, error.fields)
}
})
)
if (Object.keys(errors).length) throw errors
return true
}
form.formRef.value = {
validateField: validateFields,
validate: () => validateFields(),
clearValidate: () => {}
}
return { form, api, requests, warnings, draftTransport, stop: () => scope.stop() }
}
const basicInfo = {
realName: '测试老人',
idCard: '11010519491231002X',
gender: 2,
birthDate: '1949-12-31',
ethnicity: '汉族',
mobile: '13800000001',
password: 'test-only',
consentConfirmed: true
}
const socialInfo = {
residenceArea: '110105',
residenceAddress: ' 测试地址一号 ',
educationLevel: 1,
maritalStatus: 0,
elderlyUniversity: false
}
test('a new form starts on step one and incomplete required fields block advancing', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
assert.equal(f.form.activeStep.value, 1)
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 1)
assert.equal(f.form.showSocialInfo.value, false)
assert.equal(f.requests.length, 0)
assert.equal(f.warnings.length, 1)
})
test('valid basic information advances without requiring unfilled step two or creating an account', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo, { gender: 3, birthDate: undefined })
await f.form.handleIdCardInput('11010519491231002x')
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 2)
assert.equal(f.form.showBasicInfo.value, false)
assert.equal(f.form.showSocialInfo.value, true)
assert.equal(f.form.data.value.birthDate, '1949-12-31')
assert.equal(f.form.data.value.gender, 2)
assert.equal(f.requests.length, 0)
})
test('returning preserves inputs and checks required information again before advancing', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo)
await f.form.selectStep(2)
Object.assign(f.form.data.value, socialInfo)
await f.form.selectStep(1)
assert.equal(f.form.data.value.educationLevel, 1)
f.form.data.value.realName = ' '
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 1)
f.form.data.value.realName = '更正姓名'
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 2)
assert.equal(f.form.data.value.residenceAddress, socialInfo.residenceAddress)
})
test('calling submit from step one cannot bypass validation or create a partial formal record', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
await f.form.submit()
assert.equal(f.form.activeStep.value, 1)
Object.assign(f.form.data.value, basicInfo)
await f.form.submit()
assert.equal(f.form.activeStep.value, 2)
assert.equal(f.requests.length, 0)
})
const contact = {
name: '测试联系人',
relationship: 'child',
phone: '13900000001',
primary: true,
contactOnly: true
}
const goToStep = async (form, step) => {
while (form.activeStep.value < step) {
const before = form.activeStep.value
await form.selectStep(before + 1)
if (form.activeStep.value === before) break
}
}
test('seven sequential steps require address, education, care and contact before one complete save', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo)
await f.form.selectStep(2)
await f.form.selectStep(3)
assert.equal(f.form.activeStep.value, 2)
assert.equal(f.requests.length, 0)
Object.assign(f.form.data.value, socialInfo)
await goToStep(f.form, 4)
await f.form.selectStep(5)
assert.equal(f.form.activeStep.value, 4)
f.form.data.value.careLevel = 1
await goToStep(f.form, 7)
await f.form.submit()
assert.equal(f.requests.length, 0)
f.form.data.value.emergencyContacts = [{ ...contact }]
await f.form.submit()
assert.equal(f.requests.length, 1)
const saved = f.requests[0].payload
assert.equal(saved.collectionStep, 7)
assert.equal(saved.idCard, basicInfo.idCard)
assert.equal(saved.residenceAddress, '测试地址一号')
assert.equal(saved.maritalStatus, 0)
assert.equal(saved.elderlyUniversity, false)
assert.equal(saved.occupation, null)
assert.equal(saved.disabled, false)
assert.equal(saved.consentConfirmed, true)
assert.equal(f.form.visible.value, false)
assert.equal(f.form.data.value.password, undefined)
})
test('a draft on step two without the unsaved new-account password returns to step one', async (t) => {
const f = fixture({ draft: { currentStep: 2, fields: { ...basicInfo, ...socialInfo } } })
t.after(f.stop)
await f.form.open('create')
assert.equal(f.form.activeStep.value, 1)
assert.equal(f.form.data.value.educationLevel, 1)
f.form.data.value.password = 're-entered'
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 2)
})
test('a valid existing-account draft resumes step two while a normal edit starts on step one', async (t) => {
const identity = {
...basicInfo,
password: undefined,
id: 456,
identityType: 10,
memberId: 8,
collectionStep: 2
}
const normal = fixture({ identity })
const resumed = fixture({ identity, draft: { currentStep: 2, fields: socialInfo } })
t.after(normal.stop)
t.after(resumed.stop)
await normal.form.open('update', 456)
assert.equal(normal.form.activeStep.value, 1)
await resumed.form.open('update', 456)
assert.equal(resumed.form.activeStep.value, 2)
})
test('read-only older records can be reviewed without validation or writes', async (t) => {
const f = fixture({
identity: { id: 456, identityType: 10, realName: '旧档案', collectionStep: 1 }
})
t.after(f.stop)
await f.form.open('view', 456)
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 2)
await f.form.submit()
assert.equal(f.requests.length, 0)
})
test('underage dates block advancement and unavailable steps cannot be selected', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo, { birthDate: '2020-01-01' })
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 1)
await f.form.selectStep(3)
assert.equal(f.form.activeStep.value, 1)
})
test('direct jumping cannot bypass sequential validation and final submit routes errors to the owning step', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo, socialInfo, {
careLevel: 1,
emergencyContacts: [{ ...contact }]
})
await f.form.selectStep(7)
assert.equal(f.form.activeStep.value, 1)
await goToStep(f.form, 7)
f.form.data.value.educationLevel = null
await f.form.submit()
assert.equal(f.form.activeStep.value, 2)
assert.equal(f.requests.length, 0)
})
test('disabled elderly require a type and duplicate contact phones or priority flags cannot save', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo, socialInfo, { careLevel: 3, disabled: true })
await goToStep(f.form, 4)
await f.form.selectStep(5)
assert.equal(f.form.activeStep.value, 4)
f.form.data.value.disabilityType = 'physical'
await goToStep(f.form, 7)
f.form.data.value.emergencyContacts = [{ ...contact }, { ...contact, primary: false }]
await f.form.submit()
assert.equal(f.requests.length, 0)
f.form.data.value.emergencyContacts[1].phone = '13900000002'
f.form.data.value.emergencyContacts[1].primary = true
await f.form.submit()
assert.equal(f.requests.length, 0)
f.form.data.value.emergencyContacts[1].primary = false
await f.form.submit()
assert.equal(f.requests.length, 1)
})
test('a failed create retains the password and all input for retry', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo, socialInfo, {
careLevel: 1,
emergencyContacts: [{ ...contact }]
})
await goToStep(f.form, 7)
let fail = true
f.api.createIdentity = async (payload) => {
if (fail) throw new Error('retry-required')
f.requests.push({ method: 'create', payload })
return 123
}
await assert.rejects(f.form.submit(), /retry-required/)
assert.equal(f.form.data.value.password, basicInfo.password)
assert.equal(f.form.visible.value, true)
assert.equal(f.form.activeStep.value, 7)
fail = false
await f.form.submit()
assert.equal(f.requests.length, 1)
})
test('complete draft resumes step seven only after all prior required steps are valid', async (t) => {
const f = fixture({
identity: { ...basicInfo, password: undefined, id: 456, memberId: 8, identityType: 10 },
draft: {
currentStep: 7,
fields: { ...socialInfo, careLevel: 1, emergencyContacts: [{ ...contact }] }
}
})
t.after(f.stop)
await f.form.open('update', 456)
assert.equal(f.form.activeStep.value, 7)
const invalid = fixture({
identity: { ...basicInfo, password: undefined, id: 456, memberId: 8, identityType: 10 },
draft: { currentStep: 7, fields: socialInfo }
})
t.after(invalid.stop)
await invalid.form.open('update', 456)
assert.equal(invalid.form.activeStep.value, 4)
})
test('malformed restored birthdays and future diagnoses fail before advancing', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, basicInfo, socialInfo, { careLevel: 1, birthDate: '1949-02-31' })
await f.form.selectStep(2)
assert.equal(f.form.activeStep.value, 1)
f.form.data.value.birthDate = basicInfo.birthDate
await goToStep(f.form, 5)
f.form.data.value.chronicDiseaseStatus = 1
f.form.data.value.chronicDiseases = [{ code: 'diabetes', diagnosedDate: '2999-01-01' }]
await f.form.selectStep(6)
assert.equal(f.form.activeStep.value, 5)
})
test('volunteer and family profiles save in one form without elderly collection fields', async (t) => {
for (const identityType of [20, 30]) {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
Object.assign(f.form.data.value, {
identityType,
realName: '测试档案',
gender: 1,
mobile: '13800000001',
password: 'test-only',
skills: '陪伴交流',
serviceAreas: '测试社区',
idCard: 'elder-only',
careLevel: 1,
insuranceType: 1,
consentConfirmed: true
})
assert.equal(f.form.isElder.value, false)
assert.equal(
f.form.profilePresentation.value.title,
identityType === 20 ? '志愿者资料登记' : '家属资料登记'
)
await f.form.submit()
assert.equal(f.requests.length, 1)
const payload = f.requests[0].payload
assert.equal(payload.identityType, identityType)
assert.equal(payload.idCard, undefined)
assert.equal(payload.careLevel, undefined)
assert.equal(payload.insuranceType, undefined)
assert.equal(payload.consentConfirmed, undefined)
assert.equal(payload.skills, '陪伴交流')
assert.equal(f.form.visible.value, false)
}
})
test('closing after a failed draft save preserves the form and supports a retry', async (t) => {
const f = fixture()
t.after(f.stop)
await f.form.open('create')
f.form.data.value.realName = '尚未保存的输入'
f.draftTransport.saveError = new Error('offline')
let closed = false
await f.form.beforeClose(() => {
closed = true
})
assert.equal(closed, false)
assert.equal(f.form.data.value.realName, '尚未保存的输入')
assert.equal(f.warnings.length, 1)
f.draftTransport.saveError = null
await f.form.beforeClose(() => {
closed = true
})
assert.equal(closed, true)
})