From 9aa5b3dec50655be19c3eb1890c5e4be3c269b9a Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 10:12:28 +0100 Subject: [PATCH 01/12] fix(youtube-player): Eliminate `any` casts --- src/youtube-player/fake-youtube-player.ts | 30 +++++++++++------------ src/youtube-player/youtube-player.spec.ts | 20 ++++++++------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/youtube-player/fake-youtube-player.ts b/src/youtube-player/fake-youtube-player.ts index b48368359b2c..c4158828ed7b 100644 --- a/src/youtube-player/fake-youtube-player.ts +++ b/src/youtube-player/fake-youtube-player.ts @@ -63,7 +63,7 @@ export function createFakeYtNamespace(): FakeYtNamespace { ]); let playerConfig: YT.PlayerOptions | undefined; - const boundListeners = new Map void>>(); + const boundListeners = new Map void>>(); const playerCtorSpy = jasmine.createSpy('Player Constructor'); // The spy target function cannot be an arrow-function as this breaks when created through `new`. @@ -72,18 +72,20 @@ 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()); - } - boundListeners.get(name)!.add(listener); - }); + playerSpy.addEventListener.and.callFake( + (name: keyof YT.Events, listener: (e: unknown) => unknown) => { + if (!boundListeners.has(name)) { + boundListeners.set(name, new Set()); + } + boundListeners.get(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: keyof YT.Events, listener: (e: unknown) => unknown) => { + boundListeners.get(name)?.delete(listener); + }, + ); function eventHandlerFactory(name: keyof YT.Events) { return (arg: Object = {}) => { @@ -91,9 +93,7 @@ export function createFakeYtNamespace(): FakeYtNamespace { throw new Error(`Player not initialized before ${name} called`); } - if (boundListeners.has(name)) { - boundListeners.get(name)!.forEach(callback => callback(arg)); - } + boundListeners.get(name)?.forEach(callback => callback(arg)); }; } 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', () => { From 597ddf726c9fd13071263a493eb7bb867c5d038c Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 11:01:55 +0100 Subject: [PATCH 02/12] fix(universal-app): ngDevMode always defined in hydration e2e test --- src/universal-app/hydration.e2e.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/universal-app/hydration.e2e.spec.ts b/src/universal-app/hydration.e2e.spec.ts index 974b1fcd289d..634f280b4a54 100644 --- a/src/universal-app/hydration.e2e.spec.ts +++ b/src/universal-app/hydration.e2e.spec.ts @@ -1,5 +1,11 @@ import {browser, by, element, ExpectedConditions} from 'protractor'; +// Expect `ngDevMode` to be always set: +declare const ngDevMode: { + hydratedComponents: number; + componentsSkippedHydration: number; +}; + describe('hydration e2e', () => { beforeEach(async () => { await browser.waitForAngularEnabled(false); @@ -27,7 +33,7 @@ async function getHydrationState() { hydratedComponents: number; componentsSkippedHydration: number; }>(() => ({ - hydratedComponents: (window as any).ngDevMode.hydratedComponents, - componentsSkippedHydration: (window as any).ngDevMode.componentsSkippedHydration, + hydratedComponents: ngDevMode.hydratedComponents, + componentsSkippedHydration: ngDevMode.componentsSkippedHydration, })); } From 65f8b7a1dc0b5b95fe28c026dbe00db92dcca623 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 11:12:21 +0100 Subject: [PATCH 03/12] fix(google-maps): Replace any types with unknown --- src/google-maps/map-event-manager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/google-maps/map-event-manager.ts b/src/google-maps/map-event-manager.ts index 0a85bc8fb39f..ceb290607b90 100644 --- a/src/google-maps/map-event-manager.ts +++ b/src/google-maps/map-event-manager.ts @@ -14,7 +14,7 @@ type MapEventManagerTarget = | { addListener: ( name: string, - callback: (...args: any[]) => void, + callback: (...args: unknown[]) => void, ) => google.maps.MapsEventListener | undefined; } | undefined; @@ -22,7 +22,7 @@ type MapEventManagerTarget = /** 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); @@ -48,8 +48,8 @@ export class MapEventManager { return undefined; } - const listener = target.addListener(name, (event: T) => { - this._ngZone.run(() => observer.next(event)); + const listener = target.addListener(name, (event: unknown) => { + this._ngZone.run(() => observer.next(event as T)); }); // If there's an error when initializing the Maps API (e.g. a wrong API key), it will From b496d506c22e8351cef93c4863485d1f9dda62f1 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 11:18:09 +0100 Subject: [PATCH 04/12] fix(universal-app): Add ElementItem interface --- src/universal-app/kitchen-sink/kitchen-sink.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/universal-app/kitchen-sink/kitchen-sink.ts b/src/universal-app/kitchen-sink/kitchen-sink.ts index f4050a3d4a16..ca89fb4af5e0 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'}, @@ -77,7 +84,7 @@ export class TableDataSource extends DataSource { ]); } - disconnect() {} + override disconnect() {} } export const AUTOMATED_KITCHEN_SINK = new InjectionToken('AUTOMATED_KITCHEN_SINK'); From e302c421fc26246493bcaca1cd6b23b5cac7a75b Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 11:22:02 +0100 Subject: [PATCH 05/12] fix(material-moment-adapter): Replace any with unknowns --- .../adapter/moment-date-adapter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); } From 71861c4c60d7d4be23a71aaafdc99c77b5c53c84 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 11:27:13 +0100 Subject: [PATCH 06/12] fix(material-luxon-adapter): Replace any with unknown --- src/material-luxon-adapter/adapter/luxon-date-adapter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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') { From a709636d228e7fd29221c54cf08839d4c233d055 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 11:28:40 +0100 Subject: [PATCH 07/12] fix(date-fns-adapter): Replace any with unknown --- src/material-date-fns-adapter/adapter/date-fns-adapter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); } From 85430b8617ec3f61ccb9d3cc2fb1b967b950f20f Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Feb 2025 12:30:57 +0100 Subject: [PATCH 08/12] fix(material-experimental/column-resize): Add NumberMatchers interface in tests --- .../column-resize/column-resize.spec.ts | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) 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); })); }); }); From 8e2c1809ed425617c73af5b86b6837b6bc5d39e1 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 21 Mar 2025 15:03:00 +0100 Subject: [PATCH 09/12] refactor(multiple): improve types --- src/google-maps/map-event-manager.ts | 10 ++--- src/universal-app/hydration.e2e.spec.ts | 17 +++++---- .../kitchen-sink/kitchen-sink.ts | 4 +- src/youtube-player/fake-youtube-player.ts | 38 ++++++++++--------- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/google-maps/map-event-manager.ts b/src/google-maps/map-event-manager.ts index ceb290607b90..452536544da8 100644 --- a/src/google-maps/map-event-manager.ts +++ b/src/google-maps/map-event-manager.ts @@ -12,10 +12,10 @@ import {switchMap} from 'rxjs/operators'; type MapEventManagerTarget = | { - addListener: ( + addListener( name: string, - callback: (...args: unknown[]) => void, - ) => google.maps.MapsEventListener | undefined; + callback: (args: T) => void, + ): google.maps.MapsEventListener | undefined; } | undefined; @@ -48,8 +48,8 @@ export class MapEventManager { return undefined; } - const listener = target.addListener(name, (event: unknown) => { - this._ngZone.run(() => observer.next(event as T)); + const listener = target.addListener(name, (event: T) => { + this._ngZone.run(() => observer.next(event)); }); // If there's an error when initializing the Maps API (e.g. a wrong API key), it will diff --git a/src/universal-app/hydration.e2e.spec.ts b/src/universal-app/hydration.e2e.spec.ts index 634f280b4a54..ace95ae861dc 100644 --- a/src/universal-app/hydration.e2e.spec.ts +++ b/src/universal-app/hydration.e2e.spec.ts @@ -1,10 +1,13 @@ import {browser, by, element, ExpectedConditions} from 'protractor'; -// Expect `ngDevMode` to be always set: -declare const ngDevMode: { - hydratedComponents: number; - componentsSkippedHydration: number; -}; +declare global { + interface Window { + ngDevMode: { + hydratedComponents: number; + componentsSkippedHydration: number; + }; + } +} describe('hydration e2e', () => { beforeEach(async () => { @@ -33,7 +36,7 @@ async function getHydrationState() { hydratedComponents: number; componentsSkippedHydration: number; }>(() => ({ - hydratedComponents: ngDevMode.hydratedComponents, - componentsSkippedHydration: 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 ca89fb4af5e0..dfd62bfcfafb 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.ts +++ b/src/universal-app/kitchen-sink/kitchen-sink.ts @@ -69,7 +69,7 @@ interface ElementItem { } export class TableDataSource extends DataSource { - connect(): Observable> { + connect(): Observable { return observableOf([ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, @@ -84,7 +84,7 @@ export class TableDataSource extends DataSource { ]); } - override disconnect() {} + disconnect() {} } export const AUTOMATED_KITCHEN_SINK = new InjectionToken('AUTOMATED_KITCHEN_SINK'); diff --git a/src/youtube-player/fake-youtube-player.ts b/src/youtube-player/fake-youtube-player.ts index c4158828ed7b..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: unknown) => unknown) => { - if (!boundListeners.has(name)) { - boundListeners.set(name, new Set()); - } - boundListeners.get(name)!.add(listener); - }, - ); + playerSpy.addEventListener.and.callFake((name, listener) => { + const store: ListenersStore = boundListeners; + if (!store[name]) { + store[name] = new Set(); + } + store[name].add(listener); + }); - playerSpy.removeEventListener.and.callFake( - (name: keyof YT.Events, listener: (e: unknown) => unknown) => { - 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`); } - boundListeners.get(name)?.forEach(callback => callback(arg)); + boundListeners[name]?.forEach(callback => + (callback as (arg: ListenerArg) => void)(arg), + ); }; } From 41c329bb9e18cba94cf8b2ad65cf56df837eeae7 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Mar 2025 18:20:48 +0100 Subject: [PATCH 10/12] build: add safevalues to pnpm-lock.yaml --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79acb67ca25d..4c764569f025 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13830,7 +13830,7 @@ packages: resolution: {integrity: sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw==} dev: false - /sass-loader@16.0.5(sass@1.86.1)(webpack@5.98.0): + /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'} peerDependencies: From ee774113edcf6ba14d10f0c7c4396144ef28543a Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 11 Apr 2025 12:06:52 +0200 Subject: [PATCH 11/12] build: revert pnpm-lock change --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c764569f025..79acb67ca25d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13830,7 +13830,7 @@ packages: resolution: {integrity: sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw==} dev: false - /sass-loader@16.0.5(sass@1.86.0)(webpack@5.98.0): + /sass-loader@16.0.5(sass@1.86.1)(webpack@5.98.0): resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} engines: {node: '>= 18.12.0'} peerDependencies: From 4554496486c74e8111fa1493747129b3c367e0f7 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 11 Apr 2025 12:18:00 +0200 Subject: [PATCH 12/12] refactor(google-maps): improve `addListener` type --- src/google-maps/map-event-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google-maps/map-event-manager.ts b/src/google-maps/map-event-manager.ts index 452536544da8..54120e7da97a 100644 --- a/src/google-maps/map-event-manager.ts +++ b/src/google-maps/map-event-manager.ts @@ -12,9 +12,9 @@ import {switchMap} from 'rxjs/operators'; type MapEventManagerTarget = | { - addListener( + addListener( name: string, - callback: (args: T) => void, + callback: (...args: T) => void, ): google.maps.MapsEventListener | undefined; } | undefined;