608 lines
25 KiB
JavaScript
608 lines
25 KiB
JavaScript
/**
|
||
* =============================================
|
||
* 时间显示修复 - 完整回归测试脚本
|
||
* =============================================
|
||
* 测试目标: 验证 createdAt 排序修复和 parseDate 本地时区解析
|
||
* 测试环境: 后端 API (localhost:3001)
|
||
* 测试日期: 2026-04-26
|
||
* =============================================
|
||
*/
|
||
|
||
import http from 'http';
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
|
||
const BASE_URL = 'http://localhost:3001';
|
||
const USER_ID = 1;
|
||
|
||
// 测试报告
|
||
const report = {
|
||
total: 0,
|
||
passed: 0,
|
||
failed: 0,
|
||
warnings: 0,
|
||
results: [],
|
||
startTime: new Date(),
|
||
endTime: null
|
||
};
|
||
|
||
function log(msg, level = 'INFO') {
|
||
const icon = { INFO: '📝', PASS: '✅', FAIL: '❌', WARN: '⚠️', HEADER: '📋', SEP: '─' };
|
||
const prefix = icon[level] || '📝';
|
||
console.log(`${prefix} ${msg}`);
|
||
}
|
||
|
||
function recordTest(name, status, detail = '') {
|
||
report.total++;
|
||
if (status === 'PASS') {
|
||
report.passed++;
|
||
log(`${name} - PASS${detail ? ' | ' + detail : ''}`, 'PASS');
|
||
} else if (status === 'WARN') {
|
||
report.warnings++;
|
||
log(`${name} - WARN${detail ? ' | ' + detail : ''}`, 'WARN');
|
||
} else {
|
||
report.failed++;
|
||
log(`${name} - FAIL${detail ? ' | ' + detail : ''}`, 'FAIL');
|
||
}
|
||
report.results.push({ name, status, detail });
|
||
}
|
||
|
||
// API 请求封装
|
||
function apiRequest(method, path, body = null) {
|
||
return new Promise((resolve, reject) => {
|
||
const url = new URL(path, BASE_URL);
|
||
const options = {
|
||
hostname: url.hostname,
|
||
port: url.port,
|
||
path: url.pathname + url.search,
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
};
|
||
|
||
const req = http.request(options, (res) => {
|
||
let data = '';
|
||
res.on('data', (chunk) => (data += chunk));
|
||
res.on('end', () => {
|
||
try {
|
||
resolve({ statusCode: res.statusCode, body: JSON.parse(data) });
|
||
} catch (e) {
|
||
resolve({ statusCode: res.statusCode, body: data });
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on('error', reject);
|
||
if (body) req.write(JSON.stringify(body));
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// 创建记录辅助函数
|
||
async function createRecord(type, category, amount, description, date = null) {
|
||
const payload = {
|
||
userId: USER_ID,
|
||
accountId: 3,
|
||
type,
|
||
amount,
|
||
category,
|
||
description,
|
||
date: date || new Date().toISOString().split('T')[0]
|
||
};
|
||
const result = await apiRequest('POST', '/api/records', payload);
|
||
if (result.body.success) {
|
||
return result.body.data;
|
||
} else {
|
||
throw new Error(`创建记录失败: ${result.body.message}`);
|
||
}
|
||
}
|
||
|
||
// 获取记录辅助函数
|
||
async function getRecords(params = {}) {
|
||
const query = new URLSearchParams({ userId: USER_ID, ...params });
|
||
const result = await apiRequest('GET', `/api/records?${query}`);
|
||
if (result.body.success) {
|
||
return result.body.data;
|
||
}
|
||
return [];
|
||
}
|
||
|
||
// 删除记录辅助函数
|
||
async function deleteRecord(id) {
|
||
await apiRequest('DELETE', `/api/records/${id}`);
|
||
}
|
||
|
||
async function sleep(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
// =============================================
|
||
// 主测试流程
|
||
// =============================================
|
||
async function runTests() {
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log('🧪 时间显示修复 - 完整回归测试');
|
||
console.log('='.repeat(60));
|
||
console.log(`测试开始时间: ${new Date().toLocaleString('zh-CN')}`);
|
||
console.log(`后端地址: ${BASE_URL}`);
|
||
console.log(`测试用户: userId=${USER_ID}`);
|
||
console.log('='.repeat(60) + '\n');
|
||
|
||
// ==========================================
|
||
// 测试1: 验证后端 parseDate 函数
|
||
// ==========================================
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试1】验证后端 parseDate 函数对纯日期字符串的解析', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
// 创建一条记录,使用纯日期字符串
|
||
const now = new Date();
|
||
const dateStr = '2026-04-26';
|
||
const record1 = await createRecord('expense', '餐饮', 10, 'parseDate测试', dateStr);
|
||
|
||
const parsedDate = new Date(record1.date);
|
||
const expectedHour = 0; // 应该解析为当天 00:00 北京时间
|
||
const actualHour = parsedDate.getHours();
|
||
|
||
log(`输入日期字符串: "${dateStr}"`, 'INFO');
|
||
log(`解析结果: ${parsedDate.toLocaleString('zh-CN')}`, 'INFO');
|
||
log(`解析结果 ISO: ${parsedDate.toISOString()}`, 'INFO');
|
||
log(`本地时区小时: ${actualHour}:00`, 'INFO');
|
||
|
||
if (actualHour === expectedHour) {
|
||
recordTest('parseDate 解析纯日期为本地时区 00:00', 'PASS',
|
||
`解析为 ${parsedDate.toLocaleString('zh-CN')} (北京时间 00:00,非 UTC 00:00)`);
|
||
} else {
|
||
recordTest('parseDate 解析纯日期为本地时区 00:00', 'FAIL',
|
||
`期望 00:00,实际 ${actualHour}:00`);
|
||
}
|
||
|
||
// 验证 createdAt 为实际创建时间
|
||
const createdAt = new Date(record1.createdAt);
|
||
const createdAtDiff = Math.abs(createdAt.getTime() - now.getTime());
|
||
|
||
log(`记录创建时间 (createdAt): ${createdAt.toLocaleString('zh-CN')}`, 'INFO');
|
||
log(`创建时间差: ${createdAtDiff}ms`, 'INFO');
|
||
|
||
if (createdAtDiff < 5000) {
|
||
recordTest('createdAt 反映实际创建时间', 'PASS',
|
||
`与当前时间差 ${createdAtDiff}ms < 5s`);
|
||
} else {
|
||
recordTest('createdAt 反映实际创建时间', 'FAIL',
|
||
`与当前时间差 ${createdAtDiff}ms > 5s`);
|
||
}
|
||
|
||
await deleteRecord(record1.id);
|
||
|
||
// ==========================================
|
||
// 测试2: 验证后端 API 返回的 createdAt 字段
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试2】验证后端 API 返回的 createdAt 字段是否正确', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
const beforeCreate = new Date();
|
||
await sleep(100);
|
||
const record2 = await createRecord('expense', '交通', 20, 'createdAt验证');
|
||
const afterCreate = new Date();
|
||
|
||
const apiCreatedAt = new Date(record2.createdAt);
|
||
|
||
log(`创建前时间: ${beforeCreate.toLocaleString('zh-CN')}`, 'INFO');
|
||
log(`API返回 createdAt: ${apiCreatedAt.toLocaleString('zh-CN')}`, 'INFO');
|
||
log(`创建后时间: ${afterCreate.toLocaleString('zh-CN')}`, 'INFO');
|
||
|
||
const isWithinRange = apiCreatedAt >= beforeCreate && apiCreatedAt <= afterCreate;
|
||
|
||
if (isWithinRange) {
|
||
recordTest('API 返回的 createdAt 在创建时间范围内', 'PASS',
|
||
`${apiCreatedAt.toLocaleString('zh-CN')} 在 [${beforeCreate.toLocaleString('zh-CN')}, ${afterCreate.toLocaleString('zh-CN')}] 内`);
|
||
} else {
|
||
recordTest('API 返回的 createdAt 在创建时间范围内', 'FAIL',
|
||
`createdAt 不在预期时间范围内`);
|
||
}
|
||
|
||
// 验证 createdAt 包含时分秒
|
||
const hasTimeComponent = record2.createdAt.includes(':');
|
||
if (hasTimeComponent) {
|
||
recordTest('createdAt 包含时分秒信息', 'PASS', `值: ${record2.createdAt}`);
|
||
} else {
|
||
recordTest('createdAt 包含时分秒信息', 'FAIL', `值: ${record2.createdAt}`);
|
||
}
|
||
|
||
await deleteRecord(record2.id);
|
||
|
||
// ==========================================
|
||
// 测试3: 验证后端排序逻辑 (orderBy: { createdAt: 'desc' })
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试3】验证后端排序逻辑 (orderBy: createdAt desc)', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
// 清空现有记录,确保测试环境干净
|
||
const existingRecords = await getRecords();
|
||
for (const r of existingRecords) {
|
||
await deleteRecord(r.id);
|
||
}
|
||
log(`已清理 ${existingRecords.length} 条现有记录`, 'INFO');
|
||
|
||
// 创建3条同一天但不同时间的记录
|
||
const records = [];
|
||
for (let i = 0; i < 3; i++) {
|
||
const r = await createRecord('expense', '餐饮', 10 + i, `同天记录${i + 1}`);
|
||
records.push(r);
|
||
log(`创建记录 ${i + 1}: ID=${r.id}, createdAt=${new Date(r.createdAt).toLocaleTimeString('zh-CN')}`, 'INFO');
|
||
await sleep(500); // 确保 createdAt 有差异
|
||
}
|
||
|
||
// 获取排序后的记录
|
||
const sortedRecords = await getRecords();
|
||
|
||
log(`\n后端返回的排序顺序:`, 'INFO');
|
||
sortedRecords.forEach((r, i) => {
|
||
log(` [${i + 1}] ${r.category} ¥${r.amount} ${r.description} | createdAt=${new Date(r.createdAt).toLocaleTimeString('zh-CN')}`, 'INFO');
|
||
});
|
||
|
||
// 验证最新的记录在最前面
|
||
const firstRecord = sortedRecords[0];
|
||
const lastRecord = sortedRecords[sortedRecords.length - 1];
|
||
|
||
if (firstRecord && firstRecord.description === '同天记录3') {
|
||
recordTest('最新创建的记录排在第一位', 'PASS', `ID=${firstRecord.id}`);
|
||
} else {
|
||
recordTest('最新创建的记录排在第一位', 'FAIL',
|
||
`期望"同天记录3",实际"${firstRecord?.description}"`);
|
||
}
|
||
|
||
// 验证排序是严格降序
|
||
let isStrictDescending = true;
|
||
for (let i = 0; i < sortedRecords.length - 1; i++) {
|
||
const t1 = new Date(sortedRecords[i].createdAt).getTime();
|
||
const t2 = new Date(sortedRecords[i + 1].createdAt).getTime();
|
||
if (t1 < t2) {
|
||
isStrictDescending = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (isStrictDescending) {
|
||
recordTest('后端排序为严格降序', 'PASS', `${sortedRecords.length} 条记录排序正确`);
|
||
} else {
|
||
recordTest('后端排序为严格降序', 'FAIL', '排序出现异常');
|
||
}
|
||
|
||
// ==========================================
|
||
// 测试4: 验证前端 Dashboard 排序逻辑
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试4】验证前端 Dashboard 使用 createdAt 排序', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
// 读取前端代码验证排序逻辑
|
||
const dashboardPath = path.join(__dirname, '..', 'frontend', 'src', 'pages', 'Dashboard', 'index.tsx');
|
||
|
||
if (fs.existsSync(dashboardPath)) {
|
||
const dashboardCode = fs.readFileSync(dashboardPath, 'utf-8');
|
||
|
||
// 检查是否使用 createdAt 排序
|
||
const hasCreatedAtSort = dashboardCode.includes('.sort((a, b) => new Date(b.createdAt)');
|
||
const hasDateSort = dashboardCode.includes('.sort((a, b) => new Date(b.date)') && !hasCreatedAtSort;
|
||
|
||
if (hasCreatedAtSort) {
|
||
recordTest('Dashboard 使用 createdAt 排序', 'PASS', '代码使用 new Date(b.createdAt) 排序');
|
||
} else if (hasDateSort) {
|
||
recordTest('Dashboard 使用 createdAt 排序', 'FAIL', '代码仍使用 new Date(b.date) 排序');
|
||
} else {
|
||
recordTest('Dashboard 使用 createdAt 排序', 'WARN', '未能识别排序逻辑,请手动确认');
|
||
}
|
||
|
||
// 检查 formatRecordTime 函数
|
||
const hasFormatRecordTime = dashboardCode.includes('const formatRecordTime');
|
||
const usesCreatedAtForTime = dashboardCode.includes('new Date(record.createdAt)');
|
||
|
||
if (hasFormatRecordTime && usesCreatedAtForTime) {
|
||
recordTest('Dashboard 时间格式化使用 createdAt', 'PASS', 'formatRecordTime 使用 record.createdAt');
|
||
} else {
|
||
recordTest('Dashboard 时间格式化使用 createdAt', 'FAIL', '时间格式化未使用 createdAt');
|
||
}
|
||
|
||
// 检查时间显示逻辑
|
||
const showsHHMMForToday = dashboardCode.includes("createdAt.getHours()") && dashboardCode.includes("createdAt.getMinutes()");
|
||
if (showsHHMMForToday) {
|
||
recordTest('Dashboard 今天显示 HH:MM 格式', 'PASS', '使用 getHours() 和 getMinutes() 格式化');
|
||
} else {
|
||
recordTest('Dashboard 今天显示 HH:MM 格式', 'FAIL', '未找到 HH:MM 格式化逻辑');
|
||
}
|
||
|
||
const showsYesterday = dashboardCode.includes("'昨天'") || dashboardCode.includes('"昨天"');
|
||
if (showsYesterday) {
|
||
recordTest('Dashboard 昨天显示"昨天"文本', 'PASS', '包含"昨天"显示逻辑');
|
||
} else {
|
||
recordTest('Dashboard 昨天显示"昨天"文本', 'WARN', '未找到"昨天"显示逻辑');
|
||
}
|
||
} else {
|
||
recordTest('Dashboard 文件存在性', 'FAIL', `文件不存在: ${dashboardPath}`);
|
||
}
|
||
|
||
// ==========================================
|
||
// 测试5: 验证前端 Record 页面排序逻辑
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试5】验证前端 Record 页面使用 createdAt 排序', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
const recordPath = path.join(__dirname, '..', 'frontend', 'src', 'pages', 'Record', 'index.tsx');
|
||
|
||
if (fs.existsSync(recordPath)) {
|
||
const recordCode = fs.readFileSync(recordPath, 'utf-8');
|
||
|
||
// 检查排序逻辑
|
||
const hasCreatedAtSort = recordCode.includes('.sort((a, b) => new Date(b.createdAt)');
|
||
const hasDateSort = recordCode.includes('.sort((a, b) => new Date(b.date)') && !hasCreatedAtSort;
|
||
|
||
if (hasCreatedAtSort) {
|
||
recordTest('Record 页面使用 createdAt 排序', 'PASS', '代码使用 new Date(b.createdAt) 排序');
|
||
} else if (hasDateSort) {
|
||
recordTest('Record 页面使用 createdAt 排序', 'FAIL', '代码仍使用 new Date(b.date) 排序');
|
||
} else {
|
||
recordTest('Record 页面使用 createdAt 排序', 'WARN', '未能识别排序逻辑');
|
||
}
|
||
|
||
// 检查 formatTime 函数
|
||
const hasFormatTime = recordCode.includes('const formatTime');
|
||
const usesCreatedAtForTime = recordCode.includes('new Date(record.createdAt)');
|
||
|
||
if (hasFormatTime && usesCreatedAtForTime) {
|
||
recordTest('Record 页面时间格式化使用 createdAt', 'PASS', 'formatTime 使用 record.createdAt');
|
||
} else {
|
||
recordTest('Record 页面时间格式化使用 createdAt', 'FAIL', '时间格式化未使用 createdAt');
|
||
}
|
||
|
||
// 检查今天显示 HH:MM
|
||
const showsHHMM = recordCode.includes("hour: '2-digit'") && recordCode.includes("minute: '2-digit'");
|
||
if (showsHHMM) {
|
||
recordTest('Record 页面今天显示 HH:MM 格式', 'PASS', '使用 toLocaleTimeString 格式化');
|
||
} else {
|
||
recordTest('Record 页面今天显示 HH:MM 格式', 'FAIL', '未找到 HH:MM 格式化逻辑');
|
||
}
|
||
|
||
// 检查昨天显示
|
||
const showsYesterday = recordCode.includes("'昨天'") || recordCode.includes('"昨天"');
|
||
if (showsYesterday) {
|
||
recordTest('Record 页面昨天显示"昨天"文本', 'PASS', '包含"昨天"显示逻辑');
|
||
} else {
|
||
recordTest('Record 页面昨天显示"昨天"文本', 'WARN', '未找到"昨天"显示逻辑');
|
||
}
|
||
|
||
// 检查过滤后再排序
|
||
const filterThenSort = recordCode.includes('.filter(') && recordCode.includes('.sort(');
|
||
if (filterThenSort) {
|
||
recordTest('Record 页面先过滤再排序', 'PASS', '排序在过滤后执行');
|
||
} else {
|
||
recordTest('Record 页面先过滤再排序', 'WARN', '过滤和排序顺序需确认');
|
||
}
|
||
} else {
|
||
recordTest('Record 文件存在性', 'FAIL', `文件不存在: ${recordPath}`);
|
||
}
|
||
|
||
// ==========================================
|
||
// 测试6: 验证同一天多条记录的排序
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试6】验证同一天添加多条记录的排序稳定性', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
// 当前记录应该已经是按 createdAt 降序(来自测试3)
|
||
// 再添加2条记录,验证最新始终在最前
|
||
|
||
const extra1 = await createRecord('expense', '购物', 50, '额外记录1');
|
||
log(`创建额外记录1: ID=${extra1.id}`, 'INFO');
|
||
await sleep(500);
|
||
|
||
const extra2 = await createRecord('income', '兼职', 100, '额外记录2');
|
||
log(`创建额外记录2: ID=${extra2.id}`, 'INFO');
|
||
await sleep(500);
|
||
|
||
const recordsAfterExtra = await getRecords();
|
||
|
||
log(`\n添加额外记录后的排序:`, 'INFO');
|
||
recordsAfterExtra.forEach((r, i) => {
|
||
log(` [${i + 1}] ${r.type === 'income' ? '+' : '-'}¥${r.amount} ${r.category} ${r.description} | ${new Date(r.createdAt).toLocaleTimeString('zh-CN')}`, 'INFO');
|
||
});
|
||
|
||
const latestRecord = recordsAfterExtra[0];
|
||
if (latestRecord && latestRecord.id === extra2.id) {
|
||
recordTest('最新添加的记录始终在最前面', 'PASS', `最新记录: "${latestRecord.description}"`);
|
||
} else {
|
||
recordTest('最新添加的记录始终在最前面', 'FAIL',
|
||
`期望"额外记录2"在最前,实际"${latestRecord?.description}"`);
|
||
}
|
||
|
||
// 验证支出筛选后的排序
|
||
const expenseRecords = await getRecords({ type: 'expense' });
|
||
let expenseSorted = true;
|
||
for (let i = 0; i < expenseRecords.length - 1; i++) {
|
||
const t1 = new Date(expenseRecords[i].createdAt).getTime();
|
||
const t2 = new Date(expenseRecords[i + 1].createdAt).getTime();
|
||
if (t1 < t2) {
|
||
expenseSorted = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (expenseSorted) {
|
||
recordTest('支出筛选后仍保持 createdAt 降序', 'PASS', `${expenseRecords.length} 条支出记录排序正确`);
|
||
} else {
|
||
recordTest('支出筛选后仍保持 createdAt 降序', 'FAIL', '支出排序异常');
|
||
}
|
||
|
||
// 验证收入筛选后的排序
|
||
const incomeRecords = await getRecords({ type: 'income' });
|
||
let incomeSorted = true;
|
||
for (let i = 0; i < incomeRecords.length - 1; i++) {
|
||
const t1 = new Date(incomeRecords[i].createdAt).getTime();
|
||
const t2 = new Date(incomeRecords[i + 1].createdAt).getTime();
|
||
if (t1 < t2) {
|
||
incomeSorted = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (incomeSorted) {
|
||
recordTest('收入筛选后仍保持 createdAt 降序', 'PASS', `${incomeRecords.length} 条收入记录排序正确`);
|
||
} else {
|
||
recordTest('收入筛选后仍保持 createdAt 降序', 'FAIL', '收入排序异常');
|
||
}
|
||
|
||
// ==========================================
|
||
// 测试7: 验证前端时间显示逻辑 (模拟)
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试7】验证前端时间显示格式 (模拟前端逻辑)', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
// 模拟前端 formatRecordTime 逻辑 (Dashboard)
|
||
function simulateDashboardFormatTime(record) {
|
||
const createdAt = new Date(record.createdAt);
|
||
const now = new Date();
|
||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
||
const recordDay = new Date(createdAt.getFullYear(), createdAt.getMonth(), createdAt.getDate()).getTime();
|
||
const diffDays = Math.floor((today - recordDay) / (1000 * 60 * 60 * 24));
|
||
|
||
if (diffDays === 0) {
|
||
return `${createdAt.getHours().toString().padStart(2, '0')}:${createdAt.getMinutes().toString().padStart(2, '0')}`;
|
||
} else if (diffDays === 1) {
|
||
return '昨天';
|
||
} else if (diffDays <= 7) {
|
||
return `${diffDays}天前`;
|
||
} else {
|
||
return `${createdAt.getMonth() + 1}月${createdAt.getDate()}日`;
|
||
}
|
||
}
|
||
|
||
// 模拟前端 formatTime 逻辑 (Record)
|
||
function simulateRecordFormatTime(record) {
|
||
const createdAt = new Date(record.createdAt);
|
||
const today = new Date();
|
||
const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toDateString();
|
||
const recordDay = new Date(createdAt.getFullYear(), createdAt.getMonth(), createdAt.getDate()).toDateString();
|
||
|
||
if (todayStr === recordDay) {
|
||
return createdAt.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
||
}
|
||
|
||
const yesterday = new Date(today);
|
||
yesterday.setDate(yesterday.getDate() - 1);
|
||
const yesterdayStr = new Date(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate()).toDateString();
|
||
if (yesterdayStr === recordDay) {
|
||
return '昨天';
|
||
}
|
||
|
||
return createdAt.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
|
||
}
|
||
|
||
// 使用现有记录测试时间显示
|
||
const allRecords = await getRecords();
|
||
|
||
if (allRecords.length > 0) {
|
||
const todayRecord = allRecords.find(r => {
|
||
const created = new Date(r.createdAt);
|
||
const today = new Date();
|
||
return created.getFullYear() === today.getFullYear() &&
|
||
created.getMonth() === today.getMonth() &&
|
||
created.getDate() === today.getDate();
|
||
});
|
||
|
||
if (todayRecord) {
|
||
const dashTime = simulateDashboardFormatTime(todayRecord);
|
||
const recordTime = simulateRecordFormatTime(todayRecord);
|
||
|
||
log(`今天创建的记录: "${todayRecord.description}"`, 'INFO');
|
||
log(` Dashboard 显示: "${dashTime}"`, 'INFO');
|
||
log(` Record 显示: "${recordTime}"`, 'INFO');
|
||
|
||
// 验证不是 08:00
|
||
if (dashTime !== '08:00') {
|
||
recordTest('Dashboard 今天记录不显示 08:00', 'PASS', `显示: "${dashTime}"`);
|
||
} else {
|
||
recordTest('Dashboard 今天记录不显示 08:00', 'FAIL', `仍显示 "08:00"`);
|
||
}
|
||
|
||
if (recordTime !== '08:00') {
|
||
recordTest('Record 页面今天记录不显示 08:00', 'PASS', `显示: "${recordTime}"`);
|
||
} else {
|
||
recordTest('Record 页面今天记录不显示 08:00', 'FAIL', `仍显示 "08:00"`);
|
||
}
|
||
|
||
// 验证格式为 HH:MM
|
||
const dashTimeRegex = /^\d{2}:\d{2}$/;
|
||
if (dashTimeRegex.test(dashTime)) {
|
||
recordTest('Dashboard 时间格式为 HH:MM', 'PASS', `格式正确: "${dashTime}"`);
|
||
} else {
|
||
recordTest('Dashboard 时间格式为 HH:MM', 'FAIL', `格式不正确: "${dashTime}"`);
|
||
}
|
||
} else {
|
||
recordTest('今天记录时间显示验证', 'WARN', '当前没有今天的记录,跳过此测试');
|
||
}
|
||
} else {
|
||
recordTest('时间显示验证', 'WARN', '无记录可验证');
|
||
}
|
||
|
||
// ==========================================
|
||
// 测试8: 清理测试数据
|
||
// ==========================================
|
||
log('\n═══════════════════════════════════════════════════════', 'HEADER');
|
||
log('【测试8】清理测试数据', 'HEADER');
|
||
log('═══════════════════════════════════════════════════════', 'HEADER');
|
||
|
||
const finalRecords = await getRecords();
|
||
for (const r of finalRecords) {
|
||
await deleteRecord(r.id);
|
||
}
|
||
log(`已清理 ${finalRecords.length} 条测试记录`, 'INFO');
|
||
recordTest('测试数据清理完成', 'PASS', `清理 ${finalRecords.length} 条记录`);
|
||
|
||
// ==========================================
|
||
// 生成测试报告
|
||
// ==========================================
|
||
report.endTime = new Date();
|
||
const duration = report.endTime - report.startTime;
|
||
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log('📊 测试报告');
|
||
console.log('='.repeat(60));
|
||
console.log(`测试总数: ${report.total}`);
|
||
console.log(`通过: ${report.passed}`);
|
||
console.log(`失败: ${report.failed}`);
|
||
console.log(`警告: ${report.warnings}`);
|
||
console.log(`通过率: ${((report.passed / report.total) * 100).toFixed(1)}%`);
|
||
console.log(`耗时: ${duration}ms`);
|
||
console.log('='.repeat(60));
|
||
|
||
console.log('\n📋 测试结果明细:');
|
||
console.log('-'.repeat(60));
|
||
report.results.forEach((r, i) => {
|
||
const icon = r.status === 'PASS' ? '✅' : r.status === 'WARN' ? '⚠️' : '❌';
|
||
console.log(` ${icon} [${i + 1}] ${r.name}${r.detail ? ' | ' + r.detail : ''}`);
|
||
});
|
||
|
||
console.log('\n' + '='.repeat(60));
|
||
if (report.failed === 0) {
|
||
console.log('🎉 全部测试通过!时间显示修复验证成功!');
|
||
} else {
|
||
console.log(`❌ ${report.failed} 项测试失败,请检查修复!`);
|
||
}
|
||
console.log('='.repeat(60) + '\n');
|
||
|
||
return report;
|
||
}
|
||
|
||
// 执行测试
|
||
runTests().catch(err => {
|
||
console.error('测试执行异常:', err);
|
||
process.exit(1);
|
||
});
|