Skip to content

Commit 57fb305

Browse files
authored
Merge branch 'angular:main' into chips-overflow
2 parents db23bb4 + 977f46f commit 57fb305

File tree

9 files changed

+70
-51
lines changed

9 files changed

+70
-51
lines changed

goldens/material/button/testing/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface ButtonHarnessFilters extends BaseHarnessFilters {
1717
appearance?: ButtonAppearance;
1818
buttonType?: ButtonType;
1919
disabled?: boolean;
20+
iconName?: string | RegExp;
2021
text?: string | RegExp;
2122
variant?: ButtonVariant;
2223
}

src/cdk-experimental/deferred-content/deferred-content.spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,25 @@ describe('DeferredContent', () => {
3232
component.preserveContent.set(true);
3333
});
3434

35-
it('creates the content when hidden.', async () => {
35+
it('does not create the content until first visible.', async () => {
3636
collapsible.injector.get(Collapsible).contentVisible.set(false);
3737
await fixture.whenStable();
38-
expect(collapsible.nativeElement.innerText).toBe('Lazy Content');
38+
expect(collapsible.nativeElement.innerText).toBe('');
3939
});
4040

4141
it('creates the content when visible.', async () => {
4242
collapsible.injector.get(Collapsible).contentVisible.set(true);
4343
await fixture.whenStable();
4444
expect(collapsible.nativeElement.innerText).toBe('Lazy Content');
4545
});
46+
47+
it('does not remove the content when hidden.', async () => {
48+
collapsible.injector.get(Collapsible).contentVisible.set(true);
49+
await fixture.whenStable();
50+
collapsible.injector.get(Collapsible).contentVisible.set(false);
51+
await fixture.whenStable();
52+
expect(collapsible.nativeElement.innerText).toBe('Lazy Content');
53+
});
4654
});
4755
});
4856

src/cdk-experimental/deferred-content/deferred-content.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
*/
88

99
import {
10+
afterRenderEffect,
1011
Directive,
11-
effect,
1212
inject,
1313
input,
1414
TemplateRef,
@@ -21,7 +21,7 @@ import {
2121
*/
2222
@Directive()
2323
export class DeferredContentAware {
24-
contentVisible = signal(false);
24+
readonly contentVisible = signal(false);
2525
readonly preserveContent = input(false);
2626
}
2727

@@ -48,16 +48,13 @@ export class DeferredContent {
4848
private _isRendered = false;
4949

5050
constructor() {
51-
effect(() => {
52-
if (
53-
this._deferredContentAware.preserveContent() ||
54-
this._deferredContentAware.contentVisible()
55-
) {
51+
afterRenderEffect(() => {
52+
if (this._deferredContentAware.contentVisible()) {
5653
if (this._isRendered) return;
5754
this._viewContainerRef.clear();
5855
this._viewContainerRef.createEmbeddedView(this._templateRef);
5956
this._isRendered = true;
60-
} else {
57+
} else if (!this._deferredContentAware.preserveContent()) {
6158
this._viewContainerRef.clear();
6259
this._isRendered = false;
6360
}

src/cdk-experimental/tree/tree.spec.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ describe('CdkTree', () => {
119119
if (config.label !== undefined) node.label = config.label;
120120
if (config.children !== undefined) node.children = config.children;
121121
if (config.disabled !== undefined) node.disabled = config.disabled;
122-
if (config.preserveContent !== undefined) node.preserveContent = config.preserveContent;
123122
updateTree({nodes: newNodes});
124123
return;
125124
}
@@ -151,6 +150,16 @@ describe('CdkTree', () => {
151150
return item?.getAttribute('data-value') ?? undefined;
152151
}
153152

153+
function expandAll() {
154+
const fruitsEl = getTreeItemElementByValue('fruits')!;
155+
click(fruitsEl);
156+
const berriesEl = getTreeItemElementByValue('berries')!;
157+
click(berriesEl);
158+
const vegetablesEl = getTreeItemElementByValue('vegetables')!;
159+
click(vegetablesEl);
160+
updateTree({value: []});
161+
}
162+
154163
afterEach(async () => {
155164
fixture.detectChanges();
156165
await runAccessibilityChecks(fixture.nativeElement);
@@ -160,17 +169,15 @@ describe('CdkTree', () => {
160169
describe('default configuration', () => {
161170
beforeEach(() => {
162171
setupTestTree();
163-
// Preserve collapsed children nodes for checking attributes.
164-
updateTreeItemByValue('fruits', {preserveContent: true});
165-
updateTreeItemByValue('berries', {preserveContent: true});
166-
updateTreeItemByValue('vegetables', {preserveContent: true});
167172
});
168173

169174
it('should correctly set the role attribute to "tree" for CdkTree', () => {
170175
expect(treeElement.getAttribute('role')).toBe('tree');
171176
});
172177

173178
it('should correctly set the role attribute to "treeitem" for CdkTreeItem', () => {
179+
expandAll();
180+
174181
expect(getTreeItemElementByValue('fruits')!.getAttribute('role')).toBe('treeitem');
175182
expect(getTreeItemElementByValue('vegetables')!.getAttribute('role')).toBe('treeitem');
176183
expect(getTreeItemElementByValue('grains')!.getAttribute('role')).toBe('treeitem');
@@ -183,6 +190,8 @@ describe('CdkTree', () => {
183190
});
184191

185192
it('should correctly set the role attribute to "group" for CdkTreeItemGroup', () => {
193+
expandAll();
194+
186195
expect(getTreeItemGroupElementByValue('fruits')!.getAttribute('role')).toBe('group');
187196
expect(getTreeItemGroupElementByValue('vegetables')!.getAttribute('role')).toBe('group');
188197
expect(getTreeItemGroupElementByValue('berries')!.getAttribute('role')).toBe('group');
@@ -215,17 +224,19 @@ describe('CdkTree', () => {
215224
});
216225

217226
it('should set aria-level, aria-setsize, and aria-posinset correctly', () => {
227+
expandAll();
228+
218229
const fruits = getTreeItemElementByValue('fruits')!;
219230
expect(fruits.getAttribute('aria-level')).toBe('1');
220231
expect(fruits.getAttribute('aria-setsize')).toBe('4');
221232
expect(fruits.getAttribute('aria-posinset')).toBe('1');
222-
expect(fruits.getAttribute('aria-expanded')).toBe('false');
233+
expect(fruits.getAttribute('aria-expanded')).toBe('true');
223234

224235
const vegetables = getTreeItemElementByValue('vegetables')!;
225236
expect(vegetables.getAttribute('aria-level')).toBe('1');
226237
expect(vegetables.getAttribute('aria-setsize')).toBe('4');
227238
expect(vegetables.getAttribute('aria-posinset')).toBe('2');
228-
expect(vegetables.getAttribute('aria-expanded')).toBe('false');
239+
expect(vegetables.getAttribute('aria-expanded')).toBe('true');
229240

230241
const grains = getTreeItemElementByValue('grains')!;
231242
expect(grains.getAttribute('aria-level')).toBe('1');
@@ -246,7 +257,7 @@ describe('CdkTree', () => {
246257
expect(berries.getAttribute('aria-level')).toBe('2');
247258
expect(berries.getAttribute('aria-setsize')).toBe('3');
248259
expect(berries.getAttribute('aria-posinset')).toBe('3');
249-
expect(berries.getAttribute('aria-expanded')).toBe('false');
260+
expect(berries.getAttribute('aria-expanded')).toBe('true');
250261

251262
const strawberry = getTreeItemElementByValue('strawberry')!;
252263
expect(strawberry.getAttribute('aria-level')).toBe('3');
@@ -264,10 +275,6 @@ describe('CdkTree', () => {
264275
describe('custom configuration', () => {
265276
beforeEach(() => {
266277
setupTestTree();
267-
// Preserve collapsed children nodes for checking attributes.
268-
updateTreeItemByValue('fruits', {preserveContent: true});
269-
updateTreeItemByValue('berries', {preserveContent: true});
270-
updateTreeItemByValue('vegetables', {preserveContent: true});
271278
});
272279

273280
it('should set aria-orientation to "horizontal"', () => {
@@ -296,6 +303,7 @@ describe('CdkTree', () => {
296303
});
297304

298305
it('should set aria-selected to "true" for selected items', () => {
306+
expandAll();
299307
updateTree({value: ['apple']});
300308

301309
const appleItem = getTreeItemElementByValue('apple')!;
@@ -306,11 +314,13 @@ describe('CdkTree', () => {
306314

307315
it('should set aria-expanded to "true" for expanded items', () => {
308316
right();
317+
309318
const fruitsItem = getTreeItemElementByValue('fruits')!;
310319
expect(fruitsItem.getAttribute('aria-expanded')).toBe('true');
311320
});
312321

313322
it('should set aria-current to specific current type when nav="true"', () => {
323+
expandAll();
314324
updateTree({nav: true, value: ['apple']});
315325

316326
const appleItem = getTreeItemElementByValue('apple')!;
@@ -323,6 +333,8 @@ describe('CdkTree', () => {
323333
});
324334

325335
it('should not set aria-selected when nav="true"', () => {
336+
expandAll();
337+
326338
updateTree({value: ['apple'], nav: true});
327339
const appleItem = getTreeItemElementByValue('apple')!;
328340
expect(appleItem.hasAttribute('aria-selected')).toBe(false);
@@ -402,10 +414,7 @@ describe('CdkTree', () => {
402414
});
403415

404416
it('should set tabindex="-1" for all items', () => {
405-
// Preserve collapsed children nodes for checking attributes.
406-
updateTreeItemByValue('fruits', {preserveContent: true});
407-
updateTreeItemByValue('berries', {preserveContent: true});
408-
updateTreeItemByValue('vegetables', {preserveContent: true});
417+
expandAll();
409418

410419
expect(getTreeItemElementByValue('fruits')!.getAttribute('tabindex')).toBe('-1');
411420
expect(getTreeItemElementByValue('apple')!.getAttribute('tabindex')).toBe('-1');
@@ -425,10 +434,7 @@ describe('CdkTree', () => {
425434
describe('value and selection', () => {
426435
it('should select items based on the initial value input', () => {
427436
setupTestTree();
428-
// Preserve collapsed children nodes for checking attributes.
429-
updateTreeItemByValue('fruits', {preserveContent: true});
430-
updateTreeItemByValue('berries', {preserveContent: true});
431-
updateTreeItemByValue('vegetables', {preserveContent: true});
437+
expandAll();
432438
updateTree({value: ['apple', 'strawberry', 'carrot']});
433439

434440
expect(getTreeItemElementByValue('apple')!.getAttribute('aria-selected')).toBe('true');
@@ -1320,7 +1326,6 @@ interface TestTreeNode<V = string> {
13201326
label: string;
13211327
disabled?: boolean;
13221328
children?: TestTreeNode<V>[];
1323-
preserveContent?: boolean;
13241329
}
13251330

13261331
@Component({
@@ -1359,7 +1364,6 @@ interface TestTreeNode<V = string> {
13591364
<ul
13601365
cdkTreeItemGroup
13611366
[ownedBy]="treeItem"
1362-
[preserveContent]="!!node.preserveContent"
13631367
[attr.data-group-for]="node.value"
13641368
#group="cdkTreeItemGroup">
13651369
<ng-template cdkTreeItemGroupContent>

src/material/button/testing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ts_project(
1111
deps = [
1212
"//:node_modules/@angular/core",
1313
"//src/cdk/testing",
14+
"//src/material/icon/testing",
1415
],
1516
)
1617

src/material/button/testing/button-harness-filters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ export interface ButtonHarnessFilters extends BaseHarnessFilters {
3333

3434
/** Only find instances with the specified type. */
3535
buttonType?: ButtonType;
36+
37+
/** Only find instances that contain an icon whose name matches the given value. */
38+
iconName?: string | RegExp;
3639
}

src/material/button/testing/button-harness.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ describe('MatButtonHarness', () => {
115115
expect(await favIcon.getName()).toBe('favorite');
116116
});
117117

118+
it('should be able to filter buttons containing a named icon', async () => {
119+
const favBtn = await loader.getHarness(MatButtonHarness.with({iconName: 'favorite'}));
120+
121+
expect(await (await favBtn.host()).getAttribute('id')).toBe('favorite-icon');
122+
expect(await (await favBtn.getHarness(MatIconHarness)).getName()).toBe('favorite');
123+
});
124+
118125
it('should be able to ge the type variant of the button', async () => {
119126
const buttons = await loader.getAllHarnesses(MatButtonHarness);
120127
const variants = await parallel(() => buttons.map(button => button.getVariant()));

src/material/button/testing/button-harness.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ContentContainerComponentHarness,
1313
HarnessPredicate,
1414
} from '@angular/cdk/testing';
15+
import {MatIconHarness} from '@angular/material/icon/testing';
1516
import {
1617
ButtonAppearance,
1718
ButtonHarnessFilters,
@@ -58,7 +59,10 @@ export class MatButtonHarness extends ContentContainerComponentHarness {
5859
})
5960
.addOption('buttonType', options.buttonType, (harness, buttonType) =>
6061
HarnessPredicate.stringMatches(harness.getType(), buttonType),
61-
);
62+
)
63+
.addOption('iconName', options.iconName, (harness, iconName) => {
64+
return harness.hasHarness(MatIconHarness.with({name: iconName}));
65+
});
6266
}
6367

6468
/**

src/material/form-field/form-field.html

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,28 @@
9595
}
9696
</div>
9797

98-
<div
99-
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
100-
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
98+
<div aria-atomic="true" aria-live="polite"
99+
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
100+
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
101101
>
102102
@let subscriptMessageType = _getSubscriptMessageType();
103103

104-
<!--
105-
Use a single permanent wrapper for both hints and errors so aria-live works correctly,
106-
as having it appear post render will not consistently work. We also do not want to add
107-
additional divs as it causes styling regressions.
108-
-->
109-
<div aria-atomic="true" aria-live="polite"
110-
[class.mat-mdc-form-field-error-wrapper]="subscriptMessageType === 'error'"
111-
[class.mat-mdc-form-field-hint-wrapper]="subscriptMessageType === 'hint'"
112-
>
113-
@switch (subscriptMessageType) {
114-
@case ('error') {
104+
@switch (subscriptMessageType) {
105+
@case ('error') {
106+
<div class="mat-mdc-form-field-error-wrapper">
115107
<ng-content select="mat-error, [matError]"></ng-content>
116-
}
108+
</div>
109+
}
117110

118-
@case ('hint') {
111+
@case ('hint') {
112+
<div class="mat-mdc-form-field-hint-wrapper">
119113
@if (hintLabel) {
120114
<mat-hint [id]="_hintLabelId">{{hintLabel}}</mat-hint>
121115
}
122116
<ng-content select="mat-hint:not([align='end'])"></ng-content>
123117
<div class="mat-mdc-form-field-hint-spacer"></div>
124118
<ng-content select="mat-hint[align='end']"></ng-content>
125-
}
119+
</div>
126120
}
127-
</div>
121+
}
128122
</div>

0 commit comments

Comments
 (0)