Files
advanced-camera-card/tests/card-controller/media-info-manager.test.ts
T

363 lines
12 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { MediaLoadedInfoManager } from '../../src/card-controller/media-info-manager';
import {
createCardAPI,
createMediaLoadedInfo,
createMediaLoadedInfoEvent,
} from '../test-utils.js';
// @vitest-environment jsdom
describe('MediaLoadedInfoManager', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should initialize', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
manager.initialize();
expect(api.getConditionStateManager().setState).toBeCalledWith({
mediaLoadedInfo: null,
});
});
describe('set', () => {
it('should surface info and fire condition state for the selected target', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
vi.clearAllMocks();
manager.set(info, owner);
expect(manager.has()).toBeTruthy();
expect(manager.get()).toBe(info);
expect(api.getConditionStateManager().setState).toBeCalledWith({
mediaLoadedInfo: info,
});
expect(api.getStyleManager().setExpandedMode).toBeCalled();
expect(api.getCardElementManager().update).toBeCalled();
});
it('should cache info for non-selected targets without side effects', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.set(info, owner);
expect(manager.has()).toBeFalsy();
expect(manager.get()).toBeNull();
expect(api.getConditionStateManager().setState).not.toBeCalled();
expect(api.getStyleManager().setExpandedMode).not.toBeCalled();
expect(api.getCardElementManager().update).not.toBeCalled();
manager.setSelected('target-1');
expect(manager.has()).toBeTruthy();
expect(manager.get()).toBe(info);
expect(api.getConditionStateManager().setState).toBeCalledWith({
mediaLoadedInfo: info,
});
});
it('should reject info missing dimensions', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({
width: 0,
height: 0,
targetID: 'target-1',
});
manager.setSelected('target-1');
vi.clearAllMocks();
manager.set(info, owner);
expect(manager.has()).toBeFalsy();
expect(manager.get()).toBeNull();
expect(api.getConditionStateManager().setState).not.toBeCalled();
expect(api.getStyleManager().setExpandedMode).not.toBeCalled();
expect(api.getCardElementManager().update).not.toBeCalled();
});
it('should reject info without a targetID', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: undefined });
manager.setSelected('target-1');
manager.set(info, owner);
expect(manager.has()).toBeFalsy();
});
});
describe('setSelected', () => {
it('should switch between targets', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner1 = document.createElement('div');
const owner2 = document.createElement('div');
const info1 = createMediaLoadedInfo({ targetID: 'target-1' });
const info2 = createMediaLoadedInfo({ targetID: 'target-2' });
manager.set(info1, owner1);
manager.set(info2, owner2);
manager.setSelected('target-1');
expect(manager.get()).toBe(info1);
manager.setSelected('target-2');
expect(manager.get()).toBe(info2);
manager.setSelected(null);
expect(manager.get()).toBeNull();
});
it('should be a no-op when re-selecting the same target', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
manager.setSelected('target-1');
vi.clearAllMocks();
manager.setSelected('target-1');
expect(api.getConditionStateManager().setState).not.toBeCalled();
});
it('should emit null condition state when selecting a target with no info', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
manager.setSelected('target-1');
expect(api.getConditionStateManager().setState).toBeCalledWith({
mediaLoadedInfo: null,
});
});
});
describe('getLastKnown', () => {
it('should return the last known info for the selected target', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
manager.set(info, owner);
expect(manager.getLastKnown()).toBe(info);
});
it('should return null when nothing is selected', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
expect(manager.getLastKnown()).toBeNull();
});
it('should preserve last known across clear', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
manager.set(info, owner);
manager.clear();
expect(manager.has()).toBeFalsy();
expect(manager.getLastKnown()).toBe(info);
});
});
describe('handleLoadEvent', () => {
it('should register info from a valid event', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const source = document.createElement('div');
const ac = new AbortController();
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
vi.clearAllMocks();
manager.handleLoadEvent(
createMediaLoadedInfoEvent({ source, info, signal: ac.signal }),
);
expect(manager.get()).toBe(info);
expect(api.getConditionStateManager().setState).toBeCalledWith({
mediaLoadedInfo: info,
});
});
it('should ignore events whose composedPath()[0] is not an HTMLElement', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const ac = new AbortController();
const info = createMediaLoadedInfo({ targetID: 'target-1' });
const ev = createMediaLoadedInfoEvent({ info, signal: ac.signal });
// Force the path to look like a non-HTMLElement (e.g. a window).
Object.defineProperty(ev, 'composedPath', { value: () => [window] });
manager.setSelected('target-1');
manager.handleLoadEvent(ev);
expect(manager.has()).toBeFalsy();
});
it('should ignore events whose info has no targetID', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const source = document.createElement('div');
const ac = new AbortController();
const info = createMediaLoadedInfo({ targetID: undefined });
manager.setSelected('target-1');
manager.handleLoadEvent(
createMediaLoadedInfoEvent({ source, info, signal: ac.signal }),
);
expect(manager.has()).toBeFalsy();
});
it('should clear an entry on signal abort', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const source = document.createElement('div');
const ac = new AbortController();
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
manager.handleLoadEvent(
createMediaLoadedInfoEvent({ source, info, signal: ac.signal }),
);
expect(manager.has()).toBeTruthy();
ac.abort();
expect(manager.has()).toBeFalsy();
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith({
mediaLoadedInfo: null,
});
});
it('should not clear an entry whose owner has been replaced when an older signal aborts', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner1 = document.createElement('div');
const owner2 = document.createElement('div');
const ac1 = new AbortController();
const ac2 = new AbortController();
const info1 = createMediaLoadedInfo({ targetID: 'target-1' });
const info2 = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
manager.handleLoadEvent(
createMediaLoadedInfoEvent({ source: owner1, info: info1, signal: ac1.signal }),
);
manager.handleLoadEvent(
createMediaLoadedInfoEvent({ source: owner2, info: info2, signal: ac2.signal }),
);
expect(manager.get()).toBe(info2);
// The first owner's signal aborts, but it shouldn't blow away the entry
// owner2 has since taken over.
ac1.abort();
expect(manager.get()).toBe(info2);
});
it('should not fire condition state when clearing a non-selected target', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const source = document.createElement('div');
const ac = new AbortController();
const info = createMediaLoadedInfo({ targetID: 'target-1' });
// Select a different target than the one being cleared.
manager.setSelected('target-other');
manager.handleLoadEvent(
createMediaLoadedInfoEvent({ source, info, signal: ac.signal }),
);
vi.clearAllMocks();
ac.abort();
expect(api.getConditionStateManager().setState).not.toBeCalled();
});
});
describe('clear', () => {
it('should drop active entries and fire condition state when selected target had info', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
manager.set(info, owner);
vi.clearAllMocks();
manager.clear();
expect(manager.has()).toBeFalsy();
expect(api.getConditionStateManager().setState).toBeCalledWith({
mediaLoadedInfo: null,
});
});
it('should be a no-op on condition state when nothing is selected', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
manager.clear();
expect(api.getConditionStateManager().setState).not.toBeCalled();
});
it('should not fire condition state when the selected target has no info', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
manager.setSelected('target-1');
vi.clearAllMocks();
manager.clear();
expect(api.getConditionStateManager().setState).not.toBeCalled();
});
});
describe('initialize', () => {
it('should clear active state, last-known and selected', () => {
const api = createCardAPI();
const manager = new MediaLoadedInfoManager(api);
const owner = document.createElement('div');
const info = createMediaLoadedInfo({ targetID: 'target-1' });
manager.setSelected('target-1');
manager.set(info, owner);
manager.initialize();
expect(manager.get()).toBeNull();
expect(manager.getLastKnown()).toBeNull();
// Re-selecting `target-1` after initialize should produce no last-known.
manager.setSelected('target-1');
expect(manager.getLastKnown()).toBeNull();
});
});
});