mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
* docs(playback): design shared controls setting * docs(playback): plan shared controls setting * feat(settings): persist shared web controls preference * test(settings): harden shared controls normalization coverage * feat(settings): expose shared web controls toggle * fix(settings): label shared controls toggle * feat(playback): resolve shared controls from settings * test(playback): cover shared controls setting * docs(playback): document shared controls preference * fix(playback): await settings before host creation * fix(settings): normalize shared controls updates
195 lines
6.5 KiB
TypeScript
195 lines
6.5 KiB
TypeScript
import { TestBed } from '@angular/core/testing';
|
|
import { SettingsStore } from '@iptvnator/services';
|
|
|
|
describe('app routes', () => {
|
|
let workspaceRoute: import('@angular/router').Route | undefined;
|
|
let workspaceChildren: Array<{
|
|
canActivate?: unknown[];
|
|
data?: Record<string, unknown>;
|
|
loadChildren?: unknown;
|
|
loadComponent?: unknown;
|
|
path?: string;
|
|
redirectTo?: unknown;
|
|
}> = [];
|
|
let resolveElectronOnlyGlobalSearchRoute: (
|
|
runtime: { isElectron: boolean },
|
|
router: { parseUrl: (url: string) => unknown }
|
|
) => unknown;
|
|
|
|
beforeAll(async () => {
|
|
jest.unstable_mockModule(
|
|
'@iptvnator/playlist/m3u/feature-player',
|
|
() => ({
|
|
createM3uWorkspaceRoutes: () => [
|
|
{
|
|
path: '',
|
|
pathMatch: 'full',
|
|
redirectTo: 'all',
|
|
},
|
|
{
|
|
path: 'favorites',
|
|
data: {
|
|
mode: 'favorites',
|
|
portalType: 'm3u',
|
|
defaultScope: 'playlist',
|
|
},
|
|
},
|
|
{
|
|
path: 'recent',
|
|
data: {
|
|
mode: 'recent',
|
|
portalType: 'm3u',
|
|
defaultScope: 'playlist',
|
|
},
|
|
},
|
|
{
|
|
path: ':view',
|
|
},
|
|
],
|
|
})
|
|
);
|
|
|
|
const appRoutes = await import('./app.routes');
|
|
const { routes } = appRoutes;
|
|
workspaceRoute = routes.find((route) => route.path === 'workspace');
|
|
workspaceChildren = workspaceRoute?.children ?? [];
|
|
resolveElectronOnlyGlobalSearchRoute =
|
|
appRoutes.resolveElectronOnlyGlobalSearchRoute;
|
|
});
|
|
|
|
afterEach(() => {
|
|
TestBed.resetTestingModule();
|
|
});
|
|
|
|
it('waits for settings before activating workspace children', async () => {
|
|
let releaseSettings!: () => void;
|
|
const settingsPending = new Promise<void>((resolve) => {
|
|
releaseSettings = resolve;
|
|
});
|
|
const loadSettings = jest.fn(() => settingsPending);
|
|
TestBed.configureTestingModule({
|
|
providers: [
|
|
{
|
|
provide: SettingsStore,
|
|
useValue: { loadSettings },
|
|
},
|
|
],
|
|
});
|
|
const settingsReadyResolver =
|
|
workspaceRoute?.resolve?.['settingsReady'];
|
|
|
|
expect(typeof settingsReadyResolver).toBe('function');
|
|
if (typeof settingsReadyResolver !== 'function') {
|
|
return;
|
|
}
|
|
|
|
const resolution = TestBed.runInInjectionContext(() =>
|
|
Promise.resolve(
|
|
(
|
|
settingsReadyResolver as import('@angular/router').ResolveFn<void>
|
|
)(
|
|
{} as import('@angular/router').ActivatedRouteSnapshot,
|
|
{} as import('@angular/router').RouterStateSnapshot
|
|
)
|
|
)
|
|
);
|
|
let resolved = false;
|
|
void resolution.then(() => {
|
|
resolved = true;
|
|
});
|
|
|
|
await Promise.resolve();
|
|
|
|
expect(loadSettings).toHaveBeenCalledTimes(1);
|
|
expect(resolved).toBe(false);
|
|
|
|
releaseSettings();
|
|
|
|
await expect(resolution).resolves.toBeUndefined();
|
|
expect(resolved).toBe(true);
|
|
});
|
|
|
|
it('lazy-loads M3U workspace routes through the feature boundary', async () => {
|
|
const playlistRoute = workspaceChildren.find(
|
|
(route) => route.path === 'playlists/:id'
|
|
);
|
|
const loadChildren = playlistRoute?.loadChildren as
|
|
| (() => Promise<typeof workspaceChildren>)
|
|
| undefined;
|
|
const m3uRoutes = (await loadChildren?.()) ?? [];
|
|
const defaultRoute = m3uRoutes.find((route) => route.path === '');
|
|
const favoritesRoute = m3uRoutes.find(
|
|
(route) => route.path === 'favorites'
|
|
);
|
|
const recentRoute = m3uRoutes.find((route) => route.path === 'recent');
|
|
const playerRoute = m3uRoutes.find((route) => route.path === ':view');
|
|
|
|
expect(defaultRoute?.redirectTo).toBe('all');
|
|
expect(favoritesRoute?.data).toEqual({
|
|
mode: 'favorites',
|
|
portalType: 'm3u',
|
|
defaultScope: 'playlist',
|
|
});
|
|
expect(recentRoute?.data).toEqual({
|
|
mode: 'recent',
|
|
portalType: 'm3u',
|
|
defaultScope: 'playlist',
|
|
});
|
|
|
|
expect(playerRoute).toBeDefined();
|
|
});
|
|
|
|
it('adds a shared /workspace/global-recent route', async () => {
|
|
const globalRecentRoute = workspaceChildren.find(
|
|
(route) => route.path === 'global-recent'
|
|
);
|
|
|
|
expect(globalRecentRoute?.data).toEqual({
|
|
mode: 'recent',
|
|
defaultScope: 'all',
|
|
});
|
|
expect(typeof globalRecentRoute?.loadComponent).toBe('function');
|
|
});
|
|
|
|
it('adds a routed /workspace/search global search view', async () => {
|
|
const globalSearchRoute = workspaceChildren.find(
|
|
(route) => route.path === 'search'
|
|
);
|
|
|
|
expect(globalSearchRoute?.data).toEqual({
|
|
isGlobalSearch: true,
|
|
});
|
|
expect(globalSearchRoute?.canActivate).toHaveLength(1);
|
|
expect(typeof globalSearchRoute?.loadComponent).toBe('function');
|
|
});
|
|
|
|
it('redirects the Electron-only global search route in the web runtime', async () => {
|
|
const redirectTree = { url: '/workspace/sources' };
|
|
const parseUrl = jest.fn().mockReturnValue(redirectTree);
|
|
|
|
const result = resolveElectronOnlyGlobalSearchRoute(
|
|
{ isElectron: false },
|
|
{ parseUrl }
|
|
);
|
|
|
|
expect(parseUrl).toHaveBeenCalledWith('/workspace/sources');
|
|
expect(result).toBe(redirectTree);
|
|
});
|
|
|
|
it('uses a dynamic redirect for the default /workspace child route', async () => {
|
|
const workspaceDefaultRoute = workspaceChildren.find(
|
|
(route) => route.path === ''
|
|
);
|
|
|
|
expect(typeof workspaceDefaultRoute?.redirectTo).toBe('function');
|
|
});
|
|
|
|
it('protects the dashboard route behind a visibility guard', async () => {
|
|
const dashboardRoute = workspaceChildren.find(
|
|
(route) => route.path === 'dashboard'
|
|
);
|
|
|
|
expect(dashboardRoute?.canActivate).toHaveLength(1);
|
|
});
|
|
});
|