feat: rename app to Qiyun and refine Tiangong rituals and UI

This commit is contained in:
zhouzhibo committed 2026-11-03 00:16:16 +08:00
1 parent 7871114c50
commit df84a4c130
32 files changed
+482 -130

No files matched your search

+44 -6
View File
@@ -5,15 +5,16 @@ const path = require('node:path')
const vm = require('node:vm')
const ts = require('typescript')
const root = path.resolve(__dirname, '..')
function page(file, { bound = false, demo = false, modal = '', detail = 'https://example.invalid/detail.png', description = '后端返回的介绍\n保留换行' } = {}) {
function page(file, { bound = false, demo = false, modal = '', detail = 'https://example.invalid/detail.png', description = '后端返回的介绍\n保留换行', latestPrayer = null, latestDedication = null } = {}) {
const deity = { id: 'wealth', name: '财神', ritualName: '武财神', icon: 'local-list.png', statue: 'local-statue.png', introduction: '本地原型介绍', missingStatue: true }
const state = { available: true, unlocked: bound, iconUrl: 'https://example.invalid/list.png', iconDetailUrl: detail, description, days: 4, incenseCount: 12 }
const state = { available: true, unlocked: bound, iconUrl: 'https://example.invalid/list.png', iconDetailUrl: detail, description, days: 4, incenseCount: 12, latestPrayer, latestDedication }
const states = []; let index = 0
const exports = {}
vm.runInNewContext(ts.transpileModule(fs.readFileSync(path.join(root, file), 'utf8'), { compilerOptions: {
module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019, jsx: ts.JsxEmit.ReactJSX
} }).outputText, { exports, require: id => {
if (id === 'react/jsx-runtime') return require(id)
if (id.endsWith('.jpg')) return { default: id }
if (id === 'react') return { useRef: value => ({ current: value }), useState: initial => {
const current = index++
if (!(current in states)) states[current] = current === 1 && file.includes('tiangong-detail') ? modal : initial
@@ -39,6 +40,42 @@ function nodes(tree) {
}
const text = tree => nodes(tree).filter(node => node.type === 'Text').map(node => node.props.children).flat().join(' ')
const image = tree => nodes(tree).filter(node => node.type === 'Image')
const statueImages = tree => image(tree).filter(node => /tg-statue|tg-panel-statue/.test(node.props.className))
test('打开祈愿回填最新内容,编辑中不被查询快照覆盖,再次打开恢复已保存内容', () => {
const render = page('src/pages/tiangong-detail/index.tsx', { bound: true, latestPrayer: { prayerContent: '已保存的愿望' } })
const openWish = () => nodes(render()).find(node => node.props['aria-label'] === '祈愿').props.onClick()
const input = () => nodes(render()).find(node => node.type === 'Textarea')
openWish()
assert.equal(input().props.value, '已保存的愿望')
input().props.onInput({ detail: { value: '正在编辑的新愿望' } })
assert.equal(input().props.value, '正在编辑的新愿望')
nodes(render()).find(node => node.type === 'Button' && node.props.children === '取消').props.onClick()
openWish()
assert.equal(input().props.value, '已保存的愿望')
})
test('打开回向回填对象及可选祝愿,没有可用记录时均为空', () => {
for (const latestDedication of [null, { dedicationTarget: '家人', dedicationContent: '平安顺遂' }, { dedicationTarget: '一切众生', dedicationContent: null }]) {
const render = page('src/pages/tiangong-detail/index.tsx', { bound: true, latestDedication })
nodes(render()).find(node => node.props['aria-label'] === '回向').props.onClick()
assert.equal(nodes(render()).find(node => node.type === 'Input').props.value, latestDedication?.dedicationTarget || '')
assert.equal(nodes(render()).find(node => node.type === 'Textarea').props.value, latestDedication?.dedicationContent || '')
}
const render = page('src/pages/tiangong-detail/index.tsx', { bound: true })
nodes(render()).find(node => node.props['aria-label'] === '祈愿').props.onClick()
assert.equal(nodes(render()).find(node => node.type === 'Textarea').props.value, '')
})
test('请神和敬神均用原生图片加载本地山水,背景位于神像下方', () => {
for (const bound of [true, false]) {
const tree = page('src/pages/tiangong-detail/index.tsx', { bound })()
const scene = nodes(tree).find(node => node.props.className === 'tg-statue-space')
assert.equal(scene.props.style?.backgroundImage, undefined)
const background = scene.props.children[0]
assert.equal(background.type, 'Image')
assert.equal(background.props.className, 'tg-scene-background')
assert.match(background.props.src, /proto-clouds\.jpg$/)
assert.equal(background.props.mode, bound ? 'aspectFill' : 'scaleToFill')
}
})
test('详情及弹窗均使用详情图;仅请神页展示description,敬神页不展示介绍', () => {
for (const bound of [true, false]) {
const tree = page('src/pages/tiangong-detail/index.tsx', { bound, modal: 'wish' })()
@@ -66,12 +103,13 @@ test('敬神页在正式和Demo模式均不显示介绍,缺少介绍也不显
test('真实字段为空或加载失败时提示,不以本地神像、列表缩略图或原型介绍顶替', () => {
const empty = page('src/pages/tiangong-detail/index.tsx', { detail: '', description: '' })()
assert.match(text(empty), /详情图片暂未配置/); assert.match(text(empty), /暂无介绍/)
assert.equal(image(empty).length, 0)
assert.equal(statueImages(empty).length, 0)
const render = page('src/pages/tiangong-detail/index.tsx')
image(render())[0].props.onError()
statueImages(render())[0].props.onError()
const failed = render()
assert.match(text(failed), /详情图片加载失败/)
assert.equal(image(failed).length, 0)
assert.equal(statueImages(failed).length, 0)
assert.ok(image(failed).some(node => node.props.className === 'tg-scene-background'))
})
test('列表保留iconUrl与20%透明度规则,详情字段不误用到列表;显式Demo保留原型', () => {
for (const bound of [true, false]) {
@@ -82,6 +120,6 @@ test('列表保留iconUrl与20%透明度规则,详情字段不误用到列表
}
assert.match(fs.readFileSync(path.join(root, 'src/pages/fortune/index.scss'), 'utf8'), /\.deity-card\.locked \.deity-image \{ opacity: \.2; \}/)
const demo = page('src/pages/tiangong-detail/index.tsx', { demo: true })()
assert.equal(image(demo)[0].props.src, 'local-statue.png')
assert.equal(statueImages(demo)[0].props.src, 'local-statue.png')
assert.match(text(demo), /本地原型介绍/)
})