// @ts-nocheck import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; // Stub Application globally before importing ScryingPoolStrip beforeEach(() => { vi.stubGlobal('Application', class { static get defaultOptions() { return {}; } constructor() { this.position = { left: 0, top: 0 }; this.rendered = false; } render() {} close() {} activateListeners() {} }); vi.stubGlobal('foundry', { utils: { mergeObject: (base, override) => Object.assign({}, base, override), }, }); vi.stubGlobal('game', { user: { setFlag: vi.fn(), getFlag: vi.fn(() => null), }, }); }); afterEach(() => { vi.unstubAllGlobals(); vi.clearAllMocks(); }); import { LABELS, resolveTargetState, buildParticipantList, ScryingPoolStrip, } from '../../../../src/ui/gm/ScryingPoolStrip.js'; describe('LABELS', () => { it('has HIDE_FROM_TABLE equal to exact canonical string', () => { expect(LABELS.HIDE_FROM_TABLE).toBe('Hide from table'); }); it('has SHOW_TO_TABLE equal to exact canonical string', () => { expect(LABELS.SHOW_TO_TABLE).toBe('Show to table'); }); it('has FIRST_TOOLTIP set', () => { expect(LABELS.FIRST_TOOLTIP).toBe('Hide this participant from other players.'); }); it('is frozen (immutable)', () => { expect(Object.isFrozen(LABELS)).toBe(true); }); }); describe('resolveTargetState()', () => { it('returns active when current state is hidden', () => { expect(resolveTargetState('hidden')).toBe('active'); }); it('returns hidden when current state is active', () => { expect(resolveTargetState('active')).toBe('hidden'); }); it('returns hidden when current state is self-muted', () => { expect(resolveTargetState('self-muted')).toBe('hidden'); }); it('returns hidden when current state is cam-lost', () => { expect(resolveTargetState('cam-lost')).toBe('hidden'); }); it('returns hidden when current state is never-connected', () => { expect(resolveTargetState('never-connected')).toBe('hidden'); }); }); describe('buildParticipantList()', () => { let stateStore; let controller; let adapter; beforeEach(() => { stateStore = { getState: vi.fn(userId => userId === 'user-1' ? 'active' : 'hidden'), }; controller = { hasPendingOp: vi.fn(() => false), }; adapter = { users: { get: vi.fn(userId => ({ id: userId, name: `User ${userId}`, avatar: `avatars/${userId}.png`, })), current: vi.fn(() => ({ id: 'user-1' })), }, }; }); it('returns correct shape for each participant', () => { const list = buildParticipantList(['user-1', 'user-2'], stateStore, controller, adapter); expect(list).toHaveLength(2); expect(list[0]).toMatchObject({ userId: 'user-1', name: 'User user-1', avatarSrc: 'avatars/user-1.png', state: 'active', stateLabel: 'Active', hasPendingOp: false, isCurrentUser: true, }); }); it('returns isEmpty-compatible empty array for no userIds', () => { const list = buildParticipantList([], stateStore, controller, adapter); expect(list).toHaveLength(0); }); it('sets hasPendingOp to true when controller reports pending', () => { controller.hasPendingOp.mockReturnValue(true); const list = buildParticipantList(['user-1'], stateStore, controller, adapter); expect(list[0].hasPendingOp).toBe(true); }); it('uses mystery-man.svg fallback when avatar is null', () => { adapter.users.get.mockReturnValue({ id: 'user-1', name: 'Alice', avatar: null }); const list = buildParticipantList(['user-1'], stateStore, controller, adapter); expect(list[0].avatarSrc).toBe('icons/svg/mystery-man.svg'); }); it('marks only the current user as isCurrentUser', () => { const list = buildParticipantList(['user-1', 'user-2'], stateStore, controller, adapter); expect(list[0].isCurrentUser).toBe(true); expect(list[1].isCurrentUser).toBe(false); }); it('correctly maps hidden state label', () => { const list = buildParticipantList(['user-2'], stateStore, controller, adapter); expect(list[0].stateLabel).toBe('Hidden'); }); }); describe('ScryingPoolStrip', () => { let stateStore; let controller; let avTileAdapter; let adapter; let strip; beforeEach(() => { stateStore = { getState: vi.fn(() => 'active') }; controller = { action: vi.fn(), getRevision: vi.fn(() => 0), hasPendingOp: vi.fn(() => false) }; avTileAdapter = { mount: vi.fn(), unmount: vi.fn(), setStateClass: vi.fn(), disconnect: vi.fn() }; adapter = { users: { get: vi.fn(() => ({ id: 'u1', name: 'Alice', avatar: 'av.png' })), all: vi.fn(() => [{ id: 'u1' }]), current: vi.fn(() => ({ id: 'u1' })), }, }; strip = new ScryingPoolStrip(stateStore, controller, avTileAdapter, adapter); }); describe('defaultOptions', () => { it('has correct id', () => { expect(ScryingPoolStrip.defaultOptions.id).toBe('scrying-pool-strip'); }); it('has correct template path', () => { expect(ScryingPoolStrip.defaultOptions.template).toContain('roster-strip.hbs'); }); it('is not resizable', () => { expect(ScryingPoolStrip.defaultOptions.resizable).toBe(false); }); it('popOut is true', () => { expect(ScryingPoolStrip.defaultOptions.popOut).toBe(true); }); }); describe('getData()', () => { it('returns participants array', () => { const data = strip.getData(); expect(Array.isArray(data.participants)).toBe(true); }); it('returns isExpanded property', () => { const data = strip.getData(); expect(typeof data.isExpanded).toBe('boolean'); }); it('returns isEmpty true when no participants', () => { adapter.users.all.mockReturnValue([]); const data = strip.getData(); expect(data.isEmpty).toBe(true); }); it('returns isEmpty false when participants exist', () => { const data = strip.getData(); expect(data.isEmpty).toBe(false); }); }); });