Files
iptvnator/apps/web/src/app/settings/settings-section-scroll.directive.spec.ts
T
4gray e2300bea11 test(settings): split the settings spec along the facade seams (#1277)
settings.component.spec.ts was 1516 lines and the last settings file in the
max-lines baseline. The behaviour that moved into facades now has its own
specs, driven directly instead of through the rendered page.

- settings-app-update.facade.spec.ts: status polling/retry, bridge actions,
  release notes dialog, version messaging, dispose
- settings-epg.facade.spec.ts: refresh, clear flow, post-save re-fetch
- settings-playlist-reset.facade.spec.ts: summary, dialog, Electron progress,
  browser fallback, failure snackbar
- settings-backup.facade.spec.ts: desktop export, browser download fallback
- settings.component.spec.ts keeps the page shell, the facade lifecycle seam
  and runtime capabilities; settings.component.form.spec.ts takes hydration,
  section outputs, dashboard controls and submit
- settings-section-scroll.directive.spec.ts gives the directive its first spec
- shared TestBed fixtures live in settings/test-stubs/, kept out of both the
  app build (.stub.ts) and the coverage ratchet (test-stubs/)

105 settings tests, up from 94; every file is under the 400-line limit, so
settings.component.spec.ts leaves the baseline.
2026-07-27 08:31:14 +02:00

108 lines
3.8 KiB
TypeScript

import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { EpgRuntimeBridgeService } from '@iptvnator/epg/data-access';
import { SettingsContextService } from '@iptvnator/workspace/shell/util';
import { SettingsComponent } from './settings.component';
import { SettingsSectionScrollDirective } from './settings-section-scroll.directive';
import {
configureSettingsComponentTestBed,
createElectronStub,
createEpgBridgeStub,
stubSettingsSideEffects,
} from './test-stubs/settings-test-harness.stub';
interface SettingsSectionScrollDirectiveTestApi {
getScrollRoot(): HTMLElement | null;
}
/**
* The directive only makes sense against a real settings page, so this is an
* integration spec: it renders the component and drives the section
* navigation the workspace shell would trigger.
*/
describe('SettingsSectionScrollDirective', () => {
let fixture: ComponentFixture<SettingsComponent>;
let epgBridge: Partial<EpgRuntimeBridgeService>;
const originalElectron = window.electron;
beforeEach(waitForAsync(() => {
epgBridge = createEpgBridgeStub();
configureSettingsComponentTestBed(epgBridge);
}));
beforeEach(() => {
window.electron = createElectronStub();
});
afterEach(() => {
window.electron = originalElectron;
});
it('should scroll the selected navigation target within the workspace viewport', async () => {
jest.useFakeTimers();
try {
fixture = TestBed.createComponent(SettingsComponent);
const component = fixture.componentInstance;
const settingsContext = TestBed.inject(SettingsContextService);
const originalGetElementById =
document.getElementById.bind(document);
const scrollTo = jest.fn();
const scrollRoot = {
scrollTop: 96,
clientHeight: 885,
scrollHeight: 2469,
getBoundingClientRect: () =>
({
top: 56,
}) as DOMRect,
scrollTo,
} as unknown as HTMLElement;
stubSettingsSideEffects(component);
fixture.detectChanges();
const scrollDirective = fixture.debugElement
.query(By.directive(SettingsSectionScrollDirective))
.injector.get(SettingsSectionScrollDirective);
jest.spyOn(
scrollDirective as unknown as SettingsSectionScrollDirectiveTestApi,
'getScrollRoot'
).mockReturnValue(scrollRoot);
const getElementByIdSpy = jest
.spyOn(document, 'getElementById')
.mockImplementation((id: string) => {
if (id === 'about') {
return {
getBoundingClientRect: () =>
({
top: 2050,
height: 159,
}) as DOMRect,
} as HTMLElement;
}
return originalGetElementById(id);
});
await fixture.whenStable();
fixture.detectChanges();
settingsContext.navigateToSection('about');
fixture.detectChanges();
expect(getElementByIdSpy).toHaveBeenCalledWith('about');
expect(scrollTo).toHaveBeenCalledWith({
behavior: 'smooth',
top: 1488,
});
expect(settingsContext.pendingScrollTarget()).toBe('about');
jest.advanceTimersByTime(600);
expect(settingsContext.pendingScrollTarget()).toBeNull();
} finally {
jest.useRealTimers();
}
});
});