Files
online-attendance-lottery/Test/test-v1.1.0.js

401 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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));