// 回归截图中的小程序循环作用域问题:执行真实 render,再按 WXML 作用域取文本。 const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const {baseParse} = require('@vue/compiler-dom'); const root = path.resolve(__dirname, '..'); const read = file => fs.readFileSync(path.join(root, file), 'utf8'); const en = JSON.parse(read('src/locales/en.json')); const sourceKeys = JSON.parse(read('src/locales/source-keys.json')); const localeCode = read('src/locales/index.js').replace(/^import .*;\r?\n/gm, '').replace(/export /g, ''); const locale = new Function('createI18n','en','sourceKeys',localeCode+'\nreturn {t,formatBattleTip};')( () => ({global: {t: key => key}}), en, sourceKeys); const computed = getter => ({get value() {return getter();}}); const vendor = { computed, unref: value => value, e: (...parts) => Object.assign({}, ...parts), f: (items, render) => (typeof items === 'number' ? Array.from({length: items}, (_,i) => i+1) : items) .map((item,index) => render(item,index,index)), t: value => value == null ? '' : String(value), _export_sfc: component => component, }; function loadComponent(file, dependencies) { let component; vm.runInNewContext(read(file), { exports: {}, wx: {createComponent: value => {component = value;}}, require: id => { for (const [suffix,value] of Object.entries(dependencies)) if (id.endsWith(suffix)) return value; throw Error('Unexpected component dependency: '+id); }, }, {filename: file}); assert(component, 'No compiled component: '+file); return component; } const prefix = 'dist/build/mp-weixin/pages/team-battle/components/'; const footer = loadComponent(prefix+'BattleFooter.js', { 'common/vendor.js': vendor, 'locales/index.js': locale, 'constants.js': {RoundImages: {round1:'round1',round2:'round2',round3:'round3',gold1:'gold1'}}, }); const wxml = baseParse(read(prefix+'BattleFooter.wxml')); function evaluate(expression, scope) { return new Function('scope', 'with(scope){return ('+expression+');}')(scope); } function renderedTexts(node,scope) { const attr = name => (node.props || []).find(p => p.name === name)?.value?.content; const forValue = attr('wx:for'); if (forValue) { const rows = evaluate(forValue.slice(2,-2),scope); return rows.flatMap((item,index) => renderedTexts({...node,props:node.props.filter(p=>p.name!=='wx:for')}, {...scope,[attr('wx:for-item') || 'item']:item,[attr('wx:for-index') || 'index']:index})); } const ifValue = attr('wx:if'); if (ifValue && !evaluate(ifValue.slice(2,-2),scope)) return []; if (node.tag === 'text') { return [(node.children || []).map(child => { const value = child.type === 5 ? evaluate(child.content.content,scope) : child.content || ''; assert(value === null || typeof value !== 'object', 'WXML rendered a loop object in a text node'); return value == null ? '' : String(value); }).join('')]; } return (node.children || []).flatMap(child => renderedTexts(child,scope)); } for (const count of [0,1,2,3,4]) { const roundResults = Array.from({length:count},(_,index)=>({ ifGold:index===3, goldRound:index===3 ? 1 : 0, shoots:{1:[{ring:index===0 ? 10 : 0}],2:[{ring:8}]}, })); const props = {roundResults,bluePoints:0,redPoints:0,goldenRound:count===4 ? 1 : 0}; const data = footer.setup(props)(props,{}); const texts = renderedTexts(wxml,data); assert.equal(texts.filter(text=>text===en.pts).length, Math.max(3,count)*2); assert(!texts.join('').includes('[object Object]')); if (count) assert(texts.includes('10'), 'Completed round score lost'); if (count>1) assert(texts.includes('0'), 'Zero score lost'); } // 顶部只转换显示值,监听器仍接收原始中文提示,语音控制不增加播放调用。 for (const tip of ['请蓝队射箭','请红队射箭你重回']) { const store = {game:{currentShot:1,totalShot:3,tips:tip}}; const header = loadComponent(prefix+'HeaderProgress.js', { 'common/vendor.js': {...vendor, ref:value=>({value}), watch:(source,callback)=>callback(source()), onMounted:()=>{},onBeforeUnmount:()=>{},o:handler=>handler, }, 'locales/index.js':locale, 'store.js':{useStore:()=>store}, 'audioManager.js':{audioManager:{setMuted:()=>{}}}, }); const data = header.setup({})({},{}); assert.equal(data.a,locale.formatBattleTip(tip)); assert(!/[\u3400-\u9fff]/.test(data.a)); assert.equal(store.game.tips,tip); assert.equal(data.c,'1'); assert.equal(data.d,'3'); } console.log('Battle display checks passed: compiled JS + WXML, 0–3 rounds and golden round, blank/zero scores, English tips and original Chinese state preserved.');