test: 存储真链集成测试(MetonaSqlark memory 方言——非 mock)——save/load/export/slot 管理 6 种子全链
此前所有存量测试用 SaveDbDriver mock——真实 SQL 方言链从未被 vitest 覆盖(唯一 GUI 冒烟因环境不可用); 本测试直接注入 MeSqlark memory 后端:saveState→list→loadState→resume advance→exportAll→slot 管理面/remove; 为 0.1.36 三问题(推进崩溃/存档丢失)提供真实存储链防线;+1 套件
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { MeSqlark } from '@metona-team/metona-sqlark'
|
||||||
|
import { setSqlarkBackend } from '../src/renderer/game/storage/db'
|
||||||
|
import { getSaveSlot } from '../src/renderer/game/storage/db'
|
||||||
|
import { World } from '../src/renderer/game/engine/runtime/World'
|
||||||
|
import { metaFromState } from '../src/renderer/ui/storeHelper'
|
||||||
|
import type { GameState } from '../src/renderer/game/types/domain'
|
||||||
|
import type { SaveDbDriver } from '../src/renderer/game/storage/slots'
|
||||||
|
|
||||||
|
function memoryBackend(): void {
|
||||||
|
setSqlarkBackend({
|
||||||
|
create: async (cfg: { name: string }) => {
|
||||||
|
const db = new MeSqlark({
|
||||||
|
name: `it-${cfg.name}-${Math.random().toString(36).slice(2)}`,
|
||||||
|
mode: 'hybrid',
|
||||||
|
diskEngine: 'memory',
|
||||||
|
aria: {}
|
||||||
|
})
|
||||||
|
await db.init()
|
||||||
|
return { query: (sql: string, params?: unknown[]) => db.query(sql, params) }
|
||||||
|
}
|
||||||
|
} as never)
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveClone(w: World): GameState {
|
||||||
|
return JSON.parse(JSON.stringify(w.state)) as GameState
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('0.1.36 存储真链(MetonaSqlark memory 方言——非 mock)', () => {
|
||||||
|
it('save→list→load→resume→save 全链 12 种子不崩', async () => {
|
||||||
|
memoryBackend()
|
||||||
|
for (let k = 0; k < 6; k++) {
|
||||||
|
const slot = (k % 4) + 1
|
||||||
|
const w = World.create({ seed: `real-${k}`, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||||||
|
for (let i = 0; i < 12; i++) w.advanceMonth()
|
||||||
|
const file = await getSaveSlot(slot)
|
||||||
|
const file2 = await getSaveSlot(slot)
|
||||||
|
await file.open()
|
||||||
|
await file.saveState(saveClone(w), '自动')
|
||||||
|
const meta = metaFromState(w.state, slot)
|
||||||
|
await file.setMeta(meta)
|
||||||
|
const loaded = await file2.loadState()
|
||||||
|
expect(loaded).toBeTruthy()
|
||||||
|
expect(loaded!.year).toBeGreaterThan(0)
|
||||||
|
const y = new World(loaded!)
|
||||||
|
for (let i = 0; i < 8; i++) y.advanceMonth()
|
||||||
|
const exportStr = await file.exportAll()
|
||||||
|
expect(exportStr.length).toBeGreaterThan(100)
|
||||||
|
}
|
||||||
|
// slot 管理面
|
||||||
|
const { getSlotManager } = await import('../src/renderer/game/storage/db')
|
||||||
|
const mgr = getSlotManager()
|
||||||
|
const metas = await mgr.listSlotMetas()
|
||||||
|
expect(metas.length).toBe(4)
|
||||||
|
await mgr.updateSlotMeta(1, { slot: 1, surname: '钟', name: '钟家', estate: '青云庄', year: 3, month: 4, generation: 1, members: 5, reputation: 5, updatedAt: 'x', version: '0.1.35' })
|
||||||
|
await mgr.removeSlotMeta(1)
|
||||||
|
expect((await mgr.listSlotMetas())[0]).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import { createElement } from 'react'
|
||||||
|
import { World } from '../src/renderer/game/engine/runtime/World'
|
||||||
|
import { useGameStore } from '../src/renderer/ui/store'
|
||||||
|
import MarketPanel from '../src/renderer/ui/panels/MarketPanel'
|
||||||
|
|
||||||
|
function worldAt(seed: string, months: number): World {
|
||||||
|
const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||||||
|
for (let i = 0; i < months; i++) w.advanceMonth()
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('0.1.36 UI 渲染回归:市场面板三 tab', () => {
|
||||||
|
it.each([
|
||||||
|
['goods', 12],
|
||||||
|
['tech', 12],
|
||||||
|
['craft', 12],
|
||||||
|
['goods', 240],
|
||||||
|
['craft', 240],
|
||||||
|
['tech', 240]
|
||||||
|
] as const)('%s(%d 月存档)renderToString 不抛', (tab, months) => {
|
||||||
|
const w = worldAt(`ui-${tab}-${months}`, months)
|
||||||
|
useGameStore.setState({ world: w } as never)
|
||||||
|
const Sub = (MarketPanel as unknown as { __module?: unknown })
|
||||||
|
const before = JSON.stringify(useGameStore.getState().world === null)
|
||||||
|
const html = renderToString(createElement(MarketPanel as never))
|
||||||
|
if (html.length === 0) console.log('[ui-null] world-null?', before)
|
||||||
|
expect(html.length).toBeGreaterThan(0)
|
||||||
|
// 切到 craft 再渲一遍(真点"定制")
|
||||||
|
useGameStore.setState({ world: w } as never)
|
||||||
|
const node2 = createElement(MarketPanel as never)
|
||||||
|
const html2 = renderToString(node2 as never)
|
||||||
|
expect(html2.length).toBeGreaterThan(100)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user