@rancher/shell 3.0.8-rc.1 → 3.0.8-rc.12
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/brand/suse/banner.svg +1 -0
- package/assets/brand/suse/dark/banner.svg +1 -0
- package/assets/brand/suse/dark/login-landscape.svg +1 -0
- package/assets/brand/suse/dark/rancher-logo.svg +1 -1
- package/assets/brand/suse/favicon.png +0 -0
- package/assets/brand/suse/login-landscape.svg +1 -0
- package/assets/brand/suse/metadata.json +11 -1
- package/assets/brand/suse/rancher-logo.svg +1 -1
- package/assets/fonts/suse/suse-v2-latin-300.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-300.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-600.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-600.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-700.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-700.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-800.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-800.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-regular.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-regular.woff2 +0 -0
- package/assets/images/content/README.md +5 -0
- package/assets/images/content/cloud-native.svg +84 -0
- package/assets/images/content/dark/cloud-native.svg +21 -0
- package/assets/images/content/dark/shield.svg +59 -0
- package/assets/images/content/dark/suse.svg +10 -0
- package/assets/images/content/shield.svg +59 -0
- package/assets/images/content/suse.svg +10 -0
- package/assets/styles/base/_typography.scss +1 -0
- package/assets/styles/fonts/_fontstack.scss +53 -1
- package/assets/styles/global/_cards.scss +0 -3
- package/assets/styles/global/_layout.scss +21 -35
- package/assets/styles/themes/_dark.scss +1 -1
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +11 -3
- package/assets/styles/themes/_suse.scss +116 -24
- package/assets/translations/en-us.yaml +94 -10
- package/components/AutoscalerCard.vue +113 -0
- package/components/AutoscalerTab.vue +94 -0
- package/components/BackLink.vue +8 -0
- package/components/BannerGraphic.vue +36 -21
- package/components/BrandImage.vue +17 -6
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/Cron/CronExpressionEditor.vue +1 -1
- package/components/Cron/CronExpressionEditorModal.vue +1 -1
- package/components/Drawer/Chrome.vue +2 -6
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
- package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
- package/components/Drawer/ResourceDetailDrawer/index.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/types.ts +17 -0
- package/components/Drawer/types.ts +3 -0
- package/components/DynamicContent/DynamicContentBanner.vue +102 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +42 -0
- package/components/DynamicContent/DynamicContentIcon.vue +132 -0
- package/components/DynamicContent/DynamicContentPanel.vue +112 -0
- package/components/DynamicContent/content.ts +78 -0
- package/components/EmberPage.vue +1 -1
- package/components/IconOrSvg.vue +2 -2
- package/components/PaginatedResourceTable.vue +2 -6
- package/components/PopoverCard.vue +192 -0
- package/components/Questions/__tests__/index.test.ts +159 -0
- package/components/Resource/Detail/CopyToClipboard.vue +4 -1
- package/components/Resource/Detail/FetchLoader/composables.ts +18 -4
- package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +1 -1
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +4 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +1 -1
- package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
- package/components/Resource/Detail/Metadata/composables.ts +9 -9
- package/components/Resource/Detail/Metadata/index.vue +3 -3
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -19
- package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +0 -29
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +132 -150
- package/components/Resource/Detail/ResourcePopover/index.vue +54 -159
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -6
- package/components/Resource/Detail/composables.ts +12 -0
- package/components/ResourceDetail/Masthead/latest.vue +29 -0
- package/components/ResourceDetail/index.vue +4 -1
- package/components/ResourceList/Masthead.vue +1 -1
- package/components/ResourceTable.vue +1 -1
- package/components/SortableTable/index.vue +2 -1
- package/components/Tabbed/__tests__/index.test.ts +86 -0
- package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
- package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
- package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
- package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
- package/components/__tests__/AutoscalerCard.test.ts +154 -0
- package/components/__tests__/AutoscalerTab.test.ts +125 -0
- package/components/__tests__/PopoverCard.test.ts +204 -0
- package/components/auth/SelectPrincipal.vue +24 -6
- package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
- package/components/auth/login/ldap.vue +3 -3
- package/components/form/NodeScheduling.vue +2 -2
- package/components/formatter/Autoscaler.vue +97 -0
- package/components/formatter/InternalExternalIP.vue +198 -24
- package/components/formatter/__tests__/Autoscaler.test.ts +156 -0
- package/components/formatter/__tests__/InternalExternalIP.test.ts +133 -0
- package/components/google/util/__tests__/formatter.test.ts +47 -0
- package/components/google/util/formatter.ts +5 -2
- package/components/nav/Group.vue +21 -5
- package/components/nav/Header.vue +37 -17
- package/components/nav/NamespaceFilter.vue +13 -1
- package/components/nav/NotificationCenter/index.vue +2 -1
- package/components/nav/TopLevelMenu.helper.ts +16 -6
- package/components/nav/TopLevelMenu.vue +4 -2
- package/components/nav/Type.vue +8 -3
- package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
- package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
- package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
- package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
- package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
- package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
- package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
- package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
- package/components/nav/WindowManager/constants.ts +23 -0
- package/components/nav/WindowManager/index.vue +61 -575
- package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
- package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
- package/components/nav/__tests__/Type.test.ts +59 -0
- package/components/templates/default.vue +4 -40
- package/components/templates/home.vue +31 -5
- package/components/templates/plain.vue +30 -4
- package/components/templates/standalone.vue +1 -1
- package/composables/useI18n.ts +10 -1
- package/composables/useInterval.ts +15 -0
- package/config/__test__/uiplugins.test.ts +309 -0
- package/config/labels-annotations.js +9 -1
- package/config/product/explorer.js +3 -1
- package/config/product/manager.js +20 -9
- package/config/router/navigation-guards/clusters.js +3 -3
- package/config/router/navigation-guards/products.js +1 -1
- package/config/router/routes.js +10 -2
- package/config/settings.ts +2 -1
- package/config/store.js +4 -2
- package/config/table-headers.js +8 -0
- package/config/types.js +9 -0
- package/config/uiplugins.js +46 -2
- package/config/version.js +1 -1
- package/core/__test__/extension-manager-impl.test.js +236 -0
- package/core/extension-manager-impl.js +21 -4
- package/core/plugin-helpers.ts +4 -2
- package/core/plugins-loader.js +2 -2
- package/core/types-provisioning.ts +8 -1
- package/detail/pod.vue +1 -0
- package/detail/provisioning.cattle.io.cluster.vue +19 -7
- package/dialog/DeveloperLoadExtensionDialog.vue +13 -4
- package/dialog/RollbackWorkloadDialog.vue +2 -5
- package/dialog/SearchDialog.vue +1 -0
- package/directives/ui-context.ts +103 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +2 -2
- package/edit/auth/__tests__/oidc.test.ts +26 -0
- package/edit/auth/github.vue +5 -0
- package/edit/auth/oidc.vue +5 -1
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
- package/edit/cloudcredential.vue +1 -1
- package/edit/configmap.vue +1 -0
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
- package/edit/fleet.cattle.io.gitrepo.vue +0 -10
- package/edit/fleet.cattle.io.helmop.vue +6 -6
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
- package/edit/logging-flow/index.vue +1 -0
- package/edit/logging.banzaicloud.io.output/index.vue +1 -0
- package/edit/management.cattle.io.fleetworkspace.vue +1 -1
- package/edit/management.cattle.io.project.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
- package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
- package/edit/monitoring.coreos.com.route.vue +1 -1
- package/edit/namespace.vue +1 -0
- package/edit/networking.istio.io.destinationrule/index.vue +1 -0
- package/edit/networking.k8s.io.ingress/index.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
- package/edit/node.vue +1 -0
- package/edit/persistentvolume/index.vue +27 -22
- package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
- package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
- package/edit/persistentvolume/plugins/azureFile.vue +15 -14
- package/edit/persistentvolume/plugins/cephfs.vue +15 -14
- package/edit/persistentvolume/plugins/cinder.vue +15 -14
- package/edit/persistentvolume/plugins/csi.vue +18 -16
- package/edit/persistentvolume/plugins/fc.vue +13 -14
- package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
- package/edit/persistentvolume/plugins/flocker.vue +1 -3
- package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
- package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
- package/edit/persistentvolume/plugins/hostPath.vue +40 -39
- package/edit/persistentvolume/plugins/iscsi.vue +13 -14
- package/edit/persistentvolume/plugins/local.vue +1 -3
- package/edit/persistentvolume/plugins/longhorn.vue +23 -22
- package/edit/persistentvolume/plugins/nfs.vue +15 -14
- package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
- package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
- package/edit/persistentvolume/plugins/quobyte.vue +15 -14
- package/edit/persistentvolume/plugins/rbd.vue +15 -14
- package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
- package/edit/persistentvolume/plugins/storageos.vue +15 -14
- package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +32 -5
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +35 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +155 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
- package/edit/provisioning.cattle.io.cluster/index.vue +28 -18
- package/edit/provisioning.cattle.io.cluster/rke2.vue +50 -16
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +107 -5
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +92 -4
- package/edit/secret/index.vue +1 -1
- package/edit/service.vue +9 -4
- package/edit/serviceaccount.vue +1 -0
- package/edit/storage.k8s.io.storageclass/index.vue +1 -0
- package/edit/workload/index.vue +2 -1
- package/edit/workload/mixins/workload.js +1 -1
- package/initialize/App.vue +4 -4
- package/initialize/install-directives.js +2 -0
- package/initialize/install-plugins.js +19 -2
- package/list/provisioning.cattle.io.cluster.vue +15 -2
- package/machine-config/amazonec2.vue +42 -135
- package/machine-config/components/EC2Networking.vue +490 -0
- package/machine-config/components/__tests__/EC2Networking.test.ts +148 -0
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +294 -0
- package/machine-config/digitalocean.vue +11 -0
- package/machine-config/google.vue +1 -1
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/__tests__/chart.test.ts +21 -0
- package/mixins/brand.js +1 -7
- package/mixins/chart.js +7 -1
- package/mixins/create-edit-view/index.js +5 -0
- package/models/__tests__/chart.test.ts +33 -4
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
- package/models/chart.js +25 -13
- package/models/cluster/node.js +13 -6
- package/models/cluster.x-k8s.io.machine.js +10 -20
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +249 -33
- package/package.json +8 -7
- package/pages/auth/login.vue +41 -5
- package/pages/auth/setup.vue +1 -1
- package/pages/auth/verify.vue +3 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
- package/pages/c/_cluster/apps/charts/chart.vue +33 -15
- package/pages/c/_cluster/apps/charts/index.vue +11 -13
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +8 -6
- package/pages/c/_cluster/manager/hostedprovider/index.vue +220 -0
- package/pages/c/_cluster/settings/brand.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
- package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
- package/pages/c/_cluster/uiplugins/index.vue +126 -184
- package/pages/home.vue +14 -4
- package/pkg/auto-import.js +3 -3
- package/pkg/dynamic-importer.lib.js +5 -1
- package/pkg/import.js +1 -1
- package/plugins/dashboard-client-init.js +3 -0
- package/plugins/dashboard-store/getters.js +19 -2
- package/plugins/dashboard-store/model-loader.js +1 -1
- package/plugins/dashboard-store/resource-class.js +10 -6
- package/plugins/dynamic-content.js +13 -0
- package/plugins/i18n.js +8 -0
- package/plugins/plugin.js +2 -2
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
- package/plugins/steve/steve-class.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +39 -20
- package/plugins/steve/subscribe.js +17 -9
- package/plugins/subscribe-events.ts +4 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -34
- package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
- package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
- package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
- package/rancher-components/Pill/types.ts +0 -1
- package/rancher-components/RcDropdown/RcDropdownItem.vue +1 -0
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
- package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
- package/rancher-components/RcIcon/RcIcon.vue +46 -0
- package/rancher-components/RcIcon/index.ts +1 -0
- package/rancher-components/RcIcon/types.ts +160 -0
- package/rancher-components/utils/status.test.ts +67 -0
- package/rancher-components/utils/status.ts +77 -0
- package/scripts/typegen.sh +1 -0
- package/store/__tests__/catalog.test.ts +1 -1
- package/store/action-menu.js +8 -0
- package/store/auth.js +4 -4
- package/store/catalog.js +6 -0
- package/store/features.js +1 -0
- package/store/i18n.js +3 -3
- package/store/index.js +40 -19
- package/store/notifications.ts +51 -4
- package/store/plugins.js +7 -3
- package/store/prefs.js +6 -6
- package/store/type-map.js +7 -7
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/notifications/index.ts +27 -3
- package/types/shell/index.d.ts +80 -4
- package/types/store/__tests__/pagination.types.spec.ts +137 -0
- package/types/store/pagination.types.ts +157 -9
- package/types/store/subscribe-events.types.ts +8 -1
- package/types/store/subscribe.types.ts +1 -0
- package/types/window-manager.ts +24 -0
- package/utils/__tests__/object.test.ts +19 -0
- package/utils/__tests__/provider.test.ts +98 -0
- package/utils/__tests__/selector-typed.test.ts +263 -0
- package/utils/__tests__/version.test.ts +19 -1
- package/utils/autoscaler-utils.ts +7 -0
- package/utils/back-off.ts +3 -3
- package/utils/brand.ts +29 -0
- package/utils/chart.js +18 -0
- package/utils/color.js +1 -1
- package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
- package/utils/dynamic-content/__tests__/info.test.ts +21 -9
- package/utils/dynamic-content/announcement.ts +142 -0
- package/utils/dynamic-content/example.json +40 -0
- package/utils/dynamic-content/index.ts +6 -2
- package/utils/dynamic-content/info.ts +44 -2
- package/utils/dynamic-content/new-release.ts +1 -1
- package/utils/dynamic-content/notification-handler.ts +48 -0
- package/utils/dynamic-content/types.d.ts +53 -1
- package/utils/dynamic-importer.js +2 -2
- package/utils/favicon.js +4 -4
- package/utils/object.js +20 -2
- package/utils/pagination-utils.ts +2 -2
- package/utils/pagination-wrapper.ts +13 -9
- package/utils/provider.ts +14 -0
- package/utils/scroll.js +7 -0
- package/utils/selector-typed.ts +6 -2
- package/utils/settings.ts +15 -0
- package/utils/unit-tests/pagination-utils.spec.ts +8 -8
- package/utils/validators/machine-pool.ts +13 -3
- package/utils/version.js +15 -0
- package/vue.config.js +3 -3
- package/assets/images/icons/document.svg +0 -3
- package/plugins/nuxt-client-init.js +0 -3
- package/store/wm.js +0 -95
- /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
- /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
- /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import AutoscalerCard from '@shell/components/AutoscalerCard.vue';
|
|
4
|
+
import { ref } from 'vue';
|
|
5
|
+
import { createStore } from 'vuex';
|
|
6
|
+
|
|
7
|
+
const mockUseFetch = jest.fn();
|
|
8
|
+
|
|
9
|
+
jest.mock('@shell/components/Resource/Detail/FetchLoader/composables', () => ({ useFetch: (...args: any[]) => mockUseFetch(...args) }));
|
|
10
|
+
|
|
11
|
+
const mockUseInterval = jest.fn();
|
|
12
|
+
|
|
13
|
+
jest.mock('@shell/composables/useInterval', () => ({ useInterval: (...args: any[]) => mockUseInterval(...args) }));
|
|
14
|
+
|
|
15
|
+
describe('component: AutoscalerCard.vue', () => {
|
|
16
|
+
const mockLoadDetails = jest.fn();
|
|
17
|
+
const mockRefresh = jest.fn();
|
|
18
|
+
|
|
19
|
+
const createWrapper = (props: any, useFetchState: any) => {
|
|
20
|
+
// Reset and configure the useFetch mock for each test
|
|
21
|
+
mockUseFetch.mockImplementation(() => {
|
|
22
|
+
return ref({
|
|
23
|
+
loading: false,
|
|
24
|
+
refreshing: false,
|
|
25
|
+
data: null,
|
|
26
|
+
refresh: mockRefresh,
|
|
27
|
+
...useFetchState,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return mount(AutoscalerCard, {
|
|
32
|
+
props: {
|
|
33
|
+
value: { loadAutoscalerDetails: mockLoadDetails },
|
|
34
|
+
...props,
|
|
35
|
+
},
|
|
36
|
+
global: { plugins: [createStore({})] },
|
|
37
|
+
// Shallow mount to avoid rendering child components like the dynamic ones
|
|
38
|
+
shallow: true,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
jest.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should call useFetch with the correct loader function', () => {
|
|
47
|
+
createWrapper({}, {});
|
|
48
|
+
// The first argument to useFetch is the loader function
|
|
49
|
+
expect(mockUseFetch).toHaveBeenCalledWith(expect.any(Function));
|
|
50
|
+
// We can invoke the loader to ensure it calls the prop method
|
|
51
|
+
const loader = mockUseFetch.mock.calls[0][0];
|
|
52
|
+
|
|
53
|
+
loader();
|
|
54
|
+
expect(mockLoadDetails).toHaveBeenCalledWith();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should setup a polling interval to refresh data', () => {
|
|
58
|
+
createWrapper({}, {});
|
|
59
|
+
expect(mockUseInterval).toHaveBeenCalledWith(expect.any(Function), 10000);
|
|
60
|
+
|
|
61
|
+
// Invoke the interval function to ensure it calls refresh
|
|
62
|
+
const intervalFn = mockUseInterval.mock.calls[0][0];
|
|
63
|
+
|
|
64
|
+
intervalFn();
|
|
65
|
+
expect(mockRefresh).toHaveBeenCalledWith();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('uI States', () => {
|
|
69
|
+
it('should display a loading spinner on initial load', () => {
|
|
70
|
+
const wrapper = createWrapper({}, { loading: true, refreshing: false });
|
|
71
|
+
|
|
72
|
+
expect(wrapper.find('.loading').exists()).toBe(true);
|
|
73
|
+
expect(wrapper.find('.icon-spinner').exists()).toBe(true);
|
|
74
|
+
expect(wrapper.find('.details').exists()).toBe(false);
|
|
75
|
+
expect(wrapper.find('.text-warning').exists()).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should NOT display the main loading spinner during a background refresh', () => {
|
|
79
|
+
const wrapper = createWrapper({}, {
|
|
80
|
+
loading: true, refreshing: true, data: []
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(wrapper.find('.loading').exists()).toBe(false);
|
|
84
|
+
// Data should still be visible
|
|
85
|
+
expect(wrapper.find('.details').exists()).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should display an error message if loading fails', () => {
|
|
89
|
+
const wrapper = createWrapper({}, { loading: false, data: null });
|
|
90
|
+
|
|
91
|
+
expect(wrapper.find('.text-warning').exists()).toBe(true);
|
|
92
|
+
expect(wrapper.find('.text-warning').text()).toBe('autoscaler.card.loadingError');
|
|
93
|
+
expect(wrapper.find('.loading').exists()).toBe(false);
|
|
94
|
+
expect(wrapper.find('.details').exists()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should display details when data is loaded', () => {
|
|
98
|
+
const mockData = [
|
|
99
|
+
{ label: 'Status', value: 'Active' },
|
|
100
|
+
{ label: 'Nodes', value: '3' },
|
|
101
|
+
];
|
|
102
|
+
const wrapper = createWrapper({}, { loading: false, data: mockData });
|
|
103
|
+
|
|
104
|
+
expect(wrapper.find('.details').exists()).toBe(true);
|
|
105
|
+
const details = wrapper.findAll('.detail');
|
|
106
|
+
|
|
107
|
+
expect(details).toHaveLength(2);
|
|
108
|
+
expect(details[0].text()).toContain('Status');
|
|
109
|
+
expect(details[0].text()).toContain('Active');
|
|
110
|
+
expect(details[1].text()).toContain('Nodes');
|
|
111
|
+
expect(details[1].text()).toContain('3');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('detail Rendering', () => {
|
|
116
|
+
it('should render a string value', () => {
|
|
117
|
+
const mockData = [{ label: 'My Label', value: 'My Value' }];
|
|
118
|
+
const wrapper = createWrapper({}, { data: mockData });
|
|
119
|
+
const valueDiv = wrapper.find('.value');
|
|
120
|
+
|
|
121
|
+
expect(valueDiv.find('span').exists()).toBe(true);
|
|
122
|
+
expect(valueDiv.text()).toBe('My Value');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should render a dynamic component value', () => {
|
|
126
|
+
const DynamicComponent = {
|
|
127
|
+
name: 'DynamicComponent',
|
|
128
|
+
props: ['text'],
|
|
129
|
+
template: '<div>{{ text }}</div>'
|
|
130
|
+
};
|
|
131
|
+
const mockData = [{
|
|
132
|
+
label: 'My Component',
|
|
133
|
+
value: { component: DynamicComponent, props: { text: 'Dynamic Text' } }
|
|
134
|
+
}];
|
|
135
|
+
const wrapper = createWrapper({}, { data: mockData });
|
|
136
|
+
const valueDiv = wrapper.find('.value');
|
|
137
|
+
const renderedComponent = valueDiv.findComponent(DynamicComponent);
|
|
138
|
+
|
|
139
|
+
expect(renderedComponent.exists()).toBe(true);
|
|
140
|
+
expect(renderedComponent.props('text')).toBe('Dynamic Text');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should render a heading for details without a value', () => {
|
|
144
|
+
const mockData = [{ label: 'Section Header' }];
|
|
145
|
+
const wrapper = createWrapper({}, { data: mockData });
|
|
146
|
+
|
|
147
|
+
expect(wrapper.find('h5').exists()).toBe(true);
|
|
148
|
+
expect(wrapper.find('h5').text()).toBe('Section Header');
|
|
149
|
+
// Label and value should not be rendered
|
|
150
|
+
expect(wrapper.find('label').exists()).toBe(false);
|
|
151
|
+
expect(wrapper.find('.value').exists()).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import AutoscalerTab from '@shell/components/AutoscalerTab.vue';
|
|
4
|
+
import { ref } from 'vue';
|
|
5
|
+
import { createStore } from 'vuex';
|
|
6
|
+
|
|
7
|
+
const mockUseFetch = jest.fn();
|
|
8
|
+
|
|
9
|
+
jest.mock('@shell/components/Resource/Detail/FetchLoader/composables', () => ({ useFetch: (...args: any[]) => mockUseFetch(...args) }));
|
|
10
|
+
|
|
11
|
+
const mockUseInterval = jest.fn();
|
|
12
|
+
|
|
13
|
+
jest.mock('@shell/composables/useInterval', () => ({ useInterval: (...args: any[]) => mockUseInterval(...args) }));
|
|
14
|
+
|
|
15
|
+
describe('component: AutoscalerTab.vue', () => {
|
|
16
|
+
const mockLoadEvents = jest.fn();
|
|
17
|
+
const mockRefresh = jest.fn();
|
|
18
|
+
const mockChangeSort = jest.fn();
|
|
19
|
+
|
|
20
|
+
const SortableTableStub = {
|
|
21
|
+
name: 'SortableTable',
|
|
22
|
+
template: '<div></div>',
|
|
23
|
+
setup() {
|
|
24
|
+
return { changeSort: mockChangeSort };
|
|
25
|
+
},
|
|
26
|
+
props: ['namespaced', 'rowActions', 'defaultSortBy', 'headers', 'rows']
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const createWrapper = (props: any, useFetchState: any) => {
|
|
30
|
+
mockUseFetch.mockImplementation(() => {
|
|
31
|
+
return ref({
|
|
32
|
+
data: null,
|
|
33
|
+
refresh: mockRefresh,
|
|
34
|
+
...useFetchState,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return mount(AutoscalerTab, {
|
|
39
|
+
props: {
|
|
40
|
+
value: { loadAutoscalerEvents: mockLoadEvents },
|
|
41
|
+
...props,
|
|
42
|
+
},
|
|
43
|
+
global: {
|
|
44
|
+
plugins: [createStore({})],
|
|
45
|
+
stubs: {
|
|
46
|
+
Tab: {
|
|
47
|
+
name: 'Tab',
|
|
48
|
+
template: '<div><slot/></div>',
|
|
49
|
+
props: ['label']
|
|
50
|
+
},
|
|
51
|
+
SortableTable: SortableTableStub,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('initialization and Data Fetching', () => {
|
|
62
|
+
it('should call useFetch with the correct loader function', () => {
|
|
63
|
+
createWrapper({}, {});
|
|
64
|
+
expect(mockUseFetch).toHaveBeenCalledWith(expect.any(Function));
|
|
65
|
+
|
|
66
|
+
const loader = mockUseFetch.mock.calls[0][0];
|
|
67
|
+
|
|
68
|
+
loader();
|
|
69
|
+
expect(mockLoadEvents).toHaveBeenCalledWith();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should setup a polling interval to refresh data', () => {
|
|
73
|
+
createWrapper({}, {});
|
|
74
|
+
expect(mockUseInterval).toHaveBeenCalledWith(expect.any(Function), 20000);
|
|
75
|
+
|
|
76
|
+
const intervalFn = mockUseInterval.mock.calls[0][0];
|
|
77
|
+
|
|
78
|
+
intervalFn();
|
|
79
|
+
expect(mockRefresh).toHaveBeenCalledWith();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should call changeSort on the table on mounted', () => {
|
|
83
|
+
createWrapper({}, {});
|
|
84
|
+
expect(mockChangeSort).toHaveBeenCalledWith('date', true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('sortableTable props', () => {
|
|
89
|
+
it('should pass static props to the table', () => {
|
|
90
|
+
const wrapper = createWrapper({}, {});
|
|
91
|
+
const table = wrapper.findComponent(SortableTableStub);
|
|
92
|
+
|
|
93
|
+
expect(table.props('namespaced')).toBe(false);
|
|
94
|
+
expect(table.props('rowActions')).toBe(false);
|
|
95
|
+
expect(table.props('defaultSortBy')).toBe('date');
|
|
96
|
+
expect(table.props('headers')).toHaveLength(4);
|
|
97
|
+
expect(table.props('headers')[0].name).toBe('type');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should pass an empty array to rows when data is null', () => {
|
|
101
|
+
const wrapper = createWrapper({}, { data: null });
|
|
102
|
+
const table = wrapper.findComponent(SortableTableStub);
|
|
103
|
+
|
|
104
|
+
expect(table.props('rows')).toStrictEqual([]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should pass fetched data to the rows prop', () => {
|
|
108
|
+
const mockEvents = [
|
|
109
|
+
{ id: 1, message: 'Event 1' },
|
|
110
|
+
{ id: 2, message: 'Event 2' },
|
|
111
|
+
];
|
|
112
|
+
const wrapper = createWrapper({}, { data: mockEvents });
|
|
113
|
+
const table = wrapper.findComponent(SortableTableStub);
|
|
114
|
+
|
|
115
|
+
expect(table.props('rows')).toStrictEqual(mockEvents);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should pass correct label to Tab component', () => {
|
|
120
|
+
const wrapper = createWrapper({}, {});
|
|
121
|
+
const tab = wrapper.findComponent({ name: 'Tab' });
|
|
122
|
+
|
|
123
|
+
expect(tab.props('label')).toBe('autoscaler.tab.title');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import PopoverCard from '@shell/components/PopoverCard.vue';
|
|
4
|
+
|
|
5
|
+
const mockFocusTrap = jest.fn();
|
|
6
|
+
|
|
7
|
+
jest.mock('@shell/composables/focusTrap', () => ({
|
|
8
|
+
...jest.requireActual('@shell/composables/focusTrap'), // Keep DEFAULT_FOCUS_TRAP_OPTS
|
|
9
|
+
useWatcherBasedSetupFocusTrapWithDestroyIncluded: (...args: any[]) => mockFocusTrap(...args),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const VDropdownStub = {
|
|
13
|
+
props: ['shown'],
|
|
14
|
+
template: `
|
|
15
|
+
<div>
|
|
16
|
+
<slot />
|
|
17
|
+
<div v-if="shown">
|
|
18
|
+
<slot name="popper" />
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('component: PopoverCard.vue', () => {
|
|
25
|
+
const createWrapper = (props = {}, slots = {}) => {
|
|
26
|
+
return mount(PopoverCard, {
|
|
27
|
+
props: {
|
|
28
|
+
cardTitle: 'Test Title',
|
|
29
|
+
...props,
|
|
30
|
+
},
|
|
31
|
+
slots,
|
|
32
|
+
global: {
|
|
33
|
+
stubs: {
|
|
34
|
+
VDropdown: VDropdownStub,
|
|
35
|
+
Card: {
|
|
36
|
+
template: `
|
|
37
|
+
<div>
|
|
38
|
+
<slot name="heading-action" />
|
|
39
|
+
<slot />
|
|
40
|
+
</div>
|
|
41
|
+
`,
|
|
42
|
+
},
|
|
43
|
+
RcButton: { template: '<button><slot /></button>' },
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
mockFocusTrap.mockClear();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('props', () => {
|
|
54
|
+
it('should use default props', () => {
|
|
55
|
+
const wrapper = createWrapper();
|
|
56
|
+
const button = wrapper.find('button');
|
|
57
|
+
|
|
58
|
+
expect(button.attributes('aria-label')).toBe('Show more');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should accept and render custom props', () => {
|
|
62
|
+
const props = {
|
|
63
|
+
cardTitle: 'My Custom Title',
|
|
64
|
+
showPopoverAriaLabel: 'Click for details'
|
|
65
|
+
};
|
|
66
|
+
const wrapper = createWrapper(props);
|
|
67
|
+
const button = wrapper.find('button');
|
|
68
|
+
|
|
69
|
+
expect(button.attributes('aria-label')).toBe(props.showPopoverAriaLabel);
|
|
70
|
+
// Note: cardTitle is passed to the Card component inside the popper,
|
|
71
|
+
// which is only rendered when the popover is shown.
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('popover Visibility', () => {
|
|
76
|
+
it('should not be visible initially', () => {
|
|
77
|
+
const wrapper = createWrapper();
|
|
78
|
+
|
|
79
|
+
expect(wrapper.find('[id="popover-card"]').exists()).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should show on mouseenter and hide on mouseleave', async() => {
|
|
83
|
+
const wrapper = createWrapper();
|
|
84
|
+
const target = wrapper.find('.popover-card-target');
|
|
85
|
+
|
|
86
|
+
await target.trigger('mouseenter');
|
|
87
|
+
expect(wrapper.vm.showPopover).toBe(true);
|
|
88
|
+
|
|
89
|
+
const root = wrapper.find('.popover-card-base');
|
|
90
|
+
|
|
91
|
+
await root.trigger('mouseleave');
|
|
92
|
+
expect(wrapper.vm.showPopover).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should show on button click', async() => {
|
|
96
|
+
const wrapper = createWrapper();
|
|
97
|
+
const button = wrapper.find('button');
|
|
98
|
+
|
|
99
|
+
await button.trigger('click');
|
|
100
|
+
expect(wrapper.vm.showPopover).toBe(true);
|
|
101
|
+
expect(wrapper.vm.focusOpen).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should hide on Escape keydown', async() => {
|
|
105
|
+
const wrapper = createWrapper();
|
|
106
|
+
|
|
107
|
+
// Open it first
|
|
108
|
+
await wrapper.find('button').trigger('click');
|
|
109
|
+
expect(wrapper.vm.showPopover).toBe(true);
|
|
110
|
+
expect(wrapper.vm.focusOpen).toBe(true);
|
|
111
|
+
|
|
112
|
+
// Trigger escape
|
|
113
|
+
const root = wrapper.find('.popover-card-base');
|
|
114
|
+
|
|
115
|
+
await root.trigger('keydown.escape');
|
|
116
|
+
|
|
117
|
+
expect(wrapper.vm.showPopover).toBe(false);
|
|
118
|
+
expect(wrapper.vm.focusOpen).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('focus Trap', () => {
|
|
123
|
+
it('should NOT setup focus trap on mouseenter', async() => {
|
|
124
|
+
const wrapper = createWrapper();
|
|
125
|
+
const target = wrapper.find('.popover-card-target');
|
|
126
|
+
|
|
127
|
+
await target.trigger('mouseenter');
|
|
128
|
+
await wrapper.vm.$nextTick();
|
|
129
|
+
|
|
130
|
+
expect(wrapper.vm.focusOpen).toBe(false);
|
|
131
|
+
expect(mockFocusTrap).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should setup focus trap when opened via click', async() => {
|
|
135
|
+
const wrapper = createWrapper({ fallbackFocus: '#my-fallback' });
|
|
136
|
+
const button = wrapper.find('button');
|
|
137
|
+
|
|
138
|
+
await button.trigger('click');
|
|
139
|
+
await wrapper.vm.$nextTick(); // Let watcher for `card` run
|
|
140
|
+
|
|
141
|
+
expect(wrapper.vm.focusOpen).toBe(true);
|
|
142
|
+
expect(mockFocusTrap).toHaveBeenCalledTimes(1);
|
|
143
|
+
|
|
144
|
+
// Check arguments passed to the composable
|
|
145
|
+
const focusTrapOptions = mockFocusTrap.mock.calls[0][2];
|
|
146
|
+
|
|
147
|
+
expect(focusTrapOptions.fallbackFocus).toBe('#my-fallback');
|
|
148
|
+
expect(focusTrapOptions.setReturnFocus()).toBe('.focus-button');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('slots', () => {
|
|
153
|
+
it('should render the default slot content', () => {
|
|
154
|
+
const wrapper = createWrapper({}, { default: '<span class="default-slot-content">Hello</span>' });
|
|
155
|
+
|
|
156
|
+
expect(wrapper.find('.default-slot-content').exists()).toBe(true);
|
|
157
|
+
expect(wrapper.find('.default-slot-content').text()).toBe('Hello');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should render the card-body slot content', async() => {
|
|
161
|
+
const wrapper = createWrapper({}, { 'card-body': '<div class="card-body-content">Card Body</div>' });
|
|
162
|
+
|
|
163
|
+
// Open popover to render the slot
|
|
164
|
+
await wrapper.find('button').trigger('click');
|
|
165
|
+
|
|
166
|
+
expect(wrapper.find('.card-body-content').exists()).toBe(true);
|
|
167
|
+
expect(wrapper.find('.card-body-content').text()).toBe('Card Body');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should pass a close function to the heading-action slot', async() => {
|
|
171
|
+
const wrapper = createWrapper({}, {
|
|
172
|
+
'heading-action': `
|
|
173
|
+
<template #heading-action="{ close }">
|
|
174
|
+
<button class="close-button" @click="close">Close</button>
|
|
175
|
+
</template>
|
|
176
|
+
`
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Open popover
|
|
180
|
+
await wrapper.find('button').trigger('click');
|
|
181
|
+
expect(wrapper.vm.showPopover).toBe(true);
|
|
182
|
+
expect(wrapper.vm.focusOpen).toBe(true);
|
|
183
|
+
|
|
184
|
+
// Click the button that uses the `close` slot prop
|
|
185
|
+
await wrapper.find('.close-button').trigger('click');
|
|
186
|
+
|
|
187
|
+
// Due to the bug, this should be true, not false
|
|
188
|
+
expect(wrapper.vm.showPopover).toBe(false);
|
|
189
|
+
expect(wrapper.vm.focusOpen).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should allow overriding the entire card via the card slot', async() => {
|
|
193
|
+
const wrapper = createWrapper({}, { card: '<div class="custom-card">My Custom Card</div>' });
|
|
194
|
+
|
|
195
|
+
// Open popover
|
|
196
|
+
await wrapper.find('button').trigger('click');
|
|
197
|
+
|
|
198
|
+
expect(wrapper.find('.custom-card').exists()).toBe(true);
|
|
199
|
+
expect(wrapper.find('.custom-card').text()).toBe('My Custom Card');
|
|
200
|
+
// The default Card component should not be rendered
|
|
201
|
+
expect(wrapper.find('[id="popover-card"]').exists()).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -56,11 +56,13 @@ export default {
|
|
|
56
56
|
|
|
57
57
|
data() {
|
|
58
58
|
return {
|
|
59
|
-
principals:
|
|
60
|
-
searchStr:
|
|
61
|
-
options:
|
|
62
|
-
newValue:
|
|
63
|
-
tooltipContent:
|
|
59
|
+
principals: null,
|
|
60
|
+
searchStr: '',
|
|
61
|
+
options: [],
|
|
62
|
+
newValue: '',
|
|
63
|
+
tooltipContent: null,
|
|
64
|
+
hasSearchTooShort: false,
|
|
65
|
+
minSearchLength: 2,
|
|
64
66
|
};
|
|
65
67
|
},
|
|
66
68
|
|
|
@@ -133,9 +135,20 @@ export default {
|
|
|
133
135
|
this.searchStr = str;
|
|
134
136
|
|
|
135
137
|
if ( str ) {
|
|
138
|
+
// Backend requires minimum 2 characters for search
|
|
139
|
+
if (str.length < this.minSearchLength) {
|
|
140
|
+
this.hasSearchTooShort = true;
|
|
141
|
+
this.options = [];
|
|
142
|
+
loading(false);
|
|
143
|
+
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.hasSearchTooShort = false;
|
|
136
148
|
loading(true);
|
|
137
149
|
this.debouncedSearch(str, loading);
|
|
138
150
|
} else {
|
|
151
|
+
this.hasSearchTooShort = false;
|
|
139
152
|
this.search(null, loading);
|
|
140
153
|
}
|
|
141
154
|
},
|
|
@@ -196,7 +209,12 @@ export default {
|
|
|
196
209
|
@on-close="setTooltipContent()"
|
|
197
210
|
>
|
|
198
211
|
<template v-slot:no-options="{ searching }">
|
|
199
|
-
<template v-if="
|
|
212
|
+
<template v-if="hasSearchTooShort">
|
|
213
|
+
<span class="search-slot">
|
|
214
|
+
{{ t('cluster.memberRoles.addClusterMember.minCharacters', { count: minSearchLength }) }}
|
|
215
|
+
</span>
|
|
216
|
+
</template>
|
|
217
|
+
<template v-else-if="searching">
|
|
200
218
|
<span class="search-slot">
|
|
201
219
|
{{ t('cluster.memberRoles.addClusterMember.noResults') }}
|
|
202
220
|
</span>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { shallowMount, type VueWrapper } from '@vue/test-utils';
|
|
2
|
+
import SelectPrincipal from '@shell/components/auth/SelectPrincipal.vue';
|
|
3
|
+
|
|
4
|
+
describe('component: SelectPrincipal', () => {
|
|
5
|
+
const mockStore = { dispatch: jest.fn().mockResolvedValue([]) };
|
|
6
|
+
|
|
7
|
+
const defaultMountOptions = {
|
|
8
|
+
global: {
|
|
9
|
+
mocks: {
|
|
10
|
+
$fetchState: { pending: false },
|
|
11
|
+
$store: mockStore,
|
|
12
|
+
t: (key: string, opts?: any) => opts?.count ? `${ key } ${ opts.count }` : key,
|
|
13
|
+
},
|
|
14
|
+
stubs: {
|
|
15
|
+
LabeledSelect: {
|
|
16
|
+
template: '<div class="labeled-select-stub"><slot name="no-options" :searching="searching" /></div>',
|
|
17
|
+
props: ['options', 'searchable', 'filterable'],
|
|
18
|
+
data() {
|
|
19
|
+
return { searching: false };
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
Principal: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
mockStore.dispatch.mockResolvedValue([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('onSearch', () => {
|
|
33
|
+
it('should set hasSearchTooShort to true when search string is less than minSearchLength', async() => {
|
|
34
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
35
|
+
|
|
36
|
+
// Set principals to an empty array to avoid null errors
|
|
37
|
+
wrapper.vm.principals = [];
|
|
38
|
+
await wrapper.vm.$nextTick();
|
|
39
|
+
|
|
40
|
+
const loadingFn = jest.fn();
|
|
41
|
+
|
|
42
|
+
wrapper.vm.onSearch('a', loadingFn);
|
|
43
|
+
|
|
44
|
+
expect(wrapper.vm.hasSearchTooShort).toBe(true);
|
|
45
|
+
expect(wrapper.vm.options).toStrictEqual([]);
|
|
46
|
+
expect(loadingFn).toHaveBeenCalledWith(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should set hasSearchTooShort to false when search string meets minSearchLength', async() => {
|
|
50
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
51
|
+
|
|
52
|
+
wrapper.vm.principals = [];
|
|
53
|
+
await wrapper.vm.$nextTick();
|
|
54
|
+
|
|
55
|
+
const loadingFn = jest.fn();
|
|
56
|
+
|
|
57
|
+
wrapper.vm.onSearch('ab', loadingFn);
|
|
58
|
+
|
|
59
|
+
expect(wrapper.vm.hasSearchTooShort).toBe(false);
|
|
60
|
+
expect(loadingFn).toHaveBeenCalledWith(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should set hasSearchTooShort to false when search string is empty', async() => {
|
|
64
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
65
|
+
|
|
66
|
+
wrapper.vm.principals = [];
|
|
67
|
+
await wrapper.vm.$nextTick();
|
|
68
|
+
|
|
69
|
+
// First set hasSearchTooShort to true
|
|
70
|
+
wrapper.vm.hasSearchTooShort = true;
|
|
71
|
+
|
|
72
|
+
const loadingFn = jest.fn();
|
|
73
|
+
|
|
74
|
+
wrapper.vm.onSearch('', loadingFn);
|
|
75
|
+
|
|
76
|
+
expect(wrapper.vm.hasSearchTooShort).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should not call debouncedSearch when search string is too short', async() => {
|
|
80
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
81
|
+
|
|
82
|
+
wrapper.vm.principals = [];
|
|
83
|
+
await wrapper.vm.$nextTick();
|
|
84
|
+
|
|
85
|
+
// Spy on the debounced search
|
|
86
|
+
const debouncedSearchSpy = jest.spyOn(wrapper.vm, 'debouncedSearch');
|
|
87
|
+
const loadingFn = jest.fn();
|
|
88
|
+
|
|
89
|
+
wrapper.vm.onSearch('x', loadingFn);
|
|
90
|
+
|
|
91
|
+
expect(debouncedSearchSpy).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should call debouncedSearch when search string meets minimum length', async() => {
|
|
95
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
96
|
+
|
|
97
|
+
wrapper.vm.principals = [];
|
|
98
|
+
await wrapper.vm.$nextTick();
|
|
99
|
+
|
|
100
|
+
const debouncedSearchSpy = jest.spyOn(wrapper.vm, 'debouncedSearch');
|
|
101
|
+
const loadingFn = jest.fn();
|
|
102
|
+
|
|
103
|
+
wrapper.vm.onSearch('xy', loadingFn);
|
|
104
|
+
|
|
105
|
+
expect(debouncedSearchSpy).toHaveBeenCalledWith('xy', loadingFn);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('minSearchLength', () => {
|
|
110
|
+
it('should have a default minSearchLength of 2', async() => {
|
|
111
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
112
|
+
|
|
113
|
+
wrapper.vm.principals = [];
|
|
114
|
+
await wrapper.vm.$nextTick();
|
|
115
|
+
|
|
116
|
+
expect(wrapper.vm.minSearchLength).toBe(2);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -181,7 +181,7 @@ export default {
|
|
|
181
181
|
:options="selectNodeOptions"
|
|
182
182
|
:mode="mode"
|
|
183
183
|
:data-testid="'node-scheduling-selectNode'"
|
|
184
|
-
@
|
|
184
|
+
@update:value="update"
|
|
185
185
|
/>
|
|
186
186
|
</div>
|
|
187
187
|
<template v-if="selectNode === 'nodeSelector'">
|
|
@@ -205,7 +205,7 @@ export default {
|
|
|
205
205
|
v-model:value="nodeAffinity"
|
|
206
206
|
:mode="mode"
|
|
207
207
|
:data-testid="'node-scheduling-nodeAffinity'"
|
|
208
|
-
@
|
|
208
|
+
@update:value="update"
|
|
209
209
|
/>
|
|
210
210
|
</template>
|
|
211
211
|
</div>
|