@dhis2-ui/transfer 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.
Files changed (109) hide show
  1. package/package.json +10 -9
  2. package/src/__e2e__/add_remove-highlighted-options.e2e.stories.js +30 -0
  3. package/src/__e2e__/common/options.js +90 -0
  4. package/src/__e2e__/common/stateful-decorator.js +33 -0
  5. package/src/__e2e__/common.js +0 -0
  6. package/src/__e2e__/disabled-transfer-buttons.e2e.stories.js +49 -0
  7. package/src/__e2e__/disabled-transfer-options.e2e.stories.js +21 -0
  8. package/src/__e2e__/display-order.e2e.stories.js +24 -0
  9. package/src/__e2e__/filter-options-list.e2e.stories.js +87 -0
  10. package/src/__e2e__/highlight-range-of-options.e2e.stories.js +52 -0
  11. package/src/__e2e__/loading_lists.e2e.stories.js +26 -0
  12. package/src/__e2e__/notify_at_end_of_list.e2e.stories.js +116 -0
  13. package/src/__e2e__/reorder-with-buttons.e2e.stories.js +35 -0
  14. package/src/__e2e__/set_unset-highlighted-option.e2e.stories.js +30 -0
  15. package/src/__e2e__/transferring-items.e2e.stories.js +27 -0
  16. package/src/__tests__/common.test.js +131 -0
  17. package/src/__tests__/helper/add-all-selectable-source-options.test.js +46 -0
  18. package/src/__tests__/helper/add-individual-source-options.test.js +80 -0
  19. package/src/__tests__/helper/default-filter-callback.test.js +45 -0
  20. package/src/__tests__/helper/is-reorder-down-disabled.test.js +96 -0
  21. package/src/__tests__/helper/is-reorder-up-disabled.test.js +96 -0
  22. package/src/__tests__/helper/move-highlighted-picked-option-down.test.js +111 -0
  23. package/src/__tests__/helper/move-highlighted-picked-option-to-bottom.test.js +101 -0
  24. package/src/__tests__/helper/move-highlighted-picked-option-to-top.test.js +101 -0
  25. package/src/__tests__/helper/move-highlighted-picked-option-up.test.js +111 -0
  26. package/src/__tests__/helper/remove-all-picked-options.test.js +29 -0
  27. package/src/__tests__/helper/remove-individual-picked-options.test.js +38 -0
  28. package/src/__tests__/helper/use-highlighted-option/create-toggle-highlighted-option.test.js +104 -0
  29. package/src/__tests__/helper/use-highlighted-option/toggle-add.test.js +84 -0
  30. package/src/__tests__/helper/use-highlighted-option/toggle-range.test.js +150 -0
  31. package/src/__tests__/helper/use-highlighted-option/toggle-replace.test.js +39 -0
  32. package/src/__tests__/helper/use-highlighted-option.test.js +41 -0
  33. package/src/__tests__/reordering-actions.test.js +165 -0
  34. package/src/__tests__/transfer.test.js +137 -0
  35. package/src/actions.js +33 -0
  36. package/src/add-all.js +27 -0
  37. package/src/add-individual.js +27 -0
  38. package/src/common/find-option-index.js +9 -0
  39. package/src/common/get-mode-by-modifier-key.js +35 -0
  40. package/src/common/index.js +5 -0
  41. package/src/common/is-option.js +7 -0
  42. package/src/common/modes.js +11 -0
  43. package/src/common/remove-option.js +19 -0
  44. package/src/common/toggle-value.js +18 -0
  45. package/src/container.js +23 -0
  46. package/src/end-intersection-detector.js +37 -0
  47. package/src/features/add_remove-highlighted-options/index.js +92 -0
  48. package/src/features/add_remove-highlighted-options.feature +41 -0
  49. package/src/features/common/index.js +8 -0
  50. package/src/features/disabled-transfer-buttons/index.js +118 -0
  51. package/src/features/disabled-transfer-buttons.feature +46 -0
  52. package/src/features/disabled-transfer-options/index.js +182 -0
  53. package/src/features/disabled-transfer-options.feature +42 -0
  54. package/src/features/display-order/index.js +205 -0
  55. package/src/features/display-order.feature +30 -0
  56. package/src/features/filter-options-list/index.js +133 -0
  57. package/src/features/filter-options-list.feature +40 -0
  58. package/src/features/highlight-range-of-options/index.js +336 -0
  59. package/src/features/highlight-range-of-options.feature +70 -0
  60. package/src/features/loading_lists/index.js +43 -0
  61. package/src/features/loading_lists.feature +19 -0
  62. package/src/features/notify_at_end_of_list/index.js +125 -0
  63. package/src/features/notify_at_end_of_list.feature +64 -0
  64. package/src/features/reorder-with-buttons/index.js +181 -0
  65. package/src/features/reorder-with-buttons.feature +138 -0
  66. package/src/features/set_unset-highlighted-option/index.js +121 -0
  67. package/src/features/set_unset-highlighted-option.feature +42 -0
  68. package/src/features/transferring-items/index.js +375 -0
  69. package/src/features/transferring-items.feature +44 -0
  70. package/src/filter.js +38 -0
  71. package/src/icons.js +194 -0
  72. package/src/index.js +2 -0
  73. package/src/left-footer.js +22 -0
  74. package/src/left-header.js +22 -0
  75. package/src/left-side.js +34 -0
  76. package/src/locales/en/translations.json +7 -0
  77. package/src/locales/index.js +16 -0
  78. package/src/options-container.js +127 -0
  79. package/src/remove-all.js +27 -0
  80. package/src/remove-individual.js +27 -0
  81. package/src/reordering-actions.js +136 -0
  82. package/src/right-footer.js +22 -0
  83. package/src/right-header.js +22 -0
  84. package/src/right-side.js +33 -0
  85. package/src/transfer/add-all-selectable-source-options.js +37 -0
  86. package/src/transfer/add-individual-source-options.js +61 -0
  87. package/src/transfer/create-double-click-handlers.js +36 -0
  88. package/src/transfer/default-filter-callback.js +17 -0
  89. package/src/transfer/get-highlighted-picked-indices.js +26 -0
  90. package/src/transfer/get-option-click-handlers.js +19 -0
  91. package/src/transfer/index.js +17 -0
  92. package/src/transfer/is-reorder-down-disabled.js +34 -0
  93. package/src/transfer/is-reorder-up-disabled.js +30 -0
  94. package/src/transfer/move-highlighted-picked-option-down.js +54 -0
  95. package/src/transfer/move-highlighted-picked-option-to-bottom.js +44 -0
  96. package/src/transfer/move-highlighted-picked-option-to-top.js +38 -0
  97. package/src/transfer/move-highlighted-picked-option-up.js +47 -0
  98. package/src/transfer/remove-all-picked-options.js +13 -0
  99. package/src/transfer/remove-individual-picked-options.js +49 -0
  100. package/src/transfer/use-filter.js +17 -0
  101. package/src/transfer/use-highlighted-options/create-toggle-highlighted-option.js +64 -0
  102. package/src/transfer/use-highlighted-options/toggle-add.js +20 -0
  103. package/src/transfer/use-highlighted-options/toggle-range.js +61 -0
  104. package/src/transfer/use-highlighted-options/toggle-replace.js +26 -0
  105. package/src/transfer/use-highlighted-options.js +34 -0
  106. package/src/transfer/use-options-key-monitor.js +41 -0
  107. package/src/transfer-option.js +91 -0
  108. package/src/transfer.js +539 -0
  109. package/src/transfer.prod.stories.js +621 -0
@@ -0,0 +1,205 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+ import { extractOptionFromElement } from '../common/index.js'
3
+
4
+ Given('none of the supplied options have been selected', () => {
5
+ cy.visitStory('Transfer Display Order', 'No Selection')
6
+ })
7
+
8
+ Given('some of the supplied options have been selected', () => {
9
+ cy.visitStory('Transfer Display Order', 'Some Selected')
10
+ })
11
+
12
+ Given('some selectable options are selectable', () => {
13
+ cy.visitStory('Transfer Display Order', 'Some Selected')
14
+ })
15
+
16
+ When('the user deselects one selected option', () => {
17
+ cy.get('{transfer-pickedoptions} {transferoption}').first().click()
18
+ cy.get('{transfer-pickedoptions}')
19
+ .find('.highlighted')
20
+ .then(($el) => $el.toArray().map(extractOptionFromElement))
21
+ .as('deselectedOptions')
22
+ cy.get('{transfer-actions-removeindividual}').click()
23
+ })
24
+
25
+ When('the user deselects multiple selected options', () => {
26
+ cy.get('{transfer-pickedoptions} {transferoption}')
27
+ // should take third, then first item
28
+ .then(($options) => {
29
+ const $arr = $options.toArray()
30
+ return [$arr[2], $arr[0]]
31
+ })
32
+ .each(($option) => cy.wrap($option).clickWith('cmd'))
33
+ .then(($options) =>
34
+ cy
35
+ .wrap($options.toArray().map(extractOptionFromElement))
36
+ .as('deselectedOptions')
37
+ )
38
+
39
+ cy.get('{transfer-actions-removeindividual}').click()
40
+ })
41
+
42
+ When('the user selects one option', () => {
43
+ cy.get('{transfer-sourceoptions} {transferoption}').first().click()
44
+ cy.get('{transfer-sourceoptions}')
45
+ .find('.highlighted')
46
+ .then(($el) => $el.toArray().map(extractOptionFromElement))
47
+ .as('selectedOptions')
48
+ cy.get('{transfer-actions-addindividual}').click()
49
+ })
50
+
51
+ When('the user selects multiple options', () => {
52
+ cy.get('{transfer-sourceoptions} {transferoption}')
53
+ // should take fifth, then first item
54
+ .then(($options) => {
55
+ const $arr = $options.toArray()
56
+ return [$arr[4], $arr[0]]
57
+ })
58
+ .each(($option) => cy.wrap($option).clickWith('ctrl'))
59
+ .then(($options) => {
60
+ cy.wrap($options.toArray().map(extractOptionFromElement)).as(
61
+ 'selectedOptions'
62
+ )
63
+ })
64
+
65
+ cy.get('{transfer-actions-addindividual}').click()
66
+ })
67
+
68
+ Then(
69
+ 'the order of the selectable options should match the order of the supplied options',
70
+ () => {
71
+ cy.all(
72
+ () => cy.window(),
73
+ () => cy.get('{transfer-sourceoptions} {transferoption}')
74
+ ).should(([win, $options]) => {
75
+ const { options } = win
76
+ const selectableSourceOptions = $options
77
+ .toArray()
78
+ .map(extractOptionFromElement)
79
+
80
+ expect(selectableSourceOptions).to.eql(options)
81
+ })
82
+ }
83
+ )
84
+
85
+ Then(
86
+ 'the order of the remaining selectable options should match the order of the supplied options',
87
+ () => {
88
+ cy.all(
89
+ () => cy.window(),
90
+ () => cy.get('{transfer-sourceoptions} {transferoption}')
91
+ ).should(([win, $options]) => {
92
+ const selectableSourceOptions = $options
93
+ .toArray()
94
+ .map(extractOptionFromElement)
95
+
96
+ const options = win.options.filter((option) =>
97
+ selectableSourceOptions.find(
98
+ ({ label, value }) =>
99
+ label === option.label && value === option.value
100
+ )
101
+ )
102
+
103
+ expect(selectableSourceOptions).to.eql(options)
104
+ })
105
+ }
106
+ )
107
+
108
+ Then(
109
+ 'it should be positioned according to the order of the supplied options',
110
+ () => {
111
+ cy.all(
112
+ () => cy.window(),
113
+ () => cy.get('{transfer-sourceoptions} {transferoption}'),
114
+ () => cy.get('@deselectedOptions')
115
+ ).should(([win, $selectableSourceOptions, deselectedOptions]) => {
116
+ // filter out non-selectable options and compare with selectable options
117
+ // this confirms that the order is correct
118
+ const selectableSourceOptions = $selectableSourceOptions
119
+ .toArray()
120
+ .map(extractOptionFromElement)
121
+ const originalOptions = win.options.filter((option) =>
122
+ selectableSourceOptions.find(
123
+ ({ label, value }) =>
124
+ label === option.label && value === option.value
125
+ )
126
+ )
127
+ expect(selectableSourceOptions).to.eql(originalOptions)
128
+
129
+ // and confirm that the deselected option is in the selectable options list
130
+ const [deselectedOption] = deselectedOptions
131
+ const hasOption = !!selectableSourceOptions.find(
132
+ ({ label, value }) =>
133
+ label === deselectedOption.label &&
134
+ value === deselectedOption.value
135
+ )
136
+ expect(hasOption).to.equal(true)
137
+ })
138
+ }
139
+ )
140
+
141
+ Then(
142
+ 'all should take the position according to the order of the supplied options',
143
+ () => {
144
+ cy.all(
145
+ () => cy.window(),
146
+ () => cy.get('{transfer-sourceoptions} {transferoption}'),
147
+ () => cy.get('@deselectedOptions')
148
+ ).should(([win, $selectableSourceOptions, deselectedOptions]) => {
149
+ const selectableSourceOptions = $selectableSourceOptions
150
+ .toArray()
151
+ .map(extractOptionFromElement)
152
+ const options = win.options.filter((option) =>
153
+ selectableSourceOptions.find(
154
+ ({ label, value }) =>
155
+ label === option.label && value === option.value
156
+ )
157
+ )
158
+ expect(selectableSourceOptions).to.eql(options)
159
+
160
+ const hasAllOptions = deselectedOptions.every(
161
+ (deselectedOption) => {
162
+ const result = !!selectableSourceOptions.find(
163
+ ({ label, value }) =>
164
+ label === deselectedOption.label &&
165
+ value === deselectedOption.value
166
+ )
167
+
168
+ return result
169
+ }
170
+ )
171
+ expect(hasAllOptions).to.equal(true)
172
+ })
173
+ }
174
+ )
175
+
176
+ Then('it should be added to the end of the selected options list', () => {
177
+ cy.all(
178
+ () => cy.get('@selectedOptions'),
179
+ () => cy.get('{transfer-pickedoptions} {transferoption}')
180
+ ).should(([transferredOptions, $selectedOptions]) => {
181
+ const lastSelectedOptions = $selectedOptions
182
+ .toArray()
183
+ .slice(transferredOptions.length * -1)
184
+ .map(extractOptionFromElement)
185
+
186
+ expect(transferredOptions).to.eql(lastSelectedOptions)
187
+ })
188
+ })
189
+
190
+ Then(
191
+ 'they should be added to the end of the selected options list in the order they have been highlighted',
192
+ () => {
193
+ cy.all(
194
+ () => cy.get('@selectedOptions'),
195
+ () => cy.get('{transfer-pickedoptions} {transferoption}')
196
+ ).should(([transferredOptions, $selectedOptions]) => {
197
+ const lastSelectedOptions = $selectedOptions
198
+ .toArray()
199
+ .slice(transferredOptions.length * -1)
200
+ .map(extractOptionFromElement)
201
+
202
+ expect(transferredOptions).to.eql(lastSelectedOptions)
203
+ })
204
+ }
205
+ )
@@ -0,0 +1,30 @@
1
+ @component-transfer @display-ordering
2
+ Feature: Display order of items in lists
3
+
4
+ Scenario: All supplied options are rendered in the options-side
5
+ Given none of the supplied options have been selected
6
+ Then the order of the selectable options should match the order of the supplied options
7
+
8
+ Scenario: Some of the supplied options have been selected
9
+ Given some of the supplied options have been selected
10
+ Then the order of the remaining selectable options should match the order of the supplied options
11
+
12
+ Scenario: A single selected options is deselected
13
+ Given some of the supplied options have been selected
14
+ When the user deselects one selected option
15
+ Then it should be positioned according to the order of the supplied options
16
+
17
+ Scenario: Multiple selected options are deselected
18
+ Given some of the supplied options have been selected
19
+ When the user deselects multiple selected options
20
+ Then all should take the position according to the order of the supplied options
21
+
22
+ Scenario: An selectable option gets selected
23
+ Given some selectable options are selectable
24
+ When the user selects one option
25
+ Then it should be added to the end of the selected options list
26
+
27
+ Scenario: Multiple selectable options get selected
28
+ Given some selectable options are selectable
29
+ When the user selects multiple options
30
+ Then they should be added to the end of the selected options list in the order they have been highlighted
@@ -0,0 +1,133 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+ import { extractOptionFromElement } from '../common/index.js'
3
+
4
+ Given('filtering is enabled', () => {
5
+ // no op
6
+ })
7
+
8
+ Given('the options list is being filtered', () => {
9
+ // no op
10
+ })
11
+
12
+ Given('the result is not empty', () => {
13
+ cy.visitStory('Transfer filtering', 'Some Results')
14
+ })
15
+
16
+ Given('the result is empty', () => {
17
+ cy.visitStory('Transfer filtering', 'Empty Result')
18
+ })
19
+
20
+ Given('a no-result message has been provided', () => {
21
+ // no op
22
+ })
23
+
24
+ Given(
25
+ 'the options are being search with a {string} search term',
26
+ (firstCase) => {
27
+ if (firstCase === 'uppercase') {
28
+ cy.visitStory('Transfer filtering', 'Uppercase Search')
29
+ } else if (firstCase === 'lowercase') {
30
+ cy.visitStory('Transfer filtering', 'Lowercase Search')
31
+ }
32
+
33
+ cy.get('{transfer-filter} input')
34
+ .then(($input) => $input.val())
35
+ .as('firstCaseTerm')
36
+ }
37
+ )
38
+
39
+ Given('some options are listed', () => {
40
+ cy.get('{transfer-sourceoptions} {transferoption}')
41
+ .should('have.length.of.at.least', 1)
42
+ .as('firstCaseOptions')
43
+ })
44
+
45
+ Given('the filter function only returns ANC options', () => {
46
+ cy.visitStory('Transfer filtering', 'Anc Custom Filter')
47
+ })
48
+
49
+ Given("the filter value is controlled by the component's consumer", () => {
50
+ cy.visitStory('Transfer filtering', 'Controlled Filter')
51
+ })
52
+
53
+ When('the user uses the same search term but {string}', (secondCase) => {
54
+ cy.all(
55
+ () => cy.get('@firstCaseTerm'),
56
+ () => cy.get('{transfer-filter} input')
57
+ ).then(([firstCaseTerm, $filterInput]) => {
58
+ let secondCaseTerm
59
+
60
+ if (secondCase === 'uppercase') {
61
+ secondCaseTerm = firstCaseTerm.toUpperCase()
62
+ } else if (secondCase === 'lowercase') {
63
+ secondCaseTerm = firstCaseTerm.toLowerCase()
64
+ }
65
+
66
+ cy.wrap($filterInput).clear().type(secondCaseTerm)
67
+ })
68
+ })
69
+
70
+ When('searching for "s"', () => {
71
+ cy.get('{transfer-filter} input').type('s')
72
+ })
73
+
74
+ When('the filter value changes', () => {
75
+ cy.get('{transfer-filter} input')
76
+ .then(($input) => console.log('$input', $input) || $input)
77
+ .type('ANC')
78
+ })
79
+
80
+ Then('all the matching items should be shown in the options list', () => {
81
+ cy.all(
82
+ () => cy.get('{transfer-filter}'),
83
+ () => cy.get('{transferoption}')
84
+ ).should(([$filter, $options]) => {
85
+ const searchTerm = $filter.val()
86
+
87
+ expect($options).to.have.length.of.at.least(1)
88
+ $options.each((index, option) => {
89
+ const text = Cypress.$(option).text()
90
+ expect(text).to.match(new RegExp(searchTerm))
91
+ })
92
+ })
93
+ })
94
+
95
+ Then('no items should be shown in the options list', () => {
96
+ cy.get('{transferoption}').should('not.exist')
97
+ })
98
+
99
+ Then('information should be displayed that no items matched the filter', () => {
100
+ cy.get('{no-results}', { prefix: '' }).should('exist')
101
+ })
102
+
103
+ Then('the same options should be shown', () => {
104
+ cy.all(
105
+ () => cy.get('@firstCaseOptions'),
106
+ () => cy.get('{transfer-sourceoptions} {transferoption}')
107
+ ).should(([$firstCaseOptions, $secondCaseOptions]) => {
108
+ const firstCaseOptions = $firstCaseOptions
109
+ .toArray()
110
+ .map(extractOptionFromElement)
111
+ const secondCaseOptions = $secondCaseOptions
112
+ .toArray()
113
+ .map(extractOptionFromElement)
114
+
115
+ expect(firstCaseOptions).to.eql(secondCaseOptions)
116
+ })
117
+ })
118
+
119
+ Then('only the results including the word "ANC" are included', () => {
120
+ cy.get('{transfer-sourceoptions} {transferoption}').each(($option) =>
121
+ expect($option.text()).to.match(/ANC/)
122
+ )
123
+ })
124
+
125
+ Then('the onFilterChange callback will be called with the new value', () => {
126
+ cy.window().should((win) => {
127
+ expect(win.customFilterCallback.callCount).to.equal(4)
128
+ expect(win.customFilterCallback.getCall(0).args[1]).to.equal('')
129
+ expect(win.customFilterCallback.getCall(1).args[1]).to.equal('A')
130
+ expect(win.customFilterCallback.getCall(2).args[1]).to.equal('AN')
131
+ expect(win.customFilterCallback.getCall(3).args[1]).to.equal('ANC')
132
+ })
133
+ })
@@ -0,0 +1,40 @@
1
+ @component-transfer @filtering
2
+ Feature: Filter options list
3
+
4
+ Background:
5
+ Given filtering is enabled
6
+ And the options list is being filtered
7
+
8
+ Scenario: Filtering with results
9
+ Given the result is not empty
10
+ Then all the matching items should be shown in the options list
11
+
12
+ Scenario: Filtering without results
13
+ Given the result is empty
14
+ Then no items should be shown in the options list
15
+
16
+ Scenario: Displaying no-result message
17
+ Given the result is empty
18
+ And a no-result message has been provided
19
+ Then information should be displayed that no items matched the filter
20
+
21
+ Scenario Outline: Filter shows result regardless of uppercase and lowercase
22
+ Given the options are being search with a "<firstCase>" search term
23
+ And some options are listed
24
+ When the user uses the same search term but "<secondCase>"
25
+ Then the same options should be shown
26
+
27
+ Examples:
28
+ | firstCase | secondCase |
29
+ | uppercase | lowercase |
30
+ | lowercase | uppercase |
31
+
32
+ Scenario: The app defines a custom filtering method
33
+ Given the filter function only returns ANC options
34
+ When searching for "s"
35
+ Then only the results including the word "ANC" are included
36
+
37
+ Scenario: The filter value can be controlled by the app
38
+ Given the filter value is controlled by the component's consumer
39
+ When the filter value changes
40
+ Then the onFilterChange callback will be called with the new value