Files
scrying-pool/tests/unit/ui/shared/AVTileAdapter.test.js
T
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

244 lines
9.1 KiB
JavaScript

// @ts-nocheck
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { AVTileAdapter } from '../../../../src/ui/shared/AVTileAdapter.js';
import { createFoundryAdapterMock } from '../../../helpers/foundryAdapterMock.js';
describe('AVTileAdapter', () => {
let adapter;
let avAdapter;
beforeEach(() => {
document.body.innerHTML = `
<div class="camera-view" data-user-id="user-1"></div>
<div class="camera-view" data-user-id="user-2"></div>
`;
adapter = createFoundryAdapterMock();
avAdapter = new AVTileAdapter(adapter);
});
afterEach(() => {
avAdapter.disconnect();
});
describe('constructor', () => {
it('stores adapter reference without side effects', () => {
expect(avAdapter._adapter).toBe(adapter);
expect(avAdapter._observers).toBeInstanceOf(Map);
expect(avAdapter._observers.size).toBe(0);
});
});
describe('mount()', () => {
it('appends element to the matching AV tile', () => {
const el = document.createElement('div');
el.dataset.spRole = 'lock-overlay';
avAdapter.mount('user-1', el);
const tile = document.querySelector('[data-user-id="user-1"]');
expect(tile.contains(el)).toBe(true);
});
it('marks mounted element with data-sp-mount attribute', () => {
const el = document.createElement('div');
el.dataset.spRole = 'lock-overlay';
avAdapter.mount('user-1', el);
expect(el.dataset.spMount).toBe('1');
});
it('warns and does not throw when tile not found', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const el = document.createElement('div');
expect(() => avAdapter.mount('unknown-user', el)).not.toThrow();
expect(warnSpy).toHaveBeenCalledWith(
'[ScryingPool] AVTileAdapter.mount: tile not found for',
'unknown-user'
);
warnSpy.mockRestore();
});
it('is idempotent: calling twice with same element does not duplicate', () => {
const el = document.createElement('div');
el.dataset.spRole = 'lock-overlay';
avAdapter.mount('user-1', el);
avAdapter.mount('user-1', el);
const tile = document.querySelector('[data-user-id="user-1"]');
expect(tile.querySelectorAll('[data-sp-role="lock-overlay"]').length).toBe(1);
});
it('replaces existing element with same data-sp-role', () => {
const el1 = document.createElement('div');
el1.dataset.spRole = 'lock-overlay';
el1.textContent = 'first';
avAdapter.mount('user-1', el1);
const el2 = document.createElement('div');
el2.dataset.spRole = 'lock-overlay';
el2.textContent = 'second';
avAdapter.mount('user-1', el2);
const tile = document.querySelector('[data-user-id="user-1"]');
const overlays = tile.querySelectorAll('[data-sp-role="lock-overlay"]');
expect(overlays.length).toBe(1);
expect(overlays[0].textContent).toBe('second');
});
it('appends multiple elements with different roles', () => {
const el1 = document.createElement('div');
el1.dataset.spRole = 'lock-overlay';
const el2 = document.createElement('div');
el2.dataset.spRole = 'portrait-fallback';
avAdapter.mount('user-1', el1);
avAdapter.mount('user-1', el2);
const tile = document.querySelector('[data-user-id="user-1"]');
expect(tile.querySelectorAll('[data-sp-mount]').length).toBe(2);
});
it('does not affect other tiles', () => {
const el = document.createElement('div');
el.dataset.spRole = 'lock-overlay';
avAdapter.mount('user-1', el);
const tile2 = document.querySelector('[data-user-id="user-2"]');
expect(tile2.querySelectorAll('[data-sp-mount]').length).toBe(0);
});
});
describe('unmount()', () => {
it('removes all data-sp-mount children from tile', () => {
const el1 = document.createElement('div');
el1.dataset.spRole = 'lock-overlay';
const el2 = document.createElement('div');
el2.dataset.spRole = 'portrait-fallback';
avAdapter.mount('user-1', el1);
avAdapter.mount('user-1', el2);
avAdapter.unmount('user-1');
const tile = document.querySelector('[data-user-id="user-1"]');
expect(tile.querySelectorAll('[data-sp-mount]').length).toBe(0);
});
it('does not remove non-managed children', () => {
const native = document.createElement('video');
document.querySelector('[data-user-id="user-1"]').appendChild(native);
avAdapter.unmount('user-1');
const tile = document.querySelector('[data-user-id="user-1"]');
expect(tile.contains(native)).toBe(true);
});
it('is no-op if tile not found', () => {
expect(() => avAdapter.unmount('unknown-user')).not.toThrow();
});
it('does not affect other tiles', () => {
const el1 = document.createElement('div');
el1.dataset.spRole = 'lock-overlay';
const el2 = document.createElement('div');
el2.dataset.spRole = 'lock-overlay';
avAdapter.mount('user-1', el1);
avAdapter.mount('user-2', el2);
avAdapter.unmount('user-1');
const tile2 = document.querySelector('[data-user-id="user-2"]');
expect(tile2.querySelectorAll('[data-sp-mount]').length).toBe(1);
});
});
describe('setStateClass()', () => {
it('adds sp-state-{stateName} class to tile', () => {
avAdapter.setStateClass('user-1', 'hidden');
const tile = document.querySelector('[data-user-id="user-1"]');
expect(tile.classList.contains('sp-state-hidden')).toBe(true);
});
it('removes all previous sp-state-* classes before adding new', () => {
const tile = document.querySelector('[data-user-id="user-1"]');
tile.classList.add('sp-state-active');
tile.classList.add('sp-state-pending');
avAdapter.setStateClass('user-1', 'hidden');
expect(tile.classList.contains('sp-state-active')).toBe(false);
expect(tile.classList.contains('sp-state-pending')).toBe(false);
expect(tile.classList.contains('sp-state-hidden')).toBe(true);
});
it('clears all sp-state-* classes when stateName is null', () => {
const tile = document.querySelector('[data-user-id="user-1"]');
tile.classList.add('sp-state-active');
avAdapter.setStateClass('user-1', null);
expect(tile.classList.contains('sp-state-active')).toBe(false);
});
it('warns and does not throw when tile not found', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
expect(() => avAdapter.setStateClass('unknown-user', 'hidden')).not.toThrow();
expect(warnSpy).toHaveBeenCalledWith(
'[ScryingPool] AVTileAdapter.setStateClass: tile not found for',
'unknown-user'
);
warnSpy.mockRestore();
});
it('does not affect non-sp-* classes on tile', () => {
const tile = document.querySelector('[data-user-id="user-1"]');
tile.classList.add('camera-view');
tile.classList.add('some-other-class');
avAdapter.setStateClass('user-1', 'hidden');
expect(tile.classList.contains('camera-view')).toBe(true);
expect(tile.classList.contains('some-other-class')).toBe(true);
});
});
describe('onTileRerender()', () => {
it('calls callback when tile children change', async () => {
const cb = vi.fn();
avAdapter.onTileRerender('user-1', cb);
const tile = document.querySelector('[data-user-id="user-1"]');
tile.appendChild(document.createElement('span'));
await new Promise(resolve => setTimeout(resolve, 20));
expect(cb).toHaveBeenCalledWith(tile);
});
it('replaces existing observer when called again for same userId', async () => {
const cb1 = vi.fn();
const cb2 = vi.fn();
avAdapter.onTileRerender('user-1', cb1);
avAdapter.onTileRerender('user-1', cb2);
// Only one observer should be active per userId
expect(avAdapter._observers.size).toBe(1);
});
it('stores one observer per userId', () => {
avAdapter.onTileRerender('user-1', vi.fn());
avAdapter.onTileRerender('user-2', vi.fn());
expect(avAdapter._observers.size).toBe(2);
});
it('is no-op if tile not found', () => {
expect(() => avAdapter.onTileRerender('unknown-user', vi.fn())).not.toThrow();
expect(avAdapter._observers.size).toBe(0);
});
});
describe('disconnect()', () => {
it('stops observers from firing after disconnect', async () => {
const cb = vi.fn();
avAdapter.onTileRerender('user-1', cb);
avAdapter.disconnect();
const tile = document.querySelector('[data-user-id="user-1"]');
tile.appendChild(document.createElement('span'));
await new Promise(resolve => setTimeout(resolve, 20));
expect(cb).not.toHaveBeenCalled();
});
it('clears observer map', () => {
avAdapter.onTileRerender('user-1', vi.fn());
avAdapter.onTileRerender('user-2', vi.fn());
avAdapter.disconnect();
expect(avAdapter._observers.size).toBe(0);
});
it('is safe to call multiple times', () => {
avAdapter.onTileRerender('user-1', vi.fn());
expect(() => {
avAdapter.disconnect();
avAdapter.disconnect();
}).not.toThrow();
});
});
});