@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
|
@@ -314,7 +314,9 @@ self.onmessage = (e) => {
|
|
|
314
314
|
if (workerActions[action]) {
|
|
315
315
|
workerActions[action](e?.data[action]);
|
|
316
316
|
} else {
|
|
317
|
-
|
|
317
|
+
// This catches any window sendMessage event. We're hitting this on hot-reload of code where somehow this file is loaded
|
|
318
|
+
// Could be related to extensions, which have their own version of this
|
|
319
|
+
console.debug('no associated action for:', action); // eslint-disable-line no-console
|
|
318
320
|
}
|
|
319
321
|
});
|
|
320
322
|
}; // bind everything to the worker's onmessage handler via the workerActions
|
package/public/index.html
CHANGED
|
@@ -3,14 +3,23 @@ import { Banner } from './index';
|
|
|
3
3
|
|
|
4
4
|
describe('component: Banner', () => {
|
|
5
5
|
it('should display text based on label', () => {
|
|
6
|
-
const label = 'test';
|
|
6
|
+
const label = 'some-label-test';
|
|
7
7
|
const wrapper = mount(
|
|
8
8
|
Banner,
|
|
9
9
|
{ propsData: { label } });
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
expect(wrapper.html()).toContain(label);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should display text based on default slot', () => {
|
|
15
|
+
const slotText = 'some-test';
|
|
16
|
+
|
|
17
|
+
const wrapper = mount(
|
|
18
|
+
Banner,
|
|
19
|
+
{ slots: { default: slotText } }
|
|
20
|
+
);
|
|
12
21
|
|
|
13
|
-
expect(
|
|
22
|
+
expect(wrapper.html()).toContain(slotText);
|
|
14
23
|
});
|
|
15
24
|
|
|
16
25
|
it('should display an icon', () => {
|
|
@@ -56,4 +65,43 @@ describe('component: Banner', () => {
|
|
|
56
65
|
|
|
57
66
|
expect(element.classList).toContain('stacked');
|
|
58
67
|
});
|
|
68
|
+
|
|
69
|
+
it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', () => {
|
|
70
|
+
const label = 'test';
|
|
71
|
+
const icon = 'my-icon';
|
|
72
|
+
const closable = true;
|
|
73
|
+
|
|
74
|
+
const wrapper = mount(
|
|
75
|
+
Banner,
|
|
76
|
+
{
|
|
77
|
+
propsData: {
|
|
78
|
+
label, icon, closable
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const mainContainer = wrapper.find('.banner');
|
|
83
|
+
const bannerIcon = wrapper.find('.banner__icon i');
|
|
84
|
+
const bannerContent = wrapper.find('.banner__content');
|
|
85
|
+
const bannerCloseBtn = wrapper.find('.banner__content__closer');
|
|
86
|
+
const bannerCloseIcon = wrapper.find('.icon-close.closer-icon');
|
|
87
|
+
|
|
88
|
+
const mainContainerRole = mainContainer.attributes('role');
|
|
89
|
+
const mainContainerAriaLabelledBy = mainContainer.attributes('aria-labelledby');
|
|
90
|
+
|
|
91
|
+
const bannerIconAlt = bannerIcon.attributes('alt');
|
|
92
|
+
|
|
93
|
+
const bannerContentId = bannerContent.attributes('id');
|
|
94
|
+
|
|
95
|
+
const bannerCloseBtnRole = bannerCloseBtn.attributes('role');
|
|
96
|
+
const bannerCloseBtnAriaLabel = bannerCloseBtn.attributes('aria-label');
|
|
97
|
+
|
|
98
|
+
const bannerCloseIconAlt = bannerCloseIcon.attributes('alt');
|
|
99
|
+
|
|
100
|
+
expect(mainContainerRole).toBe('region');
|
|
101
|
+
expect(mainContainerAriaLabelledBy).toBe(bannerContentId);
|
|
102
|
+
expect(bannerIconAlt).toBeDefined();
|
|
103
|
+
expect(bannerCloseIconAlt).toBeDefined();
|
|
104
|
+
expect(bannerCloseBtnRole).toBe('button');
|
|
105
|
+
expect(bannerCloseBtnAriaLabel).toBeDefined();
|
|
106
|
+
});
|
|
59
107
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { defineComponent } from 'vue';
|
|
3
|
-
import { nlToBr } from '@shell/utils/string';
|
|
3
|
+
import { nlToBr, generateRandomAlphaString } from '@shell/utils/string';
|
|
4
4
|
import { stringify } from '@shell/utils/error';
|
|
5
5
|
|
|
6
6
|
export default defineComponent({
|
|
@@ -47,16 +47,26 @@ export default defineComponent({
|
|
|
47
47
|
stacked: {
|
|
48
48
|
type: Boolean,
|
|
49
49
|
default: false
|
|
50
|
-
}
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* Disabled banner - banner is shown greyed out
|
|
53
|
+
*/
|
|
54
|
+
disabled: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
default: false
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
emits: ['close'],
|
|
60
|
+
data() {
|
|
61
|
+
return { labelledbyId: `banner-labelledby-${ generateRandomAlphaString(12) }` };
|
|
51
62
|
},
|
|
52
|
-
emits: ['close'],
|
|
53
63
|
computed: {
|
|
54
64
|
/**
|
|
55
65
|
* Return message text as label.
|
|
56
66
|
*/
|
|
57
67
|
messageLabel(): string | void {
|
|
58
68
|
return !(typeof this.label === 'string') ? stringify(this.label) : undefined;
|
|
59
|
-
}
|
|
69
|
+
},
|
|
60
70
|
},
|
|
61
71
|
methods: { nlToBr }
|
|
62
72
|
});
|
|
@@ -66,8 +76,11 @@ export default defineComponent({
|
|
|
66
76
|
class="banner"
|
|
67
77
|
:class="{
|
|
68
78
|
[color]: true,
|
|
79
|
+
'banner-disabled': disabled
|
|
69
80
|
}"
|
|
70
|
-
role="
|
|
81
|
+
role="region"
|
|
82
|
+
:aria-labelledby="labelledbyId"
|
|
83
|
+
tabindex="0"
|
|
71
84
|
>
|
|
72
85
|
<div
|
|
73
86
|
v-if="icon"
|
|
@@ -77,9 +90,11 @@ export default defineComponent({
|
|
|
77
90
|
<i
|
|
78
91
|
class="icon icon-2x"
|
|
79
92
|
:class="icon"
|
|
93
|
+
:alt="t('generic.banners.bannerIcon')"
|
|
80
94
|
/>
|
|
81
95
|
</div>
|
|
82
96
|
<div
|
|
97
|
+
:id="labelledbyId"
|
|
83
98
|
class="banner__content"
|
|
84
99
|
data-testid="banner-content"
|
|
85
100
|
:class="{
|
|
@@ -94,7 +109,9 @@ export default defineComponent({
|
|
|
94
109
|
:k="labelKey"
|
|
95
110
|
:raw="true"
|
|
96
111
|
/>
|
|
97
|
-
<span
|
|
112
|
+
<span
|
|
113
|
+
v-else-if="messageLabel"
|
|
114
|
+
>{{ messageLabel }}</span>
|
|
98
115
|
<span
|
|
99
116
|
v-else
|
|
100
117
|
v-clean-html="nlToBr(label)"
|
|
@@ -113,6 +130,7 @@ export default defineComponent({
|
|
|
113
130
|
<i
|
|
114
131
|
data-testid="banner-close"
|
|
115
132
|
class="icon icon-close closer-icon"
|
|
133
|
+
:alt="t('generic.banners.altCloseBanner')"
|
|
116
134
|
/>
|
|
117
135
|
</div>
|
|
118
136
|
</div>
|
|
@@ -164,6 +182,10 @@ $icon-size: 24px;
|
|
|
164
182
|
}
|
|
165
183
|
}
|
|
166
184
|
|
|
185
|
+
&.banner-disabled {
|
|
186
|
+
filter: grayscale(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
167
189
|
&__content {
|
|
168
190
|
padding: 10px;
|
|
169
191
|
transition: all 0.2s ease;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallowMount, Wrapper } from '@vue/test-utils';
|
|
1
|
+
import { shallowMount, Wrapper, mount } from '@vue/test-utils';
|
|
2
2
|
import { Checkbox } from './index';
|
|
3
3
|
|
|
4
4
|
describe('checkbox.vue', () => {
|
|
@@ -65,4 +65,62 @@ describe('checkbox.vue', () => {
|
|
|
65
65
|
|
|
66
66
|
expect(wrapper.emitted('update:value')[0][0]).toBeNull();
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
|
|
70
|
+
const alternateLabel = 'some-alternate-aria-label';
|
|
71
|
+
const description = 'some-description';
|
|
72
|
+
const ariaDescribedById = 'some-external-id';
|
|
73
|
+
|
|
74
|
+
const wrapper: Wrapper<InstanceType<typeof Checkbox>> = mount(
|
|
75
|
+
Checkbox,
|
|
76
|
+
{
|
|
77
|
+
propsData: {
|
|
78
|
+
value: false, alternateLabel, description
|
|
79
|
+
},
|
|
80
|
+
attrs: { 'aria-describedby': ariaDescribedById },
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const field = wrapper.find('.checkbox-custom');
|
|
85
|
+
const ariaChecked = field.attributes('aria-checked');
|
|
86
|
+
const ariaLabel = field.attributes('aria-label');
|
|
87
|
+
const ariaLabelledBy = field.attributes('aria-labelledby');
|
|
88
|
+
const ariaDescribedBy = field.attributes('aria-describedby');
|
|
89
|
+
|
|
90
|
+
// validates type of input rendered
|
|
91
|
+
expect(ariaChecked).toBe('false');
|
|
92
|
+
expect(ariaLabelledBy).toBeUndefined();
|
|
93
|
+
expect(ariaLabel).toBe(alternateLabel);
|
|
94
|
+
expect(ariaDescribedBy).toBe(`${ ariaDescribedById } ${ wrapper.vm.describedById }`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('a11y: having a label should not render "aria-label" prop and have "aria-labelledby"', async() => {
|
|
98
|
+
const label = 'some-label';
|
|
99
|
+
|
|
100
|
+
const wrapper: Wrapper<InstanceType<typeof Checkbox>> = mount(
|
|
101
|
+
Checkbox,
|
|
102
|
+
{
|
|
103
|
+
propsData: {
|
|
104
|
+
value: true, label, disabled: true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const field = wrapper.find('.checkbox-custom');
|
|
110
|
+
const ariaChecked = field.attributes('aria-checked');
|
|
111
|
+
const ariaLabel = field.attributes('aria-label');
|
|
112
|
+
const ariaLabelledBy = field.attributes('aria-labelledby');
|
|
113
|
+
const ariaDisabled = field.attributes('aria-disabled');
|
|
114
|
+
const tabIndex = field.attributes('tabindex');
|
|
115
|
+
|
|
116
|
+
// validates type of input rendered
|
|
117
|
+
expect(field.exists()).toBe(true);
|
|
118
|
+
expect(ariaChecked).toBe('true');
|
|
119
|
+
expect(ariaLabelledBy).toBe(wrapper.vm.idForLabel);
|
|
120
|
+
expect(ariaLabel).toBeUndefined();
|
|
121
|
+
expect(wrapper.find(`#${ wrapper.vm.idForLabel }`).text()).toBe(label);
|
|
122
|
+
|
|
123
|
+
expect(ariaDisabled).toBe('true');
|
|
124
|
+
expect(tabIndex).toBe('-1');
|
|
125
|
+
});
|
|
68
126
|
});
|
|
@@ -124,6 +124,15 @@ export default defineComponent({
|
|
|
124
124
|
type: String,
|
|
125
125
|
default: undefined
|
|
126
126
|
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Inherited global identifier prefix for tests
|
|
130
|
+
* Define a term based on the parent component to avoid conflicts on multiple components
|
|
131
|
+
*/
|
|
132
|
+
componentTestid: {
|
|
133
|
+
type: String,
|
|
134
|
+
default: 'checkbox'
|
|
135
|
+
},
|
|
127
136
|
},
|
|
128
137
|
|
|
129
138
|
emits: ['update:value'],
|
|
@@ -133,6 +142,18 @@ export default defineComponent({
|
|
|
133
142
|
},
|
|
134
143
|
|
|
135
144
|
computed: {
|
|
145
|
+
ariaDescribedBy(): string | undefined {
|
|
146
|
+
const inheritedDescribedBy = this.$attrs['aria-describedby'];
|
|
147
|
+
const internalDescribedBy = this.descriptionKey || this.description ? this.describedById : undefined;
|
|
148
|
+
|
|
149
|
+
if (inheritedDescribedBy && internalDescribedBy) {
|
|
150
|
+
return `${ inheritedDescribedBy } ${ internalDescribedBy }`;
|
|
151
|
+
} else if (inheritedDescribedBy || internalDescribedBy) {
|
|
152
|
+
return `${ inheritedDescribedBy || internalDescribedBy }`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return undefined;
|
|
156
|
+
},
|
|
136
157
|
/**
|
|
137
158
|
* Determines if the checkbox is disabled.
|
|
138
159
|
* @returns boolean: True when the disabled prop is true or when mode is
|
|
@@ -167,7 +188,7 @@ export default defineComponent({
|
|
|
167
188
|
},
|
|
168
189
|
|
|
169
190
|
idForLabel():string {
|
|
170
|
-
return `${
|
|
191
|
+
return `${ generateRandomAlphaString(12) }-checkbox-label`;
|
|
171
192
|
}
|
|
172
193
|
},
|
|
173
194
|
|
|
@@ -271,10 +292,11 @@ export default defineComponent({
|
|
|
271
292
|
class="checkbox-custom"
|
|
272
293
|
:class="{indeterminate: indeterminate}"
|
|
273
294
|
:tabindex="isDisabled ? -1 : 0"
|
|
295
|
+
:aria-disabled="isDisabled"
|
|
274
296
|
:aria-label="replacementLabel"
|
|
275
297
|
:aria-checked="!!value"
|
|
276
298
|
:aria-labelledby="labelKey || label ? idForLabel : undefined"
|
|
277
|
-
:aria-describedby="
|
|
299
|
+
:aria-describedby="ariaDescribedBy"
|
|
278
300
|
role="checkbox"
|
|
279
301
|
/>
|
|
280
302
|
<span
|
|
@@ -298,6 +320,7 @@ export default defineComponent({
|
|
|
298
320
|
v-clean-tooltip="{content: t(tooltipKey), triggers: ['hover', 'touch', 'focus']}"
|
|
299
321
|
v-stripped-aria-label="t(tooltipKey)"
|
|
300
322
|
class="checkbox-info icon icon-info icon-lg"
|
|
323
|
+
:data-testid="componentTestid + '-info-icon'"
|
|
301
324
|
:tabindex="isDisabled ? -1 : 0"
|
|
302
325
|
/>
|
|
303
326
|
<i
|
|
@@ -305,6 +328,7 @@ export default defineComponent({
|
|
|
305
328
|
v-clean-tooltip="{content: tooltip, triggers: ['hover', 'touch', 'focus']}"
|
|
306
329
|
v-stripped-aria-label="tooltip"
|
|
307
330
|
class="checkbox-info icon icon-info icon-lg"
|
|
331
|
+
:data-testid="componentTestid + '-info-icon'"
|
|
308
332
|
:tabindex="isDisabled ? -1 : 0"
|
|
309
333
|
/>
|
|
310
334
|
</slot>
|
|
@@ -374,7 +398,7 @@ $fontColor: var(--input-label);
|
|
|
374
398
|
|
|
375
399
|
.checkbox-info {
|
|
376
400
|
line-height: normal;
|
|
377
|
-
margin-left:
|
|
401
|
+
margin-left: 4px;
|
|
378
402
|
|
|
379
403
|
&:focus-visible {
|
|
380
404
|
@include focus-outline;
|
|
@@ -54,4 +54,55 @@ describe('component: LabeledInput', () => {
|
|
|
54
54
|
expect(subLabel.text()).toBe(hint);
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
|
+
|
|
58
|
+
describe('a11y: adding ARIA props', () => {
|
|
59
|
+
const ariaLabelVal = 'some-aria-label';
|
|
60
|
+
const subLabelVal = 'some-sublabel';
|
|
61
|
+
const ariaDescribedByIdVal = 'some-external-id';
|
|
62
|
+
const ariaRequiredVal = 'true';
|
|
63
|
+
|
|
64
|
+
it.each([
|
|
65
|
+
['text', 'input', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
|
|
66
|
+
['cron', 'input', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
|
|
67
|
+
['multiline', 'textarea', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
|
|
68
|
+
['multiline-password', 'textarea', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
|
|
69
|
+
])('for type %p should correctly fill out the appropriate fields on the component', (type, validationType, ariaLabel, subLabel, ariaDescribedById) => {
|
|
70
|
+
const wrapper = mount(LabeledInput, {
|
|
71
|
+
propsData: {
|
|
72
|
+
value: '', type, ariaLabel, subLabel, required: true, mode: 'view'
|
|
73
|
+
},
|
|
74
|
+
attrs: { 'aria-describedby': ariaDescribedById },
|
|
75
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const field = wrapper.find(validationType);
|
|
79
|
+
const ariaLabelProp = field.attributes('aria-label');
|
|
80
|
+
const ariaDescribedBy = field.attributes('aria-describedby');
|
|
81
|
+
const ariaRequired = field.attributes('aria-required');
|
|
82
|
+
const ariaDisabled = field.attributes('aria-disabled');
|
|
83
|
+
const disabledAttr = field.attributes('disabled');
|
|
84
|
+
|
|
85
|
+
// validates type of input rendered
|
|
86
|
+
expect(field.exists()).toBe(true);
|
|
87
|
+
expect(ariaLabelProp).toBe(ariaLabel);
|
|
88
|
+
expect(ariaDescribedBy).toBe(`${ ariaDescribedById } ${ wrapper.vm.describedById }`);
|
|
89
|
+
expect(ariaRequired).toBe(ariaRequiredVal);
|
|
90
|
+
expect(ariaDisabled).toBe('true');
|
|
91
|
+
expect(disabledAttr).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('a11y: rendering a "label" should not render an "aria-label" prop', () => {
|
|
96
|
+
const label = 'some-label';
|
|
97
|
+
|
|
98
|
+
const wrapper = mount(LabeledInput, {
|
|
99
|
+
propsData: { type: 'text', label },
|
|
100
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const mainInput = wrapper.find('input[type="text"]');
|
|
104
|
+
|
|
105
|
+
expect(mainInput.attributes('aria-label')).toBeUndefined();
|
|
106
|
+
expect(wrapper.find('label').text()).toBe(label);
|
|
107
|
+
});
|
|
57
108
|
});
|
|
@@ -161,6 +161,19 @@ export default defineComponent({
|
|
|
161
161
|
return this.isCompact ? false : !!this.label || !!this.labelKey || !!this.$slots.label;
|
|
162
162
|
},
|
|
163
163
|
|
|
164
|
+
ariaDescribedBy(): string | undefined {
|
|
165
|
+
const inheritedDescribedBy = this.$attrs['aria-describedby'];
|
|
166
|
+
const internalDescribedBy = this.cronHint || this.subLabel ? this.describedById : undefined;
|
|
167
|
+
|
|
168
|
+
if (inheritedDescribedBy && internalDescribedBy) {
|
|
169
|
+
return `${ inheritedDescribedBy } ${ internalDescribedBy }`;
|
|
170
|
+
} else if (inheritedDescribedBy || internalDescribedBy) {
|
|
171
|
+
return `${ inheritedDescribedBy || internalDescribedBy }`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return undefined;
|
|
175
|
+
},
|
|
176
|
+
|
|
164
177
|
/**
|
|
165
178
|
* Determines if the Labeled Input should display a tooltip.
|
|
166
179
|
*/
|
|
@@ -362,6 +375,7 @@ export default defineComponent({
|
|
|
362
375
|
<span
|
|
363
376
|
v-if="requiredField"
|
|
364
377
|
class="required"
|
|
378
|
+
:aria-hidden="true"
|
|
365
379
|
>*</span>
|
|
366
380
|
</label>
|
|
367
381
|
</slot>
|
|
@@ -377,11 +391,13 @@ export default defineComponent({
|
|
|
377
391
|
v-stripped-aria-label="!hasLabel && ariaLabel ? ariaLabel : undefined"
|
|
378
392
|
:maxlength="_maxlength"
|
|
379
393
|
:disabled="isDisabled"
|
|
394
|
+
:aria-disabled="isDisabled"
|
|
380
395
|
:value="value || ''"
|
|
381
396
|
:placeholder="_placeholder"
|
|
382
397
|
autocapitalize="off"
|
|
383
398
|
:class="{ conceal: type === 'multiline-password' }"
|
|
384
|
-
:aria-describedby="
|
|
399
|
+
:aria-describedby="ariaDescribedBy"
|
|
400
|
+
:aria-required="requiredField"
|
|
385
401
|
@update:value="onInput"
|
|
386
402
|
@focus="onFocus"
|
|
387
403
|
@blur="onBlur"
|
|
@@ -396,13 +412,15 @@ export default defineComponent({
|
|
|
396
412
|
v-bind="$attrs"
|
|
397
413
|
:maxlength="_maxlength"
|
|
398
414
|
:disabled="isDisabled"
|
|
415
|
+
:aria-disabled="isDisabled"
|
|
399
416
|
:type="type === 'cron' ? 'text' : type"
|
|
400
417
|
:value="value"
|
|
401
418
|
:placeholder="_placeholder"
|
|
402
419
|
autocomplete="off"
|
|
403
420
|
autocapitalize="off"
|
|
404
421
|
:data-lpignore="ignorePasswordManagers"
|
|
405
|
-
:aria-describedby="
|
|
422
|
+
:aria-describedby="ariaDescribedBy"
|
|
423
|
+
:aria-required="requiredField"
|
|
406
424
|
@input="onInput"
|
|
407
425
|
@focus="onFocus"
|
|
408
426
|
@blur="onBlur"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallowMount } from '@vue/test-utils';
|
|
1
|
+
import { shallowMount, mount } from '@vue/test-utils';
|
|
2
2
|
import { RadioButton } from './index';
|
|
3
3
|
|
|
4
4
|
describe('radioButton.vue', () => {
|
|
@@ -30,4 +30,39 @@ describe('radioButton.vue', () => {
|
|
|
30
30
|
|
|
31
31
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label - Slot');
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
|
|
35
|
+
const val = 'foo';
|
|
36
|
+
const value = 'foo';
|
|
37
|
+
const description = 'some-description';
|
|
38
|
+
const itemLabel = 'some-label';
|
|
39
|
+
const radioOptionId = 'some-id-from-parent';
|
|
40
|
+
|
|
41
|
+
const wrapper = mount(
|
|
42
|
+
RadioButton,
|
|
43
|
+
{
|
|
44
|
+
propsData: {
|
|
45
|
+
label: itemLabel,
|
|
46
|
+
val,
|
|
47
|
+
value,
|
|
48
|
+
description,
|
|
49
|
+
radioOptionId
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const radioInputElem = wrapper.find('span[role="radio"]');
|
|
54
|
+
const role = radioInputElem.attributes('role');
|
|
55
|
+
const ariaLabel = radioInputElem.attributes('aria-label');
|
|
56
|
+
const ariaChecked = radioInputElem.attributes('aria-checked');
|
|
57
|
+
const ariaDisabled = radioInputElem.attributes('aria-disabled');
|
|
58
|
+
const ariaDescribedBy = radioInputElem.attributes('aria-describedby');
|
|
59
|
+
const itemId = radioInputElem.attributes('id');
|
|
60
|
+
|
|
61
|
+
expect(role).toBe('radio');
|
|
62
|
+
expect(ariaLabel).toBe(itemLabel);
|
|
63
|
+
expect(ariaChecked).toBe('true');
|
|
64
|
+
expect(ariaDisabled).toBe('false');
|
|
65
|
+
expect(ariaDescribedBy).toBe(wrapper.vm.describeById);
|
|
66
|
+
expect(itemId).toBe(radioOptionId);
|
|
67
|
+
});
|
|
33
68
|
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { defineComponent } from 'vue';
|
|
3
3
|
import { _VIEW } from '@shell/config/query-params';
|
|
4
|
-
import {
|
|
4
|
+
import { generateRandomAlphaString } from '@shell/utils/string';
|
|
5
5
|
|
|
6
6
|
export default defineComponent({
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
props: {
|
|
8
10
|
/**
|
|
9
11
|
* The name of the input, for grouping.
|
|
10
12
|
*/
|
|
@@ -76,7 +78,16 @@ export default defineComponent({
|
|
|
76
78
|
preventFocusOnRadioGroups: {
|
|
77
79
|
type: Boolean,
|
|
78
80
|
default: false
|
|
79
|
-
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Radio option Id - used to link to aria-activedescendant
|
|
85
|
+
* when using inside of the context of a Radio Group
|
|
86
|
+
*/
|
|
87
|
+
radioOptionId: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: undefined
|
|
90
|
+
},
|
|
80
91
|
},
|
|
81
92
|
|
|
82
93
|
emits: ['update:value'],
|
|
@@ -84,7 +95,8 @@ export default defineComponent({
|
|
|
84
95
|
data() {
|
|
85
96
|
return {
|
|
86
97
|
isChecked: this.value === this.val,
|
|
87
|
-
randomString: `${
|
|
98
|
+
randomString: `${ generateRandomAlphaString(12) }-radio`,
|
|
99
|
+
describeById: `${ generateRandomAlphaString(12) }-radio-described-id`,
|
|
88
100
|
};
|
|
89
101
|
},
|
|
90
102
|
|
|
@@ -165,11 +177,14 @@ export default defineComponent({
|
|
|
165
177
|
@click.stop.prevent
|
|
166
178
|
>
|
|
167
179
|
<span
|
|
180
|
+
:id="radioOptionId"
|
|
168
181
|
ref="custom"
|
|
169
182
|
:class="[ isDisabled ? 'text-muted' : '', 'radio-custom']"
|
|
170
183
|
:tabindex="isDisabled || preventFocusOnRadioGroups ? -1 : 0"
|
|
171
184
|
:aria-label="label"
|
|
172
185
|
:aria-checked="isChecked"
|
|
186
|
+
:aria-disabled="isDisabled"
|
|
187
|
+
:aria-describedby="descriptionKey || description ? describeById : undefined"
|
|
173
188
|
role="radio"
|
|
174
189
|
/>
|
|
175
190
|
<div class="labeling">
|
|
@@ -190,6 +205,7 @@ export default defineComponent({
|
|
|
190
205
|
</label>
|
|
191
206
|
<div
|
|
192
207
|
v-if="descriptionKey || description"
|
|
208
|
+
:id="describeById"
|
|
193
209
|
class="radio-button-outer-container-description"
|
|
194
210
|
>
|
|
195
211
|
<t
|
|
@@ -24,4 +24,64 @@ describe('component: RadioGroup', () => {
|
|
|
24
24
|
expect(slot.disabled).toBe(disabled);
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
|
|
29
|
+
const inputLabel = 'some-label';
|
|
30
|
+
const ariaDescribedById = 'some-external-id';
|
|
31
|
+
const currValue = 'whatever';
|
|
32
|
+
|
|
33
|
+
const wrapper = mount(RadioGroup, {
|
|
34
|
+
propsData: {
|
|
35
|
+
name: 'some-name',
|
|
36
|
+
label: inputLabel,
|
|
37
|
+
value: currValue,
|
|
38
|
+
options: [{ label: currValue, value: currValue }]
|
|
39
|
+
},
|
|
40
|
+
attrs: { 'aria-describedby': ariaDescribedById }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const field = wrapper.find('[role="radiogroup"]');
|
|
44
|
+
const role = field.attributes('role');
|
|
45
|
+
const ariaLabel = field.attributes('aria-label');
|
|
46
|
+
const ariaDescribedBy = field.attributes('aria-describedby');
|
|
47
|
+
const ariaActiveDescendant = field.attributes('aria-activedescendant');
|
|
48
|
+
|
|
49
|
+
expect(ariaLabel).toBe(inputLabel);
|
|
50
|
+
expect(role).toBe('radiogroup');
|
|
51
|
+
expect(ariaActiveDescendant).toBe(`${ wrapper.vm.radioOptionsIdPrefix }0`);
|
|
52
|
+
expect(ariaDescribedBy).toBe(ariaDescribedById);
|
|
53
|
+
|
|
54
|
+
const radioOption = wrapper.find(`.radio-custom`);
|
|
55
|
+
|
|
56
|
+
// make sure we validate when using RadioGroup without custom slot data
|
|
57
|
+
// we do assign an ID that is important to get 'aria-activedescendant' working
|
|
58
|
+
expect(radioOption.attributes('id')).toBe(`${ wrapper.vm.radioOptionsIdPrefix }0`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('a11y: adding aria-label ($attrs) from parent should override label-based aria-label', async() => {
|
|
62
|
+
const inputLabel = 'some-label';
|
|
63
|
+
const overrideLabel = 'some-override-label';
|
|
64
|
+
const currValue = 'whatever';
|
|
65
|
+
|
|
66
|
+
const wrapper = mount(RadioGroup, {
|
|
67
|
+
propsData: {
|
|
68
|
+
name: 'some-name',
|
|
69
|
+
label: inputLabel,
|
|
70
|
+
value: currValue,
|
|
71
|
+
disabled: true,
|
|
72
|
+
options: [{ label: currValue, value: currValue }]
|
|
73
|
+
},
|
|
74
|
+
attrs: { 'aria-label': overrideLabel }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const field = wrapper.find('[role="radiogroup"]');
|
|
78
|
+
const ariaLabel = field.attributes('aria-label');
|
|
79
|
+
const ariaDisabled = field.attributes('aria-disabled');
|
|
80
|
+
const tabIndex = field.attributes('tabindex');
|
|
81
|
+
|
|
82
|
+
expect(ariaLabel).toBe(overrideLabel);
|
|
83
|
+
expect(ariaLabel).not.toBe(inputLabel);
|
|
84
|
+
expect(ariaDisabled).toBe('true');
|
|
85
|
+
expect(tabIndex).toBe('-1');
|
|
86
|
+
});
|
|
27
87
|
});
|