Files
hera-web/scripts/tiangong.test.cjs
T

137 lines
7.0 KiB
JavaScript

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)
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)
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'])
assert.equal('balance' in first, false)
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)
assert.equal('merit' in state.deities.wealth, false)
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)
})
test('本期无功德、余额、捐款接口或入口', () => {
const mock = createTiangongMock()
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/)
}
})
test('祈愿/回向校验空值,不持久化姓名或祝愿;重置还原样例', () => {
const mock = createTiangongMock()
assert.throws(() => mock.wish('wealth', ' '), /愿望/)
assert.throws(() => mock.dedicate('wealth', '甲', ''), /填写/)
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)
})
test('未知神仙不回退为财神,页面通过服务层调用,未接入契约不被调用', () => {
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)
})
test('每位初始2次,重复恭请/取消不扣减或重置,耗尽后送神也不能绕过', () => {
const mock = createTiangongMock()
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 2)
// Viewing a confirmation/cancelling is read-only.
mock.snapshot(); mock.snapshot()
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 2)
mock.unbind('wealth')
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 1)
assert.throws(() => mock.unbind('wealth'), /未恭请/)
mock.invite('wealth'); mock.invite('wealth')
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 1)
mock.sendOff('wealth')
assert.equal(mock.snapshot().deities.wealth.remainingUnbinds, 0)
mock.invite('wealth')
for (const action of ['unbind', 'sendOff']) assert.throws(() => mock[action]('wealth'), /次数已用尽/)
assert.equal(mock.snapshot().deities.wealth.unlocked, true)
assert.equal(mock.snapshot().deities.fortune.remainingUnbinds, 2)
})
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\./)
})