diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx index 5f46899ce923..94b382bbd677 100755 --- a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx +++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx @@ -2,9 +2,9 @@ # Input hashes for repository rule npm_translate_lock(name = "npm2", pnpm_lock = "@//:pnpm-lock.yaml"). # This file should be checked into version control along with the pnpm-lock.yaml file. .npmrc=-1406867100 -package.json=130765121 +package.json=-306254715 patches/@angular__compiler-cli.patch=-65319555 -pnpm-lock.yaml=-266832367 +pnpm-lock.yaml=1755183230 pnpm-workspace.yaml=14857322 src/cdk/package.json=-908433069 -yarn.lock=230420156 +yarn.lock=-1568260908 diff --git a/package.json b/package.json index 1e941ae3809e..e0bf74152035 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/youtube": "^0.1.0", "rxjs": "^6.6.7", "rxjs-tslint-rules": "^4.34.8", + "safevalues": "^1.2.0", "tslib": "^2.3.1", "zone.js": "~0.15.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b40505363191..b5c590ad9cb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ importers: rxjs-tslint-rules: specifier: ^4.34.8 version: 4.34.8(tslint@6.1.3)(typescript@5.8.2) + safevalues: + specifier: ^1.2.0 + version: 1.2.0 tslib: specifier: ^2.3.1 version: 2.8.1 @@ -13488,6 +13491,10 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true + /safevalues@1.2.0: + resolution: {integrity: sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw==} + dev: false + /sass-loader@16.0.5(sass@1.86.0)(webpack@5.98.0): resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} engines: {node: '>= 18.12.0'} diff --git a/src/google-maps/map-event-manager.ts b/src/google-maps/map-event-manager.ts index 0a85bc8fb39f..452536544da8 100644 --- a/src/google-maps/map-event-manager.ts +++ b/src/google-maps/map-event-manager.ts @@ -12,17 +12,17 @@ import {switchMap} from 'rxjs/operators'; type MapEventManagerTarget = | { - addListener: ( + addListener( name: string, - callback: (...args: any[]) => void, - ) => google.maps.MapsEventListener | undefined; + callback: (args: T) => void, + ): google.maps.MapsEventListener | undefined; } | undefined; /** Manages event on a Google Maps object, ensuring that events are added only when necessary. */ export class MapEventManager { /** Pending listeners that were added before the target was set. */ - private _pending: {observable: Observable; observer: Subscriber}[] = []; + private _pending: {observable: Observable; observer: Subscriber}[] = []; private _listeners: google.maps.MapsEventListener[] = []; private _targetStream = new BehaviorSubject(undefined); diff --git a/src/material-date-fns-adapter/adapter/date-fns-adapter.ts b/src/material-date-fns-adapter/adapter/date-fns-adapter.ts index 29e3b9927c83..91e37fb1cc2d 100644 --- a/src/material-date-fns-adapter/adapter/date-fns-adapter.ts +++ b/src/material-date-fns-adapter/adapter/date-fns-adapter.ts @@ -161,7 +161,7 @@ export class DateFnsAdapter extends DateAdapter { return new Date(); } - parse(value: any, parseFormat: string | string[]): Date | null { + parse(value: unknown, parseFormat: string | string[]): Date | null { if (typeof value == 'string' && value.length > 0) { const iso8601Date = parseISO(value); @@ -222,7 +222,7 @@ export class DateFnsAdapter extends DateAdapter { * (https://www.ietf.org/rfc/rfc3339.txt) into valid Dates and empty string into null. Returns an * invalid date for all other values. */ - override deserialize(value: any): Date | null { + override deserialize(value: unknown): Date | null { if (typeof value === 'string') { if (!value) { return null; @@ -235,7 +235,7 @@ export class DateFnsAdapter extends DateAdapter { return super.deserialize(value); } - isDateInstance(obj: any): boolean { + isDateInstance(obj: unknown): obj is Date { return isDate(obj); } @@ -277,7 +277,7 @@ export class DateFnsAdapter extends DateAdapter { return getSeconds(date); } - override parseTime(value: any, parseFormat: string | string[]): Date | null { + override parseTime(value: unknown, parseFormat: string | string[]): Date | null { return this.parse(value, parseFormat); } diff --git a/src/material-experimental/column-resize/column-resize.spec.ts b/src/material-experimental/column-resize/column-resize.spec.ts index 045ad22c6d82..47ff9f13aa71 100644 --- a/src/material-experimental/column-resize/column-resize.spec.ts +++ b/src/material-experimental/column-resize/column-resize.spec.ts @@ -333,7 +333,7 @@ class ElementDataSource extends DataSource { } // There's 1px of variance between different browsers in terms of positioning. -const approximateMatcher = { +const approximateMatcher: jasmine.CustomMatcherFactories = { isApproximately: () => ({ compare: (actual: number, expected: number) => { const result = { @@ -348,6 +348,14 @@ const approximateMatcher = { }), }; +interface NumberMatchers extends jasmine.Matchers { + isApproximately(expected: number): void; + not: NumberMatchers; +} +declare global { + function expect(actual: number): NumberMatchers; +} + const testCases = [ [MatColumnResizeModule, MatResizeTest, 'opt-in table-based mat-table'], [MatColumnResizeModule, MatResizeOnPushTest, 'inside OnPush component'], @@ -409,12 +417,8 @@ describe('Material Popover Edit', () => { component.getOverlayThumbElement(2).classList.contains('mat-column-resize-overlay-thumb'), ).toBe(true); - (expect(component.getOverlayThumbElement(0).offsetHeight) as any).isApproximately( - headerRowHeight, - ); - (expect(component.getOverlayThumbElement(2).offsetHeight) as any).isApproximately( - headerRowHeight, - ); + expect(component.getOverlayThumbElement(0).offsetHeight).isApproximately(headerRowHeight); + expect(component.getOverlayThumbElement(2).offsetHeight).isApproximately(headerRowHeight); component.beginColumnResizeWithMouse(0); @@ -425,15 +429,11 @@ describe('Material Popover Edit', () => { component.getOverlayThumbElement(2).classList.contains('mat-column-resize-overlay-thumb'), ).toBe(true); - (expect(component.getOverlayThumbElement(0).offsetHeight) as any).isApproximately( - tableHeight, - ); - (expect(component.getOverlayThumbTopElement(0).offsetHeight) as any).isApproximately( - headerRowHeight, - ); - (expect(component.getOverlayThumbElement(2).offsetHeight) as any).isApproximately( + expect(component.getOverlayThumbElement(0).offsetHeight).isApproximately(tableHeight); + expect(component.getOverlayThumbTopElement(0).offsetHeight).isApproximately( headerRowHeight, ); + expect(component.getOverlayThumbElement(2).offsetHeight).isApproximately(headerRowHeight); component.completeResizeWithMouseInProgress(0); component.endHoverState(); @@ -462,15 +462,15 @@ describe('Material Popover Edit', () => { let columnPositionDelta = component.getColumnOriginPosition(1) - initialColumnPosition; // let nextColumnPositionDelta = // component.getColumnOriginPosition(2) - initialNextColumnPosition; - (expect(thumbPositionDelta) as any).isApproximately(columnPositionDelta); + expect(thumbPositionDelta).isApproximately(columnPositionDelta); // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. - // (expect(nextColumnPositionDelta) as any).isApproximately(columnPositionDelta); + // expect(nextColumnPositionDelta).isApproximately(columnPositionDelta); // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. - // (expect(component.getTableWidth()) as any).isApproximately(initialTableWidth + 5); - (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 5); + // expect(component.getTableWidth()).isApproximately(initialTableWidth + 5); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 5); component.updateResizeWithMouseInProgress(1); fixture.detectChanges(); @@ -478,15 +478,15 @@ describe('Material Popover Edit', () => { thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; columnPositionDelta = component.getColumnOriginPosition(1) - initialColumnPosition; - (expect(thumbPositionDelta) as any).isApproximately(columnPositionDelta); + expect(thumbPositionDelta).isApproximately(columnPositionDelta); - (expect(component.getTableWidth()) as any).isApproximately(initialTableWidth + 1); - (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 1); + expect(component.getTableWidth()).isApproximately(initialTableWidth + 1); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 1); component.completeResizeWithMouseInProgress(1); flush(); - (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 1); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 1); component.endHoverState(); fixture.detectChanges(); @@ -508,8 +508,8 @@ describe('Material Popover Edit', () => { flush(); let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; - (expect(thumbPositionDelta) as any).isApproximately(5); - (expect(component.getColumnWidth(1)) as any).toBe(initialColumnWidth); + expect(thumbPositionDelta).isApproximately(5); + expect(component.getColumnWidth(1)).toBe(initialColumnWidth); component.updateResizeWithMouseInProgress(1); fixture.detectChanges(); @@ -517,14 +517,14 @@ describe('Material Popover Edit', () => { thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; - (expect(component.getTableWidth()) as any).toBe(initialTableWidth); - (expect(component.getColumnWidth(1)) as any).toBe(initialColumnWidth); + expect(component.getTableWidth()).toBe(initialTableWidth); + expect(component.getColumnWidth(1)).toBe(initialColumnWidth); component.completeResizeWithMouseInProgress(1); flush(); - (expect(component.getTableWidth()) as any).isApproximately(initialTableWidth + 1); - (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 1); + expect(component.getTableWidth()).isApproximately(initialTableWidth + 1); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 1); component.endHoverState(); fixture.detectChanges(); @@ -562,18 +562,18 @@ describe('Material Popover Edit', () => { let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; let columnPositionDelta = component.getColumnOriginPosition(1) - initialColumnPosition; - (expect(thumbPositionDelta) as any).isApproximately(columnPositionDelta); + expect(thumbPositionDelta).isApproximately(columnPositionDelta); - (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 5); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 5); // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. - // (expect(component.getTableWidth()) as any).isApproximately(initialTableWidth + 5); + // expect(component.getTableWidth()).isApproximately(initialTableWidth + 5); dispatchKeyboardEvent(document, 'keyup', ESCAPE); flush(); - (expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth); - (expect(component.getTableWidth()) as any).isApproximately(initialTableWidth); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth); + expect(component.getTableWidth()).isApproximately(initialTableWidth); component.endHoverState(); fixture.detectChanges(); @@ -582,7 +582,7 @@ describe('Material Popover Edit', () => { it('notifies subscribers of a completed resize via ColumnResizeNotifier', fakeAsync(() => { const initialColumnWidth = component.getColumnWidth(1); - let resize: ColumnSize | null = null; + let resize: ColumnSize | null = null as ColumnSize | null; component.columnResize.columnResizeNotifier.resizeCompleted.subscribe(size => { resize = size; }); @@ -596,7 +596,7 @@ describe('Material Popover Edit', () => { fixture.detectChanges(); flush(); - expect(resize).toEqual({columnId: 'name', size: initialColumnWidth + 5} as any); + expect(resize).toEqual({columnId: 'name', size: initialColumnWidth + 5}); component.endHoverState(); fixture.detectChanges(); @@ -626,12 +626,12 @@ describe('Material Popover Edit', () => { it('performs a column resize triggered via ColumnResizeNotifier', fakeAsync(() => { // Pre-verify that we are not updating the size to the initial size. - (expect(component.getColumnWidth(1)) as any).not.isApproximately(173); + expect(component.getColumnWidth(1)).not.isApproximately(173); component.columnResize.columnResizeNotifier.resize('name', 173); flush(); - (expect(component.getColumnWidth(1)) as any).isApproximately(173); + expect(component.getColumnWidth(1)).isApproximately(173); })); }); } @@ -660,13 +660,13 @@ describe('Material Popover Edit', () => { })); it('applies the persisted size', fakeAsync(() => { - (expect(component.getColumnWidth(1)).not as any).isApproximately(300); + expect(component.getColumnWidth(1)).not.isApproximately(300); columnSizeStore.emitSize('theTable', 'name', 300); flush(); - (expect(component.getColumnWidth(1)) as any).isApproximately(300); + expect(component.getColumnWidth(1)).isApproximately(300); })); it('persists the user-triggered size update', fakeAsync(() => { @@ -689,7 +689,7 @@ describe('Material Popover Edit', () => { const {tableId, columnId, sizePx} = columnSizeStore.setSizeCalls[0]; expect(tableId).toBe('theTable'); expect(columnId).toBe('name'); - (expect(sizePx) as any).isApproximately(initialColumnWidth + 5); + expect(sizePx).isApproximately(initialColumnWidth + 5); })); it('persists the user-triggered size update (live updates off)', fakeAsync(() => { @@ -714,7 +714,7 @@ describe('Material Popover Edit', () => { const {tableId, columnId, sizePx} = columnSizeStore.setSizeCalls[0]; expect(tableId).toBe('theTable'); expect(columnId).toBe('name'); - (expect(sizePx) as any).isApproximately(initialColumnWidth + 5); + expect(sizePx).isApproximately(initialColumnWidth + 5); })); }); }); diff --git a/src/material-luxon-adapter/adapter/luxon-date-adapter.ts b/src/material-luxon-adapter/adapter/luxon-date-adapter.ts index c465fd8d45a1..30d473b764b8 100644 --- a/src/material-luxon-adapter/adapter/luxon-date-adapter.ts +++ b/src/material-luxon-adapter/adapter/luxon-date-adapter.ts @@ -177,7 +177,7 @@ export class LuxonDateAdapter extends DateAdapter { return this._useUTC ? LuxonDateTime.utc(options) : LuxonDateTime.local(options); } - parse(value: any, parseFormat: string | string[]): LuxonDateTime | null { + parse(value: unknown, parseFormat: string | string[]): LuxonDateTime | null { const options: LuxonDateTimeOptions = this._getOptions(); if (typeof value == 'string' && value.length > 0) { @@ -245,7 +245,7 @@ export class LuxonDateAdapter extends DateAdapter { * (https://www.ietf.org/rfc/rfc3339.txt) and valid Date objects into valid DateTime and empty * string into null. Returns an invalid date for all other values. */ - override deserialize(value: any): LuxonDateTime | null { + override deserialize(value: unknown): LuxonDateTime | null { const options = this._getOptions(); let date: LuxonDateTime | undefined; if (value instanceof Date) { @@ -263,7 +263,7 @@ export class LuxonDateAdapter extends DateAdapter { return super.deserialize(value); } - isDateInstance(obj: any): boolean { + isDateInstance(obj: unknown): obj is LuxonDateTime { return obj instanceof LuxonDateTime; } @@ -315,7 +315,7 @@ export class LuxonDateAdapter extends DateAdapter { return date.second; } - override parseTime(value: any, parseFormat: string | string[]): LuxonDateTime | null { + override parseTime(value: unknown, parseFormat: string | string[]): LuxonDateTime | null { const result = this.parse(value, parseFormat); if ((!result || !this.isValid(result)) && typeof value === 'string') { diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index cbf303d33958..f1b4ac96aff5 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -187,7 +187,7 @@ export class MomentDateAdapter extends DateAdapter { return this._createMoment().locale(this.locale); } - parse(value: any, parseFormat: string | string[]): Moment | null { + parse(value: unknown, parseFormat: string | string[]): Moment | null { if (value && typeof value == 'string') { return this._createMoment(value, parseFormat, this.locale); } @@ -223,7 +223,7 @@ export class MomentDateAdapter extends DateAdapter { * (https://www.ietf.org/rfc/rfc3339.txt) and valid Date objects into valid Moments and empty * string into null. Returns an invalid date for all other values. */ - override deserialize(value: any): Moment | null { + override deserialize(value: unknown): Moment | null { let date; if (value instanceof Date) { date = this._createMoment(value).locale(this.locale); @@ -243,7 +243,7 @@ export class MomentDateAdapter extends DateAdapter { return super.deserialize(value); } - isDateInstance(obj: any): boolean { + isDateInstance(obj: unknown): obj is Moment { return moment.isMoment(obj); } @@ -285,7 +285,7 @@ export class MomentDateAdapter extends DateAdapter { return date.seconds(); } - override parseTime(value: any, parseFormat: string | string[]): Moment | null { + override parseTime(value: unknown, parseFormat: string | string[]): Moment | null { return this.parse(value, parseFormat); } diff --git a/src/universal-app/hydration.e2e.spec.ts b/src/universal-app/hydration.e2e.spec.ts index 974b1fcd289d..ace95ae861dc 100644 --- a/src/universal-app/hydration.e2e.spec.ts +++ b/src/universal-app/hydration.e2e.spec.ts @@ -1,5 +1,14 @@ import {browser, by, element, ExpectedConditions} from 'protractor'; +declare global { + interface Window { + ngDevMode: { + hydratedComponents: number; + componentsSkippedHydration: number; + }; + } +} + describe('hydration e2e', () => { beforeEach(async () => { await browser.waitForAngularEnabled(false); @@ -27,7 +36,7 @@ async function getHydrationState() { hydratedComponents: number; componentsSkippedHydration: number; }>(() => ({ - hydratedComponents: (window as any).ngDevMode.hydratedComponents, - componentsSkippedHydration: (window as any).ngDevMode.componentsSkippedHydration, + hydratedComponents: window.ngDevMode.hydratedComponents, + componentsSkippedHydration: window.ngDevMode.componentsSkippedHydration, })); } diff --git a/src/universal-app/kitchen-sink/kitchen-sink.ts b/src/universal-app/kitchen-sink/kitchen-sink.ts index f4050a3d4a16..dfd62bfcfafb 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.ts +++ b/src/universal-app/kitchen-sink/kitchen-sink.ts @@ -61,8 +61,15 @@ import {MatTooltipModule} from '@angular/material/tooltip'; import {YouTubePlayer} from '@angular/youtube-player'; import {Observable, of as observableOf} from 'rxjs'; -export class TableDataSource extends DataSource { - connect(): Observable { +interface ElementItem { + position: number; + name: string; + weight: number; + symbol: string; +} + +export class TableDataSource extends DataSource { + connect(): Observable { return observableOf([ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, diff --git a/src/youtube-player/BUILD.bazel b/src/youtube-player/BUILD.bazel index aca7fc21687e..001d3068e8dc 100644 --- a/src/youtube-player/BUILD.bazel +++ b/src/youtube-player/BUILD.bazel @@ -41,6 +41,7 @@ ng_project( "//:node_modules/@angular/core", "//:node_modules/@types/youtube", "//:node_modules/rxjs", + "//:node_modules/safevalues", ], ) diff --git a/src/youtube-player/fake-youtube-player.ts b/src/youtube-player/fake-youtube-player.ts index b48368359b2c..c71c329d13db 100644 --- a/src/youtube-player/fake-youtube-player.ts +++ b/src/youtube-player/fake-youtube-player.ts @@ -30,6 +30,11 @@ interface FakeYtNamespace { namespace: typeof YT; } +type ListenersStore = {[E in EventName]?: Set>}; +type Listener = NonNullable; +type ListenerArg = + Listener extends YT.PlayerEventHandler ? T : never; + export function createFakeYtNamespace(): FakeYtNamespace { const playerSpy: jasmine.SpyObj = jasmine.createSpyObj('Player', [ 'getPlayerState', @@ -63,7 +68,7 @@ export function createFakeYtNamespace(): FakeYtNamespace { ]); let playerConfig: YT.PlayerOptions | undefined; - const boundListeners = new Map void>>(); + const boundListeners: ListenersStore = {}; const playerCtorSpy = jasmine.createSpy('Player Constructor'); // The spy target function cannot be an arrow-function as this breaks when created through `new`. @@ -72,28 +77,27 @@ export function createFakeYtNamespace(): FakeYtNamespace { return playerSpy; }); - playerSpy.addEventListener.and.callFake((name: keyof YT.Events, listener: (e: any) => any) => { - if (!boundListeners.has(name)) { - boundListeners.set(name, new Set()); + playerSpy.addEventListener.and.callFake((name, listener) => { + const store: ListenersStore = boundListeners; + if (!store[name]) { + store[name] = new Set(); } - boundListeners.get(name)!.add(listener); + store[name].add(listener); }); - playerSpy.removeEventListener.and.callFake((name: keyof YT.Events, listener: (e: any) => any) => { - if (boundListeners.has(name)) { - boundListeners.get(name)!.delete(listener); - } + playerSpy.removeEventListener.and.callFake((name, listener) => { + boundListeners[name]?.delete(listener); }); - function eventHandlerFactory(name: keyof YT.Events) { - return (arg: Object = {}) => { + function eventHandlerFactory(name: EventName) { + return (arg = {} as ListenerArg) => { if (!playerConfig) { throw new Error(`Player not initialized before ${name} called`); } - if (boundListeners.has(name)) { - boundListeners.get(name)!.forEach(callback => callback(arg)); - } + boundListeners[name]?.forEach(callback => + (callback as (arg: ListenerArg) => void)(arg), + ); }; } diff --git a/src/youtube-player/package.json b/src/youtube-player/package.json index 001df21b69bb..cdc7f392f1ac 100644 --- a/src/youtube-player/package.json +++ b/src/youtube-player/package.json @@ -23,7 +23,8 @@ "peerDependencies": { "@angular/core": "0.0.0-NG", "@angular/common": "0.0.0-NG", - "rxjs": "^6.5.3 || ^7.4.0" + "rxjs": "^6.5.3 || ^7.4.0", + "safevalues": "^1.2.0" }, "sideEffects": false, "schematics": "./schematics/collection.json", diff --git a/src/youtube-player/youtube-player.spec.ts b/src/youtube-player/youtube-player.spec.ts index 757baf8bf435..b0cfafebdb2a 100644 --- a/src/youtube-player/youtube-player.spec.ts +++ b/src/youtube-player/youtube-player.spec.ts @@ -10,8 +10,10 @@ import { } from './youtube-player'; import {PlaceholderImageQuality} from './youtube-player-placeholder'; +declare var window: Window; + const VIDEO_ID = 'a12345'; -const YT_LOADING_STATE_MOCK = {loading: 1, loaded: 0}; +const YT_LOADING_STATE_MOCK = {loading: 1, loaded: 0} as unknown as typeof YT; const TEST_PROVIDERS: (Provider | EnvironmentProviders)[] = [ { provide: YOUTUBE_PLAYER_CONFIG, @@ -56,7 +58,7 @@ describe('YoutubePlayer', () => { }); afterEach(() => { - (window as any).YT = undefined; + window.YT = undefined; window.onYouTubeIframeAPIReady = undefined; }); @@ -540,17 +542,17 @@ describe('YoutubePlayer', () => { let api: typeof YT; beforeEach(() => { - api = window.YT; - (window as any).YT = undefined; + api = window.YT!; + window.YT = undefined; }); afterEach(() => { - (window as any).YT = undefined; + window.YT = undefined; window.onYouTubeIframeAPIReady = undefined; }); it('waits until the api is ready before initializing', () => { - (window.YT as any) = YT_LOADING_STATE_MOCK; + window.YT = YT_LOADING_STATE_MOCK; TestBed.configureTestingModule({providers: TEST_PROVIDERS}); fixture = TestBed.createComponent(TestApp); testComponent = fixture.debugElement.componentInstance; @@ -560,7 +562,7 @@ describe('YoutubePlayer', () => { expect(playerCtorSpy).not.toHaveBeenCalled(); - window.YT = api!; + window.YT = api; window.onYouTubeIframeAPIReady!(); expect(playerCtorSpy).toHaveBeenCalledWith( @@ -585,7 +587,7 @@ describe('YoutubePlayer', () => { expect(playerCtorSpy).not.toHaveBeenCalled(); - window.YT = api!; + window.YT = api; window.onYouTubeIframeAPIReady!(); expect(spy).toHaveBeenCalled(); @@ -601,7 +603,7 @@ describe('YoutubePlayer', () => { }); afterEach(() => { - fixture = testComponent = (window as any).YT = window.onYouTubeIframeAPIReady = undefined!; + fixture = testComponent = window.YT = window.onYouTubeIframeAPIReady = undefined!; }); it('should show a placeholder', () => { diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts index 398b8d8711bc..c5008ceb3ae6 100644 --- a/src/youtube-player/youtube-player.ts +++ b/src/youtube-player/youtube-player.ts @@ -32,6 +32,8 @@ import { EventEmitter, } from '@angular/core'; import {isPlatformBrowser} from '@angular/common'; +import {trustedResourceUrl} from 'safevalues'; +import {setScriptSrc} from 'safevalues/dom'; import {Observable, of as observableOf, Subject, BehaviorSubject, fromEventPattern} from 'rxjs'; import {takeUntil, switchMap} from 'rxjs/operators'; import {PlaceholderImageQuality, YouTubePlayerPlaceholder} from './youtube-player-placeholder'; @@ -743,7 +745,7 @@ function loadApi(nonce: string | null): void { } // We can use `document` directly here, because this logic doesn't run outside the browser. - const url = 'https://www.youtube.com/iframe_api'; + const url = trustedResourceUrl`https://www.youtube.com/iframe_api`; const script = document.createElement('script'); const callback = (event: Event) => { script.removeEventListener('load', callback); @@ -759,7 +761,7 @@ function loadApi(nonce: string | null): void { }; script.addEventListener('load', callback); script.addEventListener('error', callback); - (script as any).src = url; + setScriptSrc(script, url); script.async = true; if (nonce) { diff --git a/yarn.lock b/yarn.lock index 9055b5004e34..ab9c03762759 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12963,6 +12963,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +safevalues@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safevalues/-/safevalues-1.2.0.tgz#f9e646d6ebf31788004ef192d2a7d646c9896bb2" + integrity sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw== + sass-loader@16.0.5: version "16.0.5" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.5.tgz#257bc90119ade066851cafe7f2c3f3504c7cda98"