@@ -29,8 +29,8 @@ class TestMultiActionChip extends MultiActionChip {
29
29
30
30
protected primaryId = 'primary' ;
31
31
32
- protected override renderPrimaryAction ( ) {
33
- return html `< button id ="primary "> </ button > ` ;
32
+ protected override renderPrimaryAction ( content : unknown ) {
33
+ return html `< button id ="primary "> ${ content } </ button > ` ;
34
34
}
35
35
36
36
protected override renderTrailingAction ( focusListener : EventListener ) {
@@ -49,10 +49,18 @@ class TestMultiActionChip extends MultiActionChip {
49
49
describe ( 'Multi-action chips' , ( ) => {
50
50
const env = new Environment ( ) ;
51
51
52
- async function setupTest ( ) {
53
- const chip = new TestMultiActionChip ( ) ;
54
- env . render ( html `${ chip } ` ) ;
52
+ async function setupTest (
53
+ template = html `< test-multi-action-chip > </ test-multi-action-chip > ` ,
54
+ ) : Promise < TestMultiActionChip > {
55
+ const root = env . render ( template ) ;
55
56
await env . waitForStability ( ) ;
57
+ const chip = root . querySelector < TestMultiActionChip > (
58
+ 'test-multi-action-chip' ,
59
+ ) ;
60
+ if ( ! chip ) {
61
+ throw new Error ( 'Failed to query the rendered <test-multi-action-chip>' ) ;
62
+ }
63
+
56
64
return chip ;
57
65
}
58
66
@@ -222,28 +230,132 @@ describe('Multi-action chips', () => {
222
230
} ) ;
223
231
224
232
it ( 'should provide a default "ariaLabelRemove" value' , async ( ) => {
233
+ const label = 'Label' ;
234
+ const chip = await setupTest (
235
+ html `< test-multi-action-chip > ${ label } </ test-multi-action-chip > ` ,
236
+ ) ;
237
+
238
+ expect ( getA11yLabelForChipRemoveButton ( chip ) ) . toEqual ( `Remove ${ label } ` ) ;
239
+ } ) ;
240
+
241
+ it ( 'should provide a default "ariaLabelRemove" when "ariaLabel" is provided' , async ( ) => {
242
+ const label = 'Label' ;
243
+ const chip = await setupTest (
244
+ html `< test-multi-action-chip aria-label =${ 'Descriptive label' } >
245
+ ${ label }
246
+ </ test-multi-action-chip > ` ,
247
+ ) ;
248
+
249
+ expect ( getA11yLabelForChipRemoveButton ( chip ) ) . toEqual (
250
+ `Remove ${ chip . ariaLabel } ` ,
251
+ ) ;
252
+ } ) ;
253
+
254
+ it ( 'should allow setting a custom "ariaLabelRemove"' , async ( ) => {
255
+ const label = 'Label' ;
256
+ const customAriaLabelRemove = 'Remove custom label' ;
257
+ const chip = await setupTest (
258
+ html `< test-multi-action-chip
259
+ aria-label =${ 'Descriptive label' }
260
+ aria-label-remove =${ customAriaLabelRemove } >
261
+ ${ label }
262
+ </ test-multi-action-chip > ` ,
263
+ ) ;
264
+
265
+ expect ( getA11yLabelForChipRemoveButton ( chip ) ) . toEqual (
266
+ customAriaLabelRemove ,
267
+ ) ;
268
+ } ) ;
269
+
270
+ // TODO(b/350810013): remove test when label property is removed.
271
+ it ( 'should provide a default "ariaLabelRemove" value (using the label property)' , async ( ) => {
225
272
const chip = await setupTest ( ) ;
226
273
chip . label = 'Label' ;
274
+ await env . waitForStability ( ) ;
227
275
228
- expect ( chip . ariaLabelRemove ) . toEqual ( `Remove ${ chip . label } ` ) ;
276
+ expect ( getA11yLabelForChipRemoveButton ( chip ) ) . toEqual (
277
+ `Remove ${ chip . label } ` ,
278
+ ) ;
229
279
} ) ;
230
280
231
- it ( 'should provide a default "ariaLabelRemove" when "ariaLabel" is provided' , async ( ) => {
281
+ // TODO(b/350810013): remove test when label property is removed.
282
+ it ( 'should provide a default "ariaLabelRemove" when "ariaLabel" is provided (using the label property)' , async ( ) => {
232
283
const chip = await setupTest ( ) ;
233
284
chip . label = 'Label' ;
234
285
chip . ariaLabel = 'Descriptive label' ;
286
+ await env . waitForStability ( ) ;
235
287
236
- expect ( chip . ariaLabelRemove ) . toEqual ( `Remove ${ chip . ariaLabel } ` ) ;
288
+ expect ( getA11yLabelForChipRemoveButton ( chip ) ) . toEqual (
289
+ `Remove ${ chip . ariaLabel } ` ,
290
+ ) ;
237
291
} ) ;
238
292
239
- it ( 'should allow setting a custom "ariaLabelRemove"' , async ( ) => {
293
+ // TODO(b/350810013): remove test when label property is removed.
294
+ it ( 'should allow setting a custom "ariaLabelRemove" (using the label property)' , async ( ) => {
240
295
const chip = await setupTest ( ) ;
241
296
chip . label = 'Label' ;
242
297
chip . ariaLabel = 'Descriptive label' ;
243
298
const customAriaLabelRemove = 'Remove custom label' ;
244
299
chip . ariaLabelRemove = customAriaLabelRemove ;
300
+ await env . waitForStability ( ) ;
245
301
246
- expect ( chip . ariaLabelRemove ) . toEqual ( customAriaLabelRemove ) ;
302
+ expect ( getA11yLabelForChipRemoveButton ( chip ) ) . toEqual (
303
+ customAriaLabelRemove ,
304
+ ) ;
247
305
} ) ;
248
306
} ) ;
249
307
} ) ;
308
+
309
+ /**
310
+ * Returns the text content of a slot.
311
+ */
312
+ function getSlotTextContent ( slot : HTMLSlotElement ) {
313
+ // Remove any newlines, comments, and whitespace from the label slot.
314
+ let text = '' ;
315
+ for ( const node of slot . assignedNodes ( ) ?? [ ] ) {
316
+ if ( node . nodeType === Node . TEXT_NODE ) {
317
+ text += node . textContent ?. trim ( ) || '' ;
318
+ }
319
+ }
320
+ return text ;
321
+ }
322
+
323
+ /**
324
+ * Returns the a11y label of the remove button. If the button has an aria-label,
325
+ * it will return that. If it has aria-labelledby, it will return the text
326
+ * content of the elements it is labelled by.
327
+ */
328
+ function getA11yLabelForChipRemoveButton ( chip : TestMultiActionChip ) : string {
329
+ const removeButton = chip . shadowRoot ! . querySelector < HTMLButtonElement > (
330
+ 'button.trailing.action' ,
331
+ ) ! ;
332
+
333
+ if ( removeButton . ariaLabel ) {
334
+ return removeButton . ariaLabel ;
335
+ }
336
+
337
+ // If the remove button is not aria-labelled, it should be aria-labelledby.
338
+ const removeButtonAriaLabelledBy =
339
+ removeButton . getAttribute ( 'aria-labelledby' ) ! ;
340
+ const elementsLabelledBy : HTMLElement [ ] = [ ] ;
341
+ removeButtonAriaLabelledBy . split ( ' ' ) . forEach ( ( id ) => {
342
+ const labelledByElement = chip . shadowRoot ?. getElementById ( id ) ;
343
+ if ( ! labelledByElement ) {
344
+ throw new Error (
345
+ `Cannot find element with ID "#{id}" in the chip's shadow root` ,
346
+ ) ;
347
+ }
348
+ elementsLabelledBy . push ( labelledByElement ) ;
349
+ } ) ;
350
+ const textFromAriaLabelledBy : string [ ] = [ ] ;
351
+ elementsLabelledBy . forEach ( ( element ) => {
352
+ const unnamedSlotChildElement =
353
+ element . querySelector < HTMLSlotElement > ( 'slot:not([name])' ) ;
354
+ if ( unnamedSlotChildElement ) {
355
+ textFromAriaLabelledBy . push ( getSlotTextContent ( unnamedSlotChildElement ) ) ;
356
+ } else {
357
+ textFromAriaLabelledBy . push ( element . textContent ?? '' ) ;
358
+ }
359
+ } ) ;
360
+ return textFromAriaLabelledBy . join ( ' ' ) ;
361
+ }
0 commit comments