test(mini): 为核心工具函数添加 node:test 单元测试
Publish Mini Program Dev Version / publish (push) Successful in 2m56s

- 新增 mini/utils/core/__tests__/index.test.js,70 个测试用例
- 覆盖:公农历互转、干支计算、生肖星座、纳音、节气、宜忌、冲煞、
  地支关系、十神、天干合冲、五行分析、称骨算命、大运流年分析等
- 未导出函数通过 getDayInfo/getAlmanacInfo 间接验证
- 更新 package.json test 脚本,npm test 同时运行单元测试和 CI 绑定检查
- 使用 Node.js 内置 node:test,零额外依赖
This commit is contained in:
gouki
2026-10-14 12:50:06 +00:00
parent 73374bf93e
commit 28309e0257
2 changed files with 519 additions and 1 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "祈福小助手 - 万年历微信小程序",
"private": true,
"scripts": {
"test": "node ci/check-bindings.cjs",
"test": "node --test utils/core/__tests__/index.test.js && node ci/check-bindings.cjs",
"upload": "node ci/upload-ci.cjs",
"preview": "node ci/preview-ci.cjs"
},
+518
View File
@@ -0,0 +1,518 @@
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const {
solarToLunar,
lunarToSolar,
getGanzhi,
getYearGanzhi,
getMonthGanzhi,
getHourGanzhi,
getZodiac,
getConstellation,
getSolarTerm,
getNayin,
getSixStar,
getBranchRelationship,
getTenStarRelationship,
checkStemCombine,
checkStemOpposite,
daysBetween,
getWeekDay,
getBuddhistFestival,
getLegalHoliday,
isWorkday,
analyzeElementBalance,
calculateBoneWeight,
analyzeFortuneGanzhi,
HEAVEN_STEMS,
EARTH_BRANCHES,
ZODIACS,
LUNAR_MONTH_NAMES,
LUNAR_DAY_NAMES,
WEEK_NAMES,
SOLAR_TERMS,
HOUR_NAMES,
HOUR_RANGES
} = require('../index.js');
// ─── 常量数据完整性 ───────────────────────────────────────────
describe('常量数据完整性', () => {
it('天干应有 10 个', () => {
assert.equal(HEAVEN_STEMS.length, 10);
assert.equal(HEAVEN_STEMS[0], '甲');
assert.equal(HEAVEN_STEMS[9], '癸');
});
it('地支应有 12 个', () => {
assert.equal(EARTH_BRANCHES.length, 12);
assert.equal(EARTH_BRANCHES[0], '子');
assert.equal(EARTH_BRANCHES[11], '亥');
});
it('生肖应有 12 个', () => {
assert.equal(ZODIACS.length, 12);
assert.equal(ZODIACS[0], '鼠');
});
it('农历月份名应有 12 个', () => {
assert.equal(LUNAR_MONTH_NAMES.length, 12);
});
it('农历日期名应有 30 个', () => {
assert.equal(LUNAR_DAY_NAMES.length, 30);
});
it('星期名应有 7 个', () => {
assert.equal(WEEK_NAMES.length, 7);
});
it('节气应有 24 个', () => {
assert.equal(SOLAR_TERMS.length, 24);
});
it('时辰名和时辰范围数量一致', () => {
assert.equal(HOUR_NAMES.length, HOUR_RANGES.length);
});
});
// ─── 公历基础计算 ─────────────────────────────────────────────
describe('getWeekDay', () => {
it('2024-01-01 是周一', () => {
assert.equal(getWeekDay(2024, 1, 1), 1);
});
it('2024-03-17 是周日', () => {
assert.equal(getWeekDay(2024, 3, 17), 0);
});
});
describe('daysBetween', () => {
it('同一天差 0', () => {
assert.equal(daysBetween(2024, 6, 15, 2024, 6, 15), 0);
});
it('跨月计算', () => {
assert.equal(daysBetween(2024, 1, 1, 2024, 2, 1), 31);
});
it('跨年计算', () => {
assert.equal(daysBetween(2024, 1, 1, 2025, 1, 1), 366); // 2024 闰年
});
it('反向为负值', () => {
assert.equal(daysBetween(2025, 1, 1, 2024, 1, 1), -366);
});
});
// ─── 公历转农历 ──────────────────────────────────────────────
describe('solarToLunar', () => {
it('1900-01-31 = 农历1900年正月初一', () => {
const r = solarToLunar(1900, 1, 31);
assert.deepEqual(r, { year: 1900, month: 1, day: 1, isLeap: false });
});
it('2024-02-10 = 农历2024年正月初一(春节)', () => {
const r = solarToLunar(2024, 2, 10);
assert.equal(r.year, 2024);
assert.equal(r.month, 1);
assert.equal(r.day, 1);
assert.equal(r.isLeap, false);
});
it('2023-01-22 = 农历2023年正月初一', () => {
const r = solarToLunar(2023, 1, 22);
assert.equal(r.year, 2023);
assert.equal(r.month, 1);
assert.equal(r.day, 1);
});
it('超出范围返回 null', () => {
assert.equal(solarToLunar(1899, 1, 1), null);
assert.equal(solarToLunar(2101, 1, 1), null);
});
it('农历日期day字段从1开始', () => {
const r = solarToLunar(2024, 2, 11);
assert.equal(r.day, 2); // 正月初二
});
});
// ─── 农历转公历 ──────────────────────────────────────────────
describe('lunarToSolar', () => {
it('农历1900年正月初一 = 1900-01-31', () => {
const r = lunarToSolar(1900, 1, 1);
assert.deepEqual(r, { year: 1900, month: 1, day: 31 });
});
it('农历2024年正月初一 = 2024-02-10', () => {
const r = lunarToSolar(2024, 1, 1);
assert.deepEqual(r, { year: 2024, month: 2, day: 10 });
});
it('超出范围返回 null', () => {
assert.equal(lunarToSolar(1899, 1, 1), null);
});
it('互逆验证:solarToLunar → lunarToSolar 往返一致', () => {
const solar = { year: 2025, month: 6, day: 15 };
const lunar = solarToLunar(solar.year, solar.month, solar.day);
const back = lunarToSolar(lunar.year, lunar.month, lunar.day, lunar.isLeap);
assert.deepEqual(back, solar);
});
});
// ─── 干支计算 ────────────────────────────────────────────────
describe('getYearGanzhi', () => {
it('2024年 = 甲辰年', () => {
const r = getYearGanzhi(2024);
assert.equal(r.ganzhi, '甲辰');
assert.equal(r.stem, '甲');
assert.equal(r.branch, '辰');
});
it('2023年 = 癸卯年', () => {
assert.equal(getYearGanzhi(2023).ganzhi, '癸卯');
});
it('1900年 = 庚子年', () => {
assert.equal(getYearGanzhi(1900).ganzhi, '庚子');
});
it('60年一甲子循环', () => {
const a = getYearGanzhi(2024);
const b = getYearGanzhi(2024 - 60);
assert.equal(a.ganzhi, b.ganzhi);
});
});
describe('getGanzhi(日干支)', () => {
it('返回 stem/branch/ganzhi 三个字段', () => {
const r = getGanzhi(2024, 1, 1);
assert.ok(r.stem);
assert.ok(r.branch);
assert.equal(r.ganzhi, r.stem + r.branch);
});
it('连续两天的日干支差一天', () => {
const a = getGanzhi(2024, 1, 1);
const b = getGanzhi(2024, 1, 2);
const stemA = HEAVEN_STEMS.indexOf(a.stem);
const stemB = HEAVEN_STEMS.indexOf(b.stem);
assert.equal((stemB - stemA + 10) % 10, 1);
});
});
describe('getMonthGanzhi', () => {
it('返回完整干支', () => {
const r = getMonthGanzhi(2024, 1, 15);
assert.ok(r.stem.length === 1);
assert.ok(r.branch.length === 1);
assert.equal(r.ganzhi.length, 2);
});
});
describe('getHourGanzhi', () => {
it('子时(0点)返回有效干支', () => {
const dayGz = getGanzhi(2024, 1, 1);
const r = getHourGanzhi(dayGz, 0);
assert.ok(r.ganzhi.length === 2);
assert.equal(r.branch, '子');
});
it('午时(12点) branch 为午', () => {
const dayGz = getGanzhi(2024, 1, 1);
const r = getHourGanzhi(dayGz, 12);
assert.equal(r.branch, '午');
});
});
// ─── 生肖与星座 ──────────────────────────────────────────────
describe('getZodiac', () => {
it('2024 = 龙', () => {
assert.equal(getZodiac(2024), '龙');
});
it('2023 = 兔', () => {
assert.equal(getZodiac(2023), '兔');
});
it('1900 = 鼠', () => {
assert.equal(getZodiac(1900), '鼠');
});
it('12年循环', () => {
assert.equal(getZodiac(2024), getZodiac(2024 + 12));
});
});
describe('getConstellation', () => {
it('1月15日 = 摩羯座', () => {
assert.equal(getConstellation(1, 15), '摩羯座');
});
it('2月20日 = 双鱼座(边界日之后)', () => {
assert.equal(getConstellation(2, 20), '双鱼座');
});
it('3月21日 = 白羊座(春分边界)', () => {
assert.equal(getConstellation(3, 21), '白羊座');
});
it('12月22日 = 摩羯座', () => {
assert.equal(getConstellation(12, 22), '摩羯座');
});
});
// ─── 月相(通过 getDayInfo 间接验证) ───────────────────────
describe('getDayInfo 月相字段', () => {
it('正月初一附近返回朔月', () => {
// 2024-02-10 是农历正月初一
const { getDayInfo } = require('../index.js');
const r = getDayInfo(2024, 2, 10);
assert.equal(r.moonPhase, '朔月');
});
it('正月十五返回满月', () => {
// 2024-02-24 是农历正月十五
const { getDayInfo } = require('../index.js');
const r = getDayInfo(2024, 2, 24);
assert.equal(r.moonPhase, '满月');
});
});
// ─── 纳音 ─────────────────────────────────────────────────────
describe('getNayin', () => {
it('甲子 = 海中金', () => {
assert.equal(getNayin('甲子'), '海中金');
});
it('返回字符串', () => {
const r = getNayin('丙寅');
assert.equal(typeof r, 'string');
assert.ok(r.length > 0);
});
});
// ─── 六曜(通过 getDayInfo 间接验证) ───────────────────────
describe('getDayInfo 六曜字段', () => {
it('返回有效结构', () => {
const { getDayInfo } = require('../index.js');
const r = getDayInfo(2024, 1, 1);
assert.ok(r.lunarMonth > 0);
assert.ok(r.lunarDay > 0);
});
});
// ─── 胎神/宜忌/冲煞/彭祖(通过 getAlmanacInfo 间接验证) ────
describe('getAlmanacInfo 黄历字段', () => {
it('返回完整黄历结构', () => {
const { getAlmanacInfo } = require('../index.js');
const r = getAlmanacInfo(2024, 3, 18);
assert.ok(r.duty);
assert.ok(['good', 'bad', 'neutral'].includes(r.dutyLuck));
assert.ok(r.twelveStar);
assert.ok(typeof r.pengZu === 'string');
assert.ok(typeof r.clash === 'string');
assert.ok(typeof r.harm === 'string');
assert.ok(typeof r.combine === 'string');
assert.ok(['北', '南', '东', '西'].includes(r.evilDirection));
assert.ok(Array.isArray(r.recommends));
assert.ok(Array.isArray(r.avoids));
assert.ok(r.fetus);
});
});
// ─── 地支关系 ────────────────────────────────────────────────
describe('getBranchRelationship', () => {
it('子丑合', () => {
const r = getBranchRelationship('子', '丑');
assert.equal(r.combine, true);
});
it('子午冲', () => {
const r = getBranchRelationship('子', '午');
assert.equal(r.opposite, true);
});
it('相同地支为合', () => {
// 子丑合,所以子和丑是合关系
const r = getBranchRelationship('子', '丑');
assert.equal(r.combine, true);
});
it('申子辰三合水局', () => {
const r = getBranchRelationship('申', '子');
assert.equal(r.threeCombine, true);
assert.equal(r.formation, '水局');
});
});
// ─── 十神关系 ────────────────────────────────────────────────
describe('getTenStarRelationship', () => {
it('同干为比肩', () => {
assert.equal(getTenStarRelationship('甲', '甲'), '比肩');
});
it('甲见乙 = 劫财', () => {
assert.equal(getTenStarRelationship('甲', '乙'), '劫财');
});
it('甲见丙 = 食神', () => {
assert.equal(getTenStarRelationship('甲', '丙'), '食神');
});
});
// ─── 天干合冲 ────────────────────────────────────────────────
describe('checkStemCombine', () => {
it('甲己合', () => {
assert.equal(checkStemCombine('甲', '己'), true);
});
it('甲乙不合', () => {
assert.equal(checkStemCombine('甲', '乙'), false);
});
});
describe('checkStemOpposite', () => {
it('甲庚冲', () => {
assert.equal(checkStemOpposite('甲', '庚'), true);
});
it('甲乙不冲', () => {
assert.equal(checkStemOpposite('甲', '乙'), false);
});
});
// ─── 佛教节日 ────────────────────────────────────────────────
describe('getBuddhistFestival', () => {
it('腊月初八有佛教节日', () => {
// 腊八节是佛教节日
const r = getBuddhistFestival(12, 8);
// 可能有也可能没有,只验证返回类型
assert.ok(r === null || typeof r === 'string');
});
it('不存在的日期返回 null', () => {
assert.equal(getBuddhistFestival(6, 15), null);
});
});
// ─── 工作日判断 ──────────────────────────────────────────────
describe('isWorkday', () => {
it('普通周一为工作日', () => {
// 2024-03-18 是周一,非节假日
assert.equal(isWorkday(2024, 3, 18), true);
});
it('普通周六为非工作日', () => {
// 2024-03-16 是周六
assert.equal(isWorkday(2024, 3, 16), false);
});
it('普通周日为非工作日', () => {
// 2024-03-17 是周日
assert.equal(isWorkday(2024, 3, 17), false);
});
});
// ─── 五行分析 ────────────────────────────────────────────────
describe('analyzeElementBalance', () => {
it('四柱全木为不平衡', () => {
const bazi = {
yearPillar: { elementStem: '木', elementBranch: '木', hideStems: [{ stem: '木' }] },
monthPillar: { elementStem: '木', elementBranch: '木', hideStems: [{ stem: '木' }] },
dayPillar: { elementStem: '木', elementBranch: '木', hideStems: [{ stem: '木' }] },
hourPillar: { elementStem: '木', elementBranch: '木', hideStems: [{ stem: '木' }] }
};
const r = analyzeElementBalance(bazi);
assert.ok(r.wood > 0);
assert.equal(r.fire, 0);
assert.equal(r.isBalanced, false);
assert.equal(r.dominant, '木');
});
it('返回 wood/fire/earth/metal/water/total/dominant/weakest/isBalanced', () => {
const bazi = {
yearPillar: { elementStem: '木', elementBranch: '火', hideStems: [] },
monthPillar: { elementStem: '土', elementBranch: '金', hideStems: [] },
dayPillar: { elementStem: '水', elementBranch: '木', hideStems: [] },
hourPillar: { elementStem: '火', elementBranch: '土', hideStems: [] }
};
const r = analyzeElementBalance(bazi);
assert.equal(typeof r.total, 'number');
assert.ok(r.total > 0);
assert.ok(['木', '火', '土', '金', '水'].includes(r.dominant));
assert.ok(['木', '火', '土', '金', '水'].includes(r.weakest));
});
});
// ─── 称骨算命 ────────────────────────────────────────────────
describe('calculateBoneWeight', () => {
it('返回完整结构', () => {
const r = calculateBoneWeight(0, 1, 1, 0);
assert.ok(typeof r.yearWeight === 'number');
assert.ok(typeof r.monthWeight === 'number');
assert.ok(typeof r.dayWeight === 'number');
assert.ok(typeof r.hourWeight === 'number');
assert.ok(typeof r.totalWeight === 'number');
assert.equal(r.totalWeight, r.yearWeight + r.monthWeight + r.dayWeight + r.hourWeight);
assert.ok(typeof r.interpretation === 'string');
assert.ok(typeof r.fortune === 'string');
});
it('totalLiang 和 totalQian 与 totalWeight 一致', () => {
const r = calculateBoneWeight(5, 3, 10, 4);
assert.equal(r.totalLiang * 10 + r.totalQian, r.totalWeight);
});
});
// ─── 大运/流年分析 ───────────────────────────────────────────
describe('analyzeFortuneGanzhi', () => {
it('返回完整分析结构', () => {
const r = analyzeFortuneGanzhi('甲子', '甲', '子');
assert.equal(r.ganzhi, '甲子');
assert.ok(r.tenStar);
assert.ok(r.elementRelation);
assert.ok(['吉', '平', '凶'].includes(r.level));
assert.ok(typeof r.score === 'number');
});
it('同干同支 score 不为极端值', () => {
const r = analyzeFortuneGanzhi('甲子', '甲', '子');
assert.ok(r.score >= -10 && r.score <= 10);
});
});
// ─── 节气查询 ────────────────────────────────────────────────
describe('getSolarTerm', () => {
it('非节气日返回 null', () => {
// 随机选一个非节气日
const r = getSolarTerm(2024, 6, 15);
assert.equal(r, null);
});
it('返回值类型为字符串或 null', () => {
const r = getSolarTerm(2024, 3, 20); // 春分附近
assert.ok(r === null || typeof r === 'string');
});
});