feat: Add richer card theme support (#1809)

This commit is contained in:
Dermot Duffy
2025-01-07 19:54:13 -08:00
committed by GitHub
parent f7ecb91578
commit 278560b5ac
65 changed files with 1187 additions and 352 deletions
+125 -70
View File
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StyleManager } from '../../src/card-controller/style-manager';
import { FrigateCardView } from '../../src/config/types';
import { createCardAPI, createConfig, createHASS, createView } from '../test-utils';
import { FrigateCardView, ThemeName } from '../../src/config/types';
import { createCardAPI, createConfig, createView } from '../test-utils';
// @vitest-environment jsdom
describe('StyleManager', () => {
@@ -9,97 +9,45 @@ describe('StyleManager', () => {
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', () => {
describe('should set dimmable', () => {
it('should be dimmable when dim is true', () => {
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',
dim: true,
},
}),
);
const manager = new StyleManager(api);
manager.setLightOrDarkMode();
manager.updateFromConfig();
expect(element.getAttribute('dark')).toBeNull();
expect(element.getAttribute('dimmable')).not.toBeNull();
});
it('dark mode explicitly set', () => {
it('should not be dimmable when dim is false', () => {
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',
dim: false,
},
}),
);
const manager = new StyleManager(api);
manager.setLightOrDarkMode();
manager.updateFromConfig();
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();
expect(element.getAttribute('dimmable')).toBeNull();
});
});
describe('setExpandedMode', () => {
describe('should set expanded mode', () => {
it('with no view or known media', () => {
const api = createCardAPI();
const element = document.createElement('div');
@@ -189,14 +137,14 @@ describe('StyleManager', () => {
});
});
describe('setMinMaxHeight', () => {
describe('should set min and max height', () => {
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();
manager.updateFromConfig();
expect(element.style.getPropertyValue('--frigate-card-max-height')).toBeFalsy();
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBeFalsy();
@@ -215,13 +163,13 @@ describe('StyleManager', () => {
);
const manager = new StyleManager(api);
manager.setMinMaxHeight();
manager.updateFromConfig();
expect(element.style.getPropertyValue('--frigate-card-height')).toBe('800px');
});
});
describe('setPerformance', () => {
describe('should set performance', () => {
it('no styles set', () => {
const api = createCardAPI();
const element = document.createElement('div');
@@ -229,7 +177,7 @@ describe('StyleManager', () => {
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue({});
const manager = new StyleManager(api);
manager.setPerformance();
manager.updateFromConfig();
expect(
element.style.getPropertyValue('--frigate-card-css-box-shadow'),
@@ -255,7 +203,7 @@ describe('StyleManager', () => {
);
const manager = new StyleManager(api);
manager.setPerformance();
manager.updateFromConfig();
expect(element.style.getPropertyValue('--frigate-card-css-box-shadow')).toEqual(
'none',
@@ -411,4 +359,111 @@ describe('StyleManager', () => {
expect(manager.getAspectRatioStyle()).toEqual({ 'aspect-ratio': '4 / 3' });
});
});
describe('should apply themes', () => {
it('should not apply themes without a config', () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
const manager = new StyleManager(api);
manager.applyTheme();
expect(element.getAttribute('themes')).toBeNull();
});
describe('should apply named theme', () => {
it.each([
['light' as const],
['dark' as const],
['traditional' as const],
['ha' as const],
])('%s', (theme: ThemeName) => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
theme: {
themes: [theme],
},
},
}),
);
const manager = new StyleManager(api);
manager.applyTheme();
expect(element.getAttribute('themes')).toBe(theme);
});
});
it('should apply multiple themes', () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
theme: {
themes: ['light', 'traditional'],
},
},
}),
);
const manager = new StyleManager(api);
manager.applyTheme();
expect(element.getAttribute('themes')).toBe('light traditional');
});
it('should treat empty themes list as default', () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
theme: {
themes: [],
},
},
}),
);
const manager = new StyleManager(api);
manager.applyTheme();
expect(element.getAttribute('themes')).toBe('traditional');
});
it('should apply overrides', () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
theme: {
overrides: {
'--test-key': 'test-value',
},
},
},
}),
);
const manager = new StyleManager(api);
manager.applyTheme();
expect(element.style.getPropertyValue('--test-key')).toBe('test-value');
});
});
});