97 lines
4.7 KiB
JavaScript
97 lines
4.7 KiB
JavaScript
// 回归截图中的小程序循环作用域问题:执行真实 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.');
|