@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8

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 (243) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +18 -12
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -258
  9. package/assets/styles/themes/_light.scss +538 -509
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +110 -29
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/Drawer/Chrome.vue +0 -1
  19. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
  20. package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
  21. package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
  22. package/components/InstallHelmCharts.vue +656 -0
  23. package/components/LazyImage.vue +60 -4
  24. package/components/Loading.vue +1 -1
  25. package/components/LocaleSelector.vue +7 -2
  26. package/components/Markdown.vue +4 -0
  27. package/components/PaginatedResourceTable.vue +46 -1
  28. package/components/PromptRestore.vue +22 -44
  29. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  30. package/components/Resource/Detail/Masthead/index.vue +37 -0
  31. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
  32. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
  33. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
  34. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
  35. package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
  36. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  37. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  38. package/components/Resource/Detail/Metadata/index.vue +17 -2
  39. package/components/Resource/Detail/Page.vue +35 -21
  40. package/components/Resource/Detail/SpacedRow.vue +1 -1
  41. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  42. package/components/Resource/Detail/TitleBar/composables.ts +5 -5
  43. package/components/Resource/Detail/TitleBar/index.vue +12 -3
  44. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  45. package/components/ResourceDetail/index.vue +569 -72
  46. package/components/ResourceList/index.vue +1 -0
  47. package/components/ResourceTable.vue +6 -1
  48. package/components/ResourceYaml.vue +1 -1
  49. package/components/RichTranslation.vue +106 -0
  50. package/components/SlideInPanelManager.vue +13 -10
  51. package/components/SortableTable/index.vue +5 -5
  52. package/components/SortableTable/selection.js +0 -1
  53. package/components/Tabbed/index.vue +35 -4
  54. package/components/__tests__/LazyImage.spec.ts +121 -0
  55. package/components/__tests__/PromptRestore.test.ts +1 -65
  56. package/components/__tests__/RichTranslation.test.ts +115 -0
  57. package/components/fleet/FleetStatus.vue +4 -0
  58. package/components/fleet/dashboard/ResourcePanel.vue +2 -1
  59. package/components/form/ClusterAppearance.vue +5 -0
  60. package/components/form/FileImageSelector.vue +1 -1
  61. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  62. package/components/form/NameNsDescription.vue +1 -0
  63. package/components/form/Networking.vue +24 -19
  64. package/components/form/ProjectMemberEditor.vue +1 -1
  65. package/components/form/ResourceLabeledSelect.vue +22 -8
  66. package/components/form/ResourceTabs/index.vue +20 -0
  67. package/components/form/SecretSelector.vue +9 -0
  68. package/components/form/SelectOrCreateAuthSecret.vue +6 -3
  69. package/components/form/__tests__/Networking.test.ts +116 -0
  70. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  71. package/components/formatter/FleetApplicationSource.vue +25 -17
  72. package/components/formatter/PodImages.vue +1 -1
  73. package/components/formatter/__tests__/LiveDate.test.ts +10 -2
  74. package/components/google/AccountAccess.vue +44 -46
  75. package/components/nav/Favorite.vue +4 -0
  76. package/components/nav/Group.vue +4 -1
  77. package/components/nav/NotificationCenter/Notification.vue +1 -27
  78. package/components/nav/WindowManager/index.vue +3 -3
  79. package/composables/resources.ts +2 -2
  80. package/config/labels-annotations.js +3 -2
  81. package/config/pagination-table-headers.js +8 -1
  82. package/config/product/explorer.js +27 -2
  83. package/config/product/manager.js +0 -1
  84. package/config/query-params.js +10 -0
  85. package/config/router/routes.js +21 -1
  86. package/config/system-namespaces.js +1 -1
  87. package/config/table-headers.js +30 -1
  88. package/config/types.js +1 -1
  89. package/config/version.js +1 -1
  90. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  91. package/detail/__tests__/workload.test.ts +164 -0
  92. package/detail/configmap.vue +33 -75
  93. package/detail/projectsecret.vue +11 -0
  94. package/detail/provisioning.cattle.io.cluster.vue +351 -369
  95. package/detail/secret.vue +49 -308
  96. package/detail/workload/index.vue +38 -21
  97. package/dialog/InstallExtensionDialog.vue +8 -5
  98. package/dialog/RotateEncryptionKeyDialog.vue +10 -30
  99. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  100. package/edit/auth/ldap/__tests__/config.test.ts +14 -0
  101. package/edit/auth/ldap/config.vue +24 -0
  102. package/edit/compliance.cattle.io.clusterscan.vue +1 -1
  103. package/edit/configmap.vue +4 -1
  104. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  105. package/edit/fleet.cattle.io.helmop.vue +78 -56
  106. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  107. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  108. package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
  109. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  110. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  111. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  112. package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
  113. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  114. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  115. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  116. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
  117. package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
  118. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  119. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
  120. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  121. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  122. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  124. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
  125. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  126. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  127. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  128. package/edit/secret/basic.vue +1 -0
  129. package/edit/secret/index.vue +126 -15
  130. package/edit/workload/index.vue +5 -14
  131. package/list/projectsecret.vue +345 -0
  132. package/list/provisioning.cattle.io.cluster.vue +1 -69
  133. package/list/secret.vue +109 -0
  134. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  135. package/machine-config/google.vue +9 -1
  136. package/machine-config/vmwarevsphere.vue +7 -17
  137. package/mixins/__tests__/brand.spec.ts +2 -2
  138. package/mixins/chart.js +0 -2
  139. package/mixins/create-edit-view/impl.js +10 -1
  140. package/mixins/resource-fetch-api-pagination.js +11 -12
  141. package/mixins/resource-fetch.js +3 -1
  142. package/models/__tests__/chart.test.ts +111 -80
  143. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  144. package/models/__tests__/node.test.ts +7 -63
  145. package/models/catalog.cattle.io.app.js +1 -1
  146. package/models/catalog.cattle.io.operation.js +1 -1
  147. package/models/chart.js +36 -20
  148. package/models/cloudcredential.js +2 -163
  149. package/models/cluster/node.js +7 -7
  150. package/models/cluster.x-k8s.io.machine.js +3 -3
  151. package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
  152. package/models/compliance.cattle.io.clusterscan.js +2 -2
  153. package/models/configmap.js +4 -0
  154. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  155. package/models/fleet-application.js +0 -17
  156. package/models/fleet.cattle.io.cluster.js +2 -2
  157. package/models/fleet.cattle.io.gitrepo.js +15 -1
  158. package/models/fleet.cattle.io.helmop.js +26 -22
  159. package/models/management.cattle.io.setting.js +4 -0
  160. package/models/persistentvolumeclaim.js +1 -1
  161. package/models/pod.js +2 -2
  162. package/models/provisioning.cattle.io.cluster.js +39 -67
  163. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  164. package/models/secret.js +161 -2
  165. package/models/storage.k8s.io.storageclass.js +2 -2
  166. package/models/workload.js +3 -3
  167. package/package.json +11 -10
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  172. package/pages/c/_cluster/apps/charts/index.vue +46 -35
  173. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  174. package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
  175. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  176. package/pages/c/_cluster/fleet/index.vue +103 -45
  177. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  178. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  179. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  180. package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
  181. package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
  182. package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
  183. package/plugins/dashboard-store/actions.js +42 -22
  184. package/plugins/dashboard-store/normalize.js +29 -17
  185. package/plugins/dashboard-store/resource-class.js +83 -17
  186. package/plugins/steve/__tests__/getters.test.ts +1 -1
  187. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  188. package/plugins/steve/getters.js +8 -2
  189. package/plugins/steve/resourceWatcher.js +10 -3
  190. package/plugins/steve/steve-pagination-utils.ts +14 -3
  191. package/plugins/steve/subscribe.js +192 -19
  192. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  193. package/rancher-components/Card/Card.vue +0 -18
  194. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  195. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  196. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  197. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  198. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  199. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  200. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  201. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  202. package/rancher-components/Pill/types.ts +2 -0
  203. package/rancher-components/RcButton/RcButton.vue +1 -1
  204. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  205. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  206. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  207. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  208. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  209. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  210. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  213. package/store/__tests__/catalog.test.ts +93 -1
  214. package/store/aws.js +19 -8
  215. package/store/catalog.js +8 -3
  216. package/types/kube/kube-api.ts +12 -0
  217. package/types/resources/settings.d.ts +1 -1
  218. package/types/shell/index.d.ts +643 -585
  219. package/types/store/pagination.types.ts +16 -6
  220. package/types/uiplugins.ts +73 -0
  221. package/utils/__tests__/back-off.test.ts +354 -0
  222. package/utils/__tests__/create-yaml.test.ts +235 -0
  223. package/utils/__tests__/kontainer.test.ts +19 -0
  224. package/utils/__tests__/uiplugins.test.ts +84 -0
  225. package/utils/back-off.ts +176 -0
  226. package/utils/create-yaml.js +103 -9
  227. package/utils/dynamic-importer.js +8 -0
  228. package/utils/kontainer.ts +3 -5
  229. package/utils/pagination-utils.ts +18 -0
  230. package/utils/style.ts +3 -0
  231. package/utils/uiplugins.ts +29 -2
  232. package/utils/validators/__tests__/setting.test.js +92 -0
  233. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  234. package/utils/validators/formRules/index.ts +83 -8
  235. package/utils/validators/setting.js +17 -0
  236. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  237. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  238. package/components/ResourceDetail/legacy.vue +0 -562
  239. package/components/formatter/CloudCredExpired.vue +0 -69
  240. package/models/etcdbackup.js +0 -45
  241. package/pages/explorer/resource/detail/configmap.vue +0 -42
  242. package/pages/explorer/resource/detail/secret.vue +0 -50
  243. package/utils/aws.js +0 -0
@@ -4,304 +4,79 @@ import { FLEET } from '@shell/config/types';
4
4
 
5
5
  const fleetWorkspaces = [
6
6
  {
7
- id: 'fleet-local',
8
- type: 'management.cattle.io.fleetworkspace',
9
- apiVersion: 'management.cattle.io/v3',
10
- kind: 'FleetWorkspace',
11
- metadata: {
12
- annotations: { 'provisioning.cattle.io/managed': 'false' },
13
- creationTimestamp: '2025-03-24T08:55:59Z',
14
- fields: [
15
- 'fleet-local',
16
- '44d'
17
- ],
18
- generation: 1,
19
- name: 'fleet-local',
20
- ownerReferences: [
21
- {
22
- apiVersion: 'v1',
23
- kind: 'Namespace',
24
- name: 'fleet-local',
25
- uid: '481ca51a-a855-4342-8932-a8753b5ff1ff'
26
- }
27
- ],
28
- relationships: [
29
- {
30
- fromId: 'fleet-local',
31
- fromType: 'namespace',
32
- rel: 'owner',
33
- state: 'active'
34
- }
35
- ],
36
- resourceVersion: '1362',
37
- state: {
38
- error: false,
39
- message: 'Resource is current',
40
- name: 'active',
41
- transitioning: false
42
- },
43
- uid: 'c117994d-5d21-4ac5-97be-15f25c5e8801'
44
- },
45
- status: {},
46
- repos: [{ name: 'lots-a' }],
47
- helmOps: [{ name: 'lots-b' }]
7
+ id: 'fleet-local',
8
+ type: 'management.cattle.io.fleetworkspace',
9
+ kind: 'FleetWorkspace',
10
+ metadata: { name: 'fleet-local' },
11
+ repos: [{ name: 'lots-a' }],
12
+ helmOps: [{ name: 'lots-b' }]
48
13
  },
49
14
  {
50
- id: 'fleet-default',
51
- type: 'management.cattle.io.fleetworkspace',
52
- apiVersion: 'management.cattle.io/v3',
53
- kind: 'FleetWorkspace',
54
- metadata: {
55
- annotations: { 'provisioning.cattle.io/managed': 'false' },
56
- creationTimestamp: '2025-03-24T08:55:59Z',
57
- fields: [
58
- 'fleet-default',
59
- '44d'
60
- ],
61
- generation: 1,
62
- name: 'fleet-default',
63
- ownerReferences: [
64
- {
65
- apiVersion: 'v1',
66
- kind: 'Namespace',
67
- name: 'fleet-default',
68
- uid: '481ca51a-a855-4342-8932-a8753b5ff1ff'
69
- }
70
- ],
71
- relationships: [
72
- {
73
- fromId: 'fleet-default',
74
- fromType: 'namespace',
75
- rel: 'owner',
76
- state: 'active'
77
- }
78
- ],
79
- resourceVersion: '1362',
80
- state: {
81
- error: false,
82
- message: 'Resource is current',
83
- name: 'active',
84
- transitioning: false
85
- },
86
- uid: 'c117994d-5d21-4ac5-97be-15f25c5e8801'
87
- },
88
- status: {},
89
- repos: [{ name: 'lots-c' }],
90
- helmOps: [{ name: 'lots-d' }]
15
+ id: 'fleet-default',
16
+ type: 'management.cattle.io.fleetworkspace',
17
+ kind: 'FleetWorkspace',
18
+ metadata: { name: 'fleet-default' },
19
+ repos: [{ name: 'lots-c' }],
20
+ helmOps: [{ name: 'lots-d' }]
91
21
  }
92
22
  ];
93
23
 
94
24
  const gitRepos = [
95
25
  {
96
- id: 'fleet-default/lots-a',
97
- type: 'fleet.cattle.io.gitrepo',
98
- apiVersion: 'fleet.cattle.io/v1alpha1',
99
- kind: 'GitRepo',
100
-
101
- metadata: {
102
- annotations: {},
103
- creationTimestamp: '2025-03-24T09:09:56Z',
104
- fields: [
105
- 'lots-a',
106
- 'https://github.com/git-repo',
107
- '420bf95e3d5ac89a7801deb0bc1206a5abc54b45',
108
- '17/20',
109
- 'Modified(2) [Cluster fleet-default/fleet-1; configmap.v1 lots-a/test-config-eight missing'
110
- ],
111
- finalizers: [
112
- 'fleet.cattle.io/gitrepo-finalizer'
113
- ],
114
- generation: 1,
115
- name: 'lots-a',
116
- namespace: 'fleet-default',
117
- resourceVersion: '22615976',
118
- state: {
119
- error: false,
120
- message: '',
121
- name: 'modified',
122
- transitioning: false
123
- },
124
- uid: 'd70cd36b-9cb9-4913-b0ec-0e3a2d7b92fd'
125
- },
126
- spec: {
127
- branch: 'main',
128
- paths: [
129
- 'scale'
130
- ],
131
- repo: 'https://github.com/git-repo',
132
- targetNamespace: 'lots-a',
133
- targets: [
134
- { clusterSelector: {} }
135
- ]
136
- },
137
- status: {
138
- commit: '420bf95e3d5ac89a7801deb0bc1206a5abc54b45',
139
- conditions: [
140
- {
141
- error: true,
142
- lastUpdateTime: '2025-05-04T16:33:15Z',
143
- message: 'Modified(2) [Cluster fleet-default/fleet-1]; configmap.v1 lots-a/test-config-eight missing',
144
- status: 'False',
145
- transitioning: true,
146
- type: 'Ready'
147
- },
148
- {
149
- error: false,
150
- lastUpdateTime: '2025-05-07T09:39:08Z',
151
- status: 'True',
152
- transitioning: false,
153
- type: 'GitPolling'
154
- },
155
- {
156
- error: false,
157
- lastUpdateTime: '2025-03-24T09:09:57Z',
158
- status: 'False',
159
- transitioning: false,
160
- type: 'Reconciling'
161
- },
162
- {
163
- error: false,
164
- lastUpdateTime: '2025-03-24T09:09:57Z',
165
- status: 'False',
166
- transitioning: false,
167
- type: 'Stalled'
168
- },
169
- {
170
- error: false,
171
- lastUpdateTime: '2025-03-24T09:09:57Z',
172
- status: 'True',
173
- transitioning: false,
174
- type: 'Accepted'
175
- }
176
- ],
177
- desiredReadyClusters: 2,
178
- display: {
179
- readyBundleDeployments: '1/2',
180
- state: 'Modified'
181
- },
182
- gitJobStatus: 'Current',
183
- lastPollingTriggered: '2025-05-07T13:34:43Z',
184
- observedGeneration: 1,
185
- perClusterResourceCounts: {
186
- 'fleet-default/fleet-1': {
187
- desiredReady: 2,
188
- missing: 1,
189
- modified: 0,
190
- notReady: 0,
191
- orphaned: 0,
192
- ready: 1,
193
- unknown: 0,
194
- waitApplied: 0
195
- },
196
- 'fleet-default/fleet-2': {
197
- desiredReady: 2,
198
- missing: 1,
199
- modified: 0,
200
- notReady: 0,
201
- orphaned: 0,
202
- ready: 1,
203
- unknown: 0,
204
- waitApplied: 0
205
- }
206
- },
207
- readyClusters: 0,
208
- resourceCounts: {
209
- desiredReady: 2,
210
- missing: 1,
211
- modified: 0,
212
- notReady: 0,
213
- orphaned: 0,
214
- ready: 1,
215
- unknown: 0,
216
- waitApplied: 0
217
- },
218
- resources: [
219
- {
220
- apiVersion: 'v1',
221
- id: 'lots-a/test-config-eight',
222
- kind: 'ConfigMap',
223
- name: 'test-config-eight',
224
- namespace: 'lots-a',
225
- perClusterState: {
226
- missing: [
227
- 'fleet-default/fleet-1'
228
- ]
229
- },
230
- state: 'Missing',
231
- type: 'configmap'
232
- },
233
- {
234
- apiVersion: 'v1',
235
- id: 'lots-a/test-config-five',
236
- kind: 'ConfigMap',
237
- name: 'test-config-five',
238
- namespace: 'lots-a',
239
- perClusterState: {
240
- missing: [
241
- 'fleet-default/fleet-1'
242
- ],
243
- ready: [
244
- 'fleet-default/fleet-2'
245
- ]
246
- },
247
- state: 'Missing',
248
- type: 'configmap'
249
- }
250
- ],
251
- summary: {
252
- desiredReady: 2,
253
- modified: 1,
254
- nonReadyResources: [
255
- {
256
- bundleState: 'Modified',
257
- modifiedStatus: [
258
- {
259
- apiVersion: 'v1',
260
- kind: 'ConfigMap',
261
- missing: true,
262
- name: 'test-config-five',
263
- namespace: 'lots-a'
264
- }
265
- ],
266
- name: 'lots-a-scale-lotsofbundles-five'
267
- }
268
- ],
269
- ready: 1
270
- }
271
- }
26
+ id: 'fleet-default/lots-a',
27
+ type: FLEET.GIT_REPO,
28
+ metadata: { namespace: 'fleet-local' },
29
+ nameDisplay: 'lots-a',
30
+ stateDisplay: 'Active',
31
+ stateSort: 'Active'
272
32
  }
273
33
  ];
274
34
 
275
- const mockedPresetMixin = { methods: { preset: jest.fn() } };
35
+ const helmOps = [
36
+ {
37
+ id: 'helm-op-1',
38
+ type: FLEET.HELM_OP,
39
+ metadata: { namespace: 'fleet-local' },
40
+ nameDisplay: 'helm-op-1',
41
+ stateDisplay: 'Active',
42
+ stateSort: 'Active'
43
+ }
44
+ ];
276
45
 
277
46
  const requiredSetup = (computed = {}, dataProps = {}) => {
278
47
  return {
279
48
  global: {
280
- mixins: [mockedPresetMixin],
281
- mocks: {
49
+ mocks: {
282
50
  $store: {
283
51
  getters: {
284
- currentProduct: { inStore: 'cluster' },
285
- 'management/schemaFor': jest.fn(),
286
- 'rancher/byId': jest.fn(),
287
- 'i18n/t': (text: string) => text,
288
- t: (text: string) => text,
52
+ currentProduct: { inStore: 'cluster' },
53
+ 'management/schemaFor': jest.fn(),
54
+ 'rancher/byId': jest.fn(),
55
+ 'i18n/t': (text: string) => text,
56
+ t: (text: string) => text,
57
+ 'slideInPanel/isOpen': false,
58
+ 'slideInPanel/isClosing': false,
289
59
  },
290
- dispatch: jest.fn()
60
+ dispatch: jest.fn(),
61
+ commit: jest.fn(),
291
62
  },
292
- $fetchState: {},
63
+ $fetchState: { pending: false },
64
+ $router: { push: jest.fn() },
65
+ $shell: { slideInPanel: jest.fn() },
66
+ t: (key: string) => key,
67
+ },
68
+ stubs: {
69
+ 'router-link': RouterLinkStub, ButtonGroup: true, Checkbox: true, RcButton: true, ResourcePanel: true, FleetApplications: true, ResourceCard: true, ResourceDetails: true, EmptyDashboard: true
293
70
  },
294
- stubs: { 'router-link': RouterLinkStub },
295
71
  directives: { 'trim-whitespace': (id: any) => id },
296
72
  },
297
73
  computed: {
298
74
  ...Dashboard.computed,
299
75
  allNamespaces: () => [],
300
- workspaces: () => [],
301
76
  ...computed
302
77
  },
303
78
  data: () => ({
304
- presetVersion: '',
79
+ presetVersion: '1.2.3',
305
80
  permissions: {},
306
81
  FLEET,
307
82
  [FLEET.GIT_REPO]: [],
@@ -311,7 +86,6 @@ const requiredSetup = (computed = {}, dataProps = {}) => {
311
86
  fleetWorkspaces: [],
312
87
  viewModeOptions: [
313
88
  {
314
- // tooltipKey: 'fleet.dashboard.viewMode.table',
315
89
  icon: 'icon-list-flat',
316
90
  value: 'flat',
317
91
  disabled: true
@@ -322,7 +96,7 @@ const requiredSetup = (computed = {}, dataProps = {}) => {
322
96
  value: 'cards',
323
97
  },
324
98
  ],
325
- viewMode: {},
99
+ viewMode: 'cards',
326
100
  isWorkspaceCollapsed: {},
327
101
  isStateCollapsed: {},
328
102
  typeFilter: {},
@@ -334,35 +108,47 @@ const requiredSetup = (computed = {}, dataProps = {}) => {
334
108
  };
335
109
 
336
110
  describe('component: FleetDashboard', () => {
337
- it('no workspaces', () => {
111
+ it('should render NoWorkspaces component when no workspaces are available and user has no permissions', async() => {
338
112
  const wrapper = mount(Dashboard, { ...requiredSetup() });
339
113
 
340
- const noWorkspaces = wrapper.find(`[data-testid="fleet-no-workspaces"]`);
341
- const noResources = wrapper.find(`[data-testid="fleet-empty-dashboard"]`);
342
- const workspaceCards = wrapper.find(`[data-testid="fleet-dashboard-workspace-cards"]`);
343
-
344
- expect(workspaceCards.exists()).toBeFalsy();
345
- expect(noWorkspaces.exists()).toBeTruthy();
346
- expect(noResources.exists()).toBeFalsy();
114
+ await wrapper.vm.$nextTick();
115
+ expect(wrapper.findComponent({ name: 'Loading' }).exists()).toBe(false);
116
+ expect(wrapper.findComponent({ name: 'NoWorkspaces' }).exists()).toBe(true);
117
+ expect(wrapper.findComponent({ name: 'EmptyDashboard' }).exists()).toBe(false);
347
118
  });
348
119
 
349
- it('no resources', () => {
120
+ it('should render EmptyDashboard component when workspaces exist but no gitRepos or helmOps', async() => {
350
121
  const wrapper = mount(Dashboard, {
351
122
  ...requiredSetup(
352
123
  { workspaces: () => fleetWorkspaces },
353
124
  ),
354
125
  });
355
126
 
356
- const noWorkspaces = wrapper.find(`[data-testid="fleet-no-workspaces"]`);
357
- const noResources = wrapper.find(`[data-testid="fleet-empty-dashboard"]`);
358
- const workspaceCards = wrapper.find(`[data-testid="fleet-dashboard-workspace-cards"]`);
127
+ await wrapper.vm.$nextTick();
128
+ expect(wrapper.findComponent({ name: 'Loading' }).exists()).toBe(false);
129
+ expect(wrapper.findComponent({ name: 'NoWorkspaces' }).exists()).toBe(false);
130
+ expect(wrapper.findComponent({ name: 'EmptyDashboard' }).exists()).toBe(true);
131
+ });
359
132
 
360
- expect(workspaceCards.exists()).toBeFalsy();
361
- expect(noWorkspaces.exists()).toBeFalsy();
362
- expect(noResources.exists()).toBeTruthy();
133
+ it('should render the dashboard when workspaces and resources exist', async() => {
134
+ const wrapper = mount(Dashboard, {
135
+ ...requiredSetup(
136
+ { workspaces: () => fleetWorkspaces },
137
+ {
138
+ [FLEET.GIT_REPO]: gitRepos,
139
+ [FLEET.HELM_OP]: helmOps,
140
+ }
141
+ ),
142
+ });
143
+
144
+ await wrapper.vm.$nextTick();
145
+ expect(wrapper.find('.dashboard').exists()).toBe(true);
146
+ expect(wrapper.findComponent({ name: 'Loading' }).exists()).toBe(false);
147
+ expect(wrapper.findComponent({ name: 'NoWorkspaces' }).exists()).toBe(false);
148
+ expect(wrapper.findComponent({ name: 'EmptyDashboard' }).exists()).toBe(false);
363
149
  });
364
150
 
365
- it('show workspace cards', () => {
151
+ it('should render workspace cards', () => {
366
152
  const wrapper = mount(Dashboard, {
367
153
  ...requiredSetup(
368
154
  { workspaces: () => fleetWorkspaces },
@@ -384,7 +170,7 @@ describe('component: FleetDashboard', () => {
384
170
  ['fleet-default', 'expanded', true],
385
171
  ['fleet-local', 'collapsed', false],
386
172
  ['fleet-default', 'collapsed', false],
387
- ])('%p workspace card is %p', async(workspace, _, collapsed) => {
173
+ ])('should %p workspace card be %p', async(workspace, _, collapsed) => {
388
174
  const wrapper = mount(Dashboard, {
389
175
  ...requiredSetup(
390
176
  { workspaces: () => fleetWorkspaces },
@@ -399,28 +185,536 @@ describe('component: FleetDashboard', () => {
399
185
  expect(expandedPanel.exists()).toBe(!collapsed);
400
186
  });
401
187
 
402
- // it.each([
403
- // ['git-repos', true],
404
- // ['helm-ops', false],
405
- // ['git-repos', true],
406
- // ['helm-ops', false],
407
- // ])('filter %p %p', async(type, isFiltered) => {
408
- // const wrapper = mount(Dashboard, {
409
- // ...requiredSetup(
410
- // {
411
- // workspaces: () => [ fleetWorkspaces[1] ],
412
- // },
413
- // {
414
- // [FLEET.GIT_REPO]: gitRepos,
415
- // }
416
- // ),
417
- // });
418
-
419
- // const statePanel = wrapper.find(`[data-testid="state-panel-Modified"]`);
420
- // const gitRepoCard1 = wrapper.find(`[data-testid="card-${ gitRepos[0].id }"]`);
421
- // const filterCheckbox = wrapper.find(`[data-testid="fleet-dashboard-filter-${ type }"]`);
422
-
423
- // filterCheckbox.setValue(false);
424
- // expect(filterCheckbox.exists()).toBe(true);
425
- // });
188
+ it('should workspaces return fleetWorkspaces if available', async() => {
189
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
190
+
191
+ wrapper.setData({ fleetWorkspaces });
192
+
193
+ await wrapper.vm.$nextTick();
194
+ expect(wrapper.vm.workspaces).toStrictEqual(fleetWorkspaces);
195
+ });
196
+
197
+ it('should allCardsExpanded be true if all workspaces are expanded', async() => {
198
+ const wrapper = mount(Dashboard, {
199
+ ...requiredSetup(
200
+ { workspaces: () => fleetWorkspaces },
201
+ {
202
+ isWorkspaceCollapsed: {
203
+ 'fleet-local': false,
204
+ 'fleet-default': false
205
+ },
206
+ }
207
+ ),
208
+ });
209
+
210
+ await wrapper.vm.$nextTick();
211
+ expect(wrapper.vm.allCardsExpanded).toBe(true);
212
+ });
213
+
214
+ it('should allCardsExpanded return false if any workspace is collapsed', async() => {
215
+ const wrapper = mount(Dashboard, {
216
+ ...requiredSetup(
217
+ { workspaces: () => fleetWorkspaces },
218
+ {
219
+ isWorkspaceCollapsed: {
220
+ 'fleet-local': true,
221
+ 'fleet-default': false
222
+ },
223
+ }
224
+ ),
225
+ });
226
+
227
+ await wrapper.vm.$nextTick();
228
+ expect(wrapper.vm.allCardsExpanded).toBe(false);
229
+ });
230
+
231
+ it('should selectStates toggle state filter and expands the workspace/state', async() => {
232
+ const workspaceId = 'fleet-local';
233
+ const stateDisplay = 'Modified';
234
+
235
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
236
+
237
+ wrapper.setData({
238
+ stateFilter: { [workspaceId]: {} },
239
+ isWorkspaceCollapsed: { [workspaceId]: true },
240
+ isStateCollapsed: { [workspaceId]: { [stateDisplay]: true } }
241
+ });
242
+
243
+ await wrapper.vm.selectStates(workspaceId, stateDisplay);
244
+
245
+ await wrapper.vm.$nextTick();
246
+
247
+ // Should set stateFilter for Modified to true
248
+ expect((wrapper.vm.stateFilter as any)[workspaceId][stateDisplay]).toBe(true);
249
+ // Workspace should remain expanded
250
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[workspaceId]).toBe(false);
251
+ // All states should be expanded
252
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][stateDisplay]).toBe(false);
253
+ });
254
+
255
+ it('should selectType toggle type filter and expands all states', async() => {
256
+ const workspaceId = 'fleet-local';
257
+ const stateDisplay = 'Modified';
258
+ const type = FLEET.GIT_REPO;
259
+
260
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
261
+
262
+ wrapper.setData({ isStateCollapsed: { [workspaceId]: { [stateDisplay]: true } } });
263
+
264
+ await wrapper.vm.selectType(workspaceId, type, false);
265
+ await wrapper.vm.$nextTick();
266
+
267
+ expect((wrapper.vm.typeFilter as any)[workspaceId][type]).toBe(false);
268
+
269
+ // Should expand all states for that workspace
270
+ Object.keys((wrapper.vm.isStateCollapsed as any)[workspaceId]).forEach((state) => {
271
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][state]).toBe(false);
272
+ });
273
+ });
274
+
275
+ it('should toggleCard toggle the collapse state of a workspace card', async() => {
276
+ const workspaceId = 'fleet-local';
277
+
278
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
279
+
280
+ await wrapper.vm.toggleCard(workspaceId);
281
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[workspaceId]).toBe(true);
282
+
283
+ await wrapper.vm.toggleCard(workspaceId);
284
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[workspaceId]).toBe(false);
285
+ });
286
+
287
+ it('should toggleCardAll expand or collapses all workspace cards', async() => {
288
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
289
+
290
+ const ws1 = fleetWorkspaces[0].id;
291
+ const ws2 = fleetWorkspaces[1].id;
292
+
293
+ wrapper.vm.isWorkspaceCollapsed = {
294
+ [ws1]: true,
295
+ [ws2]: false,
296
+ };
297
+
298
+ await wrapper.vm.$nextTick();
299
+
300
+ // Collapse all
301
+ await wrapper.vm.toggleCardAll('collapse');
302
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws1]).toBe(true);
303
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws2]).toBe(true);
304
+
305
+ // Expand all
306
+ await wrapper.vm.toggleCardAll('expand');
307
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws1]).toBe(false);
308
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws2]).toBe(false);
309
+ });
310
+
311
+ it('should toggleState toggle the collapse state of a state panel', async() => {
312
+ const workspaceId = 'fleet-local';
313
+ const stateDisplay = 'Modified';
314
+
315
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
316
+
317
+ wrapper.setData({ isStateCollapsed: { [workspaceId]: { [stateDisplay]: true } } });
318
+
319
+ await wrapper.vm.toggleState(workspaceId, stateDisplay);
320
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][stateDisplay]).toBe(false);
321
+
322
+ await wrapper.vm.toggleState(workspaceId, stateDisplay);
323
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][stateDisplay]).toBe(true);
324
+ });
325
+
326
+ it('should toggleStateAll expand or collapse all state panels for a workspace', async() => {
327
+ const workspaceId = 'fleet-local';
328
+
329
+ const state1 = 'Modified';
330
+ const state2 = 'Error';
331
+
332
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
333
+
334
+ wrapper.setData({ isStateCollapsed: { [workspaceId]: { [state1]: true } } });
335
+
336
+ (wrapper.vm.isStateCollapsed as any)[workspaceId] = {
337
+ [state1]: true,
338
+ [state2]: false,
339
+ };
340
+ await wrapper.vm.$nextTick();
341
+
342
+ // Collapse all states for the workspace
343
+ await wrapper.vm.toggleStateAll(workspaceId, 'collapse');
344
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][state1]).toBe(true);
345
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][state2]).toBe(true);
346
+
347
+ // Expand all states for the workspace
348
+ await wrapper.vm.toggleStateAll(workspaceId, 'expand');
349
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][state1]).toBe(false);
350
+ expect((wrapper.vm.isStateCollapsed as any)[workspaceId][state2]).toBe(false);
351
+ });
352
+
353
+ it('should loadMore increment cardsCount for a specific state', async() => {
354
+ const workspaceId = 'fleet-local';
355
+ const stateDisplay = 'Modified';
356
+
357
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
358
+
359
+ wrapper.vm.CARDS_MIN = 5;
360
+ wrapper.vm.CARDS_SIZE = 5;
361
+
362
+ expect((wrapper.vm.cardsCount as any)[workspaceId]).toBeUndefined();
363
+
364
+ await wrapper.vm.loadMore(workspaceId, stateDisplay);
365
+ expect((wrapper.vm.cardsCount as any)[workspaceId][stateDisplay]).toBe(wrapper.vm.CARDS_MIN + wrapper.vm.CARDS_SIZE);
366
+
367
+ await wrapper.vm.loadMore(workspaceId, stateDisplay);
368
+ expect((wrapper.vm.cardsCount as any)[workspaceId][stateDisplay]).toBe(wrapper.vm.CARDS_MIN + (2 * wrapper.vm.CARDS_SIZE));
369
+ });
370
+
371
+ it('should loadLess decrement cardsCount for a specific state, not going below CARDS_MIN', async() => {
372
+ const workspaceId = 'fleet-local';
373
+ const stateDisplay = 'Modified';
374
+
375
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
376
+
377
+ wrapper.vm.CARDS_MIN = 5;
378
+ wrapper.vm.CARDS_SIZE = 5;
379
+
380
+ (wrapper.vm.cardsCount as any)[workspaceId] = { [stateDisplay]: wrapper.vm.CARDS_MIN + (2 * wrapper.vm.CARDS_SIZE) }; // Start with enough to decrement
381
+ await wrapper.vm.$nextTick();
382
+
383
+ await wrapper.vm.loadLess(workspaceId, stateDisplay);
384
+ expect((wrapper.vm.cardsCount as any)[workspaceId][stateDisplay]).toBe(wrapper.vm.CARDS_MIN + wrapper.vm.CARDS_SIZE);
385
+
386
+ await wrapper.vm.loadLess(workspaceId, stateDisplay);
387
+ expect((wrapper.vm.cardsCount as any)[workspaceId][stateDisplay]).toBe(wrapper.vm.CARDS_MIN);
388
+ });
389
+
390
+ it('should _resourceStates groups resources by stateDisplay and sorts them', () => {
391
+ const resources = [
392
+ {
393
+ id: 'a', type: FLEET.GIT_REPO, metadata: { namespace: 'ns' }, nameDisplay: 'git-a', stateDisplay: 'Pending', stateSort: 'Pending'
394
+ },
395
+ {
396
+ id: 'b', type: FLEET.GIT_REPO, metadata: { namespace: 'ns' }, nameDisplay: 'git-b', stateDisplay: 'Active', stateSort: 'Active'
397
+ },
398
+ {
399
+ id: 'c', type: FLEET.GIT_REPO, metadata: { namespace: 'ns' }, nameDisplay: 'git-c', stateDisplay: 'Active', stateSort: 'Active'
400
+ },
401
+ {
402
+ id: 'd', type: FLEET.HELM_OP, metadata: { namespace: 'ns' }, nameDisplay: 'helm-d', stateDisplay: 'Error', stateSort: 'Error'
403
+ },
404
+ ];
405
+
406
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
407
+
408
+ const result = wrapper.vm._resourceStates(resources);
409
+
410
+ // Expect sorting by stateSort
411
+ expect(result.map((s) => s.stateDisplay)).toStrictEqual(['Active', 'Error', 'Pending']);
412
+ expect(result[0].resources).toHaveLength(2); // Active
413
+ expect(result[1].resources).toHaveLength(1); // Error
414
+ expect(result[2].resources).toHaveLength(1); // Pending
415
+ });
416
+
417
+ it('should _filterResources filters by type, state, and search filters', async() => {
418
+ const workspaceId = 'fleet-local';
419
+
420
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
421
+
422
+ wrapper.setData({ typeFilter: { [workspaceId]: { [FLEET.GIT_REPO]: false, [FLEET.HELM_OP]: true } } });
423
+
424
+ wrapper.vm.applicationStates[workspaceId] = [
425
+ {
426
+ stateDisplay: 'Active', statePanel: { id: 'active' }, resources: []
427
+ },
428
+ {
429
+ stateDisplay: 'Modified', statePanel: { id: 'modified' }, resources: []
430
+ },
431
+ {
432
+ stateDisplay: 'Error', statePanel: { id: 'error' }, resources: []
433
+ }
434
+ ];
435
+
436
+ const activeState = {
437
+ stateDisplay: 'Active',
438
+ statePanel: { id: 'active' },
439
+ resources: [
440
+ {
441
+ id: 'git-1', type: FLEET.GIT_REPO, namespace: workspaceId, nameDisplay: 'git-1', stateDisplay: 'Active', stateSort: 'Active'
442
+ },
443
+ {
444
+ id: 'helm-1', type: FLEET.HELM_OP, namespace: workspaceId, nameDisplay: 'helm-1', stateDisplay: 'Active', stateSort: 'Active'
445
+ },
446
+ ]
447
+ };
448
+ const modifiedState = {
449
+ stateDisplay: 'Modified',
450
+ statePanel: { id: 'modified' },
451
+ resources: [
452
+ {
453
+ id: 'git-2', type: FLEET.GIT_REPO, namespace: workspaceId, nameDisplay: 'git-2', stateDisplay: 'Modified', stateSort: 'Modified'
454
+ },
455
+ ]
456
+ };
457
+
458
+ let filtered = wrapper.vm._filterResources(activeState);
459
+
460
+ expect(filtered).toHaveLength(1); // Only GitRepos
461
+
462
+ // Test type filter
463
+ (wrapper.vm.typeFilter as any)[workspaceId][FLEET.GIT_REPO] = false;
464
+ (wrapper.vm.typeFilter as any)[workspaceId][FLEET.HELM_OP] = true;
465
+ filtered = wrapper.vm._filterResources(activeState);
466
+ expect(filtered).toHaveLength(1); // Only helm-1
467
+
468
+ // Test state filter
469
+ (wrapper.vm.typeFilter as any)[workspaceId][FLEET.GIT_REPO] = true;
470
+ (wrapper.vm.typeFilter as any)[workspaceId][FLEET.HELM_OP] = true;
471
+ (wrapper.vm.stateFilter as any)[workspaceId] = { active: true }; // Only show 'Active'
472
+ filtered = wrapper.vm._filterResources(modifiedState);
473
+ expect(filtered).toHaveLength(0);
474
+
475
+ // Test search filter
476
+ (wrapper.vm.stateFilter as any)[workspaceId] = {}; // Clear state filter
477
+ (wrapper.vm.searchFilter as any)[workspaceId] = 'git-1';
478
+ filtered = wrapper.vm._filterResources(activeState);
479
+ expect(filtered).toHaveLength(1); // Only git-1
480
+ expect(filtered[0].id).toBe('git-1');
481
+ });
482
+
483
+ it('should _groupByWorkspace correctly group resources by workspace ID', () => {
484
+ const workspaces = [
485
+ {
486
+ id: 'test-ws1', data: 'foo', repos: [], helmOps: []
487
+ },
488
+ {
489
+ id: 'test-ws2', data: 'bar', repos: [], helmOps: []
490
+ }
491
+ ];
492
+
493
+ const wrapper = mount(Dashboard, { ...requiredSetup({ workspaces: () => workspaces }) });
494
+
495
+ const callback = (ws: any) => ws.data.toUpperCase();
496
+ const result = wrapper.vm._groupByWorkspace(callback);
497
+
498
+ expect(result).toStrictEqual({
499
+ 'test-ws1': 'FOO',
500
+ 'test-ws2': 'BAR'
501
+ });
502
+ });
503
+
504
+ it('should _stateExistsInWorkspace correctly identify if a state exists in a workspace', () => {
505
+ const workspaceId = 'fleet-local';
506
+
507
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
508
+
509
+ wrapper.vm.applicationStates[workspaceId] = [
510
+ { stateDisplay: 'Active', statePanel: { id: 'active' } },
511
+ { stateDisplay: 'Modified', statePanel: { id: 'modified' } }
512
+ ];
513
+
514
+ expect(wrapper.vm._stateExistsInWorkspace(workspaceId, 'active')).toBe(true);
515
+ expect(wrapper.vm._stateExistsInWorkspace(workspaceId, 'non-existent')).toBe(false);
516
+ });
517
+
518
+ it('should _decodeStateFilter correctly filter based on stateFilter', () => {
519
+ const workspaceId = 'fleet-local';
520
+
521
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
522
+
523
+ wrapper.vm.applicationStates[workspaceId] = [
524
+ { stateDisplay: 'Active', statePanel: { id: 'active' } },
525
+ { stateDisplay: 'Modified', statePanel: { id: 'modified' } }
526
+ ];
527
+
528
+ (wrapper.vm.stateFilter as any)[workspaceId] = {};
529
+ expect(wrapper.vm._decodeStateFilter(workspaceId, { statePanel: { id: 'active' } })).toBe(true);
530
+
531
+ (wrapper.vm.stateFilter as any)[workspaceId] = { active: true };
532
+ expect(wrapper.vm._decodeStateFilter(workspaceId, { statePanel: { id: 'active' } })).toBe(true);
533
+ expect(wrapper.vm._decodeStateFilter(workspaceId, { statePanel: { id: 'modified' } })).toBe(false);
534
+
535
+ (wrapper.vm.stateFilter as any)[workspaceId] = { nonExistent: true };
536
+ expect(wrapper.vm._decodeStateFilter(workspaceId, { statePanel: { id: 'active' } })).toBe(true);
537
+ });
538
+
539
+ it('should _decodeTypeFilter correctly filter based on typeFilter', () => {
540
+ const workspaceId = 'fleet-local';
541
+
542
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
543
+
544
+ wrapper.vm.typeFilter = {};
545
+ expect(wrapper.vm._decodeTypeFilter(workspaceId, FLEET.GIT_REPO)).toBe(true);
546
+
547
+ (wrapper.vm.typeFilter as any)[workspaceId] = { [FLEET.GIT_REPO]: true };
548
+ expect(wrapper.vm._decodeTypeFilter(workspaceId, FLEET.GIT_REPO)).toBe(true);
549
+
550
+ expect(wrapper.vm._decodeTypeFilter(workspaceId, FLEET.HELM_OP)).toBe(false);
551
+ });
552
+
553
+ it('should _decodeSearchFilter correctly filter based on searchFilter', () => {
554
+ const workspaceId = 'fleet-local';
555
+
556
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
557
+
558
+ // No search filter set (returns true)
559
+ wrapper.vm.searchFilter = {};
560
+ expect(wrapper.vm._decodeSearchFilter(workspaceId, 'my-app')).toBe(true);
561
+
562
+ // Search filter set, name matches (returns true)
563
+ (wrapper.vm.searchFilter as any)[workspaceId] = 'my';
564
+ expect(wrapper.vm._decodeSearchFilter(workspaceId, 'my-app')).toBe(true);
565
+
566
+ // Search filter set, name does not match (returns false)
567
+ expect(wrapper.vm._decodeSearchFilter(workspaceId, 'other-app')).toBe(false);
568
+
569
+ // View mode is not CARDS (returns true)
570
+ wrapper.vm.viewMode = 'flat';
571
+ expect(wrapper.vm._decodeSearchFilter(workspaceId, 'my-app')).toBe(true);
572
+ });
573
+
574
+ it('should _cleanStateFilter remove non-existent states from stateFilter', () => {
575
+ const workspaceId = 'fleet-local';
576
+
577
+ const wrapper = mount(Dashboard, { ...requiredSetup() });
578
+
579
+ wrapper.vm.applicationStates[workspaceId] = [
580
+ { stateDisplay: 'Active', statePanel: { id: 'active' } },
581
+ ];
582
+ (wrapper.vm.stateFilter as any)[workspaceId] = {
583
+ active: true,
584
+ nonExistent: true
585
+ };
586
+
587
+ wrapper.vm._cleanStateFilter(workspaceId);
588
+
589
+ expect((wrapper.vm.stateFilter as any)[workspaceId]).toStrictEqual({ active: true });
590
+ });
591
+
592
+ it('should toggle workspace card collapse state when expand button is clicked', async() => {
593
+ const workspaceId = 'fleet-local';
594
+
595
+ const wrapper = mount(Dashboard, {
596
+ ...requiredSetup(
597
+ { workspaces: () => fleetWorkspaces },
598
+ {
599
+ [FLEET.GIT_REPO]: gitRepos,
600
+ [FLEET.HELM_OP]: helmOps,
601
+ isWorkspaceCollapsed: {
602
+ 'fleet-local': false,
603
+ 'fleet-default': true
604
+ },
605
+ isStateCollapsed: {
606
+ 'fleet-local': {
607
+ Active: true, Modified: true, Error: true
608
+ },
609
+ 'fleet-default': { Active: true }
610
+ },
611
+ typeFilter: {
612
+ 'fleet-local': {
613
+ [FLEET.GIT_REPO]: true,
614
+ [FLEET.HELM_OP]: true,
615
+ },
616
+ },
617
+ stateFilter: {},
618
+ searchFilter: {},
619
+ }
620
+ ),
621
+ stubs: ['Loading', 'NoWorkspaces', 'EmptyDashboard', 'RouterLinkStub'],
622
+ });
623
+
624
+ const expandButton = wrapper.find(`[data-testid="workspace-expand-btn-${ workspaceId }"]`);
625
+
626
+ await expandButton.trigger('click');
627
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[workspaceId]).toBe(true);
628
+
629
+ await expandButton.trigger('click');
630
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[workspaceId]).toBe(false);
631
+ });
632
+
633
+ it('should toggle all workspace cards collapse state when "Expand All" / "Collapse All" is clicked', async() => {
634
+ const wrapper = mount(Dashboard, {
635
+ ...requiredSetup(
636
+ { workspaces: () => fleetWorkspaces },
637
+ {
638
+ [FLEET.GIT_REPO]: gitRepos,
639
+ [FLEET.HELM_OP]: helmOps,
640
+ isWorkspaceCollapsed: {
641
+ 'fleet-local': false,
642
+ 'fleet-default': true
643
+ },
644
+ isStateCollapsed: {
645
+ 'fleet-local': {
646
+ Active: true, Modified: true, Error: true
647
+ },
648
+ 'fleet-default': { Active: true }
649
+ },
650
+ typeFilter: {
651
+ 'fleet-local': {
652
+ [FLEET.GIT_REPO]: true,
653
+ [FLEET.HELM_OP]: true,
654
+ },
655
+ },
656
+ stateFilter: {},
657
+ searchFilter: {},
658
+ }
659
+ ),
660
+ stubs: ['Loading', 'NoWorkspaces', 'EmptyDashboard', 'RouterLinkStub'],
661
+ });
662
+
663
+ const ws1 = fleetWorkspaces[0].id;
664
+ const ws2 = fleetWorkspaces[1].id;
665
+
666
+ (wrapper.vm.isWorkspaceCollapsed as any)[ws1] = true;
667
+ (wrapper.vm.isWorkspaceCollapsed as any)[ws2] = true;
668
+ await wrapper.vm.$nextTick();
669
+
670
+ const expandAllButton = wrapper.find('[data-testid="fleet-dashboard-expand-all"]');
671
+
672
+ // Click to Expand All
673
+ expect(wrapper.vm.allCardsExpanded).toBe(false);
674
+
675
+ await expandAllButton.trigger('click');
676
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws1]).toBe(false);
677
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws2]).toBe(false);
678
+ expect(wrapper.vm.allCardsExpanded).toBe(true);
679
+
680
+ // Click to Collapse All
681
+ await expandAllButton.trigger('click');
682
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws1]).toBe(true);
683
+ expect((wrapper.vm.isWorkspaceCollapsed as any)[ws2]).toBe(true);
684
+ expect(wrapper.vm.allCardsExpanded).toBe(false);
685
+ });
686
+
687
+ it('should update view mode when ButtonGroup value changes', async() => {
688
+ const wrapper = mount(Dashboard, {
689
+ ...requiredSetup(
690
+ { workspaces: () => fleetWorkspaces },
691
+ { [FLEET.GIT_REPO]: gitRepos }
692
+ ),
693
+ stubs: ['Loading', 'NoWorkspaces', 'EmptyDashboard', 'RouterLinkStub'],
694
+ });
695
+
696
+ const buttonGroup = wrapper.findComponent({ name: 'ButtonGroup' });
697
+
698
+ expect(wrapper.vm.viewMode).toBe('flat');
699
+
700
+ await buttonGroup.vm.$emit('update:value', 'cards');
701
+ expect(wrapper.vm.viewMode).toBe('cards');
702
+ });
703
+
704
+ it('should search filter be visible only if viewMode is cards', async() => {
705
+ const wrapper = mount(Dashboard, {
706
+ ...requiredSetup(
707
+ { workspaces: () => fleetWorkspaces },
708
+ { [FLEET.GIT_REPO]: gitRepos }
709
+ ),
710
+ stubs: ['Loading', 'NoWorkspaces', 'EmptyDashboard', 'RouterLinkStub'],
711
+ });
712
+
713
+ wrapper.vm.viewMode = 'cards';
714
+ await wrapper.vm.$nextTick();
715
+
716
+ const searchInput = wrapper.find('[data-testid="fleet-dashboard-search-input-fleet-local"]');
717
+
718
+ expect(searchInput.exists()).toBe(true);
719
+ });
426
720
  });