chore: v1.1.0 - 5项功能优化
This commit is contained in:
400
Test/test-v1.1.0.js
Normal file
400
Test/test-v1.1.0.js
Normal file
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* v1.1.0 自动化测试脚本
|
||||
* 验证 M001-M005 的核心逻辑
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const htmlPath = path.join(__dirname, '..', 'index.html');
|
||||
const html = fs.readFileSync(htmlPath, 'utf-8');
|
||||
|
||||
const results = {
|
||||
M001: [],
|
||||
M002: [],
|
||||
M003: [],
|
||||
M004: [],
|
||||
M005: [],
|
||||
};
|
||||
|
||||
function check(module, tcId, desc, expected, actual, pass) {
|
||||
results[module].push({ tcId, desc, expected, actual, pass });
|
||||
const icon = pass ? '✅' : '❌';
|
||||
console.log(`${icon} [${module}] ${tcId}: ${desc}`);
|
||||
if (!pass) {
|
||||
console.log(` 预期: ${expected}`);
|
||||
console.log(` 实际: ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// M001: 名单编辑功能(删除按钮)
|
||||
// ============================================================
|
||||
console.log('\n=== M001: 名单编辑功能 ===');
|
||||
|
||||
// TC001: 删除按钮存在
|
||||
check('M001', 'TC001', 'name-tag 有 delete-btn 按钮',
|
||||
'存在 .delete-btn 元素',
|
||||
html.includes("delBtn.className = 'delete-btn'") ? '存在' : '不存在',
|
||||
html.includes("delBtn.className = 'delete-btn'"));
|
||||
|
||||
// TC002: 删除按钮点击事件
|
||||
check('M001', 'TC002', '删除按钮有点击事件处理',
|
||||
'存在 delete-btn 的 click 事件委托',
|
||||
html.includes("e.target.closest('.delete-btn')") ? '存在' : '不存在',
|
||||
html.includes("e.target.closest('.delete-btn')"));
|
||||
|
||||
// TC003: removeByName 方法存在
|
||||
check('M001', 'TC003', 'NameManager.removeByName 方法存在',
|
||||
'存在 removeByName 方法',
|
||||
html.includes('removeByName: function(name)') ? '存在' : '不存在',
|
||||
html.includes('removeByName: function(name)'));
|
||||
|
||||
// TC004: 删除后同步清理必中名单
|
||||
check('M001', 'TC004', '删除后同步清理必中名单',
|
||||
'removeByName 中清理 mustWinList',
|
||||
html.includes("state.backdoor.mustWinList.indexOf(name)") ? '存在' : '不存在',
|
||||
html.includes("state.backdoor.mustWinList.indexOf(name)"));
|
||||
|
||||
// TC005: 删除后同步清理排除名单
|
||||
check('M001', 'TC005', '删除后同步清理排除名单',
|
||||
'removeByName 中清理 excludeList',
|
||||
html.includes("state.backdoor.excludeList.indexOf(name)") ? '存在' : '不存在',
|
||||
html.includes("state.backdoor.excludeList.indexOf(name)"));
|
||||
|
||||
// TC006: 删除后同步清理概率配置
|
||||
check('M001', 'TC006', '删除后同步清理概率配置',
|
||||
'removeByName 中清理 probabilities',
|
||||
html.includes("delete state.backdoor.probabilities[name]") ? '存在' : '不存在',
|
||||
html.includes("delete state.backdoor.probabilities[name]"));
|
||||
|
||||
// TC007: 抽奖中禁用删除按钮
|
||||
check('M001', 'TC007', '抽奖中(isDrawing=true)删除按钮被禁用',
|
||||
'isDrawing 时 delBtn.disabled = true',
|
||||
html.includes("delBtn.disabled = true") ? '存在' : '不存在',
|
||||
html.includes("delBtn.disabled = true"));
|
||||
|
||||
// TC008: 抽奖中 name-list 添加 disabled class
|
||||
check('M001', 'TC008', '抽奖中 name-list 添加 disabled class',
|
||||
'isDrawing 时 container.classList.add("disabled")',
|
||||
html.includes("container.classList.add('disabled')") ? '存在' : '不存在',
|
||||
html.includes("container.classList.add('disabled')"));
|
||||
|
||||
// TC009: CSS 中 disabled 隐藏删除按钮
|
||||
check('M001', 'TC009', 'CSS .disabled 隐藏删除按钮',
|
||||
'存在 .name-list.disabled .delete-btn { display: none }',
|
||||
html.includes('.name-list.disabled .name-tag .delete-btn') ? '存在' : '不存在',
|
||||
html.includes('.name-list.disabled .name-tag .delete-btn'));
|
||||
|
||||
// TC010: 删除最后一个人的边界处理
|
||||
check('M001', 'TC010', '删除逻辑 splice 正确处理边界',
|
||||
'splice 在 idx === -1 时跳过',
|
||||
html.includes('if (idx === -1) return') ? '存在' : '不存在',
|
||||
html.includes('if (idx === -1) return'));
|
||||
|
||||
// ============================================================
|
||||
// M002: 概率后门(每人概率设置)
|
||||
// ============================================================
|
||||
console.log('\n=== M002: 概率后门 ===');
|
||||
|
||||
// TC011: 后门面板有概率设置区域
|
||||
check('M002', 'TC011', '后门面板新增概率设置区域',
|
||||
'HTML 中有概率设置 section',
|
||||
html.includes('🎲 概率设置') ? '存在' : '不存在',
|
||||
html.includes('🎲 概率设置'));
|
||||
|
||||
// TC012: 概率列表容器
|
||||
check('M002', 'TC012', '有 probability-list 容器',
|
||||
'存在 #probabilityList 元素',
|
||||
html.includes('id="probabilityList"') ? '存在' : '不存在',
|
||||
html.includes('id="probabilityList"'));
|
||||
|
||||
// TC013: 状态中有 probabilities 字段
|
||||
check('M002', 'TC013', '状态结构包含 probabilities',
|
||||
'state.backdoor.probabilities 存在',
|
||||
html.includes('probabilities: {},') ? '存在' : '不存在',
|
||||
html.includes('probabilities: {},'));
|
||||
|
||||
// TC014: person 对象有 weight 字段
|
||||
check('M002', 'TC014', 'person 对象有 weight 字段',
|
||||
'generatePerson 返回 weight: 1',
|
||||
html.includes('weight: 1') ? '存在' : '不存在',
|
||||
html.includes('weight: 1'));
|
||||
|
||||
// TC015: 加权随机算法 - 过滤权重为0
|
||||
check('M002', 'TC015', '加权随机算法过滤权重为0的人',
|
||||
'pickOne 中过滤 weight > 0',
|
||||
html.includes('return w > 0') ? '存在' : '不存在',
|
||||
html.includes('return w > 0'));
|
||||
|
||||
// TC016: 加权随机算法 - 计算总权重
|
||||
check('M002', 'TC016', '加权随机算法计算总权重',
|
||||
'存在 totalWeight 累加逻辑',
|
||||
html.includes('totalWeight += self.getWeight') ? '存在' : '不存在',
|
||||
html.includes('totalWeight += self.getWeight'));
|
||||
|
||||
// TC017: 加权随机算法 - 累加选择
|
||||
check('M002', 'TC017', '加权随机算法使用累加方式选择',
|
||||
'存在 cumulative 累加比较',
|
||||
html.includes('cumulative += self.getWeight') ? '存在' : '不存在',
|
||||
html.includes('cumulative += self.getWeight'));
|
||||
|
||||
// TC018: 必中名单优先级最高
|
||||
check('M002', 'TC018', '必中名单优先级高于概率权重',
|
||||
'pickOne 中必中名单判断在加权随机之前',
|
||||
html.includes('若必中名单非空,取交集(最高优先级,等概率)') ? '存在' : '不存在',
|
||||
html.includes('若必中名单非空,取交集(最高优先级,等概率)'));
|
||||
|
||||
// TC019: 排除名单在加权随机之前过滤
|
||||
check('M002', 'TC019', '排除名单在加权随机之前过滤',
|
||||
'pickOne 中先 applyExclude',
|
||||
html.includes('candidates = this.applyExclude(candidates)') ? '存在' : '不存在',
|
||||
html.includes('candidates = this.applyExclude(candidates)'));
|
||||
|
||||
// TC020: 快速设置按钮
|
||||
check('M002', 'TC020', '快速设置按钮(0/1/5/10/50/100)',
|
||||
'quickValues 包含 [0,1,5,10,50,100]',
|
||||
html.includes('[0, 1, 5, 10, 50, 100]') ? '存在' : '不存在',
|
||||
html.includes('[0, 1, 5, 10, 50, 100]'));
|
||||
|
||||
// TC021: 重置所有权重功能
|
||||
check('M002', 'TC021', '重置所有概率功能',
|
||||
'存在 resetAllWeights 方法',
|
||||
html.includes('resetAllWeights: function()') ? '存在' : '不存在',
|
||||
html.includes('resetAllWeights: function()'));
|
||||
|
||||
// TC022: 重置按钮绑定
|
||||
check('M002', 'TC022', '重置按钮事件绑定',
|
||||
'btnResetAllWeights 绑定 click 事件',
|
||||
html.includes('btnResetAllWeights') && html.includes('resetAllWeights') ? '存在' : '不存在',
|
||||
html.includes('btnResetAllWeights') && html.includes('resetAllWeights'));
|
||||
|
||||
// TC023: 权重输入框范围限制
|
||||
check('M002', 'TC023', '权重输入框有 min/max 限制',
|
||||
'weight-input 有 min=0 max=100',
|
||||
html.includes("weightInput.min = 0") && html.includes("weightInput.max = 100") ? '存在' : '不存在',
|
||||
html.includes("weightInput.min = 0") && html.includes("weightInput.max = 100"));
|
||||
|
||||
// TC024: 权重值保存到 state
|
||||
check('M002', 'TC024', 'saveProbabilities 保存权重到 state',
|
||||
'state.backdoor.probabilities 被赋值',
|
||||
html.includes('state.backdoor.probabilities = newProbs') ? '存在' : '不存在',
|
||||
html.includes('state.backdoor.probabilities = newProbs'));
|
||||
|
||||
// TC025: 权重同步到 pool
|
||||
check('M002', 'TC025', '权重同步更新到 pool 中 person.weight',
|
||||
'person.weight 被更新',
|
||||
html.includes('person.weight = newProbs[person.name]') ? '存在' : '不存在',
|
||||
html.includes('person.weight = newProbs[person.name]'));
|
||||
|
||||
// ============================================================
|
||||
// M003: 滚动动画改进(左→右老虎机效果)
|
||||
// ============================================================
|
||||
console.log('\n=== M003: 滚动动画改进 ===');
|
||||
|
||||
// TC026: 水平滚动容器
|
||||
check('M003', 'TC026', '有 scroll-container 水平滚动容器',
|
||||
'存在 .scroll-container 和 overflow:hidden',
|
||||
html.includes('.scroll-container') && html.includes('overflow: hidden') ? '存在' : '不存在',
|
||||
html.includes('.scroll-container') && html.includes('overflow: hidden'));
|
||||
|
||||
// TC027: 滚动轨道
|
||||
check('M003', 'TC027', '有 scroll-track 滚动轨道',
|
||||
'存在 .scroll-track 和 display:flex',
|
||||
html.includes('.scroll-track') && html.includes('display: flex') ? '存在' : '不存在',
|
||||
html.includes('.scroll-track') && html.includes('display: flex'));
|
||||
|
||||
// TC028: 使用 translateX 实现水平滚动
|
||||
check('M003', 'TC028', '使用 translateX 实现水平滚动',
|
||||
'scrollTrackEl.style.transform = translateX',
|
||||
html.includes("scrollTrackEl.style.transform = 'translateX(") ? '存在' : '不存在',
|
||||
html.includes("scrollTrackEl.style.transform = 'translateX("));
|
||||
|
||||
// TC029: 总时长约 3.5 秒
|
||||
check('M003', 'TC029', '动画总时长约 3.5 秒',
|
||||
'duration = 3500',
|
||||
html.includes('duration = 3500') ? '存在' : '不存在',
|
||||
html.includes('duration = 3500'));
|
||||
|
||||
// TC030: 使用 easeOutCubic 缓动
|
||||
check('M003', 'TC030', '使用 easeOutCubic 缓动函数',
|
||||
'存在 easeOutCubic 函数定义',
|
||||
html.includes('easeOutCubic(t)') ? '存在' : '不存在',
|
||||
html.includes('easeOutCubic(t)'));
|
||||
|
||||
// TC031: 使用 requestAnimationFrame
|
||||
check('M003', 'TC031', '使用 requestAnimationFrame 实现动画',
|
||||
'存在 requestAnimationFrame 调用',
|
||||
html.includes('requestAnimationFrame(tick)') ? '存在' : '不存在',
|
||||
html.includes('requestAnimationFrame(tick)'));
|
||||
|
||||
// TC032: 最终停在正确的中奖人
|
||||
check('M003', 'TC032', '动画结束时停在 winner',
|
||||
'callback(winnerPerson) 传入正确中奖人',
|
||||
html.includes('callback(winnerPerson)') ? '存在' : '不存在',
|
||||
html.includes('callback(winnerPerson)'));
|
||||
|
||||
// TC033: 滚动容器初始隐藏
|
||||
check('M003', 'TC033', '滚动容器初始隐藏',
|
||||
'scroll-container 有 .hidden class',
|
||||
html.includes('id="scrollContainer" class="scroll-container hidden"') ? '存在' : '不存在',
|
||||
html.includes('id="scrollContainer" class="scroll-container hidden"'));
|
||||
|
||||
// TC034: 滚动结束后隐藏容器
|
||||
check('M003', 'TC034', '滚动结束后隐藏 scroll-container',
|
||||
'progress >= 1 时 scrollContainerEl.classList.add("hidden")',
|
||||
html.includes("scrollContainerEl.classList.add('hidden')") ? '存在' : '不存在',
|
||||
html.includes("scrollContainerEl.classList.add('hidden')"));
|
||||
|
||||
// ============================================================
|
||||
// M004: 弹窗显示中奖结果
|
||||
// ============================================================
|
||||
console.log('\n=== M004: 弹窗显示中奖结果 ===');
|
||||
|
||||
// TC035: 弹窗 HTML 结构存在
|
||||
check('M004', 'TC035', '有 winner-overlay 弹窗结构',
|
||||
'存在 #winnerOverlay 元素',
|
||||
html.includes('id="winnerOverlay"') ? '存在' : '不存在',
|
||||
html.includes('id="winnerOverlay"'));
|
||||
|
||||
// TC036: 弹窗包含大尺寸头像
|
||||
check('M004', 'TC036', '弹窗包含大尺寸头像',
|
||||
'存在 #winnerAvatarLarge 元素',
|
||||
html.includes('id="winnerAvatarLarge"') ? '存在' : '不存在',
|
||||
html.includes('id="winnerAvatarLarge"'));
|
||||
|
||||
// TC037: 弹窗包含"恭喜"字样
|
||||
check('M004', 'TC037', '弹窗包含"恭喜中奖"字样',
|
||||
'存在 .winner-congrats 含"恭喜中奖"',
|
||||
html.includes('恭喜中奖') ? '存在' : '不存在',
|
||||
html.includes('恭喜中奖'));
|
||||
|
||||
// TC038: 弹窗 z-index 高于烟花 Canvas
|
||||
check('M004', 'TC038', '弹窗 z-index (10001) > 烟花 Canvas (9999)',
|
||||
'winner-overlay z-index: 10001, fireworksCanvas z-index: 9999',
|
||||
'z-index: 10001 vs z-index: 9999',
|
||||
html.includes('z-index: 10001') && html.includes('z-index: 9999'));
|
||||
|
||||
// TC039: 关闭按钮存在
|
||||
check('M004', 'TC039', '弹窗有关闭按钮',
|
||||
'存在 #winnerClose 按钮',
|
||||
html.includes('id="winnerClose"') ? '存在' : '不存在',
|
||||
html.includes('id="winnerClose"'));
|
||||
|
||||
// TC040: 点击遮罩关闭弹窗
|
||||
check('M004', 'TC040', '点击遮罩层可关闭弹窗',
|
||||
'winnerOverlay click 事件判断 e.target',
|
||||
html.includes('e.target === DOM.winnerOverlay') ? '存在' : '不存在',
|
||||
html.includes('e.target === DOM.winnerOverlay'));
|
||||
|
||||
// TC041: 关闭按钮事件绑定
|
||||
check('M004', 'TC041', '关闭按钮绑定 click 事件',
|
||||
'winnerClose click 事件绑定',
|
||||
html.includes('DOM.winnerClose.addEventListener') ? '存在' : '不存在',
|
||||
html.includes('DOM.winnerClose.addEventListener'));
|
||||
|
||||
// TC042: 弹窗动画(winnerPopIn)
|
||||
check('M004', 'TC042', '弹窗有弹出缩放+淡入动画',
|
||||
'存在 @keyframes winnerPopIn',
|
||||
html.includes('@keyframes winnerPopIn') ? '存在' : '不存在',
|
||||
html.includes('@keyframes winnerPopIn'));
|
||||
|
||||
// TC043: 弹窗动画包含 scale 和 opacity
|
||||
check('M004', 'TC043', '弹窗动画包含 scale 和 opacity',
|
||||
'winnerPopIn 含 scale(0.5) 和 opacity: 0',
|
||||
html.includes("scale(0.5)") && html.includes("opacity: 0") ? '存在' : '不存在',
|
||||
html.includes("scale(0.5)") && html.includes("opacity: 0"));
|
||||
|
||||
// TC044: showWinnerPopup 方法存在
|
||||
check('M004', 'TC044', 'AnimationEngine.showWinnerPopup 方法存在',
|
||||
'存在 showWinnerPopup 方法',
|
||||
html.includes('showWinnerPopup: function(winnerPerson)') ? '存在' : '不存在',
|
||||
html.includes('showWinnerPopup: function(winnerPerson)'));
|
||||
|
||||
// TC045: 抽奖结束后调用 showWinnerPopup
|
||||
check('M004', 'TC045', '抽奖动画结束后调用 showWinnerPopup',
|
||||
'startLottery callback 中调用 showWinnerPopup',
|
||||
html.includes('AnimationEngine.showWinnerPopup(finalWinner)') ? '存在' : '不存在',
|
||||
html.includes('AnimationEngine.showWinnerPopup(finalWinner)'));
|
||||
|
||||
// ============================================================
|
||||
// M005: 烟花效果改进
|
||||
// ============================================================
|
||||
console.log('\n=== M005: 烟花效果改进 ===');
|
||||
|
||||
// TC046: 烟花无上升阶段
|
||||
check('M005', 'TC046', '烟花无上升阶段(直接爆炸)',
|
||||
'Firework.state 初始为 "exploding" 而非 "rising"',
|
||||
html.includes("this.state = 'exploding'") ? '存在' : '不存在',
|
||||
html.includes("this.state = 'exploding'"));
|
||||
|
||||
// TC047: 无 rising 状态
|
||||
check('M005', 'TC047', '代码中无 rising 状态',
|
||||
'无 "rising" 状态引用',
|
||||
!html.includes("'rising'") ? '不存在' : '存在(异常)',
|
||||
!html.includes("'rising'"));
|
||||
|
||||
// TC048: 爆炸点围绕弹窗位置
|
||||
check('M005', 'TC048', '爆炸点围绕弹窗位置分布',
|
||||
'获取 popupRect 并围绕其生成爆炸点',
|
||||
html.includes('popupEl.getBoundingClientRect()') && html.includes('spreadRadius') ? '存在' : '不存在',
|
||||
html.includes('popupEl.getBoundingClientRect()') && html.includes('spreadRadius'));
|
||||
|
||||
// TC049: 烟花持续 5 秒
|
||||
check('M005', 'TC049', '烟花持续时间 ≥ 3 秒(实际 5 秒)',
|
||||
'endTime = performance.now() + 5000',
|
||||
html.includes('endTime = performance.now() + 5000') ? '存在' : '不存在',
|
||||
html.includes('endTime = performance.now() + 5000'));
|
||||
|
||||
// TC050: 粒子数量充足
|
||||
check('M005', 'TC050', '每朵烟花粒子数量充足(100-140个)',
|
||||
'100 + Math.random() * 40 个粒子',
|
||||
html.includes('100 + Math.floor(Math.random() * 40)') ? '存在' : '不存在',
|
||||
html.includes('100 + Math.floor(Math.random() * 40)'));
|
||||
|
||||
// TC051: 烟花结束后 Canvas 清除
|
||||
check('M005', 'TC051', '烟花结束后 Canvas 正确清除',
|
||||
'now > endTime 时 ctx.clearRect',
|
||||
html.includes('ctx.clearRect(0, 0, canvas.width, canvas.height)') ? '存在' : '不存在',
|
||||
html.includes('ctx.clearRect(0, 0, canvas.width, canvas.height)'));
|
||||
|
||||
// TC052: 烟花在弹窗周围随机位置生成
|
||||
check('M005', 'TC052', '烟花在弹窗周围随机角度和半径生成',
|
||||
'使用随机 angle 和 radius 生成爆炸点',
|
||||
html.includes('Math.random() * Math.PI * 2') && html.includes('spreadRadius * (0.5') ? '存在' : '不存在',
|
||||
html.includes('Math.random() * Math.PI * 2') && html.includes('spreadRadius * (0.5'));
|
||||
|
||||
// TC053: 烟花调用位置正确
|
||||
check('M005', 'TC053', '抽奖结束后调用 fireFireworks',
|
||||
'startLottery callback 中调用 fireFireworks',
|
||||
html.includes('AnimationEngine.fireFireworks()') ? '存在' : '不存在',
|
||||
html.includes('AnimationEngine.fireFireworks()'));
|
||||
|
||||
// TC054: 烟花颜色丰富
|
||||
check('M005', 'TC054', '烟花有多种颜色调色板',
|
||||
'randomColorPalette 返回多种颜色组合',
|
||||
html.includes('randomColorPalette') ? '存在' : '不存在',
|
||||
html.includes('randomColorPalette'));
|
||||
|
||||
// TC055: 烟花粒子有重力效果
|
||||
check('M005', 'TC055', '烟花粒子有重力和摩擦效果',
|
||||
'Particle 有 gravity 和 friction 属性',
|
||||
html.includes('this.gravity') && html.includes('this.friction') ? '存在' : '不存在',
|
||||
html.includes('this.gravity') && html.includes('this.friction'));
|
||||
|
||||
// ============================================================
|
||||
// 汇总
|
||||
// ============================================================
|
||||
console.log('\n=== 汇总 ===');
|
||||
let totalPass = 0, totalFail = 0;
|
||||
for (const mod of ['M001', 'M002', 'M003', 'M004', 'M005']) {
|
||||
const pass = results[mod].filter(r => r.pass).length;
|
||||
const fail = results[mod].filter(r => !r.pass).length;
|
||||
totalPass += pass;
|
||||
totalFail += fail;
|
||||
console.log(`${mod}: ${pass} 通过, ${fail} 失败, 共 ${results[mod].length} 项`);
|
||||
}
|
||||
console.log(`总计: ${totalPass} 通过, ${totalFail} 失败, 共 ${totalPass + totalFail} 项`);
|
||||
|
||||
// 输出 JSON 结果供报告生成
|
||||
console.log('\n--- JSON OUTPUT ---');
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
Reference in New Issue
Block a user