@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.
- package/assets/styles/global/_button.scss +1 -1
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +183 -51
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +212 -16
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/ResourceTable.vue +1 -0
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +23 -5
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/FileSelector.vue +39 -1
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +12 -1
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +16 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/config/features.js +1 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +3 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +8 -0
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +20 -6
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -4
- package/config/table-headers.js +3 -4
- package/config/types.js +16 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +109 -7
- package/detail/workload/index.vue +12 -55
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +34 -9
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +128 -95
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +542 -141
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +39 -5
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +46 -3
- package/models/provisioning.cattle.io.cluster.js +64 -14
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +120 -20
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +236 -144
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +114 -19
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/catalog.js +57 -0
- package/store/features.js +4 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +166 -70
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/object.js +22 -2
- package/utils/operation-cr.js +19 -0
- package/utils/provider.ts +12 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
package/plugins/steve/actions.js
CHANGED
|
@@ -10,6 +10,7 @@ import { NAMESPACE } from '@shell/config/types';
|
|
|
10
10
|
import { handleKubeApiHeaderWarnings } from '@shell/plugins/steve/header-warnings';
|
|
11
11
|
import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';
|
|
12
12
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
13
|
+
import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
|
|
@@ -221,6 +222,101 @@ export default {
|
|
|
221
222
|
}
|
|
222
223
|
},
|
|
223
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Fetch aggregated state counts for a resource type via the Steve summary API.
|
|
227
|
+
* Requires VAI (ui-sql-cache) to be enabled; returns undefined otherwise.
|
|
228
|
+
*
|
|
229
|
+
* Uses `summaryonly` by default so no resource data is returned.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} type - Resource type (e.g. 'pod', 'service')
|
|
232
|
+
* @param {object} [opt] - Options object
|
|
233
|
+
* @param {string} opt.summaryField - Field to aggregate counts by.
|
|
234
|
+
* Must be a field indexed by the VAI cache (see StevePaginationUtils.VALID_FIELDS in steve-pagination-utils.ts)
|
|
235
|
+
* @param {string} [opt.namespace] - Namespace to scope the request to (only applies to namespaced resource types)
|
|
236
|
+
* @param {boolean} [opt.summaryOnly=true] - Omit resource data from the response (set to false to include data)
|
|
237
|
+
* @param {boolean} [opt.namespaceCounts] - Include per-namespace breakdowns in counts
|
|
238
|
+
* @param {PaginationParamFilter[]} [opt.filters] - Pre-built filters from PaginationParamFilter.createSingleField()
|
|
239
|
+
* @param {KubeLabelSelector} [opt.labelSelector] - Kube label selector to filter by (converted via convertLabelSelectorPaginationParams)
|
|
240
|
+
* @returns {Promise<{ count: number, summary: { property: string, counts: Record<string, { total: number, namespace?: Record<string, number> }> }[] | null } | undefined>}
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* const result = await dispatch('fetchResourceSummary', {
|
|
244
|
+
* type: 'pod',
|
|
245
|
+
* opt: { summaryField: 'metadata.state.name', labelSelector: { matchExpressions: podMatchExpression } }
|
|
246
|
+
* });
|
|
247
|
+
* // result.summary[0].counts => { running: { total: 3 }, error: { total: 1 } }
|
|
248
|
+
*
|
|
249
|
+
* // With namespace breakdowns:
|
|
250
|
+
* const result = await dispatch('fetchResourceSummary', {
|
|
251
|
+
* type: 'pod',
|
|
252
|
+
* opt: { summaryField: 'metadata.state.name', namespaceCounts: true }
|
|
253
|
+
* });
|
|
254
|
+
* // result.summary[0].counts => { running: { total: 3, namespace: { default: 2, 'kube-system': 1 } } }
|
|
255
|
+
*/
|
|
256
|
+
async fetchResourceSummary({ getters, dispatch, rootGetters }, { type, opt = {} }) {
|
|
257
|
+
type = getters.normalizeType(type);
|
|
258
|
+
const schema = getters.schemaFor(type);
|
|
259
|
+
|
|
260
|
+
if (!schema) {
|
|
261
|
+
console.warn(`fetchResourceSummary: no schema found for type "${ type }"`); // eslint-disable-line no-console
|
|
262
|
+
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!paginationUtils.isSteveCacheEnabled({ rootGetters })) {
|
|
267
|
+
console.warn(`fetchResourceSummary: VAI is not enabled, summary API unavailable for type "${ type }"`); // eslint-disable-line no-console
|
|
268
|
+
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!opt.summaryField) {
|
|
273
|
+
console.warn(`fetchResourceSummary: summaryField is required and must be a string for type "${ type }"`); // eslint-disable-line no-console
|
|
274
|
+
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const url = new URL(schema.links.collection, window.location.origin);
|
|
280
|
+
|
|
281
|
+
if (schema.attributes?.namespaced && opt.namespace) {
|
|
282
|
+
url.pathname += `/${ opt.namespace }`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
url.searchParams.set('summary', opt.summaryField);
|
|
286
|
+
|
|
287
|
+
if (opt.summaryOnly !== false) {
|
|
288
|
+
url.searchParams.set('summaryonly', '');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (opt.namespaceCounts) {
|
|
292
|
+
url.searchParams.set('summarynamespaced', '');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (opt.filters?.length) {
|
|
296
|
+
const filterParams = new URLSearchParams(stevePaginationUtils.convertPaginationParams({ schema, filters: opt.filters }));
|
|
297
|
+
|
|
298
|
+
filterParams.forEach((v, k) => url.searchParams.append(k, v));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (opt.labelSelector) {
|
|
302
|
+
const labelParams = new URLSearchParams(stevePaginationUtils.convertLabelSelectorPaginationParams({ labelSelector: opt.labelSelector }));
|
|
303
|
+
|
|
304
|
+
labelParams.forEach((v, k) => url.searchParams.append(k, v));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const res = await dispatch('request', { opt: { url: url.pathname + url.search } });
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
count: res.count ?? 0,
|
|
311
|
+
summary: res.summary || null
|
|
312
|
+
};
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.warn(`fetchResourceSummary: summary API request failed for type "${ type }"`, e); // eslint-disable-line no-console
|
|
315
|
+
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
224
320
|
promptRestore({ commit, state }, resources ) {
|
|
225
321
|
commit('action-menu/togglePromptRestore', resources, { root: true });
|
|
226
322
|
},
|
|
@@ -656,7 +656,7 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
656
656
|
* A lot of the requirements and details are taken directly from
|
|
657
657
|
* https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
|
658
658
|
*/
|
|
659
|
-
|
|
659
|
+
convertLabelSelectorPaginationParams({ labelSelector }: { labelSelector: KubeLabelSelector}): string {
|
|
660
660
|
// Get a list of matchExpressions
|
|
661
661
|
const expressions: KubeLabelSelectorExpression[] = labelSelector.matchExpressions ? [...labelSelector.matchExpressions] : [];
|
|
662
662
|
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
computed, defineComponent, nextTick, ref, useTemplateRef
|
|
4
|
+
} from 'vue';
|
|
5
|
+
import { mapGetters, useStore } from 'vuex';
|
|
6
|
+
import { useInSummary } from '@shell/components/TableOfContents/composables';
|
|
7
|
+
import { useI18n } from '@shell/composables/useI18n';
|
|
4
8
|
|
|
5
9
|
export default defineComponent({
|
|
10
|
+
name: 'Accordion',
|
|
11
|
+
|
|
6
12
|
props: {
|
|
7
13
|
title: {
|
|
8
14
|
type: String,
|
|
@@ -20,22 +26,59 @@ export default defineComponent({
|
|
|
20
26
|
}
|
|
21
27
|
},
|
|
22
28
|
|
|
29
|
+
setup(props) {
|
|
30
|
+
const store = useStore();
|
|
31
|
+
const { t } = useI18n(store);
|
|
32
|
+
const label = computed(() => props.titleKey && typeof t === 'function' ? t(props.titleKey) : props.title);
|
|
33
|
+
|
|
34
|
+
const isOpen = ref(props.openInitially);
|
|
35
|
+
const accordionSummarizedContainer = useTemplateRef<HTMLElement>('accordion-summarized-container');
|
|
36
|
+
|
|
37
|
+
const scrollTo = () => {
|
|
38
|
+
isOpen.value = true;
|
|
39
|
+
nextTick(() => {
|
|
40
|
+
accordionSummarizedContainer.value?.scrollIntoView();
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const { summary } = useInSummary({
|
|
45
|
+
scrollTo,
|
|
46
|
+
label,
|
|
47
|
+
elementRef: accordionSummarizedContainer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
summary,
|
|
52
|
+
isOpen,
|
|
53
|
+
scrollTo,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
|
|
23
57
|
data() {
|
|
24
|
-
return {
|
|
58
|
+
return {};
|
|
25
59
|
},
|
|
26
60
|
|
|
27
|
-
computed: {
|
|
61
|
+
computed: {
|
|
62
|
+
...mapGetters({ t: 'i18n/t' }),
|
|
63
|
+
|
|
64
|
+
displayTitle() {
|
|
65
|
+
return this.titleKey ? this.t(this.titleKey) : this.title;
|
|
66
|
+
},
|
|
67
|
+
},
|
|
28
68
|
|
|
29
69
|
methods: {
|
|
30
70
|
toggle() {
|
|
31
71
|
this.isOpen = !this.isOpen;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
34
74
|
});
|
|
35
75
|
</script>
|
|
36
76
|
|
|
37
77
|
<template>
|
|
38
|
-
<div
|
|
78
|
+
<div
|
|
79
|
+
ref="accordion-summarized-container"
|
|
80
|
+
class="accordion-container"
|
|
81
|
+
>
|
|
39
82
|
<div
|
|
40
83
|
class="accordion-header"
|
|
41
84
|
data-testid="accordion-header"
|
|
@@ -51,7 +94,7 @@ export default defineComponent({
|
|
|
51
94
|
data-testid="accordion-title-slot-content"
|
|
52
95
|
class="mb-0"
|
|
53
96
|
>
|
|
54
|
-
{{
|
|
97
|
+
{{ displayTitle }}
|
|
55
98
|
</h2>
|
|
56
99
|
</slot>
|
|
57
100
|
</div>
|
|
@@ -67,7 +110,8 @@ export default defineComponent({
|
|
|
67
110
|
|
|
68
111
|
<style lang="scss" scoped>
|
|
69
112
|
.accordion-container {
|
|
70
|
-
border: 1px solid var(--border)
|
|
113
|
+
border: 1px solid var(--border);
|
|
114
|
+
border-radius: var(--border-radius);
|
|
71
115
|
}
|
|
72
116
|
.accordion-header {
|
|
73
117
|
padding: 16px 16px 16px 11px;
|
|
@@ -125,6 +125,14 @@ export default defineComponent({
|
|
|
125
125
|
default: undefined
|
|
126
126
|
},
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Use body text color for the label instead of the default input-label color.
|
|
130
|
+
*/
|
|
131
|
+
useBodyTextColor: {
|
|
132
|
+
type: Boolean,
|
|
133
|
+
default: false
|
|
134
|
+
},
|
|
135
|
+
|
|
128
136
|
/**
|
|
129
137
|
* Inherited global identifier prefix for tests
|
|
130
138
|
* Define a term based on the parent component to avoid conflicts on multiple components
|
|
@@ -316,6 +324,7 @@ export default defineComponent({
|
|
|
316
324
|
<span
|
|
317
325
|
v-if="labelKey"
|
|
318
326
|
:id="idForLabel"
|
|
327
|
+
:class="{ 'body-text-color': useBodyTextColor }"
|
|
319
328
|
>
|
|
320
329
|
<t
|
|
321
330
|
:k="labelKey"
|
|
@@ -325,6 +334,7 @@ export default defineComponent({
|
|
|
325
334
|
<span
|
|
326
335
|
v-else-if="label"
|
|
327
336
|
:id="idForLabel"
|
|
337
|
+
:class="{ 'body-text-color': useBodyTextColor }"
|
|
328
338
|
>{{ label }}</span>
|
|
329
339
|
<i
|
|
330
340
|
v-if="tooltipKey"
|
|
@@ -403,6 +413,10 @@ $fontColor: var(--input-label);
|
|
|
403
413
|
display: inline-flex;
|
|
404
414
|
margin: 0px 10px 0px 5px;
|
|
405
415
|
|
|
416
|
+
.body-text-color {
|
|
417
|
+
color: var(--body-text);
|
|
418
|
+
}
|
|
419
|
+
|
|
406
420
|
&.checkbox-primary {
|
|
407
421
|
color: inherit;
|
|
408
422
|
font-weight: 600;
|
|
@@ -80,6 +80,14 @@ export default defineComponent({
|
|
|
80
80
|
default: false
|
|
81
81
|
},
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Use body text color for the label instead of the default input-label color.
|
|
85
|
+
*/
|
|
86
|
+
useBodyTextColor: {
|
|
87
|
+
type: Boolean,
|
|
88
|
+
default: false
|
|
89
|
+
},
|
|
90
|
+
|
|
83
91
|
/**
|
|
84
92
|
* Radio option Id - used to link to aria-activedescendant
|
|
85
93
|
* when using inside of the context of a Radio Group
|
|
@@ -189,7 +197,11 @@ export default defineComponent({
|
|
|
189
197
|
/>
|
|
190
198
|
<div class="labeling">
|
|
191
199
|
<label
|
|
192
|
-
|
|
200
|
+
class="radio-label m-0"
|
|
201
|
+
:class="{
|
|
202
|
+
'text-muted': muteLabel,
|
|
203
|
+
'body-text-color': useBodyTextColor
|
|
204
|
+
}"
|
|
193
205
|
:for="name"
|
|
194
206
|
>
|
|
195
207
|
<slot
|
|
@@ -314,6 +326,10 @@ $fontColor: var(--input-label);
|
|
|
314
326
|
flex-direction: column;
|
|
315
327
|
|
|
316
328
|
margin: 3px 10px 0px 5px;
|
|
329
|
+
|
|
330
|
+
.body-text-color {
|
|
331
|
+
color: var(--body-text);
|
|
332
|
+
}
|
|
317
333
|
}
|
|
318
334
|
}
|
|
319
335
|
|
|
@@ -12,6 +12,7 @@ interface Option {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export default defineComponent({
|
|
15
|
+
name: 'RadioGroup',
|
|
15
16
|
components: { RadioButton },
|
|
16
17
|
props: {
|
|
17
18
|
/**
|
|
@@ -102,6 +103,14 @@ export default defineComponent({
|
|
|
102
103
|
row: {
|
|
103
104
|
type: Boolean,
|
|
104
105
|
default: false
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Use body text color for the label instead of the default input-label color.
|
|
110
|
+
*/
|
|
111
|
+
useBodyTextColor: {
|
|
112
|
+
type: Boolean,
|
|
113
|
+
default: false
|
|
105
114
|
}
|
|
106
115
|
},
|
|
107
116
|
|
|
@@ -302,6 +311,7 @@ export default defineComponent({
|
|
|
302
311
|
:disabled="isDisabled"
|
|
303
312
|
:data-testid="`radio-button-${i}`"
|
|
304
313
|
:mode="mode"
|
|
314
|
+
:use-body-text-color="useBodyTextColor"
|
|
305
315
|
:prevent-focus-on-radio-groups="true"
|
|
306
316
|
@update:value="$emit('update:value', $event)"
|
|
307
317
|
/>
|
|
@@ -77,6 +77,16 @@ export default defineComponent({
|
|
|
77
77
|
disabled: {
|
|
78
78
|
type: Boolean,
|
|
79
79
|
default: false
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Recalculate the height when the value is changed programmatically (e.g.
|
|
84
|
+
* populated from a file) and when the window is resized, not just on user
|
|
85
|
+
* input. Opt-in to avoid changing the behaviour of existing usages.
|
|
86
|
+
*/
|
|
87
|
+
resizeOnValueChangeAndResizeWindow: {
|
|
88
|
+
type: Boolean,
|
|
89
|
+
default: false
|
|
80
90
|
}
|
|
81
91
|
},
|
|
82
92
|
|
|
@@ -117,6 +127,14 @@ export default defineComponent({
|
|
|
117
127
|
},
|
|
118
128
|
|
|
119
129
|
watch: {
|
|
130
|
+
// Recalculate the height when the value is changed programmatically (e.g.
|
|
131
|
+
// populated from a file), not just on user input. Opt-in via resizeOnValueChangeAndResizeWindow.
|
|
132
|
+
value() {
|
|
133
|
+
if (this.resizeOnValueChangeAndResizeWindow) {
|
|
134
|
+
this.queueResize();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
120
138
|
$attrs: {
|
|
121
139
|
deep: true,
|
|
122
140
|
handler() {
|
|
@@ -134,6 +152,18 @@ export default defineComponent({
|
|
|
134
152
|
this.$nextTick(() => {
|
|
135
153
|
this.autoSize();
|
|
136
154
|
});
|
|
155
|
+
|
|
156
|
+
// Width changes alter text wrapping, so the required height can change when
|
|
157
|
+
// the window is resized. Opt-in via resizeOnValueChangeAndResizeWindow.
|
|
158
|
+
if (this.resizeOnValueChangeAndResizeWindow) {
|
|
159
|
+
window.addEventListener('resize', this.queueResize);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
beforeUnmount() {
|
|
164
|
+
if (this.resizeOnValueChangeAndResizeWindow) {
|
|
165
|
+
window.removeEventListener('resize', this.queueResize);
|
|
166
|
+
}
|
|
137
167
|
},
|
|
138
168
|
|
|
139
169
|
methods: {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import TextAreaAutoGrow from '@components/Form/TextArea/TextAreaAutoGrow.vue';
|
|
3
|
+
|
|
4
|
+
describe('component: TextAreaAutoGrow', () => {
|
|
5
|
+
it('should recalculate its height when the value changes programmatically and resizeOnValueChangeAndResizeWindow is set', async() => {
|
|
6
|
+
const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
|
|
7
|
+
|
|
8
|
+
// queueResize is the (debounced) entrypoint that triggers autoSize
|
|
9
|
+
const queueResize = jest.fn();
|
|
10
|
+
|
|
11
|
+
wrapper.vm.queueResize = queueResize;
|
|
12
|
+
|
|
13
|
+
await wrapper.setProps({ value: 'a\nmuch\nlonger\nvalue\nset\nfrom\noutside' });
|
|
14
|
+
|
|
15
|
+
expect(queueResize).toHaveBeenCalledWith();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should not recalculate its height on programmatic value change by default', async() => {
|
|
19
|
+
const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial' } });
|
|
20
|
+
|
|
21
|
+
const queueResize = jest.fn();
|
|
22
|
+
|
|
23
|
+
wrapper.vm.queueResize = queueResize;
|
|
24
|
+
|
|
25
|
+
await wrapper.setProps({ value: 'a\nmuch\nlonger\nvalue\nset\nfrom\noutside' });
|
|
26
|
+
|
|
27
|
+
expect(queueResize).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should recalculate its height on user input', async() => {
|
|
31
|
+
const wrapper = mount(TextAreaAutoGrow, { props: { value: '' } });
|
|
32
|
+
|
|
33
|
+
const queueResize = jest.fn();
|
|
34
|
+
|
|
35
|
+
wrapper.vm.queueResize = queueResize;
|
|
36
|
+
|
|
37
|
+
await wrapper.find('textarea').setValue('typed value');
|
|
38
|
+
|
|
39
|
+
expect(queueResize).toHaveBeenCalledWith();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should register a window resize listener when resizeOnValueChangeAndResizeWindow is set', () => {
|
|
43
|
+
const addSpy = jest.spyOn(window, 'addEventListener');
|
|
44
|
+
|
|
45
|
+
const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
|
|
46
|
+
|
|
47
|
+
const resizeListener = addSpy.mock.calls.find(([event]) => event === 'resize')?.[1];
|
|
48
|
+
|
|
49
|
+
expect(resizeListener).toBe(wrapper.vm.queueResize);
|
|
50
|
+
|
|
51
|
+
addSpy.mockRestore();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should not register a window resize listener by default', () => {
|
|
55
|
+
const addSpy = jest.spyOn(window, 'addEventListener');
|
|
56
|
+
|
|
57
|
+
mount(TextAreaAutoGrow, { props: { value: 'initial' } });
|
|
58
|
+
|
|
59
|
+
const resizeListener = addSpy.mock.calls.find(([event]) => event === 'resize')?.[1];
|
|
60
|
+
|
|
61
|
+
expect(resizeListener).toBeUndefined();
|
|
62
|
+
|
|
63
|
+
addSpy.mockRestore();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should recalculate its height when a window resize fires and resizeOnValueChangeAndResizeWindow is set', () => {
|
|
67
|
+
jest.useFakeTimers();
|
|
68
|
+
const component = TextAreaAutoGrow as unknown as { methods: Record<string, () => void> };
|
|
69
|
+
const autoSizeSpy = jest.spyOn(component.methods, 'autoSize');
|
|
70
|
+
|
|
71
|
+
mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
|
|
72
|
+
autoSizeSpy.mockClear();
|
|
73
|
+
|
|
74
|
+
window.dispatchEvent(new Event('resize'));
|
|
75
|
+
jest.advanceTimersByTime(150); // queueResize is debounced (100ms)
|
|
76
|
+
|
|
77
|
+
expect(autoSizeSpy).toHaveBeenCalledWith(expect.any(Event));
|
|
78
|
+
|
|
79
|
+
autoSizeSpy.mockRestore();
|
|
80
|
+
jest.useRealTimers();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should remove the window resize listener when unmounted', () => {
|
|
84
|
+
const removeSpy = jest.spyOn(window, 'removeEventListener');
|
|
85
|
+
|
|
86
|
+
const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
|
|
87
|
+
const { queueResize } = wrapper.vm;
|
|
88
|
+
|
|
89
|
+
wrapper.unmount();
|
|
90
|
+
|
|
91
|
+
expect(removeSpy).toHaveBeenCalledWith('resize', queueResize);
|
|
92
|
+
|
|
93
|
+
removeSpy.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -28,7 +28,8 @@ const emit = defineEmits(['close']);
|
|
|
28
28
|
<style lang="scss" scoped>
|
|
29
29
|
.rc-tag {
|
|
30
30
|
display: inline-flex;
|
|
31
|
-
|
|
31
|
+
min-height: 24px;
|
|
32
|
+
padding: 2px 8px;
|
|
32
33
|
align-items: center;
|
|
33
34
|
gap: 8px;
|
|
34
35
|
|
|
@@ -41,7 +42,7 @@ const emit = defineEmits(['close']);
|
|
|
41
42
|
font-size: 13px;
|
|
42
43
|
font-style: normal;
|
|
43
44
|
font-weight: 400;
|
|
44
|
-
line-height:
|
|
45
|
+
line-height: 20px;
|
|
45
46
|
color: var(--body-text);
|
|
46
47
|
|
|
47
48
|
button {
|
|
@@ -139,6 +139,109 @@ describe('rcButton.vue', () => {
|
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
describe('space key navigation', () => {
|
|
143
|
+
it('triggers click when Space is pressed on a link button', async() => {
|
|
144
|
+
const to = { name: 'some-route' };
|
|
145
|
+
const wrapper = mount(RcButton, {
|
|
146
|
+
props: { to },
|
|
147
|
+
global: { stubs: { RouterLink: RouterLinkStub } },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const link = wrapper.findComponent(RouterLinkStub);
|
|
151
|
+
const clickSpy = jest.spyOn(link.element, 'click');
|
|
152
|
+
|
|
153
|
+
await link.trigger('keyup', { key: ' ' });
|
|
154
|
+
|
|
155
|
+
expect(clickSpy).toHaveBeenCalledWith();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('does not manually trigger click when Space is pressed on a regular button', async() => {
|
|
159
|
+
const wrapper = mount(RcButton);
|
|
160
|
+
const button = wrapper.find('button');
|
|
161
|
+
const clickSpy = jest.spyOn(button.element, 'click');
|
|
162
|
+
|
|
163
|
+
await button.trigger('keyup', { key: ' ' });
|
|
164
|
+
|
|
165
|
+
expect(clickSpy).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('to and href mutual exclusion', () => {
|
|
170
|
+
it('warns when both "to" and "href" props are provided', () => {
|
|
171
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
172
|
+
|
|
173
|
+
mount(RcButton, {
|
|
174
|
+
props: { to: '/foo', href: 'https://example.com' },
|
|
175
|
+
global: { stubs: { RouterLink: RouterLinkStub } },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(warnSpy).toHaveBeenCalledWith('[RcButton] "to" and "href" are mutually exclusive. Provide only one.');
|
|
179
|
+
warnSpy.mockRestore();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('does not warn when only "to" is provided', () => {
|
|
183
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
184
|
+
|
|
185
|
+
mount(RcButton, {
|
|
186
|
+
props: { to: '/foo' },
|
|
187
|
+
global: { stubs: { RouterLink: RouterLinkStub } },
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(warnSpy).not.toHaveBeenCalledWith('[RcButton] "to" and "href" are mutually exclusive. Provide only one.');
|
|
191
|
+
warnSpy.mockRestore();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('does not warn when only "href" is provided', () => {
|
|
195
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
196
|
+
|
|
197
|
+
mount(RcButton, { props: { href: 'https://example.com' } });
|
|
198
|
+
|
|
199
|
+
expect(warnSpy).not.toHaveBeenCalledWith('[RcButton] "to" and "href" are mutually exclusive. Provide only one.');
|
|
200
|
+
warnSpy.mockRestore();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('href prop', () => {
|
|
205
|
+
it('renders as an <a> element when "href" prop is provided', () => {
|
|
206
|
+
const wrapper = mount(RcButton, { props: { href: 'https://example.com' } });
|
|
207
|
+
|
|
208
|
+
expect(wrapper.find('a').exists()).toBe(true);
|
|
209
|
+
expect(wrapper.find('button').exists()).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('sets the href attribute on the rendered anchor', () => {
|
|
213
|
+
const href = 'https://example.com';
|
|
214
|
+
const wrapper = mount(RcButton, { props: { href } });
|
|
215
|
+
|
|
216
|
+
expect(wrapper.find('a').attributes('href')).toStrictEqual(href);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('sets role="link" when rendered as an anchor', () => {
|
|
220
|
+
const wrapper = mount(RcButton, { props: { href: 'https://example.com' } });
|
|
221
|
+
|
|
222
|
+
expect(wrapper.find('a').attributes('role')).toStrictEqual('link');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('applies button classes when rendered as an anchor', () => {
|
|
226
|
+
const wrapper = mount(RcButton, { props: { href: 'https://example.com', variant: 'secondary' } });
|
|
227
|
+
const anchor = wrapper.find('a');
|
|
228
|
+
|
|
229
|
+
expect(anchor.classes()).toContain('rc-button');
|
|
230
|
+
expect(anchor.classes()).toContain('btn');
|
|
231
|
+
expect(anchor.classes()).toContain('variant-secondary');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('triggers click when Space is pressed on an anchor', async() => {
|
|
235
|
+
const wrapper = mount(RcButton, { props: { href: 'https://example.com' } });
|
|
236
|
+
const anchor = wrapper.find('a');
|
|
237
|
+
const clickSpy = jest.spyOn(anchor.element, 'click');
|
|
238
|
+
|
|
239
|
+
await anchor.trigger('keyup', { key: ' ' });
|
|
240
|
+
|
|
241
|
+
expect(clickSpy).toHaveBeenCalledWith();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
142
245
|
describe('to prop', () => {
|
|
143
246
|
it('renders as a <button> when no "to" prop is provided', () => {
|
|
144
247
|
const wrapper = mount(RcButton);
|