This commit is contained in:
seavor 2026-04-18 01:36:37 -05:00
parent d04aa83258
commit dcd6dc00f4
83 changed files with 1797 additions and 390 deletions

View file

@ -1 +1,4 @@
export { withMockLocation, withEventRegistry } from './globalGuards';
export { renderWithProviders } from './renderWithProviders';
export { createMockWebClient } from './mockWebClient';
export { disconnectedState, connectedState, connectedWithRoomsState, makeUser } from './storeFixtures';

View file

@ -0,0 +1,56 @@
import type { WebClient } from '@app/websocket';
/**
* Creates a mock WebClient whose `request` property has vi.fn() stubs
* for every service method that containers/forms call. Inject this into
* tests via `renderWithProviders({ webClient: createMockWebClient() })`.
*/
export function createMockWebClient() {
return {
request: {
authentication: {
login: vi.fn(),
register: vi.fn(),
disconnect: vi.fn(),
activateAccount: vi.fn(),
resetPasswordRequest: vi.fn(),
resetPasswordChallenge: vi.fn(),
resetPassword: vi.fn(),
},
session: {
addToBuddyList: vi.fn(),
removeFromBuddyList: vi.fn(),
addToIgnoreList: vi.fn(),
removeFromIgnoreList: vi.fn(),
getUserInfo: vi.fn(),
accountEdit: vi.fn(),
accountPassword: vi.fn(),
accountImage: vi.fn(),
listUsers: vi.fn(),
},
rooms: {
joinRoom: vi.fn(),
leaveRoom: vi.fn(),
roomSay: vi.fn(),
createGame: vi.fn(),
},
game: {
joinGame: vi.fn(),
leaveGame: vi.fn(),
},
admin: {
adjustMod: vi.fn(),
reloadConfig: vi.fn(),
shutdownServer: vi.fn(),
updateServerMessage: vi.fn(),
},
moderator: {
viewLogHistory: vi.fn(),
banFromServer: vi.fn(),
warnUser: vi.fn(),
warnHistory: vi.fn(),
banHistory: vi.fn(),
},
},
} as unknown as WebClient;
}

View file

@ -0,0 +1,71 @@
import { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { configureStore, EnhancedStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { gamesReducer } from '../store/game';
import { roomsReducer } from '../store/rooms';
import { serverReducer } from '../store/server';
import { actionReducer } from '../store/actions';
import { ToastProvider } from '../components/Toast/ToastContext';
import type { RootState } from '../store/store';
// Minimal i18n instance for tests — returns keys as-is.
const testI18n = i18n.createInstance();
testI18n.use(initReactI18next).init({
lng: 'en',
resources: {},
fallbackLng: 'en',
interpolation: { escapeValue: false },
});
function createTestStore(preloadedState?: Partial<RootState>) {
return configureStore({
reducer: {
games: gamesReducer,
rooms: roomsReducer,
server: serverReducer,
action: actionReducer,
},
preloadedState: preloadedState as any,
});
}
interface ExtendedRenderOptions extends Omit<RenderOptions, 'wrapper'> {
preloadedState?: Partial<RootState>;
store?: EnhancedStore;
route?: string;
}
export function renderWithProviders(
ui: ReactElement,
{
preloadedState,
store = createTestStore(preloadedState),
route = '/',
...renderOptions
}: ExtendedRenderOptions = {},
) {
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<Provider store={store}>
<I18nextProvider i18n={testI18n}>
<ToastProvider>
<MemoryRouter initialEntries={[route]}>
{children}
</MemoryRouter>
</ToastProvider>
</I18nextProvider>
</Provider>
);
}
return {
store,
...render(ui, { wrapper: Wrapper, ...renderOptions }),
};
}

View file

@ -0,0 +1,123 @@
import { App, Data, Enriched } from '@app/types';
import type { RootState } from '../store/store';
/**
* Create a minimal ServerInfo_User object for testing.
*/
function makeUser(overrides: Partial<Data.ServerInfo_User> = {}): Data.ServerInfo_User {
return {
name: 'testUser',
realName: '',
country: 'us',
userLevel: 0,
avatarBmp: new Uint8Array(),
accountageSecs: BigInt(0),
$typeName: 'ServerInfo_User' as any,
$unknown: undefined,
gender: 0,
...overrides,
} as Data.ServerInfo_User;
}
/**
* A disconnected (default) store state. This is the state before any
* connection to a server has been made.
*/
export const disconnectedState: Partial<RootState> = {
server: {
initialized: false,
buddyList: {},
ignoreList: {},
status: {
connectionAttemptMade: false,
state: Enriched.StatusEnum.DISCONNECTED,
description: null,
},
info: { message: null, name: null, version: null },
logs: { room: [], game: [], chat: [] },
user: null,
users: {},
sortUsersBy: { field: App.UserSortField.NAME, order: App.SortDirection.ASC },
messages: {},
userInfo: {},
notifications: [],
serverShutdown: null,
banUser: '',
banHistory: {},
warnHistory: {},
warnListOptions: [],
warnUser: '',
adminNotes: {},
replays: {},
backendDecks: null,
downloadedDeck: null,
downloadedReplay: null,
gamesOfUser: {},
registrationError: null,
},
rooms: {
rooms: {},
joinedRoomIds: {},
joinedGameIds: {},
messages: {},
sortGamesBy: { field: App.GameSortField.START_TIME, order: App.SortDirection.DESC },
sortUsersBy: { field: App.UserSortField.NAME, order: App.SortDirection.ASC },
},
games: { games: {} },
action: { type: null, payload: null, meta: null, error: false, count: 0 },
};
/**
* A connected (logged-in) store state with a basic user and server info.
*/
export const connectedState: Partial<RootState> = {
...disconnectedState,
server: {
...(disconnectedState.server as any),
initialized: true,
status: {
connectionAttemptMade: true,
state: Enriched.StatusEnum.LOGGED_IN,
description: null,
},
info: {
message: '<b>Welcome</b>',
name: 'Test Server',
version: '1.0.0',
},
user: makeUser(),
users: {
testUser: makeUser(),
},
},
};
/**
* Connected state with rooms and a joined room containing games and users.
*/
export const connectedWithRoomsState: Partial<RootState> = {
...connectedState,
server: {
...(connectedState.server as any),
users: {
testUser: makeUser(),
otherUser: makeUser({ name: 'otherUser' }),
},
},
rooms: {
...(disconnectedState.rooms as any),
rooms: {
1: {
info: { roomId: 1, name: 'Main Room', description: 'The main room', autoJoin: true, permissionLevel: 0 },
gameList: [],
userList: [makeUser(), makeUser({ name: 'otherUser' })],
},
},
joinedRoomIds: { 1: true },
messages: {
1: [],
},
},
};
export { makeUser };