@rancher/shell 0.3.16 → 0.3.18

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 (174) hide show
  1. package/assets/images/wechat-qr-code.jpg +0 -0
  2. package/assets/translations/en-us.yaml +75 -16
  3. package/assets/translations/zh-hans.yaml +151 -15
  4. package/chart/__tests__/S3.test.ts +50 -0
  5. package/chart/rancher-backup/S3.vue +21 -0
  6. package/chart/rancher-backup/index.vue +4 -0
  7. package/components/AsyncButton.vue +1 -1
  8. package/components/CommunityLinks.vue +1 -0
  9. package/components/FileDiff.vue +92 -85
  10. package/components/Inactivity.vue +10 -0
  11. package/components/LazyImage.vue +2 -2
  12. package/components/PromptRestore.vue +7 -5
  13. package/components/ResourceDetail/Masthead.vue +1 -1
  14. package/components/ResourceDetail/index.vue +8 -14
  15. package/components/ResourceList/index.vue +1 -1
  16. package/components/ResourceTable.vue +50 -2
  17. package/components/YamlEditor.vue +1 -0
  18. package/components/__tests__/PromptRestore.test.ts +72 -0
  19. package/components/auth/AzureWarning.vue +1 -1
  20. package/components/auth/RoleDetailEdit.vue +1 -0
  21. package/components/fleet/FleetResources.vue +3 -64
  22. package/components/form/FileImageSelector.vue +9 -0
  23. package/components/form/FileSelector.vue +2 -1
  24. package/components/form/MatchExpressions.vue +1 -3
  25. package/components/form/NameNsDescription.vue +28 -12
  26. package/components/form/NodeAffinity.vue +2 -2
  27. package/components/form/PodAffinity.vue +2 -2
  28. package/components/form/ResourceTabs/index.vue +8 -2
  29. package/components/form/Select.vue +16 -0
  30. package/components/form/__tests__/FileImageSelector.test.ts +42 -0
  31. package/components/form/__tests__/FileSelector.test.ts +76 -0
  32. package/components/form/__tests__/NodeAffinity.test.ts +38 -0
  33. package/components/form/__tests__/PodAffinity.test.ts +46 -0
  34. package/components/formatter/ClusterLink.vue +8 -4
  35. package/components/formatter/ClusterProvider.vue +3 -1
  36. package/components/formatter/ImageName.vue +23 -0
  37. package/components/formatter/PodImages.vue +7 -1
  38. package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
  39. package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
  40. package/components/nav/Header.vue +2 -2
  41. package/components/nav/WindowManager/ContainerShell.vue +60 -36
  42. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
  43. package/config/__test__/home-links.test.ts +62 -0
  44. package/config/home-links.js +15 -3
  45. package/config/labels-annotations.js +7 -2
  46. package/config/persistentVolume.ts +108 -0
  47. package/config/product/manager.js +5 -1
  48. package/config/router.js +0 -4
  49. package/config/settings.ts +4 -0
  50. package/config/table-headers.js +6 -5
  51. package/config/types.js +2 -0
  52. package/config/uiplugins.js +50 -5
  53. package/core/plugin-helpers.js +39 -15
  54. package/core/plugin.ts +9 -0
  55. package/core/plugins.js +1 -1
  56. package/core/types-provisioning.ts +253 -0
  57. package/core/types.ts +21 -3
  58. package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
  59. package/detail/fleet.cattle.io.gitrepo.vue +10 -2
  60. package/detail/node.vue +6 -6
  61. package/detail/pod.vue +38 -9
  62. package/detail/provisioning.cattle.io.cluster.vue +46 -7
  63. package/detail/workload/index.vue +49 -18
  64. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
  65. package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
  66. package/edit/auth/github.vue +1 -0
  67. package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
  68. package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
  69. package/edit/fleet.cattle.io.clustergroup.vue +14 -3
  70. package/edit/fleet.cattle.io.gitrepo.vue +18 -1
  71. package/edit/namespace.vue +9 -1
  72. package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
  73. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
  74. package/edit/persistentvolume/index.vue +2 -1
  75. package/edit/persistentvolume/plugins/csi.vue +3 -1
  76. package/edit/persistentvolume/plugins/longhorn.vue +12 -12
  77. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
  78. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
  79. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
  80. package/edit/provisioning.cattle.io.cluster/index.vue +53 -1
  81. package/edit/provisioning.cattle.io.cluster/rke2.vue +335 -151
  82. package/edit/storage.k8s.io.storageclass/index.vue +1 -2
  83. package/edit/ui.cattle.io.navlink.vue +213 -186
  84. package/initialize/App.js +3 -13
  85. package/initialize/layouts.ts +26 -0
  86. package/layouts/default.vue +1 -1
  87. package/list/group.principal.vue +1 -1
  88. package/list/provisioning.cattle.io.cluster.vue +8 -1
  89. package/middleware/authenticated.js +101 -5
  90. package/mixins/brand.js +39 -3
  91. package/mixins/child-hook.js +2 -2
  92. package/mixins/create-edit-view/impl.js +4 -4
  93. package/models/chart.js +1 -1
  94. package/models/fleet.cattle.io.cluster.js +33 -4
  95. package/models/fleet.cattle.io.gitrepo.js +113 -38
  96. package/models/management.cattle.io.kontainerdriver.js +14 -0
  97. package/models/persistentvolume.js +2 -111
  98. package/models/pod.js +30 -0
  99. package/models/provisioning.cattle.io.cluster.js +9 -1
  100. package/models/rke.cattle.io.etcdsnapshot.js +10 -7
  101. package/package.json +2 -2
  102. package/pages/about.vue +8 -2
  103. package/pages/auth/login.vue +1 -1
  104. package/pages/auth/logout.vue +11 -3
  105. package/pages/c/_cluster/apps/charts/index.vue +5 -2
  106. package/pages/c/_cluster/apps/charts/install.vue +5 -0
  107. package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
  108. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  109. package/pages/c/_cluster/explorer/index.vue +2 -11
  110. package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
  111. package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
  112. package/pages/c/_cluster/settings/brand.vue +11 -8
  113. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
  114. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
  115. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
  116. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
  117. package/pages/c/_cluster/uiplugins/index.vue +160 -44
  118. package/pages/docs/_doc.vue +9 -3
  119. package/pages/home.vue +6 -6
  120. package/pages/support/index.vue +10 -4
  121. package/pkg/auto-import.js +1 -1
  122. package/plugins/clean-tooltip-directive.js +1 -1
  123. package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
  124. package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
  125. package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
  126. package/plugins/dashboard-store/actions.js +1 -1
  127. package/plugins/dashboard-store/resource-class.js +39 -2
  128. package/plugins/plugin.js +9 -1
  129. package/plugins/steve/__tests__/getters.spec.ts +93 -0
  130. package/plugins/steve/getters.js +21 -1
  131. package/plugins/steve/subscribe.js +1 -3
  132. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  133. package/rancher-components/Banner/Banner.test.ts +51 -1
  134. package/rancher-components/Banner/Banner.vue +134 -53
  135. package/rancher-components/Card/Card.test.ts +37 -0
  136. package/rancher-components/Card/Card.vue +24 -7
  137. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  138. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  139. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  140. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  141. package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
  142. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  143. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  144. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  145. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  146. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  147. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  148. package/rancher-components/StringList/StringList.test.ts +453 -49
  149. package/rancher-components/StringList/StringList.vue +44 -26
  150. package/scripts/extension/publish +2 -2
  151. package/scripts/typegen.sh +11 -2
  152. package/server/server-middleware.js +4 -12
  153. package/store/index.js +14 -3
  154. package/store/prefs.js +0 -3
  155. package/store/store-types.js +2 -0
  156. package/store/type-map.js +17 -29
  157. package/types/api.d.ts +1 -0
  158. package/types/fleet.d.ts +1 -0
  159. package/types/shell/index.d.ts +931 -85
  160. package/types/userPreferences.d.ts +1 -1
  161. package/utils/__mocks__/socket.js +21 -0
  162. package/utils/grafana.js +23 -11
  163. package/utils/kube.js +9 -0
  164. package/utils/object.js +27 -0
  165. package/utils/selector.js +2 -1
  166. package/utils/settings.ts +2 -2
  167. package/utils/validators/formRules/index.ts +3 -3
  168. package/vue.config.js +3 -2
  169. package/components/.DS_Store +0 -0
  170. package/components/__tests__/.DS_Store +0 -0
  171. package/creators/pkg/package-lock.json +0 -37
  172. package/pages/safeMode.vue +0 -17
  173. package/plugins/steve/urloptions.js +0 -47
  174. package/yarn-error.log +0 -196
@@ -1,80 +1,484 @@
1
- import { mount } from '@vue/test-utils';
1
+ /* eslint-disable jest/no-hooks */
2
+ import { mount, Wrapper } from '@vue/test-utils';
2
3
  import { StringList } from './index';
3
4
 
4
- describe('StringList.vue', () => {
5
+ describe('stringList.vue', () => {
6
+ let wrapper: Wrapper<InstanceType<typeof StringList>>;
5
7
 
6
- it('is empty', () => {
7
- const wrapper = mount(StringList, {
8
- propsData: { items: [] },
8
+ beforeEach(() => {
9
+ wrapper = mount(StringList, { propsData: { items: [] } });
10
+ });
11
+
12
+ describe('list box', () => {
13
+ it('is empty', () => {
14
+ const box = wrapper.find('[data-testid="div-string-list-box"]').element as HTMLElement;
15
+
16
+ expect(box.children).toHaveLength(0);
9
17
  });
10
- const box = wrapper.find('[data-testid="div-string-list-box"]').element as HTMLElement;
11
18
 
12
- expect(box.children.length).toBe(0);
13
- });
19
+ it('show multiple items', async() => {
20
+ const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'];
21
+
22
+ await wrapper.setProps({ items });
23
+
24
+ const elements = wrapper.findAll('[data-testid^="div-item"]');
14
25
 
15
- it('is showing one element', () => {
16
- const wrapper = mount(StringList, {
17
- propsData: { items: ['test'] },
26
+ expect(elements).toHaveLength(10);
18
27
  });
19
- const box = wrapper.find('.string-list-box').element as HTMLElement;
20
28
 
21
- expect(box.children.length).toBe(1);
22
- });
29
+ it('double click triggers inline edit mode', async() => {
30
+ const items = ['test'];
31
+
32
+ await wrapper.setProps({ items });
33
+
34
+ const item = wrapper.find('[data-testid="div-item-test"]');
35
+
36
+ await item.trigger('dblclick');
37
+
38
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
23
39
 
24
- it('action-buttons are visible', () => {
25
- const wrapper = mount(StringList, {
26
- propsData: { items: ['test'] },
40
+ expect(inputField.element).toBeDefined();
27
41
  });
28
- const actionButtons = wrapper.find('[data-testid="div-action-buttons"]').element as HTMLElement;
29
42
 
30
- expect(actionButtons).not.toBe(undefined);
31
- });
43
+ it('double click on empty space triggers create mode', async() => {
44
+ await wrapper.setProps({ items: [] });
45
+
46
+ // double click on empty space
47
+ const box = wrapper.find('[data-testid="div-string-list-box"]');
48
+
49
+ await box.trigger('dblclick');
50
+
51
+ const inputField = wrapper.find('[data-testid="item-create"]');
52
+
53
+ expect(inputField.element).toBeDefined();
54
+ });
55
+
56
+ it('select item when click on it', async() => {
57
+ const items = ['test'];
58
+
59
+ await wrapper.setProps({ items });
60
+
61
+ // select item
62
+ const item = wrapper.find('[data-testid^="div-item"]');
63
+
64
+ await item.trigger('mousedown');
65
+
66
+ expect(item.element.className).toContain('selected');
67
+ });
68
+
69
+ it('double click to edit item not allowed when readonly', async() => {
70
+ const items = ['test'];
32
71
 
33
- it('action-buttons are hidden when is view-only mode', () => {
34
- const wrapper = mount(StringList, {
35
- propsData: {
36
- items: ['test'],
72
+ await wrapper.setProps({
73
+ items,
37
74
  readonly: true,
38
- },
75
+ });
76
+
77
+ const item = wrapper.find('[data-testid="div-item-test"]');
78
+
79
+ await item.trigger('dblclick');
80
+
81
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
82
+
83
+ expect(inputField.element).toBeUndefined();
39
84
  });
40
- const actionButtons = wrapper.find('[data-testid="div-action-buttons"]').element as HTMLElement;
41
85
 
42
- expect(actionButtons).toBe(undefined);
43
- });
86
+ it('double click on empty space to create item not allowed when readonly', async() => {
87
+ await wrapper.setProps({
88
+ items: [],
89
+ readonly: true,
90
+ });
91
+
92
+ // double click on empty space
93
+ const box = wrapper.find('[data-testid="div-string-list-box"]');
94
+
95
+ await box.trigger('dblclick');
44
96
 
45
- it('show new item when "items" property change', async () => {
46
- const items = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'];
97
+ const inputField = wrapper.find('[data-testid="item-create"]');
47
98
 
48
- const wrapper = mount(StringList, {
49
- propsData: {
99
+ expect(inputField.element).toBeUndefined();
100
+ });
101
+
102
+ it('select item not allowed when readonly', async() => {
103
+ const items = ['test'];
104
+
105
+ await wrapper.setProps({
50
106
  items,
51
- },
107
+ readonly: true,
108
+ });
109
+
110
+ // select item
111
+ const item = wrapper.find('[data-testid^="div-item"]');
112
+
113
+ await item.trigger('mousedown');
114
+
115
+ expect(item.element.className).not.toContain('selected');
52
116
  });
53
- const elements = wrapper.findAll('[data-testid^="div-item"]');
54
117
 
55
- expect(elements.length).toBe(10);
118
+ it('emit type:item event', async() => {
119
+ // activate create mode
120
+ await wrapper.setData({ isCreateItem: true });
121
+
122
+ const inputField = wrapper.find('[data-testid="item-create"]');
123
+
124
+ // set input value to 'F'
125
+ await inputField.setValue('F');
126
+ await wrapper.vm.$nextTick();
56
127
 
57
- await wrapper.setProps({ items: [ ...items, 'new' ] });
128
+ const emitted = (wrapper.emitted('type:item') || [])[0][0][0];
58
129
 
59
- const newElements = wrapper.findAll('[data-testid^="div-item"]');
60
- expect(newElements.length).toBe(11);
130
+ expect(emitted).toBe('F');
131
+ });
61
132
  });
62
133
 
63
- it('remove item when "items" property change', async () => {
64
- const items = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'];
134
+ describe('buttons', () => {
135
+ it('are visible by default', () => {
136
+ const actionButtons = wrapper.find('[data-testid="div-action-buttons"]');
65
137
 
66
- const wrapper = mount(StringList, {
67
- propsData: {
68
- items,
69
- },
138
+ expect(actionButtons.element).toBeDefined();
139
+ });
140
+
141
+ it('are hidden when is view-only mode', async() => {
142
+ await wrapper.setProps({ readonly: true });
143
+ const actionButtons = wrapper.find('[data-testid="div-action-buttons"]');
144
+
145
+ expect(actionButtons.element).toBeUndefined();
146
+ });
147
+
148
+ describe('add button', () => {
149
+ it('is enabled by default', () => {
150
+ const addButton = wrapper.find('[data-testid="button-add"]')?.element as HTMLButtonElement;
151
+
152
+ expect(addButton.disabled).toBe(false);
153
+ });
154
+
155
+ it('show the input field when is clicked', async() => {
156
+ // click add button
157
+ const addButton = wrapper.find('[data-testid="button-add"]');
158
+
159
+ await addButton.trigger('click');
160
+
161
+ const inputField = wrapper.find('[data-testid="item-create"]');
162
+
163
+ expect(inputField.element).toBeDefined();
164
+ });
165
+
166
+ it('is disabled when create mode is active', async() => {
167
+ // click add button
168
+ const addButton = wrapper.find('[data-testid="button-add"]');
169
+
170
+ await addButton.trigger('click');
171
+
172
+ wrapper.find('[data-testid="item-create"]');
173
+
174
+ const buttonElem = addButton.element as HTMLButtonElement;
175
+
176
+ expect(buttonElem.disabled).toBe(true);
177
+ });
178
+ });
179
+
180
+ describe('remove button', () => {
181
+ it('is disabled by default', () => {
182
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
183
+ const buttonElem = removeButton.element as HTMLButtonElement;
184
+
185
+ expect(buttonElem.disabled).toBe(true);
186
+ });
187
+
188
+ it('is enabled when create mode is active', async() => {
189
+ // click add button
190
+ const addButton = wrapper.find('[data-testid="button-add"]');
191
+
192
+ await addButton.trigger('click');
193
+
194
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
195
+ const buttonElem = removeButton.element as HTMLButtonElement;
196
+
197
+ expect(buttonElem.disabled).toBe(false);
198
+ });
199
+
200
+ it('is enabled when edit mode is active', async() => {
201
+ const items = ['test'];
202
+
203
+ await wrapper.setProps({ items });
204
+
205
+ // activate edit mode
206
+ await wrapper.setData({ editedItem: 'test' });
207
+
208
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
209
+ const buttonElem = removeButton.element as HTMLButtonElement;
210
+
211
+ expect(buttonElem.disabled).toBe(false);
212
+ });
213
+
214
+ it('is enabled when an item is selected', async() => {
215
+ const items = ['test'];
216
+
217
+ await wrapper.setProps({ items });
218
+
219
+ // select item
220
+ await wrapper.setData({ selected: 'test' });
221
+
222
+ // click remove button
223
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
224
+ const buttonElem = removeButton.element as HTMLButtonElement;
225
+
226
+ expect(buttonElem.disabled).toBe(false);
227
+ });
228
+
229
+ it('removes items when an item is selected', async() => {
230
+ const items = ['a'];
231
+
232
+ await wrapper.setProps({ items });
233
+
234
+ // select item
235
+ await wrapper.setData({ selected: 'a' });
236
+
237
+ // click remove button
238
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
239
+
240
+ await removeButton.trigger('mousedown');
241
+
242
+ await wrapper.vm.$nextTick();
243
+
244
+ const itemsCount = (wrapper.emitted('change') || [])[0][0].length;
245
+
246
+ expect(itemsCount).toBe(0);
247
+ });
248
+
249
+ it('deactivates create mode', async() => {
250
+ // activate create mode
251
+ await wrapper.setData({ isCreateItem: true });
252
+
253
+ // click remove button
254
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
255
+
256
+ await removeButton.trigger('mousedown');
257
+
258
+ const inputField = await wrapper.find('[data-testid="item-create"]');
259
+
260
+ expect(inputField.element).toBeUndefined();
261
+ });
262
+
263
+ it('deactivates edit mode', async() => {
264
+ const items = ['test'];
265
+
266
+ await wrapper.setProps({ items });
267
+
268
+ // activate edit mode
269
+ await wrapper.setData({ editedItem: 'test' });
270
+
271
+ // click remove button
272
+ const removeButton = wrapper.find('[data-testid="button-remove"]');
273
+
274
+ await removeButton.trigger('mousedown');
275
+
276
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
277
+
278
+ expect(inputField.element).toBeUndefined();
279
+ });
280
+ });
281
+ });
282
+
283
+ describe('list edit', () => {
284
+ const validItem = ' item name ';
285
+ const emptyItem = ' ';
286
+
287
+ it('save a new item in create mode by pressing Enter key', async() => {
288
+ // activate create mode
289
+ await wrapper.setData({ isCreateItem: true });
290
+
291
+ // type item name
292
+ const inputField = wrapper.find('[data-testid="item-create"]');
293
+
294
+ await inputField.setValue(validItem);
295
+
296
+ // press enter
297
+ await inputField.trigger('keydown.enter');
298
+ await wrapper.vm.$nextTick();
299
+
300
+ const emitted = (wrapper.emitted('change') || [])[0][0][0];
301
+
302
+ expect(emitted).toBe(validItem.trim());
303
+ });
304
+
305
+ it('save item in edit mode by pressing Enter key', async() => {
306
+ const items = ['test'];
307
+
308
+ await wrapper.setProps({ items });
309
+
310
+ // activate edit mode
311
+ await wrapper.setData({ editedItem: 'test' });
312
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
313
+
314
+ // edit item name
315
+ await inputField.setValue(validItem);
316
+
317
+ // press enter
318
+ await inputField.trigger('keydown.enter');
319
+ await wrapper.vm.$nextTick();
320
+
321
+ const emitted = (wrapper.emitted('change') || [])[0][0][0];
322
+
323
+ expect(emitted).toBe(validItem.trim());
324
+ });
325
+
326
+ it('reject a new item in create mode when item name is empty', async() => {
327
+ // activate create mode
328
+ await wrapper.setData({ isCreateItem: true });
329
+
330
+ // type item name
331
+ const inputField = wrapper.find('[data-testid="item-create"]');
332
+
333
+ await inputField.setValue(emptyItem);
334
+
335
+ // press enter
336
+ await inputField.trigger('keydown.enter');
337
+ await wrapper.vm.$nextTick();
338
+
339
+ expect(wrapper.emitted('change')).toBeFalsy();
340
+ });
341
+
342
+ it('reject a new item in create mode when item name is duplicate', async() => {
343
+ const items = ['test'];
344
+
345
+ await wrapper.setProps({ items });
346
+
347
+ // activate create mode
348
+ await wrapper.setData({ isCreateItem: true });
349
+
350
+ // type item name
351
+ const inputField = wrapper.find('[data-testid="item-create"]');
352
+
353
+ await inputField.setValue('test');
354
+
355
+ // press enter
356
+ await inputField.trigger('keydown.enter');
357
+ await wrapper.vm.$nextTick();
358
+
359
+ expect(wrapper.emitted('change')).toBeFalsy();
70
360
  });
71
- const elements = wrapper.findAll('[data-testid^="div-item"]');
72
- expect(elements.length).toBe(10);
73
361
 
74
- await wrapper.setProps({ items: [ ...items.filter(f => f !== 'a') ] });
362
+ it('reject an item in edit mode when item name is empty', async() => {
363
+ const items = ['test'];
364
+
365
+ await wrapper.setProps({ items });
366
+
367
+ // activate edit mode
368
+ await wrapper.setData({ editedItem: 'test' });
369
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
370
+
371
+ // edit item name
372
+ await inputField.setValue(emptyItem);
373
+
374
+ // press enter
375
+ await inputField.trigger('keydown.enter');
376
+ await wrapper.vm.$nextTick();
75
377
 
76
- const newElements = wrapper.findAll('[data-testid^="div-item"]');
77
- expect(newElements.length).toBe(9);
378
+ expect(wrapper.emitted('change')).toBeFalsy();
379
+ });
380
+
381
+ it('reject an item in edit mode when item name is duplicate', async() => {
382
+ const items = ['test', 'test-1'];
383
+
384
+ await wrapper.setProps({ items });
385
+
386
+ // activate edit mode
387
+ await wrapper.setData({ editedItem: 'test' });
388
+ const inputField = wrapper.find('[data-testid^="item-edit"]');
389
+
390
+ // edit item name
391
+ await inputField.setValue('test-1');
392
+
393
+ // press enter
394
+ await inputField.trigger('keydown.enter');
395
+ await wrapper.vm.$nextTick();
396
+
397
+ expect(wrapper.emitted('change')).toBeFalsy();
398
+ });
78
399
  });
79
400
 
401
+ describe('errors handling', () => {
402
+ it('show duplicate warning icon when errorMessages is defined', async() => {
403
+ const items = ['test'];
404
+
405
+ await wrapper.setProps({
406
+ items,
407
+ errorMessages: { duplicate: 'error, item is duplicate.' },
408
+ });
409
+
410
+ // activate edit mode
411
+ await wrapper.setData({ isCreateItem: true });
412
+
413
+ // type item name
414
+ const inputField = wrapper.find('[data-testid="item-create"]');
415
+
416
+ await inputField.setValue('test');
417
+
418
+ const icon = wrapper.find('[data-testid="i-warning-icon"]');
419
+
420
+ expect(icon.element).toBeDefined();
421
+ });
422
+
423
+ it('show duplicate warning message when errorMessages is defined', async() => {
424
+ const items = ['test'];
425
+
426
+ await wrapper.setProps({
427
+ items,
428
+ errorMessages: { duplicate: 'error, item is duplicate.' },
429
+ });
430
+
431
+ // activate edit mode
432
+ await wrapper.setData({ isCreateItem: true });
433
+
434
+ // type item name
435
+ const inputField = wrapper.find('[data-testid="item-create"]');
436
+
437
+ await inputField.setValue('test');
438
+
439
+ const message = wrapper.find('[data-testid^="span-error-message"]');
440
+
441
+ expect(message.element).toBeDefined();
442
+ });
443
+
444
+ it('emit duplicate errors', async() => {
445
+ const items = ['test'];
446
+
447
+ await wrapper.setProps({ items });
448
+
449
+ // activate edit mode
450
+ await wrapper.setData({ isCreateItem: true });
451
+
452
+ // type item name
453
+ const inputField = wrapper.find('[data-testid="item-create"]');
454
+
455
+ await inputField.setValue('test');
456
+
457
+ const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
458
+
459
+ expect(isDuplicate).toBe(true);
460
+ });
461
+
462
+ it('emit duplicate errors, reset error', async() => {
463
+ const items = ['test'];
464
+
465
+ await wrapper.setProps({ items });
466
+
467
+ // activate edit mode
468
+ await wrapper.setData({ isCreateItem: true });
469
+
470
+ // type item name
471
+ const inputField = wrapper.find('[data-testid="item-create"]');
472
+
473
+ // emit duplicate errors
474
+ await inputField.setValue('test');
475
+
476
+ // it is not duplicate, reset duplicate error -> emit false
477
+ await inputField.setValue('test-1');
478
+
479
+ const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
480
+
481
+ expect(isDuplicate).toBe(false);
482
+ });
483
+ });
80
484
  });