@rancher/shell 3.0.5-rc.1 → 3.0.5-rc.3
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.
- package/assets/data/aws-regions.json +2 -0
- package/assets/images/providers/sks.svg +1 -0
- package/assets/styles/base/_helpers.scss +4 -0
- package/assets/styles/base/_variables.scss +1 -0
- package/assets/styles/global/_layout.scss +0 -1
- package/assets/translations/en-us.yaml +92 -34
- package/assets/translations/zh-hans.yaml +4 -13
- package/chart/monitoring/index.vue +4 -2
- package/components/ActionDropdownShell.vue +71 -0
- package/components/AppModal.vue +18 -4
- package/components/AsyncButton.vue +2 -0
- package/components/CodeMirror.vue +3 -3
- package/components/CommunityLinks.vue +3 -58
- package/components/CruResource.vue +109 -16
- package/components/ExplorerProjectsNamespaces.vue +19 -6
- package/components/FixedBanner.vue +19 -5
- package/components/GlobalRoleBindings.vue +5 -1
- package/components/GrowlManager.vue +1 -0
- package/components/LandingPagePreference.vue +2 -0
- package/components/LocaleSelector.vue +1 -1
- package/components/ModalManager.vue +55 -0
- package/components/PaginatedResourceTable.vue +7 -0
- package/components/PromptModal.vue +47 -8
- package/components/ResourceDetail/Masthead.vue +38 -13
- package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
- package/components/ResourceDetail/index.vue +47 -12
- package/components/ResourceList/index.vue +2 -1
- package/components/ResourceTable.vue +54 -19
- package/components/SideNav.vue +5 -1
- package/components/SlideInPanelManager.vue +125 -0
- package/components/SortableTable/THead.vue +5 -2
- package/components/SortableTable/actions.js +1 -1
- package/components/SortableTable/index.vue +54 -40
- package/components/SortableTable/paging.js +16 -19
- package/components/SortableTable/selection.js +1 -12
- package/components/Tabbed/index.vue +6 -0
- package/components/Wizard.vue +2 -2
- package/components/__tests__/AsyncButton.test.ts +39 -0
- package/components/__tests__/CruResource.test.ts +63 -0
- package/components/__tests__/ModalManager.spec.ts +176 -0
- package/components/__tests__/PromptModal.test.ts +146 -0
- package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
- package/components/auth/AuthBanner.vue +13 -11
- package/components/auth/Principal.vue +1 -0
- package/components/auth/login/ldap.vue +1 -1
- package/components/fleet/FleetResources.vue +21 -6
- package/components/form/ArrayList.vue +138 -118
- package/components/form/BannerSettings.vue +149 -85
- package/components/form/ColorInput.vue +35 -6
- package/components/form/EnvVars.vue +1 -0
- package/components/form/KeyValue.vue +10 -7
- package/components/form/LabeledSelect.vue +25 -23
- package/components/form/MatchExpressions.vue +9 -2
- package/components/form/NameNsDescription.vue +6 -2
- package/components/form/NotificationSettings.vue +15 -1
- package/components/form/Password.vue +1 -0
- package/components/form/Probe.vue +1 -0
- package/components/form/ResourceSelector.vue +26 -23
- package/components/form/ResourceTabs/index.vue +2 -1
- package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +15 -34
- package/components/form/SSHKnownHosts/index.vue +14 -11
- package/components/form/Select.vue +8 -15
- package/components/form/UnitInput.vue +13 -0
- package/components/form/ValueFromResource.vue +12 -12
- package/components/form/__tests__/ArrayList.test.ts +34 -2
- package/components/form/__tests__/ColorInput.test.ts +35 -0
- package/components/form/__tests__/KeyValue.test.ts +36 -0
- package/components/form/__tests__/LabeledSelect.test.ts +73 -0
- package/components/form/__tests__/SSHKnownHosts.test.ts +11 -2
- package/components/form/__tests__/Select.test.ts +34 -1
- package/components/form/__tests__/UnitInput.test.ts +23 -1
- package/components/formatter/ClusterLink.vue +5 -8
- package/components/formatter/Description.vue +30 -0
- package/components/formatter/__tests__/ClusterLink.test.ts +2 -32
- package/components/nav/Group.vue +12 -4
- package/components/nav/Header.vue +16 -43
- package/components/nav/NamespaceFilter.vue +134 -86
- package/components/nav/TopLevelMenu.vue +4 -5
- package/components/nav/WindowManager/ContainerLogs.vue +87 -61
- package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
- package/components/nav/WindowManager/index.vue +1 -0
- package/components/templates/default.vue +6 -3
- package/components/templates/home.vue +6 -0
- package/components/templates/plain.vue +6 -3
- package/composables/focusTrap.ts +12 -4
- package/config/product/explorer.js +16 -13
- package/config/product/manager.js +1 -28
- package/config/settings.ts +11 -13
- package/config/store.js +4 -0
- package/config/table-headers.js +7 -5
- package/config/uiplugins.js +5 -1
- package/core/types.ts +7 -6
- package/detail/catalog.cattle.io.app.vue +5 -1
- package/detail/fleet.cattle.io.bundle.vue +70 -6
- package/detail/fleet.cattle.io.gitrepo.vue +1 -1
- package/detail/namespace.vue +0 -3
- package/detail/node.vue +17 -13
- package/detail/provisioning.cattle.io.cluster.vue +85 -9
- package/detail/service.vue +0 -1
- package/detail/workload/index.vue +21 -34
- package/dialog/AddCustomBadgeDialog.vue +0 -1
- package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
- package/dialog/AssignToDialog.vue +176 -0
- package/dialog/ChangePasswordDialog.vue +106 -0
- package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
- package/dialog/DisableAuthProviderDialog.vue +101 -0
- package/dialog/DrainNode.vue +1 -1
- package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
- package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +83 -65
- package/dialog/FeatureFlagListDialog.vue +288 -0
- package/dialog/ForceMachineRemoveDialog.vue +1 -1
- package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
- package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
- package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -62
- package/dialog/MoveNamespaceDialog.vue +157 -0
- package/dialog/ScalePoolDownDialog.vue +1 -1
- package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
- package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
- package/dialog/WechatDialog.vue +57 -0
- package/edit/__tests__/service.test.ts +2 -1
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/github.vue +1 -1
- package/edit/auth/googleoauth.vue +1 -1
- package/edit/auth/ldap/index.vue +1 -1
- package/edit/auth/oidc.vue +1 -1
- package/edit/auth/saml.vue +1 -1
- package/edit/cloudcredential.vue +24 -10
- package/edit/management.cattle.io.user.vue +28 -3
- package/edit/namespace.vue +1 -4
- package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +3 -14
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +57 -62
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +3 -14
- package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.test.ts +72 -41
- package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +17 -1
- package/edit/networking.k8s.io.networkpolicy/index.vue +18 -30
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -10
- package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +21 -73
- package/edit/provisioning.cattle.io.cluster/rke2.vue +24 -7
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +4 -1
- package/edit/service.vue +13 -28
- package/initialize/install-plugins.js +2 -1
- package/list/harvesterhci.io.management.cluster.vue +4 -1
- package/list/management.cattle.io.feature.vue +4 -288
- package/list/workload.vue +6 -1
- package/machine-config/azure.vue +16 -4
- package/mixins/resource-fetch-api-pagination.js +55 -43
- package/mixins/resource-fetch.js +14 -5
- package/mixins/vue-select-overrides.js +0 -4
- package/models/__tests__/workload.test.ts +1 -0
- package/models/cluster/node.js +1 -0
- package/models/cluster.js +32 -2
- package/models/fleet.cattle.io.cluster.js +8 -2
- package/models/fleet.cattle.io.gitrepo.js +8 -34
- package/models/management.cattle.io.cluster.js +0 -20
- package/models/management.cattle.io.feature.js +7 -1
- package/models/management.cattle.io.node.js +7 -22
- package/models/management.cattle.io.nodepool.js +12 -0
- package/models/namespace.js +12 -1
- package/models/provisioning.cattle.io.cluster.js +18 -64
- package/models/service.js +24 -9
- package/models/workload.js +70 -31
- package/package.json +1 -1
- package/pages/about.vue +13 -3
- package/pages/account/index.vue +12 -5
- package/pages/auth/login.vue +7 -4
- package/pages/auth/setup.vue +1 -0
- package/pages/auth/verify.vue +9 -7
- package/pages/c/_cluster/apps/charts/install.vue +25 -26
- package/pages/c/_cluster/auth/config/index.vue +10 -12
- package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
- package/pages/c/_cluster/explorer/index.vue +28 -15
- package/pages/c/_cluster/istio/index.vue +2 -2
- package/pages/c/_cluster/longhorn/index.vue +3 -3
- package/pages/c/_cluster/monitoring/index.vue +1 -1
- package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
- package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
- package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
- package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
- package/pages/c/_cluster/neuvector/index.vue +1 -1
- package/pages/c/_cluster/settings/banners.vue +60 -5
- package/pages/c/_cluster/settings/performance.vue +7 -26
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
- package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
- package/pages/c/_cluster/uiplugins/index.vue +98 -55
- package/pages/diagnostic.vue +12 -9
- package/pages/fail-whale.vue +8 -5
- package/pages/home.vue +11 -52
- package/pages/prefs.vue +7 -6
- package/plugins/clean-html.js +2 -0
- package/plugins/dashboard-store/__tests__/actions.test.ts +4 -1
- package/plugins/dashboard-store/actions.js +122 -21
- package/plugins/dashboard-store/getters.js +74 -3
- package/plugins/dashboard-store/mutations.js +10 -5
- package/plugins/dashboard-store/resource-class.js +23 -3
- package/plugins/internal-api/index.ts +37 -0
- package/plugins/internal-api/shared/base-api.ts +13 -0
- package/plugins/internal-api/shell/shell.api.ts +108 -0
- package/plugins/steve/__tests__/getters.test.ts +18 -11
- package/plugins/steve/__tests__/steve-class.test.ts +1 -0
- package/plugins/steve/actions.js +34 -24
- package/plugins/steve/getters.js +39 -10
- package/plugins/steve/steve-class.js +5 -0
- package/plugins/steve/steve-pagination-utils.ts +199 -37
- package/plugins/steve/worker/web-worker.advanced.js +3 -1
- package/public/index.html +1 -0
- package/rancher-components/Banner/Banner.test.ts +51 -3
- package/rancher-components/Banner/Banner.vue +28 -6
- package/rancher-components/Card/Card.vue +1 -1
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +51 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
- package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
- package/rancher-components/Form/Radio/RadioButton.vue +20 -4
- package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
- package/rancher-components/Form/Radio/RadioGroup.vue +75 -35
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +26 -1
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
- package/rancher-components/RcButton/RcButton.vue +2 -1
- package/rancher-components/RcButton/types.ts +1 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +17 -6
- package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
- package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
- package/rancher-components/RcDropdown/index.ts +2 -0
- package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
- package/scripts/extension/bundle +20 -0
- package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
- package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
- package/scripts/extension/helmpatch +44 -31
- package/scripts/extension/publish +12 -13
- package/scripts/typegen.sh +2 -4
- package/store/action-menu.js +26 -56
- package/store/features.js +0 -1
- package/store/index.js +5 -0
- package/store/modal.ts +71 -0
- package/store/slideInPanel.ts +47 -0
- package/store/type-map.js +8 -1
- package/store/type-map.utils.ts +49 -6
- package/types/fleet.d.ts +1 -1
- package/types/global-vue.d.ts +5 -0
- package/types/internal-api/shell/growl.d.ts +25 -0
- package/types/internal-api/shell/modal.d.ts +77 -0
- package/types/internal-api/shell/slideIn.d.ts +15 -0
- package/types/kube/kube-api.ts +22 -0
- package/types/resources/fleet.d.ts +0 -14
- package/types/resources/settings.d.ts +0 -4
- package/types/shell/index.d.ts +375 -306
- package/types/store/dashboard-store.types.ts +24 -1
- package/types/store/pagination.types.ts +19 -2
- package/types/vue-shim.d.ts +4 -1
- package/utils/__mocks__/tabbable.js +13 -0
- package/utils/__tests__/object.test.ts +38 -4
- package/utils/cluster.js +24 -20
- package/utils/fleet.ts +15 -73
- package/utils/grafana.js +1 -0
- package/utils/object.js +36 -5
- package/utils/pagination-utils.ts +6 -2
- package/utils/perf-setting.utils.ts +28 -0
- package/utils/selector-typed.ts +205 -0
- package/utils/selector.js +29 -6
- package/utils/uiplugins.ts +10 -6
- package/utils/v-sphere.ts +5 -1
- package/utils/validators/formRules/__tests__/index.test.ts +10 -1
- package/utils/validators/formRules/index.ts +27 -3
- package/components/AssignTo.vue +0 -199
- package/components/DisableAuthProviderModal.vue +0 -115
- package/components/MoveModal.vue +0 -167
- package/components/PromptChangePassword.vue +0 -123
- package/components/fleet/FleetBundleResources.vue +0 -86
- package/components/formatter/RKETemplateName.vue +0 -37
- package/dialog/SaveAsRKETemplateDialog.vue +0 -139
- package/types/vue-shim.d +0 -20
|
@@ -49,6 +49,21 @@ export default {
|
|
|
49
49
|
return this.$store.getters['i18n/t'](this.pagingLabel, opt);
|
|
50
50
|
},
|
|
51
51
|
|
|
52
|
+
perPage() {
|
|
53
|
+
let out = this.rowsPerPage || 0;
|
|
54
|
+
|
|
55
|
+
if ( out <= 0 ) {
|
|
56
|
+
out = parseInt(this.$store.getters['prefs/get'](ROWS_PER_PAGE), 10) || 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// This should ideally never happen, but the preference value could be invalid, so return something...
|
|
60
|
+
if ( out <= 0 ) {
|
|
61
|
+
out = 10;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return out;
|
|
65
|
+
},
|
|
66
|
+
|
|
52
67
|
pagedRows() {
|
|
53
68
|
if (this.externalPaginationEnabled) {
|
|
54
69
|
return this.rows;
|
|
@@ -61,9 +76,7 @@ export default {
|
|
|
61
76
|
},
|
|
62
77
|
|
|
63
78
|
data() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return { page: 1, perPage };
|
|
79
|
+
return { page: 1 };
|
|
67
80
|
},
|
|
68
81
|
|
|
69
82
|
watch: {
|
|
@@ -89,22 +102,6 @@ export default {
|
|
|
89
102
|
},
|
|
90
103
|
|
|
91
104
|
methods: {
|
|
92
|
-
getPerPage() {
|
|
93
|
-
// perPage can not change while the list is displayed
|
|
94
|
-
let out = this.rowsPerPage || 0;
|
|
95
|
-
|
|
96
|
-
if ( out <= 0 ) {
|
|
97
|
-
out = parseInt(this.$store.getters['prefs/get'](ROWS_PER_PAGE), 10) || 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// This should ideally never happen, but the preference value could be invalid, so return something...
|
|
101
|
-
if ( out <= 0 ) {
|
|
102
|
-
out = 10;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return out;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
105
|
setPage(num) {
|
|
109
106
|
if (this.page === num) {
|
|
110
107
|
return;
|
|
@@ -340,17 +340,6 @@ export default {
|
|
|
340
340
|
if ( !isSelected ) {
|
|
341
341
|
this.update([node], this.selectedRows.slice());
|
|
342
342
|
}
|
|
343
|
-
|
|
344
|
-
let resources = this.selectedRows;
|
|
345
|
-
|
|
346
|
-
if ( this.mangleActionResources ) {
|
|
347
|
-
resources = await this.mangleActionResources(resources);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
this.$store.commit(`action-menu/show`, {
|
|
351
|
-
resources,
|
|
352
|
-
event: e,
|
|
353
|
-
});
|
|
354
343
|
},
|
|
355
344
|
|
|
356
345
|
keySelectRow(row, more = false) {
|
|
@@ -542,7 +531,7 @@ export default {
|
|
|
542
531
|
},
|
|
543
532
|
|
|
544
533
|
clearSelection() {
|
|
545
|
-
this.update([], this.selectedRows);
|
|
534
|
+
this.update([], [...this.selectedRows]);
|
|
546
535
|
},
|
|
547
536
|
|
|
548
537
|
}
|
|
@@ -61,6 +61,11 @@ export default {
|
|
|
61
61
|
tabsOnly: {
|
|
62
62
|
type: Boolean,
|
|
63
63
|
default: false,
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
resource: {
|
|
67
|
+
type: Object,
|
|
68
|
+
default: () => {}
|
|
64
69
|
}
|
|
65
70
|
},
|
|
66
71
|
|
|
@@ -355,6 +360,7 @@ export default {
|
|
|
355
360
|
>
|
|
356
361
|
<component
|
|
357
362
|
:is="tab.component"
|
|
363
|
+
:resource="resource"
|
|
358
364
|
/>
|
|
359
365
|
</Tab>
|
|
360
366
|
</div>
|
package/components/Wizard.vue
CHANGED
|
@@ -426,7 +426,7 @@ export default {
|
|
|
426
426
|
</div>
|
|
427
427
|
<div
|
|
428
428
|
id="wizard-footer-controls"
|
|
429
|
-
class="controls-row
|
|
429
|
+
class="controls-row"
|
|
430
430
|
>
|
|
431
431
|
<slot
|
|
432
432
|
name="cancel"
|
|
@@ -674,7 +674,7 @@ $spacer: 10px;
|
|
|
674
674
|
// We have to account for the absolute position of the .controls-row
|
|
675
675
|
.footer-error {
|
|
676
676
|
margin-top: -40px;
|
|
677
|
-
margin-bottom:
|
|
677
|
+
margin-bottom: calc($footer-height + 10px);
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
.controls-row {
|
|
@@ -145,4 +145,43 @@ describe('component: AsyncButton', () => {
|
|
|
145
145
|
expect(spyDone).toHaveBeenCalledWith('cancelled');
|
|
146
146
|
expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.ACTION);
|
|
147
147
|
});
|
|
148
|
+
|
|
149
|
+
it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', () => {
|
|
150
|
+
const mockExists = jest.fn().mockReturnValue(true);
|
|
151
|
+
const mockT = jest.fn().mockReturnValue('some-string');
|
|
152
|
+
const ariaLabel = 'some-aria-label';
|
|
153
|
+
const ariaLabelledBy = 'some-aria-labelledby';
|
|
154
|
+
|
|
155
|
+
const wrapper: VueWrapper<InstanceType<typeof AsyncButton>> = mount(AsyncButton, {
|
|
156
|
+
props: { icon: 'some-icon', disabled: true },
|
|
157
|
+
attrs: { 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy },
|
|
158
|
+
global: {
|
|
159
|
+
mocks: {
|
|
160
|
+
$store: {
|
|
161
|
+
getters: {
|
|
162
|
+
'i18n/exists': mockExists,
|
|
163
|
+
'i18n/t': mockT
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const item = wrapper.find('button');
|
|
171
|
+
|
|
172
|
+
const itemRole = item.attributes('role');
|
|
173
|
+
const itemAriaLabel = item.attributes('aria-label');
|
|
174
|
+
const itemAriaLabelledBy = item.attributes('aria-labelledby');
|
|
175
|
+
const itemAriaDisabled = item.attributes('aria-disabled');
|
|
176
|
+
|
|
177
|
+
// let's check some attributes passing...
|
|
178
|
+
expect(itemAriaLabel).toBe(ariaLabel);
|
|
179
|
+
expect(itemAriaLabelledBy).toBe(ariaLabelledBy);
|
|
180
|
+
|
|
181
|
+
// rest of the checks
|
|
182
|
+
expect(itemRole).toBe('button');
|
|
183
|
+
expect(itemAriaDisabled).toBe('true');
|
|
184
|
+
expect(item.find('span[data-testid="async-btn-display-label"]').attributes('id')).toBe(wrapper.vm.describedbyId);
|
|
185
|
+
expect(item.find('i').attributes('alt')).toBeDefined();
|
|
186
|
+
});
|
|
148
187
|
});
|
|
@@ -68,6 +68,69 @@ describe('component: CruResource', () => {
|
|
|
68
68
|
expect(node.text()).toContain(errors[1]);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it.each([
|
|
72
|
+
['error', 'error'],
|
|
73
|
+
[{
|
|
74
|
+
code: 'ActionNotAvailable', status: 500, message: 'error'
|
|
75
|
+
}, 'errors.actionNotAvailable'],
|
|
76
|
+
[{
|
|
77
|
+
status: 422, fieldName: 'Name', code: 'NotUnique', message: 'error'
|
|
78
|
+
}, 'errors.failedInApi.withName.withCodeExplanation.withMessageDetail'],
|
|
79
|
+
[{
|
|
80
|
+
status: 422, fieldName: 'Name', code: 'NotUnique'
|
|
81
|
+
}, 'errors.failedInApi.withName.withCodeExplanation.withoutMessageDetail'],
|
|
82
|
+
[{
|
|
83
|
+
status: 422, fieldName: 'Name', code: 'Brr', message: 'error'
|
|
84
|
+
}, 'errors.failedInApi.withName.withMessageDetail'],
|
|
85
|
+
[{
|
|
86
|
+
status: 422, fieldName: 'Name', code: 'Brr'
|
|
87
|
+
}, 'errors.failedInApi.withName.withoutAnythingElse'],
|
|
88
|
+
[{
|
|
89
|
+
status: 422, code: 'NotUnique', message: 'error'
|
|
90
|
+
}, 'errors.failedInApi.withoutName.withMessageDetail.withCodeExplanation'],
|
|
91
|
+
[{ status: 422, message: 'error' }, 'errors.failedInApi.withoutName.withMessageDetail.withoutCodeExplanation'],
|
|
92
|
+
[{ status: 422, code: 'NotUnique' }, 'errors.failedInApi.withoutName.withCode.withCodeExplanation'],
|
|
93
|
+
[{ status: 422, code: 'Brr' }, 'errors.failedInApi.withoutName.withCode.withoutCodeExplanation'],
|
|
94
|
+
[{ status: 422 }, 'errors.failedInApi.withoutAnything'],
|
|
95
|
+
[{ status: 404, message: 'message' }, 'errors.notFound.withoutUrl'],
|
|
96
|
+
[{
|
|
97
|
+
status: 404, message: 'message', opt: { url: 'test' }
|
|
98
|
+
}, 'errors.notFound.withUrl'],
|
|
99
|
+
[{ status: 500, message: 'message' }, 'errors.messageOrDetail'],
|
|
100
|
+
[{
|
|
101
|
+
status: 500, message: 'message', detail: 'detail'
|
|
102
|
+
}, 'errors.messageAndDetail'],
|
|
103
|
+
[{ status: 500, detail: 'detail' }, 'errors.messageOrDetail'],
|
|
104
|
+
])('should display correct error', (err, res) => {
|
|
105
|
+
const wrapper = mount(CruResource, {
|
|
106
|
+
props: {
|
|
107
|
+
canYaml: false,
|
|
108
|
+
mode: _EDIT,
|
|
109
|
+
resource: {},
|
|
110
|
+
errors: [err]
|
|
111
|
+
},
|
|
112
|
+
global: {
|
|
113
|
+
mocks: {
|
|
114
|
+
$store: {
|
|
115
|
+
getters: {
|
|
116
|
+
currentStore: () => 'current_store',
|
|
117
|
+
'current_store/schemaFor': jest.fn(),
|
|
118
|
+
'current_store/all': jest.fn(),
|
|
119
|
+
'i18n/t': (text: string) => text,
|
|
120
|
+
'i18n/exists': jest.fn(),
|
|
121
|
+
},
|
|
122
|
+
dispatch: jest.fn(),
|
|
123
|
+
},
|
|
124
|
+
$route: { query: { AS: _YAML } },
|
|
125
|
+
$router: { applyQuery: jest.fn() },
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const node = wrapper.find('#cru-errors');
|
|
130
|
+
|
|
131
|
+
expect(node.text()).toContain(res);
|
|
132
|
+
});
|
|
133
|
+
|
|
71
134
|
it('should prevent default events on keypress Enter', async() => {
|
|
72
135
|
const event = { preventDefault: jest.fn() };
|
|
73
136
|
const wrapper = mount(CruResource, {
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { createStore, Store } from 'vuex';
|
|
3
|
+
import { nextTick } from 'vue';
|
|
4
|
+
|
|
5
|
+
import ModalManager from '@shell/components/ModalManager.vue';
|
|
6
|
+
|
|
7
|
+
interface ModalManagerMethods {
|
|
8
|
+
registerBackgroundClosing(fn: Function): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MockComponent = {
|
|
13
|
+
template: '<div data-testid="modal-manager-component">Mock Content</div>',
|
|
14
|
+
props: ['someProp', 'resources', 'registerBackgroundClosing']
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('modalManager.vue with Teleport', () => {
|
|
18
|
+
let store: Store<any>;
|
|
19
|
+
let getters: Record<string, () => any>;
|
|
20
|
+
let modalsDiv: HTMLDivElement;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
// Create the teleport target container
|
|
24
|
+
modalsDiv = document.createElement('div');
|
|
25
|
+
modalsDiv.setAttribute('id', 'modals');
|
|
26
|
+
document.body.appendChild(modalsDiv);
|
|
27
|
+
|
|
28
|
+
getters = {
|
|
29
|
+
'modal/isOpen': () => true,
|
|
30
|
+
'modal/component': () => MockComponent,
|
|
31
|
+
'modal/componentProps': () => ({ someProp: 'testValue' }),
|
|
32
|
+
'modal/resources': () => ({ data: 'mockData' }),
|
|
33
|
+
'modal/closeOnClickOutside': () => true,
|
|
34
|
+
'modal/modalWidth': () => '500px'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
store = createStore({
|
|
38
|
+
getters,
|
|
39
|
+
mutations: { 'modal/closeModal': jest.fn() }
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
// Clean up the teleport container after each test
|
|
45
|
+
document.body.removeChild(modalsDiv);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const factory = () => {
|
|
49
|
+
return mount(ModalManager, {
|
|
50
|
+
attachTo: document.body, // attach so Teleport can work properly
|
|
51
|
+
global: {
|
|
52
|
+
plugins: [store],
|
|
53
|
+
stubs: {
|
|
54
|
+
AppModal: {
|
|
55
|
+
name: 'AppModal',
|
|
56
|
+
template: `<div data-testid="app-modal" @close="$emit('close')" :style="{ '--prompt-modal-width': width }"><slot /></div>`,
|
|
57
|
+
props: ['clickToClose', 'width']
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
it('renders the AppModal and dynamic component when modal is open', async() => {
|
|
65
|
+
factory();
|
|
66
|
+
|
|
67
|
+
await nextTick();
|
|
68
|
+
|
|
69
|
+
// Because Teleport moves content out of the normal wrapper,
|
|
70
|
+
// we query the document for the teleported elements.
|
|
71
|
+
const appModal = document.querySelector('[data-testid="app-modal"]');
|
|
72
|
+
const dynamicComponent = document.querySelector('[data-testid="modal-manager-component"]');
|
|
73
|
+
|
|
74
|
+
expect(appModal).toBeTruthy();
|
|
75
|
+
expect(dynamicComponent).toBeTruthy();
|
|
76
|
+
expect(appModal?.getAttribute('style')).toContain('--prompt-modal-width: 500px');
|
|
77
|
+
|
|
78
|
+
// We assume the mock component is rendered correctly if its markup is found.
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('does not render the AppModal when modal is closed', async() => {
|
|
82
|
+
getters['modal/isOpen'] = () => false;
|
|
83
|
+
store = createStore({
|
|
84
|
+
getters,
|
|
85
|
+
mutations: { 'modal/closeModal': jest.fn() }
|
|
86
|
+
});
|
|
87
|
+
factory();
|
|
88
|
+
await nextTick();
|
|
89
|
+
|
|
90
|
+
const appModal = document.querySelector('[data-testid="app-modal"]');
|
|
91
|
+
|
|
92
|
+
expect(appModal).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does not render the AppModal when dynamic component is null', async() => {
|
|
96
|
+
getters['modal/component'] = () => null;
|
|
97
|
+
store = createStore({
|
|
98
|
+
getters,
|
|
99
|
+
mutations: { 'modal/closeModal': jest.fn() }
|
|
100
|
+
});
|
|
101
|
+
factory();
|
|
102
|
+
await nextTick();
|
|
103
|
+
|
|
104
|
+
const appModal = document.querySelector('[data-testid="app-modal"]');
|
|
105
|
+
|
|
106
|
+
expect(appModal).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('calls store commit when close is triggered', async() => {
|
|
110
|
+
const closeModalMutation = jest.fn();
|
|
111
|
+
|
|
112
|
+
getters['modal/isOpen'] = () => true;
|
|
113
|
+
store = createStore({
|
|
114
|
+
getters,
|
|
115
|
+
mutations: { 'modal/closeModal': closeModalMutation }
|
|
116
|
+
});
|
|
117
|
+
const wrapper = factory();
|
|
118
|
+
|
|
119
|
+
await nextTick();
|
|
120
|
+
|
|
121
|
+
const appModalWrapper = wrapper.findComponent({ name: 'AppModal' });
|
|
122
|
+
|
|
123
|
+
appModalWrapper.vm.$emit('close');
|
|
124
|
+
await nextTick();
|
|
125
|
+
|
|
126
|
+
expect(closeModalMutation).toHaveBeenCalledWith({}, undefined);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('calls registered background closing function on close', async() => {
|
|
130
|
+
const closeModalMutation = jest.fn();
|
|
131
|
+
|
|
132
|
+
getters['modal/isOpen'] = () => true;
|
|
133
|
+
store = createStore({
|
|
134
|
+
getters,
|
|
135
|
+
mutations: { 'modal/closeModal': closeModalMutation }
|
|
136
|
+
});
|
|
137
|
+
const wrapper = factory();
|
|
138
|
+
|
|
139
|
+
await nextTick();
|
|
140
|
+
|
|
141
|
+
const backgroundFn = jest.fn();
|
|
142
|
+
|
|
143
|
+
(wrapper.vm as unknown as ModalManagerMethods).registerBackgroundClosing(backgroundFn);
|
|
144
|
+
await nextTick();
|
|
145
|
+
|
|
146
|
+
const appModalWrapper = wrapper.findComponent({ name: 'AppModal' });
|
|
147
|
+
|
|
148
|
+
appModalWrapper.vm.$emit('close');
|
|
149
|
+
await nextTick();
|
|
150
|
+
|
|
151
|
+
expect(backgroundFn).toHaveBeenCalledWith();
|
|
152
|
+
expect(closeModalMutation).toHaveBeenCalledWith({}, undefined);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('does nothing if modal is already closed when close is triggered', async() => {
|
|
156
|
+
const closeModalMutation = jest.fn();
|
|
157
|
+
|
|
158
|
+
getters['modal/isOpen'] = () => false;
|
|
159
|
+
store = createStore({
|
|
160
|
+
getters,
|
|
161
|
+
mutations: { 'modal/closeModal': closeModalMutation }
|
|
162
|
+
});
|
|
163
|
+
const wrapper = factory();
|
|
164
|
+
|
|
165
|
+
await nextTick();
|
|
166
|
+
|
|
167
|
+
const modalManager = wrapper.vm as unknown as ModalManagerMethods;
|
|
168
|
+
const spy = jest.spyOn(modalManager, 'close');
|
|
169
|
+
|
|
170
|
+
modalManager.close();
|
|
171
|
+
await nextTick();
|
|
172
|
+
|
|
173
|
+
expect(spy).toHaveBeenCalledWith();
|
|
174
|
+
expect(closeModalMutation).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import PromptModal from '@shell/components/PromptModal.vue';
|
|
3
|
+
|
|
4
|
+
import GenericPrompt from '@shell/dialog/GenericPrompt.vue';
|
|
5
|
+
import AddClusterMemberDialog from '@shell/dialog/AddClusterMemberDialog.vue';
|
|
6
|
+
import AddCustomBadgeDialog from '@shell/dialog/AddCustomBadgeDialog.vue';
|
|
7
|
+
import AddonConfigConfirmationDialog from '@shell/dialog/AddonConfigConfirmationDialog.vue';
|
|
8
|
+
import AddProjectMemberDialog from '@shell/dialog/AddProjectMemberDialog.vue';
|
|
9
|
+
import DeactivateDriverDialog from '@shell/dialog/DeactivateDriverDialog.vue';
|
|
10
|
+
import DiagnosticTimingsDialog from '@shell/dialog/DiagnosticTimingsDialog.vue';
|
|
11
|
+
import DrainNode from '@shell/dialog/DrainNode.vue';
|
|
12
|
+
import ForceMachineRemoveDialog from '@shell/dialog/ForceMachineRemoveDialog.vue';
|
|
13
|
+
import GitRepoForceUpdateDialog from '@shell/dialog/GitRepoForceUpdateDialog.vue';
|
|
14
|
+
import RollbackWorkloadDialog from '@shell/dialog/RollbackWorkloadDialog.vue';
|
|
15
|
+
import RotateCertificatesDialog from '@shell/dialog/RotateCertificatesDialog.vue';
|
|
16
|
+
import RotateEncryptionKeyDialog from '@shell/dialog/RotateEncryptionKeyDialog.vue';
|
|
17
|
+
import ScaleMachineDownDialog from '@shell/dialog/ScaleMachineDownDialog.vue';
|
|
18
|
+
import ScalePoolDownDialog from '@shell/dialog/ScalePoolDownDialog.vue';
|
|
19
|
+
import SloDialog from '@shell/dialog/SloDialog.vue';
|
|
20
|
+
|
|
21
|
+
import DisableAuthProviderDialog from '@shell/dialog/DisableAuthProviderDialog.vue';
|
|
22
|
+
import WechatDialog from '@shell/dialog/WechatDialog.vue';
|
|
23
|
+
import DeveloperLoadExtensionDialog from '@shell/dialog/DeveloperLoadExtensionDialog.vue';
|
|
24
|
+
import AddExtensionReposDialog from '@shell/dialog/AddExtensionReposDialog.vue';
|
|
25
|
+
import InstallExtensionDialog from '@shell/dialog/InstallExtensionDialog.vue';
|
|
26
|
+
import UninstallExtensionDialog from '@shell/dialog/UninstallExtensionDialog.vue';
|
|
27
|
+
import KnownHostsEditDialog from '@shell/dialog/KnownHostsEditDialog.vue';
|
|
28
|
+
import ImportDialog from '@shell/dialog/ImportDialog.vue';
|
|
29
|
+
import SearchDialog from '@shell/dialog/SearchDialog.vue';
|
|
30
|
+
import ChangePasswordDialog from '@shell/dialog/ChangePasswordDialog.vue';
|
|
31
|
+
import AssignToDialog from '@shell/dialog/AssignToDialog.vue';
|
|
32
|
+
import FeatureFlagListDialog from '@shell/dialog/FeatureFlagListDialog.vue';
|
|
33
|
+
import MoveNamespaceDialog from '@shell/dialog/MoveNamespaceDialog.vue';
|
|
34
|
+
import ExtensionCatalogInstallDialog from '@shell/dialog/ExtensionCatalogInstallDialog.vue';
|
|
35
|
+
import ExtensionCatalogUninstallDialog from '@shell/dialog/ExtensionCatalogUninstallDialog.vue';
|
|
36
|
+
|
|
37
|
+
import { createStore } from 'vuex';
|
|
38
|
+
|
|
39
|
+
jest.mock('@shell/utils/clipboard', () => {
|
|
40
|
+
return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function generateStore(component: any):any {
|
|
44
|
+
return createStore({
|
|
45
|
+
modules: { // promptModal
|
|
46
|
+
'action-menu': {
|
|
47
|
+
namespaced: true,
|
|
48
|
+
state: {
|
|
49
|
+
modalData: {
|
|
50
|
+
closeOnClickOutside: true,
|
|
51
|
+
resources: [{ cluster: { isRke2: true, machines: [] } }], // ScaleMachineDownDialog
|
|
52
|
+
componentProps: {
|
|
53
|
+
drivers: [], // DeactivateDriverDialog
|
|
54
|
+
driverType: 'kontainerDrivers', // DeactivateDriverDialog
|
|
55
|
+
downloadData: () => jest.fn(), // DiagnosticTimingsDialog
|
|
56
|
+
gatherResponseTimes: () => jest.fn(), // DiagnosticTimingsDialog
|
|
57
|
+
kubeNodes: [{}], // DrainNode
|
|
58
|
+
repositories: [], // GitRepoForceUpdateDialog
|
|
59
|
+
workload: { metadata: {}, kind: '' }, // RollbackWorkloadDialog
|
|
60
|
+
catalog: {}
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
getters: {
|
|
68
|
+
'type-map/importDialog': () => () => component, // promptModal
|
|
69
|
+
'i18n/exists': () => jest.fn(), // promptModal
|
|
70
|
+
'i18n/t': () => jest.fn(), // general usage
|
|
71
|
+
'rancher/schemaFor': () => jest.fn(), // general usage
|
|
72
|
+
'prefs/get': () => jest.fn(), // ScalePoolDownDialog
|
|
73
|
+
'type-map/allTypes': () => jest.fn(), // SearchDialog
|
|
74
|
+
'type-map/labelFor': () => jest.fn(), // ScaleMachineDownDialog
|
|
75
|
+
'type-map/getTree': () => jest.fn().mockReturnValue([]), // SearchDialog
|
|
76
|
+
'cluster/all': () => jest.fn(), // SearchDialog
|
|
77
|
+
currentProduct: () => { // SearchDialog
|
|
78
|
+
return { inStore: 'cluster' };
|
|
79
|
+
},
|
|
80
|
+
currentCluster: () => { // general usage
|
|
81
|
+
'local';
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe('component: PromptModal', () => {
|
|
88
|
+
it.each([
|
|
89
|
+
// current prompt modals at time of coding
|
|
90
|
+
['GenericPrompt', GenericPrompt],
|
|
91
|
+
['AddClusterMemberDialog', AddClusterMemberDialog],
|
|
92
|
+
['AddonConfigConfirmationDialog', AddonConfigConfirmationDialog],
|
|
93
|
+
['AddProjectMemberDialog', AddProjectMemberDialog],
|
|
94
|
+
['DeactivateDriverDialog', DeactivateDriverDialog],
|
|
95
|
+
['DiagnosticTimingsDialog', DiagnosticTimingsDialog],
|
|
96
|
+
['DrainNode', DrainNode],
|
|
97
|
+
['ForceMachineRemoveDialog', ForceMachineRemoveDialog],
|
|
98
|
+
['GitRepoForceUpdateDialog', GitRepoForceUpdateDialog],
|
|
99
|
+
['RollbackWorkloadDialog', RollbackWorkloadDialog],
|
|
100
|
+
['RotateCertificatesDialog', RotateCertificatesDialog],
|
|
101
|
+
['RotateEncryptionKeyDialog', RotateEncryptionKeyDialog],
|
|
102
|
+
['SloDialog', SloDialog],
|
|
103
|
+
['AddCustomBadgeDialog', AddCustomBadgeDialog],
|
|
104
|
+
['ScaleMachineDownDialog', ScaleMachineDownDialog],
|
|
105
|
+
['ScalePoolDownDialog', ScalePoolDownDialog],
|
|
106
|
+
// new modals created/moved
|
|
107
|
+
['DisableAuthProviderDialog', DisableAuthProviderDialog],
|
|
108
|
+
['WechatDialog', WechatDialog],
|
|
109
|
+
['DeveloperLoadExtensionDialog', DeveloperLoadExtensionDialog],
|
|
110
|
+
['AddExtensionReposDialog', AddExtensionReposDialog],
|
|
111
|
+
['InstallExtensionDialog', InstallExtensionDialog],
|
|
112
|
+
['UninstallExtensionDialog', UninstallExtensionDialog],
|
|
113
|
+
['KnownHostsEditDialog', KnownHostsEditDialog],
|
|
114
|
+
['ImportDialog', ImportDialog],
|
|
115
|
+
['SearchDialog', SearchDialog],
|
|
116
|
+
['ChangePasswordDialog', ChangePasswordDialog],
|
|
117
|
+
['AssignToDialog', AssignToDialog],
|
|
118
|
+
['FeatureFlagListDialog', FeatureFlagListDialog],
|
|
119
|
+
['MoveNamespaceDialog', MoveNamespaceDialog],
|
|
120
|
+
['ExtensionCatalogInstallDialog', ExtensionCatalogInstallDialog],
|
|
121
|
+
['ExtensionCatalogUninstallDialog', ExtensionCatalogUninstallDialog],
|
|
122
|
+
])('prompt Modal should render modal %p', (modalName, component) => {
|
|
123
|
+
// mock structuredClone
|
|
124
|
+
window.structuredClone = (arg) => JSON.parse(JSON.stringify(arg));
|
|
125
|
+
|
|
126
|
+
document.body.innerHTML = '<div id="modals"></div>';
|
|
127
|
+
const wrapper = mount(PromptModal,
|
|
128
|
+
{
|
|
129
|
+
attachTo: document.body,
|
|
130
|
+
data() {
|
|
131
|
+
return { opened: true }; // this controls modal content visibility
|
|
132
|
+
},
|
|
133
|
+
global: {
|
|
134
|
+
mocks: {
|
|
135
|
+
$store: generateStore(component),
|
|
136
|
+
$fetchState: {}
|
|
137
|
+
},
|
|
138
|
+
stubs: { transition: false }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(wrapper.vm.opened).toBe(true);
|
|
144
|
+
expect(wrapper.findComponent(component as any).exists()).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|