cleanup testing utilities, documentation, and AI commentary

This commit is contained in:
seavor 2026-04-18 15:32:50 -05:00
parent bd2382c94e
commit ef6cea6f6c
150 changed files with 891 additions and 1233 deletions

View file

@ -25,14 +25,14 @@ const flushStoresAndEffects = async (): Promise<void> => {
});
};
import { autoLoginSession } from '../../../src/hooks/useAutoLogin';
import { autoLoginGate } from '../../../src/hooks/useAutoLogin';
import { settingsStore } from '../../../src/hooks/useSettings';
import { knownHostsStore } from '../../../src/hooks/useKnownHosts';
import Login from '../../../src/containers/Login/Login';
import { HostDTO, SettingDTO } from '@app/services';
import { App } from '@app/types';
import { ServerSelectors, ServerDispatch } from '@app/store';
import { StatusEnum } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { resetDexie } from '../services/dexie/resetDexie';
import { renderAppScreen, store } from './helpers';
@ -41,7 +41,7 @@ import { renderAppScreen, store } from './helpers';
// dispatching updateStatus(DISCONNECTED) is what the real reducer uses to
// clear connectionAttemptMade (clearStore intentionally preserves status).
const simulateLogout = () => {
ServerDispatch.updateStatus(StatusEnum.DISCONNECTED, null);
ServerDispatch.updateStatus(WebsocketTypes.StatusEnum.DISCONNECTED, null);
};
const seedAutoConnect = async () => {
@ -86,7 +86,7 @@ beforeEach(async () => {
// cached values).
settingsStore.reset();
knownHostsStore.reset();
autoLoginSession.startupCheckRan = false;
autoLoginGate.hasChecked = false;
});
describe('autoconnect — cold start', () => {
@ -182,7 +182,7 @@ describe('autoconnect — refresh', () => {
// Simulate a browser refresh: the session gate naturally resets on a
// fresh JS context, and the real connection flag resets too.
simulateLogout();
autoLoginSession.startupCheckRan = false;
autoLoginGate.hasChecked = false;
renderAppScreen(<Login />);
await waitFor(() => {

View file

@ -19,13 +19,8 @@ import { afterEach, beforeEach, vi } from 'vitest';
import { ServerDispatch, RoomsDispatch, GameDispatch } from '@app/store';
import { Data } from '@app/types';
import {
WebClient,
StatusEnum,
WebSocketConnectReason,
setPendingOptions,
} from '@app/websocket';
import type { WebSocketConnectOptions } from '@app/websocket';
import { WebClient, setPendingOptions } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { PROTOCOL_VERSION } from '../../../src/websocket/config';
import { createWebClientRequest, createWebClientResponse } from '@app/api';
@ -109,7 +104,7 @@ function resetAll(): void {
}
client.protobuf.resetCommands();
client.status = StatusEnum.DISCONNECTED;
client.status = WebsocketTypes.StatusEnum.DISCONNECTED;
ServerDispatch.clearStore();
RoomsDispatch.clearStore();
@ -128,8 +123,8 @@ function resetAll(): void {
// ── Shared connect helpers ──────────────────────────────────────────────────
const DEFAULT_LOGIN_OPTIONS: WebSocketConnectOptions = {
reason: WebSocketConnectReason.LOGIN,
const DEFAULT_LOGIN_OPTIONS: WebsocketTypes.WebSocketConnectOptions = {
reason: WebsocketTypes.WebSocketConnectReason.LOGIN,
host: 'localhost',
port: '4748',
userName: 'alice',
@ -137,16 +132,16 @@ const DEFAULT_LOGIN_OPTIONS: WebSocketConnectOptions = {
};
export function connectRaw(
overrides: Partial<WebSocketConnectOptions> = {}
overrides: Partial<WebsocketTypes.WebSocketConnectOptions> = {}
): void {
const opts = { ...DEFAULT_LOGIN_OPTIONS, ...overrides };
setPendingOptions(opts as WebSocketConnectOptions);
setPendingOptions(opts as WebsocketTypes.WebSocketConnectOptions);
getWebClient().connect({ host: opts.host, port: opts.port });
openMockWebSocket();
}
export function connectAndHandshake(
overrides: Partial<WebSocketConnectOptions> = {}
overrides: Partial<WebsocketTypes.WebSocketConnectOptions> = {}
): void {
connectRaw(overrides);
deliverMessage(buildSessionEventMessage(
@ -160,7 +155,7 @@ export function connectAndHandshake(
}
export function connectAndHandshakeWithSalt(
overrides: Partial<WebSocketConnectOptions> = {}
overrides: Partial<WebsocketTypes.WebSocketConnectOptions> = {}
): void {
connectRaw(overrides);
deliverMessage(buildSessionEventMessage(

View file

@ -6,7 +6,7 @@ import { describe, expect, it } from 'vitest';
import { Data } from '@app/types';
import { store } from '@app/store';
import { StatusEnum, WebSocketConnectReason } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { connectAndHandshake, connectAndHandshakeWithSalt } from '../helpers/setup';
import {
@ -44,7 +44,7 @@ describe('authentication', () => {
})));
const state = store.getState().server;
expect(state.status.state).toBe(StatusEnum.LOGGED_IN);
expect(state.status.state).toBe(WebsocketTypes.StatusEnum.LOGGED_IN);
expect(state.status.description).toBe('Logged in.');
expect(state.user?.name).toBe('alice');
expect(Object.keys(state.buddyList)).toEqual(['bob']);
@ -64,7 +64,7 @@ describe('authentication', () => {
})));
const state = store.getState().server;
expect(state.status.state).toBe(StatusEnum.DISCONNECTED);
expect(state.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
expect(state.user).toBeNull();
expect(state.buddyList).toEqual({});
});
@ -72,7 +72,7 @@ describe('authentication', () => {
describe('register', () => {
const registerOptions = {
reason: WebSocketConnectReason.REGISTER as const,
reason: WebsocketTypes.WebSocketConnectReason.REGISTER as const,
host: 'localhost',
port: '4748',
userName: 'newbie',
@ -107,7 +107,7 @@ describe('authentication', () => {
responseCode: Data.Response_ResponseCode.RespRegistrationAcceptedNeedsActivation,
})));
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
expect(() => findLastSessionCommand(Data.Command_Login_ext)).toThrow();
});
});
@ -115,7 +115,7 @@ describe('authentication', () => {
describe('activate', () => {
it('auto-logs-in on RespActivationAccepted', () => {
connectAndHandshake({
reason: WebSocketConnectReason.ACTIVATE_ACCOUNT as const,
reason: WebsocketTypes.WebSocketConnectReason.ACTIVATE_ACCOUNT as const,
host: 'localhost',
port: '4748',
userName: 'alice',
@ -171,7 +171,7 @@ describe('authentication', () => {
}),
})));
expect(store.getState().server.status.state).toBe(StatusEnum.LOGGED_IN);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.LOGGED_IN);
});
});
});

View file

@ -7,7 +7,7 @@ import { describe, expect, it } from 'vitest';
import { Data } from '@app/types';
import { store } from '@app/store';
import { StatusEnum } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { PROTOCOL_VERSION } from '../../../src/websocket/config';
@ -18,17 +18,15 @@ import {
setPendingOptions,
connectAndHandshake,
} from '../helpers/setup';
import type { WebSocketConnectOptions } from '@app/websocket';
import { WebSocketConnectReason } from '@app/websocket';
import {
buildSessionEventMessage,
deliverMessage,
} from '../helpers/protobuf-builders';
import { findLastSessionCommand } from '../helpers/command-capture';
function loginOptions(overrides: Partial<{ userName: string; password: string }> = {}): WebSocketConnectOptions {
function loginOptions(overrides: Partial<{ userName: string; password: string }> = {}): WebsocketTypes.WebSocketConnectOptions {
return {
reason: WebSocketConnectReason.LOGIN,
reason: WebsocketTypes.WebSocketConnectReason.LOGIN,
host: 'localhost',
port: '4748',
userName: overrides.userName ?? 'alice',
@ -36,7 +34,7 @@ function loginOptions(overrides: Partial<{ userName: string; password: string }>
};
}
function connectWithOptions(opts: WebSocketConnectOptions): void {
function connectWithOptions(opts: WebsocketTypes.WebSocketConnectOptions): void {
setPendingOptions(opts);
getWebClient().connect({ host: opts.host, port: opts.port });
}
@ -63,7 +61,7 @@ describe('connection lifecycle', () => {
openMockWebSocket();
expect(store.getState().server.status.state).toBe(StatusEnum.CONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.CONNECTED);
expect(store.getState().server.status.description).toBe('Connected');
});
@ -73,7 +71,7 @@ describe('connection lifecycle', () => {
deliverMessage(serverIdentification());
expect(store.getState().server.status.state).toBe(StatusEnum.LOGGING_IN);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.LOGGING_IN);
expect(store.getState().server.info.name).toBe('TestServer');
expect(store.getState().server.info.version).toBe('2.8.0');
@ -90,7 +88,7 @@ describe('connection lifecycle', () => {
const mock = getMockWebSocket();
expect(mock.close).toHaveBeenCalled();
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
expect(() => findLastSessionCommand(Data.Command_Login_ext)).toThrow();
});
@ -103,7 +101,7 @@ describe('connection lifecycle', () => {
vi.advanceTimersByTime(5000);
expect(mock.close).toHaveBeenCalled();
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
it('releases keep-alive ping loop on explicit disconnect', () => {
@ -115,7 +113,7 @@ describe('connection lifecycle', () => {
getWebClient().disconnect();
expect(mock.close).toHaveBeenCalled();
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
it('drops pending commands and clears state on unexpected socket close', () => {
@ -129,6 +127,6 @@ describe('connection lifecycle', () => {
mock.readyState = 3;
mock.onclose?.({ code: 1006, reason: '', wasClean: false } as CloseEvent);
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
});

View file

@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest';
import { Data } from '@app/types';
import { store } from '@app/store';
import { StatusEnum } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { connectRaw, getMockWebSocket } from '../helpers/setup';
import {
@ -32,7 +32,7 @@ describe('keep-alive', () => {
vi.advanceTimersByTime(5000);
const second = findLastSessionCommand(Data.Command_Ping_ext);
expect(second.cmdId).toBeGreaterThan(first.cmdId);
expect(store.getState().server.status.state).toBe(StatusEnum.CONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.CONNECTED);
});
it('stays CONNECTED while pongs arrive before the next tick', () => {
@ -47,7 +47,7 @@ describe('keep-alive', () => {
})));
}
expect(store.getState().server.status.state).toBe(StatusEnum.CONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.CONNECTED);
expect(getMockWebSocket().close).not.toHaveBeenCalled();
});
@ -56,11 +56,11 @@ describe('keep-alive', () => {
vi.advanceTimersByTime(5000);
expect(() => findLastSessionCommand(Data.Command_Ping_ext)).not.toThrow();
expect(store.getState().server.status.state).toBe(StatusEnum.CONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.CONNECTED);
vi.advanceTimersByTime(5000);
expect(getMockWebSocket().close).toHaveBeenCalled();
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
});

View file

@ -6,7 +6,7 @@ import { describe, expect, it } from 'vitest';
import { Data } from '@app/types';
import { store } from '@app/store';
import { StatusEnum, WebSocketConnectReason } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { connectAndHandshake } from '../helpers/setup';
import {
@ -19,7 +19,7 @@ import { findLastSessionCommand } from '../helpers/command-capture';
describe('password reset', () => {
it('forgotPasswordRequest sends command and disconnects on success', () => {
connectAndHandshake({
reason: WebSocketConnectReason.PASSWORD_RESET_REQUEST as const,
reason: WebsocketTypes.WebSocketConnectReason.PASSWORD_RESET_REQUEST as const,
host: 'localhost',
port: '4748',
userName: 'alice',
@ -37,12 +37,12 @@ describe('password reset', () => {
}),
})));
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
it('forgotPasswordChallenge sends command with userName and email', () => {
connectAndHandshake({
reason: WebSocketConnectReason.PASSWORD_RESET_CHALLENGE as const,
reason: WebsocketTypes.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE as const,
host: 'localhost',
port: '4748',
userName: 'alice',
@ -58,12 +58,12 @@ describe('password reset', () => {
responseCode: Data.Response_ResponseCode.RespOk,
})));
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
it('forgotPasswordReset sends command with userName, token, and newPassword', () => {
connectAndHandshake({
reason: WebSocketConnectReason.PASSWORD_RESET as const,
reason: WebsocketTypes.WebSocketConnectReason.PASSWORD_RESET as const,
host: 'localhost',
port: '4748',
userName: 'alice',
@ -81,6 +81,6 @@ describe('password reset', () => {
responseCode: Data.Response_ResponseCode.RespOk,
})));
expect(store.getState().server.status.state).toBe(StatusEnum.DISCONNECTED);
expect(store.getState().server.status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
});
});

View file

@ -6,7 +6,7 @@ import { describe, expect, it } from 'vitest';
import { Data } from '@app/types';
import { store } from '@app/store';
import { StatusEnum } from '@app/websocket';
import { WebsocketTypes } from '@app/websocket/types';
import { connectAndHandshake } from '../helpers/setup';
import {
@ -73,7 +73,7 @@ describe('server events', () => {
));
const status = store.getState().server.status;
expect(status.state).toBe(StatusEnum.DISCONNECTED);
expect(status.state).toBe(WebsocketTypes.StatusEnum.DISCONNECTED);
expect(status.description).toBe('kicked by admin');
});