65 lines
6.3 KiB
JavaScript
65 lines
6.3 KiB
JavaScript
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 dir = path.resolve(__dirname, '../src/views/tcm/diagnosis/components')
|
|
const reviewFile = process.env.FOLLOWUP_INLINE_REVIEW_SOURCE || path.join(dir, 'FollowupAudioReview.vue')
|
|
function execute(source, requireFn) {
|
|
const out = ts.transpileModule(source,{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.CommonJS}}).outputText
|
|
const module={exports:{}};new Function('require','module','exports',out)(requireFn,module,module.exports);return module.exports
|
|
}
|
|
const state=execute(fs.readFileSync(path.join(dir,'followupAudioState.ts'),'utf8'),require)
|
|
const row=(id,extra={})=>({id,kind:'blood',values:{fasting_blood_sugar:6.2},record_date:'2026-10-12',record_time:'08:00',time_period:'早晨',time_estimated:true,time_text:'早晨',date_text:'今天',evidence:[{text:'六点二',channel:1,segment_id:'right-1',start_ms:0,end_ms:15000,position_type:'segment'}],needs_review:true,selected:false,target_id:null,current_values:{},expected_hash:'original',conflict:false,possible_duplicate:false,...extra})
|
|
function setup(extra={}){
|
|
const descriptor=parse(fs.readFileSync(reviewFile,'utf8'),{filename:reviewFile}).descriptor
|
|
const compiled=compileScript(descriptor,{id:'inline-review'})
|
|
const mod=execute(compiled.content,name=>name==='vue'?vue:name==='./followupAudioState'?state:{default:{}})
|
|
const props=vue.reactive({items:[row('one'),row('two'),row('three')],fields:{blood:[{key:'fasting_blood_sugar',label:'空腹血糖',type:'number'}]},disabled:false,canDaily:true,applied:false,saveState:'saved',...extra})
|
|
const events=[];const scope=vue.effectScope();const review=scope.run(()=>mod.default.setup(props,{expose(){},emit(event,...args){events.push([event,...args]);if(event==='update:items')props.items=args[0]}}))
|
|
return {review,props,events,dispose:()=>scope.stop()}
|
|
}
|
|
test('inline editor has one permanently mounted player slot and no modal overlay',()=>{
|
|
const source=fs.readFileSync(reviewFile,'utf8');assert.doesNotMatch(source,/<el-dialog/);assert.equal((source.match(/<slot name="player"/g)||[]).length,1)
|
|
assert.match(source,/position: sticky/);assert.match(source,/aria-label="同页核对与回听"/)
|
|
const descriptor=parse(source,{filename:reviewFile}).descriptor;const script=compileScript(descriptor,{id:'inline-compile'})
|
|
const render=compileTemplate({source:descriptor.template.content,filename:reviewFile,id:'inline-compile',compilerOptions:{bindingMetadata:script.bindings}});assert.deepEqual(render.errors,[])
|
|
})
|
|
test('accept-next requires explicit verification, retains immutable evidence and opens next unselected item',async()=>{
|
|
const f=setup({items:[row('one'),row('two',{selected:true,needs_review:false}),row('three')]});f.review.openReview('one')
|
|
f.review.saveReview(true,true);assert.equal(f.props.items[0].selected,false);assert.equal(f.review.editor.value.id,'one')
|
|
f.review.confirmReview(true);f.review.saveReview(true,true)
|
|
assert.equal(f.props.items[0].selected,true);assert.equal(f.props.items[0].needs_review,false);assert.equal(f.review.editor.value.id,'three')
|
|
assert.deepEqual(f.props.items[0].evidence,row('one').evidence);assert.ok(f.events.some(e=>e[0]==='saved-next'))
|
|
await vue.nextTick();assert.equal(f.review.editor.value.id,'three');f.dispose()
|
|
})
|
|
test('pending-filter accept-next captures next id before accepted row leaves filter',()=>{
|
|
const f=setup();f.review.group.value='pending';f.review.openReview('one');f.review.confirmReview(true);f.review.saveReview(true,true)
|
|
assert.equal(f.review.editor.value.id,'two');assert.deepEqual(f.review.visibleItems.value.map(r=>r.id),['two','three']);f.dispose()
|
|
})
|
|
test('skip preserves patient values, does not claim review complete, and never writes or selects',()=>{
|
|
const f=setup();f.review.openReview('one');f.review.skipAndNext();assert.equal(f.review.editor.value.id,'two');assert.equal(f.props.items[0].needs_review,true);assert.equal(f.props.items[0].selected,false)
|
|
assert.deepEqual(f.props.items[0].values,{fasting_blood_sugar:6.2});assert.ok(f.events.every(e=>['update:items','saved-next'].includes(e[0])));f.dispose()
|
|
})
|
|
test('inline row switching or skipping never silently drops unsaved edits',()=>{
|
|
const f=setup();f.review.openReview('one');f.review.setValue('fasting_blood_sugar',6.8);f.review.openReview('two');assert.equal(f.review.editor.value.id,'one');assert.match(f.review.editorNotice.value,/未保存/)
|
|
f.review.skipAndNext();assert.equal(f.review.editor.value.id,'one');assert.equal(f.review.editor.value.values.fasting_blood_sugar,6.8);assert.equal(f.props.items[0].values.fasting_blood_sugar,6.2);f.dispose()
|
|
})
|
|
test('date change remains gated until saved and authoritative target refreshed',()=>{
|
|
const f=setup();f.review.openReview('one');f.review.patchEditor({record_date:'2026-10-11'});f.review.confirmReview(true);f.review.saveReview(true,true);assert.equal(f.props.items[0].selected,false);assert.equal(f.review.editor.value.id,'one')
|
|
f.review.saveReview(false);assert.equal(f.props.items[0].record_date,'2026-10-11');assert.equal(f.props.items[0].needs_review,true);assert.ok(f.events.some(e=>e[0]==='saved-next'));f.dispose()
|
|
})
|
|
test('last item completes without wrapping or applying, read-only cannot advance mutations',()=>{
|
|
const f=setup({items:[row('one')]});f.review.openReview('one');f.review.confirmReview(true);f.review.saveReview(true,true);assert.equal(f.review.editor.value,null);assert.match(f.review.navigationNotice.value,/末项/);assert.equal(f.events.some(e=>e[0]==='apply'),false);f.dispose()
|
|
const g=setup({disabled:true});g.review.openReview('one');g.review.skipAndNext();g.review.saveReview(true,true);assert.equal(g.events.length,0);g.dispose()
|
|
})
|
|
|
|
test('reopening the same row or toggling its selection preserves unsaved editor data',()=>{
|
|
const f=setup();f.review.openReview('one');f.review.setValue('fasting_blood_sugar',7.1)
|
|
f.review.openReview('one');assert.equal(f.review.editor.value.values.fasting_blood_sugar,7.1)
|
|
f.review.toggleSelection(f.props.items[0],true);assert.equal(f.review.editor.value.values.fasting_blood_sugar,7.1)
|
|
assert.equal(f.props.items[0].values.fasting_blood_sugar,6.2);assert.equal(f.props.items[0].selected,false);f.dispose()
|
|
})
|