mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 08:14:47 -07:00
refactor typescript wiring
This commit is contained in:
parent
cea9ae62d8
commit
c62c336a11
286 changed files with 2999 additions and 3053 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import { create } from '@bufbuild/protobuf';
|
||||
import { SortDirection } from 'types';
|
||||
import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb';
|
||||
import { App, Data } from '@app/types';
|
||||
import SortUtil from './SortUtil';
|
||||
|
||||
// ── sortByField ───────────────────────────────────────────────────────────────
|
||||
|
|
@ -8,48 +7,48 @@ import SortUtil from './SortUtil';
|
|||
describe('sortByField', () => {
|
||||
it('sorts string field ASC alphabetically', () => {
|
||||
const arr = [{ name: 'Zane' }, { name: 'Alice' }, { name: 'Bob' }];
|
||||
SortUtil.sortByField(arr, { field: 'name', order: SortDirection.ASC });
|
||||
SortUtil.sortByField(arr, { field: 'name', order: App.SortDirection.ASC });
|
||||
expect(arr.map(x => x.name)).toEqual(['Alice', 'Bob', 'Zane']);
|
||||
});
|
||||
|
||||
it('sorts string field DESC reverse-alphabetically', () => {
|
||||
const arr = [{ name: 'Alice' }, { name: 'Zane' }, { name: 'Bob' }];
|
||||
SortUtil.sortByField(arr, { field: 'name', order: SortDirection.DESC });
|
||||
SortUtil.sortByField(arr, { field: 'name', order: App.SortDirection.DESC });
|
||||
expect(arr.map(x => x.name)).toEqual(['Zane', 'Bob', 'Alice']);
|
||||
});
|
||||
|
||||
it('sorts number field ASC', () => {
|
||||
const arr = [{ score: 30 }, { score: 10 }, { score: 20 }];
|
||||
SortUtil.sortByField(arr, { field: 'score', order: SortDirection.ASC });
|
||||
SortUtil.sortByField(arr, { field: 'score', order: App.SortDirection.ASC });
|
||||
expect(arr.map(x => x.score)).toEqual([10, 20, 30]);
|
||||
});
|
||||
|
||||
it('sorts number field DESC', () => {
|
||||
const arr = [{ score: 10 }, { score: 30 }, { score: 20 }];
|
||||
SortUtil.sortByField(arr, { field: 'score', order: SortDirection.DESC });
|
||||
SortUtil.sortByField(arr, { field: 'score', order: App.SortDirection.DESC });
|
||||
expect(arr.map(x => x.score)).toEqual([30, 20, 10]);
|
||||
});
|
||||
|
||||
it('no-ops on empty array without error', () => {
|
||||
expect(() => SortUtil.sortByField([], { field: 'name', order: SortDirection.ASC })).not.toThrow();
|
||||
expect(() => SortUtil.sortByField([], { field: 'name', order: App.SortDirection.ASC })).not.toThrow();
|
||||
});
|
||||
|
||||
it('sorts with nested dot-notation field', () => {
|
||||
const arr = [{ meta: { rank: 3 } }, { meta: { rank: 1 } }, { meta: { rank: 2 } }];
|
||||
SortUtil.sortByField(arr, { field: 'meta.rank', order: SortDirection.ASC });
|
||||
SortUtil.sortByField(arr, { field: 'meta.rank', order: App.SortDirection.ASC });
|
||||
expect(arr.map(x => x.meta.rank)).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('throws when field resolves to a non-string, non-number value', () => {
|
||||
const arr = [{ data: {} }, { data: {} }];
|
||||
expect(() => SortUtil.sortByField(arr, { field: 'data', order: SortDirection.ASC })).toThrow(
|
||||
expect(() => SortUtil.sortByField(arr, { field: 'data', order: App.SortDirection.ASC })).toThrow(
|
||||
'SortField must resolve to either a string or number'
|
||||
);
|
||||
});
|
||||
|
||||
it('sorts empty-string values to the bottom when sorting ASC', () => {
|
||||
const arr = [{ name: '' }, { name: 'Alice' }, { name: '' }];
|
||||
SortUtil.sortByField(arr, { field: 'name', order: SortDirection.ASC });
|
||||
SortUtil.sortByField(arr, { field: 'name', order: App.SortDirection.ASC });
|
||||
expect(arr[0].name).toBe('Alice');
|
||||
expect(arr[1].name).toBe('');
|
||||
expect(arr[2].name).toBe('');
|
||||
|
|
@ -66,8 +65,8 @@ describe('sortByFields', () => {
|
|||
{ group: 'B', name: 'Alice' },
|
||||
];
|
||||
SortUtil.sortByFields(arr, [
|
||||
{ field: 'group', order: SortDirection.ASC },
|
||||
{ field: 'name', order: SortDirection.ASC },
|
||||
{ field: 'group', order: App.SortDirection.ASC },
|
||||
{ field: 'name', order: App.SortDirection.ASC },
|
||||
]);
|
||||
expect(arr.map(x => x.group)).toEqual(['A', 'B', 'C']);
|
||||
});
|
||||
|
|
@ -79,8 +78,8 @@ describe('sortByFields', () => {
|
|||
{ group: 'B', name: 'Bob' },
|
||||
];
|
||||
SortUtil.sortByFields(arr, [
|
||||
{ field: 'group', order: SortDirection.ASC },
|
||||
{ field: 'name', order: SortDirection.ASC },
|
||||
{ field: 'group', order: App.SortDirection.ASC },
|
||||
{ field: 'name', order: App.SortDirection.ASC },
|
||||
]);
|
||||
expect(arr[0]).toEqual({ group: 'A', name: 'Alice' });
|
||||
expect(arr[1]).toEqual({ group: 'A', name: 'Zane' });
|
||||
|
|
@ -89,20 +88,20 @@ describe('sortByFields', () => {
|
|||
|
||||
it('no-ops on empty array', () => {
|
||||
expect(() =>
|
||||
SortUtil.sortByFields([], [{ field: 'name', order: SortDirection.ASC }])
|
||||
SortUtil.sortByFields([], [{ field: 'name', order: App.SortDirection.ASC }])
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('sorts by number field', () => {
|
||||
const arr = [{ score: 3 }, { score: 1 }, { score: 2 }];
|
||||
SortUtil.sortByFields(arr, [{ field: 'score', order: SortDirection.ASC }]);
|
||||
SortUtil.sortByFields(arr, [{ field: 'score', order: App.SortDirection.ASC }]);
|
||||
expect(arr.map(x => x.score)).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('returns 0 when all items tie on every sort key', () => {
|
||||
const arr = [{ score: 5 }, { score: 5 }];
|
||||
expect(() =>
|
||||
SortUtil.sortByFields(arr, [{ field: 'score', order: SortDirection.ASC }])
|
||||
SortUtil.sortByFields(arr, [{ field: 'score', order: App.SortDirection.ASC }])
|
||||
).not.toThrow();
|
||||
expect(arr).toHaveLength(2);
|
||||
});
|
||||
|
|
@ -110,7 +109,7 @@ describe('sortByFields', () => {
|
|||
it('throws when field resolves to a non-string, non-number value', () => {
|
||||
const arr = [{ data: {} }, { data: {} }];
|
||||
expect(() =>
|
||||
SortUtil.sortByFields(arr, [{ field: 'data', order: SortDirection.ASC }])
|
||||
SortUtil.sortByFields(arr, [{ field: 'data', order: App.SortDirection.ASC }])
|
||||
).toThrow('SortField must resolve to either a string or number');
|
||||
});
|
||||
});
|
||||
|
|
@ -120,11 +119,11 @@ describe('sortByFields', () => {
|
|||
describe('sortUsersByField', () => {
|
||||
it('sorts by userLevel DESC first, then name ASC', () => {
|
||||
const users = [
|
||||
create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
create(ServerInfo_UserSchema, { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }),
|
||||
create(ServerInfo_UserSchema, { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
create(Data.ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
create(Data.ServerInfo_UserSchema, { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }),
|
||||
create(Data.ServerInfo_UserSchema, { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
];
|
||||
SortUtil.sortUsersByField(users, { field: 'name', order: SortDirection.ASC });
|
||||
SortUtil.sortUsersByField(users, { field: 'name', order: App.SortDirection.ASC });
|
||||
expect(users[0].name).toBe('Bob');
|
||||
expect(users[1].name).toBe('Alice');
|
||||
expect(users[2].name).toBe('Carol');
|
||||
|
|
@ -132,17 +131,17 @@ describe('sortUsersByField', () => {
|
|||
|
||||
it('no-ops on empty array', () => {
|
||||
expect(() =>
|
||||
SortUtil.sortUsersByField([], { field: 'name', order: SortDirection.ASC })
|
||||
SortUtil.sortUsersByField([], { field: 'name', order: App.SortDirection.ASC })
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('returns 0 (stable) when two users tie on both userLevel and name', () => {
|
||||
const users = [
|
||||
create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
create(Data.ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
create(Data.ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }),
|
||||
];
|
||||
expect(() =>
|
||||
SortUtil.sortUsersByField(users, { field: 'name', order: SortDirection.ASC })
|
||||
SortUtil.sortUsersByField(users, { field: 'name', order: App.SortDirection.ASC })
|
||||
).not.toThrow();
|
||||
expect(users).toHaveLength(2);
|
||||
});
|
||||
|
|
@ -152,18 +151,18 @@ describe('sortUsersByField', () => {
|
|||
|
||||
describe('toggleSortBy', () => {
|
||||
it('same field + ASC → returns DESC', () => {
|
||||
const result = SortUtil.toggleSortBy('name', { field: 'name', order: SortDirection.ASC });
|
||||
expect(result).toEqual({ field: 'name', order: SortDirection.DESC });
|
||||
const result = SortUtil.toggleSortBy('name', { field: 'name', order: App.SortDirection.ASC });
|
||||
expect(result).toEqual({ field: 'name', order: App.SortDirection.DESC });
|
||||
});
|
||||
|
||||
it('same field + DESC → returns ASC', () => {
|
||||
const result = SortUtil.toggleSortBy('name', { field: 'name', order: SortDirection.DESC });
|
||||
expect(result).toEqual({ field: 'name', order: SortDirection.ASC });
|
||||
const result = SortUtil.toggleSortBy('name', { field: 'name', order: App.SortDirection.DESC });
|
||||
expect(result).toEqual({ field: 'name', order: App.SortDirection.ASC });
|
||||
});
|
||||
|
||||
it('different field → returns ASC regardless of current order', () => {
|
||||
const result = SortUtil.toggleSortBy('score', { field: 'name', order: SortDirection.DESC });
|
||||
expect(result).toEqual({ field: 'score', order: SortDirection.ASC });
|
||||
const result = SortUtil.toggleSortBy('score', { field: 'name', order: App.SortDirection.DESC });
|
||||
expect(result).toEqual({ field: 'score', order: App.SortDirection.ASC });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -173,7 +172,7 @@ describe('resolveFieldChain via sortByField (numeric index)', () => {
|
|||
it('resolves numeric index in dot-notation chain', () => {
|
||||
const arr = [{ items: ['c', 'b', 'a'] }, { items: ['z', 'y', 'x'] }];
|
||||
// Sort by items.0 which is the first element of the items array
|
||||
SortUtil.sortByField(arr, { field: 'items.0', order: SortDirection.ASC });
|
||||
SortUtil.sortByField(arr, { field: 'items.0', order: App.SortDirection.ASC });
|
||||
expect(arr[0].items[0]).toBe('c');
|
||||
expect(arr[1].items[0]).toBe('z');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { SortBy, SortDirection } from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import { App, Data } from '@app/types';
|
||||
|
||||
export default class SortUtil {
|
||||
static sortByField<T extends object>(arr: T[], sortBy: SortBy): void {
|
||||
static sortByField<T extends object>(arr: T[], sortBy: App.SortBy): void {
|
||||
if (arr.length) {
|
||||
const field = SortUtil.resolveFieldChain(arr[0], sortBy.field);
|
||||
const fieldType = typeof field;
|
||||
|
|
@ -21,7 +20,7 @@ export default class SortUtil {
|
|||
}
|
||||
}
|
||||
|
||||
static sortByFields<T extends object>(arr: T[], sorts: SortBy[]) {
|
||||
static sortByFields<T extends object>(arr: T[], sorts: App.SortBy[]) {
|
||||
if (arr.length) {
|
||||
arr.sort((a, b) => {
|
||||
for (let i = 0; i < sorts.length; i++) {
|
||||
|
|
@ -52,35 +51,35 @@ export default class SortUtil {
|
|||
}
|
||||
}
|
||||
|
||||
static sortUsersByField(users: ServerInfo_User[], sortBy: SortBy) {
|
||||
static sortUsersByField(users: Data.ServerInfo_User[], sortBy: App.SortBy) {
|
||||
if (users.length) {
|
||||
users.sort((a, b) => SortUtil.userComparator(a, b, sortBy))
|
||||
}
|
||||
}
|
||||
|
||||
static toggleSortBy<F extends string>(field: F, sortBy: SortBy): { field: F; order: SortDirection } {
|
||||
static toggleSortBy<F extends string>(field: F, sortBy: App.SortBy): { field: F; order: App.SortDirection } {
|
||||
const sameField = field === sortBy.field;
|
||||
const isASC = sortBy.order === SortDirection.ASC;
|
||||
const isASC = sortBy.order === App.SortDirection.ASC;
|
||||
|
||||
return {
|
||||
field,
|
||||
order: sameField && isASC ? SortDirection.DESC : SortDirection.ASC
|
||||
order: sameField && isASC ? App.SortDirection.DESC : App.SortDirection.ASC
|
||||
}
|
||||
}
|
||||
|
||||
private static sortByNumber<T extends object>(arr: T[], sortBy: SortBy): void {
|
||||
private static sortByNumber<T extends object>(arr: T[], sortBy: App.SortBy): void {
|
||||
arr.sort((a, b) => SortUtil.numberComparator(a, b, sortBy));
|
||||
}
|
||||
|
||||
private static sortByString<T extends object>(arr: T[], sortBy: SortBy): void {
|
||||
private static sortByString<T extends object>(arr: T[], sortBy: App.SortBy): void {
|
||||
arr.sort((a, b) => SortUtil.stringComparator(a, b, sortBy));
|
||||
}
|
||||
|
||||
private static userComparator(a: ServerInfo_User, b: ServerInfo_User, sortBy: SortBy, sortByUserLevel = true) {
|
||||
private static userComparator(a: Data.ServerInfo_User, b: Data.ServerInfo_User, sortBy: App.SortBy, sortByUserLevel = true) {
|
||||
if (sortByUserLevel) {
|
||||
const adminSortBy = {
|
||||
field: 'userLevel',
|
||||
order: SortDirection.DESC
|
||||
order: App.SortDirection.DESC
|
||||
};
|
||||
|
||||
const adminSorted = SortUtil.numberComparator(a, b, adminSortBy);
|
||||
|
|
@ -99,18 +98,18 @@ export default class SortUtil {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private static numberComparator<T extends object>(a: T, b: T, { field, order }: SortBy) {
|
||||
private static numberComparator<T extends object>(a: T, b: T, { field, order }: App.SortBy) {
|
||||
const aResolved = SortUtil.resolveFieldChain(a, field);
|
||||
const bResolved = SortUtil.resolveFieldChain(b, field);
|
||||
|
||||
if (order === SortDirection.ASC) {
|
||||
if (order === App.SortDirection.ASC) {
|
||||
return aResolved - bResolved;
|
||||
} else {
|
||||
return bResolved - aResolved;
|
||||
}
|
||||
}
|
||||
|
||||
private static stringComparator<T extends object>(a: T, b: T, { field, order }: SortBy) {
|
||||
private static stringComparator<T extends object>(a: T, b: T, { field, order }: App.SortBy) {
|
||||
const aResolved = SortUtil.resolveFieldChain(a, field);
|
||||
const bResolved = SortUtil.resolveFieldChain(b, field);
|
||||
|
||||
|
|
@ -125,7 +124,7 @@ export default class SortUtil {
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (order === SortDirection.ASC) {
|
||||
if (order === App.SortDirection.ASC) {
|
||||
return aResolved.localeCompare(bResolved);
|
||||
} else {
|
||||
return bResolved.localeCompare(aResolved);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
import { normalizeRoomInfo, normalizeGameObject, normalizeLogs, normalizeBannedUserError, normalizeUserMessage } from './normalizers';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb';
|
||||
import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb';
|
||||
import { Event_RoomSaySchema } from 'generated/proto/event_room_say_pb';
|
||||
import { Message } from 'types';
|
||||
import { Data, Enriched } from '@app/types';
|
||||
|
||||
describe('normalizeRoomInfo', () => {
|
||||
it('builds gametypeMap from gametypeList and normalises games', () => {
|
||||
const room = create(ServerInfo_RoomSchema, {
|
||||
const room = create(Data.ServerInfo_RoomSchema, {
|
||||
roomId: 1,
|
||||
name: 'Lobby',
|
||||
gametypeList: [{ gameTypeId: 1, description: 'Standard' }],
|
||||
gameList: [
|
||||
create(ServerInfo_GameSchema, { gameId: 10, gameTypes: [1], description: 'My Game' }),
|
||||
create(Data.ServerInfo_GameSchema, { gameId: 10, gameTypes: [1], description: 'My Game' }),
|
||||
],
|
||||
});
|
||||
|
||||
|
|
@ -25,7 +22,7 @@ describe('normalizeRoomInfo', () => {
|
|||
});
|
||||
|
||||
it('handles room with empty gametypeList', () => {
|
||||
const room = create(ServerInfo_RoomSchema, { roomId: 2, name: 'Empty' });
|
||||
const room = create(Data.ServerInfo_RoomSchema, { roomId: 2, name: 'Empty' });
|
||||
const result = normalizeRoomInfo(room);
|
||||
expect(result.gametypeMap).toEqual({});
|
||||
expect(result.gameList).toEqual([]);
|
||||
|
|
@ -34,19 +31,19 @@ describe('normalizeRoomInfo', () => {
|
|||
|
||||
describe('normalizeGameObject', () => {
|
||||
it('maps gameTypes[0] to gameType string via gametypeMap', () => {
|
||||
const game = create(ServerInfo_GameSchema, { gameId: 1, gameTypes: [5] });
|
||||
const game = create(Data.ServerInfo_GameSchema, { gameId: 1, gameTypes: [5] });
|
||||
const result = normalizeGameObject(game, { 5: 'Legacy' });
|
||||
expect(result.gameType).toBe('Legacy');
|
||||
});
|
||||
|
||||
it('returns empty string when no gameTypes', () => {
|
||||
const game = create(ServerInfo_GameSchema, { gameId: 2 });
|
||||
const game = create(Data.ServerInfo_GameSchema, { gameId: 2 });
|
||||
const result = normalizeGameObject(game, {});
|
||||
expect(result.gameType).toBe('');
|
||||
});
|
||||
|
||||
it('fills empty description with empty string', () => {
|
||||
const game = create(ServerInfo_GameSchema, { gameId: 3 });
|
||||
const game = create(Data.ServerInfo_GameSchema, { gameId: 3 });
|
||||
const result = normalizeGameObject(game, {});
|
||||
expect(result.description).toBe('');
|
||||
});
|
||||
|
|
@ -55,10 +52,10 @@ describe('normalizeGameObject', () => {
|
|||
describe('normalizeLogs', () => {
|
||||
it('groups logs by targetType', () => {
|
||||
const logs = [
|
||||
{ targetType: 'room' },
|
||||
{ targetType: 'game' },
|
||||
{ targetType: 'room' },
|
||||
] as any[];
|
||||
create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' }),
|
||||
create(Data.ServerInfo_ChatMessageSchema, { targetType: 'game' }),
|
||||
create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' }),
|
||||
];
|
||||
const result = normalizeLogs(logs);
|
||||
expect(result.room).toHaveLength(2);
|
||||
expect(result.game).toHaveLength(1);
|
||||
|
|
@ -71,7 +68,7 @@ describe('normalizeLogs', () => {
|
|||
});
|
||||
|
||||
describe('normalizeBannedUserError', () => {
|
||||
it('returns permanently banned message when endTime is 0', () => {
|
||||
it('returns permanently banned Enriched.Message when endTime is 0', () => {
|
||||
expect(normalizeBannedUserError('', 0)).toBe('You are permanently banned');
|
||||
});
|
||||
|
||||
|
|
@ -92,11 +89,11 @@ describe('normalizeBannedUserError', () => {
|
|||
});
|
||||
|
||||
describe('normalizeUserMessage', () => {
|
||||
const makeMsg = (fields: Partial<Message>): Message => ({
|
||||
...create(Event_RoomSaySchema),
|
||||
const makeMsg = (fields: Partial<Enriched.Message>): Enriched.Message => ({
|
||||
...create(Data.Event_RoomSaySchema),
|
||||
timeReceived: 0,
|
||||
...fields,
|
||||
} as Message);
|
||||
} as Enriched.Message);
|
||||
|
||||
it('prepends "name: " to message when name is present', () => {
|
||||
const result = normalizeUserMessage(makeMsg({ name: 'Alice', message: 'hello' }));
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb';
|
||||
import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb';
|
||||
import { Game, GametypeMap, LogGroups, Message, Room } from 'types';
|
||||
import { Data, Enriched } from '@app/types';
|
||||
|
||||
/** Flatten a gametype list into a lookup map of { gameTypeId → description }. */
|
||||
export function normalizeGametypeMap(gametypeList: ServerInfo_GameType[]): GametypeMap {
|
||||
return gametypeList.reduce<GametypeMap>((map, type) => {
|
||||
export function normalizeGametypeMap(gametypeList: Data.ServerInfo_GameType[]): Enriched.GametypeMap {
|
||||
return gametypeList.reduce<Enriched.GametypeMap>((map, type) => {
|
||||
map[type.gameTypeId] = type.description;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/** Flatten room gameTypes into a map object and normalize all games inside. */
|
||||
export function normalizeRoomInfo(roomInfo: ServerInfo_Room): Room {
|
||||
export function normalizeRoomInfo(roomInfo: Data.ServerInfo_Room): Enriched.Room {
|
||||
const gametypeMap = normalizeGametypeMap(roomInfo.gametypeList);
|
||||
|
||||
const gameList = roomInfo.gameList.map(
|
||||
|
|
@ -29,7 +25,7 @@ export function normalizeRoomInfo(roomInfo: ServerInfo_Room): Room {
|
|||
}
|
||||
|
||||
/** Flatten gameTypes[] into a gameType string; fill in default sortable values. */
|
||||
export function normalizeGameObject(game: ServerInfo_Game, gametypeMap: GametypeMap): Game {
|
||||
export function normalizeGameObject(game: Data.ServerInfo_Game, gametypeMap: Enriched.GametypeMap): Enriched.Game {
|
||||
const { gameTypes, description } = game;
|
||||
const hasType = gameTypes && gameTypes.length;
|
||||
|
||||
|
|
@ -41,13 +37,13 @@ export function normalizeGameObject(game: ServerInfo_Game, gametypeMap: Gametype
|
|||
}
|
||||
|
||||
/** Group a flat LogItem[] into { room, game, chat } buckets for the server store. */
|
||||
export function normalizeLogs(logs: ServerInfo_ChatMessage[]): LogGroups {
|
||||
export function normalizeLogs(logs: Data.ServerInfo_ChatMessage[]): Enriched.LogGroups {
|
||||
return logs.reduce((obj, log) => {
|
||||
const type = log.targetType as keyof LogGroups;
|
||||
const type = log.targetType as keyof Enriched.LogGroups;
|
||||
obj[type] = obj[type] || [];
|
||||
obj[type]!.push(log);
|
||||
return obj;
|
||||
}, {} as LogGroups);
|
||||
}, {} as Enriched.LogGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -56,7 +52,7 @@ export function normalizeLogs(logs: ServerInfo_ChatMessage[]): LogGroups {
|
|||
* so this is a no-op for those.
|
||||
* Returns a new Message — does not mutate the original.
|
||||
*/
|
||||
export function normalizeUserMessage(message: Message): Message {
|
||||
export function normalizeUserMessage(message: Enriched.Message): Enriched.Message {
|
||||
if (!message.name) {
|
||||
return message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import { ProtoInit } from 'types';
|
||||
import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb';
|
||||
import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb';
|
||||
import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb';
|
||||
import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb';
|
||||
import type { MessageInitShape } from '@bufbuild/protobuf';
|
||||
import { Data } from '@app/types';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb';
|
||||
import { ServerInfo_CounterSchema } from 'generated/proto/serverinfo_counter_pb';
|
||||
import { colorSchema } from 'generated/proto/color_pb';
|
||||
import { ServerInfo_ArrowSchema } from 'generated/proto/serverinfo_arrow_pb';
|
||||
import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb';
|
||||
import { GameEntry, GamesState, PlayerEntry, ZoneEntry } from '../game.interfaces';
|
||||
|
||||
export function makeCard(overrides: ProtoInit<ServerInfo_Card> = {}): ServerInfo_Card {
|
||||
return create(ServerInfo_CardSchema, {
|
||||
export function makeCard(overrides: MessageInitShape<typeof Data.ServerInfo_CardSchema> = {}): Data.ServerInfo_Card {
|
||||
return create(Data.ServerInfo_CardSchema, {
|
||||
id: 1,
|
||||
name: 'Test Card',
|
||||
x: 0,
|
||||
|
|
@ -34,19 +26,19 @@ export function makeCard(overrides: ProtoInit<ServerInfo_Card> = {}): ServerInfo
|
|||
});
|
||||
}
|
||||
|
||||
export function makeCounter(overrides: ProtoInit<ServerInfo_Counter> = {}): ServerInfo_Counter {
|
||||
return create(ServerInfo_CounterSchema, {
|
||||
export function makeCounter(overrides: MessageInitShape<typeof Data.ServerInfo_CounterSchema> = {}): Data.ServerInfo_Counter {
|
||||
return create(Data.ServerInfo_CounterSchema, {
|
||||
id: 1,
|
||||
name: 'Life',
|
||||
counterColor: create(colorSchema, { r: 0, g: 0, b: 0, a: 255 }),
|
||||
counterColor: create(Data.colorSchema, { r: 0, g: 0, b: 0, a: 255 }),
|
||||
radius: 1,
|
||||
count: 20,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
export function makeArrow(overrides: ProtoInit<ServerInfo_Arrow> = {}): ServerInfo_Arrow {
|
||||
return create(ServerInfo_ArrowSchema, {
|
||||
export function makeArrow(overrides: MessageInitShape<typeof Data.ServerInfo_ArrowSchema> = {}): Data.ServerInfo_Arrow {
|
||||
return create(Data.ServerInfo_ArrowSchema, {
|
||||
id: 1,
|
||||
startPlayerId: 1,
|
||||
startZone: 'table',
|
||||
|
|
@ -54,7 +46,7 @@ export function makeArrow(overrides: ProtoInit<ServerInfo_Arrow> = {}): ServerIn
|
|||
targetPlayerId: 1,
|
||||
targetZone: 'table',
|
||||
targetCardId: 2,
|
||||
arrowColor: create(colorSchema, { r: 255, g: 0, b: 0, a: 255 }),
|
||||
arrowColor: create(Data.colorSchema, { r: 255, g: 0, b: 0, a: 255 }),
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
|
@ -72,8 +64,10 @@ export function makeZoneEntry(overrides: Partial<ZoneEntry> = {}): ZoneEntry {
|
|||
};
|
||||
}
|
||||
|
||||
export function makePlayerProperties(overrides: ProtoInit<ServerInfo_PlayerProperties> = {}): ServerInfo_PlayerProperties {
|
||||
return create(ServerInfo_PlayerPropertiesSchema, {
|
||||
export function makePlayerProperties(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_PlayerPropertiesSchema> = {},
|
||||
): Data.ServerInfo_PlayerProperties {
|
||||
return create(Data.ServerInfo_PlayerPropertiesSchema, {
|
||||
playerId: 1,
|
||||
spectator: false,
|
||||
conceded: false,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,13 @@
|
|||
import { create } from '@bufbuild/protobuf';
|
||||
import { Data } from '@app/types';
|
||||
import { Actions } from './game.actions';
|
||||
import { Types } from './game.types';
|
||||
import {
|
||||
makeArrow,
|
||||
makeCard,
|
||||
makeCounter,
|
||||
makeGameEntry,
|
||||
makePlayerProperties,
|
||||
} from './__mocks__/fixtures';
|
||||
import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb';
|
||||
import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb';
|
||||
import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb';
|
||||
import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb';
|
||||
import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb';
|
||||
import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb';
|
||||
import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb';
|
||||
import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb';
|
||||
import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb';
|
||||
import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb';
|
||||
import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb';
|
||||
import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb';
|
||||
import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb';
|
||||
import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb';
|
||||
import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb';
|
||||
import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb';
|
||||
import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb';
|
||||
import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb';
|
||||
import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb';
|
||||
|
||||
describe('Actions', () => {
|
||||
it('clearStore', () => {
|
||||
|
|
@ -34,8 +15,8 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('gameJoined', () => {
|
||||
const entry = makeGameEntry();
|
||||
expect(Actions.gameJoined(1, entry)).toEqual({ type: Types.GAME_JOINED, gameId: 1, gameEntry: entry });
|
||||
const data = create(Data.Event_GameJoinedSchema, { hostId: 1, playerId: 2 });
|
||||
expect(Actions.gameJoined(data)).toEqual({ type: Types.GAME_JOINED, data });
|
||||
});
|
||||
|
||||
it('gameLeft', () => {
|
||||
|
|
@ -51,7 +32,7 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('gameStateChanged', () => {
|
||||
const data = create(Event_GameStateChangedSchema, {
|
||||
const data = create(Data.Event_GameStateChangedSchema, {
|
||||
playerList: [], gameStarted: true, activePlayerId: 1, activePhase: 0, secondsElapsed: 0
|
||||
});
|
||||
expect(Actions.gameStateChanged(1, data)).toEqual({ type: Types.GAME_STATE_CHANGED, gameId: 1, data });
|
||||
|
|
@ -81,85 +62,85 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('cardMoved', () => {
|
||||
const data = create(Event_MoveCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_MoveCardSchema, { cardId: 1 });
|
||||
expect(Actions.cardMoved(1, 2, data)).toEqual({ type: Types.CARD_MOVED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardFlipped', () => {
|
||||
const data = create(Event_FlipCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_FlipCardSchema, { cardId: 1 });
|
||||
expect(Actions.cardFlipped(1, 2, data)).toEqual({ type: Types.CARD_FLIPPED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardDestroyed', () => {
|
||||
const data = create(Event_DestroyCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_DestroyCardSchema, { cardId: 1 });
|
||||
expect(Actions.cardDestroyed(1, 2, data)).toEqual({ type: Types.CARD_DESTROYED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardAttached', () => {
|
||||
const data = create(Event_AttachCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_AttachCardSchema, { cardId: 1 });
|
||||
expect(Actions.cardAttached(1, 2, data)).toEqual({ type: Types.CARD_ATTACHED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('tokenCreated', () => {
|
||||
const data = create(Event_CreateTokenSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_CreateTokenSchema, { cardId: 1 });
|
||||
expect(Actions.tokenCreated(1, 2, data)).toEqual({ type: Types.TOKEN_CREATED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardAttrChanged', () => {
|
||||
const data = create(Event_SetCardAttrSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_SetCardAttrSchema, { cardId: 1 });
|
||||
expect(Actions.cardAttrChanged(1, 2, data)).toEqual({ type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardCounterChanged', () => {
|
||||
const data = create(Event_SetCardCounterSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_SetCardCounterSchema, { cardId: 1 });
|
||||
expect(Actions.cardCounterChanged(1, 2, data)).toEqual({ type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('arrowCreated', () => {
|
||||
const arrow = makeArrow();
|
||||
const data = create(Event_CreateArrowSchema, { arrowInfo: arrow });
|
||||
const data = create(Data.Event_CreateArrowSchema, { arrowInfo: arrow });
|
||||
expect(Actions.arrowCreated(1, 2, data)).toEqual({ type: Types.ARROW_CREATED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('arrowDeleted', () => {
|
||||
const data = create(Event_DeleteArrowSchema, { arrowId: 3 });
|
||||
const data = create(Data.Event_DeleteArrowSchema, { arrowId: 3 });
|
||||
expect(Actions.arrowDeleted(1, 2, data)).toEqual({ type: Types.ARROW_DELETED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('counterCreated', () => {
|
||||
const counter = makeCounter();
|
||||
const data = create(Event_CreateCounterSchema, { counterInfo: counter });
|
||||
const data = create(Data.Event_CreateCounterSchema, { counterInfo: counter });
|
||||
expect(Actions.counterCreated(1, 2, data)).toEqual({ type: Types.COUNTER_CREATED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('counterSet', () => {
|
||||
const data = create(Event_SetCounterSchema, { counterId: 1, value: 10 });
|
||||
const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 10 });
|
||||
expect(Actions.counterSet(1, 2, data)).toEqual({ type: Types.COUNTER_SET, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('counterDeleted', () => {
|
||||
const data = create(Event_DelCounterSchema, { counterId: 1 });
|
||||
const data = create(Data.Event_DelCounterSchema, { counterId: 1 });
|
||||
expect(Actions.counterDeleted(1, 2, data)).toEqual({ type: Types.COUNTER_DELETED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardsDrawn', () => {
|
||||
const card = makeCard();
|
||||
const data = create(Event_DrawCardsSchema, { number: 2, cards: [card] });
|
||||
const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [card] });
|
||||
expect(Actions.cardsDrawn(1, 2, data)).toEqual({ type: Types.CARDS_DRAWN, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('cardsRevealed', () => {
|
||||
const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] });
|
||||
const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] });
|
||||
expect(Actions.cardsRevealed(1, 2, data)).toEqual({ type: Types.CARDS_REVEALED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('zoneShuffled', () => {
|
||||
const data = create(Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 });
|
||||
const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 });
|
||||
expect(Actions.zoneShuffled(1, 2, data)).toEqual({ type: Types.ZONE_SHUFFLED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('dieRolled', () => {
|
||||
const data = create(Event_RollDieSchema, { sides: 6, value: 4, values: [4] });
|
||||
const data = create(Data.Event_RollDieSchema, { sides: 6, value: 4, values: [4] });
|
||||
expect(Actions.dieRolled(1, 2, data)).toEqual({ type: Types.DIE_ROLLED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
|
|
@ -176,12 +157,12 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('zoneDumped', () => {
|
||||
const data = create(Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false });
|
||||
const data = create(Data.Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false });
|
||||
expect(Actions.zoneDumped(1, 2, data)).toEqual({ type: Types.ZONE_DUMPED, gameId: 1, playerId: 2, data });
|
||||
});
|
||||
|
||||
it('zonePropertiesChanged', () => {
|
||||
const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false });
|
||||
const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false });
|
||||
expect(Actions.zonePropertiesChanged(1, 2, data)).toEqual({
|
||||
type: Types.ZONE_PROPERTIES_CHANGED,
|
||||
gameId: 1,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,4 @@
|
|||
import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb';
|
||||
import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb';
|
||||
import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb';
|
||||
import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb';
|
||||
import type { Event_CreateToken } from 'generated/proto/event_create_token_pb';
|
||||
import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb';
|
||||
import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb';
|
||||
import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb';
|
||||
import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb';
|
||||
import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb';
|
||||
import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb';
|
||||
import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb';
|
||||
import type { Event_MoveCard } from 'generated/proto/event_move_card_pb';
|
||||
import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb';
|
||||
import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb';
|
||||
import type { Event_RollDie } from 'generated/proto/event_roll_die_pb';
|
||||
import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb';
|
||||
import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb';
|
||||
import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb';
|
||||
import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb';
|
||||
import { GameEntry } from './game.interfaces';
|
||||
import type { Data } from '@app/types';
|
||||
import { Types } from './game.types';
|
||||
|
||||
export const Actions = {
|
||||
|
|
@ -26,10 +6,9 @@ export const Actions = {
|
|||
type: Types.CLEAR_STORE,
|
||||
}),
|
||||
|
||||
gameJoined: (gameId: number, gameEntry: GameEntry) => ({
|
||||
gameJoined: (data: Data.Event_GameJoined) => ({
|
||||
type: Types.GAME_JOINED,
|
||||
gameId,
|
||||
gameEntry,
|
||||
data,
|
||||
}),
|
||||
|
||||
gameLeft: (gameId: number) => ({
|
||||
|
|
@ -48,13 +27,13 @@ export const Actions = {
|
|||
hostId,
|
||||
}),
|
||||
|
||||
gameStateChanged: (gameId: number, data: Event_GameStateChanged) => ({
|
||||
gameStateChanged: (gameId: number, data: Data.Event_GameStateChanged) => ({
|
||||
type: Types.GAME_STATE_CHANGED,
|
||||
gameId,
|
||||
data,
|
||||
}),
|
||||
|
||||
playerJoined: (gameId: number, playerProperties: ServerInfo_PlayerProperties) => ({
|
||||
playerJoined: (gameId: number, playerProperties: Data.ServerInfo_PlayerProperties) => ({
|
||||
type: Types.PLAYER_JOINED,
|
||||
gameId,
|
||||
playerProperties,
|
||||
|
|
@ -67,7 +46,7 @@ export const Actions = {
|
|||
reason,
|
||||
}),
|
||||
|
||||
playerPropertiesChanged: (gameId: number, playerId: number, properties: ServerInfo_PlayerProperties) => ({
|
||||
playerPropertiesChanged: (gameId: number, playerId: number, properties: Data.ServerInfo_PlayerProperties) => ({
|
||||
type: Types.PLAYER_PROPERTIES_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
|
|
@ -79,112 +58,112 @@ export const Actions = {
|
|||
gameId,
|
||||
}),
|
||||
|
||||
cardMoved: (gameId: number, playerId: number, data: Event_MoveCard) => ({
|
||||
cardMoved: (gameId: number, playerId: number, data: Data.Event_MoveCard) => ({
|
||||
type: Types.CARD_MOVED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardFlipped: (gameId: number, playerId: number, data: Event_FlipCard) => ({
|
||||
cardFlipped: (gameId: number, playerId: number, data: Data.Event_FlipCard) => ({
|
||||
type: Types.CARD_FLIPPED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardDestroyed: (gameId: number, playerId: number, data: Event_DestroyCard) => ({
|
||||
cardDestroyed: (gameId: number, playerId: number, data: Data.Event_DestroyCard) => ({
|
||||
type: Types.CARD_DESTROYED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardAttached: (gameId: number, playerId: number, data: Event_AttachCard) => ({
|
||||
cardAttached: (gameId: number, playerId: number, data: Data.Event_AttachCard) => ({
|
||||
type: Types.CARD_ATTACHED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
tokenCreated: (gameId: number, playerId: number, data: Event_CreateToken) => ({
|
||||
tokenCreated: (gameId: number, playerId: number, data: Data.Event_CreateToken) => ({
|
||||
type: Types.TOKEN_CREATED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardAttrChanged: (gameId: number, playerId: number, data: Event_SetCardAttr) => ({
|
||||
cardAttrChanged: (gameId: number, playerId: number, data: Data.Event_SetCardAttr) => ({
|
||||
type: Types.CARD_ATTR_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardCounterChanged: (gameId: number, playerId: number, data: Event_SetCardCounter) => ({
|
||||
cardCounterChanged: (gameId: number, playerId: number, data: Data.Event_SetCardCounter) => ({
|
||||
type: Types.CARD_COUNTER_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
arrowCreated: (gameId: number, playerId: number, data: Event_CreateArrow) => ({
|
||||
arrowCreated: (gameId: number, playerId: number, data: Data.Event_CreateArrow) => ({
|
||||
type: Types.ARROW_CREATED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
arrowDeleted: (gameId: number, playerId: number, data: Event_DeleteArrow) => ({
|
||||
arrowDeleted: (gameId: number, playerId: number, data: Data.Event_DeleteArrow) => ({
|
||||
type: Types.ARROW_DELETED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
counterCreated: (gameId: number, playerId: number, data: Event_CreateCounter) => ({
|
||||
counterCreated: (gameId: number, playerId: number, data: Data.Event_CreateCounter) => ({
|
||||
type: Types.COUNTER_CREATED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
counterSet: (gameId: number, playerId: number, data: Event_SetCounter) => ({
|
||||
counterSet: (gameId: number, playerId: number, data: Data.Event_SetCounter) => ({
|
||||
type: Types.COUNTER_SET,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
counterDeleted: (gameId: number, playerId: number, data: Event_DelCounter) => ({
|
||||
counterDeleted: (gameId: number, playerId: number, data: Data.Event_DelCounter) => ({
|
||||
type: Types.COUNTER_DELETED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardsDrawn: (gameId: number, playerId: number, data: Event_DrawCards) => ({
|
||||
cardsDrawn: (gameId: number, playerId: number, data: Data.Event_DrawCards) => ({
|
||||
type: Types.CARDS_DRAWN,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardsRevealed: (gameId: number, playerId: number, data: Event_RevealCards) => ({
|
||||
cardsRevealed: (gameId: number, playerId: number, data: Data.Event_RevealCards) => ({
|
||||
type: Types.CARDS_REVEALED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
zoneShuffled: (gameId: number, playerId: number, data: Event_Shuffle) => ({
|
||||
zoneShuffled: (gameId: number, playerId: number, data: Data.Event_Shuffle) => ({
|
||||
type: Types.ZONE_SHUFFLED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
dieRolled: (gameId: number, playerId: number, data: Event_RollDie) => ({
|
||||
dieRolled: (gameId: number, playerId: number, data: Data.Event_RollDie) => ({
|
||||
type: Types.DIE_ROLLED,
|
||||
gameId,
|
||||
playerId,
|
||||
|
|
@ -209,14 +188,14 @@ export const Actions = {
|
|||
reversed,
|
||||
}),
|
||||
|
||||
zoneDumped: (gameId: number, playerId: number, data: Event_DumpZone) => ({
|
||||
zoneDumped: (gameId: number, playerId: number, data: Data.Event_DumpZone) => ({
|
||||
type: Types.ZONE_DUMPED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
zonePropertiesChanged: (gameId: number, playerId: number, data: Event_ChangeZoneProperties) => ({
|
||||
zonePropertiesChanged: (gameId: number, playerId: number, data: Data.Event_ChangeZoneProperties) => ({
|
||||
type: Types.ZONE_PROPERTIES_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
|
|
|
|||
|
|
@ -1,37 +1,16 @@
|
|||
vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } }));
|
||||
vi.mock('../store', () => ({ store: { dispatch: vi.fn() } }));
|
||||
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { store } from 'store/store';
|
||||
import { Data } from '@app/types';
|
||||
import { store } from '..';
|
||||
import { Actions } from './game.actions';
|
||||
import { Dispatch } from './game.dispatch';
|
||||
import {
|
||||
makeArrow,
|
||||
makeCard,
|
||||
makeCounter,
|
||||
makeGameEntry,
|
||||
makePlayerProperties,
|
||||
} from './__mocks__/fixtures';
|
||||
import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb';
|
||||
import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb';
|
||||
import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb';
|
||||
import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb';
|
||||
import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb';
|
||||
import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb';
|
||||
import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb';
|
||||
import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb';
|
||||
import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb';
|
||||
import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb';
|
||||
import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb';
|
||||
import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb';
|
||||
import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb';
|
||||
import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb';
|
||||
import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb';
|
||||
import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb';
|
||||
import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb';
|
||||
import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb';
|
||||
import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb';
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('Dispatch', () => {
|
||||
it('clearStore dispatches Actions.clearStore()', () => {
|
||||
|
|
@ -40,9 +19,9 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('gameJoined dispatches Actions.gameJoined()', () => {
|
||||
const entry = makeGameEntry();
|
||||
Dispatch.gameJoined(1, entry);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.gameJoined(1, entry));
|
||||
const data = create(Data.Event_GameJoinedSchema, { hostId: 1, playerId: 2 });
|
||||
Dispatch.gameJoined(data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.gameJoined(data));
|
||||
});
|
||||
|
||||
it('gameLeft dispatches Actions.gameLeft()', () => {
|
||||
|
|
@ -61,7 +40,7 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('gameStateChanged dispatches Actions.gameStateChanged()', () => {
|
||||
const data = create(Event_GameStateChangedSchema, {
|
||||
const data = create(Data.Event_GameStateChangedSchema, {
|
||||
playerList: [], gameStarted: false, activePlayerId: 0, activePhase: 0, secondsElapsed: 0
|
||||
});
|
||||
Dispatch.gameStateChanged(1, data);
|
||||
|
|
@ -91,97 +70,97 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('cardMoved dispatches Actions.cardMoved()', () => {
|
||||
const data = create(Event_MoveCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_MoveCardSchema, { cardId: 1 });
|
||||
Dispatch.cardMoved(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardMoved(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardFlipped dispatches Actions.cardFlipped()', () => {
|
||||
const data = create(Event_FlipCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_FlipCardSchema, { cardId: 1 });
|
||||
Dispatch.cardFlipped(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardFlipped(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardDestroyed dispatches Actions.cardDestroyed()', () => {
|
||||
const data = create(Event_DestroyCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_DestroyCardSchema, { cardId: 1 });
|
||||
Dispatch.cardDestroyed(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardDestroyed(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardAttached dispatches Actions.cardAttached()', () => {
|
||||
const data = create(Event_AttachCardSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_AttachCardSchema, { cardId: 1 });
|
||||
Dispatch.cardAttached(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttached(1, 2, data));
|
||||
});
|
||||
|
||||
it('tokenCreated dispatches Actions.tokenCreated()', () => {
|
||||
const data = create(Event_CreateTokenSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_CreateTokenSchema, { cardId: 1 });
|
||||
Dispatch.tokenCreated(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.tokenCreated(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardAttrChanged dispatches Actions.cardAttrChanged()', () => {
|
||||
const data = create(Event_SetCardAttrSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_SetCardAttrSchema, { cardId: 1 });
|
||||
Dispatch.cardAttrChanged(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttrChanged(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardCounterChanged dispatches Actions.cardCounterChanged()', () => {
|
||||
const data = create(Event_SetCardCounterSchema, { cardId: 1 });
|
||||
const data = create(Data.Event_SetCardCounterSchema, { cardId: 1 });
|
||||
Dispatch.cardCounterChanged(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardCounterChanged(1, 2, data));
|
||||
});
|
||||
|
||||
it('arrowCreated dispatches Actions.arrowCreated()', () => {
|
||||
const data = create(Event_CreateArrowSchema, { arrowInfo: makeArrow() });
|
||||
const data = create(Data.Event_CreateArrowSchema, { arrowInfo: makeArrow() });
|
||||
Dispatch.arrowCreated(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowCreated(1, 2, data));
|
||||
});
|
||||
|
||||
it('arrowDeleted dispatches Actions.arrowDeleted()', () => {
|
||||
const data = create(Event_DeleteArrowSchema, { arrowId: 3 });
|
||||
const data = create(Data.Event_DeleteArrowSchema, { arrowId: 3 });
|
||||
Dispatch.arrowDeleted(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowDeleted(1, 2, data));
|
||||
});
|
||||
|
||||
it('counterCreated dispatches Actions.counterCreated()', () => {
|
||||
const data = create(Event_CreateCounterSchema, { counterInfo: makeCounter() });
|
||||
const data = create(Data.Event_CreateCounterSchema, { counterInfo: makeCounter() });
|
||||
Dispatch.counterCreated(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.counterCreated(1, 2, data));
|
||||
});
|
||||
|
||||
it('counterSet dispatches Actions.counterSet()', () => {
|
||||
const data = create(Event_SetCounterSchema, { counterId: 1, value: 10 });
|
||||
const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 10 });
|
||||
Dispatch.counterSet(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.counterSet(1, 2, data));
|
||||
});
|
||||
|
||||
it('counterDeleted dispatches Actions.counterDeleted()', () => {
|
||||
const data = create(Event_DelCounterSchema, { counterId: 1 });
|
||||
const data = create(Data.Event_DelCounterSchema, { counterId: 1 });
|
||||
Dispatch.counterDeleted(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.counterDeleted(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardsDrawn dispatches Actions.cardsDrawn()', () => {
|
||||
const data = create(Event_DrawCardsSchema, { number: 2, cards: [makeCard()] });
|
||||
const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [makeCard()] });
|
||||
Dispatch.cardsDrawn(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsDrawn(1, 2, data));
|
||||
});
|
||||
|
||||
it('cardsRevealed dispatches Actions.cardsRevealed()', () => {
|
||||
const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] });
|
||||
const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] });
|
||||
Dispatch.cardsRevealed(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsRevealed(1, 2, data));
|
||||
});
|
||||
|
||||
it('zoneShuffled dispatches Actions.zoneShuffled()', () => {
|
||||
const data = create(Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 });
|
||||
const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 });
|
||||
Dispatch.zoneShuffled(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneShuffled(1, 2, data));
|
||||
});
|
||||
|
||||
it('dieRolled dispatches Actions.dieRolled()', () => {
|
||||
const data = create(Event_RollDieSchema, { sides: 6, value: 4, values: [4] });
|
||||
const data = create(Data.Event_RollDieSchema, { sides: 6, value: 4, values: [4] });
|
||||
Dispatch.dieRolled(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.dieRolled(1, 2, data));
|
||||
});
|
||||
|
|
@ -202,13 +181,13 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('zoneDumped dispatches Actions.zoneDumped()', () => {
|
||||
const data = create(Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false });
|
||||
const data = create(Data.Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false });
|
||||
Dispatch.zoneDumped(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneDumped(1, 2, data));
|
||||
});
|
||||
|
||||
it('zonePropertiesChanged dispatches Actions.zonePropertiesChanged()', () => {
|
||||
const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false });
|
||||
const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false });
|
||||
Dispatch.zonePropertiesChanged(1, 2, data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.zonePropertiesChanged(1, 2, data));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,34 +1,14 @@
|
|||
import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb';
|
||||
import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb';
|
||||
import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb';
|
||||
import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb';
|
||||
import type { Event_CreateToken } from 'generated/proto/event_create_token_pb';
|
||||
import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb';
|
||||
import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb';
|
||||
import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb';
|
||||
import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb';
|
||||
import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb';
|
||||
import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb';
|
||||
import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb';
|
||||
import type { Event_MoveCard } from 'generated/proto/event_move_card_pb';
|
||||
import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb';
|
||||
import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb';
|
||||
import type { Event_RollDie } from 'generated/proto/event_roll_die_pb';
|
||||
import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb';
|
||||
import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb';
|
||||
import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb';
|
||||
import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb';
|
||||
import { store } from 'store/store';
|
||||
import type { Data } from '@app/types';
|
||||
import { store } from '..';
|
||||
import { Actions } from './game.actions';
|
||||
import { GameEntry } from './game.interfaces';
|
||||
|
||||
export const Dispatch = {
|
||||
clearStore: () => {
|
||||
store.dispatch(Actions.clearStore());
|
||||
},
|
||||
|
||||
gameJoined: (gameId: number, gameEntry: GameEntry) => {
|
||||
store.dispatch(Actions.gameJoined(gameId, gameEntry));
|
||||
gameJoined: (data: Data.Event_GameJoined) => {
|
||||
store.dispatch(Actions.gameJoined(data));
|
||||
},
|
||||
|
||||
gameLeft: (gameId: number) => {
|
||||
|
|
@ -43,11 +23,11 @@ export const Dispatch = {
|
|||
store.dispatch(Actions.gameHostChanged(gameId, hostId));
|
||||
},
|
||||
|
||||
gameStateChanged: (gameId: number, data: Event_GameStateChanged) => {
|
||||
gameStateChanged: (gameId: number, data: Data.Event_GameStateChanged) => {
|
||||
store.dispatch(Actions.gameStateChanged(gameId, data));
|
||||
},
|
||||
|
||||
playerJoined: (gameId: number, playerProperties: ServerInfo_PlayerProperties) => {
|
||||
playerJoined: (gameId: number, playerProperties: Data.ServerInfo_PlayerProperties) => {
|
||||
store.dispatch(Actions.playerJoined(gameId, playerProperties));
|
||||
},
|
||||
|
||||
|
|
@ -55,7 +35,7 @@ export const Dispatch = {
|
|||
store.dispatch(Actions.playerLeft(gameId, playerId, reason));
|
||||
},
|
||||
|
||||
playerPropertiesChanged: (gameId: number, playerId: number, properties: ServerInfo_PlayerProperties) => {
|
||||
playerPropertiesChanged: (gameId: number, playerId: number, properties: Data.ServerInfo_PlayerProperties) => {
|
||||
store.dispatch(Actions.playerPropertiesChanged(gameId, playerId, properties));
|
||||
},
|
||||
|
||||
|
|
@ -63,67 +43,67 @@ export const Dispatch = {
|
|||
store.dispatch(Actions.kicked(gameId));
|
||||
},
|
||||
|
||||
cardMoved: (gameId: number, playerId: number, data: Event_MoveCard) => {
|
||||
cardMoved: (gameId: number, playerId: number, data: Data.Event_MoveCard) => {
|
||||
store.dispatch(Actions.cardMoved(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardFlipped: (gameId: number, playerId: number, data: Event_FlipCard) => {
|
||||
cardFlipped: (gameId: number, playerId: number, data: Data.Event_FlipCard) => {
|
||||
store.dispatch(Actions.cardFlipped(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardDestroyed: (gameId: number, playerId: number, data: Event_DestroyCard) => {
|
||||
cardDestroyed: (gameId: number, playerId: number, data: Data.Event_DestroyCard) => {
|
||||
store.dispatch(Actions.cardDestroyed(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardAttached: (gameId: number, playerId: number, data: Event_AttachCard) => {
|
||||
cardAttached: (gameId: number, playerId: number, data: Data.Event_AttachCard) => {
|
||||
store.dispatch(Actions.cardAttached(gameId, playerId, data));
|
||||
},
|
||||
|
||||
tokenCreated: (gameId: number, playerId: number, data: Event_CreateToken) => {
|
||||
tokenCreated: (gameId: number, playerId: number, data: Data.Event_CreateToken) => {
|
||||
store.dispatch(Actions.tokenCreated(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardAttrChanged: (gameId: number, playerId: number, data: Event_SetCardAttr) => {
|
||||
cardAttrChanged: (gameId: number, playerId: number, data: Data.Event_SetCardAttr) => {
|
||||
store.dispatch(Actions.cardAttrChanged(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardCounterChanged: (gameId: number, playerId: number, data: Event_SetCardCounter) => {
|
||||
cardCounterChanged: (gameId: number, playerId: number, data: Data.Event_SetCardCounter) => {
|
||||
store.dispatch(Actions.cardCounterChanged(gameId, playerId, data));
|
||||
},
|
||||
|
||||
arrowCreated: (gameId: number, playerId: number, data: Event_CreateArrow) => {
|
||||
arrowCreated: (gameId: number, playerId: number, data: Data.Event_CreateArrow) => {
|
||||
store.dispatch(Actions.arrowCreated(gameId, playerId, data));
|
||||
},
|
||||
|
||||
arrowDeleted: (gameId: number, playerId: number, data: Event_DeleteArrow) => {
|
||||
arrowDeleted: (gameId: number, playerId: number, data: Data.Event_DeleteArrow) => {
|
||||
store.dispatch(Actions.arrowDeleted(gameId, playerId, data));
|
||||
},
|
||||
|
||||
counterCreated: (gameId: number, playerId: number, data: Event_CreateCounter) => {
|
||||
counterCreated: (gameId: number, playerId: number, data: Data.Event_CreateCounter) => {
|
||||
store.dispatch(Actions.counterCreated(gameId, playerId, data));
|
||||
},
|
||||
|
||||
counterSet: (gameId: number, playerId: number, data: Event_SetCounter) => {
|
||||
counterSet: (gameId: number, playerId: number, data: Data.Event_SetCounter) => {
|
||||
store.dispatch(Actions.counterSet(gameId, playerId, data));
|
||||
},
|
||||
|
||||
counterDeleted: (gameId: number, playerId: number, data: Event_DelCounter) => {
|
||||
counterDeleted: (gameId: number, playerId: number, data: Data.Event_DelCounter) => {
|
||||
store.dispatch(Actions.counterDeleted(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardsDrawn: (gameId: number, playerId: number, data: Event_DrawCards) => {
|
||||
cardsDrawn: (gameId: number, playerId: number, data: Data.Event_DrawCards) => {
|
||||
store.dispatch(Actions.cardsDrawn(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardsRevealed: (gameId: number, playerId: number, data: Event_RevealCards) => {
|
||||
cardsRevealed: (gameId: number, playerId: number, data: Data.Event_RevealCards) => {
|
||||
store.dispatch(Actions.cardsRevealed(gameId, playerId, data));
|
||||
},
|
||||
|
||||
zoneShuffled: (gameId: number, playerId: number, data: Event_Shuffle) => {
|
||||
zoneShuffled: (gameId: number, playerId: number, data: Data.Event_Shuffle) => {
|
||||
store.dispatch(Actions.zoneShuffled(gameId, playerId, data));
|
||||
},
|
||||
|
||||
dieRolled: (gameId: number, playerId: number, data: Event_RollDie) => {
|
||||
dieRolled: (gameId: number, playerId: number, data: Data.Event_RollDie) => {
|
||||
store.dispatch(Actions.dieRolled(gameId, playerId, data));
|
||||
},
|
||||
|
||||
|
|
@ -139,11 +119,11 @@ export const Dispatch = {
|
|||
store.dispatch(Actions.turnReversed(gameId, reversed));
|
||||
},
|
||||
|
||||
zoneDumped: (gameId: number, playerId: number, data: Event_DumpZone) => {
|
||||
zoneDumped: (gameId: number, playerId: number, data: Data.Event_DumpZone) => {
|
||||
store.dispatch(Actions.zoneDumped(gameId, playerId, data));
|
||||
},
|
||||
|
||||
zonePropertiesChanged: (gameId: number, playerId: number, data: Event_ChangeZoneProperties) => {
|
||||
zonePropertiesChanged: (gameId: number, playerId: number, data: Data.Event_ChangeZoneProperties) => {
|
||||
store.dispatch(Actions.zonePropertiesChanged(gameId, playerId, data));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb';
|
||||
import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb';
|
||||
import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb';
|
||||
import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb';
|
||||
import type { Data } from '@app/types';
|
||||
|
||||
export interface GamesState {
|
||||
games: { [gameId: number]: GameEntry };
|
||||
|
|
@ -32,14 +29,14 @@ export interface GameEntry {
|
|||
|
||||
/** Normalized from ServerInfo_Player — keyed collections for O(1) lookup. */
|
||||
export interface PlayerEntry {
|
||||
properties: ServerInfo_PlayerProperties;
|
||||
properties: Data.ServerInfo_PlayerProperties;
|
||||
deckList: string;
|
||||
/** Zones keyed by zone name (e.g. "hand", "deck", "table"). */
|
||||
zones: { [zoneName: string]: ZoneEntry };
|
||||
/** Player-level counters (e.g. life) keyed by counter id. */
|
||||
counters: { [counterId: number]: ServerInfo_Counter };
|
||||
counters: { [counterId: number]: Data.ServerInfo_Counter };
|
||||
/** Arrows keyed by arrow id. */
|
||||
arrows: { [arrowId: number]: ServerInfo_Arrow };
|
||||
arrows: { [arrowId: number]: Data.ServerInfo_Arrow };
|
||||
}
|
||||
|
||||
/** Normalized from ServerInfo_Zone — card list is an ordered array matching proto. */
|
||||
|
|
@ -51,7 +48,7 @@ export interface ZoneEntry {
|
|||
/** Authoritative card count (used for hidden zones where cardList may be empty). */
|
||||
cardCount: number;
|
||||
/** Ordered card list; may be empty for hidden zones with no dump active. */
|
||||
cards: ServerInfo_Card[];
|
||||
cards: Data.ServerInfo_Card[];
|
||||
alwaysRevealTopCard: boolean;
|
||||
alwaysLookAtTopCard: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { create } from '@bufbuild/protobuf';
|
||||
import { CardAttribute } from 'generated/proto/card_attributes_pb';
|
||||
import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb';
|
||||
import { Data } from '@app/types';
|
||||
import { gamesReducer } from './game.reducer';
|
||||
import { Types } from './game.types';
|
||||
import {
|
||||
|
|
@ -13,7 +12,6 @@ import {
|
|||
makeState,
|
||||
makeZoneEntry,
|
||||
} from './__mocks__/fixtures';
|
||||
import { ServerInfo_PlayerSchema } from 'generated/proto/serverinfo_player_pb';
|
||||
|
||||
// ── 2A: Initialisation & lifecycle ───────────────────────────────────────────
|
||||
|
||||
|
|
@ -30,9 +28,16 @@ describe('2A: Initialisation & lifecycle', () => {
|
|||
});
|
||||
|
||||
it('GAME_JOINED → inserts gameEntry keyed by gameId', () => {
|
||||
const entry = makeGameEntry({ gameId: 42 });
|
||||
const result = gamesReducer({ games: {} }, { type: Types.GAME_JOINED, gameId: 42, gameEntry: entry });
|
||||
expect(result.games[42]).toBe(entry);
|
||||
const data = create(Data.Event_GameJoinedSchema, {
|
||||
gameInfo: create(Data.ServerInfo_GameSchema, { gameId: 42, roomId: 1, description: 'test' }),
|
||||
hostId: 5,
|
||||
playerId: 2,
|
||||
spectator: false,
|
||||
judge: false,
|
||||
resuming: false,
|
||||
});
|
||||
const result = gamesReducer({ games: {} }, { type: Types.GAME_JOINED, data });
|
||||
expect(result.games[42]).toEqual(expect.objectContaining({ gameId: 42, hostId: 5, localPlayerId: 2 }));
|
||||
});
|
||||
|
||||
it('GAME_LEFT → removes game by gameId', () => {
|
||||
|
|
@ -69,8 +74,8 @@ describe('2B: Game state & player management', () => {
|
|||
const card = makeCard({ id: 5 });
|
||||
const counter = makeCounter({ id: 2 });
|
||||
const arrow = makeArrow({ id: 3 });
|
||||
const playerList: ServerInfo_Player[] = [
|
||||
create(ServerInfo_PlayerSchema, {
|
||||
const playerList: Data.ServerInfo_Player[] = [
|
||||
create(Data.ServerInfo_PlayerSchema, {
|
||||
properties: makePlayerProperties({ playerId: 7 }),
|
||||
deckList: 'some deck',
|
||||
zoneList: [
|
||||
|
|
@ -550,7 +555,7 @@ describe('2E: CARD_ATTR_CHANGED', () => {
|
|||
});
|
||||
}
|
||||
|
||||
function dispatchAttr(state: ReturnType<typeof makeState>, attribute: CardAttribute, attrValue: string) {
|
||||
function dispatchAttr(state: ReturnType<typeof makeState>, attribute: Data.CardAttribute, attrValue: string) {
|
||||
return gamesReducer(state, {
|
||||
type: Types.CARD_ATTR_CHANGED,
|
||||
gameId: 1,
|
||||
|
|
@ -560,37 +565,37 @@ describe('2E: CARD_ATTR_CHANGED', () => {
|
|||
}
|
||||
|
||||
it('AttrTapped (1) → card.tapped = true when attrValue is "1"', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrTapped, '1');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrTapped, '1');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].tapped).toBe(true);
|
||||
});
|
||||
|
||||
it('AttrAttacking (2) → card.attacking = true when attrValue is "1"', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrAttacking, '1');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrAttacking, '1');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].attacking).toBe(true);
|
||||
});
|
||||
|
||||
it('AttrFaceDown (3) → card.faceDown = true when attrValue is "1"', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrFaceDown, '1');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrFaceDown, '1');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].faceDown).toBe(true);
|
||||
});
|
||||
|
||||
it('AttrColor (4) → card.color = attrValue', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrColor, 'red');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrColor, 'red');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].color).toBe('red');
|
||||
});
|
||||
|
||||
it('AttrPT (5) → card.pt = attrValue', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrPT, '2/3');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrPT, '2/3');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].pt).toBe('2/3');
|
||||
});
|
||||
|
||||
it('AttrAnnotation (6) → card.annotation = attrValue', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrAnnotation, 'enchanted');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrAnnotation, 'enchanted');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].annotation).toBe('enchanted');
|
||||
});
|
||||
|
||||
it('AttrDoesntUntap (7) → card.doesntUntap = true when attrValue is "1"', () => {
|
||||
const result = dispatchAttr(stateWithCard(), CardAttribute.AttrDoesntUntap, '1');
|
||||
const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrDoesntUntap, '1');
|
||||
expect(result.games[1].players[1].zones['table'].cards[0].doesntUntap).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
import { CardAttribute } from 'generated/proto/card_attributes_pb';
|
||||
import type { ServerInfo_CardCounter } from 'generated/proto/serverinfo_cardcounter_pb';
|
||||
import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb';
|
||||
import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb';
|
||||
import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb';
|
||||
import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb';
|
||||
import { Data } from '@app/types';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb';
|
||||
import { ServerInfo_CardCounterSchema } from 'generated/proto/serverinfo_cardcounter_pb';
|
||||
import { GameAction } from './game.actions';
|
||||
import { GameEntry, GameMessage, GamesState, PlayerEntry, ZoneEntry } from './game.interfaces';
|
||||
import { Types } from './game.types';
|
||||
|
|
@ -74,7 +67,7 @@ function removeGame(state: GamesState, gameId: number): GamesState {
|
|||
}
|
||||
|
||||
/** Converts the proto PlayerInfo[] array into the keyed PlayerEntry map used in the store. */
|
||||
function normalizePlayers(playerList: ServerInfo_Player[]): { [playerId: number]: PlayerEntry } {
|
||||
function normalizePlayers(playerList: Data.ServerInfo_Player[]): { [playerId: number]: PlayerEntry } {
|
||||
const players: { [playerId: number]: PlayerEntry } = {};
|
||||
for (const player of playerList) {
|
||||
const playerId = player.properties.playerId;
|
||||
|
|
@ -92,12 +85,12 @@ function normalizePlayers(playerList: ServerInfo_Player[]): { [playerId: number]
|
|||
};
|
||||
}
|
||||
|
||||
const counters: { [counterId: number]: ServerInfo_Counter } = {};
|
||||
const counters: { [counterId: number]: Data.ServerInfo_Counter } = {};
|
||||
for (const counter of player.counterList) {
|
||||
counters[counter.id] = counter;
|
||||
}
|
||||
|
||||
const arrows: { [arrowId: number]: ServerInfo_Arrow } = {};
|
||||
const arrows: { [arrowId: number]: Data.ServerInfo_Arrow } = {};
|
||||
for (const arrow of player.arrowList) {
|
||||
arrows[arrow.id] = arrow;
|
||||
}
|
||||
|
|
@ -120,8 +113,8 @@ function buildEmptyCard(
|
|||
y: number,
|
||||
faceDown: boolean,
|
||||
providerId: string
|
||||
): ServerInfo_Card {
|
||||
return create(ServerInfo_CardSchema, {
|
||||
): Data.ServerInfo_Card {
|
||||
return create(Data.ServerInfo_CardSchema, {
|
||||
id,
|
||||
name,
|
||||
x,
|
||||
|
|
@ -157,9 +150,31 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
}
|
||||
|
||||
case Types.GAME_JOINED: {
|
||||
const { data } = action;
|
||||
const gameInfo = data.gameInfo;
|
||||
if (!gameInfo) {
|
||||
return state;
|
||||
}
|
||||
const gameEntry: GameEntry = {
|
||||
gameId: gameInfo.gameId,
|
||||
roomId: gameInfo.roomId,
|
||||
description: gameInfo.description,
|
||||
hostId: data.hostId,
|
||||
localPlayerId: data.playerId,
|
||||
spectator: data.spectator,
|
||||
judge: data.judge,
|
||||
resuming: data.resuming,
|
||||
started: gameInfo.started,
|
||||
activePlayerId: -1,
|
||||
activePhase: -1,
|
||||
secondsElapsed: 0,
|
||||
reversed: false,
|
||||
players: {},
|
||||
messages: [],
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
games: { ...state.games, [action.gameId]: action.gameEntry },
|
||||
games: { ...state.games, [gameEntry.gameId]: gameEntry },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -266,8 +281,8 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
}
|
||||
|
||||
// Locate card in source zone (by id for visible zones, by position for hidden)
|
||||
let removedCard: ServerInfo_Card | undefined;
|
||||
let newSourceCards: ServerInfo_Card[];
|
||||
let removedCard: Data.ServerInfo_Card | undefined;
|
||||
let newSourceCards: Data.ServerInfo_Card[];
|
||||
if (cardId >= 0) {
|
||||
removedCard = sourceZoneEntry.cards.find(c => c.id === cardId);
|
||||
newSourceCards = sourceZoneEntry.cards.filter(c => c.id !== cardId);
|
||||
|
|
@ -280,7 +295,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
}
|
||||
|
||||
const effectiveNewId = newCardId >= 0 ? newCardId : (removedCard?.id ?? -1);
|
||||
const movedCard: ServerInfo_Card = removedCard
|
||||
const movedCard: Data.ServerInfo_Card = removedCard
|
||||
? {
|
||||
...removedCard,
|
||||
id: effectiveNewId,
|
||||
|
|
@ -423,7 +438,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
return state;
|
||||
}
|
||||
|
||||
const newCard: ServerInfo_Card = create(ServerInfo_CardSchema, {
|
||||
const newCard: Data.ServerInfo_Card = create(Data.ServerInfo_CardSchema, {
|
||||
id: cardId,
|
||||
name: cardName,
|
||||
x,
|
||||
|
|
@ -469,15 +484,15 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
return state;
|
||||
}
|
||||
|
||||
const attrPatch: Partial<ServerInfo_Card> = {};
|
||||
switch (attribute as CardAttribute) {
|
||||
case CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break;
|
||||
case CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break;
|
||||
case CardAttribute.AttrFaceDown: attrPatch.faceDown = attrValue === '1'; break;
|
||||
case CardAttribute.AttrColor: attrPatch.color = attrValue; break;
|
||||
case CardAttribute.AttrPT: attrPatch.pt = attrValue; break;
|
||||
case CardAttribute.AttrAnnotation: attrPatch.annotation = attrValue; break;
|
||||
case CardAttribute.AttrDoesntUntap: attrPatch.doesntUntap = attrValue === '1'; break;
|
||||
const attrPatch: Partial<Data.ServerInfo_Card> = {};
|
||||
switch (attribute as Data.CardAttribute) {
|
||||
case Data.CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break;
|
||||
case Data.CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break;
|
||||
case Data.CardAttribute.AttrFaceDown: attrPatch.faceDown = attrValue === '1'; break;
|
||||
case Data.CardAttribute.AttrColor: attrPatch.color = attrValue; break;
|
||||
case Data.CardAttribute.AttrPT: attrPatch.pt = attrValue; break;
|
||||
case Data.CardAttribute.AttrAnnotation: attrPatch.annotation = attrValue; break;
|
||||
case Data.CardAttribute.AttrDoesntUntap: attrPatch.doesntUntap = attrValue === '1'; break;
|
||||
}
|
||||
|
||||
const updatedCards = [...zone.cards];
|
||||
|
|
@ -507,7 +522,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
}
|
||||
|
||||
const card = zone.cards[cardIdx];
|
||||
let newCounterList: ServerInfo_CardCounter[];
|
||||
let newCounterList: Data.ServerInfo_CardCounter[];
|
||||
if (counterValue <= 0) {
|
||||
newCounterList = card.counterList.filter(c => c.id !== counterId);
|
||||
} else {
|
||||
|
|
@ -515,7 +530,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio
|
|||
newCounterList =
|
||||
existing >= 0
|
||||
? card.counterList.map(c => (c.id === counterId ? { ...c, value: counterValue } : c))
|
||||
: [...card.counterList, create(ServerInfo_CardCounterSchema, { id: counterId, value: counterValue })];
|
||||
: [...card.counterList, create(Data.ServerInfo_CardCounterSchema, { id: counterId, value: counterValue })];
|
||||
}
|
||||
|
||||
const updatedCards = [...zone.cards];
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb';
|
||||
import type { Data } from '@app/types';
|
||||
import { GamesState, GameEntry, PlayerEntry, ZoneEntry } from './game.interfaces';
|
||||
|
||||
interface State {
|
||||
games: GamesState;
|
||||
}
|
||||
|
||||
const EMPTY_ARRAY: ServerInfo_Card[] = [];
|
||||
const EMPTY_ARRAY: Data.ServerInfo_Card[] = [];
|
||||
const EMPTY_OBJECT = {} as Record<string, never>;
|
||||
|
||||
export const Selectors = {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export {
|
|||
Selectors as GameSelectors,
|
||||
Dispatch as GameDispatch } from './game';
|
||||
|
||||
export * from 'store/game/game.interfaces';
|
||||
export * from './game/game.interfaces';
|
||||
|
||||
// Server
|
||||
export {
|
||||
|
|
@ -17,13 +17,13 @@ export {
|
|||
Selectors as ServerSelectors,
|
||||
Dispatch as ServerDispatch } from './server';
|
||||
|
||||
export * from 'store/server/server.interfaces';
|
||||
export * from './server/server.interfaces';
|
||||
|
||||
export {
|
||||
Types as RoomsTypes,
|
||||
Selectors as RoomsSelectors,
|
||||
Dispatch as RoomsDispatch } from 'store/rooms';
|
||||
Dispatch as RoomsDispatch } from './rooms';
|
||||
|
||||
export * from 'store/rooms/rooms.interfaces';
|
||||
export * from './rooms/rooms.interfaces';
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,13 @@
|
|||
import {
|
||||
Game,
|
||||
GameSortField,
|
||||
Message,
|
||||
ProtoInit,
|
||||
Room,
|
||||
SortDirection,
|
||||
UserSortField,
|
||||
} from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import { App, Data, Enriched } from '@app/types';
|
||||
import type { MessageInitShape } from '@bufbuild/protobuf';
|
||||
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb';
|
||||
import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb';
|
||||
import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb';
|
||||
import { RoomsState } from '../rooms.interfaces';
|
||||
|
||||
export function makeUser(overrides: ProtoInit<ServerInfo_User> = {}): ServerInfo_User {
|
||||
return create(ServerInfo_UserSchema, {
|
||||
export function makeUser(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_UserSchema> = {}
|
||||
): Data.ServerInfo_User {
|
||||
return create(Data.ServerInfo_UserSchema, {
|
||||
name: 'TestUser',
|
||||
accountageSecs: 0n,
|
||||
privlevel: '',
|
||||
|
|
@ -24,10 +16,10 @@ export function makeUser(overrides: ProtoInit<ServerInfo_User> = {}): ServerInfo
|
|||
});
|
||||
}
|
||||
|
||||
export function makeRoom(overrides: ProtoInit<Room> = {}): Room {
|
||||
export function makeRoom(overrides: Partial<Omit<Enriched.Room, '$typeName' | '$unknown'>> = {}): Enriched.Room {
|
||||
const { gametypeMap = {}, order = 0, gameList = [], ...protoOverrides } = overrides;
|
||||
return {
|
||||
...create(ServerInfo_RoomSchema, {
|
||||
...create(Data.ServerInfo_RoomSchema, {
|
||||
roomId: 1,
|
||||
name: 'Test Room',
|
||||
description: '',
|
||||
|
|
@ -45,10 +37,12 @@ export function makeRoom(overrides: ProtoInit<Room> = {}): Room {
|
|||
};
|
||||
}
|
||||
|
||||
export function makeGame(overrides: ProtoInit<Game & { startTime: number }> = {}): Game & { startTime: number } {
|
||||
export function makeGame(
|
||||
overrides: Partial<Omit<Enriched.Game & { startTime: number }, '$typeName' | '$unknown'>> = {},
|
||||
): Enriched.Game & { startTime: number } {
|
||||
const { gameType = '', startTime = 0, ...protoOverrides } = overrides;
|
||||
return {
|
||||
...create(ServerInfo_GameSchema, {
|
||||
...create(Data.ServerInfo_GameSchema, {
|
||||
gameId: 1,
|
||||
roomId: 1,
|
||||
description: 'Test Game',
|
||||
|
|
@ -61,7 +55,7 @@ export function makeGame(overrides: ProtoInit<Game & { startTime: number }> = {}
|
|||
};
|
||||
}
|
||||
|
||||
export function makeMessage(overrides: Partial<Message> = {}): Message {
|
||||
export function makeMessage(overrides: Partial<Enriched.Message> = {}): Enriched.Message {
|
||||
return {
|
||||
message: 'hello',
|
||||
messageType: 0,
|
||||
|
|
@ -80,12 +74,12 @@ export function makeRoomsState(overrides: Partial<RoomsState> = {}): RoomsState
|
|||
joinedGameIds: {},
|
||||
messages: {},
|
||||
sortGamesBy: {
|
||||
field: GameSortField.START_TIME,
|
||||
order: SortDirection.DESC,
|
||||
field: App.GameSortField.START_TIME,
|
||||
order: App.SortDirection.DESC,
|
||||
},
|
||||
sortUsersBy: {
|
||||
field: UserSortField.NAME,
|
||||
order: SortDirection.ASC,
|
||||
field: App.UserSortField.NAME,
|
||||
order: App.SortDirection.ASC,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Actions } from './rooms.actions';
|
||||
import { Types } from './rooms.types';
|
||||
import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures';
|
||||
import { GameSortField, SortDirection } from 'types';
|
||||
import { App } from '@app/types';
|
||||
|
||||
describe('Actions', () => {
|
||||
it('clearStore', () => {
|
||||
|
|
@ -42,11 +42,11 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('sortGames', () => {
|
||||
expect(Actions.sortGames(1, GameSortField.START_TIME, SortDirection.ASC)).toEqual({
|
||||
expect(Actions.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC)).toEqual({
|
||||
type: Types.SORT_GAMES,
|
||||
roomId: 1,
|
||||
field: GameSortField.START_TIME,
|
||||
order: SortDirection.ASC,
|
||||
field: App.GameSortField.START_TIME,
|
||||
order: App.SortDirection.ASC,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import { GameSortField, Message, SortDirection } from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
import { App, Data, Enriched } from '@app/types';
|
||||
|
||||
import { Types } from './rooms.types';
|
||||
|
||||
|
|
@ -10,12 +7,12 @@ export const Actions = {
|
|||
type: Types.CLEAR_STORE,
|
||||
}),
|
||||
|
||||
updateRooms: (rooms: ServerInfo_Room[]) => ({
|
||||
updateRooms: (rooms: Data.ServerInfo_Room[]) => ({
|
||||
type: Types.UPDATE_ROOMS,
|
||||
rooms,
|
||||
}),
|
||||
|
||||
joinRoom: (roomInfo: ServerInfo_Room) => ({
|
||||
joinRoom: (roomInfo: Data.ServerInfo_Room) => ({
|
||||
type: Types.JOIN_ROOM,
|
||||
roomInfo,
|
||||
}),
|
||||
|
|
@ -25,19 +22,19 @@ export const Actions = {
|
|||
roomId,
|
||||
}),
|
||||
|
||||
addMessage: (roomId: number, message: Message) => ({
|
||||
addMessage: (roomId: number, message: Enriched.Message) => ({
|
||||
type: Types.ADD_MESSAGE,
|
||||
roomId,
|
||||
message,
|
||||
}),
|
||||
|
||||
updateGames: (roomId: number, games: ServerInfo_Game[]) => ({
|
||||
updateGames: (roomId: number, games: Data.ServerInfo_Game[]) => ({
|
||||
type: Types.UPDATE_GAMES,
|
||||
roomId,
|
||||
games,
|
||||
}),
|
||||
|
||||
userJoined: (roomId: number, user: ServerInfo_User) => ({
|
||||
userJoined: (roomId: number, user: Data.ServerInfo_User) => ({
|
||||
type: Types.USER_JOINED,
|
||||
roomId,
|
||||
user,
|
||||
|
|
@ -49,7 +46,7 @@ export const Actions = {
|
|||
name,
|
||||
}),
|
||||
|
||||
sortGames: (roomId: number, field: GameSortField, order: SortDirection) => ({
|
||||
sortGames: (roomId: number, field: App.GameSortField, order: App.SortDirection) => ({
|
||||
type: Types.SORT_GAMES,
|
||||
roomId,
|
||||
field,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
vi.mock('store', () => ({ store: { dispatch: vi.fn() } }));
|
||||
vi.mock('..', () => ({ store: { dispatch: vi.fn() } }));
|
||||
|
||||
import { store } from 'store';
|
||||
import { store } from '..';
|
||||
import { Actions } from './rooms.actions';
|
||||
import { Dispatch } from './rooms.dispatch';
|
||||
import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures';
|
||||
import { GameSortField, SortDirection } from 'types';
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
import { App } from '@app/types';
|
||||
|
||||
describe('Dispatch', () => {
|
||||
it('clearStore dispatches Actions.clearStore()', () => {
|
||||
|
|
@ -63,9 +61,9 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('sortGames dispatches Actions.sortGames()', () => {
|
||||
Dispatch.sortGames(1, GameSortField.START_TIME, SortDirection.ASC);
|
||||
Dispatch.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(
|
||||
Actions.sortGames(1, GameSortField.START_TIME, SortDirection.ASC)
|
||||
Actions.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
import { GameSortField, Message, SortDirection } from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
import { App, Data, Enriched } from '@app/types';
|
||||
|
||||
import { Actions } from './rooms.actions';
|
||||
import { store } from 'store';
|
||||
import { store } from '..';
|
||||
|
||||
export const Dispatch = {
|
||||
clearStore: () => {
|
||||
store.dispatch(Actions.clearStore());
|
||||
},
|
||||
|
||||
updateRooms: (rooms: ServerInfo_Room[]) => {
|
||||
updateRooms: (rooms: Data.ServerInfo_Room[]) => {
|
||||
store.dispatch(Actions.updateRooms(rooms));
|
||||
},
|
||||
|
||||
joinRoom: (roomInfo: ServerInfo_Room) => {
|
||||
joinRoom: (roomInfo: Data.ServerInfo_Room) => {
|
||||
store.dispatch(Actions.joinRoom(roomInfo));
|
||||
|
||||
},
|
||||
|
|
@ -24,15 +21,15 @@ export const Dispatch = {
|
|||
store.dispatch(Actions.leaveRoom(roomId));
|
||||
},
|
||||
|
||||
addMessage: (roomId: number, message: Message) => {
|
||||
addMessage: (roomId: number, message: Enriched.Message) => {
|
||||
store.dispatch(Actions.addMessage(roomId, message));
|
||||
},
|
||||
|
||||
updateGames: (roomId: number, games: ServerInfo_Game[]) => {
|
||||
updateGames: (roomId: number, games: Data.ServerInfo_Game[]) => {
|
||||
store.dispatch(Actions.updateGames(roomId, games));
|
||||
},
|
||||
|
||||
userJoined: (roomId: number, user: ServerInfo_User) => {
|
||||
userJoined: (roomId: number, user: Data.ServerInfo_User) => {
|
||||
store.dispatch(Actions.userJoined(roomId, user));
|
||||
},
|
||||
|
||||
|
|
@ -40,7 +37,7 @@ export const Dispatch = {
|
|||
store.dispatch(Actions.userLeft(roomId, name));
|
||||
},
|
||||
|
||||
sortGames: (roomId: number, field: GameSortField, order: SortDirection) => {
|
||||
sortGames: (roomId: number, field: App.GameSortField, order: App.SortDirection) => {
|
||||
store.dispatch(Actions.sortGames(roomId, field, order));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GameSortField, Message, Room, Game, SortBy, UserSortField } from 'types';
|
||||
import { App, Enriched } from '@app/types';
|
||||
|
||||
export interface RoomsState {
|
||||
rooms: RoomsStateRooms;
|
||||
|
|
@ -11,12 +11,12 @@ export interface RoomsState {
|
|||
}
|
||||
|
||||
export interface RoomsStateRooms {
|
||||
[roomId: number]: Room;
|
||||
[roomId: number]: Enriched.Room;
|
||||
}
|
||||
|
||||
export interface RoomsStateGames {
|
||||
[roomId: number]: {
|
||||
[gameId: number]: Game;
|
||||
[gameId: number]: Enriched.Game;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -31,13 +31,13 @@ export interface JoinedGames {
|
|||
}
|
||||
|
||||
export interface RoomsStateMessages {
|
||||
[roomId: number]: Message[];
|
||||
[roomId: number]: Enriched.Message[];
|
||||
}
|
||||
|
||||
export interface RoomsStateSortGamesBy extends SortBy {
|
||||
field: GameSortField
|
||||
export interface RoomsStateSortGamesBy extends App.SortBy {
|
||||
field: App.GameSortField
|
||||
}
|
||||
|
||||
export interface RoomsStateSortUsersBy extends SortBy {
|
||||
field: UserSortField
|
||||
export interface RoomsStateSortUsersBy extends App.SortBy {
|
||||
field: App.UserSortField
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GameSortField, SortDirection } from 'types';
|
||||
import { App } from '@app/types';
|
||||
import { roomsReducer } from './rooms.reducer';
|
||||
import { Types, MAX_ROOM_MESSAGES } from './rooms.types';
|
||||
import { makeGame, makeMessage, makeRoom, makeRoomsState, makeUser } from './__mocks__/rooms-fixtures';
|
||||
|
|
@ -191,11 +191,10 @@ describe('UPDATE_GAMES', () => {
|
|||
expect(result.rooms[1].gameList.find(g => g.gameId === 2).description).toBe('new');
|
||||
});
|
||||
|
||||
it('returns { ...state } (not identity) when roomId is unknown', () => {
|
||||
it('returns state identity when roomId is unknown', () => {
|
||||
const state = makeRoomsState({ rooms: {} });
|
||||
const result = roomsReducer(state, { type: Types.UPDATE_GAMES, roomId: 999, games: [] });
|
||||
expect(result).not.toBe(state);
|
||||
expect(result.rooms).toEqual(state.rooms);
|
||||
expect(result).toBe(state);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -231,10 +230,10 @@ describe('SORT_GAMES', () => {
|
|||
const result = roomsReducer(state, {
|
||||
type: Types.SORT_GAMES,
|
||||
roomId: 1,
|
||||
field: GameSortField.START_TIME,
|
||||
order: SortDirection.ASC,
|
||||
field: App.GameSortField.START_TIME,
|
||||
order: App.SortDirection.ASC,
|
||||
});
|
||||
expect(result.sortGamesBy).toEqual({ field: GameSortField.START_TIME, order: SortDirection.ASC });
|
||||
expect(result.sortGamesBy).toEqual({ field: App.GameSortField.START_TIME, order: App.SortDirection.ASC });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as _ from 'lodash';
|
||||
|
||||
import { GameSortField, Room, UserSortField, SortDirection } from 'types';
|
||||
import { App, Enriched } from '@app/types';
|
||||
|
||||
import { normalizeGameObject, normalizeGametypeMap, normalizeRoomInfo, normalizeUserMessage, SortUtil } from '../common';
|
||||
|
||||
|
|
@ -15,12 +15,12 @@ const initialState: RoomsState = {
|
|||
joinedGameIds: {},
|
||||
messages: {},
|
||||
sortGamesBy: {
|
||||
field: GameSortField.START_TIME,
|
||||
order: SortDirection.DESC
|
||||
field: App.GameSortField.START_TIME,
|
||||
order: App.SortDirection.DESC
|
||||
},
|
||||
sortUsersBy: {
|
||||
field: UserSortField.NAME,
|
||||
order: SortDirection.ASC
|
||||
field: App.UserSortField.NAME,
|
||||
order: App.SortDirection.ASC
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -46,11 +46,11 @@ export const roomsReducer = (state = initialState, action: RoomsAction) => {
|
|||
const gametypeMap = normalizeGametypeMap(gametypeList);
|
||||
|
||||
rooms[roomId] = {
|
||||
...(existing as Room),
|
||||
...(existing as Enriched.Room),
|
||||
...roomMeta,
|
||||
gametypeMap,
|
||||
gameList: (existing as Room).gameList,
|
||||
userList: (existing as Room).userList,
|
||||
gameList: (existing as Enriched.Room).gameList,
|
||||
userList: (existing as Enriched.Room).userList,
|
||||
order,
|
||||
};
|
||||
});
|
||||
|
|
@ -149,8 +149,10 @@ export const roomsReducer = (state = initialState, action: RoomsAction) => {
|
|||
const { rooms, sortGamesBy } = state;
|
||||
const room = rooms[roomId];
|
||||
|
||||
if (!room) {
|
||||
return { ...state };
|
||||
// An empty gameList means no game updates — skip to avoid
|
||||
// overwriting the existing game list with an empty one.
|
||||
if (!room || !games?.length) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Normalize incoming raw proto games using the room's gametypeMap
|
||||
|
|
|
|||
|
|
@ -1,33 +1,13 @@
|
|||
import {
|
||||
Game,
|
||||
ProtoInit,
|
||||
SortDirection,
|
||||
StatusEnum,
|
||||
UserSortField,
|
||||
WebSocketConnectOptions,
|
||||
} from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb';
|
||||
import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb';
|
||||
import type { Response_WarnList } from 'generated/proto/response_warn_list_pb';
|
||||
import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb';
|
||||
import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb';
|
||||
import type { Response_DeckList } from 'generated/proto/response_deck_list_pb';
|
||||
import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
import { App, Data, Enriched } from '@app/types';
|
||||
import type { MessageInitShape } from '@bufbuild/protobuf';
|
||||
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb';
|
||||
import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb';
|
||||
import { ServerInfo_ReplayMatchSchema } from 'generated/proto/serverinfo_replay_match_pb';
|
||||
import { ServerInfo_ChatMessageSchema } from 'generated/proto/serverinfo_chat_message_pb';
|
||||
import { ServerInfo_BanSchema } from 'generated/proto/serverinfo_ban_pb';
|
||||
import { ServerInfo_WarningSchema } from 'generated/proto/serverinfo_warning_pb';
|
||||
import { Response_WarnListSchema } from 'generated/proto/response_warn_list_pb';
|
||||
import { ServerInfo_DeckStorage_TreeItemSchema, ServerInfo_DeckStorage_FolderSchema } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb';
|
||||
import { ServerState } from '../server.interfaces';
|
||||
|
||||
export function makeUser(overrides: ProtoInit<ServerInfo_User> = {}): ServerInfo_User {
|
||||
return create(ServerInfo_UserSchema, {
|
||||
export function makeUser(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_UserSchema> = {}
|
||||
): Data.ServerInfo_User {
|
||||
return create(Data.ServerInfo_UserSchema, {
|
||||
name: 'TestUser',
|
||||
accountageSecs: 0n,
|
||||
privlevel: '',
|
||||
|
|
@ -36,8 +16,10 @@ export function makeUser(overrides: ProtoInit<ServerInfo_User> = {}): ServerInfo
|
|||
});
|
||||
}
|
||||
|
||||
export function makeLogItem(overrides: ProtoInit<ServerInfo_ChatMessage> = {}): ServerInfo_ChatMessage {
|
||||
return create(ServerInfo_ChatMessageSchema, {
|
||||
export function makeLogItem(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_ChatMessageSchema> = {}
|
||||
): Data.ServerInfo_ChatMessage {
|
||||
return create(Data.ServerInfo_ChatMessageSchema, {
|
||||
message: '',
|
||||
senderId: '',
|
||||
senderIp: '',
|
||||
|
|
@ -50,8 +32,10 @@ export function makeLogItem(overrides: ProtoInit<ServerInfo_ChatMessage> = {}):
|
|||
});
|
||||
}
|
||||
|
||||
export function makeBanHistoryItem(overrides: ProtoInit<ServerInfo_Ban> = {}): ServerInfo_Ban {
|
||||
return create(ServerInfo_BanSchema, {
|
||||
export function makeBanHistoryItem(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_BanSchema> = {}
|
||||
): Data.ServerInfo_Ban {
|
||||
return create(Data.ServerInfo_BanSchema, {
|
||||
adminId: '',
|
||||
adminName: '',
|
||||
banTime: '',
|
||||
|
|
@ -62,8 +46,10 @@ export function makeBanHistoryItem(overrides: ProtoInit<ServerInfo_Ban> = {}): S
|
|||
});
|
||||
}
|
||||
|
||||
export function makeWarnHistoryItem(overrides: ProtoInit<ServerInfo_Warning> = {}): ServerInfo_Warning {
|
||||
return create(ServerInfo_WarningSchema, {
|
||||
export function makeWarnHistoryItem(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_WarningSchema> = {}
|
||||
): Data.ServerInfo_Warning {
|
||||
return create(Data.ServerInfo_WarningSchema, {
|
||||
userName: '',
|
||||
adminName: '',
|
||||
reason: '',
|
||||
|
|
@ -72,8 +58,10 @@ export function makeWarnHistoryItem(overrides: ProtoInit<ServerInfo_Warning> = {
|
|||
});
|
||||
}
|
||||
|
||||
export function makeWarnListItem(overrides: ProtoInit<Response_WarnList> = {}): Response_WarnList {
|
||||
return create(Response_WarnListSchema, {
|
||||
export function makeWarnListItem(
|
||||
overrides: MessageInitShape<typeof Data.Response_WarnListSchema> = {}
|
||||
): Data.Response_WarnList {
|
||||
return create(Data.Response_WarnListSchema, {
|
||||
warning: [],
|
||||
userName: '',
|
||||
userClientid: '',
|
||||
|
|
@ -81,23 +69,29 @@ export function makeWarnListItem(overrides: ProtoInit<Response_WarnList> = {}):
|
|||
});
|
||||
}
|
||||
|
||||
export function makeDeckTreeItem(overrides: ProtoInit<ServerInfo_DeckStorage_TreeItem> = {}): ServerInfo_DeckStorage_TreeItem {
|
||||
return create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
export function makeDeckTreeItem(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_DeckStorage_TreeItemSchema> = {},
|
||||
): Data.ServerInfo_DeckStorage_TreeItem {
|
||||
return create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 1,
|
||||
name: 'item',
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
export function makeDeckList(overrides: ProtoInit<Response_DeckList> = {}): Response_DeckList {
|
||||
return create(Response_DeckListSchema, {
|
||||
root: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }),
|
||||
export function makeDeckList(
|
||||
overrides: MessageInitShape<typeof Data.Response_DeckListSchema> = {}
|
||||
): Data.Response_DeckList {
|
||||
return create(Data.Response_DeckListSchema, {
|
||||
root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }),
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
export function makeReplayMatch(overrides: ProtoInit<ServerInfo_ReplayMatch> = {}): ServerInfo_ReplayMatch {
|
||||
return create(ServerInfo_ReplayMatchSchema, {
|
||||
export function makeReplayMatch(
|
||||
overrides: MessageInitShape<typeof Data.ServerInfo_ReplayMatchSchema> = {}
|
||||
): Data.ServerInfo_ReplayMatch {
|
||||
return create(Data.ServerInfo_ReplayMatchSchema, {
|
||||
gameId: 1,
|
||||
roomName: 'Test Room',
|
||||
timeStarted: 0,
|
||||
|
|
@ -110,16 +104,26 @@ export function makeReplayMatch(overrides: ProtoInit<ServerInfo_ReplayMatch> = {
|
|||
});
|
||||
}
|
||||
|
||||
export function makeGame(overrides: Partial<Game> = {}): Game {
|
||||
return { ...create(ServerInfo_GameSchema, { description: '' }), gameType: '', ...overrides };
|
||||
export function makeGame(overrides: Partial<Enriched.Game> = {}): Enriched.Game {
|
||||
return { ...create(Data.ServerInfo_GameSchema, { description: '' }), gameType: '', ...overrides };
|
||||
}
|
||||
|
||||
export function makeConnectOptions(overrides: Partial<WebSocketConnectOptions> = {}): WebSocketConnectOptions {
|
||||
export function makeLoginSuccessContext(
|
||||
overrides: Partial<Enriched.LoginSuccessContext> = {}
|
||||
): Enriched.LoginSuccessContext {
|
||||
return {
|
||||
hashedPassword: 'hash',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function makePendingActivationContext(
|
||||
overrides: Partial<Enriched.PendingActivationContext> = {}
|
||||
): Enriched.PendingActivationContext {
|
||||
return {
|
||||
host: 'localhost',
|
||||
port: '4747',
|
||||
userName: 'user',
|
||||
password: 'pass',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
|
@ -131,7 +135,7 @@ export function makeServerState(overrides: Partial<ServerState> = {}): ServerSta
|
|||
ignoreList: [],
|
||||
status: {
|
||||
connectionAttemptMade: false,
|
||||
state: StatusEnum.DISCONNECTED,
|
||||
state: App.StatusEnum.DISCONNECTED,
|
||||
description: null,
|
||||
},
|
||||
info: {
|
||||
|
|
@ -147,8 +151,8 @@ export function makeServerState(overrides: Partial<ServerState> = {}): ServerSta
|
|||
user: null,
|
||||
users: [],
|
||||
sortUsersBy: {
|
||||
field: UserSortField.NAME,
|
||||
order: SortDirection.ASC,
|
||||
field: App.UserSortField.NAME,
|
||||
order: App.SortDirection.ASC,
|
||||
},
|
||||
messages: {},
|
||||
userInfo: {},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import { Actions } from './server.actions';
|
||||
import { App, Data } from '@app/types';
|
||||
import { Types } from './server.types';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb';
|
||||
import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb';
|
||||
import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb';
|
||||
import {
|
||||
makeBanHistoryItem,
|
||||
makeConnectOptions,
|
||||
makeLoginSuccessContext,
|
||||
makePendingActivationContext,
|
||||
makeDeckList,
|
||||
makeDeckTreeItem,
|
||||
makeReplayMatch,
|
||||
makeGame,
|
||||
makeUser,
|
||||
makeWarnHistoryItem,
|
||||
makeWarnListItem,
|
||||
|
|
@ -30,7 +28,7 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('loginSuccessful', () => {
|
||||
const options = makeConnectOptions();
|
||||
const options = makeLoginSuccessContext();
|
||||
expect(Actions.loginSuccessful(options)).toEqual({ type: Types.LOGIN_SUCCESSFUL, options });
|
||||
});
|
||||
|
||||
|
|
@ -38,10 +36,6 @@ describe('Actions', () => {
|
|||
expect(Actions.loginFailed()).toEqual({ type: Types.LOGIN_FAILED });
|
||||
});
|
||||
|
||||
it('connectionClosed', () => {
|
||||
expect(Actions.connectionClosed(3)).toEqual({ type: Types.CONNECTION_CLOSED, reason: 3 });
|
||||
});
|
||||
|
||||
it('connectionFailed', () => {
|
||||
expect(Actions.connectionFailed()).toEqual({ type: Types.CONNECTION_FAILED });
|
||||
});
|
||||
|
|
@ -92,7 +86,7 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('updateStatus', () => {
|
||||
const status = { state: 2, description: 'connected' };
|
||||
const status = { state: App.StatusEnum.CONNECTED, description: 'connected' };
|
||||
expect(Actions.updateStatus(status)).toEqual({ type: Types.UPDATE_STATUS, status });
|
||||
});
|
||||
|
||||
|
|
@ -116,7 +110,7 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('viewLogs', () => {
|
||||
const logs = [{ targetType: 'room' }] as any[];
|
||||
const logs = [create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' })];
|
||||
expect(Actions.viewLogs(logs)).toEqual({ type: Types.VIEW_LOGS, logs });
|
||||
});
|
||||
|
||||
|
|
@ -153,7 +147,7 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('accountAwaitingActivation', () => {
|
||||
const options = makeConnectOptions();
|
||||
const options = makePendingActivationContext();
|
||||
expect(Actions.accountAwaitingActivation(options)).toEqual({ type: Types.ACCOUNT_AWAITING_ACTIVATION, options });
|
||||
});
|
||||
|
||||
|
|
@ -222,17 +216,17 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('notifyUser', () => {
|
||||
const notification = create(Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' });
|
||||
const notification = create(Data.Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' });
|
||||
expect(Actions.notifyUser(notification)).toEqual({ type: Types.NOTIFY_USER, notification });
|
||||
});
|
||||
|
||||
it('serverShutdown', () => {
|
||||
const data = create(Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 });
|
||||
const data = create(Data.Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 });
|
||||
expect(Actions.serverShutdown(data)).toEqual({ type: Types.SERVER_SHUTDOWN, data });
|
||||
});
|
||||
|
||||
it('userMessage', () => {
|
||||
const messageData = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' });
|
||||
const messageData = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' });
|
||||
expect(Actions.userMessage(messageData)).toEqual({ type: Types.USER_MESSAGE, messageData });
|
||||
});
|
||||
|
||||
|
|
@ -360,9 +354,8 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
it('gamesOfUser', () => {
|
||||
const games = [makeGame({ gameId: 1 })];
|
||||
const gametypeMap = { 1: 'Standard' };
|
||||
expect(Actions.gamesOfUser('alice', games, gametypeMap)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap });
|
||||
const response = create(Data.Response_GetGamesOfUserSchema, { roomList: [], gameList: [] });
|
||||
expect(Actions.gamesOfUser('alice', response)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', response });
|
||||
});
|
||||
|
||||
it('clearRegistrationErrors', () => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,4 @@
|
|||
import {
|
||||
GametypeMap, WebSocketConnectOptions
|
||||
} from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb';
|
||||
import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb';
|
||||
import type { Response_WarnList } from 'generated/proto/response_warn_list_pb';
|
||||
import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb';
|
||||
import type { Response_DeckList } from 'generated/proto/response_deck_list_pb';
|
||||
import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces';
|
||||
import { Data, Enriched } from '@app/types';
|
||||
import { ServerStateStatus } from './server.interfaces';
|
||||
import { Types } from './server.types';
|
||||
|
||||
|
|
@ -24,17 +12,13 @@ export const Actions = {
|
|||
connectionAttempted: () => ({
|
||||
type: Types.CONNECTION_ATTEMPTED
|
||||
}),
|
||||
loginSuccessful: (options: WebSocketConnectOptions) => ({
|
||||
loginSuccessful: (options: Enriched.LoginSuccessContext) => ({
|
||||
type: Types.LOGIN_SUCCESSFUL,
|
||||
options
|
||||
}),
|
||||
loginFailed: () => ({
|
||||
type: Types.LOGIN_FAILED,
|
||||
}),
|
||||
connectionClosed: (reason: number) => ({
|
||||
type: Types.CONNECTION_CLOSED,
|
||||
reason
|
||||
}),
|
||||
connectionFailed: () => ({
|
||||
type: Types.CONNECTION_FAILED,
|
||||
}),
|
||||
|
|
@ -48,11 +32,11 @@ export const Actions = {
|
|||
type: Types.SERVER_MESSAGE,
|
||||
message
|
||||
}),
|
||||
updateBuddyList: (buddyList: ServerInfo_User[]) => ({
|
||||
updateBuddyList: (buddyList: Data.ServerInfo_User[]) => ({
|
||||
type: Types.UPDATE_BUDDY_LIST,
|
||||
buddyList
|
||||
}),
|
||||
addToBuddyList: (user: ServerInfo_User) => ({
|
||||
addToBuddyList: (user: Data.ServerInfo_User) => ({
|
||||
type: Types.ADD_TO_BUDDY_LIST,
|
||||
user
|
||||
}),
|
||||
|
|
@ -60,11 +44,11 @@ export const Actions = {
|
|||
type: Types.REMOVE_FROM_BUDDY_LIST,
|
||||
userName
|
||||
}),
|
||||
updateIgnoreList: (ignoreList: ServerInfo_User[]) => ({
|
||||
updateIgnoreList: (ignoreList: Data.ServerInfo_User[]) => ({
|
||||
type: Types.UPDATE_IGNORE_LIST,
|
||||
ignoreList
|
||||
}),
|
||||
addToIgnoreList: (user: ServerInfo_User) => ({
|
||||
addToIgnoreList: (user: Data.ServerInfo_User) => ({
|
||||
type: Types.ADD_TO_IGNORE_LIST,
|
||||
user
|
||||
}),
|
||||
|
|
@ -76,19 +60,19 @@ export const Actions = {
|
|||
type: Types.UPDATE_INFO,
|
||||
info
|
||||
}),
|
||||
updateStatus: (status: ServerStateStatus) => ({
|
||||
updateStatus: (status: Pick<ServerStateStatus, 'state' | 'description'>) => ({
|
||||
type: Types.UPDATE_STATUS,
|
||||
status
|
||||
}),
|
||||
updateUser: (user: ServerInfo_User) => ({
|
||||
updateUser: (user: Data.ServerInfo_User) => ({
|
||||
type: Types.UPDATE_USER,
|
||||
user
|
||||
}),
|
||||
updateUsers: (users: ServerInfo_User[]) => ({
|
||||
updateUsers: (users: Data.ServerInfo_User[]) => ({
|
||||
type: Types.UPDATE_USERS,
|
||||
users
|
||||
}),
|
||||
userJoined: (user: ServerInfo_User) => ({
|
||||
userJoined: (user: Data.ServerInfo_User) => ({
|
||||
type: Types.USER_JOINED,
|
||||
user
|
||||
}),
|
||||
|
|
@ -96,7 +80,7 @@ export const Actions = {
|
|||
type: Types.USER_LEFT,
|
||||
name
|
||||
}),
|
||||
viewLogs: (logs: ServerInfo_ChatMessage[]) => ({
|
||||
viewLogs: (logs: Data.ServerInfo_ChatMessage[]) => ({
|
||||
type: Types.VIEW_LOGS,
|
||||
logs
|
||||
}),
|
||||
|
|
@ -129,7 +113,7 @@ export const Actions = {
|
|||
clearRegistrationErrors: () => ({
|
||||
type: Types.CLEAR_REGISTRATION_ERRORS,
|
||||
}),
|
||||
accountAwaitingActivation: (options: WebSocketConnectOptions) => ({
|
||||
accountAwaitingActivation: (options: Enriched.PendingActivationContext) => ({
|
||||
type: Types.ACCOUNT_AWAITING_ACTIVATION,
|
||||
options
|
||||
}),
|
||||
|
|
@ -169,27 +153,27 @@ export const Actions = {
|
|||
accountPasswordChange: () => ({
|
||||
type: Types.ACCOUNT_PASSWORD_CHANGE,
|
||||
}),
|
||||
accountEditChanged: (user: Partial<ServerInfo_User>) => ({
|
||||
accountEditChanged: (user: Partial<Data.ServerInfo_User>) => ({
|
||||
type: Types.ACCOUNT_EDIT_CHANGED,
|
||||
user,
|
||||
}),
|
||||
accountImageChanged: (user: Partial<ServerInfo_User>) => ({
|
||||
accountImageChanged: (user: Partial<Data.ServerInfo_User>) => ({
|
||||
type: Types.ACCOUNT_IMAGE_CHANGED,
|
||||
user,
|
||||
}),
|
||||
getUserInfo: (userInfo: ServerInfo_User) => ({
|
||||
getUserInfo: (userInfo: Data.ServerInfo_User) => ({
|
||||
type: Types.GET_USER_INFO,
|
||||
userInfo,
|
||||
}),
|
||||
notifyUser: (notification: NotifyUserData) => ({
|
||||
notifyUser: (notification: Data.Event_NotifyUser) => ({
|
||||
type: Types.NOTIFY_USER,
|
||||
notification,
|
||||
}),
|
||||
serverShutdown: (data: ServerShutdownData) => ({
|
||||
serverShutdown: (data: Data.Event_ServerShutdown) => ({
|
||||
type: Types.SERVER_SHUTDOWN,
|
||||
data,
|
||||
}),
|
||||
userMessage: (messageData: UserMessageData) => ({
|
||||
userMessage: (messageData: Data.Event_UserMessage) => ({
|
||||
type: Types.USER_MESSAGE,
|
||||
messageData,
|
||||
}),
|
||||
|
|
@ -207,17 +191,17 @@ export const Actions = {
|
|||
type: Types.BAN_FROM_SERVER,
|
||||
userName,
|
||||
}),
|
||||
banHistory: (userName: string, banHistory: ServerInfo_Ban[]) => ({
|
||||
banHistory: (userName: string, banHistory: Data.ServerInfo_Ban[]) => ({
|
||||
type: Types.BAN_HISTORY,
|
||||
userName,
|
||||
banHistory,
|
||||
}),
|
||||
warnHistory: (userName: string, warnHistory: ServerInfo_Warning[]) => ({
|
||||
warnHistory: (userName: string, warnHistory: Data.ServerInfo_Warning[]) => ({
|
||||
type: Types.WARN_HISTORY,
|
||||
userName,
|
||||
warnHistory,
|
||||
}),
|
||||
warnListOptions: (warnList: Response_WarnList[]) => ({
|
||||
warnListOptions: (warnList: Data.Response_WarnList[]) => ({
|
||||
type: Types.WARN_LIST_OPTIONS,
|
||||
warnList,
|
||||
}),
|
||||
|
|
@ -245,17 +229,17 @@ export const Actions = {
|
|||
userName,
|
||||
notes,
|
||||
}),
|
||||
replayList: (matchList: ServerInfo_ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }),
|
||||
replayAdded: (matchInfo: ServerInfo_ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }),
|
||||
replayList: (matchList: Data.ServerInfo_ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }),
|
||||
replayAdded: (matchInfo: Data.ServerInfo_ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }),
|
||||
replayModifyMatch: (gameId: number, doNotHide: boolean) => ({ type: Types.REPLAY_MODIFY_MATCH, gameId, doNotHide }),
|
||||
replayDeleteMatch: (gameId: number) => ({ type: Types.REPLAY_DELETE_MATCH, gameId }),
|
||||
backendDecks: (deckList: Response_DeckList) => ({ type: Types.BACKEND_DECKS, deckList }),
|
||||
backendDecks: (deckList: Data.Response_DeckList) => ({ type: Types.BACKEND_DECKS, deckList }),
|
||||
deckNewDir: (path: string, dirName: string) => ({ type: Types.DECK_NEW_DIR, path, dirName }),
|
||||
deckDelDir: (path: string) => ({ type: Types.DECK_DEL_DIR, path }),
|
||||
deckUpload: (path: string, treeItem: ServerInfo_DeckStorage_TreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }),
|
||||
deckUpload: (path: string, treeItem: Data.ServerInfo_DeckStorage_TreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }),
|
||||
deckDelete: (deckId: number) => ({ type: Types.DECK_DELETE, deckId }),
|
||||
gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) =>
|
||||
({ type: Types.GAMES_OF_USER, userName, games, gametypeMap }),
|
||||
gamesOfUser: (userName: string, response: Data.Response_GetGamesOfUser) =>
|
||||
({ type: Types.GAMES_OF_USER, userName, response }),
|
||||
}
|
||||
|
||||
export type ServerAction = ReturnType<typeof Actions[keyof typeof Actions]>;
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
vi.mock('store', () => ({ store: { dispatch: vi.fn() } }));
|
||||
vi.mock('..', () => ({ store: { dispatch: vi.fn() } }));
|
||||
|
||||
import { store } from 'store';
|
||||
import { store } from '..';
|
||||
import { Actions } from './server.actions';
|
||||
import { Dispatch } from './server.dispatch';
|
||||
import { App, Data } from '@app/types';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb';
|
||||
import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb';
|
||||
import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb';
|
||||
import {
|
||||
makeBanHistoryItem,
|
||||
makeConnectOptions,
|
||||
makeLoginSuccessContext,
|
||||
makePendingActivationContext,
|
||||
makeDeckList,
|
||||
makeDeckTreeItem,
|
||||
makeGame,
|
||||
makeReplayMatch,
|
||||
makeUser,
|
||||
makeWarnHistoryItem,
|
||||
makeWarnListItem,
|
||||
} from './__mocks__/server-fixtures';
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('Dispatch', () => {
|
||||
it('initialized dispatches Actions.initialized()', () => {
|
||||
Dispatch.initialized();
|
||||
|
|
@ -38,7 +34,7 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('loginSuccessful dispatches Actions.loginSuccessful()', () => {
|
||||
const options = makeConnectOptions();
|
||||
const options = makeLoginSuccessContext();
|
||||
Dispatch.loginSuccessful(options);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.loginSuccessful(options));
|
||||
});
|
||||
|
|
@ -48,11 +44,6 @@ describe('Dispatch', () => {
|
|||
expect(store.dispatch).toHaveBeenCalledWith(Actions.loginFailed());
|
||||
});
|
||||
|
||||
it('connectionClosed dispatches Actions.connectionClosed()', () => {
|
||||
Dispatch.connectionClosed(3);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionClosed(3));
|
||||
});
|
||||
|
||||
it('connectionFailed dispatches Actions.connectionFailed()', () => {
|
||||
Dispatch.connectionFailed();
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionFailed());
|
||||
|
|
@ -108,8 +99,8 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('updateStatus dispatches Actions.updateStatus({ state, description })', () => {
|
||||
Dispatch.updateStatus(2, 'ok');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: 2, description: 'ok' }));
|
||||
Dispatch.updateStatus(App.StatusEnum.CONNECTED, 'ok');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: App.StatusEnum.CONNECTED, description: 'ok' }));
|
||||
});
|
||||
|
||||
it('updateUser dispatches Actions.updateUser()', () => {
|
||||
|
|
@ -136,7 +127,7 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('viewLogs dispatches Actions.viewLogs()', () => {
|
||||
const logs = [{ targetType: 'room' }] as any[];
|
||||
const logs = [create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' })];
|
||||
Dispatch.viewLogs(logs);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.viewLogs(logs));
|
||||
});
|
||||
|
|
@ -187,7 +178,7 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('accountAwaitingActivation dispatches correctly', () => {
|
||||
const options = makeConnectOptions();
|
||||
const options = makePendingActivationContext();
|
||||
Dispatch.accountAwaitingActivation(options);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.accountAwaitingActivation(options));
|
||||
});
|
||||
|
|
@ -266,19 +257,19 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('notifyUser dispatches correctly', () => {
|
||||
const notification = create(Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' });
|
||||
const notification = create(Data.Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' });
|
||||
Dispatch.notifyUser(notification);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.notifyUser(notification));
|
||||
});
|
||||
|
||||
it('serverShutdown dispatches correctly', () => {
|
||||
const data = create(Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 });
|
||||
const data = create(Data.Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 });
|
||||
Dispatch.serverShutdown(data);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.serverShutdown(data));
|
||||
});
|
||||
|
||||
it('userMessage dispatches correctly', () => {
|
||||
const messageData = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' });
|
||||
const messageData = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' });
|
||||
Dispatch.userMessage(messageData);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.userMessage(messageData));
|
||||
});
|
||||
|
|
@ -391,10 +382,9 @@ describe('Dispatch', () => {
|
|||
});
|
||||
|
||||
it('gamesOfUser dispatches correctly', () => {
|
||||
const games = [makeGame({ gameId: 1 })];
|
||||
const gametypeMap = { 1: 'Standard' };
|
||||
Dispatch.gamesOfUser('alice', games, gametypeMap);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games, gametypeMap));
|
||||
const response = create(Data.Response_GetGamesOfUserSchema, { roomList: [], gameList: [] });
|
||||
Dispatch.gamesOfUser('alice', response);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', response));
|
||||
});
|
||||
|
||||
it('clearRegistrationErrors dispatches correctly', () => {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
import { Actions } from './server.actions';
|
||||
import { store } from 'store';
|
||||
import {
|
||||
GametypeMap, WebSocketConnectOptions
|
||||
} from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb';
|
||||
import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb';
|
||||
import type { Response_WarnList } from 'generated/proto/response_warn_list_pb';
|
||||
import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb';
|
||||
import type { Response_DeckList } from 'generated/proto/response_deck_list_pb';
|
||||
import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces';
|
||||
import { store } from '..';
|
||||
import { App, Data, Enriched } from '@app/types';
|
||||
|
||||
export const Dispatch = {
|
||||
initialized: () => {
|
||||
|
|
@ -24,15 +12,12 @@ export const Dispatch = {
|
|||
connectionAttempted: () => {
|
||||
store.dispatch(Actions.connectionAttempted());
|
||||
},
|
||||
loginSuccessful: (options: WebSocketConnectOptions) => {
|
||||
loginSuccessful: (options: Enriched.LoginSuccessContext) => {
|
||||
store.dispatch(Actions.loginSuccessful(options));
|
||||
},
|
||||
loginFailed: () => {
|
||||
store.dispatch(Actions.loginFailed());
|
||||
},
|
||||
connectionClosed: (reason: number) => {
|
||||
store.dispatch(Actions.connectionClosed(reason));
|
||||
},
|
||||
connectionFailed: () => {
|
||||
store.dispatch(Actions.connectionFailed());
|
||||
},
|
||||
|
|
@ -42,19 +27,19 @@ export const Dispatch = {
|
|||
testConnectionFailed: () => {
|
||||
store.dispatch(Actions.testConnectionFailed());
|
||||
},
|
||||
updateBuddyList: (buddyList: ServerInfo_User[]) => {
|
||||
updateBuddyList: (buddyList: Data.ServerInfo_User[]) => {
|
||||
store.dispatch(Actions.updateBuddyList(buddyList));
|
||||
},
|
||||
addToBuddyList: (user: ServerInfo_User) => {
|
||||
addToBuddyList: (user: Data.ServerInfo_User) => {
|
||||
store.dispatch(Actions.addToBuddyList(user));
|
||||
},
|
||||
removeFromBuddyList: (userName: string) => {
|
||||
store.dispatch(Actions.removeFromBuddyList(userName));
|
||||
},
|
||||
updateIgnoreList: (ignoreList: ServerInfo_User[]) => {
|
||||
updateIgnoreList: (ignoreList: Data.ServerInfo_User[]) => {
|
||||
store.dispatch(Actions.updateIgnoreList(ignoreList));
|
||||
},
|
||||
addToIgnoreList: (user: ServerInfo_User) => {
|
||||
addToIgnoreList: (user: Data.ServerInfo_User) => {
|
||||
store.dispatch(Actions.addToIgnoreList(user));
|
||||
},
|
||||
removeFromIgnoreList: (userName: string) => {
|
||||
|
|
@ -66,25 +51,25 @@ export const Dispatch = {
|
|||
version
|
||||
}));
|
||||
},
|
||||
updateStatus: (state: number, description: string) => {
|
||||
updateStatus: (state: App.StatusEnum, description: string) => {
|
||||
store.dispatch(Actions.updateStatus({
|
||||
state,
|
||||
description
|
||||
}));
|
||||
},
|
||||
updateUser: (user: ServerInfo_User) => {
|
||||
updateUser: (user: Data.ServerInfo_User) => {
|
||||
store.dispatch(Actions.updateUser(user));
|
||||
},
|
||||
updateUsers: (users: ServerInfo_User[]) => {
|
||||
updateUsers: (users: Data.ServerInfo_User[]) => {
|
||||
store.dispatch(Actions.updateUsers(users));
|
||||
},
|
||||
userJoined: (user: ServerInfo_User) => {
|
||||
userJoined: (user: Data.ServerInfo_User) => {
|
||||
store.dispatch(Actions.userJoined(user));
|
||||
},
|
||||
userLeft: (name: string) => {
|
||||
store.dispatch(Actions.userLeft(name));
|
||||
},
|
||||
viewLogs: (logs: ServerInfo_ChatMessage[]) => {
|
||||
viewLogs: (logs: Data.ServerInfo_ChatMessage[]) => {
|
||||
store.dispatch(Actions.viewLogs(logs));
|
||||
},
|
||||
clearLogs: () => {
|
||||
|
|
@ -114,7 +99,7 @@ export const Dispatch = {
|
|||
registrationUserNameError: (error: string) => {
|
||||
store.dispatch(Actions.registrationUserNameError(error));
|
||||
},
|
||||
accountAwaitingActivation: (options: WebSocketConnectOptions) => {
|
||||
accountAwaitingActivation: (options: Enriched.PendingActivationContext) => {
|
||||
store.dispatch(Actions.accountAwaitingActivation(options));
|
||||
},
|
||||
accountActivationSuccess: () => {
|
||||
|
|
@ -150,22 +135,22 @@ export const Dispatch = {
|
|||
accountPasswordChange: () => {
|
||||
store.dispatch(Actions.accountPasswordChange());
|
||||
},
|
||||
accountEditChanged: (user: Partial<ServerInfo_User>) => {
|
||||
accountEditChanged: (user: Partial<Data.ServerInfo_User>) => {
|
||||
store.dispatch(Actions.accountEditChanged(user));
|
||||
},
|
||||
accountImageChanged: (user: Partial<ServerInfo_User>) => {
|
||||
accountImageChanged: (user: Partial<Data.ServerInfo_User>) => {
|
||||
store.dispatch(Actions.accountImageChanged(user));
|
||||
},
|
||||
getUserInfo: (userInfo: ServerInfo_User) => {
|
||||
getUserInfo: (userInfo: Data.ServerInfo_User) => {
|
||||
store.dispatch(Actions.getUserInfo(userInfo));
|
||||
},
|
||||
notifyUser: (notification: NotifyUserData) => {
|
||||
notifyUser: (notification: Data.Event_NotifyUser) => {
|
||||
store.dispatch(Actions.notifyUser(notification))
|
||||
},
|
||||
serverShutdown: (data: ServerShutdownData) => {
|
||||
serverShutdown: (data: Data.Event_ServerShutdown) => {
|
||||
store.dispatch(Actions.serverShutdown(data))
|
||||
},
|
||||
userMessage: (messageData: UserMessageData) => {
|
||||
userMessage: (messageData: Data.Event_UserMessage) => {
|
||||
store.dispatch(Actions.userMessage(messageData))
|
||||
},
|
||||
addToList: (list: string, userName: string) => {
|
||||
|
|
@ -177,13 +162,13 @@ export const Dispatch = {
|
|||
banFromServer: (userName: string) => {
|
||||
store.dispatch(Actions.banFromServer(userName));
|
||||
},
|
||||
banHistory: (userName: string, banHistory: ServerInfo_Ban[]) => {
|
||||
banHistory: (userName: string, banHistory: Data.ServerInfo_Ban[]) => {
|
||||
store.dispatch(Actions.banHistory(userName, banHistory))
|
||||
},
|
||||
warnHistory: (userName: string, warnHistory: ServerInfo_Warning[]) => {
|
||||
warnHistory: (userName: string, warnHistory: Data.ServerInfo_Warning[]) => {
|
||||
store.dispatch(Actions.warnHistory(userName, warnHistory))
|
||||
},
|
||||
warnListOptions: (warnList: Response_WarnList[]) => {
|
||||
warnListOptions: (warnList: Data.Response_WarnList[]) => {
|
||||
store.dispatch(Actions.warnListOptions(warnList))
|
||||
},
|
||||
warnUser: (userName: string) => {
|
||||
|
|
@ -201,10 +186,10 @@ export const Dispatch = {
|
|||
updateAdminNotes: (userName: string, notes: string) => {
|
||||
store.dispatch(Actions.updateAdminNotes(userName, notes));
|
||||
},
|
||||
replayList: (matchList: ServerInfo_ReplayMatch[]) => {
|
||||
replayList: (matchList: Data.ServerInfo_ReplayMatch[]) => {
|
||||
store.dispatch(Actions.replayList(matchList));
|
||||
},
|
||||
replayAdded: (matchInfo: ServerInfo_ReplayMatch) => {
|
||||
replayAdded: (matchInfo: Data.ServerInfo_ReplayMatch) => {
|
||||
store.dispatch(Actions.replayAdded(matchInfo));
|
||||
},
|
||||
replayModifyMatch: (gameId: number, doNotHide: boolean) => {
|
||||
|
|
@ -213,7 +198,7 @@ export const Dispatch = {
|
|||
replayDeleteMatch: (gameId: number) => {
|
||||
store.dispatch(Actions.replayDeleteMatch(gameId));
|
||||
},
|
||||
backendDecks: (deckList: Response_DeckList) => {
|
||||
backendDecks: (deckList: Data.Response_DeckList) => {
|
||||
store.dispatch(Actions.backendDecks(deckList));
|
||||
},
|
||||
deckNewDir: (path: string, dirName: string) => {
|
||||
|
|
@ -222,13 +207,13 @@ export const Dispatch = {
|
|||
deckDelDir: (path: string) => {
|
||||
store.dispatch(Actions.deckDelDir(path));
|
||||
},
|
||||
deckUpload: (path: string, treeItem: ServerInfo_DeckStorage_TreeItem) => {
|
||||
deckUpload: (path: string, treeItem: Data.ServerInfo_DeckStorage_TreeItem) => {
|
||||
store.dispatch(Actions.deckUpload(path, treeItem));
|
||||
},
|
||||
deckDelete: (deckId: number) => {
|
||||
store.dispatch(Actions.deckDelete(deckId));
|
||||
},
|
||||
gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) => {
|
||||
store.dispatch(Actions.gamesOfUser(userName, games, gametypeMap));
|
||||
gamesOfUser: (userName: string, response: Data.Response_GetGamesOfUser) => {
|
||||
store.dispatch(Actions.gamesOfUser(userName, response));
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,57 @@
|
|||
import {
|
||||
Game, SortBy, UserSortField
|
||||
} from 'types';
|
||||
import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb';
|
||||
import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb';
|
||||
import type { Response_WarnList } from 'generated/proto/response_warn_list_pb';
|
||||
import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb';
|
||||
import type { Response_DeckList } from 'generated/proto/response_deck_list_pb';
|
||||
import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb';
|
||||
import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces';
|
||||
|
||||
export interface ServerConnectParams {
|
||||
host: string;
|
||||
port: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface ServerRegisterParams {
|
||||
host: string;
|
||||
port: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
email: string;
|
||||
country: string;
|
||||
realName: string;
|
||||
}
|
||||
|
||||
export interface RequestPasswordSaltParams {
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordParams {
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordChallengeParams extends ForgotPasswordParams {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordResetParams extends ForgotPasswordParams {
|
||||
token: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export interface AccountActivationParams extends ServerRegisterParams {
|
||||
token: string;
|
||||
}
|
||||
import { App, Data, Enriched } from '@app/types';
|
||||
|
||||
export interface ServerState {
|
||||
initialized: boolean;
|
||||
buddyList: ServerInfo_User[];
|
||||
ignoreList: ServerInfo_User[];
|
||||
buddyList: Data.ServerInfo_User[];
|
||||
ignoreList: Data.ServerInfo_User[];
|
||||
info: ServerStateInfo;
|
||||
status: ServerStateStatus;
|
||||
logs: ServerStateLogs;
|
||||
user: ServerInfo_User;
|
||||
users: ServerInfo_User[];
|
||||
user: Data.ServerInfo_User | null;
|
||||
users: Data.ServerInfo_User[];
|
||||
sortUsersBy: ServerStateSortUsersBy;
|
||||
messages: {
|
||||
[userName: string]: UserMessageData[];
|
||||
[userName: string]: Data.Event_UserMessage[];
|
||||
}
|
||||
userInfo: {
|
||||
[userName: string]: ServerInfo_User;
|
||||
[userName: string]: Data.ServerInfo_User;
|
||||
}
|
||||
notifications: NotifyUserData[];
|
||||
serverShutdown: ServerShutdownData;
|
||||
notifications: Data.Event_NotifyUser[];
|
||||
serverShutdown: Data.Event_ServerShutdown | null;
|
||||
banUser: string;
|
||||
banHistory: {
|
||||
[userName: string]: ServerInfo_Ban[];
|
||||
[userName: string]: Data.ServerInfo_Ban[];
|
||||
};
|
||||
warnHistory: {
|
||||
[userName: string]: ServerInfo_Warning[];
|
||||
[userName: string]: Data.ServerInfo_Warning[];
|
||||
};
|
||||
warnListOptions: Response_WarnList[];
|
||||
warnListOptions: Data.Response_WarnList[];
|
||||
warnUser: string;
|
||||
adminNotes: { [userName: string]: string };
|
||||
replays: ServerInfo_ReplayMatch[];
|
||||
backendDecks: Response_DeckList | null;
|
||||
gamesOfUser: { [userName: string]: Game[] };
|
||||
replays: Data.ServerInfo_ReplayMatch[];
|
||||
backendDecks: Data.Response_DeckList | null;
|
||||
gamesOfUser: { [userName: string]: Enriched.Game[] };
|
||||
registrationError: string | null;
|
||||
}
|
||||
|
||||
export interface ServerStateStatus {
|
||||
connectionAttemptMade: boolean;
|
||||
description: string;
|
||||
state: number;
|
||||
description: string | null;
|
||||
state: App.StatusEnum;
|
||||
}
|
||||
|
||||
export interface ServerStateInfo {
|
||||
message: string;
|
||||
name: string;
|
||||
version: string;
|
||||
message: string | null;
|
||||
name: string | null;
|
||||
version: string | null;
|
||||
}
|
||||
|
||||
export interface ServerStateLogs {
|
||||
room: ServerInfo_ChatMessage[];
|
||||
game: ServerInfo_ChatMessage[];
|
||||
chat: ServerInfo_ChatMessage[];
|
||||
room: Data.ServerInfo_ChatMessage[];
|
||||
game: Data.ServerInfo_ChatMessage[];
|
||||
chat: Data.ServerInfo_ChatMessage[];
|
||||
}
|
||||
|
||||
export interface ServerStateSortUsersBy extends SortBy {
|
||||
field: UserSortField
|
||||
export interface ServerStateSortUsersBy extends App.SortBy {
|
||||
field: App.UserSortField
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import { StatusEnum } from 'types';
|
||||
import { ServerInfo_User_UserLevelFlag as UserLevelFlag } from 'generated/proto/serverinfo_user_pb';
|
||||
import { App, Data } from '@app/types';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb';
|
||||
import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
import { serverReducer } from './server.reducer';
|
||||
import { Types } from './server.types';
|
||||
import {
|
||||
makeBanHistoryItem,
|
||||
makeConnectOptions,
|
||||
makePendingActivationContext,
|
||||
makeDeckList,
|
||||
makeDeckTreeItem,
|
||||
makeGame,
|
||||
|
|
@ -19,6 +16,8 @@ import {
|
|||
makeWarnListItem,
|
||||
} from './__mocks__/server-fixtures';
|
||||
|
||||
const UserLevelFlag = Data.ServerInfo_User_UserLevelFlag;
|
||||
|
||||
// ── Initialisation ───────────────────────────────────────────────────────────
|
||||
|
||||
describe('Initialisation', () => {
|
||||
|
|
@ -26,7 +25,7 @@ describe('Initialisation', () => {
|
|||
const result = serverReducer(undefined, { type: '@@INIT' });
|
||||
expect(result.initialized).toBe(false);
|
||||
expect(result.buddyList).toEqual([]);
|
||||
expect(result.status.state).toBe(StatusEnum.DISCONNECTED);
|
||||
expect(result.status.state).toBe(App.StatusEnum.DISCONNECTED);
|
||||
});
|
||||
|
||||
it('INITIALIZED → resets to initialState with initialized: true', () => {
|
||||
|
|
@ -38,7 +37,7 @@ describe('Initialisation', () => {
|
|||
});
|
||||
|
||||
it('CLEAR_STORE → resets to initialState but preserves status', () => {
|
||||
const status = { state: StatusEnum.LOGGED_IN, description: 'logged in' };
|
||||
const status = { state: App.StatusEnum.LOGGED_IN, description: 'logged in', connectionAttemptMade: true };
|
||||
const state = makeServerState({ status, banUser: 'someone' });
|
||||
const result = serverReducer(state, { type: Types.CLEAR_STORE });
|
||||
expect(result.banUser).toBe('');
|
||||
|
|
@ -57,13 +56,13 @@ describe('Initialisation', () => {
|
|||
|
||||
describe('Account & Connection', () => {
|
||||
it('CONNECTION_ATTEMPTED → sets connectionAttemptMade to true', () => {
|
||||
const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null } });
|
||||
const state = makeServerState({ status: { connectionAttemptMade: false, state: App.StatusEnum.DISCONNECTED, description: null } });
|
||||
const result = serverReducer(state, { type: Types.CONNECTION_ATTEMPTED });
|
||||
expect(result.status.connectionAttemptMade).toBe(true);
|
||||
});
|
||||
|
||||
it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => {
|
||||
const options = makeConnectOptions();
|
||||
const options = makePendingActivationContext();
|
||||
const state = makeServerState();
|
||||
const result = serverReducer(state, { type: Types.ACCOUNT_AWAITING_ACTIVATION, options });
|
||||
expect(result).toBe(state);
|
||||
|
|
@ -133,11 +132,13 @@ describe('Server Info & Status', () => {
|
|||
expect(result.info.message).toBe('hi');
|
||||
});
|
||||
|
||||
it('UPDATE_STATUS → replaces state.status entirely', () => {
|
||||
it('UPDATE_STATUS → merges state and description into status', () => {
|
||||
const state = makeServerState();
|
||||
const status = { state: StatusEnum.LOGGED_IN, description: 'ok' };
|
||||
const result = serverReducer(state, { type: Types.UPDATE_STATUS, status });
|
||||
expect(result.status).toEqual(status);
|
||||
const update = { state: App.StatusEnum.LOGGED_IN, description: 'ok' };
|
||||
const result = serverReducer(state, { type: Types.UPDATE_STATUS, status: update });
|
||||
expect(result.status.state).toBe(App.StatusEnum.LOGGED_IN);
|
||||
expect(result.status.description).toBe('ok');
|
||||
expect(result.status.connectionAttemptMade).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -281,12 +282,12 @@ describe('Messaging', () => {
|
|||
});
|
||||
|
||||
it('USER_MESSAGE → appends to existing messages for that user', () => {
|
||||
const existingMsg = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'first' });
|
||||
const existingMsg = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'first' });
|
||||
const state = makeServerState({
|
||||
user: makeUser({ name: 'Bob' }),
|
||||
messages: { Alice: [existingMsg] },
|
||||
});
|
||||
const newMsg = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'second' });
|
||||
const newMsg = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'second' });
|
||||
const result = serverReducer(state, { type: Types.USER_MESSAGE, messageData: newMsg });
|
||||
expect(result.messages['Alice']).toHaveLength(2);
|
||||
});
|
||||
|
|
@ -482,11 +483,11 @@ describe('Deck Storage', () => {
|
|||
});
|
||||
|
||||
it('DECK_UPLOAD with nested path → inserts into matching subfolder', () => {
|
||||
const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'myDecks', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'myDecks', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
});
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
});
|
||||
const item = makeDeckTreeItem({ name: 'new.cod' });
|
||||
const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: 'myDecks', treeItem: item });
|
||||
|
|
@ -512,18 +513,20 @@ describe('Deck Storage', () => {
|
|||
|
||||
it('DECK_DELETE → removes item by id from tree', () => {
|
||||
const item = makeDeckTreeItem({ id: 7 });
|
||||
const state = makeServerState({ backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [item] }) }) });
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [item] }) }),
|
||||
});
|
||||
const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 7 });
|
||||
expect(result.backendDecks.root.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('DECK_DELETE → recursively removes item nested inside a subfolder', () => {
|
||||
const nested = makeDeckTreeItem({ id: 9, name: 'nested.cod' });
|
||||
const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'sub', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [nested] })
|
||||
const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'sub', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [nested] })
|
||||
});
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
});
|
||||
const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 9 });
|
||||
expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0);
|
||||
|
|
@ -544,11 +547,11 @@ describe('Deck Storage', () => {
|
|||
});
|
||||
|
||||
it('DECK_NEW_DIR nested → inserts folder inside matching subfolder', () => {
|
||||
const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'parent', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'parent', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
});
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
});
|
||||
const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: 'parent', dirName: 'child' });
|
||||
const parent = result.backendDecks.root.items.find(i => i.name === 'parent');
|
||||
|
|
@ -563,36 +566,36 @@ describe('Deck Storage', () => {
|
|||
});
|
||||
|
||||
it('DECK_DEL_DIR → removes folder from root by name', () => {
|
||||
const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'myDir', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'myDir', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
});
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
});
|
||||
const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'myDir' });
|
||||
expect(result.backendDecks.root.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('DECK_DEL_DIR → returns deck tree unchanged when path is empty', () => {
|
||||
const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'keep', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'keep', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
});
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) })
|
||||
});
|
||||
const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: '' });
|
||||
expect(result.backendDecks.root.items).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('DECK_DEL_DIR → recursively removes nested subfolder via multi-segment path', () => {
|
||||
const child = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'child', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
const child = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'child', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
});
|
||||
const parent = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'parent', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [child] })
|
||||
const parent = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: 'parent', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [child] })
|
||||
});
|
||||
const state = makeServerState({
|
||||
backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [parent] }) })
|
||||
backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [parent] }) })
|
||||
});
|
||||
const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'parent/child' });
|
||||
expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0);
|
||||
|
|
@ -603,24 +606,31 @@ describe('Deck Storage', () => {
|
|||
|
||||
describe('GAMES_OF_USER', () => {
|
||||
it('stores normalized games keyed by userName', () => {
|
||||
const games = [makeGame({ gameId: 5 })];
|
||||
const response = create(Data.Response_GetGamesOfUserSchema, {
|
||||
gameList: [create(Data.ServerInfo_GameSchema, { gameId: 5, description: '' })],
|
||||
roomList: [],
|
||||
});
|
||||
const state = makeServerState();
|
||||
const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap: {} });
|
||||
expect(result.gamesOfUser['alice']).toEqual(games);
|
||||
const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', response });
|
||||
expect(result.gamesOfUser['alice']).toEqual([makeGame({ gameId: 5 })]);
|
||||
});
|
||||
|
||||
it('overwrites previous games for same user', () => {
|
||||
const old = [makeGame({ gameId: 1 })];
|
||||
const fresh = [makeGame({ gameId: 2 })];
|
||||
const response = create(Data.Response_GetGamesOfUserSchema, {
|
||||
gameList: [create(Data.ServerInfo_GameSchema, { gameId: 2, description: '' })],
|
||||
roomList: [],
|
||||
});
|
||||
const state = makeServerState({ gamesOfUser: { alice: old } });
|
||||
const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: fresh, gametypeMap: {} });
|
||||
expect(result.gamesOfUser['alice']).toEqual(fresh);
|
||||
const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', response });
|
||||
expect(result.gamesOfUser['alice']).toEqual([makeGame({ gameId: 2 })]);
|
||||
});
|
||||
|
||||
it('does not affect other users\' entries', () => {
|
||||
const bobGames = [makeGame({ gameId: 3 })];
|
||||
const response = create(Data.Response_GetGamesOfUserSchema, { gameList: [], roomList: [] });
|
||||
const state = makeServerState({ gamesOfUser: { bob: bobGames } });
|
||||
const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: [], gametypeMap: {} });
|
||||
const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', response });
|
||||
expect(result.gamesOfUser['bob']).toBe(bobGames);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { SortDirection, StatusEnum, UserSortField } from 'types';
|
||||
import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb';
|
||||
import type { ServerInfo_DeckStorage_Folder, ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
import { App, Data } from '@app/types';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb';
|
||||
import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb';
|
||||
|
||||
import { normalizeBannedUserError, normalizeGameObject, normalizeLogs, SortUtil } from '../common';
|
||||
import { normalizeBannedUserError, normalizeGameObject, normalizeGametypeMap, normalizeLogs, SortUtil } from '../common';
|
||||
|
||||
import { ServerAction } from './server.actions';
|
||||
import { ServerState } from './server.interfaces'
|
||||
|
|
@ -16,17 +12,17 @@ function splitPath(path: string): string[] {
|
|||
}
|
||||
|
||||
function insertAtPath(
|
||||
folder: ServerInfo_DeckStorage_Folder,
|
||||
folder: Data.ServerInfo_DeckStorage_Folder,
|
||||
pathSegments: string[],
|
||||
item: ServerInfo_DeckStorage_TreeItem,
|
||||
): ServerInfo_DeckStorage_Folder {
|
||||
item: Data.ServerInfo_DeckStorage_TreeItem,
|
||||
): Data.ServerInfo_DeckStorage_Folder {
|
||||
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) {
|
||||
return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, item] });
|
||||
return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, item] });
|
||||
}
|
||||
const [head, ...tail] = pathSegments;
|
||||
const match = folder.items.find(child => child.name === head && child.folder);
|
||||
if (match) {
|
||||
return create(ServerInfo_DeckStorage_FolderSchema, {
|
||||
return create(Data.ServerInfo_DeckStorage_FolderSchema, {
|
||||
items: folder.items.map(child =>
|
||||
child === match
|
||||
? { ...child, folder: insertAtPath(child.folder!, tail, item) }
|
||||
|
|
@ -34,14 +30,14 @@ function insertAtPath(
|
|||
),
|
||||
});
|
||||
}
|
||||
const created: ServerInfo_DeckStorage_TreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: head, folder: insertAtPath(create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), tail, item)
|
||||
const created: Data.ServerInfo_DeckStorage_TreeItem = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: head, folder: insertAtPath(create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }), tail, item)
|
||||
});
|
||||
return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, created] });
|
||||
return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, created] });
|
||||
}
|
||||
|
||||
function removeById(folder: ServerInfo_DeckStorage_Folder, id: number): ServerInfo_DeckStorage_Folder {
|
||||
return create(ServerInfo_DeckStorage_FolderSchema, {
|
||||
function removeById(folder: Data.ServerInfo_DeckStorage_Folder, id: number): Data.ServerInfo_DeckStorage_Folder {
|
||||
return create(Data.ServerInfo_DeckStorage_FolderSchema, {
|
||||
items: folder.items
|
||||
.filter(item => item.id !== id)
|
||||
.map(item =>
|
||||
|
|
@ -50,17 +46,17 @@ function removeById(folder: ServerInfo_DeckStorage_Folder, id: number): ServerIn
|
|||
});
|
||||
}
|
||||
|
||||
function removeByPath(folder: ServerInfo_DeckStorage_Folder, pathSegments: string[]): ServerInfo_DeckStorage_Folder {
|
||||
function removeByPath(folder: Data.ServerInfo_DeckStorage_Folder, pathSegments: string[]): Data.ServerInfo_DeckStorage_Folder {
|
||||
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) {
|
||||
return folder;
|
||||
}
|
||||
const [head, ...tail] = pathSegments;
|
||||
if (tail.length === 0) {
|
||||
return create(ServerInfo_DeckStorage_FolderSchema, {
|
||||
return create(Data.ServerInfo_DeckStorage_FolderSchema, {
|
||||
items: folder.items.filter(item => !(item.name === head && item.folder != null))
|
||||
});
|
||||
}
|
||||
return create(ServerInfo_DeckStorage_FolderSchema, {
|
||||
return create(Data.ServerInfo_DeckStorage_FolderSchema, {
|
||||
items: folder.items.map(item =>
|
||||
item.name === head && item.folder
|
||||
? { ...item, folder: removeByPath(item.folder, tail) }
|
||||
|
|
@ -76,7 +72,7 @@ const initialState: ServerState = {
|
|||
|
||||
status: {
|
||||
connectionAttemptMade: false,
|
||||
state: StatusEnum.DISCONNECTED,
|
||||
state: App.StatusEnum.DISCONNECTED,
|
||||
description: null
|
||||
},
|
||||
info: {
|
||||
|
|
@ -92,8 +88,8 @@ const initialState: ServerState = {
|
|||
user: null,
|
||||
users: [],
|
||||
sortUsersBy: {
|
||||
field: UserSortField.NAME,
|
||||
order: SortDirection.ASC
|
||||
field: App.UserSortField.NAME,
|
||||
order: App.SortDirection.ASC
|
||||
},
|
||||
messages: {},
|
||||
userInfo: {},
|
||||
|
|
@ -232,11 +228,19 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
}
|
||||
case Types.UPDATE_STATUS: {
|
||||
const { status } = action;
|
||||
|
||||
return {
|
||||
const newState = {
|
||||
...state,
|
||||
status: { ...status }
|
||||
status: { ...state.status, ...status }
|
||||
};
|
||||
|
||||
if (status.state === App.StatusEnum.DISCONNECTED) {
|
||||
return {
|
||||
...newState,
|
||||
status: { ...newState.status, connectionAttemptMade: false }
|
||||
};
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
case Types.UPDATE_USER:
|
||||
case Types.ACCOUNT_EDIT_CHANGED:
|
||||
|
|
@ -417,11 +421,11 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
}
|
||||
let newLevel = user.userLevel;
|
||||
newLevel = shouldBeMod
|
||||
? (newLevel | ServerInfo_User_UserLevelFlag.IsModerator)
|
||||
: (newLevel & ~ServerInfo_User_UserLevelFlag.IsModerator);
|
||||
? (newLevel | Data.ServerInfo_User_UserLevelFlag.IsModerator)
|
||||
: (newLevel & ~Data.ServerInfo_User_UserLevelFlag.IsModerator);
|
||||
newLevel = shouldBeJudge
|
||||
? (newLevel | ServerInfo_User_UserLevelFlag.IsJudge)
|
||||
: (newLevel & ~ServerInfo_User_UserLevelFlag.IsJudge);
|
||||
? (newLevel | Data.ServerInfo_User_UserLevelFlag.IsJudge)
|
||||
: (newLevel & ~Data.ServerInfo_User_UserLevelFlag.IsJudge);
|
||||
return {
|
||||
...user,
|
||||
userLevel: newLevel,
|
||||
|
|
@ -455,7 +459,7 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
}
|
||||
return {
|
||||
...state,
|
||||
backendDecks: create(Response_DeckListSchema, {
|
||||
backendDecks: create(Data.Response_DeckListSchema, {
|
||||
root: insertAtPath(state.backendDecks.root, splitPath(action.path), action.treeItem),
|
||||
}),
|
||||
};
|
||||
|
|
@ -466,7 +470,7 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
}
|
||||
return {
|
||||
...state,
|
||||
backendDecks: create(Response_DeckListSchema, {
|
||||
backendDecks: create(Data.Response_DeckListSchema, {
|
||||
root: removeById(state.backendDecks.root, action.deckId),
|
||||
}),
|
||||
};
|
||||
|
|
@ -475,12 +479,12 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
if (!state.backendDecks?.root) {
|
||||
return state;
|
||||
}
|
||||
const newFolder: ServerInfo_DeckStorage_TreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: action.dirName, folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
const newFolder: Data.ServerInfo_DeckStorage_TreeItem = create(Data.ServerInfo_DeckStorage_TreeItemSchema, {
|
||||
id: 0, name: action.dirName, folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] })
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
backendDecks: create(Response_DeckListSchema, {
|
||||
backendDecks: create(Data.Response_DeckListSchema, {
|
||||
root: insertAtPath(state.backendDecks.root, splitPath(action.path), newFolder),
|
||||
}),
|
||||
};
|
||||
|
|
@ -491,14 +495,17 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
}
|
||||
return {
|
||||
...state,
|
||||
backendDecks: create(Response_DeckListSchema, {
|
||||
backendDecks: create(Data.Response_DeckListSchema, {
|
||||
root: removeByPath(state.backendDecks.root, splitPath(action.path)),
|
||||
}),
|
||||
};
|
||||
}
|
||||
case Types.GAMES_OF_USER: {
|
||||
const { userName, games, gametypeMap } = action;
|
||||
const normalizedGames = games.map(g => normalizeGameObject(g, gametypeMap));
|
||||
const { userName, response } = action;
|
||||
const gametypeMap = normalizeGametypeMap(
|
||||
(response.roomList ?? []).flatMap(room => room.gametypeList ?? [])
|
||||
);
|
||||
const normalizedGames = (response.gameList ?? []).map(g => normalizeGameObject(g, gametypeMap));
|
||||
return {
|
||||
...state,
|
||||
gamesOfUser: {
|
||||
|
|
@ -518,7 +525,6 @@ export const serverReducer = (state = initialState, action: ServerAction) => {
|
|||
// Signal-only action types — no state mutation, explicit for discriminated-union exhaustiveness
|
||||
case Types.LOGIN_SUCCESSFUL:
|
||||
case Types.LOGIN_FAILED:
|
||||
case Types.CONNECTION_CLOSED:
|
||||
case Types.CONNECTION_FAILED:
|
||||
case Types.TEST_CONNECTION_SUCCESSFUL:
|
||||
case Types.TEST_CONNECTION_FAILED:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
makeServerState,
|
||||
makeUser,
|
||||
} from './__mocks__/server-fixtures';
|
||||
import { StatusEnum } from 'types';
|
||||
import { App } from '@app/types';
|
||||
|
||||
function rootState(server: ServerState) {
|
||||
return { server };
|
||||
|
|
@ -34,17 +34,17 @@ describe('Selectors', () => {
|
|||
});
|
||||
|
||||
it('getDescription → returns status.description', () => {
|
||||
const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.CONNECTED, description: 'ok' } });
|
||||
const state = makeServerState({ status: { connectionAttemptMade: false, state: App.StatusEnum.CONNECTED, description: 'ok' } });
|
||||
expect(Selectors.getDescription(rootState(state))).toBe('ok');
|
||||
});
|
||||
|
||||
it('getState → returns status.state', () => {
|
||||
const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.LOGGED_IN, description: null } });
|
||||
expect(Selectors.getState(rootState(state))).toBe(StatusEnum.LOGGED_IN);
|
||||
const state = makeServerState({ status: { connectionAttemptMade: false, state: App.StatusEnum.LOGGED_IN, description: null } });
|
||||
expect(Selectors.getState(rootState(state))).toBe(App.StatusEnum.LOGGED_IN);
|
||||
});
|
||||
|
||||
it('getConnectionAttemptMade → returns status.connectionAttemptMade', () => {
|
||||
const state = makeServerState({ status: { connectionAttemptMade: true, state: StatusEnum.DISCONNECTED, description: null } });
|
||||
const state = makeServerState({ status: { connectionAttemptMade: true, state: App.StatusEnum.DISCONNECTED, description: null } });
|
||||
expect(Selectors.getConnectionAttemptMade(rootState(state))).toBe(true);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ export const Types = {
|
|||
CONNECTION_ATTEMPTED: '[Server] Connection Attempted',
|
||||
LOGIN_SUCCESSFUL: '[Server] Login Successful',
|
||||
LOGIN_FAILED: '[Server] Login Failed',
|
||||
CONNECTION_CLOSED: '[Server] Connection Closed',
|
||||
CONNECTION_FAILED: '[Server] Connection Failed',
|
||||
TEST_CONNECTION_SUCCESSFUL: '[Server] Test Connection Successful',
|
||||
TEST_CONNECTION_FAILED: '[Server] Test Connection Failed',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue