mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 13:16:16 -08:00
- 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>
86 lines
3.5 KiB
TypeScript
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());
|
|
});
|
|
});
|