更新
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const test = require('node:test')
|
||||
const ts = require('typescript')
|
||||
const vue = require('vue')
|
||||
const { parse, compileScript, compileTemplate } = require('@vue/compiler-sfc')
|
||||
|
||||
const root = path.resolve(__dirname, '..')
|
||||
const apiPath = path.join(root, 'src/api/setting/prescription_ai_visibility.ts')
|
||||
const pagePath = path.join(root, 'src/views/setting/prescription_ai_visibility/index.vue')
|
||||
const { descriptor, errors } = parse(fs.readFileSync(pagePath, 'utf8'), { filename: pagePath })
|
||||
assert.deepEqual(errors, [])
|
||||
const script = compileScript(descriptor, { id: 'prescription-ai-visibility' })
|
||||
|
||||
function moduleFrom(source, dependencies) {
|
||||
const compiled = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
esModuleInterop: true
|
||||
}
|
||||
}).outputText
|
||||
const module = { exports: {} }
|
||||
new Function('require', 'module', 'exports', compiled)(dependencies, module, module.exports)
|
||||
return module.exports
|
||||
}
|
||||
|
||||
const makeApi = (request) =>
|
||||
moduleFrom(fs.readFileSync(apiPath, 'utf8'), (name) => {
|
||||
assert.equal(name, '@/utils/request')
|
||||
return request
|
||||
})
|
||||
|
||||
const tick = () => new Promise((resolve) => setImmediate(resolve))
|
||||
const deferred = () => {
|
||||
let resolve
|
||||
let reject
|
||||
const promise = new Promise((yes, no) => {
|
||||
resolve = yes
|
||||
reject = no
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
function makePage(overrides = {}) {
|
||||
const saved = []
|
||||
const messages = []
|
||||
const calls = []
|
||||
const api = {
|
||||
getPrescriptionAiCapabilities: async () => {
|
||||
calls.push('capabilities')
|
||||
return { agreement_visible: false, can_manage_visibility: true }
|
||||
},
|
||||
getPrescriptionAiVisibilityConfig: async () => {
|
||||
calls.push('config')
|
||||
return { role_ids: [2, 90], admin_ids: [3, 91] }
|
||||
},
|
||||
getPrescriptionAiVisibilityOptions: async () => {
|
||||
calls.push('options')
|
||||
return {
|
||||
roles: [{ id: 2, name: '医生' }],
|
||||
admins: [{ id: 3, name: '张医生', account: 'zhang.doctor' }]
|
||||
}
|
||||
},
|
||||
setPrescriptionAiVisibilityConfig: async (params) => {
|
||||
saved.push(params)
|
||||
return params
|
||||
},
|
||||
...overrides
|
||||
}
|
||||
const component = moduleFrom(script.content, (name) => {
|
||||
if (name === 'vue') return vue
|
||||
if (name === '@/api/setting/prescription_ai_visibility') return api
|
||||
if (name === '@/utils/feedback') {
|
||||
return { msgSuccess: (message) => messages.push(message) }
|
||||
}
|
||||
throw new Error(`Unexpected import: ${name}`)
|
||||
}).default
|
||||
const page = component.setup({}, { expose() {} })
|
||||
return { page, api, saved, messages, calls }
|
||||
}
|
||||
|
||||
test('API uses the dedicated routes and rejects malformed policy responses', async () => {
|
||||
const calls = []
|
||||
let response = { role_ids: [2], admin_ids: [3] }
|
||||
const api = makeApi({
|
||||
get: async (params) => {
|
||||
calls.push(['get', params])
|
||||
return response
|
||||
},
|
||||
post: async (params) => {
|
||||
calls.push(['post', params])
|
||||
return response
|
||||
}
|
||||
})
|
||||
const data = await api.getPrescriptionAiVisibilityConfig()
|
||||
data.role_ids.push(5)
|
||||
assert.deepEqual(response.role_ids, [2])
|
||||
await api.setPrescriptionAiVisibilityConfig({ role_ids: [], admin_ids: [] })
|
||||
await api.getPrescriptionAiVisibilityOptions()
|
||||
await api.getPrescriptionAiCapabilities()
|
||||
assert.deepEqual(calls, [
|
||||
['get', { url: '/tcm.prescriptionAi/getVisibilityConfig' }],
|
||||
[
|
||||
'post',
|
||||
{
|
||||
url: '/tcm.prescriptionAi/setVisibilityConfig',
|
||||
params: { role_ids: [], admin_ids: [] }
|
||||
}
|
||||
],
|
||||
['get', { url: '/tcm.prescriptionAi/visibilityOptions' }],
|
||||
['get', { url: '/tcm.prescriptionAi/capabilities' }]
|
||||
])
|
||||
for (response of [
|
||||
null,
|
||||
{},
|
||||
{ role_ids: [], admin_ids: ['3'] },
|
||||
{ role_ids: [-1], admin_ids: [] }
|
||||
]) {
|
||||
await assert.rejects(api.getPrescriptionAiVisibilityConfig(), /响应格式不正确/)
|
||||
await assert.rejects(
|
||||
api.setPrescriptionAiVisibilityConfig({ role_ids: [], admin_ids: [] }),
|
||||
/响应格式不正确/
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('initial loading is pending and cannot submit before the real policy is read', async () => {
|
||||
const pending = deferred()
|
||||
const { page, saved } = makePage({ getPrescriptionAiVisibilityConfig: () => pending.promise })
|
||||
assert.equal(page.loading.value, true)
|
||||
assert.equal(page.ready.value, false)
|
||||
assert.equal(page.hasLoaded.value, false)
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(saved, [])
|
||||
pending.reject(new Error('offline'))
|
||||
await tick()
|
||||
assert.equal(page.loading.value, false)
|
||||
assert.equal(page.ready.value, false)
|
||||
assert.match(page.loadError.value, /加载失败/)
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(saved, [])
|
||||
})
|
||||
|
||||
test('a failed reload cannot apply a partial policy or overwrite saved selections', async () => {
|
||||
const { page, api, saved } = makePage()
|
||||
await tick()
|
||||
api.getPrescriptionAiVisibilityConfig = async () => ({ role_ids: [], admin_ids: [] })
|
||||
api.getPrescriptionAiVisibilityOptions = async () => {
|
||||
throw new Error('options failed')
|
||||
}
|
||||
await page.loadConfig()
|
||||
assert.deepEqual(page.formData.role_ids, [2, 90])
|
||||
assert.deepEqual(page.formData.admin_ids, [3, 91])
|
||||
assert.equal(page.hasLoaded.value, true)
|
||||
assert.equal(page.ready.value, false)
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(saved, [])
|
||||
api.getPrescriptionAiVisibilityOptions = async () => ({ roles: [], admins: [] })
|
||||
await page.loadConfig()
|
||||
assert.equal(page.ready.value, true)
|
||||
assert.equal(page.loadError.value, '')
|
||||
assert.deepEqual(page.formData.role_ids, [])
|
||||
})
|
||||
|
||||
test('missing selected IDs stay visible and user labels support both name and account search', async () => {
|
||||
const { page, saved } = makePage()
|
||||
await tick()
|
||||
assert.equal(page.canManage.value, true)
|
||||
assert.equal(page.ready.value, true)
|
||||
assert.deepEqual(
|
||||
page.roleOptions.value.map((row) => row.id),
|
||||
[2, 90]
|
||||
)
|
||||
assert.match(page.roleOptions.value[1].name, /#90.*当前不在可选列表/)
|
||||
assert.deepEqual(
|
||||
page.adminOptions.value.map((row) => row.id),
|
||||
[3, 91]
|
||||
)
|
||||
assert.match(page.adminOptions.value[0].label, /张医生.*zhang\.doctor/)
|
||||
assert.match(page.adminOptions.value[1].label, /#91.*当前不在可选列表/)
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(saved, [{ role_ids: [2, 90], admin_ids: [3, 91] }])
|
||||
})
|
||||
|
||||
test('explicitly clearing both selectors saves the empty policy and applies normalized response', async () => {
|
||||
const { page, saved, messages, api } = makePage()
|
||||
await tick()
|
||||
page.formData.role_ids = []
|
||||
page.formData.admin_ids = []
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(saved, [{ role_ids: [], admin_ids: [] }])
|
||||
assert.equal(messages.length, 1)
|
||||
page.formData.role_ids = [90, 2, 90]
|
||||
api.setPrescriptionAiVisibilityConfig = async () => ({ role_ids: [2, 90], admin_ids: [] })
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(page.formData.role_ids, [2, 90])
|
||||
assert.equal(page.saving.value, false)
|
||||
})
|
||||
|
||||
test('pending save blocks duplicate writes and reload; failed write keeps the user selection', async () => {
|
||||
const pending = deferred()
|
||||
const saved = []
|
||||
const { page, calls } = makePage({
|
||||
setPrescriptionAiVisibilityConfig: (params) => {
|
||||
saved.push(params)
|
||||
return pending.promise
|
||||
}
|
||||
})
|
||||
await tick()
|
||||
page.formData.role_ids = [90]
|
||||
const first = page.handleSubmit()
|
||||
assert.equal(page.saving.value, true)
|
||||
await page.handleSubmit()
|
||||
await page.loadConfig()
|
||||
assert.equal(saved.length, 1)
|
||||
assert.equal(calls.filter((name) => name === 'capabilities').length, 1)
|
||||
pending.reject(new Error('offline'))
|
||||
await first
|
||||
assert.equal(page.saving.value, false)
|
||||
assert.equal(page.ready.value, true)
|
||||
assert.deepEqual(page.formData.role_ids, [90])
|
||||
assert.deepEqual(page.formData.admin_ids, [3, 91])
|
||||
assert.match(page.saveError.value, /当前选择已保留/)
|
||||
})
|
||||
|
||||
test('management capability is independent from viewing and denies policy reads when absent', async () => {
|
||||
const { page, calls, saved } = makePage({
|
||||
getPrescriptionAiCapabilities: async () => ({
|
||||
agreement_visible: true,
|
||||
can_manage_visibility: false
|
||||
})
|
||||
})
|
||||
await tick()
|
||||
assert.deepEqual(calls, [])
|
||||
assert.equal(page.canManage.value, false)
|
||||
assert.equal(page.hasLoaded.value, false)
|
||||
assert.equal(page.ready.value, false)
|
||||
assert.match(page.loadError.value, /无权管理/)
|
||||
await page.handleSubmit()
|
||||
assert.deepEqual(saved, [])
|
||||
})
|
||||
|
||||
test('actual settings template compiles with its script bindings', () => {
|
||||
const compiled = compileTemplate({
|
||||
source: descriptor.template.content,
|
||||
filename: pagePath,
|
||||
id: 'prescription-ai-visibility',
|
||||
compilerOptions: { bindingMetadata: script.bindings }
|
||||
})
|
||||
assert.deepEqual(compiled.errors, [])
|
||||
})
|
||||
@@ -0,0 +1,167 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const test = require('node:test')
|
||||
const ts = require('typescript')
|
||||
const vue = require('vue')
|
||||
const { parse, compileScript, compileTemplate } = require('@vue/compiler-sfc')
|
||||
|
||||
const selected = [
|
||||
'patchUsageVisible', 'patchUsageSaving', 'patchUsageFormRef', 'patchUsageForm',
|
||||
'patchUsageRules', 'openPatchUsageDialog', 'resetPatchUsageForm', 'submitPatchUsage'
|
||||
]
|
||||
|
||||
for (const page of ['components/PrescriptionOrderDetailDrawer.vue', 'order_list_h5.vue']) {
|
||||
const filename = path.join(__dirname, '../src/views/consumer/prescription', page)
|
||||
const { descriptor, errors } = parse(fs.readFileSync(filename, 'utf8'), { filename })
|
||||
assert.deepEqual(errors, [])
|
||||
const script = compileScript(descriptor, { id: page })
|
||||
const template = compileTemplate({
|
||||
source: descriptor.template.content, filename, id: page,
|
||||
compilerOptions: { bindingMetadata: script.bindings }
|
||||
})
|
||||
assert.deepEqual(template.errors, [])
|
||||
|
||||
// Run the real handlers; replace the network and the rest of the order page.
|
||||
const ast = ts.createSourceFile(filename + '.ts', descriptor.scriptSetup.content, ts.ScriptTarget.Latest, true)
|
||||
const declarations = new Map()
|
||||
for (const node of ast.statements) {
|
||||
if (ts.isFunctionDeclaration(node) && node.name) declarations.set(node.name.text, node.getText(ast))
|
||||
if (ts.isVariableStatement(node)) {
|
||||
for (const declaration of node.declarationList.declarations) {
|
||||
declarations.set(declaration.name.getText(ast), `const ${declaration.getText(ast)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
const executed = page.startsWith('components/') ? [...selected, 'refresh'] : selected
|
||||
for (const name of executed) assert.ok(declarations.has(name), `${page}: missing ${name}`)
|
||||
const compiled = ts.transpileModule(executed.map(name => declarations.get(name)).join('\n'), {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
|
||||
}).outputText
|
||||
|
||||
function instance({ hasAux = true, prescriptionType = '浓缩水丸', bagCount = 2, mainBagCount = 2, status = 2, fail = false } = {}) {
|
||||
const calls = []
|
||||
const events = []
|
||||
const aux = { times_per_day: 3, usage_days: 10, dosage_bag_count: bagCount }
|
||||
const detailPrescription = vue.ref({ prescription_type: prescriptionType, times_per_day: 3, usage_days: 10, dosage_bag_count: mainBagCount })
|
||||
const detailAuxUsage = vue.ref(hasAux ? aux : null)
|
||||
const detailData = vue.ref({
|
||||
id: 42, prescription_id: 17, medication_days: 10, fulfillment_status: status,
|
||||
gancao_reciperl_order_no: 'TEST-SUBMITTED', ej_pharmacy_order_no: 'TEST-EJ-SUBMITTED'
|
||||
})
|
||||
const refresh = async () => {
|
||||
if (calls[0]?.dosage_bag_count) detailPrescription.value.dosage_bag_count = calls[0].dosage_bag_count
|
||||
if (calls[0]?.aux_dosage_bag_count) detailAuxUsage.value.dosage_bag_count = calls[0].aux_dosage_bag_count
|
||||
events.push('refresh')
|
||||
return { ...detailData.value }
|
||||
}
|
||||
const globals = {
|
||||
ref: vue.ref, reactive: vue.reactive, computed: vue.computed, nextTick: vue.nextTick,
|
||||
detailPrescription, detailAuxUsage, detailData, detailHasAuxHerbs: vue.ref(hasAux),
|
||||
feedback: { msgWarning() {}, msgSuccess() {} },
|
||||
prescriptionOrderPatchPrescriptionUsage: async payload => {
|
||||
calls.push(payload)
|
||||
if (fail) throw new Error('Save failed')
|
||||
},
|
||||
requestDetail: refresh, refreshCurrentPrescriptionOrderDetail: refresh,
|
||||
props: { loadRelatedData: true },
|
||||
emit: event => events.push(event),
|
||||
fetchLogs: async () => {
|
||||
events.push('logs:start')
|
||||
await Promise.resolve()
|
||||
events.push('logs:done')
|
||||
},
|
||||
getLists: () => events.push('lists')
|
||||
}
|
||||
const state = new Function(...Object.keys(globals), `${compiled}\nreturn { ${selected.join(', ')} }`)(...Object.values(globals))
|
||||
state.patchUsageFormRef.value = { validate: async () => {}, clearValidate() {} }
|
||||
return { state, calls, events, detailAuxUsage, detailPrescription }
|
||||
}
|
||||
|
||||
test(`${page}: edits auxiliary bags and refreshes the persisted value without changing usage`, async () => {
|
||||
const { state, calls, events, detailAuxUsage } = instance()
|
||||
state.openPatchUsageDialog()
|
||||
assert.equal(state.patchUsageForm.aux_dosage_bag_count, 2)
|
||||
state.patchUsageForm.aux_dosage_bag_count = 4
|
||||
await state.submitPatchUsage()
|
||||
assert.deepEqual(calls, [{
|
||||
id: 42, times_per_day: 3, usage_days: 10, medication_days: 10,
|
||||
dosage_bag_count: 2,
|
||||
aux_times_per_day: 3, aux_usage_days: 10, aux_dosage_bag_count: 4
|
||||
}])
|
||||
assert.equal(state.patchUsageVisible.value, false)
|
||||
assert.equal(detailAuxUsage.value.dosage_bag_count, 4)
|
||||
assert.ok(events.includes('refresh'))
|
||||
assert.ok(events.indexOf('logs:done') > events.indexOf('refresh'))
|
||||
const completedEvent = page.startsWith('components/') ? 'detail-changed' : 'lists'
|
||||
assert.ok(events.indexOf(completedEvent) > events.indexOf('logs:done'))
|
||||
state.openPatchUsageDialog()
|
||||
assert.equal(state.patchUsageForm.aux_dosage_bag_count, 4)
|
||||
})
|
||||
|
||||
test(`${page}: missing historical bag count defaults to one and resets on close`, () => {
|
||||
const { state } = instance({ bagCount: null, mainBagCount: null })
|
||||
state.openPatchUsageDialog()
|
||||
assert.equal(state.patchUsageForm.aux_dosage_bag_count, 1)
|
||||
assert.equal(state.patchUsageForm.dosage_bag_count, 1)
|
||||
state.patchUsageForm.dosage_bag_count = 4
|
||||
state.patchUsageForm.aux_dosage_bag_count = 5
|
||||
state.resetPatchUsageForm()
|
||||
assert.equal(state.patchUsageForm.aux_dosage_bag_count, 1)
|
||||
assert.equal(state.patchUsageForm.dosage_bag_count, 1)
|
||||
})
|
||||
|
||||
test(`${page}: only water pills with an auxiliary formula submit or require auxiliary bags`, async () => {
|
||||
for (const options of [{ hasAux: false }, { prescriptionType: '饮片' }, { prescriptionType: '颗粒' }]) {
|
||||
const { state, calls } = instance(options)
|
||||
state.openPatchUsageDialog()
|
||||
state.patchUsageForm.aux_dosage_bag_count = 5
|
||||
assert.equal(state.patchUsageRules.value.aux_dosage_bag_count, undefined)
|
||||
await state.submitPatchUsage()
|
||||
assert.ok(!Object.hasOwn(calls[0], 'aux_dosage_bag_count'))
|
||||
assert.equal(Object.hasOwn(calls[0], 'dosage_bag_count'), !options.prescriptionType)
|
||||
}
|
||||
const { state } = instance()
|
||||
assert.equal(state.patchUsageRules.value.aux_dosage_bag_count[0].required, true)
|
||||
assert.equal(state.patchUsageRules.value.dosage_bag_count[0].required, true)
|
||||
})
|
||||
|
||||
test(`${page}: main and auxiliary bags can be edited independently in every submitted order status`, async () => {
|
||||
for (let status = 1; status <= 12; status++) {
|
||||
const { state, calls, detailPrescription, detailAuxUsage } = instance({ status })
|
||||
state.openPatchUsageDialog()
|
||||
assert.equal(state.patchUsageForm.dosage_bag_count, 2)
|
||||
state.patchUsageForm.dosage_bag_count = 5
|
||||
state.patchUsageForm.aux_dosage_bag_count = 1
|
||||
await state.submitPatchUsage()
|
||||
assert.equal(calls.length, 1, `status ${status}`)
|
||||
assert.equal(calls[0].dosage_bag_count, 5)
|
||||
assert.equal(calls[0].aux_dosage_bag_count, 1)
|
||||
assert.equal(detailPrescription.value.dosage_bag_count, 5)
|
||||
assert.equal(detailAuxUsage.value.dosage_bag_count, 1)
|
||||
}
|
||||
const { state, calls } = instance({ hasAux: false })
|
||||
state.openPatchUsageDialog()
|
||||
state.patchUsageForm.dosage_bag_count = 4
|
||||
await state.submitPatchUsage()
|
||||
assert.equal(calls[0].dosage_bag_count, 4)
|
||||
assert.ok(!Object.hasOwn(calls[0], 'aux_dosage_bag_count'))
|
||||
})
|
||||
|
||||
test(`${page}: validation blocks requests and a rejected save retains the selected bags`, async () => {
|
||||
const { state, calls, events } = instance({ fail: true })
|
||||
state.openPatchUsageDialog()
|
||||
state.patchUsageForm.aux_dosage_bag_count = 3
|
||||
state.patchUsageForm.dosage_bag_count = 4
|
||||
state.patchUsageFormRef.value.validate = async () => { throw new Error('Required') }
|
||||
await state.submitPatchUsage()
|
||||
assert.equal(calls.length, 0)
|
||||
state.patchUsageFormRef.value.validate = async () => {}
|
||||
await state.submitPatchUsage()
|
||||
assert.equal(state.patchUsageForm.aux_dosage_bag_count, 3)
|
||||
assert.equal(state.patchUsageForm.dosage_bag_count, 4)
|
||||
assert.equal(state.patchUsageVisible.value, true)
|
||||
assert.equal(state.patchUsageSaving.value, false)
|
||||
assert.deepEqual(events, [])
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user