Files
advanced-camera-card/tests/components-lib/status-bar-controller.test.ts
T

464 lines
14 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { StatusBarController } from '../../src/components-lib/status-bar-controller';
import {
statusBarConfigSchema,
type StatusBarConfig,
} from '../../src/config/schema/status-bar';
import { createInteractionActionEvent, createLitElement } from '../test-utils';
const createConfig = (config?: unknown): StatusBarConfig => {
return statusBarConfigSchema.parse(config);
};
// @vitest-environment jsdom
describe('StatusBarController', () => {
describe('should set config', () => {
it('should set config', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
const config = createConfig({
position: 'top',
style: 'hover',
height: 50,
});
controller.setConfig(config);
expect(controller.getConfig()).toEqual(config);
expect(
host.style.getPropertyValue('--advanced-camera-card-status-bar-height'),
).toBe('50px');
expect(host.getAttribute('data-style')).toBe('hover');
expect(host.getAttribute('data-position')).toBe('top');
expect(host.requestUpdate).toHaveBeenCalled();
});
it('should not hide when not in popup style', () => {
const host = createLitElement();
host.toggleAttribute('hide', true);
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
position: 'top',
style: 'hover',
height: 50,
}),
);
expect(host.getAttribute('hide')).toBe(null);
});
it('should not show when in popup style', () => {
const host = createLitElement();
host.toggleAttribute('hide', true);
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
position: 'top',
style: 'popup',
height: 50,
}),
);
expect(host.getAttribute('hide')).not.toBe(null);
});
});
describe('should set items', () => {
beforeAll(() => {
vi.useFakeTimers();
});
afterAll(() => {
vi.useRealTimers();
});
it('set/get basic items', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
const items = [
{
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Test',
},
];
controller.setItems(items);
expect(controller.getRenderItems()).toEqual(items);
});
it('should order items', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
const item1 = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
priority: 40,
};
const item2 = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 2',
priority: 10,
};
const item3 = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 3',
priority: 60,
};
const item4 = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 4',
priority: undefined,
};
controller.setItems([item1, item2, item3, item4]);
expect(controller.getRenderItems()).toEqual([item3, item4, item1, item2]);
});
it('should treat exclusive items exclusively', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
const item1 = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
priority: 100,
};
const exclusiveItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 2',
priority: 1,
exclusive: true,
};
controller.setItems([item1, exclusiveItem]);
expect(controller.getRenderItems()).toEqual([exclusiveItem]);
});
describe('should recognize sufficient items', () => {
it('with sufficient item', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
const insufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
sufficient: false,
};
const sufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 2',
sufficient: true,
};
controller.setItems([insufficientItem, sufficientItem]);
expect(controller.getRenderItems()).toEqual([insufficientItem, sufficientItem]);
expect(controller.shouldRender()).toBeTruthy();
});
it('without sufficient item', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
const insufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
sufficient: false,
};
const sufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 2',
sufficient: false,
};
controller.setItems([insufficientItem, sufficientItem]);
expect(controller.getRenderItems()).toEqual([insufficientItem, sufficientItem]);
expect(controller.shouldRender()).toBeFalsy();
});
});
describe('should deal with popup styles correctly', () => {
it('should show from empty to sufficient', () => {
const host = createLitElement();
host.toggleAttribute('hide', true);
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const sufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
priority: 100,
sufficient: true,
};
controller.setItems([sufficientItem]);
expect(host.getAttribute('hide')).toBe(null);
});
it('should not show from empty to insufficient', () => {
const host = createLitElement();
host.toggleAttribute('hide', true);
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const insufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
priority: 100,
sufficient: false,
};
controller.setItems([insufficientItem]);
expect(host.getAttribute('hide')).not.toBeNull();
});
it('should show from sufficient to different sufficient', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const sufficientString = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'String',
priority: 100,
sufficient: true,
};
const sufficientIcon = {
type: 'custom:advanced-camera-card-status-bar-icon' as const,
icon: 'Icon',
priority: 100,
sufficient: true,
};
const sufficientImage = {
type: 'custom:advanced-camera-card-status-bar-image' as const,
image: 'Image',
priority: 100,
sufficient: true,
};
controller.setItems([sufficientString]);
// Emulate the popup being hidden.
host.toggleAttribute('hide', true);
controller.setItems([sufficientIcon]);
expect(host.getAttribute('hide')).toBe(null);
// Emulate the popup being hidden.
host.toggleAttribute('hide', true);
controller.setItems([sufficientImage]);
expect(host.getAttribute('hide')).toBe(null);
});
it('should not start popup timer when permanent items are present', () => {
const host = createLitElement();
host.toggleAttribute('hide', true);
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const permanentItem = {
type: 'custom:advanced-camera-card-status-bar-icon' as const,
icon: 'mdi:alert',
sufficient: true,
permanent: true,
};
controller.setItems([permanentItem]);
expect(host.getAttribute('hide')).toBe(null);
// Timer should not hide the bar.
vi.advanceTimersByTime(10000);
expect(host.getAttribute('hide')).toBe(null);
});
it('should start popup timer when permanent items are removed', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const permanentItem = {
type: 'custom:advanced-camera-card-status-bar-icon' as const,
icon: 'mdi:alert',
sufficient: true,
permanent: true,
};
const nonPermanentItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Title',
sufficient: true,
};
// Start with permanent item -- bar stays visible.
controller.setItems([permanentItem, nonPermanentItem]);
vi.advanceTimersByTime(10000);
expect(host.getAttribute('hide')).toBe(null);
// Remove permanent item -- popup timer starts.
controller.setItems([nonPermanentItem]);
expect(host.getAttribute('hide')).toBe(null);
vi.advanceTimersByTime(3000);
expect(host.getAttribute('hide')).not.toBe(null);
});
it('should start popup timer when permanent item is removed without changing sufficient items', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const sufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Title',
sufficient: true,
};
// A permanent item that is NOT sufficient -- removing it does not
// change the sufficient-values set, so the popup timer takes the
// dedicated permanent-removal branch.
const permanentInsufficientItem = {
type: 'custom:advanced-camera-card-status-bar-icon' as const,
icon: 'mdi:alert',
sufficient: false,
permanent: true,
};
controller.setItems([sufficientItem, permanentInsufficientItem]);
vi.advanceTimersByTime(10000);
expect(host.getAttribute('hide')).toBe(null);
// Remove the permanent (insufficient) item -- sufficient values are
// unchanged, but the popup timer must still start.
controller.setItems([sufficientItem]);
expect(host.getAttribute('hide')).toBe(null);
vi.advanceTimersByTime(3000);
expect(host.getAttribute('hide')).not.toBe(null);
});
it('should hide popup after expiry', () => {
const host = createLitElement();
const controller = new StatusBarController(host);
controller.setConfig(
createConfig({
style: 'popup',
}),
);
const sufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item 1',
priority: 100,
sufficient: true,
};
controller.setItems([sufficientItem]);
expect(host.getAttribute('hide')).toBe(null);
vi.advanceTimersByTime(1000);
expect(host.getAttribute('hide')).toBe(null);
vi.advanceTimersByTime(2000);
expect(host.getAttribute('hide')).not.toBe(null);
});
});
});
describe('should handle actions', () => {
it('should bail without action', () => {
const host = createLitElement();
const handler = vi.fn();
host.addEventListener('advanced-camera-card:action:execution-request', handler);
const controller = new StatusBarController(host);
controller.actionHandler(createInteractionActionEvent('tap'));
expect(handler).not.toHaveBeenCalled();
});
it('should request action execution', () => {
const host = createLitElement();
const handler = vi.fn();
host.addEventListener('advanced-camera-card:action:execution-request', handler);
const controller = new StatusBarController(host);
const action = {
action: 'fire-dom-event' as const,
};
const tapActionConfig = {
tap_action: action,
};
controller.actionHandler(createInteractionActionEvent('tap'), tapActionConfig);
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
detail: { actions: [action], config: tapActionConfig },
}),
);
});
});
describe('auto-hide', () => {
const sufficientItem = {
type: 'custom:advanced-camera-card-status-bar-string' as const,
string: 'Item',
sufficient: true,
};
it('should render with a config and no auto-hide state', () => {
const controller = new StatusBarController(createLitElement());
controller.setConfig(createConfig({ auto_hide: ['call'] }));
controller.setItems([sufficientItem]);
expect(controller.shouldRender()).toBe(true);
});
it('should render when no auto-hide condition is active', () => {
const controller = new StatusBarController(createLitElement());
controller.setConfig(createConfig({ auto_hide: ['call'] }));
controller.setItems([sufficientItem]);
controller.setAutoHideState({ call: false, casting: true });
expect(controller.shouldRender()).toBe(true);
});
it('should not render when a configured auto-hide condition is active', () => {
const controller = new StatusBarController(createLitElement());
controller.setConfig(createConfig({ auto_hide: ['call'] }));
controller.setItems([sufficientItem]);
controller.setAutoHideState({ call: true, casting: false });
expect(controller.shouldRender()).toBe(false);
});
});
});