2026-10-17 22:44:09 +08:00
|
|
|
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 root = path.resolve(__dirname, '..')
|
|
|
|
|
const filename = path.join(root, 'src/utils/tiangongMock.ts')
|
|
|
|
|
const loaded = new Module(filename, module)
|
2026-10-18 23:35:28 +08:00
|
|
|
const ritualFilename = path.join(root, 'src/utils/incenseRitual.ts')
|
|
|
|
|
const ritual = new Module(ritualFilename, module)
|
|
|
|
|
ritual._compile(ts.transpileModule(fs.readFileSync(ritualFilename, 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019 } }).outputText, ritualFilename)
|
|
|
|
|
loaded.require = id => id === './incenseRitual' ? ritual.exports : Module.prototype.require.call(loaded, id)
|
2026-10-17 22:44:09 +08:00
|
|
|
loaded._compile(ts.transpileModule(fs.readFileSync(filename, 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019 } }).outputText, filename)
|
|
|
|
|
const { createTiangongMock, DEITY_IDS, isDeityId, hasBoundDeity } = loaded.exports
|
|
|
|
|
|
|
|
|
|
test('天宫仅财神初始已恭请,五位神仙独立且状态快照隔离', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
const first = mock.snapshot()
|
|
|
|
|
assert.equal(DEITY_IDS.length, 5)
|
|
|
|
|
assert.deepEqual(DEITY_IDS.filter(id => first.deities[id].unlocked), ['wealth'])
|
2026-10-18 23:35:28 +08:00
|
|
|
assert.equal('balance' in first, false)
|
2026-10-17 22:44:09 +08:00
|
|
|
first.deities.wealth.incenseCount = 999
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.incenseCount, 59)
|
|
|
|
|
assert.equal(isDeityId('not-a-god'), false)
|
|
|
|
|
})
|
|
|
|
|
test('上香点亮供桌次数+1,不猜测爱心扣减与功德奖励', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
const state = mock.incense('wealth')
|
|
|
|
|
assert.equal(state.deities.wealth.incenseCount, 60)
|
|
|
|
|
assert.equal(state.deities.wealth.lit, true)
|
2026-10-18 23:35:28 +08:00
|
|
|
assert.equal('merit' in state.deities.wealth, false)
|
2026-10-17 22:44:09 +08:00
|
|
|
assert.throws(() => mock.incense('fortune'), /未恭请/)
|
|
|
|
|
})
|
|
|
|
|
test('模拟恭请和送神只改变指定神仙,返回列表状态可同步', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
mock.invite('fortune')
|
|
|
|
|
mock.incense('fortune')
|
|
|
|
|
assert.equal(mock.snapshot().deities.fortune.incenseCount, 1)
|
|
|
|
|
mock.sendOff('fortune')
|
|
|
|
|
assert.equal(mock.snapshot().deities.fortune.unlocked, false)
|
|
|
|
|
assert.equal(mock.snapshot().deities.fortune.lit, false)
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.unlocked, true)
|
|
|
|
|
})
|
2026-10-18 23:35:28 +08:00
|
|
|
test('本期无功德、余额、捐款接口或入口', () => {
|
2026-10-17 22:44:09 +08:00
|
|
|
const mock = createTiangongMock()
|
2026-10-18 23:35:28 +08:00
|
|
|
assert.equal(mock.offering, undefined)
|
|
|
|
|
assert.equal(mock.setDemoBalance, undefined)
|
|
|
|
|
for (const page of ['fortune', 'tiangong-detail']) {
|
|
|
|
|
assert.doesNotMatch(fs.readFileSync(path.join(root, `src/pages/${page}/index.tsx`), 'utf8'), /功德|爱心|充值|balance|merit/)
|
|
|
|
|
}
|
2026-10-17 22:44:09 +08:00
|
|
|
})
|
|
|
|
|
test('祈愿/回向校验空值,不持久化姓名或祝愿;重置还原样例', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
assert.throws(() => mock.wish('wealth', ' '), /愿望/)
|
2026-11-03 00:16:16 +08:00
|
|
|
assert.throws(() => mock.dedicate('wealth', '', ''), /填写/)
|
2026-10-17 22:44:09 +08:00
|
|
|
assert.deepEqual(mock.wish('wealth', '测试愿望'), { mock: true })
|
|
|
|
|
assert.deepEqual(mock.dedicate('wealth', '测试姓名', '测试祝愿'), { mock: true })
|
|
|
|
|
assert.ok(!JSON.stringify(mock.snapshot()).includes('测试'))
|
|
|
|
|
mock.invite('fortune')
|
|
|
|
|
mock.reset()
|
|
|
|
|
assert.equal(mock.snapshot().deities.fortune.unlocked, false)
|
|
|
|
|
})
|
2026-10-18 23:35:28 +08:00
|
|
|
test('未知神仙不回退为财神,页面通过服务层调用,未接入契约不被调用', () => {
|
2026-10-17 22:44:09 +08:00
|
|
|
const service = fs.readFileSync(path.join(root, 'src/services/tiangong.ts'), 'utf8')
|
|
|
|
|
assert.ok(!service.includes("id || 'wealth'"))
|
|
|
|
|
for (const page of ['fortune', 'tiangong-detail']) {
|
|
|
|
|
const source = fs.readFileSync(path.join(root, `src/pages/${page}/index.tsx`), 'utf8')
|
|
|
|
|
assert.ok(!/api\.|tiangongRemoteContract\./.test(source))
|
|
|
|
|
}
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
assert.throws(() => mock.invite('bad'), /未找到/)
|
|
|
|
|
})
|
|
|
|
|
test('图像均为本地原型素材,没有错误后缀或带写死数字的整页截图', () => {
|
|
|
|
|
for (const name of ['wealth', 'fortune', 'longevity', 'wenchang', 'matchmaker', 'clouds', 'altar', 'altar-lit']) {
|
|
|
|
|
const buffer = fs.readFileSync(path.join(root, `src/assets/tiangong/proto-${name}.jpg`))
|
|
|
|
|
assert.equal(buffer.subarray(0, 2).toString('hex'), 'ffd8')
|
|
|
|
|
}
|
|
|
|
|
for (const name of ['wealth', 'fortune']) {
|
|
|
|
|
const buffer = fs.readFileSync(path.join(root, `src/assets/tiangong/proto-${name}-statue.png`))
|
|
|
|
|
assert.equal(buffer.subarray(1, 4).toString(), 'PNG')
|
|
|
|
|
}
|
|
|
|
|
const source = fs.readFileSync(path.join(root, 'src/pages/tiangong-detail/index.tsx'), 'utf8')
|
|
|
|
|
assert.ok(!source.includes('wealth-detail-scene.jpg'))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('最后一位解绑锁定,任意一位绑定重新解锁;解绑他位不影响已有绑定', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), true)
|
|
|
|
|
mock.unbind('wealth')
|
|
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), false)
|
|
|
|
|
mock.invite('fortune')
|
|
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), true)
|
|
|
|
|
mock.invite('wealth')
|
|
|
|
|
mock.unbind('fortune')
|
|
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), true)
|
|
|
|
|
})
|
2026-11-03 00:16:16 +08:00
|
|
|
test('送神保留绑定和解绑额度,重复请神不重置累计上香次数', () => {
|
2026-10-17 22:44:09 +08:00
|
|
|
const mock = createTiangongMock()
|
2026-11-03 00:16:16 +08:00
|
|
|
mock.incense('wealth'); mock.sendOff('wealth')
|
2026-10-17 22:44:09 +08:00
|
|
|
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 2)
|
2026-11-03 00:16:16 +08:00
|
|
|
assert.equal(mock.snapshot().deities.wealth.unlocked, false)
|
|
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), true)
|
|
|
|
|
assert.throws(() => mock.incense('wealth'), /未恭请/)
|
|
|
|
|
mock.invite('wealth')
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.incenseCount, 60)
|
2026-10-17 22:44:09 +08:00
|
|
|
mock.unbind('wealth')
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 1)
|
2026-11-03 00:16:16 +08:00
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), false)
|
2026-10-17 22:44:09 +08:00
|
|
|
})
|
|
|
|
|
test('绑定、解绑、重置同步通知缓存Tab;退订后不再通知', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
const states = []
|
|
|
|
|
const unsubscribe = mock.subscribe(() => states.push(hasBoundDeity(mock.snapshot())))
|
|
|
|
|
mock.unbind('wealth'); mock.invite('fortune'); mock.reset()
|
|
|
|
|
assert.deepEqual(states, [false, true, true])
|
|
|
|
|
unsubscribe(); mock.unbind('wealth')
|
|
|
|
|
assert.equal(states.length, 3)
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 1)
|
|
|
|
|
})
|
|
|
|
|
test('绑定门禁和卡片采用神仙用语,真实NFC与解绑契约不被页面调用', () => {
|
|
|
|
|
for (const name of ['home', 'my']) {
|
|
|
|
|
const source = fs.readFileSync(path.join(root, `src/pages/${name}/index.tsx`), 'utf8')
|
|
|
|
|
assert.match(source, /hasBoundDeity/)
|
|
|
|
|
assert.match(source, /BindingGate/)
|
|
|
|
|
assert.doesNotMatch(source, /吉物|山鬼花钱/)
|
|
|
|
|
}
|
|
|
|
|
const ui = fs.readFileSync(path.join(root, 'src/components/DeityBinding.tsx'), 'utf8')
|
|
|
|
|
assert.match(ui, /disabled=\{state.remainingUnbinds === 0\}/)
|
|
|
|
|
assert.doesNotMatch(ui, /getNFCAdapter|tiangongRemoteContract\./)
|
|
|
|
|
})
|
2026-11-03 00:16:16 +08:00
|
|
|
|
|
|
|
|
test('已送神仍可解绑设备,解绑清除再次请神资格并正常扣减额度', () => {
|
|
|
|
|
const mock = createTiangongMock()
|
|
|
|
|
mock.sendOff('wealth'); assert.equal(hasBoundDeity(mock.snapshot()), true)
|
|
|
|
|
mock.unbind('wealth')
|
|
|
|
|
assert.equal(hasBoundDeity(mock.snapshot()), false)
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.canInvite, false)
|
|
|
|
|
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 1)
|
|
|
|
|
})
|