From 1f6504bfd6f57ef5b5fbc9887204fbe9830fe0bb Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 21 Nov 2025 14:46:26 +0100 Subject: [PATCH 1/5] refactor(multiple): Remove any in sort, stepper, table, timepicker, tooltip, tree --- goldens/material/stepper/index.api.md | 8 ++--- goldens/material/timepicker/index.api.md | 4 +-- goldens/material/tooltip/index.api.md | 6 ++-- src/material/sort/sort.spec.ts | 4 +-- src/material/stepper/step-content.ts | 4 +-- src/material/table/table-data-source.ts | 6 +++- src/material/table/table.spec.ts | 34 +++++++++---------- src/material/timepicker/timepicker-input.ts | 6 ++-- src/material/tooltip/tooltip.spec.ts | 26 +++++++------- src/material/tooltip/tooltip.ts | 27 ++++++++++----- src/material/tree/node.ts | 2 +- .../tree/tree-using-tree-control.spec.ts | 19 ++++++----- src/material/tree/tree.spec.ts | 19 ++++++----- 13 files changed, 93 insertions(+), 72 deletions(-) diff --git a/goldens/material/stepper/index.api.md b/goldens/material/stepper/index.api.md index 89debf3c8a63..c9661064d98e 100644 --- a/goldens/material/stepper/index.api.md +++ b/goldens/material/stepper/index.api.md @@ -50,14 +50,14 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI } // @public -export class MatStepContent { +export class MatStepContent { constructor(...args: unknown[]); // (undocumented) - _template: TemplateRef; + _template: TemplateRef; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration, "ng-template[matStepContent]", never, {}, {}, never, never, true, never>; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration, never>; } // @public (undocumented) diff --git a/goldens/material/timepicker/index.api.md b/goldens/material/timepicker/index.api.md index 29b17a051d43..4176fd9e10a2 100644 --- a/goldens/material/timepicker/index.api.md +++ b/goldens/material/timepicker/index.api.md @@ -109,7 +109,7 @@ export class MatTimepickerInput implements MatTimepickerConnectedInput, Co // (undocumented) ngOnDestroy(): void; readonly openOnClick: InputSignalWithTransform; - registerOnChange(fn: (value: any) => void): void; + registerOnChange(fn: (value: unknown) => void): void; registerOnTouched(fn: () => void): void; registerOnValidatorChange(fn: () => void): void; setDisabledState(isDisabled: boolean): void; @@ -117,7 +117,7 @@ export class MatTimepickerInput implements MatTimepickerConnectedInput, Co timepickerValueAssigned(value: D | null): void; validate(control: AbstractControl): ValidationErrors | null; readonly value: ModelSignal; - writeValue(value: any): void; + writeValue(value: unknown): void; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration, "input[matTimepicker]", ["matTimepickerInput"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "timepicker": { "alias": "matTimepicker"; "required": true; "isSignal": true; }; "min": { "alias": "matTimepickerMin"; "required": false; "isSignal": true; }; "max": { "alias": "matTimepickerMax"; "required": false; "isSignal": true; }; "openOnClick": { "alias": "matTimepickerOpenOnClick"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; // (undocumented) diff --git a/goldens/material/tooltip/index.api.md b/goldens/material/tooltip/index.api.md index 5b3a661ac3c2..fadab81be148 100644 --- a/goldens/material/tooltip/index.api.md +++ b/goldens/material/tooltip/index.api.md @@ -76,10 +76,10 @@ export class MatTooltip implements OnDestroy, AfterViewInit { y: number; }): void; get tooltipClass(): string | string[] | Set | { - [key: string]: any; + [key: string]: unknown; }; set tooltipClass(value: string | string[] | Set | { - [key: string]: any; + [key: string]: unknown; }); // (undocumented) _tooltipInstance: TooltipComponent | null; @@ -143,7 +143,7 @@ export class TooltipComponent implements OnDestroy { show(delay: number): void; _tooltip: ElementRef; tooltipClass: string | string[] | Set | { - [key: string]: any; + [key: string]: unknown; }; _triggerElement: HTMLElement; // (undocumented) diff --git a/src/material/sort/sort.spec.ts b/src/material/sort/sort.spec.ts index 10181b65bf72..a5a3e3e94fa5 100644 --- a/src/material/sort/sort.spec.ts +++ b/src/material/sort/sort.spec.ts @@ -504,8 +504,8 @@ class SimpleMatSortApp { } } -class FakeDataSource extends DataSource { - connect(collectionViewer: CollectionViewer): Observable { +class FakeDataSource extends DataSource { + connect(collectionViewer: CollectionViewer): Observable { return collectionViewer.viewChange.pipe(map(() => [])); } disconnect() {} diff --git a/src/material/stepper/step-content.ts b/src/material/stepper/step-content.ts index 8328c0d10f5e..569f22e23fb3 100644 --- a/src/material/stepper/step-content.ts +++ b/src/material/stepper/step-content.ts @@ -14,8 +14,8 @@ import {Directive, TemplateRef, inject} from '@angular/core'; @Directive({ selector: 'ng-template[matStepContent]', }) -export class MatStepContent { - _template = inject>(TemplateRef); +export class MatStepContent { + _template = inject>(TemplateRef); constructor(...args: unknown[]); constructor() {} diff --git a/src/material/table/table-data-source.ts b/src/material/table/table-data-source.ts index 81c16d3acd14..dcd9435916c7 100644 --- a/src/material/table/table-data-source.ts +++ b/src/material/table/table-data-source.ts @@ -229,10 +229,14 @@ export class MatTableDataSource extend * @returns Whether the filter matches against the data */ filterPredicate: (data: T, filter: string) => boolean = (data: T, filter: string): boolean => { + if ((typeof ngDevMode === 'undefined' || ngDevMode) && typeof data !== 'object') { + throw new Error('Default implementation of filterPredicate requires data to be object.'); + } + // Transform the filter by converting it to lowercase and removing whitespace. const transformedFilter = filter.trim().toLowerCase(); // Loops over the values in the array and returns true if any of them match the filter string - return Object.values(data as {[key: string]: any}).some(value => + return Object.values(data as object).some(value => `${value}`.toLowerCase().includes(transformedFilter), ); }; diff --git a/src/material/table/table.spec.ts b/src/material/table/table.spec.ts index ef9b41b31f17..6b03b81bfe38 100644 --- a/src/material/table/table.spec.ts +++ b/src/material/table/table.spec.ts @@ -16,9 +16,9 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], ['fourth_row'], ['Footer A', 'Footer B', 'Footer C'], ]); @@ -64,10 +64,10 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], - [data[3].a, data[3].b, data[3].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], + [data[3].a, data[3].b, data[3].c] as string[], ]); }); @@ -159,9 +159,9 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], ]); }); @@ -173,9 +173,9 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], ]); }); @@ -357,7 +357,7 @@ describe('MatTable', () => { ]); // Change the filter to a falsy value that might come in from the view. - dataSource.filter = 0 as any; + dataSource.filter = 0 as unknown as string; flushMicrotasks(); fixture.detectChanges(); expectTableToMatchContent(tableElement, [ @@ -604,7 +604,7 @@ describe('MatTable', () => { ['Footer A', 'Footer B', 'Footer C'], ]); - dataSource.data = {} as any; + dataSource.data = {} as TestData[]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expectTableToMatchContent(tableElement, [ @@ -1108,7 +1108,7 @@ function getActualTableContent(tableElement: Element): string[][] { return actualTableContent.map(row => row.map(cell => cell.textContent!.trim())); } -export function expectTableToMatchContent(tableElement: Element, expected: any[]) { +export function expectTableToMatchContent(tableElement: Element, expected: string[][]) { const missedExpectations: string[] = []; function checkCellContent(actualCell: string, expectedCell: string) { if (actualCell !== expectedCell) { @@ -1134,7 +1134,7 @@ export function expectTableToMatchContent(tableElement: Element, expected: any[] } row.forEach((actualCell, cellIndex) => { - const expectedCell = expectedRow ? expectedRow[cellIndex] : null; + const expectedCell = expectedRow[cellIndex]; checkCellContent(actualCell, expectedCell); }); }); diff --git a/src/material/timepicker/timepicker-input.ts b/src/material/timepicker/timepicker-input.ts index 656860228778..754bd01710f4 100644 --- a/src/material/timepicker/timepicker-input.ts +++ b/src/material/timepicker/timepicker-input.ts @@ -88,7 +88,7 @@ export class MatTimepickerInput private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!; private _formField = inject(MAT_FORM_FIELD, {optional: true}); - private _onChange: ((value: any) => void) | undefined; + private _onChange: ((value: unknown) => void) | undefined; private _onTouched: (() => void) | undefined; private _validatorOnChange: (() => void) | undefined; private _cleanupClick: () => void; @@ -195,7 +195,7 @@ export class MatTimepickerInput * Implemented as a part of `ControlValueAccessor`. * @docs-private */ - writeValue(value: any): void { + writeValue(value: unknown): void { // Note that we need to deserialize here, rather than depend on the value change effect, // because `getValidDateOrNull` will clobber the value if it's parseable, but not created by // the current adapter (see #30140). @@ -207,7 +207,7 @@ export class MatTimepickerInput * Implemented as a part of `ControlValueAccessor`. * @docs-private */ - registerOnChange(fn: (value: any) => void): void { + registerOnChange(fn: (value: unknown) => void): void { this._onChange = fn; } diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts index 3509ff5fbd96..3c515043f191 100644 --- a/src/material/tooltip/tooltip.spec.ts +++ b/src/material/tooltip/tooltip.spec.ts @@ -1386,7 +1386,7 @@ describe('MatTooltip', () => { fixture.detectChanges(); const styles = fixture.nativeElement.querySelector('button').style; - expect(styles.touchAction || (styles as any).webkitUserDrag).toBe('none'); + expect(styles.touchAction || styles.webkitUserDrag).toBe('none'); }); it('should allow native touch interactions if touch gestures are turned off', () => { @@ -1395,7 +1395,7 @@ describe('MatTooltip', () => { fixture.detectChanges(); const styles = fixture.nativeElement.querySelector('button').style; - expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); + expect(styles.touchAction || styles.webkitUserDrag).toBeFalsy(); }); it('should allow text selection on inputs when gestures are set to auto', () => { @@ -1407,13 +1407,13 @@ describe('MatTooltip', () => { expect(inputStyle.userSelect).toBeFalsy(); expect(inputStyle.webkitUserSelect).toBeFalsy(); - expect((inputStyle as any).msUserSelect).toBeFalsy(); - expect((inputStyle as any).MozUserSelect).toBeFalsy(); + expect(inputStyle.msUserSelect).toBeFalsy(); + expect(inputStyle.MozUserSelect).toBeFalsy(); expect(textareaStyle.userSelect).toBeFalsy(); expect(textareaStyle.webkitUserSelect).toBeFalsy(); - expect((textareaStyle as any).msUserSelect).toBeFalsy(); - expect((textareaStyle as any).MozUserSelect).toBeFalsy(); + expect(textareaStyle.msUserSelect).toBeFalsy(); + expect(textareaStyle.MozUserSelect).toBeFalsy(); }); it('should disable text selection on inputs when gestures are set to on', () => { @@ -1425,14 +1425,14 @@ describe('MatTooltip', () => { const inputUserSelect = inputStyle.userSelect || inputStyle.webkitUserSelect || - (inputStyle as any).msUserSelect || - (inputStyle as any).MozUserSelect; + inputStyle.msUserSelect || + inputStyle.MozUserSelect; const textareaStyle = fixture.componentInstance.textarea.nativeElement.style; const textareaUserSelect = textareaStyle.userSelect || textareaStyle.webkitUserSelect || - (textareaStyle as any).msUserSelect || - (textareaStyle as any).MozUserSelect; + textareaStyle.msUserSelect || + textareaStyle.MozUserSelect; expect(inputUserSelect).toBe('none'); expect(textareaUserSelect).toBe('none'); @@ -1570,7 +1570,7 @@ describe('MatTooltip', () => { }) class BasicTooltipDemo { position: TooltipPosition = 'below'; - message: any = initialTooltipMessage; + message: string | number = initialTooltipMessage; showButton = true; showTooltipClass = false; tooltipDisabled = false; @@ -1683,7 +1683,7 @@ class TooltipOnDraggableElement { imports: [MatTooltip], }) class TooltipDemoWithoutPositionBinding { - message: any = initialTooltipMessage; + message: string = initialTooltipMessage; @ViewChild(MatTooltip) tooltip: MatTooltip; @ViewChild('button') button: ElementRef; } @@ -1705,7 +1705,7 @@ class TooltipDemoWithoutTooltipClassBinding { imports: [MatTooltip], }) class TooltipDemoWithTooltipClassBinding { - message: any = initialTooltipMessage; + message: string = initialTooltipMessage; @ViewChild(MatTooltip) tooltip: MatTooltip; @ViewChild('button') button: ElementRef; } diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index a3c6f63751f0..996ade691b04 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -55,6 +55,15 @@ import {ComponentPortal} from '@angular/cdk/portal'; import {Observable, Subject} from 'rxjs'; import {_animationsDisabled} from '../core'; +declare global { + interface CSSStyleDeclaration { + msUserSelect: string; + MozUserSelect: string; + webkitUserDrag: string; + webkitTapHighlightColor: string; + } +} + /** Possible positions for a tooltip. */ export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after'; @@ -194,7 +203,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { private _position: TooltipPosition = 'below'; private _positionAtOrigin: boolean = false; private _disabled: boolean = false; - private _tooltipClass: string | string[] | Set | {[key: string]: any}; + private _tooltipClass: string | string[] | Set | {[key: string]: unknown}; private _viewInitialized = false; private _pointerExitEventsInitialized = false; private readonly _tooltipComponent = TooltipComponent; @@ -336,7 +345,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { return this._tooltipClass; } - set tooltipClass(value: string | string[] | Set | {[key: string]: any}) { + set tooltipClass(value: string | string[] | Set | {[key: string]: unknown}) { this._tooltipClass = value; if (this._tooltipInstance) { this._setTooltipClass(this._tooltipClass); @@ -701,7 +710,9 @@ export class MatTooltip implements OnDestroy, AfterViewInit { } /** Updates the tooltip class */ - private _setTooltipClass(tooltipClass: string | string[] | Set | {[key: string]: any}) { + private _setTooltipClass( + tooltipClass: string | string[] | Set | {[key: string]: unknown}, + ) { if (this._tooltipInstance) { this._tooltipInstance.tooltipClass = tooltipClass; this._tooltipInstance._markForCheck(); @@ -889,20 +900,20 @@ export class MatTooltip implements OnDestroy, AfterViewInit { // textareas, because it prevents the user from typing into them on iOS Safari. if (gestures === 'on' || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA')) { style.userSelect = - (style as any).msUserSelect = + style.msUserSelect = style.webkitUserSelect = - (style as any).MozUserSelect = + style.MozUserSelect = 'none'; } // If we have `auto` gestures and the element uses native HTML dragging, // we don't set `-webkit-user-drag` because it prevents the native behavior. if (gestures === 'on' || !element.draggable) { - (style as any).webkitUserDrag = 'none'; + style.webkitUserDrag = 'none'; } style.touchAction = 'none'; - (style as any).webkitTapHighlightColor = 'transparent'; + style.webkitTapHighlightColor = 'transparent'; } } @@ -963,7 +974,7 @@ export class TooltipComponent implements OnDestroy { message: string; /** Classes to be added to the tooltip. Supports the same syntax as `ngClass`. */ - tooltipClass: string | string[] | Set | {[key: string]: any}; + tooltipClass: string | string[] | Set | {[key: string]: unknown}; /** The timeout ID of any current timer set to show the tooltip */ private _showTimeoutId: ReturnType | undefined; diff --git a/src/material/tree/node.ts b/src/material/tree/node.ts index ef7c17a1d970..d88c18db27e1 100644 --- a/src/material/tree/node.ts +++ b/src/material/tree/node.ts @@ -31,7 +31,7 @@ import {NoopTreeKeyManager, TreeKeyManagerItem, TreeKeyManagerStrategy} from '@a function isNoopTreeKeyManager( keyManager: TreeKeyManagerStrategy, ): keyManager is NoopTreeKeyManager { - return !!(keyManager as any)._isNoopTreeKeyManager; + return !!(keyManager as NoopTreeKeyManager)._isNoopTreeKeyManager; } /** diff --git a/src/material/tree/tree-using-tree-control.spec.ts b/src/material/tree/tree-using-tree-control.spec.ts index b44b11f9f2b8..1d51cdfe4360 100644 --- a/src/material/tree/tree-using-tree-control.spec.ts +++ b/src/material/tree/tree-using-tree-control.spec.ts @@ -19,6 +19,9 @@ import { MatTreeNestedDataSource, } from './index'; +type NodeContent = string[]; +type TreeContent = NodeContent[]; + describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ const _ = {}; @@ -754,11 +757,11 @@ function getNodes(treeElement: Element): HTMLElement[] { function expectFlatTreeToMatch( treeElement: Element, expectedPaddingIndent: number = 28, - ...expectedTree: any[] + ...expectedTree: TreeContent ) { const missedExpectations: string[] = []; - function checkNode(node: Element, expectedNode: any[]) { + function checkNode(node: Element, expectedNode: NodeContent) { const actualTextContent = node.textContent!.trim(); const expectedTextContent = expectedNode[expectedNode.length - 1]; if (actualTextContent !== expectedTextContent) { @@ -768,7 +771,7 @@ function expectFlatTreeToMatch( } } - function checkLevel(node: Element, expectedNode: any[]) { + function checkLevel(node: Element, expectedNode: NodeContent) { const rawLevel = (node as HTMLElement).style.paddingLeft; // Some browsers return 0, while others return 0px. @@ -788,7 +791,7 @@ function expectFlatTreeToMatch( } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkLevel(node, expected); checkNode(node, expected); @@ -799,9 +802,9 @@ function expectFlatTreeToMatch( } } -function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { +function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: TreeContent) { const missedExpectations: string[] = []; - function checkNodeContent(node: Element, expectedNode: any[]) { + function checkNodeContent(node: Element, expectedNode: NodeContent) { const expectedTextContent = expectedNode[expectedNode.length - 1]; const actualTextContent = node.childNodes.item(0).textContent!.trim(); if (actualTextContent !== expectedTextContent) { @@ -811,7 +814,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } } - function checkNodeDescendants(node: Element, expectedNode: any[], currentIndex: number) { + function checkNodeDescendants(node: Element, expectedNode: NodeContent, currentIndex: number) { let expectedDescendant = 0; for (let i = currentIndex + 1; i < expectedTree.length; ++i) { @@ -831,7 +834,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkNodeDescendants(node, expected, index); checkNodeContent(node, expected); diff --git a/src/material/tree/tree.spec.ts b/src/material/tree/tree.spec.ts index b85f68b84543..b003c388d03f 100644 --- a/src/material/tree/tree.spec.ts +++ b/src/material/tree/tree.spec.ts @@ -12,6 +12,9 @@ import {BehaviorSubject, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {MatTree, MatTreeModule, MatTreeNestedDataSource} from './index'; +type NodeContent = string[]; +type TreeContent = NodeContent[]; + describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ const _ = {}; @@ -756,11 +759,11 @@ function getNodes(treeElement: Element): HTMLElement[] { function expectFlatTreeToMatch( treeElement: Element, expectedPaddingIndent: number = 28, - ...expectedTree: any[] + ...expectedTree: TreeContent ) { const missedExpectations: string[] = []; - function checkNode(node: Element, expectedNode: any[]) { + function checkNode(node: Element, expectedNode: NodeContent) { const actualTextContent = node.textContent!.trim(); const expectedTextContent = expectedNode[expectedNode.length - 1]; if (actualTextContent !== expectedTextContent) { @@ -770,7 +773,7 @@ function expectFlatTreeToMatch( } } - function checkLevel(node: Element, expectedNode: any[]) { + function checkLevel(node: Element, expectedNode: NodeContent) { const rawLevel = (node as HTMLElement).style.paddingLeft; // Some browsers return 0, while others return 0px. @@ -790,7 +793,7 @@ function expectFlatTreeToMatch( } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkLevel(node, expected); checkNode(node, expected); @@ -801,9 +804,9 @@ function expectFlatTreeToMatch( } } -function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { +function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: TreeContent) { const missedExpectations: string[] = []; - function checkNodeContent(node: Element, expectedNode: any[]) { + function checkNodeContent(node: Element, expectedNode: NodeContent) { const expectedTextContent = expectedNode[expectedNode.length - 1]; const actualTextContent = node.childNodes.item(0).textContent!.trim(); if (actualTextContent !== expectedTextContent) { @@ -813,7 +816,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } } - function checkNodeDescendants(node: Element, expectedNode: any[], currentIndex: number) { + function checkNodeDescendants(node: Element, expectedNode: NodeContent, currentIndex: number) { let expectedDescendant = 0; for (let i = currentIndex + 1; i < expectedTree.length; ++i) { @@ -833,7 +836,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkNodeDescendants(node, expected, index); checkNodeContent(node, expected); From 412d48e50dca472a92ebb59cc7e09e4bbc40ad6d Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 21 Nov 2025 15:10:05 +0100 Subject: [PATCH 2/5] refactor(material/table): Require `T` to be object | ArrayLike --- src/material/table/table-data-source.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/material/table/table-data-source.ts b/src/material/table/table-data-source.ts index dcd9435916c7..1d79fbf6eb83 100644 --- a/src/material/table/table-data-source.ts +++ b/src/material/table/table-data-source.ts @@ -40,7 +40,10 @@ const MAX_SAFE_INTEGER = 9007199254740991; * interactions. If your app needs to support more advanced use cases, consider implementing your * own `DataSource`. */ -export class MatTableDataSource extends DataSource { +export class MatTableDataSource< + T extends object | ArrayLike, + P extends MatPaginator = MatPaginator, +> extends DataSource { /** Stream that emits when a new data array is set on the data source. */ private readonly _data: BehaviorSubject; @@ -229,16 +232,10 @@ export class MatTableDataSource extend * @returns Whether the filter matches against the data */ filterPredicate: (data: T, filter: string) => boolean = (data: T, filter: string): boolean => { - if ((typeof ngDevMode === 'undefined' || ngDevMode) && typeof data !== 'object') { - throw new Error('Default implementation of filterPredicate requires data to be object.'); - } - // Transform the filter by converting it to lowercase and removing whitespace. const transformedFilter = filter.trim().toLowerCase(); // Loops over the values in the array and returns true if any of them match the filter string - return Object.values(data as object).some(value => - `${value}`.toLowerCase().includes(transformedFilter), - ); + return Object.values(data).some(value => `${value}`.toLowerCase().includes(transformedFilter)); }; constructor(initialData: T[] = []) { From ac6dbf701fc770a82d8661e3055f9c8c5cd5ac80 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 21 Nov 2025 15:23:18 +0100 Subject: [PATCH 3/5] refactor(material/tooltip): make message setter to accept `unknown` --- src/material/tooltip/tooltip.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 996ade691b04..b8509d4cff14 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -319,7 +319,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { return this._message; } - set message(value: string | null | undefined) { + set message(value: unknown) { const oldMessage = this._message; // If the message is not a string (e.g. number), convert it to a string and trim it. From ab31b212fcf796a001780c462cc6ddb283d6e203 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 21 Nov 2025 15:28:56 +0100 Subject: [PATCH 4/5] fix(material/tree): Fix tree tests after type changes --- src/material/tree/tree-using-tree-control.spec.ts | 2 +- src/material/tree/tree.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/material/tree/tree-using-tree-control.spec.ts b/src/material/tree/tree-using-tree-control.spec.ts index 1d51cdfe4360..d34676c2bd6e 100644 --- a/src/material/tree/tree-using-tree-control.spec.ts +++ b/src/material/tree/tree-using-tree-control.spec.ts @@ -24,7 +24,7 @@ type TreeContent = NodeContent[]; describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ - const _ = {}; + const _ = ''; let treeElement: HTMLElement; let underlyingDataSource: FakeDataSource; diff --git a/src/material/tree/tree.spec.ts b/src/material/tree/tree.spec.ts index b003c388d03f..129f679aedb1 100644 --- a/src/material/tree/tree.spec.ts +++ b/src/material/tree/tree.spec.ts @@ -17,7 +17,7 @@ type TreeContent = NodeContent[]; describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ - const _ = {}; + const _ = ''; let treeElement: HTMLElement; let underlyingDataSource: FakeDataSource; From ee47476dfcec0e5af80dac6e2dd68abc2768fc74 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 21 Nov 2025 17:16:10 +0100 Subject: [PATCH 5/5] refactor(multiple): approve api changes --- goldens/material/table/index.api.md | 2 +- goldens/material/tooltip/index.api.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/goldens/material/table/index.api.md b/goldens/material/table/index.api.md index a019f4778ae0..de879c853902 100644 --- a/goldens/material/table/index.api.md +++ b/goldens/material/table/index.api.md @@ -174,7 +174,7 @@ export class MatTable extends CdkTable { } // @public -export class MatTableDataSource extends DataSource { +export class MatTableDataSource, P extends MatPaginator = MatPaginator> extends DataSource { constructor(initialData?: T[]); connect(): BehaviorSubject; get data(): T[]; diff --git a/goldens/material/tooltip/index.api.md b/goldens/material/tooltip/index.api.md index fadab81be148..267969deed60 100644 --- a/goldens/material/tooltip/index.api.md +++ b/goldens/material/tooltip/index.api.md @@ -53,7 +53,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { set hideDelay(value: NumberInput); _isTooltipVisible(): boolean; get message(): string; - set message(value: string | null | undefined); + set message(value: unknown); // (undocumented) ngAfterViewInit(): void; ngOnDestroy(): void;