7918792f4e
- 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>
206 lines
6.4 KiB
JavaScript
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();
|
|
});
|
|
});
|
|
});
|