feat: refine profile and home UI with dynamic fortune rules

This commit is contained in:
zhouzhibo committed 2026-10-15 21:56:45 +08:00
1 parent 869376979c
commit 4608dbd5cf
59 files changed
+3424 -1347

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
const test = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const Module = require('node:module')
const ts = require('typescript')
const { Solar } = require('lunar-typescript')
const cache = {}
function load(name) {
if (cache[name]) return cache[name].exports
const filename = path.resolve(__dirname, `../src/utils/${name}.ts`)
const loaded = new Module(filename, module)
cache[name] = loaded
loaded.filename = filename
loaded.paths = Module._nodeModulePaths(path.dirname(filename))
const original = loaded.require.bind(loaded)
loaded.require = (id) => id.startsWith('./') ? load(id.slice(2)) : original(id)
loaded._compile(ts.transpileModule(fs.readFileSync(filename, 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017 } }).outputText, filename)
return loaded.exports
}
const { generateHomeOverview: generate, normalizeDimensionOrder, HOME_DIMENSION_IDS } = load('homeOverview')
const profile = { birthday: '1998-06-18', birthHour: '辰时' }
test('昨日今日明日整体切换,公历星期农历均来自实际所选日期', () => {
const reports = [-1, 0, 1].map((offset) => generate(profile, '2026-10-15', offset))
assert.deepEqual(reports.map((item) => item.activeDay), ['昨日', '今日', '明日'])
assert.deepEqual(reports.map((item) => item.date), ['2026-10-14', '2026-10-15', '2026-10-16'])
reports.forEach((report) => {
const [y, m, d] = report.date.split('-').map(Number)
const solar = Solar.fromYmd(y, m, d)
const lunar = solar.getLunar()
assert.equal(report.weekName, `星期${'日一二三四五六'[solar.getWeek()]}`)
assert.equal(report.lunarDate, `农历${lunar.getYearInGanZhi()}年 ${lunar.getMonthInChinese()}月${lunar.getDayInChinese()}`)
assert.equal(report.luckyDirection, lunar.getDayPositionXiDesc())
assert.equal(report.days.find((day) => day.label === report.activeDay).status, report.status)
})
assert.notDeepEqual(reports[0].dimensions, reports[1].dimensions)
assert.notDeepEqual(reports[1].dimensions, reports[2].dimensions)
})
test('八维评分有界,宜忌与评分一致,综合指数可复算', () => {
for (let day = 1; day <= 31; day++) {
const report = generate(profile, `2026-10-${String(day).padStart(2, '0')}`)
assert.equal(report.dimensions.length, 8)
assert.equal(new Set(report.dimensions.map((item) => item.id)).size, 8)
report.dimensions.forEach((item) => {
assert.ok(item.score >= 15 && item.score <= 90)
assert.equal(item.suitable, item.score >= 50)
assert.ok(item.headline && item.detail)
})
assert.equal(report.overallIndex, Math.round(report.dimensions.reduce((sum, item) => sum + item.score, 0) / 8))
assert.equal(report.status, report.overallIndex >= 80 ? '大吉' : report.overallIndex >= 65 ? '小吉' : report.overallIndex >= 50 ? '平日' : report.overallIndex >= 35 ? '小逆' : '谨慎')
assert.equal(report.luckyColors.length, 2)
assert.equal(new Set(report.luckyNumbers).size, 2)
}
})
test('吉时仅取所选当日的吉时片段,不越过日期边界且时支不重复', () => {
for (let day = 1; day <= 31; day++) {
const report = generate(profile, `2026-10-${String(day).padStart(2, '0')}`)
const times = Solar.fromYmd(2026, 10, day).getLunar().getTimes()
assert.ok(report.luckyHours.length > 0 && report.luckyHours.length <= 2)
assert.equal(new Set(report.luckyHours.map((hour) => hour.branch)).size, report.luckyHours.length)
report.luckyHours.forEach(({ branch, time }) => {
const [start, end] = time.split('–')
const match = times.find((hour) => hour.getZhi() === branch && hour.getMinHm() === start)
assert.ok(match)
assert.equal(match.getTianShenLuck(), '吉')
assert.ok(end <= '24:00')
assert.ok(start < end)
})
}
})
test('日期、生日、时辰影响报告;相同输入稳定且不读取后端报告', () => {
const first = generate(profile, '2026-10-15')
assert.deepEqual(first, generate({ ...profile, elementReport: { invalid: true } }, '2026-10-15'))
assert.notDeepEqual(first, generate({ birthday: '1999-11-06', birthHour: '辰时' }, '2026-10-15'))
let changedHour = false
for (const hour of ['子时', '卯时', '午时', '戌时']) {
if (JSON.stringify(generate({ ...profile, birthHour: hour }, '2026-10-15')) !== JSON.stringify(first)) changedHour = true
}
assert.equal(changedHour, true)
})
test('跨月、跨年与非法日期安全处理,不回退为固定42分报告', () => {
assert.equal(generate(profile, '2026-01-01', -1).date, '2025-12-31')
assert.equal(generate(profile, '2026-12-31', 1).date, '2027-01-01')
assert.equal(generate(profile, '2024-03-01', -1).date, '2024-02-29')
for (const date of ['', '2026-02-30', '2026-13-01']) assert.equal(generate(profile, date), null)
assert.equal(generate({ birthday: '', birthHour: '' }, '2026-10-15'), null)
assert.equal(generate(profile, '2026-10-15', 2), null)
})
test('排序兼容缺失、重复、旧字段和损坏存储,保留八个维度', () => {
assert.deepEqual(normalizeDimensionOrder(null), HOME_DIMENSION_IDS)
assert.deepEqual(normalizeDimensionOrder('bad'), HOME_DIMENSION_IDS)
const order = normalizeDimensionOrder(['travel', 'travel', 'removed', 'wealth', null])
assert.deepEqual(order.slice(0, 2), ['travel', 'wealth'])
assert.equal(order.length, 8)
assert.equal(new Set(order).size, 8)
})
test('首页八个维度、头像与天宫图标资产存在', () => {
for (const icon of ['heart', 'gourd', 'vase', 'coin', 'fan', 'scroll', 'bamboo', 'horse', 'water', 'spirit']) {
const data = fs.readFileSync(path.resolve(__dirname, `../src/assets/home/${icon}.png`))
assert.equal(data.subarray(1, 4).toString(), 'PNG')
assert.equal(data.readUInt32BE(16), 256)
}
})