@rancher/shell 3.0.8 → 3.0.9-rc.2

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 (192) hide show
  1. package/apis/intf/modal.ts +38 -0
  2. package/apis/intf/slide-in.ts +3 -1
  3. package/apis/shell/__tests__/slide-in.test.ts +36 -0
  4. package/apis/shell/slide-in.ts +5 -1
  5. package/assets/styles/base/_color.scss +1 -0
  6. package/assets/styles/base/_typography.scss +14 -5
  7. package/assets/styles/themes/_light.scss +1 -1
  8. package/assets/styles/themes/_modern.scss +1 -1
  9. package/assets/translations/en-us.yaml +94 -33
  10. package/assets/translations/zh-hans.yaml +0 -2
  11. package/components/ActionMenuShell.vue +4 -4
  12. package/components/CodeMirror.vue +4 -3
  13. package/components/DetailText.vue +54 -7
  14. package/components/Drawer/Chrome.vue +11 -4
  15. package/components/Drawer/DrawerCard.vue +19 -0
  16. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  18. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  19. package/components/Drawer/types.ts +1 -0
  20. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  21. package/components/LocaleSelector.vue +1 -1
  22. package/components/Markdown.vue +1 -1
  23. package/components/PopoverCard.vue +3 -3
  24. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  25. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  26. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  27. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  28. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  29. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
  30. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  31. package/components/Resource/Detail/Cards.vue +27 -0
  32. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  33. package/components/Resource/Detail/Masthead/index.vue +5 -0
  34. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  35. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  36. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  37. package/components/Resource/Detail/ResourceRow.vue +23 -35
  38. package/components/Resource/Detail/StatusRow.vue +5 -2
  39. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  40. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  41. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  42. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  43. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  44. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  45. package/components/ResourceDetail/Masthead/index.vue +1 -0
  46. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  47. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  48. package/components/Setting.vue +1 -1
  49. package/components/SortableTable/index.vue +25 -0
  50. package/components/SortableTable/selection.js +25 -12
  51. package/components/SortableTable/sorting.js +1 -1
  52. package/components/Tabbed/Tab.vue +1 -0
  53. package/components/Tabbed/index.vue +29 -6
  54. package/components/Window/ContainerShell.vue +10 -13
  55. package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
  56. package/components/fleet/FleetClusterTargets/index.vue +82 -29
  57. package/components/fleet/FleetClusters.vue +26 -12
  58. package/components/fleet/FleetGitRepoPaths.vue +2 -2
  59. package/components/fleet/FleetResources.vue +14 -0
  60. package/components/fleet/FleetValuesFrom.vue +2 -2
  61. package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
  62. package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
  63. package/components/fleet/dashboard/ResourceDetails.vue +96 -123
  64. package/components/form/Conditions.vue +1 -15
  65. package/components/form/HookOption.vue +5 -0
  66. package/components/form/LabeledSelect.vue +1 -1
  67. package/components/form/LifecycleHooks.vue +2 -6
  68. package/components/form/ResourceLabeledSelect.vue +12 -1
  69. package/components/form/SeccompProfile.vue +113 -0
  70. package/components/form/Security.vue +244 -133
  71. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  72. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  73. package/components/form/__tests__/Security.test.ts +125 -37
  74. package/components/formatter/Autoscaler.vue +2 -2
  75. package/components/formatter/FleetSummaryGraph.vue +4 -1
  76. package/components/nav/Group.vue +5 -0
  77. package/components/nav/Header.vue +3 -3
  78. package/components/nav/HeaderPageActionMenu.vue +1 -1
  79. package/components/nav/NamespaceFilter.vue +6 -6
  80. package/components/nav/NotificationCenter/index.vue +1 -1
  81. package/components/nav/TopLevelMenu.helper.ts +41 -16
  82. package/components/nav/TopLevelMenu.vue +45 -25
  83. package/components/nav/WorkspaceSwitcher.vue +1 -1
  84. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  85. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  86. package/components/templates/default.vue +0 -3
  87. package/components/templates/home.vue +0 -3
  88. package/components/templates/plain.vue +0 -3
  89. package/composables/useClickOutside.ts +1 -1
  90. package/config/product/explorer.js +1 -2
  91. package/config/types.js +41 -8
  92. package/detail/__tests__/workload.test.ts +8 -16
  93. package/detail/catalog.cattle.io.app.vue +6 -0
  94. package/detail/fleet.cattle.io.cluster.vue +6 -0
  95. package/detail/workload/index.vue +7 -109
  96. package/edit/__tests__/projectsecret.test.ts +42 -0
  97. package/edit/auth/__tests__/oidc.test.ts +50 -0
  98. package/edit/auth/oidc.vue +68 -44
  99. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  100. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  101. package/edit/projectsecret.vue +29 -0
  102. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  103. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  104. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  105. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  106. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  107. package/edit/workload/__tests__/index.test.ts +122 -85
  108. package/edit/workload/index.vue +48 -29
  109. package/edit/workload/mixins/workload.js +85 -32
  110. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  111. package/list/projectsecret.vue +2 -2
  112. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  113. package/machine-config/amazonec2.vue +2 -2
  114. package/machine-config/vmwarevsphere.vue +58 -4
  115. package/mixins/__tests__/brand.spec.ts +18 -13
  116. package/mixins/__tests__/chart.test.ts +63 -0
  117. package/mixins/chart.js +56 -51
  118. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  119. package/models/__tests__/workload.test.ts +333 -0
  120. package/models/catalog.cattle.io.app.js +8 -0
  121. package/models/pod.js +14 -0
  122. package/models/secret.js +1 -1
  123. package/models/workload.js +93 -27
  124. package/package.json +4 -4
  125. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  126. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  127. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  128. package/pages/c/_cluster/fleet/index.vue +18 -12
  129. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  130. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  131. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  132. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  133. package/plugins/dashboard-store/actions.js +9 -8
  134. package/plugins/dashboard-store/resource-class.js +97 -1
  135. package/plugins/steve/__tests__/revision.test.ts +84 -0
  136. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  137. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  138. package/plugins/steve/mutations.js +9 -0
  139. package/plugins/steve/revision.ts +26 -0
  140. package/plugins/steve/steve-pagination-utils.ts +6 -5
  141. package/plugins/steve/subscribe.js +211 -51
  142. package/plugins/subscribe-events.ts +2 -2
  143. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  144. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  145. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
  146. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  147. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  148. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  149. package/rancher-components/Pill/index.ts +4 -0
  150. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  151. package/rancher-components/RcButton/RcButton.vue +217 -25
  152. package/rancher-components/RcButton/types.ts +27 -1
  153. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  154. package/rancher-components/RcDropdown/types.ts +3 -3
  155. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  156. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  157. package/rancher-components/RcIcon/types.ts +13 -9
  158. package/rancher-components/utils/status.test.ts +10 -15
  159. package/rancher-components/utils/status.ts +5 -6
  160. package/store/aws.js +18 -12
  161. package/store/index.js +4 -8
  162. package/store/type-map.utils.ts +1 -1
  163. package/types/kube/kube-api.ts +29 -3
  164. package/types/rancher/steve.api.ts +40 -0
  165. package/types/shell/index.d.ts +99 -0
  166. package/types/store/dashboard-store.types.ts +29 -7
  167. package/types/store/pagination.types.ts +1 -0
  168. package/types/store/subscribe-events.types.ts +1 -0
  169. package/utils/__tests__/azure.test.ts +56 -0
  170. package/utils/__tests__/back-off.test.ts +364 -245
  171. package/utils/__tests__/error.test.ts +44 -0
  172. package/utils/__tests__/fleet.test.ts +8 -1
  173. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  174. package/utils/__tests__/version.test.ts +55 -1
  175. package/utils/azure.js +12 -0
  176. package/utils/back-off.ts +302 -69
  177. package/utils/cspAdaptor.ts +32 -14
  178. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  179. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  180. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  181. package/utils/dynamic-content/index.ts +1 -6
  182. package/utils/dynamic-content/new-release.ts +5 -3
  183. package/utils/dynamic-content/types.d.ts +0 -1
  184. package/utils/error.js +9 -0
  185. package/utils/fleet.ts +2 -2
  186. package/utils/inactivity.ts +2 -3
  187. package/utils/pagination-wrapper.ts +101 -17
  188. package/utils/validators/formRules/index.ts +3 -0
  189. package/utils/version.js +38 -0
  190. package/components/auth/AzureWarning.vue +0 -77
  191. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  192. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
@@ -25,6 +25,14 @@ export default {
25
25
  useQueryParamsForSimpleFiltering: {
26
26
  type: Boolean,
27
27
  default: false
28
+ },
29
+ removeSubRows: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ ignoreFilter: {
34
+ type: Boolean,
35
+ default: false,
28
36
  }
29
37
  },
30
38
 
@@ -99,9 +107,9 @@ export default {
99
107
  :schema="schema"
100
108
  :headers="headers"
101
109
  :rows="rows"
102
- :sub-rows="true"
103
110
  :loading="loading"
104
111
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
112
+ :ignore-filter="ignoreFilter"
105
113
  key-field="_key"
106
114
  >
107
115
  <template #cell:workspace="{row}">
@@ -151,18 +159,22 @@ export default {
151
159
  >{{ row.bundleInfo.total }}</span>
152
160
  </template>
153
161
 
154
- <template #sub-row="{fullColspan, row, onRowMouseEnter, onRowMouseLeave}">
162
+ <template
163
+ v-if="!removeSubRows"
164
+ #additional-sub-row="{fullColspan, row, onRowMouseEnter, onRowMouseLeave, showSubRow}"
165
+ >
155
166
  <tr
156
- class="labels-row sub-row"
167
+ class="labels-row additional-sub-row"
168
+ :class="{ 'has-sub-row': showSubRow}"
157
169
  @mouseenter="onRowMouseEnter"
158
170
  @mouseleave="onRowMouseLeave"
159
171
  >
160
- <template v-if="row.customLabels.length">
172
+ <template v-if="row.customLabels && row.customLabels.length">
161
173
  <td>&nbsp;</td>
162
174
  <td>&nbsp;</td>
163
175
  <td :colspan="fullColspan-2">
164
176
  <span
165
- v-if="row.customLabels.length"
177
+ v-if="row.customLabels && row.customLabels.length"
166
178
  class="mt-5"
167
179
  > {{ t('fleet.cluster.labels') }}:
168
180
  <span
@@ -184,7 +196,7 @@ export default {
184
196
  </Tag>
185
197
  </span>
186
198
  <a
187
- v-if="row.customLabels.length > 7"
199
+ v-if="row.customLabels && row.customLabels.length > 7"
188
200
  href="#"
189
201
  @click.prevent="toggleCustomLabels(row)"
190
202
  >
@@ -193,14 +205,16 @@ export default {
193
205
  </span>
194
206
  </td>
195
207
  </template>
196
- <td
197
- v-else
198
- :colspan="fullColspan"
199
- >
200
- &nbsp;
201
- </td>
202
208
  </tr>
203
209
  </template>
210
+ <template
211
+ v-else
212
+ #sub-row
213
+ >
214
+ <tr
215
+ class="sub-row"
216
+ />
217
+ </template>
204
218
  </ResourceTable>
205
219
  </template>
206
220
 
@@ -335,8 +335,8 @@ export default {
335
335
  </h4>
336
336
  <RcButton
337
337
  v-if="!isView"
338
- small
339
- link
338
+ size="small"
339
+ variant="link"
340
340
  @click="removePaths(i)"
341
341
  >
342
342
  <i class="icon icon-x" />
@@ -35,6 +35,19 @@ export default {
35
35
  },
36
36
 
37
37
  computed: {
38
+ groupOptions() {
39
+ return [{
40
+ tooltipKey: 'resourceTable.groupBy.none',
41
+ icon: 'icon-list-flat',
42
+ value: 'none',
43
+ }, {
44
+ tooltipKey: 'resourceTable.groupBy.cluster',
45
+ hideColumn: 'provider',
46
+ icon: 'icon-folder',
47
+ value: 'clusterId',
48
+ field: 'clusterId',
49
+ }];
50
+ },
38
51
  computedResources() {
39
52
  const resources = (this.rows || []).map((r) => ({
40
53
  tableKey: r.key,
@@ -107,6 +120,7 @@ export default {
107
120
  :table-actions="false"
108
121
  :row-actions="false"
109
122
  :search="search"
123
+ :group-options="groupOptions"
110
124
  key-field="tableKey"
111
125
  default-sort-by="state"
112
126
  >
@@ -276,8 +276,8 @@ export default {
276
276
  <RcButton
277
277
  v-if="!isView"
278
278
  ref="add-button"
279
- small
280
- secondary
279
+ size="small"
280
+ variant="secondary"
281
281
  class="mmt-6"
282
282
  data-testid="fleet-values-from-add"
283
283
  @click="addValueFrom"
@@ -1221,4 +1221,535 @@ describe('component: FleetClusterTargets', () => {
1221
1221
  });
1222
1222
  });
1223
1223
  });
1224
+
1225
+ describe('clusterGroup Functionality Tests', () => {
1226
+ describe('clusterGroup Data Management', () => {
1227
+ it('should initialize with empty selectedClusterGroups', () => {
1228
+ const wrapper = mount(FleetClusterTargets, {
1229
+ props: {
1230
+ targets: [],
1231
+ namespace: 'fleet-default',
1232
+ mode: _EDIT
1233
+ }
1234
+ });
1235
+
1236
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual([]);
1237
+ });
1238
+
1239
+ it('should populate allClusterGroups from store data', async() => {
1240
+ const mockClusterGroups = [
1241
+ {
1242
+ metadata: { namespace: 'fleet-default', name: 'production-group' },
1243
+ nameDisplay: 'Production Group'
1244
+ },
1245
+ {
1246
+ metadata: { namespace: 'fleet-default', name: 'staging-group' },
1247
+ nameDisplay: 'Staging Group'
1248
+ }
1249
+ ];
1250
+
1251
+ const wrapper = mount(FleetClusterTargets, {
1252
+ props: {
1253
+ targets: [],
1254
+ namespace: 'fleet-default',
1255
+ mode: _EDIT
1256
+ }
1257
+ });
1258
+
1259
+ wrapper.setData({ allClusterGroups: mockClusterGroups });
1260
+ await flushPromises();
1261
+
1262
+ expect(wrapper.vm.allClusterGroups).toStrictEqual(mockClusterGroups);
1263
+ });
1264
+
1265
+ it('should filter clusterGroupsOptions by namespace', () => {
1266
+ const mockClusterGroups = [
1267
+ {
1268
+ metadata: { namespace: 'fleet-default', name: 'group-1' },
1269
+ nameDisplay: 'Group 1'
1270
+ },
1271
+ {
1272
+ metadata: { namespace: 'other-namespace', name: 'group-2' },
1273
+ nameDisplay: 'Group 2'
1274
+ },
1275
+ {
1276
+ metadata: { namespace: 'fleet-default', name: 'group-3' },
1277
+ nameDisplay: 'Group 3'
1278
+ }
1279
+ ];
1280
+
1281
+ const wrapper = mount(FleetClusterTargets, {
1282
+ props: {
1283
+ targets: [],
1284
+ namespace: 'fleet-default',
1285
+ mode: _EDIT
1286
+ }
1287
+ });
1288
+
1289
+ wrapper.setData({ allClusterGroups: mockClusterGroups });
1290
+
1291
+ const filteredOptions = wrapper.vm.clusterGroupsOptions;
1292
+
1293
+ expect(filteredOptions).toHaveLength(2);
1294
+ expect(filteredOptions).toStrictEqual([
1295
+ { label: 'Group 1', value: 'group-1' },
1296
+ { label: 'Group 3', value: 'group-3' }
1297
+ ]);
1298
+ });
1299
+ });
1300
+
1301
+ describe('clusterGroup Selection Methods', () => {
1302
+ it('should update selectedClusterGroups when selectClusterGroups is called', async() => {
1303
+ const wrapper = mount(FleetClusterTargets, {
1304
+ props: {
1305
+ targets: [],
1306
+ namespace: 'fleet-default',
1307
+ mode: _EDIT
1308
+ }
1309
+ });
1310
+
1311
+ const updateSpy = jest.spyOn(wrapper.vm, 'update');
1312
+
1313
+ wrapper.vm.selectClusterGroups(['group-1', 'group-2']);
1314
+ await flushPromises();
1315
+
1316
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['group-1', 'group-2']);
1317
+ expect(updateSpy).toHaveBeenCalledWith();
1318
+ });
1319
+
1320
+ it('should emit update:value when selectClusterGroups is called', async() => {
1321
+ const wrapper = mount(FleetClusterTargets, {
1322
+ props: {
1323
+ targets: [],
1324
+ namespace: 'fleet-default',
1325
+ mode: _EDIT
1326
+ }
1327
+ });
1328
+
1329
+ wrapper.vm.selectClusterGroups(['test-group']);
1330
+ await flushPromises();
1331
+
1332
+ expect(wrapper.emitted('update:value')).toBeDefined();
1333
+ });
1334
+
1335
+ it('should handle empty array in selectClusterGroups', async() => {
1336
+ const wrapper = mount(FleetClusterTargets, {
1337
+ props: {
1338
+ targets: [],
1339
+ namespace: 'fleet-default',
1340
+ mode: _EDIT
1341
+ }
1342
+ });
1343
+
1344
+ // First set some groups
1345
+ wrapper.vm.selectClusterGroups(['group-1', 'group-2']);
1346
+ await flushPromises();
1347
+
1348
+ // Then clear them
1349
+ wrapper.vm.selectClusterGroups([]);
1350
+ await flushPromises();
1351
+
1352
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual([]);
1353
+ });
1354
+
1355
+ it('should replace existing selectedClusterGroups on new selection', async() => {
1356
+ const wrapper = mount(FleetClusterTargets, {
1357
+ props: {
1358
+ targets: [],
1359
+ namespace: 'fleet-default',
1360
+ mode: _EDIT
1361
+ }
1362
+ });
1363
+
1364
+ // Initial selection
1365
+ wrapper.vm.selectClusterGroups(['group-1', 'group-2']);
1366
+ await flushPromises();
1367
+
1368
+ // Replace with new selection
1369
+ wrapper.vm.selectClusterGroups(['group-3', 'group-4', 'group-5']);
1370
+ await flushPromises();
1371
+
1372
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['group-3', 'group-4', 'group-5']);
1373
+ });
1374
+ });
1375
+
1376
+ describe('clusterGroup Target Processing', () => {
1377
+ it('should parse existing targets with clusterGroup in fromTargets', () => {
1378
+ const targets = [
1379
+ { clusterGroup: 'production-group' },
1380
+ { clusterGroup: 'staging-group' },
1381
+ { clusterName: 'specific-cluster' }
1382
+ ];
1383
+
1384
+ const wrapper = mount(FleetClusterTargets, {
1385
+ props: {
1386
+ targets,
1387
+ namespace: 'fleet-default',
1388
+ mode: _EDIT
1389
+ }
1390
+ });
1391
+
1392
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['production-group', 'staging-group']);
1393
+ expect(wrapper.vm.selectedClusters).toStrictEqual(['specific-cluster']);
1394
+ });
1395
+
1396
+ it('should include clusterGroups in normalizeTargets output', () => {
1397
+ const wrapper = mount(FleetClusterTargets, {
1398
+ props: {
1399
+ targets: [],
1400
+ namespace: 'fleet-default',
1401
+ mode: _EDIT
1402
+ }
1403
+ });
1404
+
1405
+ const result = wrapper.vm.normalizeTargets(
1406
+ ['cluster-1'],
1407
+ [{ matchLabels: { env: 'prod' } }],
1408
+ ['group-1', 'group-2']
1409
+ );
1410
+
1411
+ expect(result).toStrictEqual([
1412
+ { clusterName: 'cluster-1' },
1413
+ { clusterSelector: { matchLabels: { env: 'prod' } } },
1414
+ { clusterGroup: 'group-1' },
1415
+ { clusterGroup: 'group-2' }
1416
+ ]);
1417
+ });
1418
+
1419
+ it('should handle only clusterGroups in normalizeTargets', () => {
1420
+ const wrapper = mount(FleetClusterTargets, {
1421
+ props: {
1422
+ targets: [],
1423
+ namespace: 'fleet-default',
1424
+ mode: _EDIT
1425
+ }
1426
+ });
1427
+
1428
+ const result = wrapper.vm.normalizeTargets([], [], ['group-1', 'group-2']);
1429
+
1430
+ expect(result).toStrictEqual([
1431
+ { clusterGroup: 'group-1' },
1432
+ { clusterGroup: 'group-2' }
1433
+ ]);
1434
+ });
1435
+
1436
+ it('should return undefined when normalizeTargets has no inputs', () => {
1437
+ const wrapper = mount(FleetClusterTargets, {
1438
+ props: {
1439
+ targets: [],
1440
+ namespace: 'fleet-default',
1441
+ mode: _EDIT
1442
+ }
1443
+ });
1444
+
1445
+ const result = wrapper.vm.normalizeTargets([], [], []);
1446
+
1447
+ expect(result).toBeUndefined();
1448
+ });
1449
+
1450
+ it('should include clusterGroups in toTargets when targetMode is clusters', () => {
1451
+ const wrapper = mount(FleetClusterTargets, {
1452
+ props: {
1453
+ targets: [],
1454
+ namespace: 'fleet-default',
1455
+ mode: _EDIT
1456
+ }
1457
+ });
1458
+
1459
+ wrapper.setData({
1460
+ targetMode: 'clusters',
1461
+ selectedClusters: ['cluster-1'],
1462
+ clusterSelectors: [],
1463
+ selectedClusterGroups: ['group-1', 'group-2']
1464
+ });
1465
+
1466
+ const result = wrapper.vm.toTargets();
1467
+
1468
+ expect(result).toStrictEqual([
1469
+ { clusterName: 'cluster-1' },
1470
+ { clusterGroup: 'group-1' },
1471
+ { clusterGroup: 'group-2' }
1472
+ ]);
1473
+ });
1474
+ });
1475
+
1476
+ describe('clusterGroup Integration with Target Modes', () => {
1477
+ it('should handle clusterGroup targets and set appropriate targetMode', () => {
1478
+ const targets = [
1479
+ { clusterGroup: 'test-group' }
1480
+ ];
1481
+
1482
+ const wrapper = mount(FleetClusterTargets, {
1483
+ props: {
1484
+ targets,
1485
+ namespace: 'fleet-default',
1486
+ mode: _EDIT
1487
+ }
1488
+ });
1489
+
1490
+ // ClusterGroup targets should be parsed correctly
1491
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['test-group']);
1492
+ });
1493
+ });
1494
+
1495
+ it('should handle mixed targets with clusterGroup, clusterName, and clusterSelector', () => {
1496
+ const targets = [
1497
+ { clusterName: 'specific-cluster' },
1498
+ { clusterGroup: 'production-group' },
1499
+ { clusterSelector: { matchLabels: { env: 'staging' } } },
1500
+ { clusterGroup: 'development-group' }
1501
+ ];
1502
+
1503
+ const wrapper = mount(FleetClusterTargets, {
1504
+ props: {
1505
+ targets,
1506
+ namespace: 'fleet-default',
1507
+ mode: _EDIT
1508
+ }
1509
+ });
1510
+
1511
+ expect(wrapper.vm.selectedClusters).toStrictEqual(['specific-cluster']);
1512
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['production-group', 'development-group']);
1513
+ expect(wrapper.vm.clusterSelectors).toHaveLength(1);
1514
+ expect(wrapper.vm.clusterSelectors[0].matchLabels).toStrictEqual({ env: 'staging' });
1515
+ });
1516
+
1517
+ it('should reset selectedClusterGroups when reset method is called', () => {
1518
+ const wrapper = mount(FleetClusterTargets, {
1519
+ props: {
1520
+ targets: [],
1521
+ namespace: 'fleet-default',
1522
+ mode: _EDIT
1523
+ }
1524
+ });
1525
+
1526
+ // Set some cluster groups
1527
+ wrapper.setData({
1528
+ targetMode: 'clusters',
1529
+ selectedClusterGroups: ['group-1', 'group-2'],
1530
+ selectedClusters: ['cluster-1'],
1531
+ clusterSelectors: [{ key: 1 }]
1532
+ });
1533
+
1534
+ // Call reset
1535
+ wrapper.vm.reset();
1536
+
1537
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual([]);
1538
+ expect(wrapper.vm.targetMode).toBe('all');
1539
+ expect(wrapper.vm.selectedClusters).toStrictEqual([]);
1540
+ expect(wrapper.vm.clusterSelectors).toStrictEqual([]);
1541
+ });
1542
+ });
1543
+
1544
+ describe('clusterGroup Event Handling and Updates', () => {
1545
+ it('should emit correct targets when both clusters and clusterGroups are selected', async() => {
1546
+ const wrapper = mount(FleetClusterTargets, {
1547
+ props: {
1548
+ targets: [],
1549
+ namespace: 'fleet-default',
1550
+ mode: _CREATE
1551
+ }
1552
+ });
1553
+
1554
+ // Set target mode and selections
1555
+ wrapper.setData({ targetMode: 'clusters' });
1556
+ wrapper.vm.selectClusters(['cluster-1', 'cluster-2']);
1557
+ await flushPromises();
1558
+
1559
+ wrapper.vm.selectClusterGroups(['group-1']);
1560
+ await flushPromises();
1561
+
1562
+ const emittedValues = wrapper.emitted('update:value');
1563
+ const lastEmitted = emittedValues?.[emittedValues.length - 1][0];
1564
+
1565
+ expect(lastEmitted).toStrictEqual([
1566
+ { clusterName: 'cluster-1' },
1567
+ { clusterName: 'cluster-2' },
1568
+ { clusterGroup: 'group-1' }
1569
+ ]);
1570
+ });
1571
+
1572
+ it('should handle clusterGroup selection in CREATE mode with proper event emission', async() => {
1573
+ const wrapper = mount(FleetClusterTargets, {
1574
+ props: {
1575
+ targets: [],
1576
+ namespace: 'fleet-default',
1577
+ mode: _CREATE
1578
+ }
1579
+ });
1580
+
1581
+ wrapper.setData({ targetMode: 'clusters' });
1582
+ wrapper.vm.selectClusterGroups(['create-group-1', 'create-group-2']);
1583
+ await flushPromises();
1584
+
1585
+ const emittedValues = wrapper.emitted('update:value');
1586
+
1587
+ expect(emittedValues).toBeDefined();
1588
+
1589
+ const lastEmitted = emittedValues?.[emittedValues.length - 1][0];
1590
+
1591
+ expect(lastEmitted).toStrictEqual([
1592
+ { clusterGroup: 'create-group-1' },
1593
+ { clusterGroup: 'create-group-2' }
1594
+ ]);
1595
+ });
1596
+
1597
+ it('should update component state correctly when clusterGroups prop changes', async() => {
1598
+ const initialTargets = [{ clusterGroup: 'initial-group' }];
1599
+
1600
+ const wrapper = mount(FleetClusterTargets, {
1601
+ props: {
1602
+ targets: initialTargets,
1603
+ namespace: 'fleet-default',
1604
+ mode: _EDIT
1605
+ }
1606
+ });
1607
+
1608
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['initial-group']);
1609
+
1610
+ // Update props
1611
+ const newTargets = [
1612
+ { clusterGroup: 'new-group-1' },
1613
+ { clusterGroup: 'new-group-2' }
1614
+ ];
1615
+
1616
+ await wrapper.setProps({ targets: newTargets });
1617
+
1618
+ // Reset and then parse new targets to simulate component update
1619
+ wrapper.vm.reset();
1620
+ wrapper.vm.fromTargets();
1621
+
1622
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['new-group-1', 'new-group-2']);
1623
+ });
1624
+ });
1625
+
1626
+ describe('clusterGroup Edge Cases and Error Handling', () => {
1627
+ it('should handle undefined clusterGroup in targets gracefully', () => {
1628
+ const targets = [
1629
+ { clusterGroup: undefined },
1630
+ { clusterGroup: 'valid-group' },
1631
+ { clusterName: 'cluster-1' }
1632
+ ];
1633
+
1634
+ const wrapper = mount(FleetClusterTargets, {
1635
+ props: {
1636
+ targets: targets as any,
1637
+ namespace: 'fleet-default',
1638
+ mode: _EDIT
1639
+ }
1640
+ });
1641
+
1642
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['valid-group']);
1643
+ });
1644
+
1645
+ it('should handle empty string clusterGroup in targets', () => {
1646
+ const targets = [
1647
+ { clusterGroup: '' },
1648
+ { clusterGroup: 'valid-group' }
1649
+ ];
1650
+
1651
+ const wrapper = mount(FleetClusterTargets, {
1652
+ props: {
1653
+ targets: targets as any,
1654
+ namespace: 'fleet-default',
1655
+ mode: _EDIT
1656
+ }
1657
+ });
1658
+
1659
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['valid-group']);
1660
+ });
1661
+
1662
+ it('should handle empty allClusterGroups data', () => {
1663
+ const wrapper = mount(FleetClusterTargets, {
1664
+ props: {
1665
+ targets: [],
1666
+ namespace: 'fleet-default',
1667
+ mode: _EDIT
1668
+ }
1669
+ });
1670
+
1671
+ wrapper.setData({ allClusterGroups: [] });
1672
+
1673
+ expect(() => wrapper.vm.clusterGroupsOptions).not.toThrow();
1674
+ expect(wrapper.vm.clusterGroupsOptions).toStrictEqual([]);
1675
+ });
1676
+
1677
+ it('should handle clusterGroups with missing nameDisplay', () => {
1678
+ const mockClusterGroups = [
1679
+ {
1680
+ metadata: { namespace: 'fleet-default', name: 'group-1' }
1681
+ // Missing nameDisplay
1682
+ },
1683
+ {
1684
+ metadata: { namespace: 'fleet-default', name: 'group-2' },
1685
+ nameDisplay: 'Group 2'
1686
+ }
1687
+ ];
1688
+
1689
+ const wrapper = mount(FleetClusterTargets, {
1690
+ props: {
1691
+ targets: [],
1692
+ namespace: 'fleet-default',
1693
+ mode: _EDIT
1694
+ }
1695
+ });
1696
+
1697
+ wrapper.setData({ allClusterGroups: mockClusterGroups });
1698
+
1699
+ const options = wrapper.vm.clusterGroupsOptions;
1700
+
1701
+ expect(options).toStrictEqual([
1702
+ { label: undefined, value: 'group-1' },
1703
+ { label: 'Group 2', value: 'group-2' }
1704
+ ]);
1705
+ });
1706
+ });
1707
+
1708
+ describe('clusterGroup Component Lifecycle', () => {
1709
+ it('should preserve clusterGroup selections during component updates', async() => {
1710
+ const wrapper = mount(FleetClusterTargets, {
1711
+ props: {
1712
+ targets: [],
1713
+ namespace: 'fleet-default',
1714
+ mode: _EDIT
1715
+ }
1716
+ });
1717
+
1718
+ // Set initial selection
1719
+ wrapper.vm.selectClusterGroups(['persistent-group']);
1720
+ await flushPromises();
1721
+
1722
+ // Trigger component update by changing namespace
1723
+ await wrapper.setProps({ namespace: 'different-namespace' });
1724
+ await flushPromises();
1725
+
1726
+ // In EDIT mode, selections should be preserved unless explicitly reset
1727
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual(['persistent-group']);
1728
+ });
1729
+
1730
+ it('should clear clusterGroup selections on namespace change in CREATE mode', async() => {
1731
+ const wrapper = mount(FleetClusterTargets, {
1732
+ props: {
1733
+ targets: [],
1734
+ namespace: 'fleet-default',
1735
+ mode: _CREATE
1736
+ }
1737
+ });
1738
+
1739
+ // Set initial selection
1740
+ wrapper.vm.selectClusterGroups(['temp-group']);
1741
+ await flushPromises();
1742
+
1743
+ // Mock the reset method call that happens on namespace change in CREATE mode
1744
+ const resetSpy = jest.spyOn(wrapper.vm, 'reset');
1745
+
1746
+ await wrapper.setProps({ namespace: 'different-namespace' });
1747
+
1748
+ // Manually trigger reset to simulate the watcher behavior
1749
+ wrapper.vm.reset();
1750
+
1751
+ expect(resetSpy).toHaveBeenCalledWith();
1752
+ expect(wrapper.vm.selectedClusterGroups).toStrictEqual([]);
1753
+ });
1754
+ });
1224
1755
  });