@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.5

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 (315) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/styles/global/_layout.scss +4 -0
  3. package/assets/translations/en-us.yaml +183 -51
  4. package/assets/translations/zh-hans.yaml +1 -7
  5. package/chart/monitoring/ClusterSelector.vue +0 -21
  6. package/chart/monitoring/prometheus/index.vue +6 -3
  7. package/components/ActionDropdownShell.vue +5 -3
  8. package/components/ButtonGroup.vue +26 -1
  9. package/components/CruResource.vue +212 -16
  10. package/components/ExplorerMembers.vue +8 -4
  11. package/components/ExplorerProjectsNamespaces.vue +10 -6
  12. package/components/GrowlManager.vue +4 -0
  13. package/components/MgmtNodeList.vue +184 -0
  14. package/components/PromptRestore.vue +93 -32
  15. package/components/Questions/index.vue +1 -0
  16. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  17. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  18. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  19. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  21. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  22. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  23. package/components/ResourceDetail/index.vue +1 -1
  24. package/components/ResourceList/Masthead.vue +7 -1
  25. package/components/ResourceList/index.vue +82 -1
  26. package/components/ResourceTable.vue +1 -0
  27. package/components/RichTranslation.vue +5 -2
  28. package/components/Setting.vue +1 -0
  29. package/components/SortableTable/index.vue +4 -3
  30. package/components/SubtleLink.vue +31 -6
  31. package/components/Tabbed/Tab.vue +29 -3
  32. package/components/Tabbed/index.vue +25 -3
  33. package/components/TableOfContents/TableOfContents.vue +109 -0
  34. package/components/TableOfContents/composables.ts +258 -0
  35. package/components/Window/ContainerShell.vue +21 -11
  36. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  37. package/components/Wizard.vue +23 -5
  38. package/components/__tests__/ButtonGroup.test.ts +56 -0
  39. package/components/__tests__/PromptRestore.test.ts +169 -19
  40. package/components/fleet/AppCoChartGrid.vue +401 -0
  41. package/components/fleet/AppCoEmptyState.vue +127 -0
  42. package/components/fleet/AppCoPageHeader.vue +119 -0
  43. package/components/fleet/AppCoVersionSelect.vue +70 -0
  44. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  45. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  46. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  47. package/components/fleet/FleetIntro.vue +7 -3
  48. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  49. package/components/fleet/FleetSecretSelector.vue +5 -3
  50. package/components/fleet/FleetValuesFrom.vue +8 -2
  51. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  52. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  53. package/components/fleet/GitRepoTargetTab.vue +0 -2
  54. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  55. package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
  56. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  57. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  58. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  59. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  60. package/components/fleet/HelmOpTargetTab.vue +64 -60
  61. package/components/fleet/HelmOpValuesTab.vue +129 -105
  62. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  63. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  64. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  65. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  66. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  67. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  68. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  69. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  70. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  71. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  72. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  73. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  74. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  75. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  76. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  77. package/components/fleet/dashboard/Empty.vue +8 -4
  78. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  79. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  80. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  81. package/components/form/ArrayList.vue +61 -4
  82. package/components/form/FileSelector.vue +39 -1
  83. package/components/form/KeyValue.vue +23 -2
  84. package/components/form/LabeledSelect.vue +39 -1
  85. package/components/form/Labels.vue +22 -3
  86. package/components/form/NameNsDescription.vue +13 -5
  87. package/components/form/PrivateRegistry.constants.ts +7 -0
  88. package/components/form/PrivateRegistry.vue +253 -18
  89. package/components/form/ResourceTabs/index.vue +1 -0
  90. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  91. package/components/form/__tests__/FileSelector.test.ts +23 -0
  92. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  93. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  94. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  95. package/components/formatter/EtcdSnapshotName.vue +73 -0
  96. package/components/formatter/InternalExternalIP.vue +10 -4
  97. package/components/formatter/ServiceTargets.vue +26 -7
  98. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  99. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  100. package/components/nav/Header.vue +12 -1
  101. package/components/nav/TopLevelMenu.vue +7 -2
  102. package/components/nav/__tests__/Header.test.ts +15 -0
  103. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  104. package/components/templates/default.vue +16 -4
  105. package/components/templates/home.vue +9 -4
  106. package/components/templates/plain.vue +9 -4
  107. package/composables/useHelmOpResources.test.ts +56 -0
  108. package/composables/useHelmOpResources.ts +32 -0
  109. package/composables/useStateColor.test.ts +325 -0
  110. package/composables/useStateColor.ts +128 -0
  111. package/config/features.js +1 -0
  112. package/config/home-links.js +1 -1
  113. package/config/labels-annotations.js +3 -0
  114. package/config/product/explorer.js +17 -4
  115. package/config/product/manager.js +8 -0
  116. package/config/router/index.js +16 -0
  117. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  118. package/config/router/navigation-guards/authentication.js +10 -4
  119. package/config/router/routes.js +20 -6
  120. package/config/secret.ts +10 -0
  121. package/config/settings.ts +6 -4
  122. package/config/table-headers.js +3 -4
  123. package/config/types.js +16 -0
  124. package/core/plugin-products-base.ts +3 -3
  125. package/core/plugin-types.ts +83 -30
  126. package/core/plugin.ts +3 -0
  127. package/core/types-provisioning.ts +34 -1
  128. package/core/types.ts +15 -2
  129. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  130. package/detail/__tests__/workload.test.ts +3 -152
  131. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  132. package/detail/provisioning.cattle.io.cluster.vue +109 -7
  133. package/detail/workload/index.vue +12 -55
  134. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  135. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  136. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  137. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  138. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
  139. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  140. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  141. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  142. package/edit/auth/__tests__/azuread.test.ts +34 -9
  143. package/edit/auth/__tests__/github.test.ts +234 -0
  144. package/edit/auth/__tests__/oidc.test.ts +26 -6
  145. package/edit/auth/__tests__/saml.test.ts +196 -0
  146. package/edit/auth/azuread.vue +128 -95
  147. package/edit/auth/github.vue +72 -13
  148. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  149. package/edit/auth/ldap/config.vue +8 -0
  150. package/edit/auth/ldap/index.vue +75 -1
  151. package/edit/auth/oidc.vue +119 -73
  152. package/edit/auth/saml.vue +76 -12
  153. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  154. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  155. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  156. package/edit/fleet.cattle.io.helmop.vue +542 -141
  157. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  158. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  159. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  160. package/edit/management.cattle.io.user.vue +5 -2
  161. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  162. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  163. package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
  164. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  165. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  166. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  167. package/list/group.principal.vue +5 -4
  168. package/list/harvesterhci.io.management.cluster.vue +8 -9
  169. package/list/management.cattle.io.user.vue +12 -9
  170. package/list/provisioning.cattle.io.cluster.vue +16 -10
  171. package/mixins/__tests__/auth-config.test.ts +90 -0
  172. package/mixins/__tests__/chart.test.ts +94 -0
  173. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  174. package/mixins/auth-config.js +7 -0
  175. package/mixins/chart.js +11 -2
  176. package/mixins/child-hook.js +12 -6
  177. package/mixins/create-edit-view/impl.js +5 -3
  178. package/mixins/resource-fetch-api-pagination.js +21 -1
  179. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  180. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  181. package/models/__tests__/fleet-application.test.ts +175 -0
  182. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  183. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  184. package/models/__tests__/management.cattle.io.node.ts +22 -0
  185. package/models/__tests__/namespace.test.ts +36 -0
  186. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
  187. package/models/__tests__/secret.test.ts +68 -1
  188. package/models/__tests__/workload.test.ts +401 -26
  189. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  190. package/models/compliance.cattle.io.clusterscan.js +39 -4
  191. package/models/fleet-application.js +4 -0
  192. package/models/fleet.cattle.io.helmop.js +20 -1
  193. package/models/management.cattle.io.cluster.js +39 -5
  194. package/models/management.cattle.io.node.js +44 -3
  195. package/models/namespace.js +1 -1
  196. package/models/pod.js +46 -3
  197. package/models/provisioning.cattle.io.cluster.js +64 -14
  198. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  199. package/models/secret.js +19 -0
  200. package/models/workload.js +120 -20
  201. package/models/workload.service.js +5 -0
  202. package/package.json +14 -13
  203. package/pages/about.vue +5 -6
  204. package/pages/auth/login.vue +0 -35
  205. package/pages/auth/setup.vue +11 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  208. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  209. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  210. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  211. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  212. package/pages/c/_cluster/apps/charts/install.vue +236 -144
  213. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  214. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  215. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  216. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  217. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  218. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  219. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  220. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  221. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  222. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  223. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  224. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  225. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  226. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  227. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  228. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  229. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  230. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  231. package/pages/c/_cluster/fleet/index.vue +2 -2
  232. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  233. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  234. package/pages/fail-whale.vue +16 -11
  235. package/pages/home.vue +16 -46
  236. package/pkg/require-asset.lib.js +25 -0
  237. package/pkg/vue.config.js +7 -0
  238. package/plugins/clean-html.d.ts +9 -0
  239. package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
  240. package/plugins/dashboard-store/getters.js +0 -1
  241. package/plugins/dashboard-store/resource-class.js +114 -19
  242. package/plugins/steve/__tests__/actions.test.ts +212 -0
  243. package/plugins/steve/actions.js +96 -0
  244. package/plugins/steve/steve-pagination-utils.ts +1 -1
  245. package/rancher-components/Accordion/Accordion.vue +53 -9
  246. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  247. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  248. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  249. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  250. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  251. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  252. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  253. package/rancher-components/RcButton/RcButton.vue +94 -15
  254. package/rancher-components/RcButton/index.ts +1 -1
  255. package/rancher-components/RcButton/types.ts +3 -0
  256. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  257. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  258. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  259. package/rancher-components/RcSection/RcSection.vue +28 -3
  260. package/scripts/extension/helm/package/Dockerfile +1 -1
  261. package/scripts/test-plugins-build.sh +2 -1
  262. package/store/__tests__/features.test.ts +131 -0
  263. package/store/__tests__/growl.test.ts +374 -0
  264. package/store/__tests__/modal.test.ts +131 -0
  265. package/store/__tests__/notifications.test.ts +434 -0
  266. package/store/__tests__/slideInPanel.test.ts +88 -0
  267. package/store/__tests__/type-map.utils.test.ts +433 -0
  268. package/store/catalog.js +57 -0
  269. package/store/features.js +4 -0
  270. package/store/plugins.js +7 -4
  271. package/types/components/buttonGroup.ts +5 -0
  272. package/types/shell/index.d.ts +166 -70
  273. package/utils/__tests__/auth.test.ts +273 -0
  274. package/utils/__tests__/computed.test.ts +193 -0
  275. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  276. package/utils/__tests__/dom.test.ts +81 -0
  277. package/utils/__tests__/duration.test.ts +37 -1
  278. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  279. package/utils/__tests__/fleet-appco.test.ts +312 -0
  280. package/utils/__tests__/monitoring.test.ts +130 -0
  281. package/utils/__tests__/object.test.ts +22 -0
  282. package/utils/__tests__/operation-cr.test.ts +34 -0
  283. package/utils/__tests__/platform.test.ts +91 -0
  284. package/utils/__tests__/position.test.ts +237 -0
  285. package/utils/__tests__/provider.test.ts +51 -1
  286. package/utils/__tests__/queue.test.ts +232 -0
  287. package/utils/__tests__/release-notes.test.ts +221 -0
  288. package/utils/__tests__/router.test.js +254 -1
  289. package/utils/__tests__/select.test.ts +208 -0
  290. package/utils/__tests__/time.test.ts +265 -1
  291. package/utils/__tests__/title.test.ts +47 -0
  292. package/utils/__tests__/width.test.ts +53 -0
  293. package/utils/__tests__/window.test.ts +158 -0
  294. package/utils/__tests__/xccdf.test.ts +126 -6
  295. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  296. package/utils/crypto/__tests__/index.test.ts +144 -0
  297. package/utils/duration.ts +104 -0
  298. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  299. package/utils/dynamic-content/info.ts +2 -1
  300. package/utils/error.js +13 -0
  301. package/utils/fleet-appco.ts +323 -0
  302. package/utils/object.js +22 -2
  303. package/utils/operation-cr.js +19 -0
  304. package/utils/provider.ts +12 -0
  305. package/utils/require-asset.ts +7 -0
  306. package/utils/validators/__tests__/container-images.test.ts +104 -0
  307. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  308. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  309. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  310. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  311. package/utils/validators/private-registry.ts +15 -4
  312. package/utils/xccdf.ts +39 -42
  313. package/vue.config.js +1 -1
  314. package/pages/support/index.vue +0 -264
  315. package/utils/duration.js +0 -43
@@ -1,6 +1,6 @@
1
1
  import { nextTick } from 'vue';
2
2
  /* eslint-disable jest/no-hooks */
3
- import { mount } from '@vue/test-utils';
3
+ import { mount, flushPromises } from '@vue/test-utils';
4
4
  import AzureAD from '@shell/edit/auth/azuread.vue';
5
5
  import { _EDIT } from '@shell/config/query-params';
6
6
  import { SLO_OPTION_VALUES } from '@shell/mixins/auth-config';
@@ -65,11 +65,36 @@ const requiredSetup = (modelOverrides = {}) => ({
65
65
  },
66
66
  });
67
67
 
68
+ describe('edit: azureAD accessMode default', () => {
69
+ let wrapper: any;
70
+
71
+ afterEach(() => {
72
+ wrapper.unmount();
73
+ });
74
+
75
+ it('should default accessMode to required when not set', async() => {
76
+ wrapper = mount(AzureAD, { ...requiredSetup({ accessMode: '' }) });
77
+ wrapper.setData({ model: { tenantId: 'trigger-watcher' } });
78
+ await nextTick();
79
+
80
+ expect(wrapper.vm.model.accessMode).toStrictEqual('unrestricted');
81
+ });
82
+
83
+ it('should not override accessMode when already set', async() => {
84
+ wrapper = mount(AzureAD, { ...requiredSetup({ accessMode: 'required' }) });
85
+ wrapper.setData({ model: { tenantId: 'trigger-watcher' } });
86
+ await nextTick();
87
+
88
+ expect(wrapper.vm.model.accessMode).toStrictEqual('required');
89
+ });
90
+ });
91
+
68
92
  describe('edit: azureAD should', () => {
69
93
  let wrapper: any;
70
94
 
71
- beforeEach(() => {
95
+ beforeEach(async() => {
72
96
  wrapper = mount(AzureAD, { ...requiredSetup() });
97
+ await flushPromises();
73
98
  });
74
99
  afterEach(() => {
75
100
  wrapper.unmount();
@@ -121,7 +146,7 @@ describe('edit: azureAD should', () => {
121
146
  tenantIdInputField.setValue(testCase.tenantId);
122
147
  applicationIdInputField.setValue(testCase.applicationId);
123
148
  applicationSecretInputField.setValue(testCase.applicationSecret);
124
- await nextTick();
149
+ await flushPromises();
125
150
 
126
151
  expect(saveButton.disabled).toBe(testCase.result);
127
152
  });
@@ -217,11 +242,11 @@ describe('edit: azureAD should', () => {
217
242
  tenantIdInputField.setValue(validTenantId);
218
243
  applicationIdInputField.setValue(validApplicationId);
219
244
  applicationSecretInputField.setValue(validAppSecret);
220
- await nextTick();
245
+ await flushPromises();
221
246
 
222
247
  expect(saveButton.disabled).toBe(false);
223
248
  customButton.trigger('click');
224
- await nextTick();
249
+ await flushPromises();
225
250
  expect(saveButton.disabled).toBe(true);
226
251
 
227
252
  const endpointInputField = wrapper.find('[data-testid="input-azureAD-endpoint"]');
@@ -233,7 +258,7 @@ describe('edit: azureAD should', () => {
233
258
  graphEndpointInputField.setValue(testCase.graphEndpoint);
234
259
  tokenEndpointInputField.setValue(testCase.tokenEndpoint);
235
260
  authEndpointInputField.setValue(testCase.authEndpoint);
236
- await nextTick();
261
+ await flushPromises();
237
262
 
238
263
  expect(saveButton.disabled).toBe(testCase.result);
239
264
  });
@@ -287,7 +312,7 @@ describe('edit: azureAD SSO logout should', () => {
287
312
  };
288
313
  },
289
314
  });
290
- await nextTick();
315
+ await flushPromises();
291
316
  const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
292
317
 
293
318
  expect(endSessionEndpointField.exists()).toBe(true);
@@ -303,7 +328,7 @@ describe('edit: azureAD SSO logout should', () => {
303
328
  };
304
329
  },
305
330
  });
306
- await nextTick();
331
+ await flushPromises();
307
332
  const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
308
333
 
309
334
  expect(endSessionEndpointField.exists()).toBe(true);
@@ -358,7 +383,7 @@ describe('edit: azureAD SSO logout should', () => {
358
383
  };
359
384
  },
360
385
  });
361
- await nextTick();
386
+ await flushPromises();
362
387
  const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
363
388
 
364
389
  expect(saveButton.disabled).toBe(testCase.disabled);
@@ -0,0 +1,234 @@
1
+ import { nextTick } from 'vue';
2
+ import { mount, type VueWrapper, flushPromises } from '@vue/test-utils';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+
5
+ import GitHub from '@shell/edit/auth/github.vue';
6
+
7
+ jest.mock('@shell/utils/clipboard', () => {
8
+ return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
9
+ });
10
+
11
+ const validClientId = 'github-client-id';
12
+ const validClientSecret = 'github-client-secret';
13
+ const validAppId = '12345';
14
+ const validPrivateKey = '-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----';
15
+ const validTargetUrl = 'https://github.mycompany.com';
16
+
17
+ const mockGitHubModel = {
18
+ enabled: false,
19
+ id: 'github',
20
+ clientId: validClientId,
21
+ clientSecret: validClientSecret,
22
+ hostname: 'github.com',
23
+ tls: true,
24
+ };
25
+
26
+ const mockGitHubAppModel = {
27
+ ...mockGitHubModel,
28
+ id: 'githubapp',
29
+ appId: validAppId,
30
+ privateKey: validPrivateKey,
31
+ };
32
+
33
+ const requiredSetup = (modelOverrides = {}, dataOverrides = {}) => ({
34
+ data() {
35
+ return {
36
+ isEnabling: false,
37
+ editConfig: false,
38
+ model: { ...mockGitHubModel, ...modelOverrides },
39
+ serverSetting: null,
40
+ errors: [],
41
+ originalModel: null,
42
+ principals: [],
43
+ authConfigName: 'github',
44
+ targetType: 'public',
45
+ targetUrl: '',
46
+ ...dataOverrides,
47
+ } as any;
48
+ },
49
+ global: {
50
+ mocks: {
51
+ $fetchState: { pending: false },
52
+ $store: {
53
+ getters: {
54
+ currentStore: () => 'current_store',
55
+ 'current_store/schemaFor': jest.fn(),
56
+ 'current_store/all': jest.fn(),
57
+ 'i18n/t': (val: string) => val,
58
+ 'i18n/exists': jest.fn(),
59
+ },
60
+ dispatch: jest.fn(),
61
+ },
62
+ $route: { query: { AS: '' }, params: { id: 'github' } },
63
+ $router: { applyQuery: jest.fn() },
64
+ },
65
+ },
66
+ props: {
67
+ value: {},
68
+ mode: _EDIT,
69
+ },
70
+ });
71
+
72
+ describe('github.vue', () => {
73
+ describe('GitHub provider', () => {
74
+ let wrapper: VueWrapper<any, any>;
75
+
76
+ beforeEach(() => {
77
+ wrapper = mount(GitHub, { ...requiredSetup() });
78
+ });
79
+
80
+ afterEach(() => {
81
+ wrapper.unmount();
82
+ });
83
+
84
+ describe('save button disabled', () => {
85
+ it('when clientId is empty', async() => {
86
+ wrapper.setData({ model: { clientId: '' } });
87
+ await wrapper.vm.validateAllFields();
88
+ await flushPromises();
89
+
90
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
91
+
92
+ expect(saveButton.disabled).toBe(true);
93
+ });
94
+
95
+ it('when clientSecret is empty', async() => {
96
+ wrapper.setData({ model: { clientSecret: '' } });
97
+ await wrapper.vm.validateAllFields();
98
+ await flushPromises();
99
+
100
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
101
+
102
+ expect(saveButton.disabled).toBe(true);
103
+ });
104
+
105
+ it('when both clientId and clientSecret are empty', async() => {
106
+ wrapper.setData({ model: { clientId: '' } });
107
+ wrapper.setData({ model: { clientSecret: '' } });
108
+ await wrapper.vm.validateAllFields();
109
+ await flushPromises();
110
+
111
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
112
+
113
+ expect(saveButton.disabled).toBe(true);
114
+ });
115
+ });
116
+
117
+ describe('save button enabled', () => {
118
+ it('when clientId and clientSecret are filled', async() => {
119
+ await nextTick();
120
+
121
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
122
+
123
+ expect(saveButton.disabled).toBe(false);
124
+ });
125
+
126
+ it('when provider is already enabled and not editing config', async() => {
127
+ wrapper.setData({ model: { ...mockGitHubModel, enabled: true }, editConfig: false });
128
+ await nextTick();
129
+
130
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
131
+
132
+ expect(saveButton.disabled).toBe(false);
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('GitHub App provider', () => {
138
+ let wrapper: VueWrapper<any, any>;
139
+
140
+ beforeEach(() => {
141
+ wrapper = mount(GitHub, { ...requiredSetup(mockGitHubAppModel) });
142
+ });
143
+
144
+ afterEach(() => {
145
+ wrapper.unmount();
146
+ });
147
+
148
+ describe('save button disabled', () => {
149
+ it('when appId is empty', async() => {
150
+ wrapper.setData({ model: { appId: '' } });
151
+ await wrapper.vm.validateAllFields();
152
+ await flushPromises();
153
+
154
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
155
+
156
+ expect(saveButton.disabled).toBe(true);
157
+ });
158
+
159
+ it('when privateKey is empty', async() => {
160
+ wrapper.vm.model.privateKey = '';
161
+ wrapper.setData({ model: { privateKey: '' } });
162
+ await wrapper.vm.validateAllFields();
163
+ await flushPromises();
164
+
165
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
166
+
167
+ expect(saveButton.disabled).toBe(true);
168
+ });
169
+ });
170
+
171
+ describe('save button enabled', () => {
172
+ it('when all required fields are filled', async() => {
173
+ await nextTick();
174
+
175
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
176
+
177
+ expect(saveButton.disabled).toBe(false);
178
+ });
179
+ });
180
+ });
181
+
182
+ describe('GitHub App fields not required for GitHub provider', () => {
183
+ let wrapper: VueWrapper<any, any>;
184
+
185
+ beforeEach(() => {
186
+ wrapper = mount(GitHub, { ...requiredSetup() });
187
+ });
188
+
189
+ afterEach(() => {
190
+ wrapper.unmount();
191
+ });
192
+
193
+ it('save button is enabled even when appId and privateKey are absent', async() => {
194
+ await nextTick();
195
+
196
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
197
+
198
+ expect(saveButton.disabled).toBe(false);
199
+ });
200
+ });
201
+
202
+ describe('private GitHub Enterprise target', () => {
203
+ let wrapper: VueWrapper<any, any>;
204
+
205
+ beforeEach(() => {
206
+ wrapper = mount(
207
+ GitHub,
208
+ { ...requiredSetup({}, { targetType: 'private', targetUrl: validTargetUrl }) }
209
+ );
210
+ });
211
+
212
+ afterEach(() => {
213
+ wrapper.unmount();
214
+ });
215
+
216
+ it('save button is disabled when targetUrl is empty', async() => {
217
+ wrapper.setData({ targetType: 'private', targetUrl: '' });
218
+ await wrapper.vm.validateAllFields();
219
+ await flushPromises();
220
+
221
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
222
+
223
+ expect(saveButton.disabled).toBe(true);
224
+ });
225
+
226
+ it('save button is enabled when targetUrl is filled', async() => {
227
+ await nextTick();
228
+
229
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
230
+
231
+ expect(saveButton.disabled).toBe(false);
232
+ });
233
+ });
234
+ });
@@ -1,6 +1,6 @@
1
1
  import { nextTick } from 'vue';
2
2
  /* eslint-disable jest/no-hooks */
3
- import { mount, type VueWrapper } from '@vue/test-utils';
3
+ import { mount, type VueWrapper, flushPromises } from '@vue/test-utils';
4
4
  import { _EDIT } from '@shell/config/query-params';
5
5
 
6
6
  import oidc from '@shell/edit/auth/oidc.vue';
@@ -9,6 +9,18 @@ jest.mock('@shell/utils/clipboard', () => {
9
9
  return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
10
10
  });
11
11
 
12
+ const mockStore = {
13
+ getters: {
14
+ 'i18n/t': (key: string) => key,
15
+ 'i18n/exists': () => false,
16
+ },
17
+ };
18
+
19
+ jest.mock('vuex', () => ({
20
+ ...jest.requireActual('vuex'),
21
+ useStore: () => mockStore,
22
+ }));
23
+
12
24
  const validClientId = 'rancheroidc';
13
25
  const validClientSecret = 'TOkUxg0P67m1UXWNkJLHDPkUZFIKOWSq';
14
26
  const validUrl = 'https://localhost:8080';
@@ -79,20 +91,26 @@ describe('oidc.vue', () => {
79
91
  });
80
92
 
81
93
  describe('have "Create" button disabled', () => {
82
- it('given missing Auth endpoint URL', () => {
94
+ // validateAllFields() replicates what happens on blur in a real browser: it runs
95
+ // the toTypedSchema validators for every registered field and flushes async errors.
96
+ it('given missing Auth endpoint URL', async() => {
83
97
  wrapper.vm.model.authEndpoint = '';
84
98
  wrapper.vm.model.scopes = 'openid profile email'; // set scope to be sure
85
99
  wrapper.vm.oidcScope = ['openid', 'profile', 'email']; // TODO #13457: this is duplicated due wrong format of scopes
100
+ await wrapper.vm.validateAllFields();
101
+ await flushPromises();
86
102
 
87
103
  const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
88
104
 
89
105
  expect(saveButton.disabled).toBe(true);
90
106
  });
91
107
 
92
- it('given missing required basic scopes', () => {
93
- wrapper.vm.model.authEndpoint = 'whatever'; // set auth endpoint to be sure
108
+ it('given missing required basic scopes', async() => {
109
+ wrapper.vm.model.authEndpoint = validAuthEndpoint; // set auth endpoint to be sure
94
110
  wrapper.vm.model.scopes = 'something else'; // set wrong scope
95
111
  wrapper.vm.oidcScope = ['something', 'else']; // TODO #13457: this is duplicated due wrong format of scopes
112
+ await wrapper.vm.validateAllFields();
113
+ await flushPromises();
96
114
 
97
115
  const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
98
116
 
@@ -101,7 +119,8 @@ describe('oidc.vue', () => {
101
119
 
102
120
  it('when provider is disabled and editing config before fields are filled in', async() => {
103
121
  wrapper.setData({ model: {}, editConfig: true });
104
- await nextTick();
122
+ await wrapper.vm.validateAllFields();
123
+ await flushPromises();
105
124
 
106
125
  const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
107
126
 
@@ -110,7 +129,8 @@ describe('oidc.vue', () => {
110
129
 
111
130
  it('when provider is disabled and editing config after required fields and scope is missing openid', async() => {
112
131
  wrapper.setData({ oidcUrls: { url: validUrl, realm: validRealm } });
113
- await nextTick();
132
+ await wrapper.vm.validateAllFields();
133
+ await flushPromises();
114
134
 
115
135
  const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
116
136
 
@@ -0,0 +1,196 @@
1
+ import { nextTick } from 'vue';
2
+ import { mount, type VueWrapper, flushPromises } from '@vue/test-utils';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import Saml from '@shell/edit/auth/saml.vue';
5
+
6
+ const REQUIRED_FIELDS = [
7
+ 'displayNameField',
8
+ 'userNameField',
9
+ 'uidField',
10
+ 'groupsField',
11
+ 'rancherApiHost',
12
+ 'spKey',
13
+ 'spCert',
14
+ 'idpMetadataContent',
15
+ ] as const;
16
+
17
+ type RequiredField = typeof REQUIRED_FIELDS[number];
18
+
19
+ const validModel = {
20
+ enabled: false,
21
+ id: 'shibboleth',
22
+ displayNameField: 'givenName',
23
+ userNameField: 'uid',
24
+ uidField: 'uid',
25
+ groupsField: 'memberOf',
26
+ rancherApiHost: 'https://rancher.example.com',
27
+ spKey: '-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----',
28
+ spCert: '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----',
29
+ idpMetadataContent: '<EntityDescriptor/>',
30
+ };
31
+
32
+ const emptyRequiredFields: Record<RequiredField, string> = {
33
+ displayNameField: '',
34
+ userNameField: '',
35
+ uidField: '',
36
+ groupsField: '',
37
+ rancherApiHost: '',
38
+ spKey: '',
39
+ spCert: '',
40
+ idpMetadataContent: '',
41
+ };
42
+
43
+ const mountOptions = (model: object) => ({
44
+ data() {
45
+ return {
46
+ isEnabling: false,
47
+ editConfig: false,
48
+ model: { ...model },
49
+ serverSetting: null,
50
+ errors: [],
51
+ originalModel: null,
52
+ principals: [],
53
+ authConfigName: 'shibboleth',
54
+ } as any;
55
+ },
56
+ global: {
57
+ mocks: {
58
+ $fetchState: { pending: false },
59
+ $store: {
60
+ getters: {
61
+ currentStore: () => 'current_store',
62
+ 'current_store/schemaFor': jest.fn(),
63
+ 'current_store/all': jest.fn(),
64
+ 'i18n/t': (val: string) => val,
65
+ 'i18n/exists': jest.fn(),
66
+ },
67
+ dispatch: jest.fn(),
68
+ },
69
+ $route: { query: { AS: '' }, params: { id: 'shibboleth' } },
70
+ $router: { applyQuery: jest.fn() },
71
+ },
72
+ },
73
+ props: {
74
+ value: {},
75
+ mode: _EDIT,
76
+ },
77
+ });
78
+
79
+ describe('saml.vue', () => {
80
+ describe('validationPassed computed', () => {
81
+ let wrapper: VueWrapper<any, any>;
82
+
83
+ afterEach(() => {
84
+ wrapper.unmount();
85
+ });
86
+
87
+ it('returns true when all required fields are filled', async() => {
88
+ wrapper = mount(Saml, mountOptions(validModel));
89
+ await flushPromises();
90
+
91
+ expect(wrapper.vm.validationPassed).toBe(true);
92
+ });
93
+
94
+ it('returns false when all required fields are empty', async() => {
95
+ wrapper = mount(Saml, mountOptions({ ...validModel, ...emptyRequiredFields }));
96
+ await wrapper.vm.validateAllFields();
97
+ await flushPromises();
98
+
99
+ expect(wrapper.vm.validationPassed).toBe(false);
100
+ });
101
+
102
+ it('returns true when provider is enabled and not editing config, regardless of field state', async() => {
103
+ wrapper = mount(Saml, mountOptions({
104
+ ...validModel, ...emptyRequiredFields, enabled: true
105
+ }));
106
+ await wrapper.vm.validateAllFields();
107
+ await flushPromises();
108
+
109
+ expect(wrapper.vm.validationPassed).toBe(true);
110
+ });
111
+
112
+ it('returns false when provider is enabled but editConfig is true and required fields are empty', async() => {
113
+ wrapper = mount(Saml, {
114
+ ...mountOptions({
115
+ ...validModel, ...emptyRequiredFields, enabled: true
116
+ }),
117
+ data() {
118
+ return {
119
+ isEnabling: false,
120
+ editConfig: true,
121
+ model: {
122
+ ...validModel, ...emptyRequiredFields, enabled: true
123
+ },
124
+ serverSetting: null,
125
+ errors: [],
126
+ originalModel: null,
127
+ principals: [],
128
+ authConfigName: 'shibboleth',
129
+ } as any;
130
+ },
131
+ });
132
+ await wrapper.vm.validateAllFields();
133
+ await flushPromises();
134
+
135
+ expect(wrapper.vm.validationPassed).toBe(false);
136
+ });
137
+ });
138
+
139
+ describe('Enable button', () => {
140
+ describe('is disabled', () => {
141
+ let wrapper: VueWrapper<any, any>;
142
+
143
+ afterEach(() => {
144
+ wrapper.unmount();
145
+ });
146
+
147
+ it('when all required fields are empty', async() => {
148
+ wrapper = mount(Saml, mountOptions({ ...validModel, ...emptyRequiredFields }));
149
+ await wrapper.vm.validateAllFields();
150
+ await flushPromises();
151
+
152
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
153
+
154
+ expect(saveButton.disabled).toBe(true);
155
+ });
156
+
157
+ it.each([...REQUIRED_FIELDS])('when only %s is empty', async(field: RequiredField) => {
158
+ wrapper = mount(Saml, mountOptions({ ...validModel, [field]: '' }));
159
+ await wrapper.vm.validateAllFields();
160
+ await flushPromises();
161
+
162
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
163
+
164
+ expect(saveButton.disabled).toBe(true);
165
+ });
166
+ });
167
+
168
+ describe('is enabled', () => {
169
+ let wrapper: VueWrapper<any, any>;
170
+
171
+ afterEach(() => {
172
+ wrapper.unmount();
173
+ });
174
+
175
+ it('when all required fields are filled', async() => {
176
+ wrapper = mount(Saml, mountOptions(validModel));
177
+ await flushPromises();
178
+
179
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
180
+
181
+ expect(saveButton.disabled).toBe(false);
182
+ });
183
+
184
+ it('when provider is already enabled and not editing config', async() => {
185
+ wrapper = mount(Saml, mountOptions({
186
+ ...validModel, ...emptyRequiredFields, enabled: true
187
+ }));
188
+ await nextTick();
189
+
190
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
191
+
192
+ expect(saveButton.disabled).toBe(false);
193
+ });
194
+ });
195
+ });
196
+ });