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
+7 -50
View File
@@ -98,10 +98,10 @@ describe("PlayerPrivacyManager", () => {
});
it("should return saved settings when flag exists", () => {
const savedSettings = { reactionCamEnabled: true, hpReactiveCamStylingEnabled: false, customPortraitFallback: null };
const savedSettings = { reactionCamEnabled: true, customPortraitFallback: null };
adapter.users.get.mockReturnValue({
getFlag: vi.fn((scope, key) => {
if (scope === "video-view-manager") {
if (scope === "scrying-pool") {
return savedSettings[key];
}
return undefined;
@@ -114,7 +114,7 @@ describe("PlayerPrivacyManager", () => {
it("should return partial settings merged with defaults", () => {
adapter.users.get.mockReturnValue({
getFlag: vi.fn((scope, key) => {
if (scope === "video-view-manager" && key === "reactionCamEnabled") {
if (scope === "scrying-pool" && key === "reactionCamEnabled") {
return true;
}
return undefined;
@@ -123,7 +123,6 @@ describe("PlayerPrivacyManager", () => {
const result = manager.getSettings("user1");
expect(result).toEqual({
reactionCamEnabled: true,
hpReactiveCamStylingEnabled: false,
customPortraitFallback: null,
});
});
@@ -182,7 +181,7 @@ describe("PlayerPrivacyManager", () => {
adapter.users.get.mockReturnValue(mockUser);
await manager.setSetting("user1", "reactionCamEnabled", true);
expect(mockUser.setFlag).toHaveBeenCalledWith(
"video-view-manager",
"scrying-pool",
"reactionCamEnabled",
true
);
@@ -268,15 +267,6 @@ describe("PlayerPrivacyManager", () => {
expect(manager.isOptedIn("nonexistent", "reactionCam")).toBe(false);
});
it("should work for hpReactiveCamStyling feature", () => {
adapter.users.get.mockReturnValue({
getFlag: vi.fn((scope, key) => {
if (key === "hpReactiveCamStylingEnabled") return true;
return false;
}),
});
expect(manager.isOptedIn("user1", "hpReactiveCamStyling")).toBe(true);
});
});
describe("getAllSettings", () => {
@@ -295,7 +285,6 @@ describe("PlayerPrivacyManager", () => {
id: "user1",
getFlag: vi.fn((scope, key) => {
if (key === "reactionCamEnabled") return true;
if (key === "hpReactiveCamStylingEnabled") return false;
return undefined; // customPortraitFallback and other keys
}),
};
@@ -303,7 +292,6 @@ describe("PlayerPrivacyManager", () => {
id: "user2",
getFlag: vi.fn((scope, key) => {
if (key === "reactionCamEnabled") return false;
if (key === "hpReactiveCamStylingEnabled") return true;
return undefined; // customPortraitFallback and other keys
}),
};
@@ -318,12 +306,10 @@ describe("PlayerPrivacyManager", () => {
expect(result.size).toBe(2);
expect(result.get("user1")).toEqual({
reactionCamEnabled: true,
hpReactiveCamStylingEnabled: false,
customPortraitFallback: null,
});
expect(result.get("user2")).toEqual({
reactionCamEnabled: false,
hpReactiveCamStylingEnabled: true,
customPortraitFallback: null,
});
});
@@ -429,34 +415,6 @@ describe("PlayerPrivacyManager", () => {
expect(manager.isOptedIn("player1", "reactionCam")).toBe(true);
});
it("should handle player disabling HP-Reactive Cam Styling", async () => {
const mockUser = {
id: "player1",
getFlag: vi.fn((scope, key) => {
if (key === "hpReactiveCamStylingEnabled") return true;
return false;
}),
setFlag: vi.fn().mockResolvedValue(undefined),
};
adapter.users.get.mockReturnValue(mockUser);
// Initially opted in
expect(manager.isOptedIn("player1", "hpReactiveCamStyling")).toBe(true);
// Disable HP-Reactive Cam Styling
await manager.setSetting("player1", "hpReactiveCamStylingEnabled", false);
// After setting, the mock should return false for hpReactiveCamStylingEnabled
adapter.users.get.mockReturnValue({
id: "player1",
getFlag: vi.fn((scope, key) => {
if (key === "hpReactiveCamStylingEnabled") return false;
return false;
}),
setFlag: vi.fn().mockResolvedValue(undefined),
});
expect(manager.isOptedIn("player1", "hpReactiveCamStyling")).toBe(false);
});
it("should allow GM to view all players' settings", () => {
const gm = { id: "gm1", isGM: true, getFlag: vi.fn(() => false) };
const player1 = {
@@ -465,7 +423,7 @@ describe("PlayerPrivacyManager", () => {
};
const player2 = {
id: "player2",
getFlag: vi.fn((scope, key) => (key === "hpReactiveCamStylingEnabled" ? true : false)),
getFlag: vi.fn((scope, key) => (key === "reactionCamEnabled" ? false : false)),
};
adapter.users.all.mockReturnValue([gm, player1, player2]);
@@ -518,7 +476,7 @@ describe("PlayerPrivacyManager", () => {
).resolves.not.toThrow();
expect(mockUser.setFlag).toHaveBeenCalledWith(
"video-view-manager",
"scrying-pool",
"customPortraitFallback",
dataURL
);
@@ -648,7 +606,7 @@ describe("PlayerPrivacyManager", () => {
await manager.removePortraitFallback("player1");
expect(mockUser.unsetFlag).toHaveBeenCalledWith(
"video-view-manager",
"scrying-pool",
"customPortraitFallback"
);
});
@@ -686,7 +644,6 @@ describe("PlayerPrivacyManager", () => {
getFlag: vi.fn((scope, key) => {
if (key === "customPortraitFallback") return dataURL;
if (key === "reactionCamEnabled") return true;
if (key === "hpReactiveCamStylingEnabled") return false;
return undefined;
}),
};
+1 -1
View File
@@ -815,7 +815,7 @@ describe('ScenePresetManager', () => {
});
expect(mockScene.setFlag).toHaveBeenCalledWith(
'video-view-manager',
'scrying-pool',
'presets',
expect.objectContaining({
_version: 1,
+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);
});