Commit 07bd61e6 authored by Tom Quirk's avatar Tom Quirk Committed by Natalia Tepluhina

Create SSoT for issuable sidebar toggling

parent 6446b6aa
...@@ -153,9 +153,9 @@ export default { ...@@ -153,9 +153,9 @@ export default {
</template> </template>
</issuable-discussion> </issuable-discussion>
<issuable-sidebar @sidebar-toggle="$emit('sidebar-toggle', $event)"> <issuable-sidebar>
<template #right-sidebar-items="sidebarProps"> <template #right-sidebar-items="{ sidebarExpanded, toggleSidebar }">
<slot name="right-sidebar-items" v-bind="sidebarProps"></slot> <slot name="right-sidebar-items" v-bind="{ sidebarExpanded, toggleSidebar }"></slot>
</template> </template>
</issuable-sidebar> </issuable-sidebar>
</div> </div>
......
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { USER_COLLAPSED_GUTTER_COOKIE } from '../constants';
export default { export default {
components: { components: {
GlIcon, GlIcon,
}, },
data() { data() {
const userExpanded = !parseBoolean(Cookies.get('collapsed_gutter')); const userExpanded = !parseBoolean(Cookies.get(USER_COLLAPSED_GUTTER_COOKIE));
// We're deliberately keeping two different props for sidebar status; // We're deliberately keeping two different props for sidebar status;
// 1. userExpanded reflects value based on cookie `collapsed_gutter`. // 1. userExpanded reflects value based on cookie `collapsed_gutter`.
...@@ -20,13 +20,6 @@ export default { ...@@ -20,13 +20,6 @@ export default {
isExpanded: userExpanded ? bp.isDesktop() : userExpanded, isExpanded: userExpanded ? bp.isDesktop() : userExpanded,
}; };
}, },
watch: {
isExpanded(expanded) {
this.$emit('sidebar-toggle', {
expanded,
});
},
},
mounted() { mounted() {
window.addEventListener('resize', this.handleWindowResize); window.addEventListener('resize', this.handleWindowResize);
this.updatePageContainerClass(); this.updatePageContainerClass();
...@@ -49,11 +42,11 @@ export default { ...@@ -49,11 +42,11 @@ export default {
this.updatePageContainerClass(); this.updatePageContainerClass();
} }
}, },
handleToggleSidebarClick() { toggleSidebar() {
this.isExpanded = !this.isExpanded; this.isExpanded = !this.isExpanded;
this.userExpanded = this.isExpanded; this.userExpanded = this.isExpanded;
Cookies.set('collapsed_gutter', !this.userExpanded); Cookies.set(USER_COLLAPSED_GUTTER_COOKIE, !this.userExpanded);
this.updatePageContainerClass(); this.updatePageContainerClass();
}, },
}, },
...@@ -68,8 +61,9 @@ export default { ...@@ -68,8 +61,9 @@ export default {
> >
<button <button
class="toggle-right-sidebar-button js-toggle-right-sidebar-button w-100 gl-text-decoration-none! gl-display-flex gl-outline-0!" class="toggle-right-sidebar-button js-toggle-right-sidebar-button w-100 gl-text-decoration-none! gl-display-flex gl-outline-0!"
data-testid="toggle-right-sidebar-button"
:title="__('Toggle sidebar')" :title="__('Toggle sidebar')"
@click="handleToggleSidebarClick" @click="toggleSidebar"
> >
<span v-if="isExpanded" class="collapse-text gl-flex-grow-1 gl-text-left">{{ <span v-if="isExpanded" class="collapse-text gl-flex-grow-1 gl-text-left">{{
__('Collapse sidebar') __('Collapse sidebar')
...@@ -83,7 +77,10 @@ export default { ...@@ -83,7 +77,10 @@ export default {
/> />
</button> </button>
<div data-testid="sidebar-items" class="issuable-sidebar"> <div data-testid="sidebar-items" class="issuable-sidebar">
<slot name="right-sidebar-items" v-bind="{ sidebarExpanded: isExpanded }"></slot> <slot
name="right-sidebar-items"
v-bind="{ sidebarExpanded: isExpanded, toggleSidebar }"
></slot>
</div> </div>
</aside> </aside>
</template> </template>
export const USER_COLLAPSED_GUTTER_COOKIE = 'collapsed_gutter';
...@@ -174,7 +174,7 @@ export default { ...@@ -174,7 +174,7 @@ export default {
> >
<template #status-badge>{{ statusBadgeText }}</template> <template #status-badge>{{ statusBadgeText }}</template>
<template #right-sidebar-items="{ sidebarExpanded }"> <template #right-sidebar-items="{ sidebarExpanded, toggleSidebar }">
<jira-issue-sidebar <jira-issue-sidebar
:sidebar-expanded="sidebarExpanded" :sidebar-expanded="sidebarExpanded"
:issue="issue" :issue="issue"
...@@ -185,6 +185,7 @@ export default { ...@@ -185,6 +185,7 @@ export default {
@issue-labels-updated="onIssueLabelsUpdated" @issue-labels-updated="onIssueLabelsUpdated"
@issue-status-fetch="onIssueStatusFetch" @issue-status-fetch="onIssueStatusFetch"
@issue-status-updated="onIssueStatusUpdated" @issue-status-updated="onIssueStatusUpdated"
@sidebar-toggle="toggleSidebar"
/> />
</template> </template>
......
...@@ -85,11 +85,10 @@ export default { ...@@ -85,11 +85,10 @@ export default {
}, },
mounted() { mounted() {
this.sidebarEl = document.querySelector('aside.right-sidebar'); this.sidebarEl = document.querySelector('aside.right-sidebar');
this.sidebarToggleEl = document.querySelector('.js-toggle-right-sidebar-button');
}, },
methods: { methods: {
toggleSidebar() { toggleSidebar() {
this.sidebarToggleEl.dispatchEvent(new Event('click')); this.$emit('sidebar-toggle');
}, },
afterSidebarTransitioned(callback) { afterSidebarTransitioned(callback) {
// Wait for sidebar expand animation to complete // Wait for sidebar expand animation to complete
......
...@@ -229,13 +229,14 @@ export default { ...@@ -229,13 +229,14 @@ export default {
{{ __('Cancel') }} {{ __('Cancel') }}
</gl-button> </gl-button>
</template> </template>
<template #right-sidebar-items="{ sidebarExpanded }"> <template #right-sidebar-items="{ sidebarExpanded, toggleSidebar }">
<test-case-sidebar <test-case-sidebar
:sidebar-expanded="sidebarExpanded" :sidebar-expanded="sidebarExpanded"
:selected-labels="selectedLabels" :selected-labels="selectedLabels"
:todo="todo" :todo="todo"
:moved="testCase.moved" :moved="testCase.moved"
@test-case-updated="handleTestCaseUpdated" @test-case-updated="handleTestCaseUpdated"
@sidebar-toggle="toggleSidebar"
/> />
</template> </template>
</issuable-show> </issuable-show>
......
...@@ -91,7 +91,7 @@ export default { ...@@ -91,7 +91,7 @@ export default {
} }
}, },
toggleSidebar() { toggleSidebar() {
document.querySelector('.js-toggle-right-sidebar-button').dispatchEvent(new Event('click')); this.$emit('sidebar-toggle');
}, },
expandSidebarAndOpenDropdown(dropdownButtonSelector) { expandSidebarAndOpenDropdown(dropdownButtonSelector) {
// Expand the sidebar if not already expanded. // Expand the sidebar if not already expanded.
......
...@@ -167,5 +167,15 @@ describe('JiraIssuesShow', () => { ...@@ -167,5 +167,15 @@ describe('JiraIssuesShow', () => {
expect(findJiraIssueSidebar().props('isUpdatingStatus')).toBe(false); expect(findJiraIssueSidebar().props('isUpdatingStatus')).toBe(false);
}); });
it('updates `sidebarExpanded` prop on `sidebar-toggle` event', async () => {
const jiraIssueSidebar = findJiraIssueSidebar();
expect(jiraIssueSidebar.props('sidebarExpanded')).toBe(true);
jiraIssueSidebar.vm.$emit('sidebar-toggle');
await wrapper.vm.$nextTick();
expect(jiraIssueSidebar.props('sidebarExpanded')).toBe(false);
});
}); });
}); });
...@@ -55,6 +55,8 @@ const createComponent = ({ testCase, testCaseQueryLoading = false } = {}) => ...@@ -55,6 +55,8 @@ const createComponent = ({ testCase, testCaseQueryLoading = false } = {}) =>
describe('TestCaseShowRoot', () => { describe('TestCaseShowRoot', () => {
let wrapper; let wrapper;
const findTestCaseSidebar = () => wrapper.findComponent(TestCaseSidebar);
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
}); });
...@@ -361,7 +363,17 @@ describe('TestCaseShowRoot', () => { ...@@ -361,7 +363,17 @@ describe('TestCaseShowRoot', () => {
}); });
it('renders test-case-sidebar', async () => { it('renders test-case-sidebar', async () => {
expect(wrapper.find(TestCaseSidebar).exists()).toBe(true); expect(findTestCaseSidebar().exists()).toBe(true);
});
it('updates `sidebarExpanded` prop on `sidebar-toggle` event', async () => {
const testCaseSidebar = findTestCaseSidebar();
expect(testCaseSidebar.props('sidebarExpanded')).toBe(true);
testCaseSidebar.vm.$emit('sidebar-toggle');
await wrapper.vm.$nextTick();
expect(testCaseSidebar.props('sidebarExpanded')).toBe(false);
}); });
}); });
}); });
...@@ -139,17 +139,9 @@ describe('TestCaseSidebar', () => { ...@@ -139,17 +139,9 @@ describe('TestCaseSidebar', () => {
}); });
it('dispatches click event on sidebar toggle button', () => { it('dispatches click event on sidebar toggle button', () => {
const buttonEl = document.querySelector('.js-toggle-right-sidebar-button');
jest.spyOn(buttonEl, 'dispatchEvent');
wrapper.vm.toggleSidebar(); wrapper.vm.toggleSidebar();
expect(buttonEl.dispatchEvent).toHaveBeenCalledWith( expect(wrapper.emitted('sidebar-toggle')).toBeDefined();
expect.objectContaining({
type: 'click',
}),
);
}); });
}); });
......
...@@ -133,14 +133,6 @@ describe('IssuableShowRoot', () => { ...@@ -133,14 +133,6 @@ describe('IssuableShowRoot', () => {
expect(wrapper.emitted('task-list-update-failure')).toBeTruthy(); expect(wrapper.emitted('task-list-update-failure')).toBeTruthy();
}); });
it('component emits `sidebar-toggle` event bubbled via issuable-sidebar', () => {
const issuableSidebar = wrapper.find(IssuableSidebar);
issuableSidebar.vm.$emit('sidebar-toggle', true);
expect(wrapper.emitted('sidebar-toggle')).toBeTruthy();
});
it.each(['keydown-title', 'keydown-description'])( it.each(['keydown-title', 'keydown-description'])(
'component emits `%s` event with event object and issuableMeta params via issuable-body', 'component emits `%s` event with event object and issuableMeta params via issuable-body',
(eventName) => { (eventName) => {
......
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import IssuableSidebarRoot from '~/issuable_sidebar/components/issuable_sidebar_root.vue'; import IssuableSidebarRoot from '~/issuable_sidebar/components/issuable_sidebar_root.vue';
import { USER_COLLAPSED_GUTTER_COOKIE } from '~/issuable_sidebar/constants';
const createComponent = (expanded = true) => const MOCK_LAYOUT_PAGE_CLASS = 'layout-page';
shallowMount(IssuableSidebarRoot, {
propsData: { const createComponent = () => {
expanded, setFixtures(`<div class="${MOCK_LAYOUT_PAGE_CLASS}"></div>`);
},
return shallowMountExtended(IssuableSidebarRoot, {
slots: { slots: {
'right-sidebar-items': ` 'right-sidebar-items': `
<button class="js-todo">Todo</button> <button class="js-todo">Todo</button>
`, `,
}, },
}); });
};
describe('IssuableSidebarRoot', () => { describe('IssuableSidebarRoot', () => {
let wrapper; let wrapper;
beforeEach(() => { const findToggleSidebarButton = () => wrapper.findByTestId('toggle-right-sidebar-button');
wrapper = createComponent();
}); const assertPageLayoutClasses = ({ isExpanded }) => {
const { classList } = document.querySelector(`.${MOCK_LAYOUT_PAGE_CLASS}`);
if (isExpanded) {
expect(classList).toContain('right-sidebar-expanded');
expect(classList).not.toContain('right-sidebar-collapsed');
} else {
expect(classList).toContain('right-sidebar-collapsed');
expect(classList).not.toContain('right-sidebar-expanded');
}
};
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('watch', () => { describe('when sidebar is expanded', () => {
describe('isExpanded', () => { beforeEach(() => {
it('emits `sidebar-toggle` event on component', async () => { jest.spyOn(Cookies, 'set').mockImplementation(jest.fn());
wrapper.setData({ jest.spyOn(Cookies, 'get').mockReturnValue(false);
isExpanded: false, jest.spyOn(bp, 'isDesktop').mockReturnValue(true);
});
await wrapper.vm.$nextTick();
expect(wrapper.emitted('sidebar-toggle')).toBeTruthy(); wrapper = createComponent();
expect(wrapper.emitted('sidebar-toggle')[0]).toEqual([
{
expanded: false,
},
]);
});
});
}); });
describe('methods', () => { it('renders component container element with class `right-sidebar-expanded`', () => {
describe('updatePageContainerClass', () => { expect(wrapper.classes()).toContain('right-sidebar-expanded');
beforeEach(() => {
setFixtures('<div class="layout-page"></div>');
}); });
it.each` it('sets layout class to reflect expanded state', () => {
isExpanded | layoutPageClass assertPageLayoutClasses({ isExpanded: true });
${true} | ${'right-sidebar-expanded'}
${false} | ${'right-sidebar-collapsed'}
`(
'set class $layoutPageClass to container element when `isExpanded` prop is $isExpanded',
async ({ isExpanded, layoutPageClass }) => {
wrapper.setData({
isExpanded,
}); });
await wrapper.vm.$nextTick(); it('renders sidebar toggle button with text and icon', () => {
const buttonEl = findToggleSidebarButton();
wrapper.vm.updatePageContainerClass();
expect(document.querySelector('.layout-page').classList.contains(layoutPageClass)).toBe( expect(buttonEl.exists()).toBe(true);
true, expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
); expect(buttonEl.find('span').text()).toBe('Collapse sidebar');
}, expect(wrapper.findByTestId('icon-collapse').isVisible()).toBe(true);
);
}); });
describe('handleWindowResize', () => { describe('when collapsing the sidebar', () => {
beforeEach(async () => { it('updates "collapsed_gutter" cookie value and layout classes', async () => {
wrapper.setData({ await findToggleSidebarButton().trigger('click');
userExpanded: true,
});
await wrapper.vm.$nextTick(); expect(Cookies.set).toHaveBeenCalledWith(USER_COLLAPSED_GUTTER_COOKIE, true);
assertPageLayoutClasses({ isExpanded: false });
});
}); });
describe('when window `resize` event is triggered', () => {
it.each` it.each`
breakpoint | isExpandedValue breakpoint | isExpandedValue
${'xs'} | ${false} ${'xs'} | ${false}
...@@ -91,109 +83,49 @@ describe('IssuableSidebarRoot', () => { ...@@ -91,109 +83,49 @@ describe('IssuableSidebarRoot', () => {
${'lg'} | ${true} ${'lg'} | ${true}
${'xl'} | ${true} ${'xl'} | ${true}
`( `(
'sets `isExpanded` prop to $isExpandedValue only when current screen size is `lg` or `xl`', 'sets page layout classes correctly when current screen size is `$breakpoint`',
async ({ breakpoint, isExpandedValue }) => { async ({ breakpoint, isExpandedValue }) => {
jest.spyOn(bp, 'isDesktop').mockReturnValue(breakpoint === 'lg' || breakpoint === 'xl'); jest.spyOn(bp, 'isDesktop').mockReturnValue(breakpoint === 'lg' || breakpoint === 'xl');
wrapper.vm.handleWindowResize(); window.dispatchEvent(new Event('resize'));
await wrapper.vm.$nextTick();
expect(wrapper.vm.isExpanded).toBe(isExpandedValue); assertPageLayoutClasses({ isExpanded: isExpandedValue });
}, },
); );
it('calls `updatePageContainerClass` method', () => {
jest.spyOn(wrapper.vm, 'updatePageContainerClass');
wrapper.vm.handleWindowResize();
expect(wrapper.vm.updatePageContainerClass).toHaveBeenCalled();
}); });
}); });
describe('handleToggleSidebarClick', () => { describe('when sidebar is collapsed', () => {
beforeEach(async () => { beforeEach(() => {
jest.spyOn(Cookies, 'set').mockImplementation(jest.fn()); jest.spyOn(Cookies, 'get').mockReturnValue(true);
wrapper.setData({
isExpanded: true,
});
await wrapper.vm.$nextTick();
});
it('flips value of `isExpanded`', () => {
wrapper.vm.handleToggleSidebarClick();
expect(wrapper.vm.isExpanded).toBe(false);
expect(wrapper.vm.userExpanded).toBe(false);
});
it('updates "collapsed_gutter" cookie value', () => {
wrapper.vm.handleToggleSidebarClick();
expect(Cookies.set).toHaveBeenCalledWith('collapsed_gutter', true);
});
it('calls `updatePageContainerClass` method', () => {
jest.spyOn(wrapper.vm, 'updatePageContainerClass');
wrapper.vm.handleWindowResize();
expect(wrapper.vm.updatePageContainerClass).toHaveBeenCalled(); wrapper = createComponent();
});
});
}); });
describe('template', () => { it('renders component container element with class `right-sidebar-collapsed`', () => {
describe('sidebar expanded', () => { expect(wrapper.classes()).toContain('right-sidebar-collapsed');
beforeEach(async () => {
wrapper.setData({
isExpanded: true,
}); });
await wrapper.vm.$nextTick(); it('sets layout class to reflect collapsed state', () => {
}); assertPageLayoutClasses({ isExpanded: false });
it('renders component container element with class `right-sidebar-expanded` when `isExpanded` prop is true', () => {
expect(wrapper.classes()).toContain('right-sidebar-expanded');
}); });
it('renders sidebar toggle button with text and icon', () => { it('renders sidebar toggle button with text and icon', () => {
const buttonEl = wrapper.find('button'); const buttonEl = findToggleSidebarButton();
expect(buttonEl.exists()).toBe(true); expect(buttonEl.exists()).toBe(true);
expect(buttonEl.attributes('title')).toBe('Toggle sidebar'); expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
expect(buttonEl.find('span').text()).toBe('Collapse sidebar'); expect(wrapper.findByTestId('icon-expand').isVisible()).toBe(true);
expect(buttonEl.find('[data-testid="icon-collapse"]').isVisible()).toBe(true);
});
});
describe('sidebar collapsed', () => {
beforeEach(async () => {
wrapper.setData({
isExpanded: false,
}); });
await wrapper.vm.$nextTick();
});
it('renders component container element with class `right-sidebar-collapsed` when `isExpanded` prop is false', () => {
expect(wrapper.classes()).toContain('right-sidebar-collapsed');
}); });
it('renders sidebar toggle button with text and icon', () => { it('renders slotted sidebar items', () => {
const buttonEl = wrapper.find('button'); wrapper = createComponent();
expect(buttonEl.exists()).toBe(true);
expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
expect(buttonEl.find('[data-testid="icon-expand"]').isVisible()).toBe(true);
});
});
it('renders sidebar items', () => { const sidebarItemsEl = wrapper.findByTestId('sidebar-items');
const sidebarItemsEl = wrapper.find('[data-testid="sidebar-items"]');
expect(sidebarItemsEl.exists()).toBe(true); expect(sidebarItemsEl.exists()).toBe(true);
expect(sidebarItemsEl.find('button.js-todo').exists()).toBe(true); expect(sidebarItemsEl.find('button.js-todo').exists()).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