@dhis2/analytics 25.1.21 → 25.2.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.
Files changed (58) hide show
  1. package/build/cjs/__demo__/FileMenu.stories.js +8 -6
  2. package/build/cjs/__demo__/Toolbar.stories.js +77 -0
  3. package/build/cjs/components/FileMenu/FileMenu.js +21 -59
  4. package/build/cjs/components/FileMenu/__tests__/FileMenu.spec.js +318 -194
  5. package/build/cjs/components/Options/VisualizationOptions.js +3 -1
  6. package/build/cjs/components/Toolbar/HoverMenuBar/HoverMenuBar.js +107 -0
  7. package/build/cjs/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js +66 -0
  8. package/build/cjs/components/Toolbar/HoverMenuBar/HoverMenuList.js +94 -0
  9. package/build/cjs/components/Toolbar/HoverMenuBar/HoverMenuListItem.js +99 -0
  10. package/build/cjs/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js +13 -0
  11. package/build/cjs/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js +219 -0
  12. package/build/cjs/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js +23 -0
  13. package/build/cjs/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js +56 -0
  14. package/build/cjs/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js +50 -0
  15. package/build/cjs/components/Toolbar/HoverMenuBar/index.js +37 -0
  16. package/build/cjs/components/Toolbar/InterpretationsAndDetailsToggler.js +50 -0
  17. package/build/cjs/components/Toolbar/MenuButton.styles.js +13 -0
  18. package/build/cjs/components/Toolbar/Toolbar.js +39 -0
  19. package/build/cjs/components/Toolbar/ToolbarSidebar.js +45 -0
  20. package/build/cjs/components/Toolbar/UpdateButton.js +57 -0
  21. package/build/cjs/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js +50 -0
  22. package/build/cjs/components/Toolbar/__tests__/Toolbar.spec.js +24 -0
  23. package/build/cjs/components/Toolbar/__tests__/ToolbarSidebar.spec.js +30 -0
  24. package/build/cjs/components/Toolbar/__tests__/UpdateButton.spec.js +44 -0
  25. package/build/cjs/components/Toolbar/index.js +57 -0
  26. package/build/cjs/index.js +304 -46
  27. package/build/cjs/locales/en/translations.json +1 -0
  28. package/build/cjs/locales/es/translations.json +48 -48
  29. package/build/es/__demo__/FileMenu.stories.js +7 -6
  30. package/build/es/__demo__/Toolbar.stories.js +69 -0
  31. package/build/es/components/FileMenu/FileMenu.js +20 -57
  32. package/build/es/components/FileMenu/__tests__/FileMenu.spec.js +293 -189
  33. package/build/es/components/Options/VisualizationOptions.js +3 -1
  34. package/build/es/components/Toolbar/HoverMenuBar/HoverMenuBar.js +90 -0
  35. package/build/es/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js +44 -0
  36. package/build/es/components/Toolbar/HoverMenuBar/HoverMenuList.js +75 -0
  37. package/build/es/components/Toolbar/HoverMenuBar/HoverMenuListItem.js +78 -0
  38. package/build/es/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js +4 -0
  39. package/build/es/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js +168 -0
  40. package/build/es/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js +16 -0
  41. package/build/es/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js +49 -0
  42. package/build/es/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js +41 -0
  43. package/build/es/components/Toolbar/HoverMenuBar/index.js +4 -0
  44. package/build/es/components/Toolbar/InterpretationsAndDetailsToggler.js +33 -0
  45. package/build/es/components/Toolbar/MenuButton.styles.js +4 -0
  46. package/build/es/components/Toolbar/Toolbar.js +24 -0
  47. package/build/es/components/Toolbar/ToolbarSidebar.js +29 -0
  48. package/build/es/components/Toolbar/UpdateButton.js +38 -0
  49. package/build/es/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js +43 -0
  50. package/build/es/components/Toolbar/__tests__/Toolbar.spec.js +17 -0
  51. package/build/es/components/Toolbar/__tests__/ToolbarSidebar.spec.js +23 -0
  52. package/build/es/components/Toolbar/__tests__/UpdateButton.spec.js +37 -0
  53. package/build/es/components/Toolbar/index.js +5 -0
  54. package/build/es/index.js +1 -0
  55. package/build/es/locales/en/translations.json +1 -0
  56. package/build/es/locales/es/translations.json +48 -48
  57. package/package.json +3 -1
  58. package/CHANGELOG.md +0 -4037
@@ -1,16 +1,19 @@
1
- import { SharingDialog } from '@dhis2/ui';
2
- import { shallow } from 'enzyme';
1
+ import { CustomDataProvider } from '@dhis2/app-runtime';
2
+ import { render, fireEvent, screen, getByText } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
3
4
  import React from 'react';
4
- import { OpenFileDialog } from '../../OpenFileDialog/OpenFileDialog.js';
5
- import { TranslationDialog } from '../../TranslationDialog/index.js';
6
- import { DeleteDialog } from '../DeleteDialog.js';
5
+ import { HoverMenuBar } from '../../Toolbar/index.js';
7
6
  import { FileMenu } from '../FileMenu.js';
8
- import { GetLinkDialog } from '../GetLinkDialog.js';
9
- import { RenameDialog } from '../RenameDialog.js';
10
- import { SaveAsDialog } from '../SaveAsDialog.js';
7
+ jest.mock('../../TranslationDialog/TranslationModal/useTranslationsResults.js', () => ({
8
+ /* This will keep the translation dialog in
9
+ * a loading state, which prevents it from
10
+ * throwing other errors */
11
+ useTranslationsResults: () => ({
12
+ translationsData: undefined,
13
+ fetching: true
14
+ })
15
+ }));
11
16
  describe('The FileMenu component ', () => {
12
- let shallowFileMenu;
13
- let props;
14
17
  const onDelete = jest.fn();
15
18
  const onError = jest.fn();
16
19
  const onNew = jest.fn();
@@ -20,206 +23,307 @@ describe('The FileMenu component ', () => {
20
23
  const onSaveAs = jest.fn();
21
24
  const onShare = jest.fn();
22
25
  const onTranslate = jest.fn();
23
-
24
- const getFileMenuComponent = props => {
25
- if (!shallowFileMenu) {
26
- shallowFileMenu = shallow( /*#__PURE__*/React.createElement(FileMenu, props));
27
- }
28
-
29
- return shallowFileMenu;
26
+ const baseProps = {
27
+ currentUser: {
28
+ id: 'u1',
29
+ displayName: 'Test user'
30
+ },
31
+ fileType: 'visualization',
32
+ fileObject: undefined,
33
+ onDelete,
34
+ onError,
35
+ onNew,
36
+ onOpen,
37
+ onRename,
38
+ onSave,
39
+ onSaveAs,
40
+ onShare,
41
+ onTranslate
30
42
  };
31
-
32
- beforeEach(() => {
33
- shallowFileMenu = undefined;
34
- props = {
35
- currentUser: {
36
- id: 'u1',
37
- displayName: 'Test user'
38
- },
39
- fileType: 'visualization',
40
- fileObject: undefined,
41
- onDelete,
42
- onError,
43
- onNew,
44
- onOpen,
45
- onRename,
46
- onSave,
47
- onSaveAs,
48
- onShare,
49
- onTranslate
50
- };
51
- });
52
- it('renders a button', () => {
53
- expect(getFileMenuComponent(props).find('button')).toHaveLength(1);
54
- });
55
- it('renders some enabled buttons regardless of the access settings', () => {
56
- const fileMenuComponent = getFileMenuComponent(props);
57
- fileMenuComponent.find('button').simulate('click');
58
- const buttonLabels = ['New', 'Open…'];
59
- buttonLabels.forEach(buttonLabel => expect(fileMenuComponent.findWhere(n => n.prop('label') === buttonLabel).prop('disabled')).toBe(undefined));
60
- });
61
- it('renders some disabled buttons when no fileObject is present', () => {
62
- const fileMenuComponent = getFileMenuComponent(props);
63
- fileMenuComponent.find('button').simulate('click');
64
- const buttonLabels = ['Save as…', 'Rename…', 'Translate…', 'Share…', 'Get link…', 'Delete'];
65
- buttonLabels.forEach(buttonLabel => expect(fileMenuComponent.findWhere(n => n.prop('label') === buttonLabel).prop('disabled')).toBe(true));
66
- });
67
- it('renders some enabled buttons when update access is granted', () => {
68
- props.fileObject = {
69
- id: 'test',
70
- access: {
71
- delete: false,
72
- manage: false,
73
- update: true
74
- }
75
- };
76
- const fileMenuComponent = getFileMenuComponent(props);
77
- fileMenuComponent.find('button').simulate('click');
78
- const buttonLabels = ['Save', 'Rename…', 'Translate…'];
79
- buttonLabels.forEach(buttonLabel => expect(fileMenuComponent.findWhere(n => n.prop('label') === buttonLabel).prop('disabled')).toBe(false));
80
- });
81
- it('renders enabled Delete button when delete access is granted', () => {
82
- props.fileObject = {
83
- id: 'test',
84
- access: {
85
- delete: true,
86
- manage: false,
87
- update: false
88
- }
89
- };
90
- const fileMenuComponent = getFileMenuComponent(props);
91
- fileMenuComponent.find('button').simulate('click');
92
- expect(fileMenuComponent.findWhere(n => n.prop('label') === 'Delete').prop('disabled')).toBe(false);
93
- });
94
- it('renders enabled Share button when manage access is granted', () => {
95
- props.fileObject = {
96
- id: 'test',
97
- access: {
98
- delete: false,
99
- manage: true,
100
- update: false
101
- }
102
- };
103
- const fileMenuComponent = getFileMenuComponent(props);
104
- fileMenuComponent.find('button').simulate('click');
105
- expect(fileMenuComponent.findWhere(n => n.prop('label') === 'Share…').prop('disabled')).toBe(false);
106
- });
107
- it('renders the OpenFileDialog component when the Open button is clicked', () => {
108
- const fileMenuComponent = getFileMenuComponent(props);
109
- fileMenuComponent.find('button').simulate('click');
110
- fileMenuComponent.findWhere(n => n.prop('label') === 'Open…').simulate('click');
111
- expect(fileMenuComponent.find(OpenFileDialog)).toHaveLength(1);
112
- });
113
- it('renders the RenameDialog when the Rename button is clicked', () => {
114
- props.fileObject = {
43
+ const fullAccessProps = {
44
+ fileObject: {
115
45
  id: 'test',
116
46
  access: {
117
47
  delete: true,
118
48
  manage: true,
119
49
  update: true
120
- }
50
+ },
51
+ href: 'http://dhis2.org'
52
+ }
53
+ };
54
+
55
+ const renderFileMenu = function () {
56
+ let customProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
57
+ const props = { ...baseProps,
58
+ ...customProps
121
59
  };
122
- const fileMenuComponent = getFileMenuComponent(props);
123
- fileMenuComponent.find('button').simulate('click');
124
- fileMenuComponent.findWhere(n => n.prop('label') === 'Rename…').simulate('click');
125
- expect(fileMenuComponent.find(RenameDialog)).toHaveLength(1);
126
- });
127
- it('renders the TranslationDialog when the Translate button is clicked', () => {
128
- props.fileObject = {
129
- id: 'test',
130
- access: {
131
- delete: true,
132
- manage: true,
133
- update: true
60
+ const providerData = {
61
+ translations: {
62
+ translations: {}
63
+ },
64
+ sharing: {
65
+ meta: {
66
+ allowPublicAccess: true
67
+ },
68
+ object: {
69
+ userAccesses: [],
70
+ userGroupAccesses: []
71
+ }
134
72
  }
135
73
  };
136
- const fileMenuComponent = getFileMenuComponent(props);
137
- fileMenuComponent.find('button').simulate('click');
138
- fileMenuComponent.findWhere(n => n.prop('label') === 'Translate…').simulate('click');
139
- expect(fileMenuComponent.find(TranslationDialog)).toHaveLength(1);
74
+ return render( /*#__PURE__*/React.createElement(CustomDataProvider, {
75
+ data: providerData
76
+ }, /*#__PURE__*/React.createElement(HoverMenuBar, null, /*#__PURE__*/React.createElement(FileMenu, props))));
77
+ };
78
+
79
+ const openDropdown = async () => {
80
+ fireEvent.click(screen.getByTestId('dhis2-analytics-hovermenudropdown'));
81
+ expect(await screen.findByTestId('file-menu-container')).toBeVisible();
82
+ };
83
+
84
+ const MENU_ITEMS = {
85
+ NEW: {
86
+ testId: 'file-menu-new',
87
+ text: 'New'
88
+ },
89
+ OPEN: {
90
+ testId: 'file-menu-open',
91
+ text: 'Open…'
92
+ },
93
+ SAVE: {
94
+ testId: 'file-menu-save',
95
+ text: 'Save'
96
+ },
97
+ SAVE_AS: {
98
+ testId: 'file-menu-saveas',
99
+ text: 'Save as…'
100
+ },
101
+ RENAME: {
102
+ testId: 'file-menu-rename',
103
+ text: 'Rename…'
104
+ },
105
+ TRANSLATE: {
106
+ testId: 'file-menu-translate',
107
+ text: 'Translate…'
108
+ },
109
+ SHARE: {
110
+ testId: 'file-menu-sharing',
111
+ text: 'Share…'
112
+ },
113
+ GET_LINK: {
114
+ testId: 'file-menu-getlink',
115
+ text: 'Get link…'
116
+ },
117
+ DELETE: {
118
+ testId: 'file-menu-delete',
119
+ text: 'Delete'
120
+ }
121
+ };
122
+
123
+ const assertMenuItemsDisabledState = menuItems => {
124
+ for (const menuTitem of menuItems) {
125
+ const li = screen.getByTestId(menuTitem.testId);
126
+ expect(getByText(li, menuTitem.text)).toBeVisible();
127
+
128
+ if (menuTitem.disabled) {
129
+ expect(li).toHaveClass('disabled');
130
+ } else {
131
+ expect(li).not.toHaveClass('disabled');
132
+ }
133
+ }
134
+ };
135
+
136
+ it('renders a button', () => {
137
+ renderFileMenu();
138
+ expect(screen.getAllByTestId('dhis2-analytics-hovermenudropdown')).toHaveLength(1);
139
+ const button = screen.getByTestId('dhis2-analytics-hovermenudropdown');
140
+ expect(button).toBeVisible();
141
+ expect(button).toHaveTextContent('File');
140
142
  });
141
- it('renders the SharingDialog when the Share button is clicked', () => {
142
- props.fileObject = {
143
- id: 'test',
144
- access: {
145
- delete: true,
146
- manage: true,
147
- update: true
143
+ it('opens when clicking the button', async () => {
144
+ renderFileMenu();
145
+ expect(screen.queryByTestId('file-menu-container')).not.toBeInTheDocument();
146
+ await openDropdown();
147
+ expect(await screen.findByTestId('file-menu-container')).toBeVisible();
148
+ });
149
+ it('renders some enabled buttons regardless of the access settings', async () => {
150
+ renderFileMenu();
151
+ await openDropdown();
152
+ assertMenuItemsDisabledState([{ ...MENU_ITEMS.NEW,
153
+ disabled: false
154
+ }, { ...MENU_ITEMS.OPEN,
155
+ disabled: false
156
+ }]);
157
+ });
158
+ it('renders some disabled buttons when no fileObject is present', async () => {
159
+ renderFileMenu();
160
+ await openDropdown();
161
+ assertMenuItemsDisabledState([{ ...MENU_ITEMS.SAVE_AS,
162
+ disabled: true
163
+ }, { ...MENU_ITEMS.RENAME,
164
+ disabled: true
165
+ }, { ...MENU_ITEMS.TRANSLATE,
166
+ disabled: true
167
+ }, { ...MENU_ITEMS.SHARE,
168
+ disabled: true
169
+ }, { ...MENU_ITEMS.GET_LINK,
170
+ disabled: true
171
+ }, { ...MENU_ITEMS.DELETE,
172
+ disabled: true
173
+ }]);
174
+ });
175
+ it('renders some enabled buttons when update access is granted', async () => {
176
+ const customProps = {
177
+ fileObject: {
178
+ id: 'test',
179
+ access: {
180
+ delete: false,
181
+ manage: false,
182
+ update: true
183
+ }
148
184
  }
149
185
  };
150
- const fileMenuComponent = getFileMenuComponent(props);
151
- fileMenuComponent.find('button').simulate('click');
152
- fileMenuComponent.findWhere(n => n.prop('label') === 'Share…').simulate('click');
153
- expect(fileMenuComponent.find(SharingDialog)).toHaveLength(1);
186
+ renderFileMenu(customProps);
187
+ await openDropdown();
188
+ assertMenuItemsDisabledState([{ ...MENU_ITEMS.SAVE,
189
+ disabled: false
190
+ }, { ...MENU_ITEMS.RENAME,
191
+ disabled: false
192
+ }, { ...MENU_ITEMS.TRANSLATE,
193
+ disabled: false
194
+ }]);
154
195
  });
155
- it('renders the GetLinkDialog when the Get link button is clicked', () => {
156
- props.fileObject = {
157
- id: 'test',
158
- access: {
159
- delete: true,
160
- manage: true,
161
- update: true
196
+ it('renders enabled Delete button when delete access is granted', async () => {
197
+ const customProps = {
198
+ fileObject: {
199
+ id: 'test',
200
+ access: {
201
+ delete: true,
202
+ manage: false,
203
+ update: false
204
+ }
162
205
  }
163
206
  };
164
- const fileMenuComponent = getFileMenuComponent(props);
165
- fileMenuComponent.find('button').simulate('click');
166
- fileMenuComponent.findWhere(n => n.prop('label') === 'Get link…').simulate('click');
167
- expect(fileMenuComponent.find(GetLinkDialog)).toHaveLength(1);
168
- });
169
- it('renders the DeleteDialog when the Delete button is clicked', () => {
170
- props.fileObject = {
171
- id: 'delete-test',
172
- access: {
173
- delete: true,
174
- manage: true,
175
- update: true
207
+ renderFileMenu(customProps);
208
+ await openDropdown();
209
+ assertMenuItemsDisabledState([{ ...MENU_ITEMS.DELETE,
210
+ disabled: false
211
+ }]);
212
+ });
213
+ it('renders enabled Share button when manage access is granted', async () => {
214
+ const customProps = {
215
+ fileObject: {
216
+ id: 'test',
217
+ access: {
218
+ delete: false,
219
+ manage: true,
220
+ update: false
221
+ }
176
222
  }
177
223
  };
178
- const fileMenuComponent = getFileMenuComponent(props);
179
- fileMenuComponent.find('button').simulate('click');
180
- fileMenuComponent.findWhere(n => n.prop('label') === 'Delete').simulate('click');
181
- expect(fileMenuComponent.find(DeleteDialog)).toHaveLength(1);
224
+ renderFileMenu(customProps);
225
+ await openDropdown();
226
+ assertMenuItemsDisabledState([{ ...MENU_ITEMS.SHARE,
227
+ disabled: false
228
+ }]);
182
229
  });
183
- it('renders the SaveAsDialog when the Save as… button is clicked', () => {
184
- props.fileObject = {
185
- id: 'test',
186
- access: {
187
- delete: true,
188
- manage: true,
189
- update: true
230
+ it('renders the OpenFileDialog component when the Open button is clicked', async () => {
231
+ renderFileMenu();
232
+ await openDropdown();
233
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.OPEN.testId));
234
+ expect(await screen.findByText('Open a visualization', {
235
+ selector: 'h1'
236
+ })).toBeVisible();
237
+ });
238
+ it('renders the RenameDialog when the Rename button is clicked', async () => {
239
+ renderFileMenu(fullAccessProps);
240
+ await openDropdown();
241
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.RENAME.testId));
242
+ expect(await screen.findByText('Rename visualization', {
243
+ selector: 'h1'
244
+ })).toBeVisible();
245
+ });
246
+ it('renders the TranslationDialog when the Translate button is clicked', async () => {
247
+ renderFileMenu(fullAccessProps);
248
+ await openDropdown();
249
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.TRANSLATE.testId));
250
+ expect(await screen.findByText('Translate', {
251
+ exact: false,
252
+ selector: 'h1'
253
+ })).toBeVisible();
254
+ });
255
+ it('renders the SharingDialog when the Share button is clicked', async () => {
256
+ renderFileMenu(fullAccessProps);
257
+ await openDropdown();
258
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.SHARE.testId));
259
+ expect(await screen.findByText('Sharing and access', {
260
+ selector: 'h1'
261
+ })).toBeVisible();
262
+ });
263
+ it('renders the GetLinkDialog when the Get link button is clicked', async () => {
264
+ const url = 'http://localhost/dhis-web-data-visualizer/#/test';
265
+ renderFileMenu(fullAccessProps);
266
+ await openDropdown();
267
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.GET_LINK.testId));
268
+ expect(await screen.findByTestId('dhis2-uicore-modal')).toBeVisible();
269
+ expect(screen.getByRole('link', {
270
+ name: url
271
+ })).toHaveAttribute('href', url);
272
+ });
273
+ it('renders the DeleteDialog when the Delete button is clicked', async () => {
274
+ const customProps = {
275
+ fileObject: {
276
+ id: 'delete-test',
277
+ access: {
278
+ delete: true,
279
+ manage: true,
280
+ update: true
281
+ }
190
282
  }
191
283
  };
192
- const fileMenuComponent = getFileMenuComponent(props);
193
- fileMenuComponent.find('button').simulate('click');
194
- fileMenuComponent.findWhere(n => n.prop('label') === 'Save as…').simulate('click');
195
- expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1);
196
- });
197
- it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', () => {
198
- const fileMenuComponent = getFileMenuComponent(props);
199
- fileMenuComponent.find('button').simulate('click');
200
- fileMenuComponent.findWhere(n => n.prop('label') === 'Save…').simulate('click');
201
- expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1);
202
- });
203
- it('calls the onSave callback when the Save button is clicked and a fileObject is present', () => {
204
- props.fileObject = {
205
- id: 'test',
206
- access: {
207
- delete: true,
208
- manage: true,
209
- update: true
284
+ renderFileMenu(customProps);
285
+ await openDropdown();
286
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.DELETE.testId));
287
+ expect(await screen.findByText('Delete visualization', {
288
+ selector: 'h1'
289
+ })).toBeVisible();
290
+ });
291
+ it('renders the SaveAsDialog when the Save as… button is clicked', async () => {
292
+ renderFileMenu(fullAccessProps);
293
+ await openDropdown();
294
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE_AS.testId));
295
+ expect(await screen.findByText('Save visualization as', {
296
+ selector: 'h1'
297
+ })).toBeVisible();
298
+ });
299
+ it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', async () => {
300
+ const customProps = {
301
+ fileObject: {
302
+ // NOTE: no `id` field
303
+ access: {
304
+ update: true
305
+ }
210
306
  }
211
307
  };
212
- const fileMenuComponent = getFileMenuComponent(props);
213
- fileMenuComponent.find('button').simulate('click');
214
- fileMenuComponent.findWhere(n => n.prop('label') === 'Save').simulate('click');
215
- expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false);
216
- expect(onSave).toHaveBeenCalled();
217
- });
218
- it('calls the onNew callback when the New button is clicked', () => {
219
- const fileMenuComponent = getFileMenuComponent(props);
220
- fileMenuComponent.find('button').simulate('click');
221
- fileMenuComponent.findWhere(n => n.prop('label') === 'New').simulate('click');
222
- expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false);
223
- expect(onNew).toHaveBeenCalled();
308
+ renderFileMenu(customProps);
309
+ await openDropdown();
310
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId));
311
+ expect(await screen.findByText('Save visualization as', {
312
+ selector: 'h1'
313
+ })).toBeVisible();
314
+ });
315
+ it('calls the onSave callback when the Save button is clicked and a fileObject is present', async () => {
316
+ renderFileMenu(fullAccessProps);
317
+ await openDropdown();
318
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId));
319
+ expect(screen.queryByText('Open a visualization')).not.toBeVisible();
320
+ expect(onSave).toHaveBeenCalledTimes(1);
321
+ });
322
+ it('calls the onNew callback when the New button is clicked', async () => {
323
+ renderFileMenu();
324
+ await openDropdown();
325
+ fireEvent.click(screen.getByTestId(MENU_ITEMS.NEW.testId));
326
+ expect(screen.queryByText('Open a visualization')).not.toBeVisible();
327
+ expect(onNew).toHaveBeenCalledTimes(1);
224
328
  });
225
329
  });
@@ -6,11 +6,12 @@ import { modalContent, tabSection, tabSectionTitle, tabSectionTitleMargin, tabSe
6
6
 
7
7
  const VisualizationOptions = _ref => {
8
8
  let {
9
+ initiallyActiveTabKey,
9
10
  optionsConfig,
10
11
  onClose,
11
12
  onUpdate
12
13
  } = _ref;
13
- const [activeTabKey, setActiveTabKey] = useState();
14
+ const [activeTabKey, setActiveTabKey] = useState(initiallyActiveTabKey);
14
15
 
15
16
  const generateTabContent = sections => sections.map(_ref2 => {
16
17
  let {
@@ -92,6 +93,7 @@ const VisualizationOptions = _ref => {
92
93
 
93
94
  VisualizationOptions.propTypes = {
94
95
  optionsConfig: PropTypes.array.isRequired,
96
+ initiallyActiveTabKey: PropTypes.string,
95
97
  onClose: PropTypes.func,
96
98
  onUpdate: PropTypes.func
97
99
  };
@@ -0,0 +1,90 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ import PropTypes from 'prop-types';
3
+ import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
4
+
5
+ const throwErrorIfNotInitialized = () => {
6
+ throw new Error('`HoverMenubarContext` has not been initialised');
7
+ };
8
+
9
+ const HoverMenubarContext = /*#__PURE__*/createContext({
10
+ closeMenu: throwErrorIfNotInitialized,
11
+ onDropDownButtonClick: throwErrorIfNotInitialized,
12
+ onDropDownButtonMouseOver: throwErrorIfNotInitialized,
13
+ setLastHoveredSubMenuEl: throwErrorIfNotInitialized,
14
+ openedDropdownEl: null
15
+ });
16
+
17
+ const useHoverMenubarContext = () => useContext(HoverMenubarContext);
18
+
19
+ const HoverMenuBar = _ref => {
20
+ let {
21
+ children,
22
+ dataTest
23
+ } = _ref;
24
+ const [openedDropdownEl, setOpenedDropdownEl] = useState(null);
25
+ const [lastHoveredSubMenuEl, setLastHoveredSubMenuEl] = useState(null);
26
+ const [isInHoverMode, setIsInHoverMode] = useState(false);
27
+ const closeMenu = useCallback(() => {
28
+ setIsInHoverMode(false);
29
+ setOpenedDropdownEl(null);
30
+ }, []);
31
+ const onDocumentClick = useCallback(event => {
32
+ const isClickOnOpenedSubMenuAnchor = lastHoveredSubMenuEl && (lastHoveredSubMenuEl === event.target || lastHoveredSubMenuEl.contains(event.target));
33
+
34
+ if (!isClickOnOpenedSubMenuAnchor) {
35
+ closeMenu();
36
+ }
37
+ }, [closeMenu, lastHoveredSubMenuEl]);
38
+ const onDropDownButtonClick = useCallback(event => {
39
+ if (!isInHoverMode) {
40
+ setIsInHoverMode(true);
41
+ setOpenedDropdownEl(event.currentTarget);
42
+ } else {
43
+ closeMenu();
44
+ }
45
+ }, [closeMenu, isInHoverMode]);
46
+ const onDropDownButtonMouseOver = useCallback(event => {
47
+ if (isInHoverMode) {
48
+ setOpenedDropdownEl(event.currentTarget);
49
+ }
50
+ }, [isInHoverMode]);
51
+ const closeMenuWithEsc = useCallback(event => {
52
+ if (event.keyCode === 27) {
53
+ closeMenu();
54
+ }
55
+ }, [closeMenu]);
56
+ useEffect(() => {
57
+ if (isInHoverMode) {
58
+ document.addEventListener('click', onDocumentClick, {
59
+ once: true
60
+ });
61
+ }
62
+
63
+ return () => {
64
+ document.removeEventListener('click', onDocumentClick);
65
+ };
66
+ }, [onDocumentClick, isInHoverMode]);
67
+ return /*#__PURE__*/React.createElement(HoverMenubarContext.Provider, {
68
+ value: {
69
+ onDropDownButtonClick,
70
+ onDropDownButtonMouseOver,
71
+ openedDropdownEl,
72
+ setLastHoveredSubMenuEl
73
+ }
74
+ }, /*#__PURE__*/React.createElement("div", {
75
+ onKeyDown: closeMenuWithEsc,
76
+ "data-test": dataTest,
77
+ className: "jsx-3020154784"
78
+ }, children, /*#__PURE__*/React.createElement(_JSXStyle, {
79
+ id: "3020154784"
80
+ }, [".jsx-3020154784{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}"])));
81
+ };
82
+
83
+ HoverMenuBar.defaultProps = {
84
+ dataTest: 'dhis2-analytics-hovermenubar'
85
+ };
86
+ HoverMenuBar.propTypes = {
87
+ children: PropTypes.node.isRequired,
88
+ dataTest: PropTypes.string
89
+ };
90
+ export { HoverMenuBar, useHoverMenubarContext };
@@ -0,0 +1,44 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ import { Popper } from '@dhis2-ui/popper';
3
+ import { Portal } from '@dhis2-ui/portal';
4
+ import PropTypes from 'prop-types';
5
+ import React, { useRef } from 'react';
6
+ import menuButtonStyles from '../MenuButton.styles.js';
7
+ import { useHoverMenubarContext } from './HoverMenuBar.js';
8
+ export const HoverMenuDropdown = _ref => {
9
+ let {
10
+ children,
11
+ label,
12
+ dataTest,
13
+ disabled
14
+ } = _ref;
15
+ const buttonRef = useRef();
16
+ const {
17
+ onDropDownButtonClick,
18
+ onDropDownButtonMouseOver,
19
+ openedDropdownEl
20
+ } = useHoverMenubarContext();
21
+ const isOpen = openedDropdownEl === buttonRef.current;
22
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
23
+ ref: buttonRef,
24
+ onClick: onDropDownButtonClick,
25
+ disabled: disabled,
26
+ onMouseOver: disabled ? undefined : onDropDownButtonMouseOver,
27
+ "data-test": dataTest,
28
+ className: "jsx-".concat(menuButtonStyles.__hash)
29
+ }, label, /*#__PURE__*/React.createElement(_JSXStyle, {
30
+ id: menuButtonStyles.__hash
31
+ }, menuButtonStyles)), isOpen && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(Popper, {
32
+ placement: "bottom-start",
33
+ reference: buttonRef
34
+ }, children)));
35
+ };
36
+ HoverMenuDropdown.defaultProps = {
37
+ dataTest: 'dhis2-analytics-hovermenudropdown'
38
+ };
39
+ HoverMenuDropdown.propTypes = {
40
+ children: PropTypes.node.isRequired,
41
+ label: PropTypes.node.isRequired,
42
+ dataTest: PropTypes.string,
43
+ disabled: PropTypes.bool
44
+ };