Commit 44e251d6 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch...

Merge branch '339292-improve-accessibility-of-app-assets-javascripts-vue_shared-components-settings' into 'master'

Clean up `settings_block.vue`

See merge request gitlab-org/gitlab!68921
parents d981488c d857e904
...@@ -88,7 +88,7 @@ export default { ...@@ -88,7 +88,7 @@ export default {
<template> <template>
<section data-testid="registry-settings-app"> <section data-testid="registry-settings-app">
<cleanup-policy-enabled-alert v-if="showCleanupPolicyOnAlert" :project-path="projectPath" /> <cleanup-policy-enabled-alert v-if="showCleanupPolicyOnAlert" :project-path="projectPath" />
<settings-block default-expanded> <settings-block :collapsible="false">
<template #title> {{ __('Clean up image tags') }}</template> <template #title> {{ __('Clean up image tags') }}</template>
<template #description> <template #description>
<span data-testid="description"> <span data-testid="description">
......
import SettingsBlock from './settings_block.vue';
export default {
component: SettingsBlock,
title: 'vue_shared/components/settings/settings_block',
};
const Template = (args, { argTypes }) => ({
components: { SettingsBlock },
props: Object.keys(argTypes),
template: `
<settings-block v-bind="$props">
<template #title>Settings section title</template>
<template #description>Settings section description</template>
<template #default>
<p>Content</p>
<p>More content</p>
<p>Content</p>
<p>More content...</p>
<p>Content</p>
</template>
</settings-block>
`,
});
export const Default = Template.bind({});
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default { export default {
...@@ -15,50 +17,99 @@ export default { ...@@ -15,50 +17,99 @@ export default {
default: false, default: false,
required: false, required: false,
}, },
collapsible: {
type: Boolean,
default: true,
required: false,
},
}, },
data() { data() {
return { return {
sectionExpanded: false, // Non-collapsible sections should always be expanded.
// For collapsible sections, fall back to defaultExpanded.
sectionExpanded: !this.collapsible || this.defaultExpanded,
}; };
}, },
computed: { computed: {
expanded() {
return this.defaultExpanded || this.sectionExpanded;
},
toggleText() { toggleText() {
return this.expanded ? __('Collapse') : __('Expand'); const { collapseText, expandText } = this.$options.i18n;
return this.sectionExpanded ? collapseText : expandText;
},
settingsContentId() {
return uniqueId('settings_content_');
},
settingsLabelId() {
return uniqueId('settings_label_');
},
toggleButtonAriaLabel() {
const { collapseAriaLabel, expandAriaLabel } = this.$options.i18n;
return this.sectionExpanded ? collapseAriaLabel : expandAriaLabel;
},
ariaExpanded() {
return String(this.sectionExpanded);
}, },
}, },
methods: { methods: {
toggleSectionExpanded() { toggleSectionExpanded() {
this.sectionExpanded = !this.sectionExpanded; this.sectionExpanded = !this.sectionExpanded;
if (this.sectionExpanded) {
this.$refs.settingsContent.focus();
}
},
}, },
i18n: {
collapseText: __('Collapse'),
expandText: __('Expand'),
collapseAriaLabel: __('Collapse settings section'),
expandAriaLabel: __('Expand settings section'),
}, },
}; };
</script> </script>
<template> <template>
<section class="settings" :class="{ 'no-animate': !slideAnimated, expanded }"> <section class="settings" :class="{ 'no-animate': !slideAnimated, expanded: sectionExpanded }">
<div class="settings-header"> <div class="settings-header">
<h4> <h4>
<span <span
v-if="collapsible"
:id="settingsLabelId"
role="button" role="button"
tabindex="0" tabindex="0"
class="gl-cursor-pointer" class="gl-cursor-pointer"
data-testid="section-title" :aria-controls="settingsContentId"
:aria-expanded="ariaExpanded"
data-testid="section-title-button"
@click="toggleSectionExpanded" @click="toggleSectionExpanded"
@keydown.enter.space="toggleSectionExpanded"
> >
<slot name="title"></slot> <slot name="title"></slot>
</span> </span>
<template v-else>
<slot name="title"></slot>
</template>
</h4> </h4>
<gl-button @click="toggleSectionExpanded"> <gl-button
v-if="collapsible"
:aria-controls="settingsContentId"
:aria-expanded="ariaExpanded"
:aria-label="toggleButtonAriaLabel"
@click="toggleSectionExpanded"
>
{{ toggleText }} {{ toggleText }}
</gl-button> </gl-button>
<p> <p>
<slot name="description"></slot> <slot name="description"></slot>
</p> </p>
</div> </div>
<div class="settings-content"> <div
:id="settingsContentId"
ref="settingsContent"
:aria-labelledby="settingsLabelId"
tabindex="-1"
role="region"
class="settings-content"
>
<slot></slot> <slot></slot>
</div> </div>
</section> </section>
......
...@@ -8140,6 +8140,9 @@ msgstr "" ...@@ -8140,6 +8140,9 @@ msgstr ""
msgid "Collapse replies" msgid "Collapse replies"
msgstr "" msgstr ""
msgid "Collapse settings section"
msgstr ""
msgid "Collapse sidebar" msgid "Collapse sidebar"
msgstr "" msgstr ""
...@@ -13686,6 +13689,9 @@ msgstr "" ...@@ -13686,6 +13689,9 @@ msgstr ""
msgid "Expand pipeline" msgid "Expand pipeline"
msgstr "" msgstr ""
msgid "Expand settings section"
msgstr ""
msgid "Expand sidebar" msgid "Expand sidebar"
msgstr "" msgstr ""
......
...@@ -9,8 +9,11 @@ exports[`Settings Block renders the correct markup 1`] = ` ...@@ -9,8 +9,11 @@ exports[`Settings Block renders the correct markup 1`] = `
> >
<h4> <h4>
<span <span
aria-controls="settings_content_3"
aria-expanded="false"
class="gl-cursor-pointer" class="gl-cursor-pointer"
data-testid="section-title" data-testid="section-title-button"
id="settings_label_2"
role="button" role="button"
tabindex="0" tabindex="0"
> >
...@@ -21,6 +24,9 @@ exports[`Settings Block renders the correct markup 1`] = ` ...@@ -21,6 +24,9 @@ exports[`Settings Block renders the correct markup 1`] = `
</h4> </h4>
<gl-button-stub <gl-button-stub
aria-controls="settings_content_3"
aria-expanded="false"
aria-label="Expand settings section"
buttontextclasses="" buttontextclasses=""
category="primary" category="primary"
icon="" icon=""
...@@ -40,7 +46,11 @@ exports[`Settings Block renders the correct markup 1`] = ` ...@@ -40,7 +46,11 @@ exports[`Settings Block renders the correct markup 1`] = `
</div> </div>
<div <div
aria-labelledby="settings_label_2"
class="settings-content" class="settings-content"
id="settings_content_3"
role="region"
tabindex="-1"
> >
<div <div
data-testid="default-slot" data-testid="default-slot"
......
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import component from '~/vue_shared/components/settings/settings_block.vue'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
describe('Settings Block', () => { describe('Settings Block', () => {
let wrapper; let wrapper;
const mountComponent = (propsData) => { const mountComponent = (propsData) => {
wrapper = shallowMount(component, { wrapper = shallowMount(SettingsBlock, {
propsData, propsData,
slots: { slots: {
title: '<div data-testid="title-slot"></div>', title: '<div data-testid="title-slot"></div>',
...@@ -24,7 +24,19 @@ describe('Settings Block', () => { ...@@ -24,7 +24,19 @@ describe('Settings Block', () => {
const findTitleSlot = () => wrapper.find('[data-testid="title-slot"]'); const findTitleSlot = () => wrapper.find('[data-testid="title-slot"]');
const findDescriptionSlot = () => wrapper.find('[data-testid="description-slot"]'); const findDescriptionSlot = () => wrapper.find('[data-testid="description-slot"]');
const findExpandButton = () => wrapper.findComponent(GlButton); const findExpandButton = () => wrapper.findComponent(GlButton);
const findSectionTitle = () => wrapper.find('[data-testid="section-title"]'); const findSectionTitleButton = () => wrapper.find('[data-testid="section-title-button"]');
const expectExpandedState = ({ expanded = true } = {}) => {
const settingsExpandButton = findExpandButton();
expect(wrapper.classes('expanded')).toBe(expanded);
expect(settingsExpandButton.text()).toBe(
expanded ? SettingsBlock.i18n.collapseText : SettingsBlock.i18n.expandText,
);
expect(settingsExpandButton.attributes('aria-label')).toBe(
expanded ? SettingsBlock.i18n.collapseAriaLabel : SettingsBlock.i18n.expandAriaLabel,
);
};
it('renders the correct markup', () => { it('renders the correct markup', () => {
mountComponent(); mountComponent();
...@@ -75,45 +87,41 @@ describe('Settings Block', () => { ...@@ -75,45 +87,41 @@ describe('Settings Block', () => {
it('is collapsed by default', () => { it('is collapsed by default', () => {
mountComponent(); mountComponent();
expect(wrapper.classes('expanded')).toBe(false); expectExpandedState({ expanded: false });
}); });
it('adds expanded class when the expand button is clicked', async () => { it('adds expanded class when the expand button is clicked', async () => {
mountComponent(); mountComponent();
expect(wrapper.classes('expanded')).toBe(false);
expect(findExpandButton().text()).toBe('Expand');
await findExpandButton().vm.$emit('click'); await findExpandButton().vm.$emit('click');
expect(wrapper.classes('expanded')).toBe(true); expectExpandedState({ expanded: true });
expect(findExpandButton().text()).toBe('Collapse');
}); });
it('adds expanded class when the section title is clicked', async () => { it('adds expanded class when the section title is clicked', async () => {
mountComponent(); mountComponent();
expect(wrapper.classes('expanded')).toBe(false); await findSectionTitleButton().trigger('click');
expect(findExpandButton().text()).toBe('Expand');
await findSectionTitle().trigger('click');
expect(wrapper.classes('expanded')).toBe(true); expectExpandedState({ expanded: true });
expect(findExpandButton().text()).toBe('Collapse');
}); });
it('is expanded when `defaultExpanded` is true no matter what', async () => { describe('when `collapsible` is `false`', () => {
mountComponent({ defaultExpanded: true }); beforeEach(() => {
mountComponent({ collapsible: false });
expect(wrapper.classes('expanded')).toBe(true); });
await findExpandButton().vm.$emit('click'); it('does not render clickable section title', () => {
expect(findSectionTitleButton().exists()).toBe(false);
});
it('contains expanded class', () => {
expect(wrapper.classes('expanded')).toBe(true); expect(wrapper.classes('expanded')).toBe(true);
});
await findExpandButton().vm.$emit('click'); it('does not render expand toggle button', () => {
expect(findExpandButton().exists()).toBe(false);
expect(wrapper.classes('expanded')).toBe(true); });
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment