@dhis2-ui/organisation-unit-tree 10.16.2 → 10.16.3-alpha.1
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/package.json +8 -7
- package/src/__e2e__/children_as_child_nodes.js +23 -0
- package/src/__e2e__/common.js +70 -0
- package/src/__e2e__/controlled_expanded.js +89 -0
- package/src/__e2e__/displaying_loading_error.js +45 -0
- package/src/__e2e__/expanded.js +42 -0
- package/src/__e2e__/force_reload.js +66 -0
- package/src/__e2e__/get-organisation-unit-data.js +119 -0
- package/src/__e2e__/highlight.js +23 -0
- package/src/__e2e__/loading_state.js +37 -0
- package/src/__e2e__/multi_selection.js +24 -0
- package/src/__e2e__/namespace.js +1 -0
- package/src/__e2e__/no_selection.js +32 -0
- package/src/__e2e__/path_based_filtering.js +49 -0
- package/src/__e2e__/single_selection.js +46 -0
- package/src/__e2e__/sub_unit_as_root.js +28 -0
- package/src/__e2e__/tree_api.js +55 -0
- package/src/__stories__/collapsed.js +11 -0
- package/src/__stories__/custom-expanded-imperative-open.js +181 -0
- package/src/__stories__/custom-node-label.js +19 -0
- package/src/__stories__/development-stories.js +86 -0
- package/src/__stories__/expanded.js +12 -0
- package/src/__stories__/filtered-root.js +15 -0
- package/src/__stories__/filtered.js +13 -0
- package/src/__stories__/force-reload-all.js +46 -0
- package/src/__stories__/force-reload-one-unit.js +36 -0
- package/src/__stories__/highlighted.js +13 -0
- package/src/__stories__/indeterminate.js +13 -0
- package/src/__stories__/loading-error-grandchild.js +39 -0
- package/src/__stories__/loading.js +27 -0
- package/src/__stories__/multiple-roots.js +20 -0
- package/src/__stories__/no-selection.js +16 -0
- package/src/__stories__/replace-roots.js +28 -0
- package/src/__stories__/root-error.js +36 -0
- package/src/__stories__/root-loading.js +34 -0
- package/src/__stories__/rtl.js +14 -0
- package/src/__stories__/selected-multiple.js +18 -0
- package/src/__stories__/shared.js +192 -0
- package/src/__stories__/single-selection.js +16 -0
- package/src/features/children_as_child_nodes/index.js +29 -0
- package/src/features/children_as_child_nodes.feature +6 -0
- package/src/features/controlled_expanded/index.js +86 -0
- package/src/features/controlled_expanded.feature +11 -0
- package/src/features/displaying_loading_error/index.js +46 -0
- package/src/features/displaying_loading_error.feature +24 -0
- package/src/features/expanded/index.js +87 -0
- package/src/features/expanded.feature +27 -0
- package/src/features/force_reload/index.js +36 -0
- package/src/features/force_reload.feature +7 -0
- package/src/features/highlight/index.js +9 -0
- package/src/features/highlight.feature +5 -0
- package/src/features/loading_state/index.js +26 -0
- package/src/features/loading_state.feature +7 -0
- package/src/features/multi_selection/index.js +94 -0
- package/src/features/multi_selection.feature +31 -0
- package/src/features/no_selection/index.js +41 -0
- package/src/features/no_selection.feature +13 -0
- package/src/features/path_based_filtering/index.js +97 -0
- package/src/features/path_based_filtering.feature +24 -0
- package/src/features/single_selection/index.js +41 -0
- package/src/features/single_selection.feature +20 -0
- package/src/features/sub_unit_as_root/index.js +83 -0
- package/src/features/sub_unit_as_root.feature +34 -0
- package/src/features/tree_api/index.js +121 -0
- package/src/features/tree_api.feature +37 -0
- package/src/get-all-expanded-paths/get-all-expanded-paths.js +32 -0
- package/src/get-all-expanded-paths/get-all-expanded-paths.test.js +22 -0
- package/src/get-all-expanded-paths/index.js +1 -0
- package/src/helpers/index.js +3 -0
- package/src/helpers/is-path-included.js +15 -0
- package/src/helpers/left-trim-to-root-id.js +3 -0
- package/src/helpers/sort-node-children-alphabetically.js +5 -0
- package/src/index.js +6 -0
- package/src/locales/ar/translations.json +5 -0
- package/src/locales/cs/translations.json +5 -0
- package/src/locales/en/translations.json +5 -0
- package/src/locales/es/translations.json +5 -0
- package/src/locales/es_419/translations.json +5 -0
- package/src/locales/fr/translations.json +5 -0
- package/src/locales/index.js +50 -0
- package/src/locales/lo/translations.json +5 -0
- package/src/locales/nb/translations.json +5 -0
- package/src/locales/nl/translations.json +5 -0
- package/src/locales/pt/translations.json +5 -0
- package/src/locales/ru/translations.json +5 -0
- package/src/locales/uk/translations.json +5 -0
- package/src/locales/uz_Latn/translations.json +5 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +5 -0
- package/src/locales/uz_UZ_Latn/translations.json +5 -0
- package/src/locales/vi/translations.json +5 -0
- package/src/locales/zh/translations.json +5 -0
- package/src/locales/zh_CN/translations.json +5 -0
- package/src/organisation-unit-node/compute-child-nodes.js +27 -0
- package/src/organisation-unit-node/compute-child-nodes.test.js +85 -0
- package/src/organisation-unit-node/error-message.js +23 -0
- package/src/organisation-unit-node/has-descendant-selected-paths.js +15 -0
- package/src/organisation-unit-node/has-descendant-selected-paths.test.js +30 -0
- package/src/organisation-unit-node/index.js +1 -0
- package/src/organisation-unit-node/label/disabled-selection-label.js +26 -0
- package/src/organisation-unit-node/label/icon-empty.js +31 -0
- package/src/organisation-unit-node/label/icon-folder-closed.js +38 -0
- package/src/organisation-unit-node/label/icon-folder-open.js +49 -0
- package/src/organisation-unit-node/label/icon-single.js +41 -0
- package/src/organisation-unit-node/label/icon.js +35 -0
- package/src/organisation-unit-node/label/iconized-checkbox.js +67 -0
- package/src/organisation-unit-node/label/index.js +1 -0
- package/src/organisation-unit-node/label/label-container.js +36 -0
- package/src/organisation-unit-node/label/label.js +146 -0
- package/src/organisation-unit-node/label/single-selection-label.js +60 -0
- package/src/organisation-unit-node/loading-spinner.js +22 -0
- package/src/organisation-unit-node/organisation-unit-node-children.js +123 -0
- package/src/organisation-unit-node/organisation-unit-node.js +190 -0
- package/src/organisation-unit-node/use-open-state.js +37 -0
- package/src/organisation-unit-node/use-open-state.test.js +111 -0
- package/src/organisation-unit-node/use-org-children.js +63 -0
- package/src/organisation-unit-node/use-org-children.test.js +314 -0
- package/src/organisation-unit-node/use-org-data/index.js +1 -0
- package/src/organisation-unit-node/use-org-data/use-org-data.js +40 -0
- package/src/organisation-unit-node/use-org-data/use-org-data.test.js +137 -0
- package/src/organisation-unit-tree/default-render-node-label/default-render-node-label.js +1 -0
- package/src/organisation-unit-tree/default-render-node-label/index.js +1 -0
- package/src/organisation-unit-tree/filter-root-ids.js +9 -0
- package/src/organisation-unit-tree/index.js +3 -0
- package/src/organisation-unit-tree/organisation-unit-tree-root-error.js +20 -0
- package/src/organisation-unit-tree/organisation-unit-tree-root-loading.js +22 -0
- package/src/organisation-unit-tree/organisation-unit-tree.js +253 -0
- package/src/organisation-unit-tree/organisation-unit-tree.test.js +77 -0
- package/src/organisation-unit-tree/use-expanded/create-expand-handlers.js +45 -0
- package/src/organisation-unit-tree/use-expanded/create-expand-handlers.test.js +54 -0
- package/src/organisation-unit-tree/use-expanded/index.js +1 -0
- package/src/organisation-unit-tree/use-expanded/use-expanded.js +42 -0
- package/src/organisation-unit-tree/use-expanded/use-expanded.test.js +73 -0
- package/src/organisation-unit-tree/use-force-reload.js +22 -0
- package/src/organisation-unit-tree/use-force-reload.test.js +43 -0
- package/src/organisation-unit-tree/use-root-org-data/index.js +1 -0
- package/src/organisation-unit-tree/use-root-org-data/patch-missing-display-name.js +20 -0
- package/src/organisation-unit-tree/use-root-org-data/patch-missing-display-name.test.js +24 -0
- package/src/organisation-unit-tree/use-root-org-data/use-root-org-data.js +60 -0
- package/src/organisation-unit-tree/use-root-org-data/use-root-org-unit.test.js +184 -0
- package/src/organisation-unit-tree.e2e.stories.js +28 -0
- package/src/organisation-unit-tree.prod.stories.js +70 -0
- package/src/prop-types.js +33 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
const expectStubPayloadToEqual = (stub, prop, expected) => {
|
|
4
|
+
const calls = stub.getCalls()
|
|
5
|
+
const { args } = calls[calls.length - 1]
|
|
6
|
+
const [payload] = args
|
|
7
|
+
expect(payload[prop]).to.eql(expected)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Given('an OrganisationUnitTree is rendered', () => {
|
|
11
|
+
cy.visitStory('OrganisationUnitTree', 'Events')
|
|
12
|
+
cy.getOrgUnitByLabel('Org Unit 1').shouldBeDoneLoading()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
Given('a node has been selected', () => {
|
|
16
|
+
cy.getOrgUnitByLabel('Org Unit 1').toggleOrgUnitNodeSelection(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
Given('a node with children is rendered', () => {
|
|
20
|
+
cy.visitStory('OrganisationUnitTree', 'Events')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
Given('the node has been expanded', () => {
|
|
24
|
+
cy.getOrgUnitByLabel('Org Unit 1').openOrgUnitNode()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
Given("the children haven't been loaded yet", () => {
|
|
28
|
+
cy.window().should((win) => {
|
|
29
|
+
expect(win.onExpand).not.to.be.called
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
When('a node gets selected', () => {
|
|
34
|
+
cy.getOrgUnitByLabel('Org Unit 1').toggleOrgUnitNodeSelection(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
When('a node gets deselected', () => {
|
|
38
|
+
cy.getOrgUnitByLabel('Org Unit 1').toggleOrgUnitNodeSelection(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
When('the node is expanded', () => {
|
|
42
|
+
cy.getOrgUnitByLabel('Org Unit 1').openOrgUnitNode()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
When('a node is collapsed', () => {
|
|
46
|
+
cy.getOrgUnitByLabel('Org Unit 1').closeOrgUnitNode()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
When('the children have been loaded', () => {
|
|
50
|
+
cy.getOrgUnitByLabel('Org Unit 1').toggleOrgUnitNode(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
Then('the onChange callback gets called', () => {
|
|
54
|
+
cy.window().should((win) => {
|
|
55
|
+
expect(win.onChange).to.be.called
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
Then('the payload includes the path of the selected node', () => {
|
|
60
|
+
cy.window().should((win) => {
|
|
61
|
+
expectStubPayloadToEqual(win.onChange, 'path', '/A0000000000')
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
Then('the payload includes checked which is set to "true"', () => {
|
|
66
|
+
cy.window().should((win) => {
|
|
67
|
+
expectStubPayloadToEqual(win.onChange, 'checked', true)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
Then('the payload includes all selected nodes', () => {
|
|
72
|
+
cy.window().should((win) => {
|
|
73
|
+
expectStubPayloadToEqual(win.onChange, 'selected', ['/A0000000000'])
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
Then('the payload includes checked which is set to "false"', () => {
|
|
78
|
+
cy.window().should((win) => {
|
|
79
|
+
expectStubPayloadToEqual(win.onChange, 'checked', false)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
Then('the onExpand callback gets called', () => {
|
|
84
|
+
cy.window().should((win) => {
|
|
85
|
+
expect(win.onExpand).to.be.called
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
Then('the payload includes the path of the expanded node', () => {
|
|
90
|
+
cy.window().should((win) => {
|
|
91
|
+
expectStubPayloadToEqual(win.onExpand, 'path', '/A0000000000')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
Then('the onCollapse callback gets called', () => {
|
|
96
|
+
cy.window().should((win) => {
|
|
97
|
+
expect(win.onCollapse).to.be.called
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
Then('the payload includes the path of the collapsed node', () => {
|
|
102
|
+
cy.window().should((win) => {
|
|
103
|
+
expectStubPayloadToEqual(win.onCollapse, 'path', '/A0000000000')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
Then('the onChildrenLoaded callback gets called', () => {
|
|
108
|
+
cy.window().should((win) => {
|
|
109
|
+
expect(win.onChildrenLoaded).to.be.called
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
Then("the payload contains the loaded children's data", () => {
|
|
114
|
+
cy.window().should((win) => {
|
|
115
|
+
const calls = win.onChildrenLoaded.getCalls()
|
|
116
|
+
expect(calls).to.have.length(1)
|
|
117
|
+
|
|
118
|
+
const [{ args }] = calls
|
|
119
|
+
expect(args[0].id).to.equal('A0000000000')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Feature: The OrganisationUnitTree offers props for its events
|
|
2
|
+
|
|
3
|
+
Scenario: The user selects a node
|
|
4
|
+
Given an OrganisationUnitTree is rendered
|
|
5
|
+
When a node gets selected
|
|
6
|
+
Then the onChange callback gets called
|
|
7
|
+
And the payload includes the path of the selected node
|
|
8
|
+
And the payload includes checked which is set to "true"
|
|
9
|
+
And the payload includes all selected nodes
|
|
10
|
+
|
|
11
|
+
Scenario: The user deselects a node
|
|
12
|
+
Given an OrganisationUnitTree is rendered
|
|
13
|
+
And a node has been selected
|
|
14
|
+
When a node gets deselected
|
|
15
|
+
Then the onChange callback gets called
|
|
16
|
+
And the payload includes the path of the selected node
|
|
17
|
+
And the payload includes checked which is set to "false"
|
|
18
|
+
|
|
19
|
+
Scenario: The user expands a node with children
|
|
20
|
+
Given a node with children is rendered
|
|
21
|
+
When the node is expanded
|
|
22
|
+
Then the onExpand callback gets called
|
|
23
|
+
And the payload includes the path of the expanded node
|
|
24
|
+
|
|
25
|
+
Scenario: The user collapses a node with children
|
|
26
|
+
Given a node with children is rendered
|
|
27
|
+
And the node has been expanded
|
|
28
|
+
When a node is collapsed
|
|
29
|
+
Then the onCollapse callback gets called
|
|
30
|
+
And the payload includes the path of the collapsed node
|
|
31
|
+
|
|
32
|
+
Scenario: Children of a node are done being loaded
|
|
33
|
+
Given a node with children is rendered
|
|
34
|
+
But the children haven't been loaded yet
|
|
35
|
+
When the children have been loaded
|
|
36
|
+
Then the onChildrenLoaded callback gets called
|
|
37
|
+
And the payload contains the loaded children's data
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template T
|
|
3
|
+
* @param {Array.<T>} arr
|
|
4
|
+
* @returns {Array.<T>}
|
|
5
|
+
*/
|
|
6
|
+
const removeDuplicates = (arr) => Array.from(new Set(arr))
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} path
|
|
10
|
+
* @returns {string[]}
|
|
11
|
+
*/
|
|
12
|
+
const extractAllPathsFromPath = (path) => {
|
|
13
|
+
// remove leading slash and split by path delimiter/slashes
|
|
14
|
+
const segments = path.replace(/^\//, '').split('/')
|
|
15
|
+
|
|
16
|
+
const withSubPaths = segments.map((segment, index) => {
|
|
17
|
+
// take all segments from 0 to index and join them with the delimiter
|
|
18
|
+
return `/${segments.slice(0, index + 1).join('/')}`
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return removeDuplicates(withSubPaths)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string[]} initiallyExpanded
|
|
26
|
+
* @returns {string[]}
|
|
27
|
+
*/
|
|
28
|
+
export const getAllExpandedPaths = (initiallyExpanded) =>
|
|
29
|
+
initiallyExpanded.reduce((all, curPath) => {
|
|
30
|
+
const allPathsInCurPath = extractAllPathsFromPath(curPath)
|
|
31
|
+
return [...all, ...allPathsInCurPath]
|
|
32
|
+
}, [])
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getAllExpandedPaths } from './get-all-expanded-paths.js'
|
|
2
|
+
|
|
3
|
+
describe('OrganisationUnitTree - getAllExpandedPaths', () => {
|
|
4
|
+
const initiallyExpanded = ['/foo/bar/baz', '/foobar/barbaz/bazfoo']
|
|
5
|
+
|
|
6
|
+
it('should include all initiallyExpanded paths in the returned expanded array', () => {
|
|
7
|
+
const actual = getAllExpandedPaths(initiallyExpanded)
|
|
8
|
+
const expected = expect.arrayContaining(initiallyExpanded)
|
|
9
|
+
expect(actual).toEqual(expected)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should include all sub paths of the paths in initiallyExpanded in the returned exanded array', () => {
|
|
13
|
+
const actual = getAllExpandedPaths(initiallyExpanded)
|
|
14
|
+
const expected = expect.arrayContaining([
|
|
15
|
+
'/foo',
|
|
16
|
+
'/foo/bar',
|
|
17
|
+
'/foobar',
|
|
18
|
+
'/foobar/barbaz',
|
|
19
|
+
])
|
|
20
|
+
expect(actual).toEqual(expected)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getAllExpandedPaths } from './get-all-expanded-paths.js'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string[]} includedPaths
|
|
3
|
+
* @param {string} path
|
|
4
|
+
* @returns {bool}
|
|
5
|
+
*/
|
|
6
|
+
export const isPathIncluded = (includedPaths, path) => {
|
|
7
|
+
const isIncluded = includedPaths.some((includedPath) => {
|
|
8
|
+
if (path === includedPath) {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
return includedPath.startsWith(`${path}/`)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
return isIncluded
|
|
15
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//------------------------------------------------------------------------------
|
|
2
|
+
// <auto-generated>
|
|
3
|
+
// This code was generated by d2-i18n-generate.
|
|
4
|
+
//
|
|
5
|
+
// Changes to this file may cause incorrect behavior and will be lost if
|
|
6
|
+
// the code is regenerated.
|
|
7
|
+
// </auto-generated>
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
import i18n from '@dhis2/d2-i18n'
|
|
10
|
+
|
|
11
|
+
import arTranslations from './ar/translations.json'
|
|
12
|
+
import csTranslations from './cs/translations.json'
|
|
13
|
+
import enTranslations from './en/translations.json'
|
|
14
|
+
import esTranslations from './es/translations.json'
|
|
15
|
+
import es_419Translations from './es_419/translations.json'
|
|
16
|
+
import frTranslations from './fr/translations.json'
|
|
17
|
+
import loTranslations from './lo/translations.json'
|
|
18
|
+
import nbTranslations from './nb/translations.json'
|
|
19
|
+
import nlTranslations from './nl/translations.json'
|
|
20
|
+
import ptTranslations from './pt/translations.json'
|
|
21
|
+
import ruTranslations from './ru/translations.json'
|
|
22
|
+
import ukTranslations from './uk/translations.json'
|
|
23
|
+
import uz_LatnTranslations from './uz_Latn/translations.json'
|
|
24
|
+
import uz_UZ_CyrlTranslations from './uz_UZ_Cyrl/translations.json'
|
|
25
|
+
import uz_UZ_LatnTranslations from './uz_UZ_Latn/translations.json'
|
|
26
|
+
import viTranslations from './vi/translations.json'
|
|
27
|
+
import zhTranslations from './zh/translations.json'
|
|
28
|
+
import zh_CNTranslations from './zh_CN/translations.json'
|
|
29
|
+
|
|
30
|
+
const namespace = 'default'
|
|
31
|
+
i18n.addResources('ar', namespace, arTranslations)
|
|
32
|
+
i18n.addResources('cs', namespace, csTranslations)
|
|
33
|
+
i18n.addResources('en', namespace, enTranslations)
|
|
34
|
+
i18n.addResources('es', namespace, esTranslations)
|
|
35
|
+
i18n.addResources('es_419', namespace, es_419Translations)
|
|
36
|
+
i18n.addResources('fr', namespace, frTranslations)
|
|
37
|
+
i18n.addResources('lo', namespace, loTranslations)
|
|
38
|
+
i18n.addResources('nb', namespace, nbTranslations)
|
|
39
|
+
i18n.addResources('nl', namespace, nlTranslations)
|
|
40
|
+
i18n.addResources('pt', namespace, ptTranslations)
|
|
41
|
+
i18n.addResources('ru', namespace, ruTranslations)
|
|
42
|
+
i18n.addResources('uk', namespace, ukTranslations)
|
|
43
|
+
i18n.addResources('uz_Latn', namespace, uz_LatnTranslations)
|
|
44
|
+
i18n.addResources('uz_UZ_Cyrl', namespace, uz_UZ_CyrlTranslations)
|
|
45
|
+
i18n.addResources('uz_UZ_Latn', namespace, uz_UZ_LatnTranslations)
|
|
46
|
+
i18n.addResources('vi', namespace, viTranslations)
|
|
47
|
+
i18n.addResources('zh', namespace, zhTranslations)
|
|
48
|
+
i18n.addResources('zh_CN', namespace, zh_CNTranslations)
|
|
49
|
+
|
|
50
|
+
export default i18n
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isPathIncluded } from '../helpers/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns all the childrenIds that should be rendered.
|
|
5
|
+
* An id will be included if it's parent's path + the id is inside
|
|
6
|
+
* the "filter" or the parent's path + id is a substring
|
|
7
|
+
* of the paths in "filter" (then it's a parent path of
|
|
8
|
+
* the units that should be included itself)
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} node
|
|
11
|
+
* @param {Object[]} node.children
|
|
12
|
+
* @param {string[]} includedPaths
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
export const computeChildNodes = (node, filter) => {
|
|
16
|
+
if (!node.children) {
|
|
17
|
+
return []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!filter.length) {
|
|
21
|
+
return node.children
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return node.children.filter((child) => {
|
|
25
|
+
return isPathIncluded(filter, `${node.path}/${child.id}`)
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { computeChildNodes } from './compute-child-nodes.js'
|
|
2
|
+
|
|
3
|
+
describe('computeChildNodes', () => {
|
|
4
|
+
let node = { path: 'foo/bar' }
|
|
5
|
+
const defaultChildren = [
|
|
6
|
+
{ id: 'foobar' },
|
|
7
|
+
{ id: 'barbaz' },
|
|
8
|
+
{ id: 'foobarbaz' },
|
|
9
|
+
]
|
|
10
|
+
const defaultChildrenIds = defaultChildren
|
|
11
|
+
const irrelevantPaths = ['irrelevant/path']
|
|
12
|
+
const pathsToIncludeWithMatchingAncestors = [
|
|
13
|
+
'foo/bar/foobar/barbaz',
|
|
14
|
+
'foo/bar/barbaz/bazbazfoo',
|
|
15
|
+
]
|
|
16
|
+
const pathsToIncludeWithMatchingChildren = ['foo/bar/foobar']
|
|
17
|
+
const pathsToIncludeWithMatchingParents = ['foo/bar']
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
node = { path: 'foo/bar' }
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should return all children when filter is empty', () => {
|
|
24
|
+
node.children = defaultChildren
|
|
25
|
+
const filter = []
|
|
26
|
+
const actual = computeChildNodes(node, filter)
|
|
27
|
+
const expected = defaultChildrenIds
|
|
28
|
+
|
|
29
|
+
expect(actual).toEqual(expected)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("should return an empty array when the node has children but neither the children's or the parent path are in filter", () => {
|
|
33
|
+
node.children = defaultChildren
|
|
34
|
+
const filter = irrelevantPaths
|
|
35
|
+
const actual = computeChildNodes(node, filter)
|
|
36
|
+
const expected = []
|
|
37
|
+
|
|
38
|
+
expect(actual).toEqual(expected)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should return an empty array when filter contains none of the child paths but a parent path', () => {
|
|
42
|
+
node.children = defaultChildren
|
|
43
|
+
const filter = pathsToIncludeWithMatchingParents
|
|
44
|
+
const actual = computeChildNodes(node, filter)
|
|
45
|
+
const expected = []
|
|
46
|
+
|
|
47
|
+
expect(actual).toEqual(expected)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should return some the children ids when filter contains none of the child paths but some ancestor paths', () => {
|
|
51
|
+
node.children = defaultChildren
|
|
52
|
+
const filter = pathsToIncludeWithMatchingAncestors
|
|
53
|
+
const actual = computeChildNodes(node, filter)
|
|
54
|
+
const expected = [{ id: 'foobar' }, { id: 'barbaz' }]
|
|
55
|
+
|
|
56
|
+
expect(actual).toEqual(expected)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should return one of the children ids when filter contains one of the child paths but no parent path', () => {
|
|
60
|
+
node.children = defaultChildren
|
|
61
|
+
const filter = pathsToIncludeWithMatchingChildren
|
|
62
|
+
const actual = computeChildNodes(node, filter)
|
|
63
|
+
const expected = [{ id: 'foobar' }]
|
|
64
|
+
|
|
65
|
+
expect(actual).toEqual(expected)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should return an empty array when the node has no children but filter is not empty', () => {
|
|
69
|
+
node.children = []
|
|
70
|
+
const filter = pathsToIncludeWithMatchingParents
|
|
71
|
+
const actual = computeChildNodes(node, filter)
|
|
72
|
+
const expected = []
|
|
73
|
+
|
|
74
|
+
expect(actual).toEqual(expected)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should return an empty array when the node has no children and filter is empty', () => {
|
|
78
|
+
node.children = []
|
|
79
|
+
const filter = []
|
|
80
|
+
const actual = computeChildNodes(node, filter)
|
|
81
|
+
const expected = []
|
|
82
|
+
|
|
83
|
+
expect(actual).toEqual(expected)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { colors, spacers, theme } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export const ErrorMessage = ({ children, dataTest }) => (
|
|
6
|
+
<span data-test={`${dataTest}-error`}>
|
|
7
|
+
{children}
|
|
8
|
+
|
|
9
|
+
<style jsx>{`
|
|
10
|
+
span {
|
|
11
|
+
font-size: 14px;
|
|
12
|
+
color: ${colors.grey800};
|
|
13
|
+
margin: ${spacers.dp4};
|
|
14
|
+
color: ${theme.error};
|
|
15
|
+
}
|
|
16
|
+
`}</style>
|
|
17
|
+
</span>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
ErrorMessage.propTypes = {
|
|
21
|
+
children: PropTypes.any.isRequired,
|
|
22
|
+
dataTest: PropTypes.string.isRequired,
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks wether there are descendants of a path in the
|
|
3
|
+
* array of selected paths
|
|
4
|
+
*
|
|
5
|
+
* @param {string} path
|
|
6
|
+
* @param {string[]} selected
|
|
7
|
+
* @returns {bool}
|
|
8
|
+
*/
|
|
9
|
+
export const hasDescendantSelectedPaths = (path, selected) => {
|
|
10
|
+
return selected.some((selectedPath) => {
|
|
11
|
+
const isNotPath = !selectedPath.match(new RegExp(`${path}$`))
|
|
12
|
+
const isSubPath = selectedPath.match(new RegExp(path))
|
|
13
|
+
return isNotPath && isSubPath
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { hasDescendantSelectedPaths } from './has-descendant-selected-paths.js'
|
|
2
|
+
|
|
3
|
+
describe('hasDescendantSelectedPaths', () => {
|
|
4
|
+
it('should return true when the path is a parent path of one of the selected paths', () => {
|
|
5
|
+
const path = '/foo/bar'
|
|
6
|
+
const selected = ['/foo/bar/baz', '/foobar/barbaz/']
|
|
7
|
+
const expected = true
|
|
8
|
+
const actual = hasDescendantSelectedPaths(path, selected)
|
|
9
|
+
|
|
10
|
+
expect(actual).toBe(expected)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('should return false when the paths is not a parent path but an exact match with one of the selected paths', () => {
|
|
14
|
+
const path = '/foo/bar/baz'
|
|
15
|
+
const selected = ['/foo/bar/baz', '/foobar/barbaz/']
|
|
16
|
+
const expected = false
|
|
17
|
+
const actual = hasDescendantSelectedPaths(path, selected)
|
|
18
|
+
|
|
19
|
+
expect(actual).toBe(expected)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should return false when the paths is neither a parent path nor an exact match with one of the selected paths', () => {
|
|
23
|
+
const path = '/foobarbaz/boofarzab'
|
|
24
|
+
const selected = ['/foo/bar/baz', '/foobar/barbaz/']
|
|
25
|
+
const expected = false
|
|
26
|
+
const actual = hasDescendantSelectedPaths(path, selected)
|
|
27
|
+
|
|
28
|
+
expect(actual).toBe(expected)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OrganisationUnitNode } from './organisation-unit-node.js'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { SingleSelectionLabel } from './single-selection-label.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {Object} props
|
|
7
|
+
* @param {string} props.label
|
|
8
|
+
* @param {Function} props.onToggleOpen
|
|
9
|
+
* @param {bool} [props.loading]
|
|
10
|
+
* @returns {React.Component}
|
|
11
|
+
*/
|
|
12
|
+
export const DisabledSelectionLabel = ({ children, loading, onToggleOpen }) => (
|
|
13
|
+
<SingleSelectionLabel
|
|
14
|
+
checked={false}
|
|
15
|
+
loading={loading}
|
|
16
|
+
onChange={onToggleOpen}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</SingleSelectionLabel>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DisabledSelectionLabel.propTypes = {
|
|
23
|
+
children: PropTypes.any.isRequired,
|
|
24
|
+
onToggleOpen: PropTypes.func.isRequired,
|
|
25
|
+
loading: PropTypes.bool,
|
|
26
|
+
}
|