@kaizen/components 0.0.0-canary-04-titleblock-logic-20251211225600 → 0.0.0-canary-guidance-block-codemod-20251212045145

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/codemods/migrateGuidanceBlockActionsToActionsSlot/migrateGuidanceBlockActionsToActionsSlot.spec.ts +209 -26
  2. package/codemods/migrateGuidanceBlockActionsToActionsSlot/migrateGuidanceBlockActionsToActionsSlot.ts +24 -1
  3. package/codemods/migrateGuidanceBlockActionsToActionsSlot/transformActionsToActionsSlot.spec.ts +2 -1
  4. package/codemods/migrateGuidanceBlockActionsToActionsSlot/transformActionsToActionsSlot.ts +20 -0
  5. package/codemods/runV1Codemods/__snapshots__/runV1Codemods.spec.ts.snap +2 -3
  6. package/codemods/runV1Codemods/runV1Codemods.spec.ts +2 -3
  7. package/codemods/utils/transformV1ButtonPropsToButtonOrLinkButton.ts +40 -0
  8. package/dist/cjs/src/MenuV1/index.cjs +3 -4
  9. package/dist/cjs/src/MenuV1/subcomponents/MenuHeading/MenuHeading.cjs +27 -0
  10. package/dist/cjs/src/MenuV1/subcomponents/MenuHeading/MenuHeading.module.scss.cjs +6 -0
  11. package/dist/cjs/src/TitleBlock/TitleBlock.cjs +36 -32
  12. package/dist/cjs/src/TitleBlock/TitleBlock.module.scss.cjs +1 -5
  13. package/dist/cjs/src/TitleBlock/subcomponents/MainActions.cjs +45 -90
  14. package/dist/cjs/src/TitleBlock/subcomponents/MainActions.module.scss.cjs +1 -3
  15. package/dist/cjs/src/TitleBlock/subcomponents/MobileActions.cjs +306 -0
  16. package/dist/cjs/src/TitleBlock/subcomponents/MobileActions.module.scss.cjs +16 -0
  17. package/dist/cjs/src/TitleBlock/subcomponents/SecondaryActions.cjs +14 -60
  18. package/dist/esm/src/MenuV1/index.mjs +3 -5
  19. package/dist/esm/src/MenuV1/subcomponents/MenuHeading/MenuHeading.mjs +21 -0
  20. package/dist/esm/src/MenuV1/subcomponents/MenuHeading/MenuHeading.module.scss.mjs +4 -0
  21. package/dist/esm/src/TitleBlock/TitleBlock.mjs +37 -33
  22. package/dist/esm/src/TitleBlock/TitleBlock.module.scss.mjs +1 -5
  23. package/dist/esm/src/TitleBlock/subcomponents/MainActions.mjs +47 -92
  24. package/dist/esm/src/TitleBlock/subcomponents/MainActions.module.scss.mjs +1 -3
  25. package/dist/esm/src/TitleBlock/subcomponents/MobileActions.mjs +300 -0
  26. package/dist/esm/src/TitleBlock/subcomponents/MobileActions.module.scss.mjs +14 -0
  27. package/dist/esm/src/TitleBlock/subcomponents/SecondaryActions.mjs +14 -60
  28. package/dist/styles.css +206 -82
  29. package/dist/types/TitleBlock/TitleBlock.d.ts +1 -1
  30. package/dist/types/TitleBlock/subcomponents/MainActions.d.ts +3 -4
  31. package/dist/types/TitleBlock/subcomponents/MobileActions.d.ts +14 -0
  32. package/package.json +1 -1
  33. package/src/TitleBlock/TitleBlock.module.scss +14 -55
  34. package/src/TitleBlock/TitleBlock.spec.tsx +461 -33
  35. package/src/TitleBlock/TitleBlock.tsx +24 -7
  36. package/src/TitleBlock/_docs/TitleBlock.stories.tsx +5 -25
  37. package/src/TitleBlock/_mixins.scss +0 -6
  38. package/src/TitleBlock/subcomponents/MainActions.module.scss +2 -28
  39. package/src/TitleBlock/subcomponents/MainActions.tsx +70 -127
  40. package/src/TitleBlock/subcomponents/MobileActions.module.scss +208 -0
  41. package/src/TitleBlock/subcomponents/MobileActions.spec.tsx +210 -0
  42. package/src/TitleBlock/subcomponents/MobileActions.tsx +472 -0
  43. package/src/TitleBlock/subcomponents/SecondaryActions.tsx +45 -114
  44. package/src/TitleBlock/subcomponents/Toolbar.tsx +0 -1
@@ -35,15 +35,25 @@ describe('<TitleBlock />', () => {
35
35
  }
36
36
 
37
37
  it('renders the primary action button label and href', () => {
38
- const { getByRole } = render(
38
+ const { getByTestId } = render(
39
39
  <TitleBlock title="Test Title" primaryAction={primaryActionAsLink}>
40
40
  Example
41
41
  </TitleBlock>,
42
42
  )
43
- const btn = getByRole('link', { name: primaryActionAsLink.label })
43
+ const btn = getByTestId('title-block-primary-action-button')
44
44
  expect(btn.textContent).toEqual(primaryActionAsLink.label)
45
45
  expect(btn.getAttribute('href')).toEqual(primaryActionAsLink.href)
46
46
  })
47
+
48
+ it('passes the href to the mobile action drawer button', () => {
49
+ const { getByTestId } = render(
50
+ <TitleBlock title="Test Title" primaryAction={primaryActionAsLink}>
51
+ Example
52
+ </TitleBlock>,
53
+ )
54
+ const btn = getByTestId('title-block-mobile-actions-primary-button')
55
+ expect(btn.getAttribute('href')).toEqual(primaryActionAsLink.href)
56
+ })
47
57
  })
48
58
 
49
59
  describe('when the primary action is a button with only an onClick', () => {
@@ -59,12 +69,12 @@ describe('<TitleBlock />', () => {
59
69
  })
60
70
 
61
71
  it('renders the primary action button label and onClick', async () => {
62
- const { getByRole } = render(
72
+ const { getByTestId } = render(
63
73
  <TitleBlock title="Test Title" primaryAction={primaryActionAsButton}>
64
74
  Example
65
75
  </TitleBlock>,
66
76
  )
67
- const btn = getByRole('button', { name: primaryActionAsButton.label })
77
+ const btn = getByTestId('title-block-primary-action-button')
68
78
  expect(btn.textContent).toEqual(primaryActionAsButton.label)
69
79
  await user.click(btn)
70
80
 
@@ -72,6 +82,21 @@ describe('<TitleBlock />', () => {
72
82
  expect(testOnClickFn).toHaveBeenCalled()
73
83
  })
74
84
  })
85
+
86
+ it('creates a mobile actions primary button', async () => {
87
+ const { getByTestId } = render(
88
+ <TitleBlock title="Test Title" primaryAction={primaryActionAsButton}>
89
+ Example
90
+ </TitleBlock>,
91
+ )
92
+
93
+ const btn = getByTestId('title-block-mobile-actions-primary-button')
94
+ expect(btn.textContent).toEqual(primaryActionAsButton.label)
95
+ await user.click(btn)
96
+ await waitFor(() => {
97
+ expect(testOnClickFn).toHaveBeenCalled()
98
+ })
99
+ })
75
100
  })
76
101
 
77
102
  describe('when the primary action is disabled', () => {
@@ -93,12 +118,12 @@ describe('<TitleBlock />', () => {
93
118
  })
94
119
 
95
120
  it('renders a disabled primary action button', async () => {
96
- const { getByRole } = render(
121
+ const { getByTestId } = render(
97
122
  <TitleBlock title="Test Title" primaryAction={primaryActionAsButton}>
98
123
  Example
99
124
  </TitleBlock>,
100
125
  )
101
- const btn = getByRole('button', { name: primaryActionAsButton.label }) as HTMLButtonElement
126
+ const btn = getByTestId('title-block-primary-action-button') as HTMLButtonElement
102
127
  expect(btn.textContent).toEqual(primaryActionAsButton.label)
103
128
  expect(btn.disabled).toBeTruthy()
104
129
  await user.click(btn)
@@ -109,12 +134,40 @@ describe('<TitleBlock />', () => {
109
134
  })
110
135
 
111
136
  it('renders a disabled primary action link button', () => {
112
- const { getByRole } = render(
137
+ const { getByTestId } = render(
113
138
  <TitleBlock title="Test Title" primaryAction={primaryActionAsLink}>
114
139
  Example
115
140
  </TitleBlock>,
116
141
  )
117
- const btn = getByRole('button', { name: primaryActionAsButton.label }) as HTMLButtonElement
142
+ const btn = getByTestId('title-block-primary-action-button') as HTMLButtonElement
143
+ expect(btn.textContent).toEqual(primaryActionAsLink.label)
144
+ expect(btn.getAttribute('href')).not.toEqual(primaryActionAsLink.href)
145
+ })
146
+
147
+ it('creates a mobile actions primary button with disabled styles and no onClick', async () => {
148
+ const { getByTestId } = render(
149
+ <TitleBlock title="Test Title" primaryAction={primaryActionAsButton}>
150
+ Example
151
+ </TitleBlock>,
152
+ )
153
+
154
+ const btn = getByTestId('title-block-mobile-actions-primary-button') as HTMLButtonElement
155
+ expect(btn.textContent).toEqual(primaryActionAsButton.label)
156
+ await user.click(btn)
157
+
158
+ await waitFor(() => {
159
+ expect(testOnClickFn).not.toHaveBeenCalled()
160
+ })
161
+ })
162
+
163
+ it('creates a mobile actions primary button with disabled styles and no href', () => {
164
+ const { getByTestId } = render(
165
+ <TitleBlock title="Test Title" primaryAction={primaryActionAsLink}>
166
+ Example
167
+ </TitleBlock>,
168
+ )
169
+
170
+ const btn = getByTestId('title-block-mobile-actions-primary-button') as HTMLButtonElement
118
171
  expect(btn.textContent).toEqual(primaryActionAsLink.label)
119
172
  expect(btn.getAttribute('href')).not.toEqual(primaryActionAsLink.href)
120
173
  })
@@ -134,12 +187,12 @@ describe('<TitleBlock />', () => {
134
187
  })
135
188
 
136
189
  it('renders the primary action button label, href and onClick', async () => {
137
- const { getByRole } = render(
190
+ const { getByTestId } = render(
138
191
  <TitleBlock title="Test Title" primaryAction={primaryActionAsLinkAndOnClick}>
139
192
  Example
140
193
  </TitleBlock>,
141
194
  )
142
- const btn = getByRole('link', { name: primaryActionAsLinkAndOnClick.label })
195
+ const btn = getByTestId('title-block-primary-action-button')
143
196
  expect(btn.textContent).toEqual(primaryActionAsLinkAndOnClick.label)
144
197
  expect(btn.getAttribute('href')).toEqual(primaryActionAsLinkAndOnClick.href)
145
198
  await user.click(btn)
@@ -148,6 +201,21 @@ describe('<TitleBlock />', () => {
148
201
  expect(testOnClickFn).toHaveBeenCalled()
149
202
  })
150
203
  })
204
+
205
+ it('passes both the href and onClick to the mobile action drawer button', async () => {
206
+ const { getByTestId } = render(
207
+ <TitleBlock title="Test Title" primaryAction={primaryActionAsLinkAndOnClick}>
208
+ Example
209
+ </TitleBlock>,
210
+ )
211
+ const btn = getByTestId('title-block-mobile-actions-primary-button')
212
+ expect(btn.getAttribute('href')).toEqual(primaryActionAsLinkAndOnClick.href)
213
+ await user.click(btn)
214
+
215
+ await waitFor(() => {
216
+ expect(testOnClickFn).toHaveBeenCalled()
217
+ })
218
+ })
151
219
  })
152
220
 
153
221
  describe('when the primary action is a menu', () => {
@@ -166,20 +234,30 @@ describe('<TitleBlock />', () => {
166
234
  }
167
235
 
168
236
  it('renders the primary action menu button with label and menu items', async () => {
169
- const { getByRole, getAllByRole } = render(
237
+ const { getByTestId, getAllByTestId } = render(
170
238
  <TitleBlock title="Test Title" primaryAction={primaryActionAsMenu}>
171
239
  Example
172
240
  </TitleBlock>,
173
241
  )
174
- const btn = getByRole('button', { name: primaryActionAsMenu.label })
242
+ const btn = getByTestId('title-block-primary-action-button')
175
243
  expect(btn).toHaveAccessibleName(primaryActionAsMenu.label)
176
244
  await user.click(btn)
177
245
 
178
246
  await waitFor(() => {
179
- const menuItems = getAllByRole('listitem')
247
+ const menuItems = getAllByTestId(/^main-action-primary-menu-item-/)
180
248
  expect(menuItems.length).toEqual(2)
181
249
  })
182
250
  })
251
+
252
+ it('passes the primary menu items to the mobile actions drawer', () => {
253
+ const { getAllByTestId } = render(
254
+ <TitleBlock title="Test Title" primaryAction={primaryActionAsMenu}>
255
+ Example
256
+ </TitleBlock>,
257
+ )
258
+ const menuItems = getAllByTestId(/^title-block-mobile-actions-primary-link-/)
259
+ expect(menuItems.length).toEqual(2)
260
+ })
183
261
  })
184
262
 
185
263
  describe('when the default action is a button with only an href', () => {
@@ -189,22 +267,45 @@ describe('<TitleBlock />', () => {
189
267
  }
190
268
 
191
269
  it('renders the default action button label and href', () => {
192
- const { getByRole } = render(
270
+ const { getByTestId } = render(
193
271
  <TitleBlock title="Test Title" defaultAction={defaultActionAsLink}>
194
272
  Example
195
273
  </TitleBlock>,
196
274
  )
197
- const btn = getByRole('link', { name: defaultActionAsLink.label })
275
+ const btn = getByTestId('title-block-default-action-button')
198
276
  expect(btn.textContent).toEqual(defaultActionAsLink.label)
199
277
  expect(btn.getAttribute('href')).toEqual(defaultActionAsLink.href)
200
278
  })
279
+
280
+ it('creates a mobile actions default action menu item', () => {
281
+ const { getByTestId } = render(
282
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsLink}>
283
+ Example
284
+ </TitleBlock>,
285
+ )
286
+
287
+ const menuItem = getByTestId('title-block-mobile-actions-default-link')
288
+ expect(menuItem.getAttribute('href')).toEqual(defaultActionAsLink.href)
289
+ expect(menuItem.textContent).toEqual(defaultActionAsLink.label)
290
+ })
291
+
292
+ it('renders the mobile actions menu drawer handle even with no primary action', () => {
293
+ const { getByTestId } = render(
294
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsLink}>
295
+ Example
296
+ </TitleBlock>,
297
+ )
298
+
299
+ expect(getByTestId('title-block-mobile-actions-drawer-handle')).toBeTruthy()
300
+ })
201
301
  })
202
302
 
203
303
  describe('when the default action is a button with only an onClick', () => {
204
304
  const testOnClickFn = vi.fn()
205
305
  const defaultActionAsButton = {
206
- label: 'defaultActionLabel',
207
- onClick: testOnClickFn,
306
+ 'label': 'defaultActionLabel',
307
+ 'onClick': testOnClickFn,
308
+ 'data-testid': 'title-block-mobile-actions-default-action',
208
309
  }
209
310
 
210
311
  beforeEach(() => {
@@ -212,12 +313,12 @@ describe('<TitleBlock />', () => {
212
313
  })
213
314
 
214
315
  it('renders the default action button label and onClick', async () => {
215
- const { getByRole } = render(
316
+ const { getByTestId } = render(
216
317
  <TitleBlock title="Test Title" defaultAction={defaultActionAsButton}>
217
318
  Example
218
319
  </TitleBlock>,
219
320
  )
220
- const btn = getByRole('button', { name: defaultActionAsButton.label })
321
+ const btn = getByTestId('title-block-default-action-button')
221
322
  expect(btn.textContent).toEqual(defaultActionAsButton.label)
222
323
  await user.click(btn)
223
324
 
@@ -225,6 +326,32 @@ describe('<TitleBlock />', () => {
225
326
  expect(testOnClickFn).toHaveBeenCalled()
226
327
  })
227
328
  })
329
+
330
+ it('creates a mobile actions default action menu item', async () => {
331
+ const { getByTestId } = render(
332
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsButton}>
333
+ Example
334
+ </TitleBlock>,
335
+ )
336
+
337
+ const menuItem = getByTestId('title-block-mobile-actions-default-action')
338
+ expect(menuItem.textContent).toEqual(defaultActionAsButton.label)
339
+ await user.click(menuItem)
340
+
341
+ await waitFor(() => {
342
+ expect(testOnClickFn).toHaveBeenCalled()
343
+ })
344
+ })
345
+
346
+ it('renders the mobile actions menu drawer handle even with no primary action', () => {
347
+ const { getByTestId } = render(
348
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsButton}>
349
+ Example
350
+ </TitleBlock>,
351
+ )
352
+
353
+ expect(getByTestId('title-block-mobile-actions-drawer-handle')).toBeTruthy()
354
+ })
228
355
  })
229
356
 
230
357
  describe('when the default action is a button with both an href and an onClick', () => {
@@ -240,12 +367,12 @@ describe('<TitleBlock />', () => {
240
367
  })
241
368
 
242
369
  it('renders the default action button label, href and onClick', async () => {
243
- const { getByRole } = render(
370
+ const { getByTestId } = render(
244
371
  <TitleBlock title="Test Title" defaultAction={defaultActionAsLinkAndOnClick}>
245
372
  Example
246
373
  </TitleBlock>,
247
374
  )
248
- const btn = getByRole('link', { name: defaultActionAsLinkAndOnClick.label })
375
+ const btn = getByTestId('title-block-default-action-button')
249
376
  expect(btn.textContent).toEqual(defaultActionAsLinkAndOnClick.label)
250
377
  expect(btn.getAttribute('href')).toEqual(defaultActionAsLinkAndOnClick.href)
251
378
  await user.click(btn)
@@ -254,6 +381,25 @@ describe('<TitleBlock />', () => {
254
381
  expect(testOnClickFn).toHaveBeenCalled()
255
382
  })
256
383
  })
384
+
385
+ it('creates a single mobile actions default link menu item with both href and onClick', async () => {
386
+ const { getByTestId, queryByTestId } = render(
387
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsLinkAndOnClick}>
388
+ Example
389
+ </TitleBlock>,
390
+ )
391
+
392
+ const menuItem = getByTestId('title-block-mobile-actions-default-link')
393
+ const defaultAction = queryByTestId('title-block-mobile-actions-default-action')
394
+ expect(defaultAction).toBeFalsy()
395
+ expect(menuItem.getAttribute('href')).toEqual(defaultActionAsLinkAndOnClick.href)
396
+ expect(menuItem.textContent).toEqual(defaultActionAsLinkAndOnClick.label)
397
+ await user.click(menuItem)
398
+
399
+ await waitFor(() => {
400
+ expect(testOnClickFn).not.toHaveBeenCalled()
401
+ })
402
+ })
257
403
  })
258
404
 
259
405
  describe('when the default action is disabled', () => {
@@ -274,12 +420,12 @@ describe('<TitleBlock />', () => {
274
420
  })
275
421
 
276
422
  it('renders a disabled default action button', async () => {
277
- const { getByRole } = render(
423
+ const { getByTestId } = render(
278
424
  <TitleBlock title="Test Title" defaultAction={defaultActionAsButton}>
279
425
  Example
280
426
  </TitleBlock>,
281
427
  )
282
- const btn = getByRole('button', { name: defaultActionAsButton.label }) as HTMLButtonElement
428
+ const btn = getByTestId('title-block-default-action-button') as HTMLButtonElement
283
429
  expect(btn.textContent).toEqual(defaultActionAsButton.label)
284
430
  expect(btn.disabled).toBeTruthy()
285
431
  await user.click(btn)
@@ -290,12 +436,40 @@ describe('<TitleBlock />', () => {
290
436
  })
291
437
 
292
438
  it('renders a disabled default action link button', () => {
293
- const { getByRole } = render(
439
+ const { getByTestId } = render(
294
440
  <TitleBlock title="Test Title" defaultAction={defaultActionAsLink}>
295
441
  Example
296
442
  </TitleBlock>,
297
443
  )
298
- const btn = getByRole('button', { name: defaultActionAsLink.label }) as HTMLButtonElement
444
+ const btn = getByTestId('title-block-default-action-button') as HTMLButtonElement
445
+ expect(btn.textContent).toEqual(defaultActionAsLink.label)
446
+ expect(btn.getAttribute('href')).not.toEqual(defaultActionAsLink.href)
447
+ })
448
+
449
+ it('creates a mobile actions default action menu item with disabled styles and no onClick', async () => {
450
+ const { getByTestId } = render(
451
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsButton}>
452
+ Example
453
+ </TitleBlock>,
454
+ )
455
+
456
+ const btn = getByTestId('title-block-mobile-actions-default-action') as HTMLButtonElement
457
+ expect(btn.textContent).toEqual(defaultActionAsButton.label)
458
+ await user.click(btn)
459
+
460
+ await waitFor(() => {
461
+ expect(testOnClickFn).not.toHaveBeenCalled()
462
+ })
463
+ })
464
+
465
+ it('creates a mobile actions default link menu item with disabled styles and no href', () => {
466
+ const { getByTestId } = render(
467
+ <TitleBlock title="Test Title" defaultAction={defaultActionAsLink}>
468
+ Example
469
+ </TitleBlock>,
470
+ )
471
+
472
+ const btn = getByTestId('title-block-mobile-actions-default-link') as HTMLButtonElement
299
473
  expect(btn.textContent).toEqual(defaultActionAsLink.label)
300
474
  expect(btn.getAttribute('href')).not.toEqual(defaultActionAsLink.href)
301
475
  })
@@ -316,14 +490,12 @@ describe('<TitleBlock />', () => {
316
490
  it('renders the secondary action with both the href and onClick', async () => {
317
491
  const mockWarnFn = vi.fn()
318
492
  const spy = vi.spyOn(global.console, 'warn').mockImplementation(mockWarnFn)
319
- const { getByRole } = render(
493
+ const { getByTestId } = render(
320
494
  <TitleBlock title="Test Title" secondaryActions={[secondaryActionWithLinkAndOnClick]}>
321
495
  Example
322
496
  </TitleBlock>,
323
497
  )
324
- const btn = getByRole('link', {
325
- name: secondaryActionWithLinkAndOnClick.label,
326
- })
498
+ const btn = getByTestId('title-block-secondary-actions-button')
327
499
  expect(btn).toBeTruthy()
328
500
  expect(mockWarnFn).toBeCalled()
329
501
  expect(btn.textContent).toEqual(secondaryActionWithLinkAndOnClick.label)
@@ -334,9 +506,28 @@ describe('<TitleBlock />', () => {
334
506
  })
335
507
  spy.mockRestore()
336
508
  })
509
+
510
+ it('renders the action as a single mobile actions drawer item with an onClick', async () => {
511
+ const mockWarnFn = vi.fn()
512
+ const spy = vi.spyOn(global.console, 'warn').mockImplementation(mockWarnFn)
513
+ const { getAllByTestId } = render(
514
+ <TitleBlock title="Test Title" secondaryActions={[secondaryActionWithLinkAndOnClick]}>
515
+ Example
516
+ </TitleBlock>,
517
+ )
518
+ const btn = getAllByTestId('title-block-mobile-actions-secondary-action')
519
+ expect(btn.length).toEqual(1)
520
+ expect(btn[0].getAttribute('href')).not.toEqual(secondaryActionWithLinkAndOnClick.href)
521
+ await user.click(btn[0])
522
+
523
+ await waitFor(() => {
524
+ expect(testOnClickFn).toHaveBeenCalled()
525
+ })
526
+ spy.mockRestore()
527
+ })
337
528
  })
338
529
 
339
- describe('when a custom component is provided for section title', () => {
530
+ describe('when a custom compent is provided for section title', () => {
340
531
  it('renders a custom element in section title', async () => {
341
532
  const expectedText = 'This is a button'
342
533
  const CustomComponent = (props: SectionTitleRenderProps): JSX.Element => (
@@ -361,6 +552,132 @@ describe('<TitleBlock />', () => {
361
552
  })
362
553
  })
363
554
 
555
+ describe('when a secondary action is passed with only an href', () => {
556
+ const secondaryActionWithLink = {
557
+ label: 'secondaryActionLabel',
558
+ href: '#secondaryActionHref',
559
+ }
560
+
561
+ it('renders the action as a single mobile actions drawer item with the correct href', () => {
562
+ const { getAllByTestId } = render(
563
+ <TitleBlock title="Test Title" secondaryActions={[secondaryActionWithLink]}>
564
+ Example
565
+ </TitleBlock>,
566
+ )
567
+ const btn = getAllByTestId('title-block-mobile-actions-secondary-action')
568
+ expect(btn.length).toEqual(1)
569
+ expect(btn[0].getAttribute('href')).toEqual(secondaryActionWithLink.href)
570
+ })
571
+ })
572
+
573
+ describe('when autoHideMobileActionsMenu is true', () => {
574
+ const secondaryActionWithLink = {
575
+ label: 'secondaryActionLabel',
576
+ href: '#secondaryActionHref',
577
+ }
578
+
579
+ it('hides the other actions menu when user clicks a menu item', async () => {
580
+ const { getAllByTestId } = render(
581
+ <TitleBlock
582
+ title="Test Title"
583
+ secondaryActions={[secondaryActionWithLink]}
584
+ autoHideMobileActionsMenu
585
+ >
586
+ Example
587
+ </TitleBlock>,
588
+ )
589
+
590
+ const mobileActionsButton = screen.getByRole('button', {
591
+ name: 'Other actions',
592
+ })
593
+
594
+ expect(mobileActionsButton.getAttribute('aria-expanded')).toEqual('false')
595
+ await user.click(mobileActionsButton)
596
+ await waitFor(() => {
597
+ expect(mobileActionsButton.getAttribute('aria-expanded')).toEqual('true')
598
+ })
599
+
600
+ const btn = getAllByTestId('title-block-mobile-actions-secondary-action')
601
+ expect(btn.length).toEqual(1)
602
+ await user.click(btn[0])
603
+
604
+ await waitFor(() => {
605
+ expect(mobileActionsButton.getAttribute('aria-expanded')).toEqual('false')
606
+ })
607
+ })
608
+ })
609
+
610
+ describe('when a disabled secondary action is passed with only an href', () => {
611
+ const secondaryActionWithLink = {
612
+ label: 'secondaryActionLabel',
613
+ href: '#secondaryActionHref',
614
+ disabled: true,
615
+ }
616
+
617
+ it('renders the action as a single disabled mobile actions drawer item with no href', () => {
618
+ const { getAllByTestId } = render(
619
+ <TitleBlock title="Test Title" secondaryActions={[secondaryActionWithLink]}>
620
+ Example
621
+ </TitleBlock>,
622
+ )
623
+ const btn = getAllByTestId('title-block-mobile-actions-secondary-action')
624
+ expect(btn.length).toEqual(1)
625
+ expect(btn[0].getAttribute('href')).not.toEqual(secondaryActionWithLink.href)
626
+ })
627
+ })
628
+
629
+ describe('when a disabled secondary action is passed with only an onClick', () => {
630
+ const testOnClickFn = vi.fn()
631
+ const secondaryActionWithOnClick = {
632
+ label: 'secondaryActionLabel',
633
+ onClick: testOnClickFn,
634
+ disabled: true,
635
+ }
636
+
637
+ it('renders the action as a single disabled mobile actions drawer item with no onClick', async () => {
638
+ const { getAllByTestId } = render(
639
+ <TitleBlock title="Test Title" secondaryActions={[secondaryActionWithOnClick]}>
640
+ Example
641
+ </TitleBlock>,
642
+ )
643
+ const btn = getAllByTestId('title-block-mobile-actions-secondary-action')
644
+ expect(btn.length).toEqual(1)
645
+ await user.click(btn[0])
646
+
647
+ await waitFor(() => {
648
+ expect(testOnClickFn).not.toHaveBeenCalled()
649
+ })
650
+ })
651
+ })
652
+
653
+ describe('when a disabled secondary overflow menu item is passed with only an onClick for the action', () => {
654
+ const testOnClickFn = vi.fn()
655
+ const secondaryOverflowMenuItemWithOnClick = {
656
+ label: 'secondaryActionOverflowMenuItemLabel',
657
+ action: testOnClickFn,
658
+ disabled: true,
659
+ }
660
+
661
+ it('renders the action as a single disabled mobile actions drawer item with no onClick', async () => {
662
+ const { getAllByTestId } = render(
663
+ <TitleBlock
664
+ title="Test Title"
665
+ secondaryActions={[]}
666
+ secondaryOverflowMenuItems={[secondaryOverflowMenuItemWithOnClick]}
667
+ >
668
+ Example
669
+ </TitleBlock>,
670
+ )
671
+ const btn = getAllByTestId('title-block-mobile-actions-overflow-menu-item')
672
+ expect(btn.length).toEqual(1)
673
+ await user.click(btn[0])
674
+
675
+ await waitFor(() => {
676
+ expect(testOnClickFn).not.toHaveBeenCalled()
677
+ })
678
+ })
679
+ })
680
+
364
681
  describe('automation ID behaviour', () => {
365
682
  describe('when default automation IDs are not provided alongside required conditional renders', () => {
366
683
  it('renders the default automation IDs', () => {
@@ -553,6 +870,55 @@ describe('<TitleBlock />', () => {
553
870
  ).toHaveAttribute('href', '#test-primary')
554
871
  })
555
872
 
873
+ it('will render a custom anchor component in the mobile drawer', () => {
874
+ render(
875
+ <TitleBlock
876
+ title="Test Title"
877
+ primaryAction={{
878
+ label: 'Primary action',
879
+ href: '#test-primary',
880
+ component: MockLinkComponent,
881
+ }}
882
+ >
883
+ Example
884
+ </TitleBlock>,
885
+ )
886
+ const drawer = screen.getByTestId('title-block-mobile-actions-drawer-handle')
887
+ within(drawer).getByRole('link', {
888
+ name: 'Primary action',
889
+ })
890
+ expect(
891
+ within(drawer).getByRole('link', {
892
+ name: 'Primary action',
893
+ }),
894
+ ).toHaveAttribute('href', '#test-primary')
895
+ })
896
+
897
+ it('will render custom button with functional onClick', async () => {
898
+ const testClickFunc = vi.fn()
899
+ render(
900
+ <TitleBlock
901
+ title="Test Title"
902
+ primaryAction={{
903
+ label: 'Primary action',
904
+ onClick: testClickFunc,
905
+ component: MockButtonComponent,
906
+ }}
907
+ >
908
+ Example
909
+ </TitleBlock>,
910
+ )
911
+ const drawer = screen.getByTestId('title-block-mobile-actions-drawer-handle')
912
+ const drawerBtn = within(drawer).getByRole('button', {
913
+ name: 'Primary action',
914
+ })
915
+ await user.click(drawerBtn)
916
+
917
+ await waitFor(() => {
918
+ expect(testClickFunc).toBeCalledTimes(1)
919
+ })
920
+ })
921
+
556
922
  it('will render custom button with children and not label', () => {
557
923
  const testClickFunc = vi.fn()
558
924
  render(
@@ -569,12 +935,12 @@ describe('<TitleBlock />', () => {
569
935
  Example
570
936
  </TitleBlock>,
571
937
  )
572
- const toolbar = screen.getByTestId('title-block-main-actions-toolbar')
573
- within(toolbar).getByRole('button', {
938
+ const drawer = screen.getByTestId('title-block-mobile-actions-drawer-handle')
939
+ within(drawer).getByRole('button', {
574
940
  name: 'This will replace label',
575
941
  })
576
942
  expect(
577
- within(toolbar).queryByRole('button', {
943
+ within(drawer).queryByRole('button', {
578
944
  name: 'Primary action',
579
945
  }),
580
946
  ).toBeFalsy()
@@ -608,6 +974,32 @@ describe('<TitleBlock />', () => {
608
974
  expect(links[0]).toHaveAttribute('href', '#test-secondary')
609
975
  expect(links[1]).toHaveAttribute('href', '#test-secondary-2')
610
976
  })
977
+
978
+ it('will render multiple custom anchor components in the secondary actions mobile Drawer', () => {
979
+ render(
980
+ <TitleBlock
981
+ title="Test Title"
982
+ secondaryActions={[
983
+ {
984
+ label: 'Secondary action 1',
985
+ href: '#test-secondary',
986
+ component: MockLinkComponent,
987
+ },
988
+ {
989
+ label: 'Secondary action 2',
990
+ href: '#test-secondary-2',
991
+ component: MockLinkComponent,
992
+ },
993
+ ]}
994
+ >
995
+ Example
996
+ </TitleBlock>,
997
+ )
998
+ const links = screen.getAllByTestId('title-block-mobile-actions-secondary-action')
999
+ expect(links.length).toBe(2)
1000
+ expect(links[0]).toHaveAttribute('href', '#test-secondary')
1001
+ expect(links[1]).toHaveAttribute('href', '#test-secondary-2')
1002
+ })
611
1003
  })
612
1004
 
613
1005
  describe('defaultAction', () => {
@@ -630,6 +1022,42 @@ describe('<TitleBlock />', () => {
630
1022
  })
631
1023
  expect(defaultActionAnchor).toHaveAttribute('href', '#test-default')
632
1024
  })
1025
+
1026
+ it('will render the component above primary action in the Drawer content if it is a link', () => {
1027
+ render(
1028
+ <TitleBlock
1029
+ title="Test Title"
1030
+ defaultAction={{
1031
+ label: 'Default action',
1032
+ href: '#test-default',
1033
+ component: MockLinkComponent,
1034
+ }}
1035
+ >
1036
+ Example
1037
+ </TitleBlock>,
1038
+ )
1039
+ const mobileActionLink = screen.getByTestId('title-block-mobile-actions-default-link')
1040
+
1041
+ expect(mobileActionLink).toBeInTheDocument()
1042
+ })
1043
+
1044
+ it('will render the component in the top list of the Drawer content if it is a clickable button', () => {
1045
+ const testClickFunc = vi.fn()
1046
+ render(
1047
+ <TitleBlock
1048
+ title="Test Title"
1049
+ defaultAction={{
1050
+ label: 'Default action',
1051
+ onClick: testClickFunc,
1052
+ component: MockButtonComponent,
1053
+ }}
1054
+ >
1055
+ Example
1056
+ </TitleBlock>,
1057
+ )
1058
+ expect(screen.queryByTestId('title-block-mobile-actions-default-link')).toBeFalsy()
1059
+ expect(screen.getByTestId('title-block-mobile-actions-default-action')).toBeInTheDocument()
1060
+ })
633
1061
  })
634
1062
  })
635
1063
  })