feat(framework): RoomStore + RoomState.players 类型精确化

RoomState.players: unknown[] → Array<PlayerState | null>
- 座位未占用显式为 null(不是 undefined、不是空对象)
- 长度由 maxPlayerCount 决定,RoomStore.applyRecovery 校验填充

deskinfo 字段注释强调"C 规范 §5 边界判据 2:平台层不持有、不解析",
新测试覆盖引用相等透传(不深拷贝)。

新增 RoomStore: applyRecovery/clear,响应式 + 类型化入参 RoomRecoveryData。

97/97 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 18:17:29 +08:00
co-authored by Claude Opus 5
parent 7404719c24
commit b54de5bbda
3 changed files with 151 additions and 2 deletions
@@ -0,0 +1,75 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { RoomStore } from '../../YouleNexus/assets/framework/platform/stores/room-store.ts';
test('初始: inRoom=false, seat=-1, players=[]', () => {
const v = new RoomStore().state.value;
assert.equal(v.inRoom, false);
assert.equal(v.seat, -1);
assert.deepEqual(v.players, []);
});
test('applyRecovery: 填充 B 组并置 inRoom=true', () => {
const store = new RoomStore();
store.applyRecovery({
roomcode: 'ROOM1', isbattle: 1, roommode: 1,
seat: 2, isowner: 1,
players: [
null,
{ playerid: 100, nickname: '甲', bean: 0, roomcard: 0 } as any,
null,
],
});
const v = store.state.value;
assert.equal(v.inRoom, true);
assert.equal(v.roomcode, 'ROOM1');
assert.equal(v.isbattle, 1);
assert.equal(v.seat, 2);
assert.equal(v.players.length, 3);
assert.equal(v.players[0], null);
assert.equal(v.players[1]?.playerid, 100);
assert.equal(v.players[2], null);
});
test('applyRecovery: 未传 players 字段 → 空数组(非 null,非 undefined)', () => {
const store = new RoomStore();
store.applyRecovery({ roomcode: 'R' } as any);
assert.deepEqual(store.state.value.players, []);
});
test('applyRecovery: deskinfo 透传原值不解析(第二准则:平台层不解析对局态)', () => {
const store = new RoomStore();
const deskinfo = { round: 3, cards: ['A', 'B'] };
store.applyRecovery({ roomcode: 'R', deskinfo } as any);
assert.equal(store.state.value.deskinfo, deskinfo, 'deskinfo 必须是引用相等(不深拷贝、不解析)');
});
test('applyRecovery: deskinfo 缺省 → null', () => {
const store = new RoomStore();
store.applyRecovery({ roomcode: 'R' } as any);
assert.equal(store.state.value.deskinfo, null);
});
test('clear: 复位 inRoom 与字段, players 也清空', () => {
const store = new RoomStore();
store.applyRecovery({
roomcode: 'R', seat: 1,
players: [{ playerid: 1 } as any, null],
});
store.clear();
const v = store.state.value;
assert.equal(v.inRoom, false);
assert.equal(v.roomcode, null);
assert.equal(v.seat, -1);
assert.deepEqual(v.players, []);
});
test('subscribe: inRoom 由 false → true 触发一次', () => {
const store = new RoomStore();
let n = 0;
store.state.subscribe(() => { n++;
});
store.applyRecovery({ roomcode: 'R' } as any);
store.clear();
assert.equal(n, 2);
});