Commit 183db979 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'euko-fix-epic-dropdown-in-swimlanes-sidebar' into 'master'

Fix epic dropdown not opening in swimlanes sidebar

See merge request gitlab-org/gitlab!53686
parents 0ebd272f e8832985
......@@ -10,7 +10,6 @@ import {
} from '@gitlab/ui';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import createFlash from '~/flash';
import { BV_DROPDOWN_HIDE } from '~/lib/utils/constants';
import { __, s__ } from '~/locale';
import projectMilestones from '../../graphql/project_milestones.query.graphql';
......@@ -73,21 +72,20 @@ export default {
return this.activeIssue.milestone?.title ?? this.$options.i18n.noMilestone;
},
},
mounted() {
this.$root.$on(BV_DROPDOWN_HIDE, () => {
this.$refs.sidebarItem.collapse();
});
},
methods: {
...mapActions(['setActiveIssueMilestone']),
handleOpen() {
this.edit = true;
this.$refs.dropdown.show();
},
handleClose() {
this.edit = false;
this.$refs.sidebarItem.collapse();
},
async setMilestone(milestoneId) {
this.loading = true;
this.searchTitle = '';
this.$refs.sidebarItem.collapse();
this.handleClose();
try {
const input = { milestoneId, projectPath: this.projectPath };
......@@ -116,7 +114,7 @@ export default {
:title="$options.i18n.milestone"
:loading="loading"
@open="handleOpen()"
@close="edit = false"
@close="handleClose"
>
<template v-if="hasMilestone" #collapsed>
<strong class="gl-text-gray-900">{{ activeIssue.milestone.title }}</strong>
......@@ -126,6 +124,7 @@ export default {
:text="dropdownText"
:header-text="$options.i18n.assignMilestone"
block
@hide="handleClose"
>
<gl-search-box-by-type ref="search" v-model.trim="searchTitle" class="gl-m-3" />
<gl-dropdown-item
......
......@@ -64,15 +64,25 @@ export default {
},
methods: {
...mapActions(['setActiveIssueEpic', 'fetchEpicForActiveIssue']),
openEpicsDropdown() {
if (!this.loading) {
this.$refs.epicSelect.handleEditClick();
handleOpen() {
if (!this.epicFetchInProgress) {
this.$refs.epicSelect.toggleFormDropdown();
} else {
this.$refs.sidebarItem.collapse();
}
},
async setEpic(selectedEpic) {
handleClose() {
this.$refs.sidebarItem.collapse();
this.$refs.epicSelect.toggleFormDropdown();
},
async setEpic(selectedEpic) {
this.handleClose();
const epicId = selectedEpic?.id ? fullEpicId(selectedEpic.id) : null;
const assignedEpicId = this.epic?.id ? fullEpicId(this.epic.id) : null;
if (epicId === assignedEpicId) {
return;
}
try {
await this.setActiveIssueEpic(epicId);
......@@ -89,7 +99,8 @@ export default {
ref="sidebarItem"
:title="$options.i18n.epic"
:loading="epicFetchInProgress"
@open="openEpicsDropdown"
@open="handleOpen"
@close="handleClose"
>
<template v-if="epicData.title" #collapsed>
<a class="gl-text-gray-900! gl-font-weight-bold" href="#">
......@@ -107,6 +118,7 @@ export default {
variant="standalone"
:show-header="false"
@epicSelect="setEpic"
@hide="handleClose"
/>
</board-editable-item>
</template>
......@@ -199,6 +199,7 @@ export default {
},
hideDropdown() {
this.isDropdownShowing = this.isDropdownVariantStandalone;
this.$emit('hide');
},
toggleFormDropdown() {
const { dropdown } = this.$refs.dropdown.$refs;
......
......@@ -27,7 +27,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
wrapper = null;
});
const fakeStore = ({
const createStore = ({
initialState = {
activeId: mockIssueWithoutEpic.id,
issues: { [mockIssueWithoutEpic.id]: { ...mockIssueWithoutEpic } },
......@@ -59,7 +59,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
BoardEditableItem,
EpicsSelect: stubComponent(EpicsSelect, {
methods: {
handleEditClick: epicsSelectHandleEditClick,
toggleFormDropdown: epicsSelectHandleEditClick,
},
}),
},
......@@ -69,9 +69,43 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
const findEpicSelect = () => wrapper.find({ ref: 'epicSelect' });
const findItemWrapper = () => wrapper.find({ ref: 'sidebarItem' });
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
describe('when not editing', () => {
it('expands the milestone dropdown on clicking edit', async () => {
createStore();
createWrapper();
await findBoardEditableItem().vm.$emit('open');
expect(epicsSelectHandleEditClick).toHaveBeenCalled();
});
});
describe('when editing', () => {
beforeEach(() => {
createStore();
createWrapper();
findItemWrapper().vm.$emit('open');
jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse');
});
it('collapses BoardEditableItem on clicking edit', async () => {
await findBoardEditableItem().vm.$emit('close');
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
});
it('collapses BoardEditableItem on hiding dropdown', async () => {
await wrapper.find(EpicsSelect).vm.$emit('hide');
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
});
});
it('renders "None" when no epic is assigned to the active issue', async () => {
fakeStore();
createStore();
createWrapper();
await wrapper.vm.$nextTick();
......@@ -83,7 +117,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
it('fetches an epic for active issue', () => {
const fetchEpicForActiveIssue = jest.fn(() => Promise.resolve());
fakeStore({
createStore({
initialState: {
activeId: mockIssueWithEpic.id,
issues: { [mockIssueWithEpic.id]: { ...mockIssueWithEpic } },
......@@ -101,7 +135,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
});
it('flashes an error message when fetch fails', async () => {
fakeStore({
createStore({
initialState: {
activeId: mockIssueWithEpic.id,
issues: { [mockIssueWithEpic.id]: { ...mockIssueWithEpic } },
......@@ -126,7 +160,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
});
it('renders epic title when issue has an assigned epic', async () => {
fakeStore({
createStore({
initialState: {
activeId: mockIssueWithEpic.id,
issues: { [mockIssueWithEpic.id]: { ...mockIssueWithEpic } },
......@@ -143,18 +177,9 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
});
});
it('expands the dropdown when editing', () => {
fakeStore();
createWrapper();
findItemWrapper().vm.$emit('open');
expect(epicsSelectHandleEditClick).toHaveBeenCalled();
});
describe('when epic is selected', () => {
beforeEach(async () => {
fakeStore({
createStore({
initialState: {
activeId: mockIssueWithoutEpic.id,
issues: { [mockIssueWithoutEpic.id]: { ...mockIssueWithoutEpic } },
......@@ -190,11 +215,25 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
expect(findCollapsed().isVisible()).toBe(true);
expect(findCollapsed().text()).toBe(mockAssignedEpic.title);
});
describe('when the selected epic did not change', () => {
it('does not commit change to the server', async () => {
createStore();
createWrapper();
jest.spyOn(wrapper.vm, 'setActiveIssueEpic').mockImplementation();
findEpicSelect().vm.$emit('epicSelect', null);
await wrapper.vm.$nextTick();
expect(wrapper.vm.setActiveIssueEpic).not.toHaveBeenCalled();
});
});
});
describe('when no epic is selected', () => {
beforeEach(async () => {
fakeStore({
createStore({
initialState: {
activeId: mockIssueWithEpic.id,
issues: { [mockIssueWithEpic.id]: { ...mockIssueWithEpic } },
......@@ -226,7 +265,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
});
it('flashes an error when update fails', async () => {
fakeStore({
createStore({
actionsMock: {
setActiveIssueEpic: jest.fn().mockRejectedValue('mayday'),
},
......@@ -234,7 +273,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
createWrapper();
findEpicSelect().vm.$emit('epicSelect', null);
findEpicSelect().vm.$emit('epicSelect', { id: 'foo' });
await wrapper.vm.$nextTick();
......
......@@ -137,6 +137,12 @@ describe('EpicsSelect', () => {
expect(wrapperStandalone.vm.isDropdownShowing).toBe(true);
});
it('should emit `hide` event', () => {
wrapperStandalone.vm.hideDropdown();
expect(wrapperStandalone.emitted().hide.length).toBe(1);
});
});
describe('handleItemSelect', () => {
......
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlDropdown } from '@gitlab/ui';
import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
......@@ -46,10 +46,42 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
const findLoader = () => wrapper.find(GlLoadingIcon);
const findDropdown = () => wrapper.find(GlDropdown);
const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]');
const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]');
const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]');
describe('when not editing', () => {
it('opens the milestone dropdown on clicking edit', async () => {
createWrapper();
wrapper.vm.$refs.dropdown.show = jest.fn();
await findBoardEditableItem().vm.$emit('open');
expect(wrapper.vm.$refs.dropdown.show).toHaveBeenCalledTimes(1);
});
});
describe('when editing', () => {
beforeEach(() => {
createWrapper();
jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse');
});
it('collapses BoardEditableItem on clicking edit', async () => {
await findBoardEditableItem().vm.$emit('close');
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
});
it('collapses BoardEditableItem on hiding dropdown', async () => {
await findDropdown().vm.$emit('hide');
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
});
});
it('renders "None" when no milestone is selected', () => {
createWrapper();
......
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