@dhis2-ui/organisation-unit-tree 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 (142) hide show
  1. package/package.json +8 -7
  2. package/src/__e2e__/children_as_child_nodes.js +23 -0
  3. package/src/__e2e__/common.js +70 -0
  4. package/src/__e2e__/controlled_expanded.js +89 -0
  5. package/src/__e2e__/displaying_loading_error.js +45 -0
  6. package/src/__e2e__/expanded.js +42 -0
  7. package/src/__e2e__/force_reload.js +66 -0
  8. package/src/__e2e__/get-organisation-unit-data.js +119 -0
  9. package/src/__e2e__/highlight.js +23 -0
  10. package/src/__e2e__/loading_state.js +37 -0
  11. package/src/__e2e__/multi_selection.js +24 -0
  12. package/src/__e2e__/namespace.js +1 -0
  13. package/src/__e2e__/no_selection.js +32 -0
  14. package/src/__e2e__/path_based_filtering.js +49 -0
  15. package/src/__e2e__/single_selection.js +46 -0
  16. package/src/__e2e__/sub_unit_as_root.js +28 -0
  17. package/src/__e2e__/tree_api.js +55 -0
  18. package/src/__stories__/collapsed.js +11 -0
  19. package/src/__stories__/custom-expanded-imperative-open.js +181 -0
  20. package/src/__stories__/custom-node-label.js +19 -0
  21. package/src/__stories__/development-stories.js +86 -0
  22. package/src/__stories__/expanded.js +12 -0
  23. package/src/__stories__/filtered-root.js +15 -0
  24. package/src/__stories__/filtered.js +13 -0
  25. package/src/__stories__/force-reload-all.js +46 -0
  26. package/src/__stories__/force-reload-one-unit.js +36 -0
  27. package/src/__stories__/highlighted.js +13 -0
  28. package/src/__stories__/indeterminate.js +13 -0
  29. package/src/__stories__/loading-error-grandchild.js +39 -0
  30. package/src/__stories__/loading.js +27 -0
  31. package/src/__stories__/multiple-roots.js +20 -0
  32. package/src/__stories__/no-selection.js +16 -0
  33. package/src/__stories__/replace-roots.js +28 -0
  34. package/src/__stories__/root-error.js +36 -0
  35. package/src/__stories__/root-loading.js +34 -0
  36. package/src/__stories__/rtl.js +14 -0
  37. package/src/__stories__/selected-multiple.js +18 -0
  38. package/src/__stories__/shared.js +192 -0
  39. package/src/__stories__/single-selection.js +16 -0
  40. package/src/features/children_as_child_nodes/index.js +29 -0
  41. package/src/features/children_as_child_nodes.feature +6 -0
  42. package/src/features/controlled_expanded/index.js +86 -0
  43. package/src/features/controlled_expanded.feature +11 -0
  44. package/src/features/displaying_loading_error/index.js +46 -0
  45. package/src/features/displaying_loading_error.feature +24 -0
  46. package/src/features/expanded/index.js +87 -0
  47. package/src/features/expanded.feature +27 -0
  48. package/src/features/force_reload/index.js +36 -0
  49. package/src/features/force_reload.feature +7 -0
  50. package/src/features/highlight/index.js +9 -0
  51. package/src/features/highlight.feature +5 -0
  52. package/src/features/loading_state/index.js +26 -0
  53. package/src/features/loading_state.feature +7 -0
  54. package/src/features/multi_selection/index.js +94 -0
  55. package/src/features/multi_selection.feature +31 -0
  56. package/src/features/no_selection/index.js +41 -0
  57. package/src/features/no_selection.feature +13 -0
  58. package/src/features/path_based_filtering/index.js +97 -0
  59. package/src/features/path_based_filtering.feature +24 -0
  60. package/src/features/single_selection/index.js +41 -0
  61. package/src/features/single_selection.feature +20 -0
  62. package/src/features/sub_unit_as_root/index.js +83 -0
  63. package/src/features/sub_unit_as_root.feature +34 -0
  64. package/src/features/tree_api/index.js +121 -0
  65. package/src/features/tree_api.feature +37 -0
  66. package/src/get-all-expanded-paths/get-all-expanded-paths.js +32 -0
  67. package/src/get-all-expanded-paths/get-all-expanded-paths.test.js +22 -0
  68. package/src/get-all-expanded-paths/index.js +1 -0
  69. package/src/helpers/index.js +3 -0
  70. package/src/helpers/is-path-included.js +15 -0
  71. package/src/helpers/left-trim-to-root-id.js +3 -0
  72. package/src/helpers/sort-node-children-alphabetically.js +5 -0
  73. package/src/index.js +6 -0
  74. package/src/locales/ar/translations.json +5 -0
  75. package/src/locales/cs/translations.json +5 -0
  76. package/src/locales/en/translations.json +5 -0
  77. package/src/locales/es/translations.json +5 -0
  78. package/src/locales/es_419/translations.json +5 -0
  79. package/src/locales/fr/translations.json +5 -0
  80. package/src/locales/index.js +50 -0
  81. package/src/locales/lo/translations.json +5 -0
  82. package/src/locales/nb/translations.json +5 -0
  83. package/src/locales/nl/translations.json +5 -0
  84. package/src/locales/pt/translations.json +5 -0
  85. package/src/locales/ru/translations.json +5 -0
  86. package/src/locales/uk/translations.json +5 -0
  87. package/src/locales/uz_Latn/translations.json +5 -0
  88. package/src/locales/uz_UZ_Cyrl/translations.json +5 -0
  89. package/src/locales/uz_UZ_Latn/translations.json +5 -0
  90. package/src/locales/vi/translations.json +5 -0
  91. package/src/locales/zh/translations.json +5 -0
  92. package/src/locales/zh_CN/translations.json +5 -0
  93. package/src/organisation-unit-node/compute-child-nodes.js +27 -0
  94. package/src/organisation-unit-node/compute-child-nodes.test.js +85 -0
  95. package/src/organisation-unit-node/error-message.js +23 -0
  96. package/src/organisation-unit-node/has-descendant-selected-paths.js +15 -0
  97. package/src/organisation-unit-node/has-descendant-selected-paths.test.js +30 -0
  98. package/src/organisation-unit-node/index.js +1 -0
  99. package/src/organisation-unit-node/label/disabled-selection-label.js +26 -0
  100. package/src/organisation-unit-node/label/icon-empty.js +31 -0
  101. package/src/organisation-unit-node/label/icon-folder-closed.js +38 -0
  102. package/src/organisation-unit-node/label/icon-folder-open.js +49 -0
  103. package/src/organisation-unit-node/label/icon-single.js +41 -0
  104. package/src/organisation-unit-node/label/icon.js +35 -0
  105. package/src/organisation-unit-node/label/iconized-checkbox.js +67 -0
  106. package/src/organisation-unit-node/label/index.js +1 -0
  107. package/src/organisation-unit-node/label/label-container.js +36 -0
  108. package/src/organisation-unit-node/label/label.js +146 -0
  109. package/src/organisation-unit-node/label/single-selection-label.js +60 -0
  110. package/src/organisation-unit-node/loading-spinner.js +22 -0
  111. package/src/organisation-unit-node/organisation-unit-node-children.js +123 -0
  112. package/src/organisation-unit-node/organisation-unit-node.js +190 -0
  113. package/src/organisation-unit-node/use-open-state.js +37 -0
  114. package/src/organisation-unit-node/use-open-state.test.js +111 -0
  115. package/src/organisation-unit-node/use-org-children.js +63 -0
  116. package/src/organisation-unit-node/use-org-children.test.js +314 -0
  117. package/src/organisation-unit-node/use-org-data/index.js +1 -0
  118. package/src/organisation-unit-node/use-org-data/use-org-data.js +40 -0
  119. package/src/organisation-unit-node/use-org-data/use-org-data.test.js +137 -0
  120. package/src/organisation-unit-tree/default-render-node-label/default-render-node-label.js +1 -0
  121. package/src/organisation-unit-tree/default-render-node-label/index.js +1 -0
  122. package/src/organisation-unit-tree/filter-root-ids.js +9 -0
  123. package/src/organisation-unit-tree/index.js +3 -0
  124. package/src/organisation-unit-tree/organisation-unit-tree-root-error.js +20 -0
  125. package/src/organisation-unit-tree/organisation-unit-tree-root-loading.js +22 -0
  126. package/src/organisation-unit-tree/organisation-unit-tree.js +253 -0
  127. package/src/organisation-unit-tree/organisation-unit-tree.test.js +77 -0
  128. package/src/organisation-unit-tree/use-expanded/create-expand-handlers.js +45 -0
  129. package/src/organisation-unit-tree/use-expanded/create-expand-handlers.test.js +54 -0
  130. package/src/organisation-unit-tree/use-expanded/index.js +1 -0
  131. package/src/organisation-unit-tree/use-expanded/use-expanded.js +42 -0
  132. package/src/organisation-unit-tree/use-expanded/use-expanded.test.js +73 -0
  133. package/src/organisation-unit-tree/use-force-reload.js +22 -0
  134. package/src/organisation-unit-tree/use-force-reload.test.js +43 -0
  135. package/src/organisation-unit-tree/use-root-org-data/index.js +1 -0
  136. package/src/organisation-unit-tree/use-root-org-data/patch-missing-display-name.js +20 -0
  137. package/src/organisation-unit-tree/use-root-org-data/patch-missing-display-name.test.js +24 -0
  138. package/src/organisation-unit-tree/use-root-org-data/use-root-org-data.js +60 -0
  139. package/src/organisation-unit-tree/use-root-org-data/use-root-org-unit.test.js +184 -0
  140. package/src/organisation-unit-tree.e2e.stories.js +28 -0
  141. package/src/organisation-unit-tree.prod.stories.js +70 -0
  142. package/src/prop-types.js +33 -0
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+ import { OrganisationUnitTree } from '../index.js'
3
+ import { onChange } from './shared.js'
4
+
5
+ export const NoSelection = () => (
6
+ <OrganisationUnitTree
7
+ onChange={onChange}
8
+ disableSelection
9
+ name="Root org unit"
10
+ roots={['A0000000000']}
11
+ selected={['/A0000000000/A0000000001']}
12
+ initiallyExpanded={['/A0000000000']}
13
+ />
14
+ )
15
+
16
+ NoSelection.storyName = 'No selection'
@@ -0,0 +1,28 @@
1
+ import React from 'react'
2
+
3
+ export const ReplaceRoots = () => {
4
+ return (
5
+ <p>
6
+ This is currently not working due to limitations of the data engine
7
+ in the app runtime. Normally the root unit would&apos;ve been
8
+ replaced after 1000 milliseconds.
9
+ </p>
10
+ )
11
+
12
+ //const [roots, setRoots] = useState(['A0000000000'])
13
+
14
+ //useEffect(() => {
15
+ // setTimeout(() => setRoots(['A0000000001']), delay)
16
+ //}, [])
17
+
18
+ //return (
19
+ // <OrganisationUnitTree
20
+ // name="Root org unit"
21
+ // roots={roots}
22
+ // onChange={console.log.bind(null, 'onChange')}
23
+ // initiallyExpanded={['/A0000000001']}
24
+ // />
25
+ //)
26
+ }
27
+
28
+ ReplaceRoots.storyName = 'Replace roots'
@@ -0,0 +1,36 @@
1
+ import { CustomDataProvider } from '@dhis2/app-runtime'
2
+ import React from 'react'
3
+ import { OrganisationUnitTree } from '../index.js'
4
+ import { customData, onChange } from './shared.js'
5
+
6
+ export const RootError = () => (
7
+ <CustomDataProvider
8
+ data={{
9
+ ...customData,
10
+ organisationUnits: (...args) => {
11
+ const [, { id }] = args
12
+ if (id === 'A0000000000') {
13
+ return Promise.reject(
14
+ 'This is a custom error message, it could be anything'
15
+ )
16
+ }
17
+
18
+ return customData.organisationUnits(...args)
19
+ },
20
+ }}
21
+ >
22
+ <fieldset style={{ maxWidth: 600 }}>
23
+ <legend style={{ padding: '0 10px' }}>
24
+ Custom container (max-width: 600px)
25
+ </legend>
26
+ <OrganisationUnitTree
27
+ onChange={onChange}
28
+ name="Root org unit"
29
+ roots={['A0000000000']}
30
+ initiallyExpanded={['/A0000000000/A0000000001']}
31
+ />
32
+ </fieldset>
33
+ </CustomDataProvider>
34
+ )
35
+
36
+ RootError.storyName = 'Root error'
@@ -0,0 +1,34 @@
1
+ import { CustomDataProvider } from '@dhis2/app-runtime'
2
+ import React from 'react'
3
+ import { OrganisationUnitTree } from '../index.js'
4
+ import { customData, onChange } from './shared.js'
5
+
6
+ export const RootLoading = () => (
7
+ <CustomDataProvider
8
+ data={{
9
+ ...customData,
10
+ organisationUnits: (...args) => {
11
+ const [, { id }] = args
12
+ if (id === 'A0000000000') {
13
+ return new Promise(() => null)
14
+ }
15
+
16
+ return customData.organisationUnits(...args)
17
+ },
18
+ }}
19
+ >
20
+ <fieldset style={{ maxWidth: 600 }}>
21
+ <legend style={{ padding: '0 10px' }}>
22
+ Custom container (max-width: 600px)
23
+ </legend>
24
+ <OrganisationUnitTree
25
+ onChange={onChange}
26
+ name="Root org unit"
27
+ roots={['A0000000000']}
28
+ initiallyExpanded={['/A0000000000/A0000000001']}
29
+ />
30
+ </fieldset>
31
+ </CustomDataProvider>
32
+ )
33
+
34
+ RootLoading.storyName = 'Root loading'
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+ import { OrganisationUnitTree } from '../index.js'
3
+ import { onChange } from './shared.js'
4
+
5
+ export const RTL = () => (
6
+ <div dir="rtl">
7
+ <OrganisationUnitTree
8
+ onChange={onChange}
9
+ name="Root org unit"
10
+ roots={['A0000000000']}
11
+ initiallyExpanded={['/A0000000000/A0000000001']}
12
+ />
13
+ </div>
14
+ )
@@ -0,0 +1,18 @@
1
+ import React from 'react'
2
+ import { OrganisationUnitTree } from '../index.js'
3
+ import { onChange } from './shared.js'
4
+
5
+ export const SelectedMultiple = () => (
6
+ <OrganisationUnitTree
7
+ onChange={onChange}
8
+ name="Root org unit"
9
+ roots={['A0000000000']}
10
+ selected={[
11
+ '/A0000000000/A0000000002',
12
+ '/A0000000000/A0000000001/A0000000003',
13
+ ]}
14
+ initiallyExpanded={['/A0000000000', '/A0000000000/A0000000001']}
15
+ />
16
+ )
17
+
18
+ SelectedMultiple.storyName = 'Selected multiple'
@@ -0,0 +1,192 @@
1
+ import { CustomDataProvider } from '@dhis2/app-runtime'
2
+ import PropTypes from 'prop-types'
3
+ import React, { useState } from 'react'
4
+
5
+ export const log = true
6
+ export const onChange = (...args) => log && console.log('onChange', ...args)
7
+ export const onExpand = (...args) => log && console.log('onExpand', ...args)
8
+ export const onCollapse = (...args) => log && console.log('onCollapse', ...args)
9
+ export const onChildrenLoaded = (...args) =>
10
+ log && console.log('onChildrenLoaded', ...args)
11
+
12
+ const createResponse = ({ fields, id, path, displayName, children }) => ({
13
+ ...(fields.includes('id') ? { id } : {}),
14
+ ...(fields.includes('path') ? { path } : {}),
15
+ ...(fields.includes('displayName') ? { displayName } : {}),
16
+ ...(fields.includes('children::size') ? { children: children.length } : {}),
17
+ ...(fields.includes('children[id,path,displayName]') ? { children } : {}),
18
+ })
19
+
20
+ export const getOrganisationUnitData = (id, { fields }) => {
21
+ let data
22
+
23
+ if (id === 'A0000000000') {
24
+ data = createResponse({
25
+ fields,
26
+ id: 'A0000000000',
27
+ path: '/A0000000000',
28
+ displayName: 'Org Unit 1',
29
+ children: [
30
+ {
31
+ id: 'A0000000001',
32
+ path: '/A0000000000/A0000000001',
33
+ children: [{ id: 'A0000000003' }, { id: 'A0000000004' }],
34
+ displayName: 'Org Unit 2',
35
+ },
36
+ {
37
+ id: 'A0000000002',
38
+ path: '/A0000000000/A0000000002',
39
+ children: [],
40
+ displayName: 'Org Unit 3',
41
+ },
42
+ {
43
+ id: 'A0000000006',
44
+ path: '/A0000000000/A0000000006',
45
+ children: [],
46
+ displayName: 'Org Unit 7',
47
+ },
48
+ ],
49
+ })
50
+ }
51
+
52
+ if (id === 'A0000000001') {
53
+ data = createResponse({
54
+ fields,
55
+ id: 'A0000000001',
56
+ path: '/A0000000000/A0000000001',
57
+ displayName: 'Org Unit 2',
58
+ children: [
59
+ {
60
+ id: 'A0000000003',
61
+ path: '/A0000000000/A0000000001/A0000000003',
62
+ children: [],
63
+ displayName: 'Org Unit 4',
64
+ },
65
+ {
66
+ id: 'A0000000004',
67
+ path: '/A0000000000/A0000000001/A0000000004',
68
+ children: [],
69
+ displayName: 'Org Unit 5',
70
+ },
71
+ ],
72
+ })
73
+ }
74
+
75
+ if (id === 'A0000000002') {
76
+ data = createResponse({
77
+ fields,
78
+ displayName: 'Org Unit 3',
79
+ id: 'A0000000002',
80
+ path: '/A0000000000/A0000000002',
81
+ children: [],
82
+ })
83
+ }
84
+
85
+ if (id === 'A0000000003') {
86
+ data = createResponse({
87
+ fields,
88
+ displayName: 'Org Unit 4',
89
+ id: 'A0000000003',
90
+ path: '/A0000000000/A0000000001/A0000000003',
91
+ children: [
92
+ {
93
+ id: 'A0000000007',
94
+ path: '/A0000000000/A0000000001/A0000000003/A0000000007',
95
+ children: [],
96
+ displayName: 'Org Unit 8',
97
+ },
98
+ ],
99
+ })
100
+ }
101
+
102
+ if (id === 'A0000000004') {
103
+ data = createResponse({
104
+ fields,
105
+ displayName: 'Org Unit 5',
106
+ id: 'A0000000004',
107
+ path: '/A0000000000/A0000000001/A0000000004',
108
+ children: [],
109
+ })
110
+ }
111
+
112
+ if (id === 'A0000000006') {
113
+ data = createResponse({
114
+ fields,
115
+ displayName: 'Org Unit 7',
116
+ id: 'A0000000006',
117
+ path: '/A0000000000/A0000000006',
118
+ children: [],
119
+ })
120
+ }
121
+
122
+ if (id === 'A0000000007') {
123
+ data = createResponse({
124
+ fields,
125
+ displayName: 'Org Unit 8',
126
+ id: 'A0000000007',
127
+ path: '/A0000000000/A0000000001/A0000000003/A0000000007',
128
+ children: [],
129
+ })
130
+ }
131
+
132
+ return data
133
+ }
134
+
135
+ export const customData = {
136
+ organisationUnits: (_, { id, params = {} }) => {
137
+ const data = getOrganisationUnitData(id, params)
138
+
139
+ if (!data) {
140
+ return Promise.reject(new Error('404 - Org unit not found'))
141
+ }
142
+
143
+ return Promise.resolve(data)
144
+ },
145
+ }
146
+
147
+ export const StatefulMultiSelectionWrapper = ({
148
+ children,
149
+ onSelectionChange = () => null,
150
+ }) => {
151
+ const [selected, setSelected] = useState([])
152
+
153
+ return children({
154
+ selected,
155
+ onChange: (...args) => {
156
+ onChange(...args)
157
+
158
+ const [{ selected: newSelected }] = args
159
+ setSelected(newSelected)
160
+ onSelectionChange(newSelected)
161
+ },
162
+ })
163
+ }
164
+
165
+ StatefulMultiSelectionWrapper.propTypes = {
166
+ children: PropTypes.func.isRequired,
167
+ onSelectionChange: PropTypes.func,
168
+ }
169
+
170
+ export const createDecoratorStatefulMultiSelection = (args) => {
171
+ return (Story) => (
172
+ <StatefulMultiSelectionWrapper
173
+ onSelectionChange={args?.onSelectionChange}
174
+ >
175
+ {({ selected, onChange }) => (
176
+ <Story selected={selected} onChange={onChange} />
177
+ )}
178
+ </StatefulMultiSelectionWrapper>
179
+ )
180
+ }
181
+
182
+ export const createDecoratorCustomDataProvider = (args) => {
183
+ const data = args?.data || customData
184
+
185
+ return (Story) => {
186
+ return (
187
+ <CustomDataProvider data={data}>
188
+ <Story />
189
+ </CustomDataProvider>
190
+ )
191
+ }
192
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+ import { OrganisationUnitTree } from '../index.js'
3
+ import { onChange } from './shared.js'
4
+
5
+ export const SingleSelection = () => (
6
+ <OrganisationUnitTree
7
+ onChange={onChange}
8
+ singleSelection
9
+ name="Root org unit"
10
+ roots={['A0000000000']}
11
+ selected={['/A0000000000/A0000000001']}
12
+ initiallyExpanded={['/A0000000000']}
13
+ />
14
+ )
15
+
16
+ SingleSelection.storyName = 'Single selection'
@@ -0,0 +1,29 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('an OrganisationUnitTree with children is rendered', () => {
4
+ cy.visitStory('OrganisationUnitTree', 'Closed with children')
5
+ })
6
+
7
+ Given('the node is open', () => {
8
+ cy.get(
9
+ '[data-test="dhis2-uiwidgets-orgunittree"] > [data-test="dhis2-uiwidgets-orgunittree-node"]'
10
+ ).as('rootUnit')
11
+
12
+ cy.get('@rootUnit').openOrgUnitNode()
13
+ })
14
+
15
+ Then("its children are nodes inside the unit's node", () => {
16
+ cy.get('@rootUnit')
17
+ .find(
18
+ '> [data-test="dhis2-uiwidgets-orgunittree-node-content"] > [data-test="dhis2-uiwidgets-orgunittree-node-leaves"]'
19
+ )
20
+ .children()
21
+ .should('have.length', 3)
22
+ .and((children) =>
23
+ children.each((_, child) => {
24
+ const $child = Cypress.$(child)
25
+ const dataTest = $child.data('test')
26
+ expect(dataTest).to.equal('dhis2-uiwidgets-orgunittree-node')
27
+ })
28
+ )
29
+ })
@@ -0,0 +1,6 @@
1
+ Feature: Children of a node are rendered as a child node
2
+
3
+ Scenario: A unit has some children
4
+ Given an OrganisationUnitTree with children is rendered
5
+ And the node is open
6
+ Then its children are nodes inside the unit's node
@@ -0,0 +1,86 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+ import { getOrganisationUnitData } from '../../__e2e__/get-organisation-unit-data.js'
3
+ import { namespace } from '../../__e2e__/namespace.js'
4
+
5
+ const expectOrgUnitsToBeDisplayed = (ids) => {
6
+ const expandedLabels = ids.map(
7
+ (id) =>
8
+ getOrganisationUnitData(id, { fields: ['displayName'] }).displayName
9
+ )
10
+
11
+ expandedLabels.forEach((label) => {
12
+ cy.get(`:contains("${label}")`).should('exist')
13
+ })
14
+ }
15
+
16
+ const expectOrgUnitsToNotBeDisplayed = (ids) => {
17
+ const expandedLabels = ids.map((id) => {
18
+ const data = getOrganisationUnitData(id, { fields: ['displayName'] })
19
+ return data.displayName
20
+ })
21
+
22
+ expandedLabels.forEach((label) => {
23
+ cy.get(`:contains("${label}")`).should('not.exist')
24
+ })
25
+ }
26
+
27
+ Given(
28
+ 'the initial state of the controlled expanded prop has some paths',
29
+ () => {
30
+ cy.visitStory(namespace, 'Controlled')
31
+ cy.get(':contains("Org Unit 1")').should('exist')
32
+ cy.window().then((win) => {
33
+ cy.wrap(win.initiallyExpandedPaths).as('providedPaths', {
34
+ type: 'static',
35
+ })
36
+ })
37
+ }
38
+ )
39
+
40
+ When('the org unit tree should is done loading the provided paths', () => {
41
+ cy.window().then((win) => {
42
+ const expandedIds = win.initiallyExpandedPaths.map(
43
+ (path) => path.match(/[^/]+$/)[0]
44
+ )
45
+
46
+ expectOrgUnitsToBeDisplayed(expandedIds)
47
+ })
48
+ })
49
+
50
+ When('the user clicks on a button to collapse one of the opened paths', () => {
51
+ cy.window().then((win) => {
52
+ cy.wrap([win.orgUnitPathToExpand]).as('providedPaths', {
53
+ type: 'static',
54
+ })
55
+ })
56
+
57
+ cy.get('[data-test="org-unit-toggle"]').click()
58
+ })
59
+
60
+ Then(
61
+ 'the org unit tree should open the provided paths when done loading',
62
+ () => {
63
+ cy.get('@providedPaths').then((providedPaths) => {
64
+ const providedIds = providedPaths.map(
65
+ (path) => path.match(/[^/]+$/)[0]
66
+ )
67
+
68
+ expectOrgUnitsToBeDisplayed(providedIds)
69
+ })
70
+ }
71
+ )
72
+
73
+ Then('the path should close', () => {
74
+ cy.get('@providedPaths').then((providedPaths) => {
75
+ const providedIds = providedPaths.map((path) => path.match(/[^/]+$/)[0])
76
+ const hiddenChildrenIds = providedIds.reduce((acc, cur) => {
77
+ const curData = getOrganisationUnitData(cur, {
78
+ fields: ['children[id,path,displayName]'],
79
+ })
80
+ const childrenIds = curData.children.map(({ id }) => id)
81
+ return [...acc, ...childrenIds]
82
+ }, [])
83
+
84
+ expectOrgUnitsToNotBeDisplayed(hiddenChildrenIds)
85
+ })
86
+ })
@@ -0,0 +1,11 @@
1
+ Feature: The expanded paths of the org unit tree can be controlled
2
+
3
+ Scenario: The org unit tree expands some paths initially
4
+ Given the initial state of the controlled expanded prop has some paths
5
+ Then the org unit tree should open the provided paths when done loading
6
+
7
+ Scenario: The user collapses a path by pressing a button
8
+ Given the initial state of the controlled expanded prop has some paths
9
+ When the org unit tree should is done loading the provided paths
10
+ When the user clicks on a button to collapse one of the opened paths
11
+ Then the path should close
@@ -0,0 +1,46 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given(
4
+ "loading errors do not display automatically and loading A0000000001's children will fail",
5
+ () => {
6
+ cy.visitStory('OrganisationUnitTree', 'A 0000000001 loading error')
7
+ }
8
+ )
9
+
10
+ Given(
11
+ "loading errors display automatically and loading A0000000001's children will fail",
12
+ () => {
13
+ cy.visitStory(
14
+ 'OrganisationUnitTree',
15
+ 'A 0000000001 loading error autoexpand'
16
+ )
17
+ }
18
+ )
19
+
20
+ Given('the OrganisationUnitTree is closed', () => {
21
+ cy.get(
22
+ '[data-test="dhis2-uiwidgets-orgunittree"] > [data-test="dhis2-uiwidgets-orgunittree-node"]'
23
+ )
24
+ .as('rootNode')
25
+ .shouldBeAClosedNode()
26
+ })
27
+
28
+ When('the A0000000000 path is opened', () => {
29
+ cy.getOrgUnitByLabel('Org Unit 1').openOrgUnitNode()
30
+ })
31
+
32
+ When('the A0000000000 -> A0000000001 path is opened', () => {
33
+ cy.getOrgUnitByLabel('Org Unit 2').openOrgUnitNode()
34
+ })
35
+
36
+ Then('no error message is shown', () => {
37
+ cy.getOrgUnitByLabel('Org Unit 2')
38
+ .find('[data-test="dhis2-uiwidgets-orgunittree-error"]')
39
+ .should('not.exist')
40
+ })
41
+
42
+ Then('an appropriate error message is shown', () => {
43
+ cy.getOrgUnitByLabel('Org Unit 2')
44
+ .find('[data-test="dhis2-uiwidgets-orgunittree-error"]')
45
+ .should('contain', 'Could not load children')
46
+ })
@@ -0,0 +1,24 @@
1
+ Feature: When loading children fails a loading error should be shown
2
+
3
+ Due to the asynchronous nature of the tree, loading children
4
+ can fail at any given time. An appropriate error message should
5
+ be displayed when this happens
6
+
7
+ The error does not show automatically until a node's
8
+ children should be displayed.
9
+ This behavior can be changed to expand the node immediately
10
+ when the loading process fails.
11
+
12
+ Scenario: Loading the children of the root unit fails and error should not be auto expanded
13
+ Given loading errors do not display automatically and loading A0000000001's children will fail
14
+ And the OrganisationUnitTree is closed
15
+ When the A0000000000 path is opened
16
+ Then no error message is shown
17
+ When the A0000000000 -> A0000000001 path is opened
18
+ Then an appropriate error message is shown
19
+
20
+ Scenario: Loading the children of the root unit fails and error should be auto expanded
21
+ Given loading errors display automatically and loading A0000000001's children will fail
22
+ And the OrganisationUnitTree is closed
23
+ When the A0000000000 path is opened
24
+ Then an appropriate error message is shown
@@ -0,0 +1,87 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ const getRootOrgUnitByLabel = (label) => {
4
+ const rootOrgUnitLabelSelector = `
5
+ [data-test="dhis2-uiwidgets-orgunittree"]
6
+ > [data-test="dhis2-uiwidgets-orgunittree-node"]
7
+ > [data-test="dhis2-uiwidgets-orgunittree-node-content"]
8
+ > [data-test="dhis2-uiwidgets-orgunittree-node-label"]
9
+ label
10
+ `
11
+
12
+ return cy
13
+ .contains(rootOrgUnitLabelSelector, label)
14
+ .parents('[data-test="dhis2-uiwidgets-orgunittree-node"]')
15
+ }
16
+
17
+ Given(
18
+ 'a OrganisationUnitTree with children and no paths in the initiallyExpanded prop is rendered',
19
+ () => {
20
+ cy.visitStory('OrganisationUnitTree', 'No initially expanded paths')
21
+ }
22
+ )
23
+
24
+ Given(
25
+ 'a OrganisationUnitTree with children and the path of the first unit on the second level in the initiallyExpanded prop is rendered',
26
+ () => {
27
+ cy.visitStory('OrganisationUnitTree', 'Initially expanded paths')
28
+ }
29
+ )
30
+
31
+ Given(
32
+ 'both a sub org unit with children and a main org unit are root org units',
33
+ () => {
34
+ cy.visitStory(
35
+ 'OrganisationUnitTree',
36
+ 'with root main and root sub org unit'
37
+ )
38
+ }
39
+ )
40
+
41
+ Given('the root main org unit is expanded', () => {
42
+ getRootOrgUnitByLabel('Org Unit 1')
43
+ .find('> [data-test="dhis2-uiwidgets-orgunittree-node-toggle"]')
44
+ .click()
45
+ })
46
+
47
+ When('the user expands the sub org unit within the main org unit tree', () => {
48
+ getRootOrgUnitByLabel('Org Unit 1')
49
+ .contains(
50
+ '[data-test="dhis2-uiwidgets-orgunittree-node"]',
51
+ 'Org Unit 2'
52
+ )
53
+ .find('> [data-test="dhis2-uiwidgets-orgunittree-node-toggle"]')
54
+ .click()
55
+ .should('have.class', 'open')
56
+ })
57
+
58
+ When('the user expands the root sub org unit', () => {
59
+ getRootOrgUnitByLabel('Org Unit 2')
60
+ .find('> [data-test="dhis2-uiwidgets-orgunittree-node-toggle"]')
61
+ .click()
62
+ .should('have.class', 'open')
63
+ })
64
+
65
+ Then('the root unit is closed', () => {
66
+ cy.getOrgUnitByLabel('Org Unit 1').shouldBeAClosedNode()
67
+ })
68
+
69
+ Then('the root unit is opened', () => {
70
+ cy.getOrgUnitByLabel('Org Unit 1').shouldBeAnOrgUnitNode()
71
+ })
72
+
73
+ Then('the root sub org unit should not expand', () => {
74
+ getRootOrgUnitByLabel('Org Unit 2')
75
+ .find('> [data-test="dhis2-uiwidgets-orgunittree-node-toggle"]')
76
+ .should('not.have.class', 'open')
77
+ })
78
+
79
+ Then('the sub org unit within the main org unit tree should not expand', () => {
80
+ getRootOrgUnitByLabel('Org Unit 1')
81
+ .contains(
82
+ '[data-test="dhis2-uiwidgets-orgunittree-node"]',
83
+ 'Org Unit 2'
84
+ )
85
+ .find('> [data-test="dhis2-uiwidgets-orgunittree-node-toggle"]')
86
+ .should('not.have.class', 'open')
87
+ })