Files
iptvnator/apps/web/src/app/app-startup-status.component.spec.ts
T
4grayandClaude Opus 5.5 fc32934ce3 fix(server-storage): merge user data on move, keep failed writes, sign-in route gate
- A merged playlist gains the browser document's favorites and history;
  Xtream values for one server entry are combined (lists unioned, maps
  merged, the server winning conflicts) and written once.
- A failed user-data write stays queued and survives a re-read; the
  page-hide flush waits for a batch still travelling.
- The start-up screen lets the sign-in route through whatever its check
  reports.
- The backend refuses IPTVNATOR_ACCESS_TOKEN without server storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:56 +02:00

86 lines
3.5 KiB
TypeScript

import { TestBed } from '@angular/core/testing';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { TranslateModule } from '@ngx-translate/core';
import {
PlaylistActions,
selectPlaylistsLoadFailed,
selectPlaylistsLoadingFlag,
} from '@iptvnator/m3u-state';
import { provideRouter, Router } from '@angular/router';
import { AppStartupStatusComponent } from './app-startup-status.component';
describe('AppStartupStatusComponent', () => {
beforeEach(() =>
TestBed.configureTestingModule({
imports: [AppStartupStatusComponent, TranslateModule.forRoot()],
providers: [
provideRouter([{ path: 'server-sign-in', children: [] }]),
provideMockStore({
selectors: [
{ selector: selectPlaylistsLoadingFlag, value: false },
{ selector: selectPlaylistsLoadFailed, value: false },
],
}),
],
})
);
it('keeps showing preparation while EPG reconciliation delays the route', () => {
const store = TestBed.inject(MockStore);
store.overrideSelector(selectPlaylistsLoadingFlag, true);
const fixture = TestBed.createComponent(AppStartupStatusComponent);
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector('[role="status"]')
).not.toBeNull();
fixture.componentRef.setInput('routeReady', true);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('section')).toBeNull();
});
it('does not expose an empty workspace before sources finish loading', () => {
const fixture = TestBed.createComponent(AppStartupStatusComponent);
fixture.componentRef.setInput('routeReady', true);
fixture.detectChanges();
expect(fixture.componentInstance.complete()).toBe(false);
const store = TestBed.inject(MockStore);
store.overrideSelector(selectPlaylistsLoadingFlag, true);
store.refreshState();
fixture.detectChanges();
expect(fixture.componentInstance.complete()).toBe(true);
});
it('shows the server sign-in page although sources could not load', async () => {
TestBed.inject(MockStore).overrideSelector(
selectPlaylistsLoadFailed,
true
);
const fixture = TestBed.createComponent(AppStartupStatusComponent);
fixture.componentRef.setInput('routeReady', true);
fixture.detectChanges();
expect(fixture.componentInstance.complete()).toBe(false);
await TestBed.inject(Router).navigateByUrl('/server-sign-in');
fixture.detectChanges();
expect(fixture.componentInstance.complete()).toBe(true);
expect(fixture.nativeElement.querySelector('section')).toBeNull();
});
it('offers a retry on failure without rendering raw storage errors', () => {
TestBed.inject(MockStore).overrideSelector(
selectPlaylistsLoadFailed,
true
);
const fixture = TestBed.createComponent(AppStartupStatusComponent);
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector('[role="alert"]')
).not.toBeNull();
expect(fixture.nativeElement.querySelector('mat-spinner')).toBeNull();
const dispatch = jest.spyOn(TestBed.inject(MockStore), 'dispatch');
fixture.nativeElement.querySelector('button').click();
expect(dispatch).toHaveBeenCalledWith(PlaylistActions.loadPlaylists());
});
});