Skip to content

Commit 8a1373e

Browse files
[LG-5566] tests(Wizard) Implement TestUtils & LGIDs for Wizard (#3338)
* rm step wrapper * rm descendants dep * export WizardProvider * delete-wizard-demo private endpoints useFetchRequiredActionTableData renam ReqAct cards composable basic table stream processing card federated db card applications card clusters card wizard step context Delete requiredActionsConfig.tsx re-enable wizard add useRequiredActionAcknowledgements mv required action. add skeleton Update ModelApiKeysCard.tsx * Update pnpm Update package.json * fix wizard changes * Adds `requiresAcknowledgement` prop to Wizard.Step * Implements `isAcknowledged` state inside provider * Update Wizard.stories.tsx * rm delete demo * Update wizard.md * rm temp changesets * Update README.md * Update WizardStep.spec.tsx * footer tests * Update Wizard.spec.tsx * update package json * update provider props * revert toast changes? * Update .npmrc * Update pnpm-lock.yaml * Update WizardStep.spec.tsx * exports form footer types * Update WizardFooter.types.ts * adds `totalSteps` to wizard context * fix bad merge * adds LGIDs * adds test utils * lint * fix bad merge * removes Step test utils * add layout comments * form-footer lgids * updates wizard testids * updates readme * updates tsdoc * fixes tests * fixes ack reset test * Squashed commit of the following: commit 4fd3668 Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 13:18:59 2025 -0500 fixes ack reset test commit 4f024b1 Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 13:11:35 2025 -0500 fixes tests commit f919ecc Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 13:11:29 2025 -0500 updates tsdoc commit 6842bbb Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 13:02:43 2025 -0500 updates readme * Update WizardStep.spec.tsx * Update WizardContext.tsx * Update WizardStep.spec.tsx * Squashed commit of the following: commit 982ef72 Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 13:52:15 2025 -0500 Update WizardStep.spec.tsx * fixes stories * Squashed commit of the following: commit 4b32ed6 Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 17:49:16 2025 -0500 fixes stories commit 982ef72 Author: Adam Michael Thompson <adam.thompson@mongodb.com> Date: Tue Nov 25 13:52:15 2025 -0500 Update WizardStep.spec.tsx * Update WizardStep.stories.tsx * Update packages/wizard/src/testing/getTestUtils.tsx Co-authored-by: Shaneeza <shaneeza.ali@mongodb.com> * Update README.md * use Button test utils * use test utils * Update pnpm-lock.yaml --------- Co-authored-by: Shaneeza <shaneeza.ali@mongodb.com>
1 parent 4fab471 commit 8a1373e

File tree

18 files changed

+608
-82
lines changed

18 files changed

+608
-82
lines changed

.changeset/form-footer-lgids.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@leafygreen-ui/form-footer': patch
3+
---
4+
5+
Passes `data-lgid` to the root `footer` element

packages/form-footer/src/FormFooter.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default function FormFooter({
4747
<LeafyGreenProvider darkMode={darkMode}>
4848
<footer
4949
data-testid={lgIds.root}
50+
data-lgid={lgIds.root}
5051
className={getFormFooterStyles({ theme, className })}
5152
{...rest}
5253
>

packages/wizard/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,51 @@ const MyWizardStepContents = () => {
108108
### Wizard.Footer
109109

110110
The `Wizard.Footer` is a convenience wrapper around the `FormFooter` component. Each step should render its own Footer component
111+
112+
# Test Harnesses
113+
114+
## getTestUtils()
115+
116+
`getTestUtils()` is a util that allows consumers to reliably interact with LG `Wizard` in a product test suite. If the `Wizard` component cannot be found, an error will be thrown.
117+
118+
### Usage
119+
120+
```tsx
121+
import { getTestUtils } from '@leafygreen-ui/wizard';
122+
123+
const utils = getTestUtils(lgId?: `lg-${string}`); // lgId refers to the custom `data-lgid` attribute passed to `Wizard`. It defaults to 'lg-wizard' if left empty.
124+
```
125+
126+
### Test Utils
127+
128+
```tsx
129+
const {
130+
getFooter,
131+
queryFooter,
132+
findFooter,
133+
getPrimaryButton,
134+
queryPrimaryButton,
135+
findPrimaryButton,
136+
getBackButton,
137+
queryBackButton,
138+
findBackButton,
139+
getCancelButton,
140+
queryCancelButton,
141+
findCancelButton,
142+
} = getTestUtils();
143+
```
144+
145+
| Util | Description | Returns |
146+
| ---------------------- | ------------------------------------------------------------- | ----------------------------- |
147+
| `getFooter()` | Returns the footer element | `HTMLElement` |
148+
| `queryFooter()` | Returns the footer element or null if not found | `HTMLElement` \| `null` |
149+
| `findFooter()` | Returns a promise that resolves to the footer element | `Promise<HTMLElement>` |
150+
| `getPrimaryButton()` | Returns the primary button element | `HTMLButtonElement` |
151+
| `queryPrimaryButton()` | Returns the primary button element or null if not found | `HTMLButtonElement` \| `null` |
152+
| `findPrimaryButton()` | Returns a promise that resolves to the primary button element | `Promise<HTMLButtonElement>` |
153+
| `getBackButton()` | Returns the back button element | `HTMLButtonElement` |
154+
| `queryBackButton()` | Returns the back button element or null if not found | `HTMLButtonElement` \| `null` |
155+
| `findBackButton()` | Returns a promise that resolves to the back button element | `Promise<HTMLButtonElement>` |
156+
| `getCancelButton()` | Returns the cancel button element | `HTMLButtonElement` |
157+
| `queryCancelButton()` | Returns the cancel button element or null if not found | `HTMLButtonElement` \| `null` |
158+
| `findCancelButton()` | Returns a promise that resolves to the cancel button element | `Promise<HTMLButtonElement>` |

packages/wizard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"access": "public"
2828
},
2929
"dependencies": {
30+
"@leafygreen-ui/button": "workspace:^",
3031
"@leafygreen-ui/compound-component": "workspace:^",
3132
"@leafygreen-ui/emotion": "workspace:^",
3233
"@leafygreen-ui/form-footer": "workspace:^",
@@ -38,7 +39,6 @@
3839
},
3940
"devDependencies": {
4041
"@faker-js/faker": "^8.0.0",
41-
"@leafygreen-ui/button": "workspace:^",
4242
"@leafygreen-ui/badge": "workspace:^",
4343
"@leafygreen-ui/banner": "workspace:^",
4444
"@leafygreen-ui/card": "workspace:^",

packages/wizard/src/Wizard/Wizard.spec.tsx

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { render } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44

5+
import { getTestUtils } from '../testing';
56
import { useWizardStepContext } from '../WizardStep';
67

78
import { Wizard } from '.';
@@ -69,7 +70,7 @@ describe('packages/wizard', () => {
6970
});
7071

7172
test('does not render back button on first step', () => {
72-
const { queryByRole, getByRole } = render(
73+
render(
7374
<Wizard activeStep={0}>
7475
<Wizard.Step>
7576
<div data-testid="step-1-content">Content 1</div>
@@ -88,13 +89,15 @@ describe('packages/wizard', () => {
8889
</Wizard>,
8990
);
9091

92+
const { queryBackButton, getPrimaryButton } = getTestUtils();
93+
9194
// Back button should not be rendered on first step
92-
expect(queryByRole('button', { name: 'Back' })).not.toBeInTheDocument();
93-
expect(getByRole('button', { name: 'Next' })).toBeInTheDocument();
95+
expect(queryBackButton()).not.toBeInTheDocument();
96+
expect(getPrimaryButton()).toBeInTheDocument();
9497
});
9598

9699
test('renders back button on second step', () => {
97-
const { getByRole } = render(
100+
render(
98101
<Wizard activeStep={1}>
99102
<Wizard.Step>
100103
<div data-testid="step-1-content">Content 1</div>
@@ -113,16 +116,18 @@ describe('packages/wizard', () => {
113116
</Wizard>,
114117
);
115118

116-
expect(getByRole('button', { name: 'Back' })).toBeInTheDocument();
117-
expect(getByRole('button', { name: 'Next' })).toBeInTheDocument();
119+
const { getBackButton, getPrimaryButton } = getTestUtils();
120+
121+
expect(getBackButton()).toBeInTheDocument();
122+
expect(getPrimaryButton()).toBeInTheDocument();
118123
});
119124
});
120125

121126
describe('interaction', () => {
122127
test('calls `onStepChange` when incrementing step', async () => {
123128
const onStepChange = jest.fn();
124129

125-
const { getByRole } = render(
130+
render(
126131
<Wizard activeStep={0} onStepChange={onStepChange}>
127132
<Wizard.Step>
128133
<div data-testid="step-1-content">Content 1</div>
@@ -135,15 +140,16 @@ describe('packages/wizard', () => {
135140
</Wizard>,
136141
);
137142

138-
await userEvent.click(getByRole('button', { name: 'Next' }));
143+
const { getPrimaryButton } = getTestUtils();
144+
userEvent.click(getPrimaryButton());
139145

140146
expect(onStepChange).toHaveBeenCalledWith(1);
141147
});
142148

143149
test('calls `onStepChange` when decrementing step', async () => {
144150
const onStepChange = jest.fn();
145151

146-
const { getByRole } = render(
152+
render(
147153
<Wizard activeStep={1} onStepChange={onStepChange}>
148154
<Wizard.Step>
149155
<div data-testid="step-1-content">Content 1</div>
@@ -162,7 +168,8 @@ describe('packages/wizard', () => {
162168
</Wizard>,
163169
);
164170

165-
await userEvent.click(getByRole('button', { name: 'Back' }));
171+
const { getBackButton } = getTestUtils();
172+
userEvent.click(getBackButton());
166173

167174
expect(onStepChange).toHaveBeenCalledWith(0);
168175
});
@@ -173,7 +180,7 @@ describe('packages/wizard', () => {
173180
const onPrimaryClick = jest.fn();
174181
const onCancelClick = jest.fn();
175182

176-
const { getByRole } = render(
183+
render(
177184
<Wizard activeStep={1} onStepChange={onStepChange}>
178185
<Wizard.Step>
179186
<div data-testid="step-1-content">Content 1</div>
@@ -194,21 +201,24 @@ describe('packages/wizard', () => {
194201
</Wizard>,
195202
);
196203

197-
await userEvent.click(getByRole('button', { name: 'Back' }));
204+
const { getBackButton, getPrimaryButton, getCancelButton } =
205+
getTestUtils();
206+
207+
userEvent.click(getBackButton());
198208
expect(onBackClick).toHaveBeenCalled();
199209
expect(onStepChange).toHaveBeenCalledWith(0);
200210

201-
await userEvent.click(getByRole('button', { name: 'Next' }));
211+
userEvent.click(getPrimaryButton());
202212
expect(onPrimaryClick).toHaveBeenCalled();
203213
expect(onStepChange).toHaveBeenCalledWith(1);
204214

205-
await userEvent.click(getByRole('button', { name: 'Cancel' }));
215+
userEvent.click(getCancelButton());
206216
expect(onCancelClick).toHaveBeenCalled();
207217
});
208218

209219
describe('uncontrolled', () => {
210220
test('does not increment step beyond Steps count', async () => {
211-
const { getByTestId, queryByTestId, getByRole } = render(
221+
const { getByTestId, queryByTestId } = render(
212222
<Wizard>
213223
<Wizard.Step>
214224
<div data-testid="step-1-content">Content 1</div>
@@ -221,16 +231,18 @@ describe('packages/wizard', () => {
221231
</Wizard>,
222232
);
223233

234+
const { getPrimaryButton } = getTestUtils();
235+
224236
// Start at step 1
225237
expect(getByTestId('step-1-content')).toBeInTheDocument();
226238

227239
// Click next to go to step 2
228-
await userEvent.click(getByRole('button', { name: 'Next' }));
240+
userEvent.click(getPrimaryButton());
229241
expect(getByTestId('step-2-content')).toBeInTheDocument();
230242
expect(queryByTestId('step-1-content')).not.toBeInTheDocument();
231243

232244
// Click next again - should stay at step 2 (last step)
233-
await userEvent.click(getByRole('button', { name: 'Next' }));
245+
userEvent.click(getPrimaryButton());
234246
expect(getByTestId('step-2-content')).toBeInTheDocument();
235247
expect(queryByTestId('step-1-content')).not.toBeInTheDocument();
236248
});
@@ -240,7 +252,7 @@ describe('packages/wizard', () => {
240252
test('does not change steps internally when controlled', async () => {
241253
const onStepChange = jest.fn();
242254

243-
const { getByTestId, queryByTestId, getByRole } = render(
255+
const { getByTestId, queryByTestId } = render(
244256
<Wizard activeStep={0} onStepChange={onStepChange}>
245257
<Wizard.Step>
246258
<div data-testid="step-1-content">Content 1</div>
@@ -253,11 +265,13 @@ describe('packages/wizard', () => {
253265
</Wizard>,
254266
);
255267

268+
const { getPrimaryButton } = getTestUtils();
269+
256270
// Should start at step 1
257271
expect(getByTestId('step-1-content')).toBeInTheDocument();
258272

259273
// Click next
260-
await userEvent.click(getByRole('button', { name: 'Next' }));
274+
userEvent.click(getPrimaryButton());
261275

262276
// Should still be at step 1 since it's controlled
263277
expect(getByTestId('step-1-content')).toBeInTheDocument();
@@ -318,7 +332,7 @@ describe('packages/wizard', () => {
318332

319333
describe('requiresAcknowledgement', () => {
320334
test('disables primary button when requiresAcknowledgement is true and not acknowledged', () => {
321-
const { getByRole } = render(
335+
render(
322336
<Wizard>
323337
<Wizard.Step requiresAcknowledgement>
324338
<div data-testid="step-1-content">Content 1</div>
@@ -327,8 +341,8 @@ describe('packages/wizard', () => {
327341
</Wizard>,
328342
);
329343

330-
const primaryButton = getByRole('button', { name: 'Next' });
331-
expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
344+
const { isPrimaryButtonDisabled } = getTestUtils();
345+
expect(isPrimaryButtonDisabled()).toBe(true);
332346
});
333347

334348
test('enables primary button when requiresAcknowledgement is true and acknowledged', async () => {
@@ -349,18 +363,18 @@ describe('packages/wizard', () => {
349363
</Wizard>,
350364
);
351365

352-
const primaryButton = getByRole('button', { name: 'Next' });
353-
expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
366+
const { isPrimaryButtonDisabled } = getTestUtils();
367+
expect(isPrimaryButtonDisabled()).toBe(true);
354368

355369
// Acknowledge the step
356370
const acknowledgeButton = getByRole('button', { name: 'Acknowledge' });
357-
await userEvent.click(acknowledgeButton);
371+
userEvent.click(acknowledgeButton);
358372

359-
expect(primaryButton).toHaveAttribute('aria-disabled', 'false');
373+
expect(isPrimaryButtonDisabled()).toBe(false);
360374
});
361375

362376
test('enables primary button when requiresAcknowledgement is false', () => {
363-
const { getByRole } = render(
377+
render(
364378
<Wizard>
365379
<Wizard.Step requiresAcknowledgement={false}>
366380
<div data-testid="step-1-content">Content 1</div>
@@ -369,12 +383,12 @@ describe('packages/wizard', () => {
369383
</Wizard>,
370384
);
371385

372-
const primaryButton = getByRole('button', { name: 'Next' });
373-
expect(primaryButton).toHaveAttribute('aria-disabled', 'false');
386+
const { isPrimaryButtonDisabled } = getTestUtils();
387+
expect(isPrimaryButtonDisabled()).toBe(false);
374388
});
375389

376390
test('enables primary button when requiresAcknowledgement is not set (default)', () => {
377-
const { getByRole } = render(
391+
render(
378392
<Wizard>
379393
<Wizard.Step>
380394
<div data-testid="step-1-content">Content 1</div>
@@ -383,8 +397,8 @@ describe('packages/wizard', () => {
383397
</Wizard>,
384398
);
385399

386-
const primaryButton = getByRole('button', { name: 'Next' });
387-
expect(primaryButton).toHaveAttribute('aria-disabled', 'false');
400+
const { isPrimaryButtonDisabled } = getTestUtils();
401+
expect(isPrimaryButtonDisabled()).toBe(false);
388402
});
389403
});
390404
});

packages/wizard/src/Wizard/Wizard.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import {
77
import { useControlled } from '@leafygreen-ui/hooks';
88

99
import { WizardSubComponentProperties } from '../constants';
10+
import { getLgIds } from '../utils/getLgIds';
1011
import { WizardProvider } from '../WizardContext/WizardContext';
1112
import { WizardFooter } from '../WizardFooter';
1213
import { WizardStep } from '../WizardStep';
1314

1415
import { WizardProps } from './Wizard.types';
1516

1617
export const Wizard = CompoundComponent(
17-
({ activeStep: activeStepProp, onStepChange, children }: WizardProps) => {
18+
({
19+
activeStep: activeStepProp,
20+
onStepChange,
21+
children,
22+
'data-lgid': dataLgId,
23+
}: WizardProps) => {
24+
const lgIds = getLgIds(dataLgId);
1825
const stepChildren = findChildren(
1926
children,
2027
WizardSubComponentProperties.Step,
@@ -47,11 +54,16 @@ export const Wizard = CompoundComponent(
4754
[setActiveStep, stepChildren.length],
4855
);
4956

57+
/**
58+
* NB: We're intentionally do _not_ wrap the `Wizard` (or `WizardStep`) component in a container element.
59+
* This is done to ensure the Wizard is flexible, and can be rendered in any containing layout.
60+
*/
5061
return (
5162
<WizardProvider
5263
activeStep={activeStep}
5364
updateStep={updateStep}
5465
totalSteps={stepChildren.length}
66+
lgIds={lgIds}
5567
>
5668
{stepChildren.map((child, i) => (i === activeStep ? child : null))}
5769
</WizardProvider>

packages/wizard/src/Wizard/Wizard.types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ReactNode } from 'react';
22

3-
export interface WizardProps {
3+
import { LgIdProps } from '@leafygreen-ui/lib';
4+
5+
export interface WizardProps extends LgIdProps {
46
/**
57
* The current active step index (0-based).
68
*

0 commit comments

Comments
 (0)