Module cleanup and tests
CI / ci (push) Failing after 7s

This commit is contained in:
2026-05-24 23:13:45 +02:00
parent 63d83e999a
commit 5dc9b3b8d4
72 changed files with 2545 additions and 1220 deletions
+33 -21
View File
@@ -84,14 +84,15 @@ describe('ScryingPoolController', () => {
// ── AC-2: action() happy path ─────────────────────────────────────────────
describe('action() happy path (AC-2)', () => {
it('stores a PendingOp in _pendingOps keyed by participantId', () => {
it('registers a PendingOp via socketHandler.registerPendingOp with correct shape', () => {
// With self-confirm, _pendingOps is cleared synchronously after action().
// Verify the op was passed to registerPendingOp before being confirmed.
controller.action('ui', 'user-1', 'hidden', 'op-1', 0);
expect(controller._pendingOps.has('user-1')).toBe(true);
expect(controller._pendingOps.get('user-1')).toMatchObject({
opId: 'op-1',
userId: 'user-1',
targetState: 'hidden',
});
expect(socketHandler.registerPendingOp).toHaveBeenCalledWith(
expect.objectContaining({ opId: 'op-1', userId: 'user-1', targetState: 'hidden' }),
'scrying-pool.visibility.set',
expect.objectContaining({ opId: 'op-1' })
);
});
it('calls stateStore.setVisibility with the target state (optimistic update)', () => {
@@ -117,18 +118,24 @@ describe('ScryingPoolController', () => {
);
});
it('fires Hooks.callAll scrying-pool:controllerAction with correct payload', () => {
it('fires Hooks.callAll scrying-pool:controllerAction after self-confirm', () => {
// Self-confirm calls _onEcho which fires the hook with source: 'echo'.
controller.action('ui', 'user-1', 'hidden', 'op-1', 0);
expect(hooksStub.callAll).toHaveBeenCalledWith(
'scrying-pool:controllerAction',
expect.objectContaining({ participantId: 'user-1', targetState: 'hidden', source: 'ui', opId: 'op-1' })
expect.objectContaining({ participantId: 'user-1', targetState: 'hidden', source: 'echo', opId: 'op-1' })
);
});
it('sets previousState to null-coalesced "never-connected" when participant is new', () => {
it('sets previousState to "active" when participant is new (not yet in matrix)', () => {
// With self-confirm, _pendingOps is cleared synchronously. Verify via registerPendingOp arg.
controller.action('ui', 'new-user', 'hidden', 'op-1', 0);
const op = controller._pendingOps.get('new-user');
expect(op.previousState).toBe('never-connected');
// 'active' is the render-time default for users not in the matrix.
expect(socketHandler.registerPendingOp).toHaveBeenCalledWith(
expect.objectContaining({ previousState: 'active' }),
expect.any(String),
expect.any(Object)
);
});
});
@@ -226,23 +233,30 @@ describe('ScryingPoolController', () => {
return adapter.socket.on.mock.calls[0][1];
}
// Helper: directly register a pending op (bypasses action() self-confirm)
function seedPendingOp(userId, opId, targetState = 'hidden') {
const op = { opId, userId, targetState, previousState: 'active' };
controller._pendingOps.set(userId, op);
socketHandler.registerPendingOp(op, 'scrying-pool.visibility.set', {});
}
it('calls socketHandler.confirmPendingOp with the opId', () => {
controller.action('ui', 'user-1', 'hidden', 'op-1', 0);
const echoHandler = getEchoHandler();
seedPendingOp('user-1', 'op-1');
echoHandler({ opId: 'op-1', userId: 'user-1', state: 'hidden', revision: 1 });
expect(socketHandler.confirmPendingOp).toHaveBeenCalledWith('op-1');
});
it('stores the echo revision in _revisions for the userId', () => {
controller.action('ui', 'user-1', 'hidden', 'op-2', 0);
const echoHandler = getEchoHandler();
seedPendingOp('user-1', 'op-2');
echoHandler({ opId: 'op-2', userId: 'user-1', state: 'hidden', revision: 7 });
expect(controller._revisions.get('user-1')).toBe(7);
});
it('calls stateStore.setVisibility with the authoritative state', () => {
controller.action('ui', 'user-1', 'active', 'op-3', 0);
const echoHandler = getEchoHandler();
seedPendingOp('user-1', 'op-3', 'active');
const setSpy = vi.spyOn(stateStore, 'setVisibility');
echoHandler({ opId: 'op-3', userId: 'user-1', state: 'active', revision: 2 });
@@ -251,8 +265,8 @@ describe('ScryingPoolController', () => {
});
it('fires Hooks.callAll scrying-pool:controllerAction with source: echo', () => {
controller.action('ui', 'user-1', 'hidden', 'op-4', 0);
const echoHandler = getEchoHandler();
seedPendingOp('user-1', 'op-4');
echoHandler({ opId: 'op-4', userId: 'user-1', state: 'hidden', revision: 1 });
expect(hooksStub.callAll).toHaveBeenCalledWith(
@@ -262,20 +276,18 @@ describe('ScryingPoolController', () => {
});
it('removes the participant from _pendingOps after echo', () => {
// Register a pending op first
controller.action('ui', 'user-1', 'hidden', 'op-1', 0);
const echoHandler = getEchoHandler();
seedPendingOp('user-1', 'op-1');
expect(controller._pendingOps.has('user-1')).toBe(true);
const echoHandler = getEchoHandler();
echoHandler({ opId: 'op-1', userId: 'user-1', state: 'hidden', revision: 1 });
expect(controller._pendingOps.has('user-1')).toBe(false);
});
it('defaults revision to 0 when echo payload omits revision field', () => {
// Register a pending op first (required by new validation)
controller.action('ui', 'user-1', 'hidden', 'op-1', 0);
const echoHandler = getEchoHandler();
seedPendingOp('user-1', 'op-1');
echoHandler({ opId: 'op-1', userId: 'user-1', state: 'hidden' }); // no revision
expect(controller._revisions.get('user-1')).toBe(0);
});