fix(mobile): 修复协议单项目校验引用导致保存中断
循环模板内字符串 ref 被 Vue 收集为数组,调用 validate 时在请求前抛错。改用函数 ref 保持单组件实例,并覆盖两角色新增、编辑及项目校验提示。关联工单 #78。
This commit is contained in:
1 parent
1032631318
commit
f343f12645
2 files changed
+111
-1
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import vm from 'node:vm'
|
||||
import * as Vue from 'vue'
|
||||
import { compile } from '@vue/compiler-dom'
|
||||
import { projectValidation } from './project-assignment.js'
|
||||
|
||||
const source = readFileSync(new URL('../views/market/MarketModuleForm.vue', import.meta.url), 'utf8')
|
||||
// Render the real loop/slot/ref structure; native host nodes need no browser or service.
|
||||
const template = source.slice(source.indexOf('<template v-for="section in leadingSections"'), source.indexOf('\n\n <section'))
|
||||
const render = new Function('Vue', compile(template, { prefixIdentifiers: true }).code)(Vue)
|
||||
const submitSource = source.slice(source.indexOf('async function submit()'), source.indexOf('\nasync function confirmLeave()'))
|
||||
const renderer = Vue.createRenderer({
|
||||
createElement: () => ({}), createText: () => ({}), createComment: () => ({}),
|
||||
insert(node, parent, anchor = null) {
|
||||
parent.children ||= []
|
||||
node.parent = parent
|
||||
const index = parent.children.indexOf(anchor)
|
||||
parent.children.splice(index < 0 ? parent.children.length : index, 0, node)
|
||||
},
|
||||
remove(node) {
|
||||
const siblings = node.parent?.children || []
|
||||
const index = siblings.indexOf(node)
|
||||
if (index >= 0) siblings.splice(index, 1)
|
||||
},
|
||||
setText() {}, setElementText() {}, patchProp() {},
|
||||
parentNode: (node) => node.parent,
|
||||
nextSibling: (node) => {
|
||||
const siblings = node.parent?.children || []
|
||||
return siblings[siblings.indexOf(node) + 1] || null
|
||||
}
|
||||
})
|
||||
|
||||
async function mountForm({ role, mode = 'create', projectId = 'project-1', loading = false, error = '' }) {
|
||||
const projectAssignment = Vue.ref(null)
|
||||
const formData = Vue.ref({ source: 'research_donation', projectId })
|
||||
const messages = [], requests = []
|
||||
let validations = 0
|
||||
const ProjectAssignmentField = {
|
||||
setup(_, { expose }) {
|
||||
expose({ validate() {
|
||||
validations++
|
||||
return projectValidation({ editing: mode === 'edit', required: true, projectId, loading, error })
|
||||
} })
|
||||
return () => Vue.h('div')
|
||||
}
|
||||
}
|
||||
const Section = { setup: (_, { slots }) => () => slots.insert?.() }
|
||||
const context = Vue.ref({ module: 'protocol', marketRole: role })
|
||||
const app = renderer.createApp({
|
||||
components: { MarketFormSection: Section, ProjectAssignmentField },
|
||||
setup: () => ({
|
||||
projectAssignment, context, formData, mode,
|
||||
leadingSections: [{ key: 'customer' }, { key: 'protocol' }],
|
||||
formOptions: {}, fieldsLocked: false, dirty: false,
|
||||
sectionValue: () => ({}), searchProtocolCustomers() {}, setSectionValue() {},
|
||||
uploadField() {}, handleFieldChange() {}, handleCustomerSelect() {}
|
||||
}), render
|
||||
})
|
||||
app.mount({})
|
||||
await Vue.nextTick()
|
||||
const scope = {
|
||||
projectAssignment, context, formData, mode: Vue.ref(mode),
|
||||
submitting: Vue.ref(false), uploadPending: Vue.ref(0), isProtocolV2: Vue.ref(false),
|
||||
isProtocolV1: Vue.ref(false), id: Vue.ref(mode === 'edit' ? 'record-1' : ''),
|
||||
action: Vue.ref(mode), visibleSections: Vue.ref([]), dirty: Vue.ref(true),
|
||||
showToast: (text) => messages.push(text), showSuccessToast: (text) => messages.push(text),
|
||||
validateRequiredFields: () => '', buildMarketActionPayload: (_module, _action, data) => data,
|
||||
sanitizeMarketPayload: (data) => data, buildPayload: () => formData.value,
|
||||
AbortController, submitController: null, allowLeave: false,
|
||||
saveMarketRecord: async (...args) => { requests.push(args); return { id: 'saved' } },
|
||||
mountGuard: { isCurrent: () => true }, mountToken: 1,
|
||||
resolveMarketSavedDetailId: () => 'saved', router: { replace: async () => {} },
|
||||
route: { query: {} }, marketRecordNavigationQuery: () => ({}),
|
||||
marketErrorMessage: (cause) => cause.message
|
||||
}
|
||||
const submit = vm.runInNewContext(`${submitSource}\nsubmit`, scope)
|
||||
return { submit, messages, requests, app, validations: () => validations }
|
||||
}
|
||||
|
||||
for (const role of ['specialist', 'manager']) {
|
||||
for (const mode of ['create', 'edit']) {
|
||||
test(`${role} ${mode}: a valid project reaches the save request`, async () => {
|
||||
const form = await mountForm({ role, mode })
|
||||
try {
|
||||
await form.submit()
|
||||
assert.equal(form.validations(), 1)
|
||||
assert.equal(form.requests.length, 1)
|
||||
assert.equal(form.requests[0][0].marketRole, role)
|
||||
assert.deepEqual(form.messages, ['保存成功'])
|
||||
} finally { form.app.unmount() }
|
||||
})
|
||||
}
|
||||
for (const [state, message] of [
|
||||
[{ projectId: '' }, '请选择所属项目'],
|
||||
[{ loading: true }, '项目选项加载中,请稍后保存'],
|
||||
[{ error: 'network' }, '项目选项加载失败,请重试后保存']
|
||||
]) {
|
||||
test(`${role}: ${message}`, async () => {
|
||||
const form = await mountForm({ role, ...state })
|
||||
try {
|
||||
await form.submit()
|
||||
assert.equal(form.validations(), 1)
|
||||
assert.equal(form.requests.length, 0)
|
||||
assert.deepEqual(form.messages, [message])
|
||||
} finally { form.app.unmount() }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
>
|
||||
<template #insert>
|
||||
<ProjectAssignmentField
|
||||
ref="projectAssignment"
|
||||
:ref="(instance) => (projectAssignment = instance)"
|
||||
embedded
|
||||
v-model="formData.projectId"
|
||||
:biz-type="formData.source"
|
||||
|
||||
Reference in new issue
Block a user