@transferwise/components 0.0.0-experimental-09066ec → 0.0.0-experimental-148ffef
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/build/common/liveRegion/LiveRegion.js +44 -7
- package/build/common/liveRegion/LiveRegion.js.map +1 -1
- package/build/common/liveRegion/LiveRegion.mjs +44 -7
- package/build/common/liveRegion/LiveRegion.mjs.map +1 -1
- package/build/flowNavigation/FlowNavigation.js +1 -0
- package/build/flowNavigation/FlowNavigation.js.map +1 -1
- package/build/flowNavigation/FlowNavigation.mjs +1 -0
- package/build/flowNavigation/FlowNavigation.mjs.map +1 -1
- package/build/overlayHeader/OverlayHeader.js +1 -0
- package/build/overlayHeader/OverlayHeader.js.map +1 -1
- package/build/overlayHeader/OverlayHeader.mjs +1 -0
- package/build/overlayHeader/OverlayHeader.mjs.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.js +2 -0
- package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.mjs +2 -0
- package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
- package/build/types/common/liveRegion/LiveRegion.d.ts +5 -2
- package/build/types/common/liveRegion/LiveRegion.d.ts.map +1 -1
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/{DisabledComponents.story.tsx → DisabledComponents.test.story.tsx} +1 -1
- package/src/actionButton/ActionButton.story.tsx +11 -7
- package/src/actionButton/ActionButton.test.story.tsx +49 -0
- package/src/avatarLayout/AvatarLayout.story.tsx +1 -105
- package/src/avatarLayout/AvatarLayout.test.story.tsx +117 -0
- package/src/avatarView/AvatarView.story.tsx +1 -104
- package/src/avatarView/AvatarView.test.story.tsx +114 -0
- package/src/button/LegacyButton.story.tsx +1 -26
- package/src/button/LegacyButton.test.story.tsx +37 -0
- package/src/button/_stories/Button.story.tsx +17 -41
- package/src/button/_stories/Button.test.story.tsx +30 -0
- package/src/checkbox/Checkbox.story.tsx +1 -6
- package/src/checkbox/Checkbox.test.story.tsx +33 -0
- package/src/circularButton/CircularButton.story.tsx +0 -6
- package/src/circularButton/CircularButton.test.story.tsx +62 -1
- package/src/common/bottomSheet/BottomSheet.story.tsx +0 -55
- package/src/common/bottomSheet/BottomSheet.test.story.tsx +118 -2
- package/src/common/liveRegion/LiveRegion.test.tsx +69 -2
- package/src/common/liveRegion/LiveRegion.tsx +75 -8
- package/src/criticalBanner/CriticalCommsBanner.test.story.tsx +1 -1
- package/src/dateLookup/DateLookup.story.tsx +0 -18
- package/src/dateLookup/DateLookup.test.story.tsx +51 -1
- package/src/divider/Divider.story.tsx +0 -1
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.autofocus.docs.mdx +1 -1
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.story.tsx +0 -9
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.test.story.tsx +23 -0
- package/src/field/Field.story.tsx +1 -34
- package/src/field/Field.test.story.tsx +56 -0
- package/src/header/Header.test.story.tsx +1 -1
- package/src/info/Info.story.tsx +1 -34
- package/src/info/Info.test.story.tsx +53 -0
- package/src/inputWithDisplayFormat/InputWithDisplayFormat.story.tsx +0 -12
- package/src/inputWithDisplayFormat/InputWithDisplayFormat.test.story.tsx +33 -0
- package/src/legacylistItem/LegacyListItem.test.story.tsx +1 -1
- package/src/listItem/_stories/Breakpoints/ListItem.noMedia.test.story.tsx +1 -1
- package/src/listItem/_stories/Breakpoints/ListItem.sideMedia.test.story.tsx +1 -1
- package/src/listItem/_stories/Breakpoints/ListItem.stackedMedia.test.story.tsx +1 -1
- package/src/listItem/_stories/ListItem.focus.test.story.tsx +1 -1
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.brightGreen.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.dark.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.forestGreen.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.medium.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.neutral.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.personal.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.rtl.test.story.tsx +1 -1
- package/src/listItem/_stories/variants/ListItem.small.test.story.tsx +1 -1
- package/src/logo/Logo.story.tsx +0 -6
- package/src/logo/Logo.test.story.tsx +20 -0
- package/src/modal/Modal.story.tsx +0 -26
- package/src/modal/Modal.test.story.tsx +125 -0
- package/src/moneyInput/MoneyInput.test.story.tsx +1 -1
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +0 -43
- package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +45 -1
- package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +142 -5
- package/src/prompt/InfoPrompt/InfoPrompt.test.tsx +11 -6
- package/src/prompt/InfoPrompt/InfoPrompt.tsx +2 -1
- package/src/radioGroup/RadioGroup.test.story.tsx +1 -1
- package/src/stepper/Stepper.test.story.tsx +1 -1
- package/src/summary/Summary.test.story.tsx +1 -1
- package/src/tooltip/Tooltip.story.tsx +0 -21
- package/src/tooltip/Tooltip.test.story.tsx +42 -0
- package/src/typeahead/Typeahead.story.tsx +0 -52
- package/src/typeahead/Typeahead.test.story.tsx +73 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { action } from 'storybook/actions';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
|
+
import { ScreenMode, ThemeProvider } from '@wise/components-theming';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { Button, Modal, ModalProps } from '..';
|
|
6
|
+
import { CommonProps, Scroll } from '../common';
|
|
7
|
+
import { lorem10, lorem100, lorem1000 } from '../test-utils';
|
|
8
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof Modal> = {
|
|
11
|
+
component: Modal,
|
|
12
|
+
tags: ['!autodocs', '!manifest'],
|
|
13
|
+
title: 'Dialogs/Modal/Tests',
|
|
14
|
+
args: {
|
|
15
|
+
size: 'md',
|
|
16
|
+
position: 'center',
|
|
17
|
+
open: false,
|
|
18
|
+
},
|
|
19
|
+
parameters: {
|
|
20
|
+
chromatic: {
|
|
21
|
+
delay: 2000,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export default meta;
|
|
26
|
+
|
|
27
|
+
type Story = StoryObj<typeof Modal>;
|
|
28
|
+
|
|
29
|
+
export interface StoryContentProps {
|
|
30
|
+
args: CommonProps & ModalProps;
|
|
31
|
+
screenMode?: ScreenMode;
|
|
32
|
+
viewMode?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const StoryContent = ({ args, screenMode, viewMode }: StoryContentProps) => {
|
|
36
|
+
const [open, setOpen] = useState(viewMode !== 'docs');
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<Button v2 onClick={() => setOpen(true)}>
|
|
41
|
+
Open Modal
|
|
42
|
+
</Button>
|
|
43
|
+
<Modal
|
|
44
|
+
{...args}
|
|
45
|
+
body={
|
|
46
|
+
!screenMode ? (
|
|
47
|
+
args.body
|
|
48
|
+
) : (
|
|
49
|
+
<ThemeProvider theme="personal" screenMode={screenMode}>
|
|
50
|
+
{lorem100}
|
|
51
|
+
</ThemeProvider>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
open={open}
|
|
55
|
+
disableDimmerClickToClose={args.disableDimmerClickToClose}
|
|
56
|
+
onClose={() => {
|
|
57
|
+
setOpen(false);
|
|
58
|
+
action('Modal closed')();
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const BasicMobile: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
title: 'Title',
|
|
68
|
+
body: lorem10,
|
|
69
|
+
scroll: Scroll.VIEWPORT,
|
|
70
|
+
footer: (
|
|
71
|
+
<Button v2 block>
|
|
72
|
+
Action
|
|
73
|
+
</Button>
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
render: (args, context) => <StoryContent args={args} viewMode={context.viewMode} />,
|
|
77
|
+
...withVariantConfig(['mobile']),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const ContentScrollMobile: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
title: lorem10,
|
|
83
|
+
body: lorem1000,
|
|
84
|
+
scroll: Scroll.CONTENT,
|
|
85
|
+
},
|
|
86
|
+
render: (args, context) => <StoryContent args={args} viewMode={context.viewMode} />,
|
|
87
|
+
...withVariantConfig(['mobile']),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const ViewportScrollMobile: Story = {
|
|
91
|
+
args: {
|
|
92
|
+
title: lorem10,
|
|
93
|
+
body: lorem1000,
|
|
94
|
+
scroll: Scroll.VIEWPORT,
|
|
95
|
+
},
|
|
96
|
+
render: (args, context) => <StoryContent args={args} viewMode={context.viewMode} />,
|
|
97
|
+
...withVariantConfig(['mobile']),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const WithoutTitleMobile: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
body: lorem1000,
|
|
103
|
+
scroll: Scroll.CONTENT,
|
|
104
|
+
},
|
|
105
|
+
render: (args, context) => <StoryContent args={args} viewMode={context.viewMode} />,
|
|
106
|
+
...withVariantConfig(['mobile']),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const WithThemeProviderInContentMobile: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
title: lorem10,
|
|
112
|
+
body: lorem100,
|
|
113
|
+
scroll: Scroll.VIEWPORT,
|
|
114
|
+
footer: (
|
|
115
|
+
<Button v2 block>
|
|
116
|
+
Action
|
|
117
|
+
</Button>
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
render: (args, { globals: { screenMode }, viewMode }) => (
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
122
|
+
<StoryContent args={args} screenMode={screenMode} viewMode={viewMode} />
|
|
123
|
+
),
|
|
124
|
+
...withVariantConfig(['mobile']),
|
|
125
|
+
};
|
|
@@ -335,49 +335,6 @@ export const Dismissible: Story = {
|
|
|
335
335
|
},
|
|
336
336
|
};
|
|
337
337
|
|
|
338
|
-
/**
|
|
339
|
-
* ActionPrompt adapts its layout based on available width. At narrow widths (mobile),
|
|
340
|
-
* the buttons stack vertically. At wider widths (desktop), they appear side by side.
|
|
341
|
-
*/
|
|
342
|
-
export const Responsiveness: Story = {
|
|
343
|
-
render: (args) => (
|
|
344
|
-
<div
|
|
345
|
-
style={{
|
|
346
|
-
display: 'grid',
|
|
347
|
-
gridTemplateColumns: '320px 480px',
|
|
348
|
-
gap: '1rem',
|
|
349
|
-
alignItems: 'start',
|
|
350
|
-
}}
|
|
351
|
-
>
|
|
352
|
-
<ActionPrompt
|
|
353
|
-
sentiment="proposition"
|
|
354
|
-
title="Stacked (320px)"
|
|
355
|
-
description={lorem10}
|
|
356
|
-
media={{ avatar: { asset: <Travel /> } }}
|
|
357
|
-
action={{ label: 'Primary action', onClick: fn() }}
|
|
358
|
-
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
359
|
-
onDismiss={fn()}
|
|
360
|
-
/>
|
|
361
|
-
<ActionPrompt
|
|
362
|
-
sentiment="proposition"
|
|
363
|
-
title="Side-by-side (480px)"
|
|
364
|
-
description={lorem10}
|
|
365
|
-
media={{ avatar: { asset: <Travel /> } }}
|
|
366
|
-
action={{ label: 'Primary action', onClick: fn() }}
|
|
367
|
-
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
368
|
-
onDismiss={fn()}
|
|
369
|
-
/>
|
|
370
|
-
</div>
|
|
371
|
-
),
|
|
372
|
-
parameters: {
|
|
373
|
-
docs: {
|
|
374
|
-
canvas: {
|
|
375
|
-
sourceState: 'hidden',
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
};
|
|
380
|
-
|
|
381
338
|
/**
|
|
382
339
|
* While the component itself will stretch to the full available width, the text container will be
|
|
383
340
|
* capped at `480px` to ensure optimal readability.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
|
-
import { Freeze, People } from '@transferwise/icons';
|
|
2
|
+
import { Freeze, People, Travel } from '@transferwise/icons';
|
|
3
3
|
import { action } from 'storybook/actions';
|
|
4
4
|
import { fn } from 'storybook/test';
|
|
5
5
|
import ActionPrompt from './ActionPrompt';
|
|
@@ -7,6 +7,7 @@ import { Body, Title } from '../..';
|
|
|
7
7
|
import { StoryObj } from '@storybook/react-webpack5';
|
|
8
8
|
import { allModes } from '../../../.storybook/modes';
|
|
9
9
|
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
10
|
+
import { lorem10 } from '../../test-utils';
|
|
10
11
|
|
|
11
12
|
const withComponentGrid =
|
|
12
13
|
({ gap = '1rem' } = {}) =>
|
|
@@ -228,3 +229,46 @@ export const TinyScreen: Story = {
|
|
|
228
229
|
),
|
|
229
230
|
...withVariantConfig(['400%']),
|
|
230
231
|
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* ActionPrompt adapts its layout based on available width. At narrow widths (mobile),
|
|
235
|
+
* the buttons stack vertically. At wider widths (desktop), they appear side by side.
|
|
236
|
+
*/
|
|
237
|
+
export const Responsiveness: Story = {
|
|
238
|
+
render: (args) => (
|
|
239
|
+
<div
|
|
240
|
+
style={{
|
|
241
|
+
display: 'grid',
|
|
242
|
+
gridTemplateColumns: '320px 480px',
|
|
243
|
+
gap: '1rem',
|
|
244
|
+
alignItems: 'start',
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
<ActionPrompt
|
|
248
|
+
sentiment="proposition"
|
|
249
|
+
title="Stacked (320px)"
|
|
250
|
+
description={lorem10}
|
|
251
|
+
media={{ avatar: { asset: <Travel /> } }}
|
|
252
|
+
action={{ label: 'Primary action', onClick: fn() }}
|
|
253
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
254
|
+
onDismiss={fn()}
|
|
255
|
+
/>
|
|
256
|
+
<ActionPrompt
|
|
257
|
+
sentiment="proposition"
|
|
258
|
+
title="Side-by-side (480px)"
|
|
259
|
+
description={lorem10}
|
|
260
|
+
media={{ avatar: { asset: <Travel /> } }}
|
|
261
|
+
action={{ label: 'Primary action', onClick: fn() }}
|
|
262
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
263
|
+
onDismiss={fn()}
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
),
|
|
267
|
+
parameters: {
|
|
268
|
+
docs: {
|
|
269
|
+
canvas: {
|
|
270
|
+
sourceState: 'hidden',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
};
|
|
@@ -40,7 +40,6 @@ const wait = async (duration = 500) =>
|
|
|
40
40
|
export const DismissInteraction: Story = {
|
|
41
41
|
play: async ({ canvasElement, step }) => {
|
|
42
42
|
const canvas = within(canvasElement);
|
|
43
|
-
const dismissButton = canvas.getByRole('button', { name: /close/i });
|
|
44
43
|
|
|
45
44
|
await step('Verify prompt is visible', async () => {
|
|
46
45
|
await waitFor(async () =>
|
|
@@ -49,6 +48,7 @@ export const DismissInteraction: Story = {
|
|
|
49
48
|
});
|
|
50
49
|
|
|
51
50
|
await step('Click the dismiss button', async () => {
|
|
51
|
+
const dismissButton = canvas.getByRole('button', { name: /close/i, hidden: true });
|
|
52
52
|
await userEvent.click(dismissButton);
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -120,7 +120,6 @@ export const DismissViaKeyboard: Story = {
|
|
|
120
120
|
export const ActionClickInteraction: Story = {
|
|
121
121
|
play: async ({ canvasElement, step }) => {
|
|
122
122
|
const canvas = within(canvasElement);
|
|
123
|
-
const actionLink = canvas.getByRole('link', { name: 'Learn more' });
|
|
124
123
|
|
|
125
124
|
await step('Verify prompt with action is visible', async () => {
|
|
126
125
|
await waitFor(async () =>
|
|
@@ -129,6 +128,7 @@ export const ActionClickInteraction: Story = {
|
|
|
129
128
|
});
|
|
130
129
|
|
|
131
130
|
await step('Click the action link', async () => {
|
|
131
|
+
const actionLink = await waitFor(() => canvas.getByRole('link', { name: 'Learn more' }));
|
|
132
132
|
await userEvent.click(actionLink);
|
|
133
133
|
});
|
|
134
134
|
|
|
@@ -165,7 +165,6 @@ export const ActionClickInteraction: Story = {
|
|
|
165
165
|
export const MultipleDismissInteraction: Story = {
|
|
166
166
|
play: async ({ canvasElement, step }) => {
|
|
167
167
|
const canvas = within(canvasElement);
|
|
168
|
-
const dismissButtons = canvas.getAllByRole('button', { name: /close/i });
|
|
169
168
|
|
|
170
169
|
await step('Verify all prompts are visible', async () => {
|
|
171
170
|
await waitFor(async () => {
|
|
@@ -177,6 +176,7 @@ export const MultipleDismissInteraction: Story = {
|
|
|
177
176
|
|
|
178
177
|
await step('Dismiss the warning prompt', async () => {
|
|
179
178
|
// Click the second dismiss button (warning)
|
|
179
|
+
const dismissButtons = canvas.getAllByRole('button', { name: /close/i, hidden: true });
|
|
180
180
|
await userEvent.click(dismissButtons[1]);
|
|
181
181
|
});
|
|
182
182
|
|
|
@@ -220,13 +220,13 @@ export const MultipleDismissInteraction: Story = {
|
|
|
220
220
|
export const TouchInteraction: Story = {
|
|
221
221
|
play: async ({ canvasElement, step }) => {
|
|
222
222
|
const canvas = within(canvasElement);
|
|
223
|
-
const actionLink = canvas.getByRole('link', { name: 'Navigate' });
|
|
224
223
|
|
|
225
224
|
await step('Verify prompt with action is visible', async () => {
|
|
226
225
|
await waitFor(async () => expect(canvas.getByText('Tap the prompt.')).toBeInTheDocument());
|
|
227
226
|
});
|
|
228
227
|
|
|
229
228
|
await step('Click the action (simulating touch)', async () => {
|
|
229
|
+
const actionLink = await waitFor(() => canvas.getByRole('link', { name: 'Navigate' }));
|
|
230
230
|
await userEvent.click(actionLink);
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -352,6 +352,143 @@ export const LiveRegionAssertive: Story = {
|
|
|
352
352
|
},
|
|
353
353
|
};
|
|
354
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Test that a polite InfoPrompt added to the DOM gets a delayed live region.
|
|
357
|
+
*/
|
|
358
|
+
export const LiveRegionPoliteWhenAddedToDom: Story = {
|
|
359
|
+
play: async ({ canvasElement, step }) => {
|
|
360
|
+
const canvas = within(canvasElement);
|
|
361
|
+
|
|
362
|
+
await step('Verify prompt is not in the DOM before trigger', async () => {
|
|
363
|
+
await expect(canvas.queryByText('Polite prompt added later')).not.toBeInTheDocument();
|
|
364
|
+
await expect(canvas.queryByRole('status')).not.toBeInTheDocument();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await step('Add two prompts to the DOM', async () => {
|
|
368
|
+
const addButton = canvas.getByRole('button', { name: 'Add polite prompt' });
|
|
369
|
+
await userEvent.click(addButton);
|
|
370
|
+
await userEvent.click(addButton);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
await step('Verify delayed polite live regions appear with content', async () => {
|
|
374
|
+
await expect(canvas.getByText('Polite prompt added later 1')).toBeInTheDocument();
|
|
375
|
+
await expect(canvas.getByText('Polite prompt added later 2')).toBeInTheDocument();
|
|
376
|
+
const initialLiveRegions = canvas.queryAllByRole('status');
|
|
377
|
+
await expect(initialLiveRegions).toHaveLength(2);
|
|
378
|
+
await expect(initialLiveRegions[0].firstElementChild).toHaveAttribute('aria-hidden', 'true');
|
|
379
|
+
await expect(initialLiveRegions[1].firstElementChild).toHaveAttribute('aria-hidden', 'true');
|
|
380
|
+
|
|
381
|
+
await waitFor(async () => {
|
|
382
|
+
const liveRegions = canvas.getAllByRole('status');
|
|
383
|
+
await expect(liveRegions).toHaveLength(2);
|
|
384
|
+
await expect(liveRegions[0]).toHaveAttribute('aria-live', 'polite');
|
|
385
|
+
await expect(liveRegions[1]).toHaveAttribute('aria-live', 'polite');
|
|
386
|
+
await expect(liveRegions[0]).toHaveAttribute('aria-atomic', 'true');
|
|
387
|
+
await expect(liveRegions[1]).toHaveAttribute('aria-atomic', 'true');
|
|
388
|
+
await expect(liveRegions[0].firstElementChild).not.toHaveAttribute('aria-hidden');
|
|
389
|
+
await expect(liveRegions[1].firstElementChild).not.toHaveAttribute('aria-hidden');
|
|
390
|
+
await expect(
|
|
391
|
+
within(liveRegions[0]).getByText('Polite prompt added later 1'),
|
|
392
|
+
).toBeInTheDocument();
|
|
393
|
+
await expect(
|
|
394
|
+
within(liveRegions[1]).getByText('Polite prompt added later 2'),
|
|
395
|
+
).toBeInTheDocument();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
render: function Render(args: InfoPromptProps) {
|
|
400
|
+
const [promptIndexes, setPromptIndexes] = useState<number[]>([]);
|
|
401
|
+
|
|
402
|
+
const addPrompt = () => {
|
|
403
|
+
setPromptIndexes((previousIndexes) => [...previousIndexes, previousIndexes.length + 1]);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<>
|
|
408
|
+
<button type="button" onClick={addPrompt}>
|
|
409
|
+
Add polite prompt
|
|
410
|
+
</button>
|
|
411
|
+
{promptIndexes.map((promptIndex) => (
|
|
412
|
+
<InfoPrompt
|
|
413
|
+
key={`polite-${promptIndex}`}
|
|
414
|
+
{...args}
|
|
415
|
+
description={`Polite prompt added later ${promptIndex}`}
|
|
416
|
+
/>
|
|
417
|
+
))}
|
|
418
|
+
</>
|
|
419
|
+
);
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Test that an assertive InfoPrompt added to the DOM gets a delayed live region.
|
|
425
|
+
*/
|
|
426
|
+
export const LiveRegionAssertiveWhenAddedToDom: Story = {
|
|
427
|
+
play: async ({ canvasElement, step }) => {
|
|
428
|
+
const canvas = within(canvasElement);
|
|
429
|
+
|
|
430
|
+
await step('Verify prompt is not in the DOM before trigger', async () => {
|
|
431
|
+
await expect(canvas.queryByText('Assertive prompt added later')).not.toBeInTheDocument();
|
|
432
|
+
await expect(canvas.queryByRole('alert')).not.toBeInTheDocument();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
await step('Add two prompts to the DOM', async () => {
|
|
436
|
+
const addButton = canvas.getByRole('button', { name: 'Add assertive prompt' });
|
|
437
|
+
await userEvent.click(addButton);
|
|
438
|
+
await userEvent.click(addButton);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await step('Verify delayed assertive live regions appear with content', async () => {
|
|
442
|
+
await expect(canvas.getByText('Assertive prompt added later 1')).toBeInTheDocument();
|
|
443
|
+
await expect(canvas.getByText('Assertive prompt added later 2')).toBeInTheDocument();
|
|
444
|
+
const initialLiveRegions = canvas.queryAllByRole('alert');
|
|
445
|
+
await expect(initialLiveRegions).toHaveLength(2);
|
|
446
|
+
await expect(initialLiveRegions[0].firstElementChild).toHaveAttribute('aria-hidden', 'true');
|
|
447
|
+
await expect(initialLiveRegions[1].firstElementChild).toHaveAttribute('aria-hidden', 'true');
|
|
448
|
+
|
|
449
|
+
await waitFor(async () => {
|
|
450
|
+
const liveRegions = canvas.getAllByRole('alert');
|
|
451
|
+
await expect(liveRegions).toHaveLength(2);
|
|
452
|
+
await expect(liveRegions[0]).toHaveAttribute('aria-live', 'assertive');
|
|
453
|
+
await expect(liveRegions[1]).toHaveAttribute('aria-live', 'assertive');
|
|
454
|
+
await expect(liveRegions[0]).toHaveAttribute('aria-atomic', 'true');
|
|
455
|
+
await expect(liveRegions[1]).toHaveAttribute('aria-atomic', 'true');
|
|
456
|
+
await expect(liveRegions[0].firstElementChild).not.toHaveAttribute('aria-hidden');
|
|
457
|
+
await expect(liveRegions[1].firstElementChild).not.toHaveAttribute('aria-hidden');
|
|
458
|
+
await expect(
|
|
459
|
+
within(liveRegions[0]).getByText('Assertive prompt added later 1'),
|
|
460
|
+
).toBeInTheDocument();
|
|
461
|
+
await expect(
|
|
462
|
+
within(liveRegions[1]).getByText('Assertive prompt added later 2'),
|
|
463
|
+
).toBeInTheDocument();
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
render: function Render(args: InfoPromptProps) {
|
|
468
|
+
const [promptIndexes, setPromptIndexes] = useState<number[]>([]);
|
|
469
|
+
|
|
470
|
+
const addPrompt = () => {
|
|
471
|
+
setPromptIndexes((previousIndexes) => [...previousIndexes, previousIndexes.length + 1]);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return (
|
|
475
|
+
<>
|
|
476
|
+
<button type="button" onClick={addPrompt}>
|
|
477
|
+
Add assertive prompt
|
|
478
|
+
</button>
|
|
479
|
+
{promptIndexes.map((promptIndex) => (
|
|
480
|
+
<InfoPrompt
|
|
481
|
+
key={`assertive-${promptIndex}`}
|
|
482
|
+
{...args}
|
|
483
|
+
description={`Assertive prompt added later ${promptIndex}`}
|
|
484
|
+
aria-live="assertive"
|
|
485
|
+
/>
|
|
486
|
+
))}
|
|
487
|
+
</>
|
|
488
|
+
);
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
355
492
|
/**
|
|
356
493
|
* Test that aria-live="off" renders without a live region wrapper.
|
|
357
494
|
*/
|
|
@@ -390,7 +527,7 @@ export const LiveRegionDismiss: Story = {
|
|
|
390
527
|
});
|
|
391
528
|
|
|
392
529
|
await step('Dismiss the prompt', async () => {
|
|
393
|
-
const dismissButton = canvas.getByRole('button', { name: /close/i });
|
|
530
|
+
const dismissButton = canvas.getByRole('button', { name: /close/i, hidden: true });
|
|
394
531
|
await userEvent.click(dismissButton);
|
|
395
532
|
});
|
|
396
533
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { fireEvent, mockMatchMedia, render, screen, userEvent } from '../../test-utils';
|
|
1
|
+
import { fireEvent, mockMatchMedia, render, screen, userEvent, waitFor } from '../../test-utils';
|
|
2
|
+
import { resetLiveRegionAnnouncementQueue } from '../../common/liveRegion/LiveRegion';
|
|
2
3
|
import { InfoPrompt, InfoPromptProps } from './InfoPrompt';
|
|
3
4
|
|
|
4
5
|
mockMatchMedia();
|
|
@@ -10,6 +11,10 @@ describe('InfoPrompt', () => {
|
|
|
10
11
|
description: 'Prompt description',
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
resetLiveRegionAnnouncementQueue();
|
|
16
|
+
});
|
|
17
|
+
|
|
13
18
|
it('renders description', () => {
|
|
14
19
|
render(<InfoPrompt {...defaultProps} />);
|
|
15
20
|
expect(screen.getByText('Prompt description')).toBeInTheDocument();
|
|
@@ -59,23 +64,23 @@ describe('InfoPrompt', () => {
|
|
|
59
64
|
});
|
|
60
65
|
|
|
61
66
|
describe('action', () => {
|
|
62
|
-
it('should render action link when action is provided with href', () => {
|
|
67
|
+
it('should render action link when action is provided with href', async () => {
|
|
63
68
|
render(
|
|
64
69
|
<InfoPrompt {...defaultProps} action={{ label: 'Learn more', href: '/learn-more' }} />,
|
|
65
70
|
);
|
|
66
|
-
const actionLink = screen.
|
|
71
|
+
const actionLink = await screen.findByRole('link', { name: 'Learn more' });
|
|
67
72
|
expect(actionLink).toBeInTheDocument();
|
|
68
73
|
expect(actionLink).toHaveAttribute('href', '/learn-more');
|
|
69
74
|
});
|
|
70
75
|
|
|
71
|
-
it('should render action link with target when provided', () => {
|
|
76
|
+
it('should render action link with target when provided', async () => {
|
|
72
77
|
render(
|
|
73
78
|
<InfoPrompt
|
|
74
79
|
{...defaultProps}
|
|
75
80
|
action={{ label: 'External link', href: 'https://example.com', target: '_blank' }}
|
|
76
81
|
/>,
|
|
77
82
|
);
|
|
78
|
-
const actionLink = screen.
|
|
83
|
+
const actionLink = await screen.findByRole('link', { name: /External link/i });
|
|
79
84
|
expect(actionLink).toHaveAttribute('target', '_blank');
|
|
80
85
|
});
|
|
81
86
|
|
|
@@ -83,7 +88,7 @@ describe('InfoPrompt', () => {
|
|
|
83
88
|
const user = userEvent.setup();
|
|
84
89
|
const onClick = jest.fn();
|
|
85
90
|
render(<InfoPrompt {...defaultProps} action={{ label: 'Click me', onClick }} />);
|
|
86
|
-
const actionButton = screen.
|
|
91
|
+
const actionButton = await screen.findByRole('button', { name: 'Click me' });
|
|
87
92
|
await user.click(actionButton);
|
|
88
93
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
89
94
|
});
|
|
@@ -86,6 +86,7 @@ export const InfoPrompt = ({
|
|
|
86
86
|
...restProps
|
|
87
87
|
}: InfoPromptProps) => {
|
|
88
88
|
const [shouldFire, setShouldFire] = useState<boolean>();
|
|
89
|
+
const announceOnChange = [title, description, action?.label].filter(Boolean).join('|');
|
|
89
90
|
const statusIconSentiment =
|
|
90
91
|
sentiment === 'success'
|
|
91
92
|
? Sentiment.POSITIVE
|
|
@@ -124,7 +125,7 @@ export const InfoPrompt = ({
|
|
|
124
125
|
|
|
125
126
|
// Render content directly in LiveRegion
|
|
126
127
|
return (
|
|
127
|
-
<LiveRegion aria-live={ariaLive}>
|
|
128
|
+
<LiveRegion aria-live={ariaLive} announceOnChange={announceOnChange}>
|
|
128
129
|
<PrimitivePrompt
|
|
129
130
|
sentiment={sentiment}
|
|
130
131
|
media={renderMedia()}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import { userEvent, within } from 'storybook/test';
|
|
3
|
-
|
|
4
2
|
import Button from '../button';
|
|
5
|
-
|
|
6
3
|
import Tooltip from './Tooltip';
|
|
7
|
-
import { withVariantConfig } from '../../.storybook/helpers';
|
|
8
4
|
|
|
9
5
|
export default {
|
|
10
6
|
component: Tooltip,
|
|
@@ -25,20 +21,3 @@ export default {
|
|
|
25
21
|
type Story = StoryObj<typeof Tooltip>;
|
|
26
22
|
|
|
27
23
|
export const Basic: Story = {};
|
|
28
|
-
|
|
29
|
-
export const OpenedTooltip: Story = {
|
|
30
|
-
parameters: {
|
|
31
|
-
chromatic: {
|
|
32
|
-
delay: 1000,
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
play: async ({ canvasElement }) => {
|
|
36
|
-
const canvas = within(canvasElement);
|
|
37
|
-
await userEvent.hover(canvas.getByRole('button'));
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const OpenedTooltipDark: Story = {
|
|
42
|
-
...OpenedTooltip,
|
|
43
|
-
...withVariantConfig(['dark'], OpenedTooltip),
|
|
44
|
-
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { userEvent, within } from 'storybook/test';
|
|
3
|
+
import Button from '../button';
|
|
4
|
+
import Tooltip from './Tooltip';
|
|
5
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Tooltip> = {
|
|
8
|
+
component: Tooltip,
|
|
9
|
+
tags: ['!autodocs', '!manifest'],
|
|
10
|
+
title: 'Dialogs/Tooltip/Tests',
|
|
11
|
+
args: {
|
|
12
|
+
label: 'This is the content',
|
|
13
|
+
position: 'bottom',
|
|
14
|
+
},
|
|
15
|
+
render: (args) => (
|
|
16
|
+
<div className="text-xs-center" style={{ flexBasis: '150px' }}>
|
|
17
|
+
<Tooltip {...args}>
|
|
18
|
+
<Button>Hover me</Button>
|
|
19
|
+
</Tooltip>
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof Tooltip>;
|
|
26
|
+
|
|
27
|
+
export const OpenedTooltip: Story = {
|
|
28
|
+
parameters: {
|
|
29
|
+
chromatic: {
|
|
30
|
+
delay: 1000,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
play: async ({ canvasElement }) => {
|
|
34
|
+
const canvas = within(canvasElement);
|
|
35
|
+
await userEvent.hover(canvas.getByRole('button'));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const OpenedTooltipDark: Story = {
|
|
40
|
+
...OpenedTooltip,
|
|
41
|
+
...withVariantConfig(['dark'], OpenedTooltip),
|
|
42
|
+
};
|