@patternfly/react-core 4.240.0 → 4.241.0
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.
- package/CHANGELOG.md +11 -0
- package/dist/esm/next/components/Wizard/Wizard.d.ts +4 -2
- package/dist/esm/next/components/Wizard/Wizard.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/Wizard.js +9 -18
- package/dist/esm/next/components/Wizard/Wizard.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardBody.d.ts +1 -1
- package/dist/esm/next/components/Wizard/WizardBody.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardContext.d.ts +3 -3
- package/dist/esm/next/components/Wizard/WizardContext.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardContext.js +12 -2
- package/dist/esm/next/components/Wizard/WizardContext.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNav.d.ts +1 -1
- package/dist/esm/next/components/Wizard/WizardNav.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNav.js +2 -2
- package/dist/esm/next/components/Wizard/WizardNav.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNavItem.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNavItem.js +4 -1
- package/dist/esm/next/components/Wizard/WizardNavItem.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardStep.d.ts +3 -3
- package/dist/esm/next/components/Wizard/WizardStep.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardStep.js +1 -1
- package/dist/esm/next/components/Wizard/WizardStep.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardToggle.d.ts +17 -8
- package/dist/esm/next/components/Wizard/WizardToggle.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardToggle.js +29 -24
- package/dist/esm/next/components/Wizard/WizardToggle.js.map +1 -1
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.d.ts +2 -1
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.js.map +1 -1
- package/dist/esm/next/components/Wizard/types.d.ts +7 -1
- package/dist/esm/next/components/Wizard/types.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/types.js.map +1 -1
- package/dist/esm/next/components/Wizard/utils.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/utils.js +16 -18
- package/dist/esm/next/components/Wizard/utils.js.map +1 -1
- package/dist/js/next/components/Wizard/Wizard.d.ts +4 -2
- package/dist/js/next/components/Wizard/Wizard.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/Wizard.js +6 -15
- package/dist/js/next/components/Wizard/Wizard.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardBody.d.ts +1 -1
- package/dist/js/next/components/Wizard/WizardBody.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardContext.d.ts +3 -3
- package/dist/js/next/components/Wizard/WizardContext.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardContext.js +12 -2
- package/dist/js/next/components/Wizard/WizardContext.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardNav.d.ts +1 -1
- package/dist/js/next/components/Wizard/WizardNav.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardNav.js +2 -2
- package/dist/js/next/components/Wizard/WizardNav.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardNavItem.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardNavItem.js +4 -1
- package/dist/js/next/components/Wizard/WizardNavItem.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardStep.d.ts +3 -3
- package/dist/js/next/components/Wizard/WizardStep.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardStep.js +1 -1
- package/dist/js/next/components/Wizard/WizardStep.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardToggle.d.ts +17 -8
- package/dist/js/next/components/Wizard/WizardToggle.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardToggle.js +32 -26
- package/dist/js/next/components/Wizard/WizardToggle.js.map +1 -1
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.d.ts +2 -1
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.js.map +1 -1
- package/dist/js/next/components/Wizard/types.d.ts +7 -1
- package/dist/js/next/components/Wizard/types.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/types.js.map +1 -1
- package/dist/js/next/components/Wizard/utils.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/utils.js +16 -18
- package/dist/js/next/components/Wizard/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/next/components/Wizard/Wizard.tsx +34 -47
- package/src/next/components/Wizard/WizardBody.tsx +1 -1
- package/src/next/components/Wizard/WizardContext.tsx +29 -5
- package/src/next/components/Wizard/WizardNav.tsx +3 -3
- package/src/next/components/Wizard/WizardNavItem.tsx +4 -1
- package/src/next/components/Wizard/WizardStep.tsx +4 -4
- package/src/next/components/Wizard/WizardToggle.tsx +74 -49
- package/src/next/components/Wizard/__tests__/Wizard.test.tsx +316 -298
- package/src/next/components/Wizard/__tests__/WizardBody.test.tsx +41 -0
- package/src/next/components/Wizard/__tests__/WizardFooter.test.tsx +93 -0
- package/src/next/components/Wizard/__tests__/WizardStep.test.tsx +33 -0
- package/src/next/components/Wizard/__tests__/WizardToggle.test.tsx +113 -0
- package/src/next/components/Wizard/__tests__/utils.test.tsx +66 -0
- package/src/next/components/Wizard/examples/Wizard.md +3 -5
- package/src/next/components/Wizard/examples/WizardCustomNav.tsx +2 -2
- package/src/next/components/Wizard/examples/WizardKitchenSink.tsx +3 -11
- package/src/next/components/Wizard/hooks/__tests__/useWizardFooter.test.tsx +51 -0
- package/src/next/components/Wizard/hooks/useWizardFooter.tsx +3 -1
- package/src/next/components/Wizard/types.tsx +7 -1
- package/src/next/components/Wizard/utils.ts +23 -21
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { WizardBody } from '../WizardBody';
|
|
4
|
+
|
|
5
|
+
test('renders children without additional props', () => {
|
|
6
|
+
const { container } = render(<WizardBody>content</WizardBody>);
|
|
7
|
+
|
|
8
|
+
expect(container).toHaveTextContent('content');
|
|
9
|
+
expect(container).not.toHaveAttribute('aria-label');
|
|
10
|
+
expect(container).not.toHaveAttribute('aria-labelledby');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('has no padding className when hasNoBodyPadding is not specified', () => {
|
|
14
|
+
render(<WizardBody>content</WizardBody>);
|
|
15
|
+
expect(screen.getByText('content')).not.toHaveClass('pf-m-no-padding');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('has padding className when hasNoBodyPadding is specified', () => {
|
|
19
|
+
render(<WizardBody hasNoBodyPadding>content</WizardBody>);
|
|
20
|
+
expect(screen.getByText('content')).toHaveClass('pf-m-no-padding');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('has aria-label when one is specified', () => {
|
|
24
|
+
render(<WizardBody aria-label="Body label">content</WizardBody>);
|
|
25
|
+
expect(screen.getByLabelText('Body label')).toBeVisible();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('has aria-labelledby when one is specified', () => {
|
|
29
|
+
const { container } = render(<WizardBody aria-labelledby="some-id">content</WizardBody>);
|
|
30
|
+
expect(container.firstElementChild).toHaveAttribute('aria-labelledby', 'some-id');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('wrapper element is of type div when wrapperElement is not specified', () => {
|
|
34
|
+
const { container } = render(<WizardBody aria-label="Wizard body">content</WizardBody>);
|
|
35
|
+
expect(container.firstElementChild?.tagName).toEqual('DIV');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('renders with custom wrapperElement', () => {
|
|
39
|
+
const { container } = render(<WizardBody wrapperElement="main">content</WizardBody>);
|
|
40
|
+
expect(container.firstElementChild?.tagName).toEqual('MAIN');
|
|
41
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
import { WizardFooter } from '../WizardFooter';
|
|
7
|
+
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
activeStep: { name: 'Step name', id: 'some-id' },
|
|
10
|
+
onNext: jest.fn(),
|
|
11
|
+
onBack: jest.fn(),
|
|
12
|
+
onClose: jest.fn()
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('has button names of "Next", "Back", and "Cancel" by default', () => {
|
|
16
|
+
render(<WizardFooter {...defaultProps} />);
|
|
17
|
+
|
|
18
|
+
expect(screen.getByRole('button', { name: 'Next' })).toBeVisible();
|
|
19
|
+
expect(screen.getByRole('button', { name: 'Back' })).toBeVisible();
|
|
20
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('calls onNext when the next button is clicked', async () => {
|
|
24
|
+
const onNext = jest.fn();
|
|
25
|
+
const user = userEvent.setup();
|
|
26
|
+
|
|
27
|
+
render(<WizardFooter {...defaultProps} onNext={onNext} />);
|
|
28
|
+
|
|
29
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
30
|
+
expect(onNext).toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('calls onBack when the back button is clicked', async () => {
|
|
34
|
+
const onBack = jest.fn();
|
|
35
|
+
const user = userEvent.setup();
|
|
36
|
+
|
|
37
|
+
render(<WizardFooter {...defaultProps} onBack={onBack} />);
|
|
38
|
+
|
|
39
|
+
await user.click(screen.getByRole('button', { name: 'Back' }));
|
|
40
|
+
expect(onBack).toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('calls onClose when the close button is clicked', async () => {
|
|
44
|
+
const onClose = jest.fn();
|
|
45
|
+
const user = userEvent.setup();
|
|
46
|
+
|
|
47
|
+
render(<WizardFooter {...defaultProps} onClose={onClose} />);
|
|
48
|
+
|
|
49
|
+
await user.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
50
|
+
expect(onClose).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('can have custom button names', () => {
|
|
54
|
+
render(
|
|
55
|
+
<WizardFooter
|
|
56
|
+
{...defaultProps}
|
|
57
|
+
nextButtonText={<>Go!</>}
|
|
58
|
+
backButtonText="Turn around!"
|
|
59
|
+
cancelButtonText={<span>Get out!</span>}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(screen.getByRole('button', { name: 'Go!' })).toBeVisible();
|
|
64
|
+
expect(screen.getByRole('button', { name: 'Turn around!' })).toBeVisible();
|
|
65
|
+
expect(screen.getByRole('button', { name: 'Get out!' })).toBeVisible();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('has disabled back button when disableBackButton is enabled', () => {
|
|
69
|
+
render(<WizardFooter {...defaultProps} disableBackButton />);
|
|
70
|
+
expect(screen.getByRole('button', { name: 'Back' })).toHaveAttribute('disabled');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('has no back button when activeStep has hideBackButton enabled', () => {
|
|
74
|
+
render(<WizardFooter {...defaultProps} activeStep={{ ...defaultProps.activeStep, hideBackButton: true }} />);
|
|
75
|
+
expect(screen.queryByRole('button', { name: 'Back' })).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('has no cancel button when activeStep has hideCancelButton enabled', () => {
|
|
79
|
+
render(<WizardFooter {...defaultProps} activeStep={{ ...defaultProps.activeStep, hideCancelButton: true }} />);
|
|
80
|
+
expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test(`uses activeStep's nextButtonText when specified instead of nextButtonText of WizardFooter`, () => {
|
|
84
|
+
render(
|
|
85
|
+
<WizardFooter
|
|
86
|
+
{...defaultProps}
|
|
87
|
+
nextButtonText="Footer next"
|
|
88
|
+
activeStep={{ ...defaultProps.activeStep, nextButtonText: 'Active step next' }}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
expect(screen.queryByRole('button', { name: 'Footer next' })).toBeNull();
|
|
92
|
+
expect(screen.getByRole('button', { name: 'Active step next' })).toBeVisible();
|
|
93
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { WizardStep } from '../WizardStep';
|
|
4
|
+
|
|
5
|
+
test('renders without children', () => {
|
|
6
|
+
const { container } = render(<WizardStep id="test-step" name="Test step" />);
|
|
7
|
+
expect(container).toBeVisible();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('renders with children', () => {
|
|
11
|
+
render(
|
|
12
|
+
<WizardStep id="test-step" name="Test step">
|
|
13
|
+
content
|
|
14
|
+
</WizardStep>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
expect(screen.getByText('content')).toBeVisible();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('excludes WizardBody when body is set to null', () => {
|
|
21
|
+
render(<WizardStep id="test-step" name="Test step" body={null} />);
|
|
22
|
+
expect(screen.queryByRole('main')).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('uses body props for WizardBody when passed', () => {
|
|
26
|
+
render(
|
|
27
|
+
<WizardStep id="test-step" name="Test step" body={{ 'aria-label': 'Some label' }}>
|
|
28
|
+
content
|
|
29
|
+
</WizardStep>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(screen.getByLabelText('Some label')).toBeVisible();
|
|
33
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
import { KeyTypes } from '../../../../helpers';
|
|
7
|
+
import { WizardToggle, WizardToggleProps } from '../WizardToggle';
|
|
8
|
+
|
|
9
|
+
const steps = [
|
|
10
|
+
{ id: 'id-1', name: 'First step', component: <>First step content</> },
|
|
11
|
+
{ id: 'id-2', name: 'Second step', component: <>Second step content</> }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const defaultProps: WizardToggleProps = {
|
|
15
|
+
steps,
|
|
16
|
+
activeStep: steps[0],
|
|
17
|
+
footer: <div>Some footer</div>,
|
|
18
|
+
nav: <div>Some nav</div>,
|
|
19
|
+
'aria-label': 'Some label'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
test('renders provided footer, nav, and active step content', () => {
|
|
23
|
+
render(<WizardToggle {...defaultProps} />);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByText('Some footer')).toBeVisible();
|
|
26
|
+
expect(screen.getByText('Some nav')).toBeVisible();
|
|
27
|
+
expect(screen.getByText('First step content')).toBeVisible();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('applies aria-label to the expandable toggle button when specified', () => {
|
|
31
|
+
render(<WizardToggle {...defaultProps} aria-label="Some label" />);
|
|
32
|
+
expect(screen.getByRole('button', { name: 'Some label' })).toBeVisible();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('has expanded properties when isNavExpanded is true', async () => {
|
|
36
|
+
render(<WizardToggle {...defaultProps} aria-label="Some label" isNavExpanded />);
|
|
37
|
+
|
|
38
|
+
const toggleButton = screen.getByRole('button', { name: 'Some label' });
|
|
39
|
+
|
|
40
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'true');
|
|
41
|
+
expect(toggleButton).toHaveClass('pf-m-expanded');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('calls toggleNavExpanded on toggle button click', async () => {
|
|
45
|
+
const toggleNavExpanded = jest.fn();
|
|
46
|
+
const user = userEvent.setup();
|
|
47
|
+
|
|
48
|
+
render(<WizardToggle {...defaultProps} aria-label="Some label" toggleNavExpanded={toggleNavExpanded} />);
|
|
49
|
+
|
|
50
|
+
await user.click(screen.getByRole('button', { name: 'Some label' }));
|
|
51
|
+
expect(toggleNavExpanded).toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('renders only the active step content by default', async () => {
|
|
55
|
+
render(<WizardToggle {...defaultProps} />);
|
|
56
|
+
|
|
57
|
+
expect(screen.getByText('First step content')).toBeInTheDocument();
|
|
58
|
+
expect(screen.queryByText('Second step content')).toBeNull;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('renders all step content when unmountInactiveSteps is false', async () => {
|
|
62
|
+
render(<WizardToggle {...defaultProps} unmountInactiveSteps={false} />);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText('First step content')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Second step content')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('has expanded properties when the toggle dropdown button is clicked', async () => {
|
|
69
|
+
const user = userEvent.setup();
|
|
70
|
+
|
|
71
|
+
render(<WizardToggleExpand aria-label="Some label" />);
|
|
72
|
+
|
|
73
|
+
const toggleButton = screen.getByRole('button', { name: 'Some label' });
|
|
74
|
+
|
|
75
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
|
|
76
|
+
expect(toggleButton).not.toHaveClass('pf-m-expanded');
|
|
77
|
+
|
|
78
|
+
await user.click(toggleButton);
|
|
79
|
+
|
|
80
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'true');
|
|
81
|
+
expect(toggleButton).toHaveClass('pf-m-expanded');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('unsets expanded properties when using the Escape key', async () => {
|
|
85
|
+
const user = userEvent.setup();
|
|
86
|
+
|
|
87
|
+
render(<WizardToggleExpand aria-label="Some label" />);
|
|
88
|
+
|
|
89
|
+
const toggleButton = screen.getByRole('button', { name: 'Some label' });
|
|
90
|
+
|
|
91
|
+
await user.click(toggleButton);
|
|
92
|
+
|
|
93
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'true');
|
|
94
|
+
expect(toggleButton).toHaveClass('pf-m-expanded');
|
|
95
|
+
|
|
96
|
+
await user.type(toggleButton, `{${KeyTypes.Escape}}`);
|
|
97
|
+
|
|
98
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
|
|
99
|
+
expect(toggleButton).not.toHaveClass('pf-m-expanded');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const WizardToggleExpand = props => {
|
|
103
|
+
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<WizardToggle
|
|
107
|
+
{...defaultProps}
|
|
108
|
+
isNavExpanded={isExpanded}
|
|
109
|
+
toggleNavExpanded={() => setIsExpanded(!isExpanded)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { WizardParentStep, WizardSubStep } from '../types';
|
|
4
|
+
import { buildSteps } from '../utils';
|
|
5
|
+
import { WizardStep } from '../WizardStep';
|
|
6
|
+
|
|
7
|
+
describe('buildSteps', () => {
|
|
8
|
+
test('throws error if child does not have required props or type of WizardStep', () => {
|
|
9
|
+
try {
|
|
10
|
+
buildSteps(<div />);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
expect(error.message).toEqual('Wizard only accepts children of type WizardStep');
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('throws error if no children are valid react elements', () => {
|
|
17
|
+
try {
|
|
18
|
+
buildSteps('test' as any);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
expect(error.message).toEqual('Wizard only accepts children of type WizardStep');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('returns array of steps if children are of type WizardStep', () => {
|
|
25
|
+
const component = <WizardStep name="Step 1" id="step-1" />;
|
|
26
|
+
const [step] = buildSteps(component);
|
|
27
|
+
|
|
28
|
+
expect(step.id).toEqual('step-1');
|
|
29
|
+
expect(step.name).toEqual('Step 1');
|
|
30
|
+
expect(step.component?.props).toEqual(component.props);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('returns array of steps if children have required props', () => {
|
|
34
|
+
const CustomStep = props => <div {...props} />;
|
|
35
|
+
const component = (
|
|
36
|
+
<CustomStep name="Step 1" id="step-1">
|
|
37
|
+
Content
|
|
38
|
+
</CustomStep>
|
|
39
|
+
);
|
|
40
|
+
const [step] = buildSteps(component);
|
|
41
|
+
|
|
42
|
+
expect(step.id).toEqual('step-1');
|
|
43
|
+
expect(step.name).toEqual('Step 1');
|
|
44
|
+
expect(step.component?.props).toEqual(component.props);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('returns flattened array of steps and sub-steps when sub-steps exist', () => {
|
|
48
|
+
const CustomSubStep = props => <div {...props} />;
|
|
49
|
+
|
|
50
|
+
const component = (
|
|
51
|
+
<WizardStep
|
|
52
|
+
name="Step 1"
|
|
53
|
+
id="step-1"
|
|
54
|
+
steps={[
|
|
55
|
+
<WizardStep name="SubStep" id="sub-step" />,
|
|
56
|
+
<CustomSubStep name="Custom SubStep" id="custom-sub-step" />
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
const [subStep, customSubStep, parentStep] = buildSteps(component);
|
|
61
|
+
|
|
62
|
+
expect((subStep as WizardSubStep).parentId).toEqual('step-1');
|
|
63
|
+
expect((customSubStep as WizardSubStep).parentId).toEqual('step-1');
|
|
64
|
+
expect((parentStep as WizardParentStep).subStepIds).toEqual(['sub-step', 'custom-sub-step']);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -47,8 +47,6 @@ WizardNavItem,
|
|
|
47
47
|
WizardNav,
|
|
48
48
|
WizardHeader
|
|
49
49
|
} from '@patternfly/react-core/next';
|
|
50
|
-
import { css } from '@patternfly/react-styles';
|
|
51
|
-
import styles from '@patternfly/react-styles/css/components/Wizard/wizard';
|
|
52
50
|
|
|
53
51
|
PatternFly has two implementations of a `Wizard`. This newer `Wizard` takes a more explicit and declarative approach compared to the older implementation, which can be found under the [React](/components/wizard/react) tab.
|
|
54
52
|
|
|
@@ -63,10 +61,10 @@ PatternFly has two implementations of a `Wizard`. This newer `Wizard` takes a mo
|
|
|
63
61
|
|
|
64
62
|
The `Wizard`'s `nav` property can be used to build your own navigation.
|
|
65
63
|
|
|
66
|
-
```
|
|
64
|
+
```noLive
|
|
67
65
|
/** Callback for the Wizard's 'nav' property. Returns element which replaces the Wizard's default navigation. */
|
|
68
66
|
export type CustomWizardNavFunction = (
|
|
69
|
-
|
|
67
|
+
isExpanded: boolean,
|
|
70
68
|
steps: WizardControlStep[],
|
|
71
69
|
activeStep: WizardControlStep,
|
|
72
70
|
goToStepByIndex: (index: number) => void
|
|
@@ -85,7 +83,7 @@ Includes a header, custom footer, sub-steps, step content with a drawer, custom
|
|
|
85
83
|
|
|
86
84
|
Custom operations when navigating between steps can be achieved by utilizing `onNext`, `onBack` or `onNavByIndex` properties whose callback functions return the 'id' and 'name' of the currently focused step (currentStep), and the previously focused step (previousStep).
|
|
87
85
|
|
|
88
|
-
```
|
|
86
|
+
```noLive
|
|
89
87
|
/** Callback for the Wizard's 'onNext', 'onBack', and 'onNavByIndex' properties */
|
|
90
88
|
type WizardNavStepFunction = (currentStep: WizardNavStepData, previousStep: WizardNavStepData) => void;
|
|
91
89
|
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
|
|
11
11
|
export const WizardCustomNav: React.FunctionComponent = () => {
|
|
12
12
|
const nav: CustomWizardNavFunction = (
|
|
13
|
-
|
|
13
|
+
isExpanded: boolean,
|
|
14
14
|
steps: WizardControlStep[],
|
|
15
15
|
activeStep: WizardControlStep,
|
|
16
16
|
goToStepByIndex: (index: number) => void
|
|
17
17
|
) => (
|
|
18
|
-
<WizardNav
|
|
18
|
+
<WizardNav isExpanded={isExpanded}>
|
|
19
19
|
{steps.map((step, index) => (
|
|
20
20
|
<WizardNavItem
|
|
21
21
|
key={step.id}
|
|
@@ -27,8 +27,6 @@ import {
|
|
|
27
27
|
WizardNavItem,
|
|
28
28
|
WizardHeader
|
|
29
29
|
} from '@patternfly/react-core/next';
|
|
30
|
-
import { css } from '@patternfly/react-styles';
|
|
31
|
-
import styles from '@patternfly/react-styles/css/components/Wizard/wizard';
|
|
32
30
|
|
|
33
31
|
const CustomWizardFooter = () => {
|
|
34
32
|
const { activeStep, onNext, onBack, onClose } = useWizardContext();
|
|
@@ -100,14 +98,8 @@ const StepWithCustomFooter = () => {
|
|
|
100
98
|
|
|
101
99
|
const footer = React.useMemo(
|
|
102
100
|
() => (
|
|
103
|
-
|
|
104
|
-
<Button
|
|
105
|
-
variant="primary"
|
|
106
|
-
type="submit"
|
|
107
|
-
onClick={() => onNext(goToNextStep)}
|
|
108
|
-
isLoading={isLoading}
|
|
109
|
-
isDisabled={isLoading}
|
|
110
|
-
>
|
|
101
|
+
<>
|
|
102
|
+
<Button variant="primary" onClick={() => onNext(goToNextStep)} isLoading={isLoading} isDisabled={isLoading}>
|
|
111
103
|
Async Next
|
|
112
104
|
</Button>
|
|
113
105
|
<Button variant="secondary" onClick={onBack}>
|
|
@@ -116,7 +108,7 @@ const StepWithCustomFooter = () => {
|
|
|
116
108
|
<Button variant="link" onClick={onClose}>
|
|
117
109
|
Cancel
|
|
118
110
|
</Button>
|
|
119
|
-
|
|
111
|
+
</>
|
|
120
112
|
),
|
|
121
113
|
[isLoading, onBack, onClose, goToNextStep]
|
|
122
114
|
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { renderHook } from '@testing-library/react-hooks';
|
|
4
|
+
|
|
5
|
+
import { useWizardFooter } from '../useWizardFooter';
|
|
6
|
+
import * as WizardContext from '../../WizardContext';
|
|
7
|
+
|
|
8
|
+
describe('useWizardFooter', () => {
|
|
9
|
+
const customFooter = <>My custom footer</>;
|
|
10
|
+
const useWizardContextSpy = jest.spyOn(WizardContext, 'useWizardContext');
|
|
11
|
+
const setFooter = jest.fn();
|
|
12
|
+
|
|
13
|
+
it('sets the footer when one is provided without a stepId', () => {
|
|
14
|
+
useWizardContextSpy.mockReturnValueOnce({ setFooter } as any);
|
|
15
|
+
|
|
16
|
+
renderHook(() => useWizardFooter(customFooter));
|
|
17
|
+
expect(setFooter).toHaveBeenCalledWith(customFooter);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it(`sets the footer when the provided stepId matches the activeStep's id`, () => {
|
|
21
|
+
useWizardContextSpy.mockReturnValueOnce({ setFooter, activeStep: { id: 'active-step-id' } } as any);
|
|
22
|
+
|
|
23
|
+
renderHook(() => useWizardFooter(customFooter, 'active-step-id'));
|
|
24
|
+
expect(setFooter).toHaveBeenCalledWith(customFooter);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it(`does not set the footer when the provided stepId does not match the activeStep's id`, () => {
|
|
28
|
+
useWizardContextSpy.mockReturnValueOnce({ setFooter, activeStep: { id: 'active-step-id' } } as any);
|
|
29
|
+
|
|
30
|
+
renderHook(() => useWizardFooter(customFooter, 'some-other-step-id'));
|
|
31
|
+
expect(setFooter).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('sets the footer to null on unmount', () => {
|
|
35
|
+
useWizardContextSpy.mockReturnValueOnce({ setFooter } as any);
|
|
36
|
+
|
|
37
|
+
const { unmount } = renderHook(() => useWizardFooter(customFooter));
|
|
38
|
+
unmount();
|
|
39
|
+
|
|
40
|
+
expect(setFooter).toHaveBeenCalledWith(null);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('sets footer with default props instead of react element', () => {
|
|
44
|
+
const footerProps = { onNext: jest.fn(), onBack: jest.fn(), onClose: jest.fn() };
|
|
45
|
+
useWizardContextSpy.mockReturnValueOnce({ setFooter } as any);
|
|
46
|
+
|
|
47
|
+
renderHook(() => useWizardFooter(footerProps));
|
|
48
|
+
|
|
49
|
+
expect(setFooter).toHaveBeenCalledWith(footerProps);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { DefaultWizardFooterProps } from '../types';
|
|
2
4
|
import { useWizardContext } from '../WizardContext';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -6,7 +8,7 @@ import { useWizardContext } from '../WizardContext';
|
|
|
6
8
|
* @param footer
|
|
7
9
|
* @param stepId
|
|
8
10
|
*/
|
|
9
|
-
export const useWizardFooter = (footer: React.ReactElement, stepId?: string | number) => {
|
|
11
|
+
export const useWizardFooter = (footer: DefaultWizardFooterProps | React.ReactElement, stepId?: string | number) => {
|
|
10
12
|
const { activeStep, setFooter } = useWizardContext();
|
|
11
13
|
|
|
12
14
|
React.useEffect(() => {
|
|
@@ -57,6 +57,12 @@ export interface DefaultWizardFooterProps {
|
|
|
57
57
|
backButtonText?: React.ReactNode;
|
|
58
58
|
/** The Cancel button text */
|
|
59
59
|
cancelButtonText?: React.ReactNode;
|
|
60
|
+
/** Next button callback */
|
|
61
|
+
onNext?: () => WizardNavStepFunction | void;
|
|
62
|
+
/** Back button callback */
|
|
63
|
+
onBack?: () => WizardNavStepFunction | void;
|
|
64
|
+
/** Cancel link callback */
|
|
65
|
+
onClose?: () => void;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
/** Encompasses all step type variants that are internally controlled by the Wizard. */
|
|
@@ -70,7 +76,7 @@ export type WizardNavStepData = Pick<WizardControlStep, 'id' | 'name'>;
|
|
|
70
76
|
|
|
71
77
|
/** Callback for the Wizard's 'nav' property. Returns element which replaces the Wizard's default navigation. */
|
|
72
78
|
export type CustomWizardNavFunction = (
|
|
73
|
-
|
|
79
|
+
isExpanded: boolean,
|
|
74
80
|
steps: WizardControlStep[],
|
|
75
81
|
activeStep: WizardControlStep,
|
|
76
82
|
goToStepByIndex: (index: number) => void
|
|
@@ -4,7 +4,7 @@ import { WizardControlStep, WizardNavStepData } from './types';
|
|
|
4
4
|
import { WizardStep, WizardStepProps } from './WizardStep';
|
|
5
5
|
|
|
6
6
|
function hasWizardStepProps(props: WizardStepProps | any): props is WizardStepProps {
|
|
7
|
-
return props.name !== undefined && props.id !== undefined
|
|
7
|
+
return props.name !== undefined && props.id !== undefined;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -14,29 +14,31 @@ function hasWizardStepProps(props: WizardStepProps | any): props is WizardStepPr
|
|
|
14
14
|
*/
|
|
15
15
|
export const buildSteps = (children: React.ReactElement<WizardStepProps> | React.ReactElement<WizardStepProps>[]) =>
|
|
16
16
|
React.Children.toArray(children).reduce((acc: WizardControlStep[], child) => {
|
|
17
|
-
if (React.isValidElement(child)) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const { steps: subSteps, id, children, ...stepProps } = child.props as WizardStepProps;
|
|
17
|
+
if (React.isValidElement(child) && (child.type === WizardStep || hasWizardStepProps(child.props))) {
|
|
18
|
+
// Omit "children" and use the whole "child" (WizardStep) for the component prop. Sub-steps will do the same.
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
20
|
+
const { steps: subSteps, id, children, ...stepProps } = child.props as WizardStepProps;
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
acc.push({
|
|
23
|
+
id,
|
|
24
|
+
component: child,
|
|
25
|
+
...stepProps,
|
|
26
|
+
...(subSteps && {
|
|
27
|
+
subStepIds: subSteps?.map(subStep => {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
const { children, ...subStepProps } = subStep.props;
|
|
30
|
+
acc.push({
|
|
31
|
+
...subStepProps,
|
|
32
|
+
component: subStep,
|
|
33
|
+
parentId: id
|
|
34
|
+
});
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
})
|
|
36
|
+
return subStep.props.id;
|
|
35
37
|
})
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error('Wizard only accepts children of type WizardStep');
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
return acc;
|