refactor(theme): remove obsolete compatibility

This commit is contained in:
nessar committed 2026-08-28 14:56:45 +02:00
1 parent c19b62ff85
commit dfb25b249b
3 files changed
+55 -137

No files matched your search

@@ -6,47 +6,36 @@ import { vi } from 'vitest';
class ControllableMatchMedia { class ControllableMatchMedia {
matches: boolean; matches: boolean;
readonly media = '(prefers-color-scheme: light)'; readonly media = '(prefers-color-scheme: light)';
onchange: ((this: MediaQueryList, event: MediaQueryListEvent) => unknown) | null = null; private listener?: (event: MediaQueryListEvent) => void;
private readonly listeners = new Set<(event: MediaQueryListEvent) => void>();
constructor(matches: boolean) { constructor(matches: boolean) {
this.matches = matches; this.matches = matches;
} }
addEventListener(_type: string, listener: EventListenerOrEventListenerObject | null): void { addEventListener(_type: string, listener: (event: MediaQueryListEvent) => void): void {
if (typeof listener === 'function') { this.listener = listener;
this.listeners.add(listener as (event: MediaQueryListEvent) => void);
}
}
removeEventListener(_type: string, listener: EventListenerOrEventListenerObject | null): void {
if (typeof listener === 'function') {
this.listeners.delete(listener as (event: MediaQueryListEvent) => void);
}
}
addListener(listener: (this: MediaQueryList, event: MediaQueryListEvent) => unknown): void {
this.listeners.add(listener);
}
removeListener(listener: (this: MediaQueryList, event: MediaQueryListEvent) => unknown): void {
this.listeners.delete(listener);
}
dispatchEvent(_event: Event): boolean {
return true;
} }
change(matches: boolean): void { change(matches: boolean): void {
this.matches = matches; this.matches = matches;
const event = { matches, media: this.media } as MediaQueryListEvent; this.listener?.({ matches, media: this.media } as MediaQueryListEvent);
this.listeners.forEach((listener) => listener(event));
this.onchange?.call(this as unknown as MediaQueryList, event);
} }
} }
function expectTheme(
button: HTMLButtonElement,
icon: string,
description: string,
isLight: boolean,
): void {
expect(button.querySelector('mat-icon')?.getAttribute('data-mat-icon-name')).toBe(icon);
expect(button.getAttribute('aria-label')).toBe(description);
expect(button.getAttribute('title')).toBe(description);
expect(document.body.classList.contains('light-mode')).toBe(isLight);
expect(document.body.classList.contains('dark-mode')).toBe(!isLight);
}
describe('DarkLightModeButton', () => { describe('DarkLightModeButton', () => {
let component: DarkLightModeButton;
let fixture: ComponentFixture<DarkLightModeButton>; let fixture: ComponentFixture<DarkLightModeButton>;
let mediaQuery: ControllableMatchMedia; let mediaQuery: ControllableMatchMedia;
@@ -60,7 +49,6 @@ describe('DarkLightModeButton', () => {
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(DarkLightModeButton); fixture = TestBed.createComponent(DarkLightModeButton);
component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -69,23 +57,14 @@ describe('DarkLightModeButton', () => {
document.body.classList.remove('light-mode', 'dark-mode'); document.body.classList.remove('light-mode', 'dark-mode');
}); });
it('should create', () => {
expect(component).toBeTruthy();
});
it('starts in System and resolves its initial appearance from the browser preference', () => { it('starts in System and resolves its initial appearance from the browser preference', () => {
const button = fixture.nativeElement.querySelector('button') as HTMLButtonElement; const button = fixture.nativeElement.querySelector('button') as HTMLButtonElement;
const icon = button.querySelector('mat-icon'); expectTheme(
button,
expect(icon?.getAttribute('data-mat-icon-name')).toBe('brightness_auto'); 'brightness_auto',
expect(button.getAttribute('aria-label')).toBe(
'Theme: System (currently Light). Switch to Light theme', 'Theme: System (currently Light). Switch to Light theme',
true,
); );
expect(button.getAttribute('title')).toBe(
'Theme: System (currently Light). Switch to Light theme',
);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
}); });
it('cycles System to Light to Dark and wraps back to System', () => { it('cycles System to Light to Dark and wraps back to System', () => {
@@ -93,45 +72,30 @@ describe('DarkLightModeButton', () => {
button.click(); button.click();
fixture.detectChanges(); fixture.detectChanges();
expect(button.querySelector('mat-icon')?.getAttribute('data-mat-icon-name')).toBe( expectTheme(
button,
'light_mode', 'light_mode',
);
expect(button.getAttribute('aria-label')).toBe(
'Theme: Light (currently Light). Switch to Dark theme', 'Theme: Light (currently Light). Switch to Dark theme',
true,
); );
expect(button.getAttribute('title')).toBe(
'Theme: Light (currently Light). Switch to Dark theme',
);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
button.click(); button.click();
fixture.detectChanges(); fixture.detectChanges();
expect(button.querySelector('mat-icon')?.getAttribute('data-mat-icon-name')).toBe( expectTheme(
button,
'dark_mode', 'dark_mode',
);
expect(button.getAttribute('aria-label')).toBe(
'Theme: Dark (currently Dark). Switch to System theme', 'Theme: Dark (currently Dark). Switch to System theme',
false,
); );
expect(button.getAttribute('title')).toBe(
'Theme: Dark (currently Dark). Switch to System theme',
);
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
button.click(); button.click();
fixture.detectChanges(); fixture.detectChanges();
expect(button.querySelector('mat-icon')?.getAttribute('data-mat-icon-name')).toBe( expectTheme(
button,
'brightness_auto', 'brightness_auto',
);
expect(button.getAttribute('aria-label')).toBe(
'Theme: System (currently Light). Switch to Light theme', 'Theme: System (currently Light). Switch to Light theme',
true,
); );
expect(button.getAttribute('title')).toBe(
'Theme: System (currently Light). Switch to Light theme',
);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
}); });
it('follows live browser changes while System is selected', () => { it('follows live browser changes while System is selected', () => {
@@ -139,22 +103,21 @@ describe('DarkLightModeButton', () => {
mediaQuery.change(false); mediaQuery.change(false);
fixture.detectChanges(); fixture.detectChanges();
expect(button.getAttribute('aria-label')).toBe( expectTheme(
button,
'brightness_auto',
'Theme: System (currently Dark). Switch to Light theme', 'Theme: System (currently Dark). Switch to Light theme',
); false,
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
expect(document.body.classList.contains('light-mode')).not.toBe(
document.body.classList.contains('dark-mode'),
); );
mediaQuery.change(true); mediaQuery.change(true);
fixture.detectChanges(); fixture.detectChanges();
expect(button.getAttribute('aria-label')).toBe( expectTheme(
button,
'brightness_auto',
'Theme: System (currently Light). Switch to Light theme', 'Theme: System (currently Light). Switch to Light theme',
true,
); );
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
}); });
it('keeps explicit modes fixed and uses the latest browser value when returning to System', () => { it('keeps explicit modes fixed and uses the latest browser value when returning to System', () => {
@@ -164,29 +127,32 @@ describe('DarkLightModeButton', () => {
fixture.detectChanges(); fixture.detectChanges();
mediaQuery.change(false); mediaQuery.change(false);
fixture.detectChanges(); fixture.detectChanges();
expect(button.getAttribute('aria-label')).toBe( expectTheme(
button,
'light_mode',
'Theme: Light (currently Light). Switch to Dark theme', 'Theme: Light (currently Light). Switch to Dark theme',
true,
); );
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
button.click(); button.click();
fixture.detectChanges(); fixture.detectChanges();
mediaQuery.change(true); mediaQuery.change(true);
fixture.detectChanges(); fixture.detectChanges();
expect(button.getAttribute('aria-label')).toBe( expectTheme(
button,
'dark_mode',
'Theme: Dark (currently Dark). Switch to System theme', 'Theme: Dark (currently Dark). Switch to System theme',
false,
); );
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
mediaQuery.change(false); mediaQuery.change(false);
button.click(); button.click();
fixture.detectChanges(); fixture.detectChanges();
expect(button.getAttribute('aria-label')).toBe( expectTheme(
button,
'brightness_auto',
'Theme: System (currently Dark). Switch to Light theme', 'Theme: System (currently Dark). Switch to Light theme',
false,
); );
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
}); });
}); });
-42
View File
@@ -1,42 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { ThemeService } from './theme-service';
describe('ThemeService', () => {
let service: ThemeService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(ThemeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with system preference', () => {
expect(service.isLightMode()).toBeDefined();
});
it('should toggle theme', () => {
const initialMode = service.isLightMode();
service.toggleTheme();
expect(service.isLightMode()).toBe(!initialMode);
});
it('should force light mode when specified', () => {
service.toggleTheme(true);
expect(service.isLightMode()).toBe(true);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
});
it('should force dark mode when specified', () => {
service.toggleTheme(false);
expect(service.isLightMode()).toBe(false);
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
});
});
+5 -11
View File
@@ -13,7 +13,6 @@ export class ThemeService {
const preference = this.selectedPreference(); const preference = this.selectedPreference();
return preference === 'system' ? (this.systemIsLight() ? 'light' : 'dark') : preference; return preference === 'system' ? (this.systemIsLight() ? 'light' : 'dark') : preference;
}); });
readonly isLightMode = computed(() => this.effectiveTheme() === 'light');
readonly nextThemePreference = computed(() => this.nextPreference(this.selectedPreference())); readonly nextThemePreference = computed(() => this.nextPreference(this.selectedPreference()));
constructor() { constructor() {
@@ -26,18 +25,13 @@ export class ThemeService {
}); });
} }
setThemePreference(preference: ThemePreference): void { toggleTheme(): void {
this.selectedPreference.set(preference); this.setThemePreference(this.nextThemePreference());
this.applyEffectiveTheme();
} }
toggleTheme(forceLight?: boolean): void { private setThemePreference(preference: ThemePreference): void {
if (forceLight !== undefined) { this.selectedPreference.set(preference);
this.setThemePreference(forceLight ? 'light' : 'dark'); this.applyEffectiveTheme();
return;
}
this.setThemePreference(this.nextThemePreference());
} }
private nextPreference(preference: ThemePreference): ThemePreference { private nextPreference(preference: ThemePreference): ThemePreference {