Files
uberwald 7918792f4e Fix Story 2.3 code review findings: remove duplicate ParticipantCard.js, fix lint in ScryingPoolStrip.js
- Delete src/ui/shared/ParticipantCard.js (duplicate of boardUtils.js with conflicting implementations)
- Delete tests/unit/ui/shared/ParticipantCard.test.js (tests for deleted file)
- Add directorsBoard to global declarations in ScryingPoolStrip.js to fix lint errors

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-23 11:31:01 +02:00

206 lines
6.4 KiB
JavaScript

// @ts-nocheck
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { createFoundryAdapterMock } from '../../helpers/foundryAdapterMock.js';
// Mock ScryingPoolStrip before it's imported so Application global isn't needed
vi.mock('../../../src/ui/gm/ScryingPoolStrip.js', () => ({
ScryingPoolStrip: vi.fn().mockImplementation(() => ({
render: vi.fn().mockResolvedValue(undefined),
close: vi.fn(),
rendered: false,
})),
}));
import { RoleRenderer } from '../../../src/ui/RoleRenderer.js';
function makeAVTileAdapter() {
return {
mount: vi.fn(),
unmount: vi.fn(),
setStateClass: vi.fn(),
disconnect: vi.fn(),
onTileRerender: vi.fn(),
};
}
function makeStateStore() {
const states = new Map();
return {
getState: vi.fn(userId => states.get(userId) ?? 'active'),
_states: states,
};
}
function makeController() {
return {
action: vi.fn(),
getRevision: vi.fn(() => 0),
hasPendingOp: vi.fn(() => false),
};
}
describe('RoleRenderer', () => {
let adapter;
let avTileAdapter;
let stateStore;
let controller;
let renderer;
let hooksStub;
beforeEach(() => {
hooksStub = { on: vi.fn(), off: vi.fn(), once: vi.fn(), callAll: vi.fn() };
vi.stubGlobal('Hooks', hooksStub);
vi.stubGlobal('game', { webrtc: {}, user: { setFlag: vi.fn(), getFlag: vi.fn(() => null) } });
adapter = createFoundryAdapterMock();
avTileAdapter = makeAVTileAdapter();
stateStore = makeStateStore();
controller = makeController();
renderer = new RoleRenderer(stateStore, controller, avTileAdapter, adapter);
});
afterEach(() => {
vi.unstubAllGlobals();
vi.clearAllMocks();
});
describe('constructor', () => {
it('stores all injected dependencies without side effects', () => {
expect(renderer._stateStore).toBe(stateStore);
expect(renderer._controller).toBe(controller);
expect(renderer._avTileAdapter).toBe(avTileAdapter);
expect(renderer._adapter).toBe(adapter);
});
it('does not register any Hooks in constructor', () => {
expect(hooksStub.on).not.toHaveBeenCalled();
});
it('_strip is null before openStrip()', () => {
expect(renderer._strip).toBeNull();
});
});
describe('init()', () => {
it('registers scrying-pool:stateChanged hook', () => {
renderer.init();
expect(hooksStub.on).toHaveBeenCalledWith(
'scrying-pool:stateChanged',
expect.any(Function)
);
});
it('registers scrying-pool:controllerAction hook', () => {
renderer.init();
expect(hooksStub.on).toHaveBeenCalledWith(
'scrying-pool:controllerAction',
expect.any(Function)
);
});
it('registers updateUser hook', () => {
renderer.init();
expect(hooksStub.on).toHaveBeenCalledWith('updateUser', expect.any(Function));
});
});
describe('_applyAVTileState()', () => {
it('calls setStateClass on avTileAdapter with userId and state', () => {
renderer._applyAVTileState('user-1', 'active');
expect(avTileAdapter.setStateClass).toHaveBeenCalledWith('user-1', 'active');
});
it('mounts lock-overlay element when state is hidden', () => {
renderer._applyAVTileState('user-1', 'hidden');
expect(avTileAdapter.mount).toHaveBeenCalled();
const el = avTileAdapter.mount.mock.calls[0][1];
expect(el.dataset.spRole).toBe('lock-overlay');
});
it('unmounts lock-overlay when state transitions away from hidden', () => {
renderer._applyAVTileState('user-1', 'active');
expect(avTileAdapter.unmount).toHaveBeenCalledWith('user-1');
});
it('mounts portrait-fallback when state is never-connected', () => {
renderer._applyAVTileState('user-1', 'never-connected');
expect(avTileAdapter.mount).toHaveBeenCalled();
const el = avTileAdapter.mount.mock.calls[0][1];
expect(el.dataset.spRole).toBe('portrait-fallback');
});
it('mounts portrait-fallback when state is cam-lost', () => {
renderer._applyAVTileState('user-1', 'cam-lost');
expect(avTileAdapter.mount).toHaveBeenCalled();
const el = avTileAdapter.mount.mock.calls[0][1];
expect(el.dataset.spRole).toBe('portrait-fallback');
});
it('unmounts portrait-fallback when state is not camera-absent', () => {
renderer._applyAVTileState('user-1', 'active');
expect(avTileAdapter.unmount).toHaveBeenCalled();
});
it('does not mount any overlay for active state', () => {
renderer._applyAVTileState('user-1', 'active');
expect(avTileAdapter.mount).not.toHaveBeenCalled();
});
});
describe('stateChanged hook handler', () => {
it('calls _applyAVTileState when scrying-pool:stateChanged fires', () => {
renderer.init();
const spy = vi.spyOn(renderer, '_applyAVTileState');
const handler = hooksStub.on.mock.calls.find(
c => c[0] === 'scrying-pool:stateChanged'
)[1];
handler({ userId: 'user-1', state: 'hidden' });
expect(spy).toHaveBeenCalledWith('user-1', 'hidden');
});
it('handles bulk matrix payload gracefully', () => {
renderer.init();
const handler = hooksStub.on.mock.calls.find(
c => c[0] === 'scrying-pool:stateChanged'
)[1];
// bulk payload has no userId
expect(() => handler({ matrix: {}, timestamp: Date.now(), revision: 1 })).not.toThrow();
});
});
describe('openStrip()', () => {
it('constructs ScryingPoolStrip lazily on first call', async () => {
const { ScryingPoolStrip } = await import('../../../src/ui/gm/ScryingPoolStrip.js');
vi.clearAllMocks();
renderer.openStrip();
expect(ScryingPoolStrip).toHaveBeenCalledOnce();
});
it('reuses existing strip instance on second call', async () => {
const { ScryingPoolStrip } = await import('../../../src/ui/gm/ScryingPoolStrip.js');
vi.clearAllMocks();
renderer.openStrip();
renderer.openStrip();
expect(ScryingPoolStrip).toHaveBeenCalledOnce();
});
it('calls render on the strip', () => {
renderer.openStrip();
expect(renderer._strip.render).toHaveBeenCalled();
});
});
describe('closeStrip()', () => {
it('calls close on the strip if it exists', () => {
renderer.openStrip();
const strip = renderer._strip;
renderer.closeStrip();
expect(strip.close).toHaveBeenCalled();
});
it('is no-op if strip is not open', () => {
expect(() => renderer.closeStrip()).not.toThrow();
});
});
});