@dhis2-ui/button 10.16.2 → 10.16.3

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 (60) hide show
  1. package/package.json +8 -7
  2. package/src/button/__tests__/Button.test.js +126 -0
  3. package/src/button/button.e2e.stories.js +24 -0
  4. package/src/button/button.js +162 -0
  5. package/src/button/button.prod.stories.js +305 -0
  6. package/src/button/button.styles.js +298 -0
  7. package/src/button/features/can_be_blurred/index.js +19 -0
  8. package/src/button/features/can_be_blurred.feature +6 -0
  9. package/src/button/features/can_be_clicked/index.js +18 -0
  10. package/src/button/features/can_be_clicked.feature +6 -0
  11. package/src/button/features/can_be_focused/index.js +18 -0
  12. package/src/button/features/can_be_focused.feature +6 -0
  13. package/src/button/index.js +1 -0
  14. package/src/button-strip/button-strip.e2e.stories.js +13 -0
  15. package/src/button-strip/button-strip.js +58 -0
  16. package/src/button-strip/button-strip.prod.stories.js +88 -0
  17. package/src/button-strip/features/accepts_children/index.js +10 -0
  18. package/src/button-strip/features/accepts_children.feature +5 -0
  19. package/src/button-strip/index.js +1 -0
  20. package/src/dropdown-button/__tests__/dropdown-button.test.js +135 -0
  21. package/src/dropdown-button/dropdown-button.e2e.stories.js +67 -0
  22. package/src/dropdown-button/dropdown-button.js +239 -0
  23. package/src/dropdown-button/dropdown-button.prod.stories.js +129 -0
  24. package/src/dropdown-button/features/accepts_children/index.js +10 -0
  25. package/src/dropdown-button/features/accepts_children.feature +5 -0
  26. package/src/dropdown-button/features/accepts_component/index.js +18 -0
  27. package/src/dropdown-button/features/accepts_component.feature +5 -0
  28. package/src/dropdown-button/features/accepts_icon/index.js +10 -0
  29. package/src/dropdown-button/features/accepts_icon.feature +5 -0
  30. package/src/dropdown-button/features/accepts_initial_focus/index.js +11 -0
  31. package/src/dropdown-button/features/accepts_initial_focus.feature +5 -0
  32. package/src/dropdown-button/features/button_is_clickable/index.js +15 -0
  33. package/src/dropdown-button/features/button_is_clickable.feature +6 -0
  34. package/src/dropdown-button/features/can_be_disabled/index.js +11 -0
  35. package/src/dropdown-button/features/can_be_disabled.feature +6 -0
  36. package/src/dropdown-button/features/common/index.js +5 -0
  37. package/src/dropdown-button/features/opens_a_dropdown/index.js +26 -0
  38. package/src/dropdown-button/features/opens_a_dropdown.feature +11 -0
  39. package/src/dropdown-button/index.js +1 -0
  40. package/src/index.js +4 -0
  41. package/src/locales/en/translations.json +3 -0
  42. package/src/locales/index.js +16 -0
  43. package/src/split-button/features/accepts_children/index.js +12 -0
  44. package/src/split-button/features/accepts_children.feature +5 -0
  45. package/src/split-button/features/accepts_icon/index.js +16 -0
  46. package/src/split-button/features/accepts_icon.feature +5 -0
  47. package/src/split-button/features/accepts_initial_focus/index.js +13 -0
  48. package/src/split-button/features/accepts_initial_focus.feature +5 -0
  49. package/src/split-button/features/arrow_opens_menu/index.js +34 -0
  50. package/src/split-button/features/arrow_opens_menu.feature +15 -0
  51. package/src/split-button/features/button_is_clickable/index.js +13 -0
  52. package/src/split-button/features/button_is_clickable.feature +6 -0
  53. package/src/split-button/features/can_be_disabled/index.js +25 -0
  54. package/src/split-button/features/can_be_disabled.feature +11 -0
  55. package/src/split-button/features/common/index.js +9 -0
  56. package/src/split-button/index.js +1 -0
  57. package/src/split-button/split-button.e2e.stories.js +56 -0
  58. package/src/split-button/split-button.js +273 -0
  59. package/src/split-button/split-button.prod.stories.js +162 -0
  60. package/src/split-button/split-button.test.js +84 -0
@@ -0,0 +1,298 @@
1
+ import { colors, theme, spacers } from '@dhis2/ui-constants'
2
+ import css from 'styled-jsx/css'
3
+
4
+ export default css`
5
+ button {
6
+ display: inline-flex;
7
+ position: relative;
8
+ align-items: center;
9
+ justify-content: center;
10
+ border-radius: 4px;
11
+ font-weight: 400;
12
+ letter-spacing: 0.5px;
13
+ text-decoration: none;
14
+ cursor: pointer;
15
+ user-select: none;
16
+ color: ${colors.grey900};
17
+
18
+ /*medium*/
19
+ height: 36px;
20
+ padding: 0 ${spacers.dp12};
21
+ font-size: 14px;
22
+ line-height: 16px;
23
+
24
+ /*basic*/
25
+ border: 1px solid ${colors.grey500};
26
+ background-color: #f9fafb;
27
+ }
28
+
29
+ button:disabled {
30
+ cursor: not-allowed;
31
+ }
32
+
33
+ button:focus {
34
+ outline: 3px solid ${theme.focus};
35
+ outline-offset: -3px;
36
+ text-decoration: underline;
37
+ }
38
+
39
+ /* Prevent focus styles when mouse clicking */
40
+ button:focus:not(:focus-visible) {
41
+ outline: none;
42
+ text-decoration: none;
43
+ }
44
+
45
+ /* Prevent focus styles on active and disabled buttons */
46
+ button:active:focus,
47
+ button:disabled:focus {
48
+ outline: none;
49
+ text-decoration: none;
50
+ }
51
+
52
+ button:hover {
53
+ border-color: ${colors.grey500};
54
+ background-color: ${colors.grey200};
55
+ }
56
+
57
+ button:active,
58
+ button:active:focus {
59
+ border-color: ${colors.grey500};
60
+ background-color: ${colors.grey200};
61
+ box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.1) inset;
62
+ }
63
+
64
+ button:focus {
65
+ background-color: #f9fafb;
66
+ }
67
+
68
+ button:disabled {
69
+ border-color: ${colors.grey400};
70
+ background-color: #f9fafb;
71
+ box-shadow: none;
72
+ color: ${theme.disabled};
73
+ fill: ${theme.disabled};
74
+ }
75
+
76
+ .small {
77
+ height: 28px;
78
+ padding: 0 6px;
79
+ font-size: 14px;
80
+ line-height: 16px;
81
+ }
82
+
83
+ .large {
84
+ height: 43px;
85
+ padding: 0 ${spacers.dp24};
86
+ font-size: 16px;
87
+ letter-spacing: 0.57px;
88
+ line-height: 19px;
89
+ }
90
+
91
+ .primary {
92
+ border-color: ${theme.primary800};
93
+ background: linear-gradient(180deg, #1565c0 0%, #0650a3 100%);
94
+ background-color: #2b61b3;
95
+ color: ${colors.white};
96
+ fill: ${colors.white};
97
+ font-weight: 500;
98
+ }
99
+
100
+ .primary:hover {
101
+ border-color: ${theme.primary800};
102
+ background: linear-gradient(180deg, #054fa3 0%, #034793 100%);
103
+ background-color: #21539f;
104
+ }
105
+
106
+ .primary:active,
107
+ .primary:active:focus {
108
+ background: linear-gradient(180deg, #054fa3 0%, #034793 100%);
109
+ background-color: #1c4a90;
110
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18) inset;
111
+ }
112
+
113
+ .primary:focus {
114
+ background: ${colors.blue800};
115
+ border-color: ${colors.blue900};
116
+ outline-offset: -5px;
117
+ }
118
+
119
+ .primary:disabled {
120
+ border-color: #93a6bd;
121
+ background: #b3c6de;
122
+ box-shadow: none;
123
+ color: ${colors.white};
124
+ fill: ${colors.white};
125
+ }
126
+
127
+ .secondary {
128
+ border-color: rgba(74, 87, 104, 0.25);
129
+ background-color: transparent;
130
+ }
131
+
132
+ .secondary:hover {
133
+ border-color: rgba(74, 87, 104, 0.5);
134
+ background-color: rgba(160, 173, 186, 0.05);
135
+ }
136
+
137
+ .secondary:active,
138
+ .secondary:active:focus {
139
+ background-color: rgba(160, 173, 186, 0.2);
140
+ box-shadow: none;
141
+ }
142
+
143
+ .secondary:focus {
144
+ background-color: transparent;
145
+ }
146
+
147
+ .secondary:disabled {
148
+ border-color: rgba(74, 87, 104, 0.25);
149
+ background-color: transparent;
150
+ box-shadow: none;
151
+ color: ${theme.disabled};
152
+ fill: ${theme.disabled};
153
+ }
154
+
155
+ .destructive {
156
+ border-color: #a10b0b;
157
+ background: linear-gradient(180deg, #d32f2f 0%, #b71c1c 100%);
158
+ background-color: #b9242b;
159
+ color: ${colors.white};
160
+ fill: ${colors.white};
161
+ font-weight: 500;
162
+ }
163
+
164
+ .destructive:hover {
165
+ border-color: #a10b0b;
166
+ background: linear-gradient(180deg, #b81c1c 0%, #b80c0b 100%);
167
+ background-color: #ac0f1a;
168
+ }
169
+
170
+ .destructive:active,
171
+ .destructive:active:focus {
172
+ background: linear-gradient(180deg, #b81c1c 0%, #b80c0b 100%);
173
+ background-color: #ac101b;
174
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18) inset;
175
+ }
176
+
177
+ .destructive:focus {
178
+ background: linear-gradient(180deg, #d32f2f 0%, #b71c1c 100%);
179
+ background-color: #b72229;
180
+ }
181
+
182
+ .destructive:disabled {
183
+ border-color: #c59898;
184
+ background: #d6a8a8;
185
+ box-shadow: none;
186
+ color: ${colors.white};
187
+ fill: ${colors.white};
188
+ }
189
+
190
+ .destructive.secondary {
191
+ border-color: rgba(74, 87, 104, 0.25);
192
+ background: transparent;
193
+ color: ${colors.red700};
194
+ fill: ${colors.red700};
195
+ font-weight: 400;
196
+ }
197
+
198
+ .destructive.secondary:hover {
199
+ border-color: ${colors.red600};
200
+ background: ${colors.red050};
201
+ color: ${colors.red800};
202
+ fill: ${colors.red800};
203
+ }
204
+
205
+ .destructive.secondary:active,
206
+ .destructive.secondary:active:focus {
207
+ background: ${colors.red100};
208
+ border-color: ${colors.red700};
209
+ box-shadow: none;
210
+ }
211
+
212
+ .destructive.secondary:disabled {
213
+ background: transparent;
214
+ border-color: rgba(74, 87, 104, 0.25);
215
+ color: rgba(183, 28, 28, 0.6);
216
+ fill: rgba(183, 28, 28, 0.6);
217
+ }
218
+
219
+ .icon-only {
220
+ padding: 0 0 0 5px;
221
+ }
222
+
223
+ .button-icon {
224
+ margin-inline-end: 6px;
225
+ color: inherit;
226
+ fill: inherit;
227
+ display: inline-flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ pointer-events: none;
231
+ }
232
+
233
+ .icon-only .button-icon {
234
+ margin-inline-end: 5px;
235
+ }
236
+
237
+ .small.icon-only {
238
+ padding-block: 0;
239
+ padding-inline-start: 5px;
240
+ padding-inline-end: 4px;
241
+ }
242
+
243
+ .small .button-icon {
244
+ margin-inline-end: 2px;
245
+ }
246
+
247
+ .small.icon-only .button-icon {
248
+ margin-inline-end: 1px;
249
+ }
250
+
251
+ .toggled {
252
+ background: ${colors.grey700};
253
+ border: 1px solid ${colors.grey900};
254
+ color: ${colors.grey050};
255
+ fill: ${colors.grey050};
256
+ }
257
+
258
+ .toggled:focus {
259
+ background: ${colors.grey800};
260
+ }
261
+
262
+ .toggled:hover {
263
+ background: ${colors.grey800};
264
+ border-color: ${colors.grey900};
265
+ }
266
+
267
+ .toggled:active,
268
+ .toggled:active:focus {
269
+ background: ${colors.grey900};
270
+ border-color: ${colors.grey900};
271
+ }
272
+
273
+ .toggled:disabled {
274
+ background: ${colors.grey500};
275
+ border-color: ${colors.grey600};
276
+ color: ${colors.grey050};
277
+ fill: ${colors.grey050};
278
+ }
279
+
280
+ .loader {
281
+ width: 16px;
282
+ height: 16px;
283
+ margin-inline-end: 8px;
284
+ }
285
+
286
+ .loader + .button-icon {
287
+ display: none;
288
+ }
289
+
290
+ .icon-only .loader {
291
+ margin: 0 8px 0 4px;
292
+ margin-inline-start: 4px;
293
+ margin-inline-end: 8px;
294
+ }
295
+ .small.icon-only .loader {
296
+ margin: 0 4px;
297
+ }
298
+ `
@@ -0,0 +1,19 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('an Button with initialFocus and onBlur handler is rendered', () => {
4
+ cy.visitStory('Button', 'With initial focus and on blur')
5
+ cy.focused().should('exist')
6
+ })
7
+
8
+ When('the Button is blurred', () => {
9
+ cy.get('[data-test="dhis2-uicore-button"]').blur()
10
+ })
11
+
12
+ Then('the onBlur handler is called', () => {
13
+ cy.window().should((win) => {
14
+ expect(win.onBlur).to.be.calledWith({
15
+ value: 'default',
16
+ name: 'Button',
17
+ })
18
+ })
19
+ })
@@ -0,0 +1,6 @@
1
+ Feature: The Button has an onBlur api
2
+
3
+ Scenario: The user blurs the Button
4
+ Given an Button with initialFocus and onBlur handler is rendered
5
+ When the Button is blurred
6
+ Then the onBlur handler is called
@@ -0,0 +1,18 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a Button with onClick handler is rendered', () => {
4
+ cy.visitStory('Button', 'With on click')
5
+ })
6
+
7
+ When('the Button is clicked', () => {
8
+ cy.get('[data-test="dhis2-uicore-button"]').click()
9
+ })
10
+
11
+ Then('the onClick handler is called', () => {
12
+ cy.window().should((win) => {
13
+ expect(win.onClick).to.be.calledWith({
14
+ name: 'Button',
15
+ value: 'default',
16
+ })
17
+ })
18
+ })
@@ -0,0 +1,6 @@
1
+ Feature: The button has an onClick api
2
+
3
+ Scenario: The user clicks on the Button
4
+ Given a Button with onClick handler is rendered
5
+ When the Button is clicked
6
+ Then the onClick handler is called
@@ -0,0 +1,18 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a Button with onFocus handler is rendered', () => {
4
+ cy.visitStory('Button', 'With on focus')
5
+ })
6
+
7
+ When('the Button is focused', () => {
8
+ cy.get('[data-test="dhis2-uicore-button"]').focus()
9
+ })
10
+
11
+ Then('the onFocus handler is called', () => {
12
+ cy.window().should((win) => {
13
+ expect(win.onFocus).to.be.calledWith({
14
+ value: 'default',
15
+ name: 'Button',
16
+ })
17
+ })
18
+ })
@@ -0,0 +1,6 @@
1
+ Feature: The Button has an onFocus api
2
+
3
+ Scenario: The user focuses the Button
4
+ Given a Button with onFocus handler is rendered
5
+ When the Button is focused
6
+ Then the onFocus handler is called
@@ -0,0 +1 @@
1
+ export { Button } from './button.js'
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import { Button } from '../index.js'
3
+ import { ButtonStrip } from './index.js'
4
+
5
+ export default { title: 'ButtonStrip' }
6
+
7
+ export const WithChildren = () => (
8
+ <ButtonStrip>
9
+ <Button>I am a child</Button>
10
+ <Button>I am a child</Button>
11
+ <Button>I am a child</Button>
12
+ </ButtonStrip>
13
+ )
@@ -0,0 +1,58 @@
1
+ import { mutuallyExclusive } from '@dhis2/prop-types'
2
+ import { spacers } from '@dhis2/ui-constants'
3
+ import cx from 'classnames'
4
+ import PropTypes from 'prop-types'
5
+ import React, { Children } from 'react'
6
+
7
+ const ButtonStrip = ({
8
+ className,
9
+ children,
10
+ middle,
11
+ end,
12
+ dataTest = 'dhis2-uicore-buttonstrip',
13
+ }) => (
14
+ <div
15
+ className={cx(className, { start: !middle && !end, middle, end })}
16
+ data-test={dataTest}
17
+ >
18
+ {Children.map(children, (child) => (
19
+ <div className="box">{child}</div>
20
+ ))}
21
+
22
+ <style jsx>{`
23
+ div {
24
+ display: flex;
25
+ }
26
+
27
+ div.middle {
28
+ justify-content: center;
29
+ }
30
+
31
+ div.end {
32
+ justify-content: flex-end;
33
+ }
34
+
35
+ .box {
36
+ margin-inline-start: ${spacers.dp8};
37
+ }
38
+
39
+ .box:first-child {
40
+ margin-inline-start: 0;
41
+ }
42
+ `}</style>
43
+ </div>
44
+ )
45
+
46
+ const alignmentPropType = mutuallyExclusive(['middle', 'end'], PropTypes.bool)
47
+
48
+ ButtonStrip.propTypes = {
49
+ children: PropTypes.node,
50
+ className: PropTypes.string,
51
+ dataTest: PropTypes.string,
52
+ /** Horizontal alignment for buttons. Mutually exclusive with `middle` prop */
53
+ end: alignmentPropType,
54
+ /** Horizontal alignment. Mutually exclusive with `end` prop */
55
+ middle: alignmentPropType,
56
+ }
57
+
58
+ export { ButtonStrip }
@@ -0,0 +1,88 @@
1
+ import React from 'react'
2
+ import { Button, SplitButton } from '../index.js'
3
+ import { ButtonStrip } from './index.js'
4
+
5
+ const description = `
6
+ A wrapper for buttons to add spacing and alignment.
7
+
8
+ \`\`\`js
9
+ import { ButtonStrip } from '@dhis2/ui'
10
+ \`\`\`
11
+ `
12
+
13
+ const Wrapper = (fn) => (
14
+ <div
15
+ style={{
16
+ width: '100%',
17
+ border: '1px solid #c4c9cc',
18
+ padding: 8,
19
+ }}
20
+ >
21
+ {fn()}
22
+ </div>
23
+ )
24
+
25
+ const alignmentArgType = {
26
+ table: {
27
+ type: {
28
+ summary: 'bool',
29
+ detail: "'middle' and 'end' are mutually exclusive props",
30
+ },
31
+ },
32
+ control: { type: 'boolean' },
33
+ }
34
+
35
+ export default {
36
+ title: 'Button Strip',
37
+ component: ButtonStrip,
38
+ decorators: [Wrapper],
39
+ parameters: { docs: { description: { component: description } } },
40
+ argTypes: {
41
+ middle: { ...alignmentArgType },
42
+ end: { ...alignmentArgType },
43
+ },
44
+ }
45
+
46
+ export const Default = (args) => (
47
+ <ButtonStrip {...args}>
48
+ <Button>Save</Button>
49
+ <Button>Save</Button>
50
+ <Button>Save</Button>
51
+ <Button>Save</Button>
52
+ <SplitButton>Label?</SplitButton>
53
+ </ButtonStrip>
54
+ )
55
+
56
+ export const DefaultAlignedMiddle = (args) => (
57
+ <ButtonStrip {...args}>
58
+ <Button>Save</Button>
59
+ <Button>Save</Button>
60
+ <Button>Save</Button>
61
+ <Button>Save</Button>
62
+ </ButtonStrip>
63
+ )
64
+ DefaultAlignedMiddle.args = { middle: true }
65
+ DefaultAlignedMiddle.storyName = 'Default - aligned middle'
66
+
67
+ export const DefaultAlignedRight = (args) => (
68
+ <ButtonStrip {...args}>
69
+ <Button>Save</Button>
70
+ <Button>Save</Button>
71
+ <Button>Save</Button>
72
+ <Button>Save</Button>
73
+ </ButtonStrip>
74
+ )
75
+ DefaultAlignedRight.args = { end: true }
76
+ DefaultAlignedRight.storyName = 'Default - aligned right'
77
+
78
+ export const DefaultRTL = (args) => (
79
+ <div dir="rtl">
80
+ <ButtonStrip {...args}>
81
+ <Button>Save</Button>
82
+ <Button>Save</Button>
83
+ <Button>Save</Button>
84
+ <Button>Save</Button>
85
+ </ButtonStrip>
86
+ </div>
87
+ )
88
+ DefaultRTL.storyName = 'Default - RTL'
@@ -0,0 +1,10 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a ButtonStrip with children is rendered', () => {
4
+ cy.visitStory('ButtonStrip', 'With children')
5
+ cy.get('[data-test="dhis2-uicore-buttonstrip"]').should('be.visible')
6
+ })
7
+
8
+ Then('the children are visible', () => {
9
+ cy.contains('I am a child').should('be.visible')
10
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The ButtonStrip renders children
2
+
3
+ Scenario: A ButtonStrip with children
4
+ Given a ButtonStrip with children is rendered
5
+ Then the children are visible
@@ -0,0 +1 @@
1
+ export { ButtonStrip } from './button-strip.js'
@@ -0,0 +1,135 @@
1
+ import { Layer } from '@dhis2-ui/layer'
2
+ import { Popper } from '@dhis2-ui/popper'
3
+ import { render, fireEvent, waitFor } from '@testing-library/react'
4
+ import { mount } from 'enzyme'
5
+ import React from 'react'
6
+ import { act } from 'react-dom/test-utils'
7
+ import { Modal } from '../../../../modal/src/modal/modal.js'
8
+ import { Button } from '../../index.js'
9
+ import { DropdownButton } from '../dropdown-button.js'
10
+
11
+ describe('<DropdownButton>', () => {
12
+ describe('controlled mode', () => {
13
+ describe('open state', () => {
14
+ const onClick = jest.fn()
15
+ const Component = (
16
+ <DropdownButton
17
+ onClick={onClick}
18
+ open={true}
19
+ component={<span>test</span>}
20
+ />
21
+ )
22
+ it('shows the Popper when open is true', async () => {
23
+ // TODO: https://github.com/popperjs/react-popper/issues/350
24
+ const wrapper = mount(Component)
25
+ await act(async () => await null)
26
+
27
+ const popper = wrapper.find(Popper)
28
+ expect(popper).toHaveLength(1)
29
+ expect(popper.find('span').text()).toEqual('test')
30
+ })
31
+ it('passes an "open" property to the callback payload with the next open state', async () => {
32
+ // TODO: https://github.com/popperjs/react-popper/issues/350
33
+ const wrapper = mount(Component)
34
+ await act(async () => await null)
35
+
36
+ wrapper.find(Layer).find('.backdrop').simulate('click')
37
+ expect(onClick).toHaveBeenCalledTimes(1)
38
+
39
+ const args = onClick.mock.calls[0]
40
+
41
+ expect(args).toHaveLength(2)
42
+ expect(args[0]).toEqual(
43
+ expect.objectContaining({
44
+ open: false,
45
+ })
46
+ )
47
+ })
48
+ it('closes dropdown when escape key is pressed', async () => {
49
+ const componentText = 'Dropdown Content'
50
+
51
+ const { getByTestId, queryByText } = render(
52
+ <DropdownButton component={componentText} />
53
+ )
54
+
55
+ const toggleButton = getByTestId(
56
+ 'dhis2-uicore-dropdownbutton-toggle'
57
+ )
58
+ fireEvent.click(toggleButton)
59
+
60
+ await waitFor(() => {
61
+ expect(queryByText(componentText)).toBeInTheDocument()
62
+ })
63
+
64
+ fireEvent.keyDown(document, { key: 'Escape' })
65
+
66
+ await waitFor(() => {
67
+ expect(queryByText(componentText)).not.toBeInTheDocument()
68
+ })
69
+ })
70
+ test('modal remains open when dropdown button is closed on escape click', async () => {
71
+ const dropdownButtonText = 'Dropdown Content'
72
+ const headingText = 'Heading Text'
73
+ const modalContent = (
74
+ <div>
75
+ <h1>{headingText}</h1>
76
+ <DropdownButton component={dropdownButtonText} />
77
+ </div>
78
+ )
79
+
80
+ const { getByTestId, queryByText } = render(
81
+ <Modal hide={false} onClose={() => {}}>
82
+ {modalContent}
83
+ </Modal>
84
+ )
85
+
86
+ const toggleButton = getByTestId(
87
+ 'dhis2-uicore-dropdownbutton-toggle'
88
+ )
89
+ fireEvent.click(toggleButton)
90
+
91
+ await waitFor(() => {
92
+ expect(queryByText(dropdownButtonText)).toBeInTheDocument()
93
+ })
94
+
95
+ fireEvent.keyDown(document, { key: 'Escape' })
96
+
97
+ await waitFor(() => {
98
+ expect(
99
+ queryByText(dropdownButtonText)
100
+ ).not.toBeInTheDocument()
101
+ expect(queryByText(headingText)).toBeInTheDocument()
102
+ })
103
+ })
104
+ })
105
+
106
+ describe('closed state', () => {
107
+ const onClick = jest.fn()
108
+ const wrapper = mount(
109
+ <DropdownButton
110
+ onClick={onClick}
111
+ open={false}
112
+ component={<span>test</span>}
113
+ />
114
+ )
115
+
116
+ it('it does not show the Popper when open is false', () => {
117
+ const popper = wrapper.find(Popper)
118
+ expect(popper).toHaveLength(0)
119
+ })
120
+ it('passes an "open" property to the callback payload with the next open state (false)', () => {
121
+ wrapper.find(Button).simulate('click')
122
+ expect(onClick).toHaveBeenCalledTimes(1)
123
+
124
+ const args = onClick.mock.calls[0]
125
+
126
+ expect(args).toHaveLength(2)
127
+ expect(args[0]).toEqual(
128
+ expect.objectContaining({
129
+ open: true,
130
+ })
131
+ )
132
+ })
133
+ })
134
+ })
135
+ })