mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
chore(deps): upgrade Angular to 22.1 and Nx to 23.2 (#1603)
* chore(deps): upgrade Angular to 22.1 and Nx to 23.2 * fix(deps): complete Angular migrations after rebasing on master * fix(ci): use the Node pin for Windows runtime refresh * docs(deps): synchronize the workspace-shell Node requirements
This commit is contained in:
1 parent
ef3f98d026
commit
e9eca1c386
200 files changed
+3515
-6890
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: internal
|
||||
area: deps
|
||||
---
|
||||
|
||||
The application uses Angular 22 and Nx 23 with updated build and test tooling.
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Resolve Linux runtime toolchain cache key
|
||||
id: linux-runtime-cache-key
|
||||
@@ -379,7 +379,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Linux system dependencies
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Refresh pin when it approaches upstream retention
|
||||
id: refresh
|
||||
|
||||
@@ -19,10 +19,12 @@ This file provides guidance to coding agents working in this repository.
|
||||
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
|
||||
- Keep `nx` and every official `@nx/*` package on the same exact version; run
|
||||
`pnpm run deps:nx:validate` after dependency updates.
|
||||
- Vite `7.3.6`, resolved through Angular's build tooling, is patched with
|
||||
bounded transform prefilters and the upstream precise matchers in
|
||||
`patches/vite@7.3.6.patch`. Keep the patch until supported Angular tooling
|
||||
resolves a Vite version containing the fix, and run `pnpm run deps:vite:test`
|
||||
- Use the Node version in `.nvmrc` for development and CI. Angular 22 requires
|
||||
Node `^22.22.3 || ^24.15.0` and TypeScript `>=6.0 <6.1` in this workspace.
|
||||
- Vite `8.1.5`, resolved through Angular's build tooling, retains upstream
|
||||
precise matchers and adds bounded raw-code prefilters through
|
||||
`patches/vite@8.1.5.patch`. Keep the patch until upstream also preserves
|
||||
comment-bearing asset/worker expressions; run `pnpm run deps:vite:test`
|
||||
after related dependency updates.
|
||||
- `app-builder-lib` `26.15.7` (electron-builder's macOS signing) is patched in
|
||||
`patches/app-builder-lib@26.15.7.patch` with the upstream backport
|
||||
@@ -30,11 +32,16 @@ This file provides guidance to coding agents working in this repository.
|
||||
must receive the temporary keychain's own password, not the `.p12` import
|
||||
password. macOS runner images since `macos-26-arm64` 20260831 verify that
|
||||
password, and `Build on macos arm64` failed with `SecKeychainUnlock: The user
|
||||
name or passphrase you entered is not correct`. Keep the patch until
|
||||
name or passphrase you entered is not correct`. Keep the patch until
|
||||
electron-builder resolves an `app-builder-lib` containing the fix (26.16.1+),
|
||||
and run `pnpm run deps:electron-builder:test` after related dependency
|
||||
updates — the test fails when the patched version no longer matches the
|
||||
installed one.
|
||||
- `nx-electron@22.0.0` uses a local Nx 23 export-path patch and an explicit
|
||||
`webpack-node-externals` package extension. Scoped peer allowances for it
|
||||
and `ngx-indexed-db@22.0.0` live in `pnpm-workspace.yaml`; they are project
|
||||
compatibility bridges, not upstream support declarations. See
|
||||
`docs/architecture/nx-workspace-boundaries.md` before removing them.
|
||||
- A directory holding files consumed by other projects must be an Nx project.
|
||||
Nx builds its graph from TypeScript imports only, so a relative SCSS `@use`
|
||||
across project roots creates no edge and the imported file lands in no task
|
||||
|
||||
@@ -97,10 +97,12 @@ pnpm nx show projects
|
||||
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
|
||||
- Keep `nx` and every official `@nx/*` package on the same exact version; run
|
||||
`pnpm run deps:nx:validate` after dependency updates.
|
||||
- Vite `7.3.6`, resolved through Angular's build tooling, is patched with
|
||||
bounded transform prefilters and the upstream precise matchers in
|
||||
`patches/vite@7.3.6.patch`. Keep the patch until supported Angular tooling
|
||||
resolves a Vite version containing the fix, and run `pnpm run deps:vite:test`
|
||||
- Use the Node version in `.nvmrc` for development and CI. Angular 22 requires
|
||||
Node `^22.22.3 || ^24.15.0` and TypeScript `>=6.0 <6.1` in this workspace.
|
||||
- Vite `8.1.5`, resolved through Angular's build tooling, retains upstream
|
||||
precise matchers and adds bounded raw-code prefilters through
|
||||
`patches/vite@8.1.5.patch`. Keep the patch until upstream also preserves
|
||||
comment-bearing asset/worker expressions; run `pnpm run deps:vite:test`
|
||||
after related dependency updates.
|
||||
- `app-builder-lib` `26.15.7` (electron-builder's macOS signing) is patched in
|
||||
`patches/app-builder-lib@26.15.7.patch` with the upstream backport
|
||||
@@ -108,11 +110,16 @@ pnpm nx show projects
|
||||
must receive the temporary keychain's own password, not the `.p12` import
|
||||
password. macOS runner images since `macos-26-arm64` 20260831 verify that
|
||||
password, and `Build on macos arm64` failed with `SecKeychainUnlock: The user
|
||||
name or passphrase you entered is not correct`. Keep the patch until
|
||||
name or passphrase you entered is not correct`. Keep the patch until
|
||||
electron-builder resolves an `app-builder-lib` containing the fix (26.16.1+),
|
||||
and run `pnpm run deps:electron-builder:test` after related dependency
|
||||
updates — the test fails when the patched version no longer matches the
|
||||
installed one.
|
||||
- `nx-electron@22.0.0` uses a local Nx 23 export-path patch and an explicit
|
||||
`webpack-node-externals` package extension. Scoped peer allowances for it
|
||||
and `ngx-indexed-db@22.0.0` live in `pnpm-workspace.yaml`; they are project
|
||||
compatibility bridges, not upstream support declarations. See
|
||||
`docs/architecture/nx-workspace-boundaries.md` before removing them.
|
||||
- A directory holding files consumed by other projects must be an Nx project.
|
||||
Nx builds its graph from TypeScript imports only, so a relative SCSS `@use`
|
||||
across project roots creates no edge and the imported file lands in no task
|
||||
|
||||
@@ -272,11 +272,18 @@ needed.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Node.js 22.13–22.x or 24 and newer with pnpm (via Corepack)
|
||||
- Node.js 22.22.3 or newer within 22.x, or 24.15.0 or newer within 24.x
|
||||
- pnpm 10.33.0 (via Corepack)
|
||||
|
||||
The repository's `.nvmrc` pins the Node.js version used by CI. With nvm,
|
||||
run `nvm install` and `nvm use` from the repository root to use the same version
|
||||
for local development.
|
||||
|
||||
1. Clone this repository and install project dependencies:
|
||||
|
||||
```
|
||||
$ nvm install
|
||||
$ nvm use
|
||||
$ corepack enable
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "NodeNext"
|
||||
"module": "NodeNext",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
provideBrowserGlobalErrorListeners,
|
||||
provideZoneChangeDetection,
|
||||
} from '@angular/core';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClient, withXhr } from '@angular/common/http';
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
@@ -17,7 +17,7 @@ export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideHttpClient(),
|
||||
provideHttpClient(withXhr()),
|
||||
importProvidersFrom(
|
||||
TranslateModule.forRoot({
|
||||
defaultLanguage: 'en',
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
inject,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { RemoteControlComponent } from '@iptvnator/ui/remote-control';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@@ -6,6 +11,8 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
imports: [RemoteControlComponent],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.html',
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styleUrl: './app.scss',
|
||||
})
|
||||
export class App implements OnInit {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,5 +10,13 @@
|
||||
"src/test-setup.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts"
|
||||
]
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"extendedDiagnostics": {
|
||||
"checks": {
|
||||
"nullishCoalescingNotNullable": "suppress",
|
||||
"optionalChainNotNullable": "suppress"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"types": ["*"],
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
@@ -18,7 +18,26 @@
|
||||
"bundle": true,
|
||||
"thirdParty": true,
|
||||
"generatePackageJson": true
|
||||
}
|
||||
},
|
||||
"inputs": [
|
||||
"default",
|
||||
"^default",
|
||||
"{workspaceRoot}/pnpm-workspace.yaml",
|
||||
{
|
||||
"json": "{workspaceRoot}/package.json",
|
||||
"fields": [
|
||||
"packageManager",
|
||||
"pnpm.onlyBuiltDependencies",
|
||||
"pnpm.neverBuiltDependencies",
|
||||
"pnpm.allowBuilds",
|
||||
"pnpm.supportedArchitectures",
|
||||
"pnpm.patchedDependencies"
|
||||
]
|
||||
},
|
||||
{
|
||||
"runtime": "node -e \"try{console.log('pnpm major '+require('child_process').execSync('pnpm --version',{stdio:['ignore','pipe','ignore']}).toString().trim().split('.')[0])}catch{console.log('pnpm major unavailable')}\""
|
||||
}
|
||||
]
|
||||
},
|
||||
"serve": {
|
||||
"continuous": true,
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"lib": ["ES2022"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -4,6 +4,12 @@ import { expect, test } from './fixtures';
|
||||
const fixturePath = join(__dirname, 'fixtures/test.m3u');
|
||||
|
||||
test('@web @m3u basic playlist import flow', async ({ page }) => {
|
||||
// The fixture's placeholder media hosts must not delay navigation or make
|
||||
// persistence coverage depend on external DNS and asset availability.
|
||||
await page.route(
|
||||
/^https?:\/\/(?:channel\.icons\.url|example\.channels|xml-url)\//,
|
||||
(route) => route.abort()
|
||||
);
|
||||
await page.goto('/');
|
||||
|
||||
// Basic checks
|
||||
@@ -29,6 +35,15 @@ test('@web @m3u basic playlist import flow', async ({ page }) => {
|
||||
await expect(page.getByText('4 channels')).toBeVisible();
|
||||
await expect(page.getByText('1. Channel 1')).toBeVisible();
|
||||
await expect(page.getByText('4. HappyKids TV')).toBeVisible();
|
||||
|
||||
const importedPlaylistUrl = page.url();
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
|
||||
await expect(page).toHaveURL(importedPlaylistUrl);
|
||||
await expect(page.getByText('test', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('4 channels')).toBeVisible();
|
||||
await expect(page.getByText('1. Channel 1')).toBeVisible();
|
||||
await expect(page.getByText('4. HappyKids TV')).toBeVisible();
|
||||
});
|
||||
|
||||
test('@web @auto-detect pasted provider message prefills the Xtream form', async ({
|
||||
@@ -110,7 +125,161 @@ test('@web @auto-detect keeps the pasted message when switching methods', async
|
||||
);
|
||||
});
|
||||
|
||||
test('@web keyboard shortcuts help opens from question mark', async ({ page }) => {
|
||||
test('@web @m3u reads an existing v1 IndexedDB playlist after application upgrade', async ({
|
||||
page,
|
||||
}) => {
|
||||
const playlistId = 'legacy-indexeddb-playlist';
|
||||
// Seed before Angular boots so the application must open the old database.
|
||||
await page.route('**/__indexeddb-seed__', (route) =>
|
||||
route.fulfill({ contentType: 'text/html', body: '<!doctype html>' })
|
||||
);
|
||||
await page.route('https://legacy.example.invalid/**', (route) =>
|
||||
route.abort()
|
||||
);
|
||||
await page.goto('/__indexeddb-seed__');
|
||||
await page.evaluate(
|
||||
(id) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.open('iptvnator', 1);
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onupgradeneeded = () => {
|
||||
const store = request.result.createObjectStore(
|
||||
'playlists',
|
||||
{
|
||||
keyPath: '_id',
|
||||
autoIncrement: false,
|
||||
}
|
||||
);
|
||||
// Freeze the historical schema independently of app config.
|
||||
for (const name of [
|
||||
'_id',
|
||||
'filename',
|
||||
'title',
|
||||
'count',
|
||||
'playlist',
|
||||
'importDate',
|
||||
'lastUsage',
|
||||
'favorites',
|
||||
'recentlyViewed',
|
||||
'autoRefresh',
|
||||
'url',
|
||||
'filePath',
|
||||
]) {
|
||||
store.createIndex(name, name, { unique: false });
|
||||
}
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const transaction = db.transaction(
|
||||
'playlists',
|
||||
'readwrite'
|
||||
);
|
||||
transaction.onabort = () => {
|
||||
db.close();
|
||||
reject(transaction.error);
|
||||
};
|
||||
transaction.oncomplete = () => {
|
||||
db.close();
|
||||
resolve();
|
||||
};
|
||||
transaction.objectStore('playlists').add({
|
||||
_id: id,
|
||||
title: 'Legacy playlist',
|
||||
filename: 'legacy.m3u',
|
||||
importDate: '2025-01-01T00:00:00.000Z',
|
||||
lastUsage: '2025-01-01T00:00:00.000Z',
|
||||
count: 1,
|
||||
autoRefresh: false,
|
||||
favorites: [],
|
||||
recentlyViewed: [],
|
||||
playlist: {
|
||||
header: { attrs: {}, raw: '#EXTM3U' },
|
||||
items: [
|
||||
{
|
||||
id: 'legacy-channel',
|
||||
name: 'Legacy channel',
|
||||
url: 'https://legacy.example.invalid/live.m3u8',
|
||||
group: { title: 'News' },
|
||||
tvg: {
|
||||
id: '',
|
||||
name: '',
|
||||
url: '',
|
||||
logo: '',
|
||||
rec: '',
|
||||
},
|
||||
http: {
|
||||
referrer: '',
|
||||
'user-agent': '',
|
||||
origin: '',
|
||||
},
|
||||
radio: 'false',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
}),
|
||||
playlistId
|
||||
);
|
||||
|
||||
const playlistPath = `/workspace/playlists/${playlistId}/all`;
|
||||
await page.goto(playlistPath, { waitUntil: 'domcontentloaded' });
|
||||
await expect(
|
||||
page.getByText('Legacy playlist', { exact: true })
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('1. Legacy channel', { exact: true })
|
||||
).toBeVisible();
|
||||
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await expect(page).toHaveURL(new RegExp(`${playlistPath}$`));
|
||||
await expect(
|
||||
page.getByText('Legacy playlist', { exact: true })
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('1. Legacy channel', { exact: true })
|
||||
).toBeVisible();
|
||||
|
||||
const persisted = await page.evaluate(
|
||||
(id) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('iptvnator');
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const transaction = db.transaction('playlists', 'readonly');
|
||||
const store = transaction.objectStore('playlists');
|
||||
const record = store.get(id);
|
||||
transaction.onabort = () => {
|
||||
db.close();
|
||||
reject(transaction.error);
|
||||
};
|
||||
transaction.oncomplete = () => {
|
||||
resolve({
|
||||
version: db.version,
|
||||
keyPath: store.keyPath,
|
||||
autoIncrement: store.autoIncrement,
|
||||
id: record.result?._id,
|
||||
channel: record.result?.playlist.items[0].name,
|
||||
});
|
||||
db.close();
|
||||
};
|
||||
};
|
||||
}),
|
||||
playlistId
|
||||
);
|
||||
expect(persisted).toEqual({
|
||||
version: 1,
|
||||
keyPath: '_id',
|
||||
autoIncrement: false,
|
||||
id: playlistId,
|
||||
channel: 'Legacy channel',
|
||||
});
|
||||
});
|
||||
|
||||
test('@web keyboard shortcuts help opens from question mark', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
|
||||
await page.getByRole('button', { name: 'Open keyboard shortcuts' }).focus();
|
||||
|
||||
@@ -66,6 +66,18 @@ test.skip(
|
||||
'DASH ClearKey coverage targets Chromium'
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.addInitScript(() => {
|
||||
const play = HTMLMediaElement.prototype.play;
|
||||
HTMLMediaElement.prototype.play = function () {
|
||||
// Keep real decoding while avoiding the host's audio-output clock.
|
||||
// Muting must happen before play, including replacement players.
|
||||
this.muted = true;
|
||||
return play.call(this);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
async function serveDashFixtures(page: Page): Promise<void> {
|
||||
await page.route(`${FIXTURE_HOST}/**`, async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
@@ -150,7 +162,19 @@ test('@web @m3u @dash ClearKey and clear DASH channels play inline', async ({
|
||||
await page.getByText('1. ClearKey DASH').click();
|
||||
await expectVideoPlaying(page);
|
||||
|
||||
const clearKeyVideo = await page
|
||||
.locator('app-web-player-view video')
|
||||
.first()
|
||||
.elementHandle();
|
||||
if (!clearKeyVideo) {
|
||||
throw new Error('Missing playing ClearKey video');
|
||||
}
|
||||
await page.getByText('2. Clear DASH').click();
|
||||
// The new source mounts asynchronously; never accept the old video's time.
|
||||
await page.waitForFunction(
|
||||
(previous) => !previous.isConnected,
|
||||
clearKeyVideo
|
||||
);
|
||||
await expectVideoPlaying(page);
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nxPreset } from '@nx/jest/preset.js';
|
||||
import { nxPreset } from '@nx/jest/preset';
|
||||
import { createEsmPreset } from 'jest-preset-angular/presets/index.js';
|
||||
|
||||
const angularEsmPreset = createEsmPreset({
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
OnInit,
|
||||
signal,
|
||||
inject,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
@@ -101,6 +102,8 @@ import { AppUpdateReleaseNotesDialogComponent } from './settings/app-update-rele
|
||||
</section>
|
||||
}
|
||||
`,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
.app-update-notification {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
inject,
|
||||
OnInit,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
@@ -48,6 +49,8 @@ const debugAppComponent = createDevLogger('AppComponent');
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
AppStartupStatusComponent,
|
||||
AppUpdateNotificationPanelComponent,
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
HttpClient,
|
||||
provideHttpClient,
|
||||
withInterceptorsFromDi,
|
||||
withXhr,
|
||||
} from '@angular/common/http';
|
||||
import {
|
||||
FullscreenOverlayContainer,
|
||||
@@ -114,7 +115,7 @@ export const appConfig: ApplicationConfig = {
|
||||
// fullscreen channel panel's sort/context menus and the controls'
|
||||
// tooltips render while the player is fullscreen.
|
||||
{ provide: OverlayContainer, useClass: FullscreenOverlayContainer },
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClient(withXhr(), withInterceptorsFromDi()),
|
||||
provideStore({
|
||||
router: routerReducer,
|
||||
playlistState: playlistReducer,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
SecurityContext,
|
||||
signal,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -76,7 +77,9 @@ function decorateReleaseNotesHtml(html: string): string {
|
||||
</button>
|
||||
|
||||
<div class="release-notes-dialog__version">
|
||||
<strong>{{ notes()?.releaseName || notes()?.tagName }}</strong>
|
||||
<strong>{{
|
||||
notes()?.releaseName || notes()?.tagName
|
||||
}}</strong>
|
||||
@if (notes()?.publishedAt; as publishedAt) {
|
||||
<span>{{ publishedAt | date: 'mediumDate' }}</span>
|
||||
}
|
||||
@@ -120,6 +123,8 @@ function decorateReleaseNotesHtml(html: string): string {
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
.release-notes-dialog {
|
||||
@@ -210,9 +215,8 @@ function decorateReleaseNotesHtml(html: string): string {
|
||||
],
|
||||
})
|
||||
export class AppUpdateReleaseNotesDialogComponent implements OnInit {
|
||||
private readonly data = inject<AppUpdateReleaseNotesDialogData>(
|
||||
MAT_DIALOG_DATA
|
||||
);
|
||||
private readonly data =
|
||||
inject<AppUpdateReleaseNotesDialogData>(MAT_DIALOG_DATA);
|
||||
private readonly dialogRef = inject(
|
||||
MatDialogRef<AppUpdateReleaseNotesDialogComponent>
|
||||
);
|
||||
@@ -271,7 +275,9 @@ export class AppUpdateReleaseNotesDialogComponent implements OnInit {
|
||||
await window.electron.getAppUpdateReleaseNotes(request)
|
||||
);
|
||||
} catch (error) {
|
||||
this.error.set(error instanceof Error ? error.message : String(error));
|
||||
this.error.set(
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
} finally {
|
||||
this.loading.set(false);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
imports: [MatButtonModule, MatIconModule, TranslateModule],
|
||||
templateUrl: './settings-about-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
':host { display: contents; }',
|
||||
'.version-block .build-commit { opacity: 0.65; font-size: 0.85em; }',
|
||||
@@ -28,7 +31,9 @@ export class SettingsAboutSectionComponent {
|
||||
readonly version = input<string | undefined>();
|
||||
readonly buildCommit = input<string | undefined>();
|
||||
readonly updateMessage = input<string | undefined>();
|
||||
readonly appUpdateStatus = input<ElectronBridgeAppUpdateStatus | null>(null);
|
||||
readonly appUpdateStatus = input<ElectronBridgeAppUpdateStatus | null>(
|
||||
null
|
||||
);
|
||||
|
||||
readonly buildCommitShort = computed(() => {
|
||||
const commit = this.buildCommit()?.trim();
|
||||
@@ -74,7 +79,7 @@ export class SettingsAboutSectionComponent {
|
||||
|
||||
return Boolean(
|
||||
status?.currentVersion &&
|
||||
status.status !== ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Checking
|
||||
status.status !== ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Checking
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Component, input, output, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
@@ -14,6 +20,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
],
|
||||
templateUrl: './settings-backup-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsBackupSectionComponent {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, input, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -16,6 +21,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
],
|
||||
templateUrl: './settings-dashboard-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsDashboardSectionComponent {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -25,6 +30,8 @@ type SettingsDeleteSummaryItem = {
|
||||
selector: 'app-settings-delete-all-playlists-dialog',
|
||||
templateUrl: './settings-delete-all-playlists-dialog.component.html',
|
||||
styleUrls: ['./settings-delete-all-playlists-dialog.component.scss'],
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Component, input, output, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormArray, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -28,6 +34,8 @@ import { EpgViewModeOption } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-epg-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsEpgSectionComponent {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, input, output, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
@@ -27,6 +33,8 @@ import {
|
||||
],
|
||||
templateUrl: './settings-general-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsGeneralSectionComponent {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, input, output, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -30,6 +36,8 @@ import { SettingsPlayerOption } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-playback-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsPlaybackSectionComponent {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Component, input, output, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -24,6 +30,8 @@ import { QRCodeComponent } from 'angularx-qrcode';
|
||||
],
|
||||
templateUrl: './settings-remote-control-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsRemoteControlSectionComponent {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Component, input, output, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
@@ -15,6 +21,8 @@ import { SettingsPlaylistDeleteSummary } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-reset-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsResetSectionComponent {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
input,
|
||||
signal,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -32,6 +33,8 @@ type TmdbKeyTestState = 'idle' | 'testing' | 'success' | 'error';
|
||||
],
|
||||
templateUrl: './settings-tmdb-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
app-settings-tmdb-section {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -38,6 +38,8 @@ export interface SettingsUnsavedChangesDialogData {
|
||||
}
|
||||
`,
|
||||
],
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
template: `
|
||||
<h2 mat-dialog-title>
|
||||
{{ 'SETTINGS.UNSAVED_DIALOG_TITLE' | translate }}
|
||||
|
||||
@@ -472,7 +472,11 @@ describe('SettingsComponent form', () => {
|
||||
const webFixture = TestBed.createComponent(SettingsComponent);
|
||||
const webComponent = webFixture.componentInstance;
|
||||
stubSettingsSideEffects(webComponent);
|
||||
const loadSettings = jest.spyOn(webComponent.form, 'loadSettings');
|
||||
webFixture.detectChanges();
|
||||
expect(loadSettings).toHaveBeenCalledTimes(1);
|
||||
// Await hydration before editing; Zone cannot track native awaits.
|
||||
await loadSettings.mock.results[0].value;
|
||||
await webFixture.whenStable();
|
||||
|
||||
webComponent.settingsForm.patchValue({ theme: Theme.DarkTheme });
|
||||
|
||||
@@ -78,6 +78,10 @@ describe('SettingsComponent', () => {
|
||||
const lifecycleFixture = TestBed.createComponent(SettingsComponent);
|
||||
const lifecycleComponent = lifecycleFixture.componentInstance;
|
||||
stubSettingsSideEffects(lifecycleComponent);
|
||||
const loadSettings = jest.spyOn(
|
||||
lifecycleComponent.form,
|
||||
'loadSettings'
|
||||
);
|
||||
const appUpdateInit = jest.spyOn(lifecycleComponent.appUpdate, 'init');
|
||||
const appUpdateDispose = jest.spyOn(
|
||||
lifecycleComponent.appUpdate,
|
||||
@@ -89,6 +93,9 @@ describe('SettingsComponent', () => {
|
||||
);
|
||||
|
||||
lifecycleFixture.detectChanges();
|
||||
expect(loadSettings).toHaveBeenCalledTimes(1);
|
||||
// Let ngOnInit resume after its native promise, which Zone cannot track.
|
||||
await loadSettings.mock.results[0].value;
|
||||
await lifecycleFixture.whenStable();
|
||||
|
||||
expect(appUpdateInit).toHaveBeenCalledTimes(1);
|
||||
@@ -197,7 +204,10 @@ describe('SettingsComponent', () => {
|
||||
|
||||
it('save-and-leave persists before allowing the navigation', async () => {
|
||||
component.settingsForm.markAsDirty();
|
||||
jest.spyOn(component.epg, 'fetchConfiguredEpg').mockImplementation();
|
||||
jest.spyOn(
|
||||
component.epg,
|
||||
'fetchConfiguredEpg'
|
||||
).mockImplementation();
|
||||
answerDialogWith('save');
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
@@ -93,6 +94,8 @@ export const SETTINGS_DEFAULT_SECTION = 'general';
|
||||
SettingsResetSectionComponent,
|
||||
SettingsTmdbSectionComponent,
|
||||
],
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
providers: [
|
||||
SettingsAppUpdateFacade,
|
||||
SettingsBackupFacade,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone/index.mjs';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless/index.mjs';
|
||||
import { installDuplicateVideoJsQualityLevelsWarnFilter } from '@iptvnator/shared/testing';
|
||||
|
||||
Object.defineProperty(globalThis, 'jest', {
|
||||
@@ -25,7 +28,7 @@ if (!Element.prototype.animate) {
|
||||
|
||||
installDuplicateVideoJsQualityLevelsWarnFilter();
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -12,5 +12,13 @@
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.stub.ts"
|
||||
]
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"extendedDiagnostics": {
|
||||
"checks": {
|
||||
"nullishCoalescingNotNullable": "suppress",
|
||||
"optionalChainNotNullable": "suppress"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"useDefineForClassFields": false
|
||||
"useDefineForClassFields": false,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"ignoreDeprecations": "6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"types": ["*"],
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node10",
|
||||
"types": ["jest", "node"],
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ RUN apk add --no-cache python3 make g++ git
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY .npmrc ./
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY patches ./patches
|
||||
|
||||
# Node 25 unbundled Corepack, so `corepack enable` cannot be the way pnpm gets
|
||||
|
||||
@@ -56,23 +56,70 @@ Omit `pnpm nx migrate --run-migrations` when the first command reports that no
|
||||
migrations exist. Major Nx updates always use this manual workflow and the
|
||||
resulting PR runs the full CI pipeline.
|
||||
|
||||
## Angular 22 Toolchain Compatibility
|
||||
|
||||
Angular framework/Material 22.1 and CLI/build 22.1 use Nx 23.2 and TypeScript
|
||||
6.0. Framework and CLI patch numbers may differ; keep framework packages aligned
|
||||
in both dependency sections and all official Nx packages on one exact version.
|
||||
Use `.nvmrc` for development/CI; supported LTS Node ranges are
|
||||
`^22.22.3 || ^24.15.0`. Docker uses the supported Node 24 line.
|
||||
|
||||
The Angular 22 migration explicitly preserves the old default change detection
|
||||
with `ChangeDetectionStrategy.Eager`. Existing OnPush components and
|
||||
`provideZoneChangeDetection` remain in their existing modes.
|
||||
Existing eager components under Angular ESLint carry a line-scoped explanation
|
||||
for the newly recommended OnPush rule; new components keep that rule enabled.
|
||||
Adopting OnPush in those components requires a separate behavior review.
|
||||
TypeScript 6 migration settings preserve compiler defaults and per-project output roots;
|
||||
`ignoreDeprecations: "6.0"` temporarily supports legacy Node test configs and
|
||||
`baseUrl`, rather than changing the whole workspace to browser resolution.
|
||||
|
||||
Two third-party packages need scoped compatibility bridges in
|
||||
`pnpm-workspace.yaml`:
|
||||
|
||||
- `nx-electron@22.0.0` still declares Nx 22 peers. Its existing package-layout
|
||||
patch also changes the removed `@nx/js/src/utils/buildable-libs-utils` import
|
||||
to `@nx/js/internal`. A package extension supplies its undeclared runtime
|
||||
dependency `webpack-node-externals`. The packaging suite loads every executor
|
||||
and checks the single application manifest. Remove these bridges only after
|
||||
the upstream package declares and implements the current Nx APIs.
|
||||
- `ngx-indexed-db@22.0.0` declares Angular `<22`, but its used API remains
|
||||
compatible. Its allowance is scoped to the exact Angular version and must be
|
||||
revalidated on upgrades. `apps/web-e2e/src/basic.e2e.ts` opens a historical
|
||||
`iptvnator` v1 database, retains the `_id` key path, and checks reload
|
||||
persistence. The shared interfaces package declares the same library version.
|
||||
Remove the allowance when upstream includes the installed Angular version.
|
||||
|
||||
Jest uses root Babel 7 while Angular build retains its own Babel 8. The website
|
||||
uses Astro 7 with the root TypeScript 6; no Astro-specific compiler package
|
||||
extension is needed. Keep the website build in toolchain validation.
|
||||
The website's alias uses a relative `./src/*` target without the deprecated
|
||||
`baseUrl` option.
|
||||
|
||||
Electron's webpack build resolves `ts-loader` 9.6.2 through the existing
|
||||
`nx-electron` version range. Keep at least 9.5.7: earlier versions clear
|
||||
`rootDir` in transpile-only mode and fail with TypeScript 6 (TS5011/TS6059).
|
||||
|
||||
Angular unit-test setup explicitly uses `setupZonelessTestEnv` and separately
|
||||
imports `zone.js/testing` for existing `fakeAsync` helpers. This preserves the
|
||||
Angular 21 scheduler used by jest-preset-angular 15: its `setupZoneTestEnv` loaded
|
||||
Zone but did not provide zone-based change detection on Angular 20+. Preset 17
|
||||
does provide it, which disables automatic fixture rendering and changes
|
||||
`whenStable()` behavior. Production applications continue to use their existing
|
||||
`provideZoneChangeDetection`; browser and Electron E2E validate that runtime.
|
||||
|
||||
## Vite Dev-Server Patch
|
||||
|
||||
Angular's development builder currently resolves Vite `7.3.6`. That release's
|
||||
asset and worker transform prefilters can catastrophically backtrack on a large
|
||||
generated chunk containing unrelated `new URL...` expressions while searching
|
||||
for a valid `new URL(..., import.meta.url)` construct. The Electron development
|
||||
server can then fail a lazy chunk request with `Maximum call stack size
|
||||
exceeded`, even though static builds succeed because they do not pass emitted
|
||||
chunks through Vite's request-time plugin container.
|
||||
Angular's development builder resolves Vite `8.1.5`. It contains upstream precise
|
||||
asset/worker URL matchers, but also uses them as raw-code prefilters. Those
|
||||
prefilters reject valid expressions containing comments before the handlers can
|
||||
strip the comments and apply the precise matchers.
|
||||
|
||||
`patches/vite@7.3.6.patch` backports Vite's upstream precise-matcher fix from
|
||||
[vitejs/vite#21800](https://github.com/vitejs/vite/pull/21800). Bounded
|
||||
prefilters keep the request-time scan linear while allowing comment-bearing
|
||||
asset and worker expressions to reach the precise matcher after Vite strips
|
||||
comments. Keep the patch while the supported Angular toolchain resolves Vite
|
||||
`7.3.6`; remove it only after the resolved Vite contains the upstream fix. Run
|
||||
the regression check after any related manifest or lockfile update:
|
||||
`patches/vite@8.1.5.patch` adds bounded asset/worker prefilters and uses the asset
|
||||
prefilter for bundled URL rewriting too. The precise handler matchers remain
|
||||
unchanged. This preserves the earlier protection against expensive scanning of
|
||||
large false-positive chunks and retains comment-bearing expressions. Remove the
|
||||
patch only when the resolved upstream Vite passes the same behavior checks:
|
||||
|
||||
```bash
|
||||
pnpm run deps:vite:test
|
||||
|
||||
@@ -304,27 +304,28 @@ handlers in `apps/electron-backend/src/app/events/window.events.ts`):
|
||||
snap, F11). The controls hide themselves while the window is
|
||||
fullscreen.
|
||||
|
||||
Window state is **never re-read at event time**. `attachWindowStateEvents`
|
||||
seeds `{ isMaximized, isFullScreen }` once at window creation and each
|
||||
event patches only the flag it names; every push carries a copy of that
|
||||
tracked state. On Windows both getters can still report the
|
||||
pre-transition value while the matching event fires — `isFullScreen()`
|
||||
stays `true` during an HTML fullscreen exit, and `isMaximized()` reads
|
||||
`false` while the window is fullscreen. Because the renderer replaces
|
||||
both flags on every push and no later event corrects a stale one,
|
||||
polling left the controls hidden forever after leaving fullscreen and
|
||||
stuck the maximize/restore glyph on the wrong icon. Regression coverage:
|
||||
`app-window-state.spec.ts` and `window-controls.e2e.ts`.
|
||||
Window state is **never re-read at event time**. `attachWindowStateEvents`
|
||||
seeds `{ isMaximized, isFullScreen }` once at window creation and each
|
||||
event patches only the flag it names; every push carries a copy of that
|
||||
tracked state. On Windows both getters can still report the
|
||||
pre-transition value while the matching event fires — `isFullScreen()`
|
||||
stays `true` during an HTML fullscreen exit, and `isMaximized()` reads
|
||||
`false` while the window is fullscreen. Because the renderer replaces
|
||||
both flags on every push and no later event corrects a stale one,
|
||||
polling left the controls hidden forever after leaving fullscreen and
|
||||
stuck the maximize/restore glyph on the wrong icon. Regression coverage:
|
||||
`app-window-state.spec.ts` and `window-controls.e2e.ts`.
|
||||
|
||||
The pushed `isFullScreen` is the OR of two flags tracked apart: native
|
||||
(OS-level, fed by `enter/leave-full-screen`) and HTML-element (fed by
|
||||
the `*-html-*` pair). Electron remembers when the window was already
|
||||
natively fullscreen before the player entered HTML fullscreen and then
|
||||
leaves ONLY the HTML state on exit — no `leave-full-screen` fires and the
|
||||
window stays fullscreen — so a single flag cleared by
|
||||
`leave-html-full-screen` would un-hide the controls over a window that
|
||||
is still fullscreen. A fullscreen launch (below) or F11 followed by the
|
||||
player's `F` → `Esc` makes that path routine on Windows/Linux.
|
||||
|
||||
The pushed `isFullScreen` is the OR of two flags tracked apart: native
|
||||
(OS-level, fed by `enter/leave-full-screen`) and HTML-element (fed by
|
||||
the `*-html-*` pair). Electron remembers when the window was already
|
||||
natively fullscreen before the player entered HTML fullscreen and then
|
||||
leaves ONLY the HTML state on exit — no `leave-full-screen` fires and the
|
||||
window stays fullscreen — so a single flag cleared by
|
||||
`leave-html-full-screen` would un-hide the controls over a window that
|
||||
is still fullscreen. A fullscreen launch (below) or F11 followed by the
|
||||
player's `F` → `Esc` makes that path routine on Windows/Linux.
|
||||
3. `WINDOW:TOGGLE_FULLSCREEN` toggles OS-level fullscreen (`setFullScreen`)
|
||||
and, like the maximize toggle, reports the requested state and leaves
|
||||
the `WINDOW:STATE_CHANGED` push authoritative. Because the transition is
|
||||
@@ -439,15 +440,15 @@ Toolchain notes for the Electron 43 baseline:
|
||||
implicit `node-gyp rebuild` bypasses those binaries and makes installation
|
||||
depend on the host compiler toolchain. The old `node-abi` override belonged
|
||||
to v12's removed `prebuild-install` path and is no longer required.
|
||||
2. Local development supports **Node 22.13–22.x or Node >= 24**, declared in
|
||||
`engines` as `^22.13.0 || >=24.0.0`.
|
||||
The direct `@faker-js/faker` dependency and current lint tooling require
|
||||
that floor. `electron-builder` 26.15.7 also pulls `@electron/rebuild` 4,
|
||||
2. Local development supports **Node 22.22.3–22.x or 24.15.0–24.x**, declared in
|
||||
`engines` as `^22.22.3 || ^24.15.0`. Use `.nvmrc` (currently 22.23.2) for
|
||||
development and CI. Angular 22 sets this supported LTS range and requires
|
||||
TypeScript `>=6.0 <6.1`. `electron-builder` 26.15.7 also pulls `@electron/rebuild` 4,
|
||||
which requires Node 22.12 or newer, and the root `postinstall` runs
|
||||
`install-app-deps` on every `pnpm install`. The 26.15.7 minimum also
|
||||
carries the v26 backport that fully extracts the Snap template's `.tar.7z`
|
||||
payload; 26.15.0–26.15.6 can
|
||||
produce a Snap that is missing `desktop-init.sh`. CI already runs Node 22.
|
||||
produce a Snap that is missing `desktop-init.sh`.
|
||||
3. Dependabot keeps Electron, native database, packaging, EPG parser, and
|
||||
version-locked Shaka/mpegts updates out of the shared npm minor/patch group.
|
||||
Those dependencies require standalone PRs so their dedicated package,
|
||||
@@ -457,7 +458,7 @@ Toolchain notes for the Electron 43 baseline:
|
||||
Electron command instead of during package `postinstall`, so `electron`
|
||||
must not remain in pnpm's `onlyBuiltDependencies` allowlist. The first local
|
||||
`pnpm run serve:backend` can include a one-time download; use `pnpm exec
|
||||
electron --version` to prewarm it before an offline run.
|
||||
electron --version` to prewarm it before an offline run.
|
||||
|
||||
Known caveats:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nxPreset } from '@nx/jest/preset.js';
|
||||
import { nxPreset } from '@nx/jest/preset';
|
||||
import { createEsmPreset } from 'jest-preset-angular/presets/index.js';
|
||||
|
||||
const angularEsmPreset = createEsmPreset({
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -9,7 +9,8 @@
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
|
||||
+5
-2
@@ -6,6 +6,7 @@ import {
|
||||
signal,
|
||||
ViewEncapsulation,
|
||||
viewChild,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import {
|
||||
@@ -70,6 +71,7 @@ const METHOD_BY_CANDIDATE_KIND: Record<
|
||||
selector: 'app-add-playlist',
|
||||
templateUrl: './add-playlist-dialog.component.html',
|
||||
styleUrl: './add-playlist-dialog.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class AddPlaylistDialogComponent {
|
||||
@@ -98,8 +100,9 @@ export class AddPlaylistDialogComponent {
|
||||
* the prefill is applied by an effect that re-runs once the child's
|
||||
* viewChild signal resolves, then clears this.
|
||||
*/
|
||||
private readonly pendingPrefill =
|
||||
signal<ProviderImportCandidate | null>(null);
|
||||
private readonly pendingPrefill = signal<ProviderImportCandidate | null>(
|
||||
null
|
||||
);
|
||||
|
||||
/**
|
||||
* Survives the auto-detect surface being destroyed by a method switch, so
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Component, computed, input, OnInit, output } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
OnInit,
|
||||
output,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -139,6 +146,7 @@ function maskUrlQueryPasswords(url: string): string {
|
||||
selector: 'app-auto-import',
|
||||
templateUrl: './auto-import.component.html',
|
||||
styleUrl: './auto-import.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
@@ -228,10 +236,7 @@ export class AutoImportComponent implements OnInit {
|
||||
);
|
||||
break;
|
||||
case 'xtream':
|
||||
push(
|
||||
'HOME.XTREAM_PLAYLIST.SERVER_URL',
|
||||
candidate.serverUrl
|
||||
);
|
||||
push('HOME.XTREAM_PLAYLIST.SERVER_URL', candidate.serverUrl);
|
||||
push('HOME.XTREAM_PLAYLIST.USERNAME', candidate.username);
|
||||
push(
|
||||
'HOME.XTREAM_PLAYLIST.PASSWORD',
|
||||
@@ -239,14 +244,8 @@ export class AutoImportComponent implements OnInit {
|
||||
);
|
||||
break;
|
||||
case 'stalker':
|
||||
push(
|
||||
'HOME.STALKER_PORTAL.SERVER_URL',
|
||||
candidate.portalUrl
|
||||
);
|
||||
push(
|
||||
'HOME.STALKER_PORTAL.MAC_ADDRESS',
|
||||
candidate.macAddress
|
||||
);
|
||||
push('HOME.STALKER_PORTAL.SERVER_URL', candidate.portalUrl);
|
||||
push('HOME.STALKER_PORTAL.MAC_ADDRESS', candidate.macAddress);
|
||||
push(
|
||||
'HOME.STALKER_PORTAL.SERIAL_NUMBER',
|
||||
candidate.serialNumber
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Component, EventEmitter, Output, inject, signal } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
inject,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { PlaylistFileImportService } from '@iptvnator/playlist/shared/util';
|
||||
@@ -11,6 +18,7 @@ const KB = 1024;
|
||||
imports: [DragDropFileUploadDirective, MatIconModule, TranslatePipe],
|
||||
selector: 'app-file-upload',
|
||||
templateUrl: './file-upload.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styleUrls: ['./file-upload.component.scss'],
|
||||
})
|
||||
export class FileUploadComponent {
|
||||
|
||||
+8
-1
@@ -1,4 +1,10 @@
|
||||
import { Component, inject, output, signal } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
output,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
@@ -59,6 +65,7 @@ interface StalkerSettledIdentity {
|
||||
],
|
||||
selector: 'app-stalker-portal-import',
|
||||
templateUrl: './stalker-portal-import.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
@@ -12,6 +17,7 @@ import { TranslatePipe } from '@ngx-translate/core';
|
||||
selector: 'app-text-import',
|
||||
templateUrl: './text-import.component.html',
|
||||
styleUrls: ['./text-import.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [MatInputModule, ReactiveFormsModule, TranslatePipe],
|
||||
})
|
||||
export class TextImportComponent {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
@@ -14,6 +19,7 @@ import { RuntimeCapabilitiesService } from '@iptvnator/services';
|
||||
@Component({
|
||||
selector: 'app-url-upload',
|
||||
templateUrl: './url-upload.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
|
||||
+8
-1
@@ -1,4 +1,10 @@
|
||||
import { Component, EventEmitter, Output, inject } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
inject,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormControl,
|
||||
@@ -47,6 +53,7 @@ function xtreamServerUrlValidator(
|
||||
],
|
||||
selector: 'app-xtream-code-import',
|
||||
templateUrl: './xtream-code-import.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
output,
|
||||
signal,
|
||||
untracked,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
@@ -59,6 +60,7 @@ import { M3uVodMetadataService } from './m3u-vod-metadata.service';
|
||||
TranslatePipe,
|
||||
],
|
||||
templateUrl: './m3u-vod-detail.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styleUrls: ['./m3u-vod-detail.component.scss'],
|
||||
})
|
||||
export class M3uVodDetailComponent {
|
||||
|
||||
@@ -105,7 +105,9 @@
|
||||
@if (activeChannel.radio === 'true') {
|
||||
<app-audio-player
|
||||
[url]="activeChannel.url"
|
||||
[icon]="activeChannel?.tvg?.logo!"
|
||||
[icon]="
|
||||
$safeNavigationMigration(activeChannel?.tvg?.logo)!
|
||||
"
|
||||
[channelName]="displayChannelName()"
|
||||
[volume]="volume()"
|
||||
(volumeChange)="onInlineVolumeChange($event)"
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
signal,
|
||||
untracked,
|
||||
viewChild,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -226,6 +227,7 @@ function isInsideScrollableRegion(
|
||||
{ provide: EPG_GUIDE_SOURCE, useExisting: M3uEpgGuideSourceService },
|
||||
],
|
||||
templateUrl: './video-player.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styleUrl: './video-player.component.scss',
|
||||
})
|
||||
export class VideoPlayerComponent
|
||||
|
||||
+8
-5
@@ -1,14 +1,16 @@
|
||||
import { Component, input, output } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import type { PlaylistType } from '../../add-playlist-menu/playlist-type';
|
||||
|
||||
export type EmptyStateType =
|
||||
| 'welcome-dashboard'
|
||||
| 'welcome-sources'
|
||||
| 'no-results'
|
||||
| 'no-data';
|
||||
'welcome-dashboard' | 'welcome-sources' | 'no-results' | 'no-data';
|
||||
|
||||
interface FeatureCard {
|
||||
icon: string;
|
||||
@@ -100,6 +102,7 @@ const SOURCE_CARDS: readonly SourceCard[] = [
|
||||
'./empty-state.responsive.scss',
|
||||
'./empty-state.themes.scss',
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [MatButtonModule, MatIcon, TranslatePipe],
|
||||
})
|
||||
export class EmptyStateComponent {
|
||||
|
||||
+3
-9
@@ -171,15 +171,9 @@ describe('PlaylistInfoComponent', () => {
|
||||
currentLang: 'en',
|
||||
get: jest.fn((key: string) => of(key)),
|
||||
instant: jest.fn((key: string) => key),
|
||||
onDefaultLangChange: of({
|
||||
lang: 'en',
|
||||
translations: {},
|
||||
}),
|
||||
onLangChange: of({ lang: 'en', translations: {} }),
|
||||
onTranslationChange: of({
|
||||
lang: 'en',
|
||||
translations: {},
|
||||
}),
|
||||
onDefaultLangChange: new Subject(),
|
||||
onLangChange: new Subject(),
|
||||
onTranslationChange: new Subject(),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+8
-1
@@ -1,6 +1,12 @@
|
||||
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, DestroyRef, inject, signal } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
FormControl,
|
||||
ReactiveFormsModule,
|
||||
@@ -159,6 +165,7 @@ type DesktopFileSaveBridge = Pick<
|
||||
`,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
ClipboardModule,
|
||||
MatButton,
|
||||
|
||||
+2
@@ -12,6 +12,7 @@ import {
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
@@ -33,6 +34,7 @@ import { PlaylistMeta } from '@iptvnator/shared/interfaces';
|
||||
selector: 'app-playlist-item',
|
||||
templateUrl: './playlist-item.component.html',
|
||||
styleUrls: ['./playlist-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
SourceHealthIndicatorComponent,
|
||||
DatePipe,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Component, DestroyRef, computed, effect, inject } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
@@ -107,7 +114,9 @@ export interface SourceCleanupDialogData extends SourceCleanupContext {
|
||||
@if (entry.status === 'ready') {
|
||||
<span>{{
|
||||
'SOURCE_HEALTH.REASON.' +
|
||||
entry.health?.reason | translate
|
||||
$safeNavigationMigration(
|
||||
entry.health?.reason
|
||||
) | translate
|
||||
}}</span>
|
||||
} @else {
|
||||
<span>{{
|
||||
@@ -117,8 +126,9 @@ export interface SourceCleanupDialogData extends SourceCleanupContext {
|
||||
}
|
||||
@if (entry.health?.checkedAt) {
|
||||
<time>{{
|
||||
entry.health?.checkedAt
|
||||
| date: 'shortTime'
|
||||
$safeNavigationMigration(
|
||||
entry.health?.checkedAt
|
||||
) | date: 'shortTime'
|
||||
}}</time>
|
||||
}
|
||||
@if (entry.warning) {
|
||||
@@ -170,6 +180,7 @@ export interface SourceCleanupDialogData extends SourceCleanupContext {
|
||||
</button>
|
||||
}
|
||||
</mat-dialog-actions>`,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
inject,
|
||||
input,
|
||||
untracked,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -28,6 +29,7 @@ import { PlaylistMeta, sourceHealthType } from '@iptvnator/shared/interfaces';
|
||||
role="img"
|
||||
></span>
|
||||
}`,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
+10
-1
@@ -1,4 +1,10 @@
|
||||
import { Component, input, output, signal } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { NgComponentOutlet } from '@angular/common';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
@@ -21,6 +27,7 @@ import { CategoryContentViewComponent } from './category-content-view.component'
|
||||
@Component({
|
||||
selector: 'app-grid-list',
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
template: '',
|
||||
})
|
||||
class MockGridListComponent {
|
||||
@@ -37,6 +44,7 @@ class MockGridListComponent {
|
||||
@Component({
|
||||
selector: 'app-playlist-error-view',
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
template: '',
|
||||
})
|
||||
class MockPlaylistErrorViewComponent {
|
||||
@@ -48,6 +56,7 @@ class MockPlaylistErrorViewComponent {
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
template: '',
|
||||
})
|
||||
class MockDetailComponent {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
+16
-5
@@ -312,7 +312,10 @@
|
||||
{{
|
||||
episodeTitle(
|
||||
episode.item,
|
||||
episode.episodeMetadata?.title
|
||||
$safeNavigationMigration(
|
||||
episode.episodeMetadata
|
||||
?.title
|
||||
)
|
||||
)
|
||||
}}
|
||||
</strong>
|
||||
@@ -360,7 +363,10 @@
|
||||
' ' +
|
||||
episodeTitle(
|
||||
episode.item,
|
||||
episode.episodeMetadata?.title
|
||||
$safeNavigationMigration(
|
||||
episode.episodeMetadata
|
||||
?.title
|
||||
)
|
||||
)
|
||||
"
|
||||
(click)="
|
||||
@@ -390,7 +396,10 @@
|
||||
': ' +
|
||||
episodeTitle(
|
||||
episode.item,
|
||||
episode.episodeMetadata?.title
|
||||
$safeNavigationMigration(
|
||||
episode.episodeMetadata
|
||||
?.title
|
||||
)
|
||||
)
|
||||
"
|
||||
>
|
||||
@@ -418,8 +427,10 @@
|
||||
' ' +
|
||||
episodeTitle(
|
||||
episode.item,
|
||||
episode.episodeMetadata
|
||||
?.title
|
||||
$safeNavigationMigration(
|
||||
episode.episodeMetadata
|
||||
?.title
|
||||
)
|
||||
)
|
||||
"
|
||||
(click)="
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
+8
-1
@@ -1,4 +1,10 @@
|
||||
import { Component, inject, input, output } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
@@ -50,6 +56,7 @@ export interface FavoriteLayoutItem {
|
||||
'./favorites-layout.component.scss',
|
||||
'../../styles/portal-sidebar.scss',
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [
|
||||
CategoryViewComponent,
|
||||
ContentCardComponent,
|
||||
|
||||
+7
-1
@@ -1,4 +1,9 @@
|
||||
import { Component, inject, input } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -14,6 +19,7 @@ import { PlaylistActions } from '@iptvnator/m3u-state';
|
||||
selector: 'app-playlist-error-view',
|
||||
templateUrl: './playlist-error-view.component.html',
|
||||
styleUrls: ['./playlist-error-view.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [MatButtonModule, MatIconModule, RouterLink, TranslateModule],
|
||||
})
|
||||
export class PlaylistErrorViewComponent {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
model,
|
||||
Output,
|
||||
ViewChild,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -38,6 +39,7 @@ export interface SearchFilter {
|
||||
TranslatePipe,
|
||||
],
|
||||
templateUrl: './search-form.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
.search-container {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { InfiniteScrollDirective } from './infinite-scroll.directive';
|
||||
@@ -13,6 +13,7 @@ import { InfiniteScrollDirective } from './infinite-scroll.directive';
|
||||
[infiniteResetKey]="resetKey()"
|
||||
(infiniteLoadMore)="onLoadMore()"
|
||||
></div>`,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
imports: [InfiniteScrollDirective],
|
||||
})
|
||||
class TestHostComponent {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Component, inject, input } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
@@ -8,12 +13,16 @@ import {
|
||||
RouterLink,
|
||||
RouterLinkActive,
|
||||
} from '@angular/router';
|
||||
import { PortalRailLink, PortalRailSection } from '@iptvnator/portal/shared/util';
|
||||
import {
|
||||
PortalRailLink,
|
||||
PortalRailSection,
|
||||
} from '@iptvnator/portal/shared/util';
|
||||
|
||||
@Component({
|
||||
selector: 'app-portal-rail-links',
|
||||
imports: [MatIcon, MatListModule, MatTooltip, RouterLink, RouterLinkActive],
|
||||
templateUrl: './portal-rail-links.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styleUrl: './portal-rail-links.component.scss',
|
||||
})
|
||||
export class PortalRailLinksComponent {
|
||||
@@ -27,7 +36,9 @@ export class PortalRailLinksComponent {
|
||||
readonly variant = input<'list' | 'rail'>('list');
|
||||
|
||||
resolveIcon(link: PortalRailLink): string {
|
||||
const normalizedPath = link.path.map((segment) => String(segment)).join('/');
|
||||
const normalizedPath = link.path
|
||||
.map((segment) => String(segment))
|
||||
.join('/');
|
||||
if (normalizedPath.includes('/workspace/sources')) {
|
||||
return 'playlist_play';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
// Keep Zone's fakeAsync helpers while preserving the Angular 21 test scheduler.
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
|
||||
|
||||
setupZoneTestEnv({
|
||||
setupZonelessTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -10,7 +10,8 @@
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
"module": "preserve",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
+17
-3
@@ -1,5 +1,11 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, input, output, signal } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
@@ -33,7 +39,11 @@ function deferred<T>() {
|
||||
return { promise, reject, resolve };
|
||||
}
|
||||
|
||||
@Component({ selector: 'app-vod-details', template: '' })
|
||||
@Component({
|
||||
selector: 'app-vod-details',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
template: '',
|
||||
})
|
||||
class StubVodDetailsComponent {
|
||||
readonly playbackSessionKey = input.required<string>();
|
||||
readonly item = input.required<unknown>();
|
||||
@@ -53,7 +63,11 @@ class StubVodDetailsComponent {
|
||||
readonly inlineExternalFallbackRequested = output<unknown>();
|
||||
}
|
||||
|
||||
@Component({ selector: 'app-stalker-series-view', template: '' })
|
||||
@Component({
|
||||
selector: 'app-stalker-series-view',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
template: '',
|
||||
})
|
||||
class StubStalkerSeriesViewComponent {
|
||||
readonly vodWithSeries = input<unknown>(null);
|
||||
readonly providerOnly = input(false);
|
||||
|
||||
+2
@@ -6,6 +6,7 @@ import {
|
||||
inject,
|
||||
input,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
@@ -55,6 +56,7 @@ import { createPlaybackSessionKey } from '@iptvnator/playback/util';
|
||||
selector: 'app-stalker-catalog-detail',
|
||||
imports: [StalkerSeriesViewComponent, VodDetailsComponent],
|
||||
templateUrl: './stalker-catalog-detail.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
|
||||
Loaded 100 of 200 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user