* fix: Replace `max_height` with `height` BREAKING CHANGE: This entirely removes `min_height`, and replaces `max_height` with `height`. The behavior is obviously not exactly the same, but the prior behavior did not actually work correctly. CSS does not limit the height of child elements that are `100%` of their parents height if the parent does not have an explicit height set (this caused spillage over the set max height). As such, it's simpler to just allow the user to set the actual height of the card should they need to do so, with a (hopefully) minor loss of flexibility. * Test fixes
415 lines
14 KiB
TypeScript
415 lines
14 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { FrigateCardView } from '../../src/config/types';
|
|
import { StyleManager } from '../../src/card-controller/style-manager';
|
|
import { createCardAPI, createConfig, createHASS, createView } from '../test-utils';
|
|
|
|
// @vitest-environment jsdom
|
|
describe('StyleManager', () => {
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
});
|
|
|
|
describe('setLightOrDarkMode', () => {
|
|
it('dark mode unspecified', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setLightOrDarkMode();
|
|
|
|
expect(element.getAttribute('dark')).toBeNull();
|
|
});
|
|
|
|
it('dark mode explicitly off', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
dark_mode: 'off',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setLightOrDarkMode();
|
|
|
|
expect(element.getAttribute('dark')).toBeNull();
|
|
});
|
|
|
|
it('dark mode explicitly set', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
dark_mode: 'on',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setLightOrDarkMode();
|
|
|
|
expect(element.getAttribute('dark')).not.toBeNull();
|
|
});
|
|
|
|
it('dark mode auto without interaction', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
dark_mode: 'auto',
|
|
},
|
|
}),
|
|
);
|
|
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setLightOrDarkMode();
|
|
|
|
expect(element.getAttribute('dark')).not.toBeNull();
|
|
});
|
|
|
|
it('dark mode auto with HA dark mode', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
dark_mode: 'auto',
|
|
},
|
|
}),
|
|
);
|
|
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(true);
|
|
const hass = createHASS();
|
|
hass.themes.darkMode = true;
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setLightOrDarkMode();
|
|
|
|
expect(element.getAttribute('dark')).not.toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('setExpandedMode', () => {
|
|
it('with no view or known media', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue(null);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setExpandedMode();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
|
'unset',
|
|
);
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe(
|
|
'var(--frigate-card-expand-max-width)',
|
|
);
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
|
'var(--frigate-card-expand-max-height)',
|
|
);
|
|
});
|
|
|
|
it('with view but without media', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
const view = createView({ view: 'media', displayMode: 'single' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue(null);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setExpandedMode();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
|
'unset',
|
|
);
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe('none');
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
|
'none',
|
|
);
|
|
});
|
|
|
|
it('with view and media', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
const view = createView({ view: 'media', displayMode: 'single' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue({
|
|
width: 800,
|
|
height: 600,
|
|
});
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setExpandedMode();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
|
'800 / 600',
|
|
);
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe('none');
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
|
'none',
|
|
);
|
|
});
|
|
|
|
it('with view and grid display mode', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
const view = createView({ view: 'media', displayMode: 'grid' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue({
|
|
width: 800,
|
|
height: 600,
|
|
});
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setExpandedMode();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
|
'800 / 600',
|
|
);
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe(
|
|
'var(--frigate-card-expand-max-width)',
|
|
);
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
|
'var(--frigate-card-expand-max-height)',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('setMinMaxHeight', () => {
|
|
it('without a config', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setMinMaxHeight();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-max-height')).toBeFalsy();
|
|
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBeFalsy();
|
|
});
|
|
|
|
it('with a config', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
dimensions: {
|
|
height: '800px',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setMinMaxHeight();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-height')).toBe('800px');
|
|
});
|
|
});
|
|
|
|
describe('setPerformance', () => {
|
|
it('no styles set', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue({});
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setPerformance();
|
|
|
|
expect(
|
|
element.style.getPropertyValue('--frigate-card-css-box-shadow'),
|
|
).toBeFalsy();
|
|
expect(
|
|
element.style.getPropertyValue('--frigate-card-css-border-radius'),
|
|
).toBeFalsy();
|
|
});
|
|
|
|
it('valid styles set', () => {
|
|
const api = createCardAPI();
|
|
const element = document.createElement('div');
|
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
|
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue(
|
|
createConfig({
|
|
performance: {
|
|
style: {
|
|
box_shadow: false,
|
|
border_radius: true,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
manager.setPerformance();
|
|
|
|
expect(element.style.getPropertyValue('--frigate-card-css-box-shadow')).toEqual(
|
|
'none',
|
|
);
|
|
expect(
|
|
element.style.getPropertyValue('--frigate-card-css-border-radius'),
|
|
).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('getAspectRatioStyle', () => {
|
|
it('without config or view', () => {
|
|
const api = createCardAPI();
|
|
const manager = new StyleManager(api);
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '16 / 9' });
|
|
});
|
|
|
|
it('should be auto with unconstrained aspect ratio', () => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: 'media' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
dimensions: {
|
|
aspect_ratio_mode: 'unconstrained',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
|
});
|
|
|
|
it('should be auto in fullscreen', () => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: 'media' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
vi.mocked(api.getFullscreenManager().isInFullscreen).mockReturnValue(true);
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
|
});
|
|
|
|
it('should be auto when expanded', () => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: 'media' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
vi.mocked(api.getExpandManager().isExpanded).mockReturnValue(true);
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
|
});
|
|
|
|
it('should be auto when there is yet to be a view', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(null);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
|
});
|
|
|
|
describe('should be auto when dynamic in certain views', () => {
|
|
it.each([
|
|
['clip' as const],
|
|
['diagnostics' as const],
|
|
['image' as const],
|
|
['media' as const],
|
|
['live' as const],
|
|
['recording' as const],
|
|
['snapshot' as const],
|
|
['timeline' as const],
|
|
])('%s', (viewName: FrigateCardView) => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: viewName });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
dimensions: {
|
|
aspect_ratio_mode: 'dynamic',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': 'auto' });
|
|
});
|
|
});
|
|
|
|
describe('should be enforced when dynamic in certain views', () => {
|
|
it.each([['clips' as const], ['recordings' as const], ['snapshots' as const]])(
|
|
'%s',
|
|
(viewName: FrigateCardView) => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: viewName });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
dimensions: {
|
|
aspect_ratio_mode: 'dynamic',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '16 / 9' });
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('should use media dimensions in dynamic', () => {
|
|
it.each([['clips' as const], ['recordings' as const], ['snapshots' as const]])(
|
|
'%s',
|
|
(viewName: FrigateCardView) => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: viewName });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
dimensions: {
|
|
aspect_ratio_mode: 'dynamic',
|
|
},
|
|
}),
|
|
);
|
|
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue({
|
|
width: 800,
|
|
height: 600,
|
|
});
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({
|
|
'aspect-ratio': '800 / 600',
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
it('should respect default aspect ratio', () => {
|
|
const api = createCardAPI();
|
|
const view = createView({ view: 'clips' });
|
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
dimensions: {
|
|
aspect_ratio_mode: 'dynamic',
|
|
aspect_ratio: '4:3',
|
|
},
|
|
}),
|
|
);
|
|
const manager = new StyleManager(api);
|
|
|
|
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '4 / 3' });
|
|
});
|
|
});
|
|
});
|