Commit f62b6a67 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch '349612-update-iteration-dropdowns' into 'master'

Update iteration dropdown to use dates

See merge request gitlab-org/gitlab!78361
parents db24476b 92287e52
...@@ -184,19 +184,6 @@ export default { ...@@ -184,19 +184,6 @@ export default {
:issuable-type="issuableType" :issuable-type="issuableType"
data-testid="sidebar-milestones" data-testid="sidebar-milestones"
/> />
<template v-if="!glFeatures.iterationCadences">
<sidebar-dropdown-widget
v-if="iterationFeatureAvailable && !isIncidentSidebar"
:iid="activeBoardItem.iid"
issuable-attribute="iteration"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
class="gl-mt-5"
data-testid="iteration-edit"
/>
</template>
<template v-else>
<iteration-sidebar-dropdown-widget <iteration-sidebar-dropdown-widget
v-if="iterationFeatureAvailable && !isIncidentSidebar" v-if="iterationFeatureAvailable && !isIncidentSidebar"
:iid="activeBoardItem.iid" :iid="activeBoardItem.iid"
...@@ -206,7 +193,6 @@ export default { ...@@ -206,7 +193,6 @@ export default {
class="gl-mt-5" class="gl-mt-5"
data-testid="iteration-edit" data-testid="iteration-edit"
/> />
</template>
</div> </div>
<board-sidebar-time-tracker /> <board-sidebar-time-tracker />
<sidebar-date-widget <sidebar-date-widget
......
...@@ -17,8 +17,8 @@ import { ListType } from '~/boards/constants'; ...@@ -17,8 +17,8 @@ import { ListType } from '~/boards/constants';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { groupByIterationCadences } from 'ee/iterations/utils'; import { groupByIterationCadences, getIterationPeriod } from 'ee/iterations/utils';
import IterationPeriod from 'ee/iterations/components/iteration_period.vue'; import IterationTitle from 'ee/iterations/components/iteration_title.vue';
export const listTypeInfo = { export const listTypeInfo = {
[ListType.label]: { [ListType.label]: {
...@@ -66,7 +66,7 @@ export default { ...@@ -66,7 +66,7 @@ export default {
GlFormGroup, GlFormGroup,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
IterationPeriod, IterationTitle,
}, },
directives: { directives: {
GlTooltip, GlTooltip,
...@@ -240,6 +240,8 @@ export default { ...@@ -240,6 +240,8 @@ export default {
this.selectedItem = { ...item }; this.selectedItem = { ...item };
} }
}, },
getIterationPeriod,
}, },
}; };
</script> </script>
...@@ -320,13 +322,13 @@ export default { ...@@ -320,13 +322,13 @@ export default {
</div> </div>
</gl-dropdown-section-header> </gl-dropdown-section-header>
<gl-dropdown-text v-for="iteration in cadence.iterations" :key="iteration.id"> <gl-dropdown-text v-for="iteration in cadence.iterations" :key="iteration.id">
<gl-form-radio :value="iteration.id" :aria-describedby="cadence.id"> <gl-form-radio
{{ iteration.title }} :value="iteration.id"
<div class="gl-display-inline-block"> :aria-describedby="cadence.id"
<IterationPeriod data-testid="new-column-iteration-period">{{ data-testid="new-column-iteration-item"
iteration.period >
}}</IterationPeriod> {{ iteration.period }}
</div> <iteration-title v-if="iteration.title" :title="iteration.title" />
</gl-form-radio> </gl-form-radio>
</gl-dropdown-text> </gl-dropdown-text>
</div> </div>
...@@ -363,6 +365,14 @@ export default { ...@@ -363,6 +365,14 @@ export default {
:sub-label="`@${item.username}`" :sub-label="`@${item.username}`"
:src="item.avatarUrl" :src="item.avatarUrl"
/> />
<div
v-else-if="iterationTypeSelected"
class="gl-display-inline-block"
data-testid="new-column-iteration-item"
>
{{ getIterationPeriod(item) }}
<iteration-title v-if="item.title" :title="item.title" />
</div>
<div v-else class="gl-display-inline-block"> <div v-else class="gl-display-inline-block">
{{ item.title }} {{ item.title }}
</div> </div>
......
<script>
export default {
name: 'IterationTitle',
props: {
title: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="gl-text-gray-400">
{{ title }}
</div>
</template>
...@@ -8,15 +8,15 @@ import { ...@@ -8,15 +8,15 @@ import {
GlTooltipDirective, GlTooltipDirective,
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import IterationPeriod from 'ee/iterations/components/iteration_period.vue'; import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import { groupByIterationCadences } from 'ee/iterations/utils'; import { groupByIterationCadences, getIterationPeriod } from 'ee/iterations/utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { iterationSelectTextMap, iterationDisplayState } from '../constants'; import { iterationSelectTextMap, iterationDisplayState } from '../constants';
import groupIterationsQuery from '../queries/iterations.query.graphql'; import groupIterationsQuery from '../queries/iterations.query.graphql';
export default { export default {
noIteration: { title: iterationSelectTextMap.noIteration, id: null }, noIteration: { text: iterationSelectTextMap.noIteration, id: null },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
GlSearchBoxByType, GlSearchBoxByType,
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlLoadingIcon, GlLoadingIcon,
IterationPeriod, IterationTitle,
}, },
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
apollo: { apollo: {
...@@ -72,8 +72,10 @@ export default { ...@@ -72,8 +72,10 @@ export default {
iterationCadences() { iterationCadences() {
return groupByIterationCadences(this.iterations); return groupByIterationCadences(this.iterations);
}, },
title() { dropdownSelectedText() {
return this.currentIteration?.title || __('Select iteration'); return this.currentIteration?.startDate || this.currentIteration?.period
? this.getIterationPeriod(this.currentIteration)
: __('Select iteration');
}, },
}, },
methods: { methods: {
...@@ -92,12 +94,13 @@ export default { ...@@ -92,12 +94,13 @@ export default {
onDropdownShow() { onDropdownShow() {
this.shouldFetch = true; this.shouldFetch = true;
}, },
getIterationPeriod,
}, },
}; };
</script> </script>
<template> <template>
<gl-dropdown :text="title" class="gl-w-full" block @show="onDropdownShow"> <gl-dropdown :text="dropdownSelectedText" class="gl-w-full" block @show="onDropdownShow">
<gl-dropdown-section-header class="gl-display-flex! gl-justify-content-center">{{ <gl-dropdown-section-header class="gl-display-flex! gl-justify-content-center">{{
__('Assign Iteration') __('Assign Iteration')
}}</gl-dropdown-section-header> }}</gl-dropdown-section-header>
...@@ -107,7 +110,7 @@ export default { ...@@ -107,7 +110,7 @@ export default {
:is-checked="isIterationChecked($options.noIteration.id)" :is-checked="isIterationChecked($options.noIteration.id)"
@click="onClick($options.noIteration)" @click="onClick($options.noIteration)"
> >
{{ $options.noIteration.title }} {{ $options.noIteration.text }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-divider /> <gl-dropdown-divider />
<gl-loading-icon v-if="$apollo.queries.iterations.loading" size="sm" /> <gl-loading-icon v-if="$apollo.queries.iterations.loading" size="sm" />
...@@ -118,8 +121,10 @@ export default { ...@@ -118,8 +121,10 @@ export default {
:is-check-item="true" :is-check-item="true"
:is-checked="isIterationChecked(iterationItem.id)" :is-checked="isIterationChecked(iterationItem.id)"
@click="onClick(iterationItem)" @click="onClick(iterationItem)"
>{{ iterationItem.title }}</gl-dropdown-item
> >
{{ getIterationPeriod(iterationItem) }}
<iteration-title v-if="iterationItem.title" :title="iterationItem.title" />
</gl-dropdown-item>
</template> </template>
<template v-else> <template v-else>
<template v-for="(cadence, index) in iterationCadences"> <template v-for="(cadence, index) in iterationCadences">
...@@ -134,8 +139,8 @@ export default { ...@@ -134,8 +139,8 @@ export default {
:is-checked="isIterationChecked(iterationItem.id)" :is-checked="isIterationChecked(iterationItem.id)"
@click="onClick(iterationItem)" @click="onClick(iterationItem)"
> >
{{ iterationItem.title }} {{ iterationItem.period }}
<IterationPeriod>{{ iterationItem.period }}</IterationPeriod> <iteration-title v-if="iterationItem.title" :title="iterationItem.title" />
</gl-dropdown-item> </gl-dropdown-item>
</template> </template>
</template> </template>
......
...@@ -7,9 +7,10 @@ import { ...@@ -7,9 +7,10 @@ import {
GlLink, GlLink,
} from '@gitlab/ui'; } from '@gitlab/ui';
import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue'; import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue';
import IterationPeriod from 'ee/iterations/components/iteration_period.vue'; import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import { getIterationPeriod, groupByIterationCadences } from 'ee/iterations/utils'; import { getIterationPeriod, groupByIterationCadences } from 'ee/iterations/utils';
import { IssuableType } from '~/issues/constants'; import { IssuableType } from '~/issues/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { IssuableAttributeType } from '../constants'; import { IssuableAttributeType } from '../constants';
export default { export default {
...@@ -21,8 +22,9 @@ export default { ...@@ -21,8 +22,9 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
SidebarDropdownWidget, SidebarDropdownWidget,
IterationPeriod, IterationTitle,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
attrWorkspacePath: { attrWorkspacePath: {
required: true, required: true,
...@@ -48,12 +50,8 @@ export default { ...@@ -48,12 +50,8 @@ export default {
getCadenceTitle(currentIteration) { getCadenceTitle(currentIteration) {
return currentIteration?.iterationCadence?.title; return currentIteration?.iterationCadence?.title;
}, },
getIterationPeriod(iteration) { groupByIterationCadences,
return getIterationPeriod({ startDate: iteration?.startDate, dueDate: iteration?.dueDate }); getIterationPeriod,
},
groupByIterationCadences(iterations) {
return groupByIterationCadences(iterations);
},
}, },
}; };
</script> </script>
...@@ -66,8 +64,8 @@ export default { ...@@ -66,8 +64,8 @@ export default {
:issuable-type="issuableType" :issuable-type="issuableType"
:workspace-path="workspacePath" :workspace-path="workspacePath"
> >
<template #value="{ attributeTitle, attributeUrl, currentAttribute }"> <template #value="{ attributeUrl, currentAttribute }">
<p class="gl-font-weight-bold gl-line-height-20 gl-m-0"> <p v-if="glFeatures.iterationCadences" class="gl-font-weight-bold gl-line-height-20 gl-m-0">
{{ getCadenceTitle(currentAttribute) }} {{ getCadenceTitle(currentAttribute) }}
</p> </p>
<gl-link <gl-link
...@@ -77,15 +75,19 @@ export default { ...@@ -77,15 +75,19 @@ export default {
> >
<div> <div>
<gl-icon name="iteration" class="gl-mr-1" /> <gl-icon name="iteration" class="gl-mr-1" />
{{ attributeTitle }} {{ getIterationPeriod(currentAttribute) }}
</div> </div>
<IterationPeriod>{{ getIterationPeriod(currentAttribute) }}</IterationPeriod> <iteration-title v-if="currentAttribute.title" :title="currentAttribute.title" />
</gl-link> </gl-link>
</template> </template>
<template #list="{ attributesList = [], isAttributeChecked, updateAttribute }"> <template #list="{ attributesList = [], isAttributeChecked, updateAttribute }">
<template v-for="(cadence, index) in groupByIterationCadences(attributesList)"> <template v-for="(cadence, index) in groupByIterationCadences(attributesList)">
<gl-dropdown-divider v-if="index !== 0" :key="index" /> <gl-dropdown-divider v-if="index !== 0 && glFeatures.iterationCadences" :key="index" />
<gl-dropdown-section-header :key="cadence.title"> <gl-dropdown-section-header
v-if="glFeatures.iterationCadences"
:key="cadence.title"
data-testid="cadence-title"
>
{{ cadence.title }} {{ cadence.title }}
</gl-dropdown-section-header> </gl-dropdown-section-header>
<gl-dropdown-item <gl-dropdown-item
...@@ -96,8 +98,8 @@ export default { ...@@ -96,8 +98,8 @@ export default {
:data-testid="`${$options.issuableAttribute}-items`" :data-testid="`${$options.issuableAttribute}-items`"
@click="updateAttribute(iteration.id)" @click="updateAttribute(iteration.id)"
> >
{{ iteration.title }} {{ iteration.period }}
<IterationPeriod>{{ iteration.period }}</IterationPeriod> <iteration-title v-if="iteration.title" :title="iteration.title" />
</gl-dropdown-item> </gl-dropdown-item>
</template> </template>
</template> </template>
......
...@@ -128,10 +128,6 @@ function mountIterationSelect() { ...@@ -128,10 +128,6 @@ function mountIterationSelect() {
const { groupPath, canEdit, projectPath, issueIid } = el.dataset; const { groupPath, canEdit, projectPath, issueIid } = el.dataset;
const IterationDropdown = gon.features.iterationCadences
? IterationSidebarDropdownWidget
: SidebarDropdownWidget;
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
...@@ -140,7 +136,7 @@ function mountIterationSelect() { ...@@ -140,7 +136,7 @@ function mountIterationSelect() {
isClassicSidebar: true, isClassicSidebar: true,
}, },
render: (createElement) => render: (createElement) =>
createElement(IterationDropdown, { createElement(IterationSidebarDropdownWidget, {
props: { props: {
attrWorkspacePath: groupPath, attrWorkspacePath: groupPath,
workspacePath: projectPath, workspacePath: projectPath,
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User adds milestone lists', :js do RSpec.describe 'User adds milestone/iterations lists', :js do
include IterationHelpers
let_it_be(:group) { create(:group, :nested) } let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:group_board) { create(:board, group: group) } let_it_be(:group_board) { create(:board, group: group) }
...@@ -62,9 +64,10 @@ RSpec.describe 'User adds milestone lists', :js do ...@@ -62,9 +64,10 @@ RSpec.describe 'User adds milestone lists', :js do
end end
it 'creates iteration column' do it 'creates iteration column' do
add_list('Iteration', iteration.title) period = iteration_period(iteration)
add_list('Iteration', period)
expect(page).to have_selector('.board', text: iteration.title) expect(page).to have_selector('.board', text: period)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue_with_iteration.title) expect(find('.board:nth-child(2) .board-card')).to have_content(issue_with_iteration.title)
end end
end end
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Issue Sidebar' do RSpec.describe 'Issue Sidebar' do
include MobileHelpers include MobileHelpers
include IterationHelpers
let_it_be(:group) { create(:group, :nested) } let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:project) { create(:project, :public, namespace: group) }
...@@ -169,6 +170,7 @@ RSpec.describe 'Issue Sidebar' do ...@@ -169,6 +170,7 @@ RSpec.describe 'Issue Sidebar' do
within '[data-testid="iteration-edit"]' do within '[data-testid="iteration-edit"]' do
expect(page).not_to have_text(iteration_cadence.title) expect(page).not_to have_text(iteration_cadence.title)
expect(page).to have_text(iteration.title) expect(page).to have_text(iteration.title)
expect(page).to have_text(iteration_period(iteration))
end end
select_iteration(iteration.title) select_iteration(iteration.title)
...@@ -176,6 +178,7 @@ RSpec.describe 'Issue Sidebar' do ...@@ -176,6 +178,7 @@ RSpec.describe 'Issue Sidebar' do
within '[data-testid="select-iteration"]' do within '[data-testid="select-iteration"]' do
expect(page).not_to have_text(iteration_cadence.title) expect(page).not_to have_text(iteration_cadence.title)
expect(page).to have_text(iteration.title) expect(page).to have_text(iteration.title)
expect(page).to have_text(iteration_period(iteration))
end end
find_and_click_edit_iteration find_and_click_edit_iteration
...@@ -297,8 +300,4 @@ RSpec.describe 'Issue Sidebar' do ...@@ -297,8 +300,4 @@ RSpec.describe 'Issue Sidebar' do
wait_for_requests wait_for_requests
end end
end end
def iteration_period(iteration)
"#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
end
end end
...@@ -573,14 +573,13 @@ exports[`ee/BoardContentSidebar issue sidebar matches the snapshot 1`] = ` ...@@ -573,14 +573,13 @@ exports[`ee/BoardContentSidebar issue sidebar matches the snapshot 1`] = `
workspacepath="gitlab-org/gitlab-test" workspacepath="gitlab-org/gitlab-test"
/> />
<sidebardropdownwidget-stub <iterationsidebardropdownwidget-stub
attrworkspacepath="gitlab-org" attr-workspace-path="gitlab-org"
class="gl-mt-5" class="gl-mt-5"
data-testid="iteration-edit" data-testid="iteration-edit"
iid="27" iid="27"
issuableattribute="iteration" issuable-type="issue"
issuabletype="issue" workspace-path="gitlab-org/gitlab-test"
workspacepath="gitlab-org/gitlab-test"
/> />
</div> </div>
......
...@@ -5,8 +5,10 @@ import Vuex from 'vuex'; ...@@ -5,8 +5,10 @@ import Vuex from 'vuex';
import BoardAddNewColumn, { listTypeInfo } from 'ee/boards/components/board_add_new_column.vue'; import BoardAddNewColumn, { listTypeInfo } from 'ee/boards/components/board_add_new_column.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import { ListType } from '~/boards/constants'; import { ListType } from '~/boards/constants';
import defaultState from '~/boards/stores/state'; import defaultState from '~/boards/stores/state';
import { getIterationPeriod } from 'ee/iterations/utils';
import { mockAssignees, mockLists, mockIterations } from '../mock_data'; import { mockAssignees, mockLists, mockIterations } from '../mock_data';
const mockLabelList = mockLists[1]; const mockLabelList = mockLists[1];
...@@ -46,6 +48,7 @@ describe('BoardAddNewColumn', () => { ...@@ -46,6 +48,7 @@ describe('BoardAddNewColumn', () => {
BoardAddNewColumnForm, BoardAddNewColumnForm,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
IterationTitle,
}, },
data() { data() {
return { return {
...@@ -94,8 +97,7 @@ describe('BoardAddNewColumn', () => { ...@@ -94,8 +97,7 @@ describe('BoardAddNewColumn', () => {
const findForm = () => wrapper.findComponent(BoardAddNewColumnForm); const findForm = () => wrapper.findComponent(BoardAddNewColumnForm);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn'); const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton'); const submitButton = () => wrapper.findByTestId('addNewColumnButton');
const findLabels = () => wrapper.findComponent(GlDropdown).findAll('label'); const findIterationItemAt = (i) => wrapper.findAllByTestId('new-column-iteration-item').at(i);
const findIterationPeriod = (item) => item.find('[data-testid="new-column-iteration-period"]');
const listTypeSelect = (type) => { const listTypeSelect = (type) => {
const radio = wrapper const radio = wrapper
.findAllComponents(GlFormRadio) .findAllComponents(GlFormRadio)
...@@ -110,6 +112,16 @@ describe('BoardAddNewColumn', () => { ...@@ -110,6 +112,16 @@ describe('BoardAddNewColumn', () => {
await nextTick(); await nextTick();
}; };
const expectIterationWithTitle = () => {
expect(findIterationItemAt(1).text()).toContain(getIterationPeriod(mockIterations[1]));
expect(findIterationItemAt(1).text()).toContain(mockIterations[1].title);
};
const expectIterationWithoutTitle = () => {
expect(findIterationItemAt(0).text()).toContain(getIterationPeriod(mockIterations[0]));
expect(findIterationItemAt(0).findComponent(IterationTitle).exists()).toBe(false);
};
it('clicking cancel hides the form', () => { it('clicking cancel hides the form', () => {
const setAddColumnFormVisibility = jest.fn(); const setAddColumnFormVisibility = jest.fn();
mountComponent({ mountComponent({
...@@ -238,16 +250,8 @@ describe('BoardAddNewColumn', () => { ...@@ -238,16 +250,8 @@ describe('BoardAddNewColumn', () => {
const itemList = wrapper.findComponent(GlDropdown).findAllComponents(GlFormRadio); const itemList = wrapper.findComponent(GlDropdown).findAllComponents(GlFormRadio);
expect(itemList).toHaveLength(mockIterations.length); expect(itemList).toHaveLength(mockIterations.length);
expect(itemList.at(0).attributes('value')).toBe(mockIterations[0].id); expectIterationWithoutTitle();
expect(itemList.at(1).attributes('value')).toBe(mockIterations[1].id); expectIterationWithTitle();
});
describe('iteration_cadences feature flag is off', () => {
it('does not display iteration period', async () => {
const labels = findLabels();
expect(findIterationPeriod(labels.at(0)).exists()).toBe(false);
expect(findIterationPeriod(labels.at(1)).exists()).toBe(false);
});
}); });
}); });
...@@ -280,12 +284,9 @@ describe('BoardAddNewColumn', () => { ...@@ -280,12 +284,9 @@ describe('BoardAddNewColumn', () => {
expect(cadenceTitles).toEqual(cadenceTitles.map((_, idx) => getCadenceTitleFromMocks(idx))); expect(cadenceTitles).toEqual(cadenceTitles.map((_, idx) => getCadenceTitleFromMocks(idx)));
}); });
it('displays iteration period', async () => { it('displays iteration period optionally with title', async () => {
const iterations = wrapper.findAllByTestId('new-column-iteration-period'); expectIterationWithoutTitle();
expect(iterations.at(0).text()).toContain('Oct 5, 2021 - Oct 10, 2021'); expectIterationWithTitle();
expect(findIterationPeriod(iterations.at(0)).isVisible()).toBe(true);
expect(iterations.at(1).text()).toContain('Oct 12, 2021 - Oct 17, 2021');
expect(findIterationPeriod(iterations.at(1)).isVisible()).toBe(true);
}); });
}); });
}); });
...@@ -80,6 +80,7 @@ describe('ee/BoardContentSidebar', () => { ...@@ -80,6 +80,7 @@ describe('ee/BoardContentSidebar', () => {
SidebarSubscriptionsWidget: true, SidebarSubscriptionsWidget: true,
SidebarWeightWidget: true, SidebarWeightWidget: true,
SidebarDropdownWidget: true, SidebarDropdownWidget: true,
IterationSidebarDropdownWidget: true,
SidebarTodoWidget: true, SidebarTodoWidget: true,
MountingPortal: true, MountingPortal: true,
}, },
......
...@@ -121,7 +121,7 @@ export const mockMilestones = [ ...@@ -121,7 +121,7 @@ export const mockMilestones = [
export const mockIterations = [ export const mockIterations = [
{ {
id: 'gid://gitlab/Iteration/1', id: 'gid://gitlab/Iteration/1',
title: 'Iteration 1', title: null,
iterationCadence: { iterationCadence: {
id: 'gid://gitlab/Iterations::Cadence/1', id: 'gid://gitlab/Iterations::Cadence/1',
title: 'GitLab.org Iterations', title: 'GitLab.org Iterations',
...@@ -131,7 +131,7 @@ export const mockIterations = [ ...@@ -131,7 +131,7 @@ export const mockIterations = [
}, },
{ {
id: 'gid://gitlab/Iteration/2', id: 'gid://gitlab/Iteration/2',
title: 'Iteration 2', title: 'Some iteration',
iterationCadence: { iterationCadence: {
id: 'gid://gitlab/Iterations::Cadence/2', id: 'gid://gitlab/Iterations::Cadence/2',
title: 'GitLab.org Iterations: Volume II', title: 'GitLab.org Iterations: Volume II',
......
import { shallowMount } from '@vue/test-utils';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
describe('Iterations title', () => {
let wrapper;
const createComponent = (propsData) => {
wrapper = shallowMount(IterationTitle, {
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
it('shows empty state', () => {
createComponent({ title: 'abc' });
expect(wrapper.html()).toHaveText('abc');
});
});
...@@ -12,6 +12,8 @@ import VueApollo from 'vue-apollo'; ...@@ -12,6 +12,8 @@ import VueApollo from 'vue-apollo';
import IterationDropdown from 'ee/sidebar/components/iteration_dropdown.vue'; import IterationDropdown from 'ee/sidebar/components/iteration_dropdown.vue';
import groupIterationsQuery from 'ee/sidebar/queries/iterations.query.graphql'; import groupIterationsQuery from 'ee/sidebar/queries/iterations.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { getIterationPeriod } from 'ee/iterations/utils';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -32,7 +34,7 @@ const TEST_ITERATIONS = [ ...@@ -32,7 +34,7 @@ const TEST_ITERATIONS = [
}, },
{ {
id: '22', id: '22',
title: 'Another Test Title', title: null,
startDate: '2021-10-06', startDate: '2021-10-06',
dueDate: '2021-10-10', dueDate: '2021-10-10',
webUrl: '', webUrl: '',
...@@ -44,7 +46,7 @@ const TEST_ITERATIONS = [ ...@@ -44,7 +46,7 @@ const TEST_ITERATIONS = [
}, },
{ {
id: '33', id: '33',
title: 'Yet Another Test Title', title: null,
startDate: '2021-10-11', startDate: '2021-10-11',
dueDate: '2021-10-15', dueDate: '2021-10-15',
webUrl: '', webUrl: '',
...@@ -83,15 +85,11 @@ describe('IterationDropdown', () => { ...@@ -83,15 +85,11 @@ describe('IterationDropdown', () => {
await nextTick(); await nextTick();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
}; };
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findDropdownItemsAt = (i) => findDropdownItems().at(i);
const findDropdownItemWithText = (text) => const findDropdownItemWithText = (text) =>
findDropdownItems().wrappers.find((x) => x.text() === text); findDropdownItems().wrappers.find((x) => x.text().includes(text));
const findDropdownItemsData = () =>
findDropdownItems().wrappers.map((x) => ({
isCheckItem: x.props('isCheckItem'),
isChecked: x.props('isChecked'),
text: x.text(),
}));
const selectDropdownItemAndWait = async (text) => { const selectDropdownItemAndWait = async (text) => {
const item = findDropdownItemWithText(text); const item = findDropdownItemWithText(text);
...@@ -115,6 +113,9 @@ describe('IterationDropdown', () => { ...@@ -115,6 +113,9 @@ describe('IterationDropdown', () => {
propsData: { propsData: {
fullPath: TEST_FULL_PATH, fullPath: TEST_FULL_PATH,
}, },
stubs: {
IterationTitle,
},
provide: { provide: {
glFeatures: { glFeatures: {
iterationCadences, iterationCadences,
...@@ -173,16 +174,21 @@ describe('IterationDropdown', () => { ...@@ -173,16 +174,21 @@ describe('IterationDropdown', () => {
expect(isLoading()).toBe(false); expect(isLoading()).toBe(false);
}); });
it('shows dropdown items', () => { it('shows checkable dropdown items in unchecked state', () => {
const result = [IterationDropdown.noIteration].concat(TEST_ITERATIONS); expect(findDropdownItems().wrappers.every((x) => x.props('isCheckItem'))).toBe(true);
expect(findDropdownItems().wrappers.every((x) => x.props('isChecked'))).toBe(false);
});
expect(findDropdownItemsData()).toEqual( it('populates dropdown items with correct names', () => {
result.map((x) => ({ // "No iteration" dropdown item
isCheckItem: true, expect(findDropdownItemsAt(0).text()).toContain(IterationDropdown.noIteration.text);
isChecked: false,
text: x.title, // Iteration with title
})), expect(findDropdownItemsAt(1).text()).toContain(getIterationPeriod(TEST_ITERATIONS[0]));
); expect(findDropdownItemsAt(1).text()).toContain(TEST_ITERATIONS[0].title);
// Iteration without title
expect(findDropdownItemsAt(2).text()).toContain(getIterationPeriod(TEST_ITERATIONS[1]));
}); });
it('does not re-query if opened again', async () => { it('does not re-query if opened again', async () => {
...@@ -192,41 +198,40 @@ describe('IterationDropdown', () => { ...@@ -192,41 +198,40 @@ describe('IterationDropdown', () => {
expect(groupIterationsSpy).not.toHaveBeenCalled(); expect(groupIterationsSpy).not.toHaveBeenCalled();
}); });
describe.each([0, 1, 2])('when item %s is selected', (index) => { describe.each([
const allIterations = [IterationDropdown.noIteration].concat(TEST_ITERATIONS); {
const selected = allIterations[index]; text: IterationDropdown.noIteration.text,
const asNotChecked = ({ title }) => ({ isCheckItem: true, isChecked: false, text: title }); dropdownText: 'Select iteration',
iteration: IterationDropdown.noIteration,
beforeEach(async () => { },
await selectDropdownItemAndWait(selected.title); {
}); text: getIterationPeriod(TEST_ITERATIONS[0]),
dropdownText: getIterationPeriod(TEST_ITERATIONS[0]),
it('shows item as checked', () => { iteration: TEST_ITERATIONS[0],
const prevSelected = allIterations.slice(0, index); },
const afterSelected = allIterations.slice(index + 1);
expect(findDropdownItemsData()).toEqual([
...prevSelected.map(asNotChecked),
{ {
isCheckItem: true, text: getIterationPeriod(TEST_ITERATIONS[1]),
isChecked: true, dropdownText: getIterationPeriod(TEST_ITERATIONS[1]),
text: selected.title, iteration: TEST_ITERATIONS[1],
}, },
...afterSelected.map(asNotChecked), ])("when iteration '%s' is selected", ({ text, dropdownText, iteration }) => {
]); beforeEach(async () => {
await selectDropdownItemAndWait(text);
}); });
it('emits event', () => { it('shows item as checked with text and emits event', () => {
expect(wrapper.emitted('onIterationSelect')).toEqual([[selected]]); expect(findDropdown().props('text')).toBe(dropdownText);
expect(findDropdownItemWithText(text).props('isChecked')).toBe(true);
expect(wrapper.emitted('onIterationSelect')).toEqual([[iteration]]);
}); });
describe('when item is clicked again', () => { describe('when item is clicked again', () => {
beforeEach(async () => { beforeEach(async () => {
await selectDropdownItemAndWait(selected.title); await selectDropdownItemAndWait(text);
}); });
it('shows item as unchecked', () => { it('shows item as unchecked', () => {
expect(findDropdownItemsData()).toEqual(allIterations.map(asNotChecked)); expect(findDropdownItems().wrappers.every((x) => x.props('isChecked'))).toBe(false);
}); });
it('emits event', () => { it('emits event', () => {
...@@ -271,18 +276,18 @@ describe('IterationDropdown', () => { ...@@ -271,18 +276,18 @@ describe('IterationDropdown', () => {
expect(dropdownItems.at(0).text()).toBe('Assign Iteration'); expect(dropdownItems.at(0).text()).toBe('Assign Iteration');
expect(dropdownItems.at(1).text()).toContain('No iteration'); expect(dropdownItems.at(1).text()).toContain('No iteration');
expect(dropdownItems.at(2).findComponent(GlDropdownDivider).exists()).toBe(true); expect(dropdownItems.at(2).findComponent(GlDropdownDivider).exists()).toBe(true);
expect(dropdownItems.at(3).findComponent(GlDropdownSectionHeader).text()).toBe('My Cadence'); expect(dropdownItems.at(3).findComponent(GlDropdownSectionHeader).text()).toBe('My Cadence');
expect(dropdownItems.at(4).text()).toContain(getIterationPeriod(TEST_ITERATIONS[0]));
expect(dropdownItems.at(4).text()).toContain('Test Title'); expect(dropdownItems.at(4).text()).toContain('Test Title');
expect(dropdownItems.at(4).text()).toContain('Oct 1, 2021 - Oct 5, 2021'); expect(dropdownItems.at(5).text()).toContain(getIterationPeriod(TEST_ITERATIONS[2]));
expect(dropdownItems.at(5).text()).toContain('Yet Another Test Title');
expect(dropdownItems.at(5).text()).toContain('Oct 11, 2021 - Oct 15, 2021');
expect(dropdownItems.at(6).findComponent(GlDropdownDivider).exists()).toBe(true); expect(dropdownItems.at(6).findComponent(GlDropdownDivider).exists()).toBe(true);
expect(dropdownItems.at(7).findComponent(GlDropdownSectionHeader).text()).toBe( expect(dropdownItems.at(7).findComponent(GlDropdownSectionHeader).text()).toBe(
'My Second Cadence', 'My Second Cadence',
); );
expect(dropdownItems.at(8).text()).toContain('Another Test Title'); expect(dropdownItems.at(8).text()).toContain(getIterationPeriod(TEST_ITERATIONS[1]));
expect(dropdownItems.at(8).text()).toContain('Oct 6, 2021 - Oct 10, 2021');
}); });
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import IterationSidebarDropdownWidget from 'ee/sidebar/components/iteration_sidebar_dropdown_widget.vue'; import IterationSidebarDropdownWidget from 'ee/sidebar/components/iteration_sidebar_dropdown_widget.vue';
import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue'; import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { IssuableType } from '~/issues/constants'; import { IssuableType } from '~/issues/constants';
import groupIterationsQuery from 'ee/sidebar/queries/group_iterations.query.graphql';
import projectIssueIterationQuery from 'ee/sidebar/queries/project_issue_iteration.query.graphql';
import { IssuableAttributeType, issuableAttributesQueries } from 'ee/sidebar/constants';
import { getIterationPeriod } from 'ee/iterations/utils';
import {
mockIssue,
mockGroupIterationsResponse,
mockIteration1,
mockIteration2,
mockCurrentIterationResponse1,
mockCurrentIterationResponse2,
} from '../mock_data';
import { clickEdit } from '../helpers';
Vue.use(VueApollo);
describe('IterationSidebarDropdownWidget', () => { describe('IterationSidebarDropdownWidget', () => {
let wrapper; let wrapper;
let mockApollo;
const findCurrentIterationText = () => wrapper.findByTestId('select-iteration').text();
const findIterationItemsTextAt = (at) => wrapper.findAllByTestId('iteration-items').at(at).text();
const findIterationCadenceTitleAt = (at) =>
wrapper.findAllByTestId('cadence-title').at(at).text();
const defaultProps = { const createComponentWithApollo = async ({
attrWorkspacePath: 'attr/workspace/path', iterationCadences = false,
iid: 'iid', currentIterationResponse = mockCurrentIterationResponse1,
} = {}) => {
mockApollo = createMockApollo([
[groupIterationsQuery, jest.fn().mockResolvedValue(mockGroupIterationsResponse)],
[projectIssueIterationQuery, jest.fn().mockResolvedValue(currentIterationResponse)],
]);
wrapper = extendedWrapper(
mount(IterationSidebarDropdownWidget, {
provide: {
glFeatures: { iterationCadences },
issuableAttributesQueries,
canUpdate: true,
},
apolloProvider: mockApollo,
propsData: {
workspacePath: mockIssue.projectPath,
attrWorkspacePath: mockIssue.groupPath,
iid: mockIssue.iid,
issuableType: IssuableType.Issue, issuableType: IssuableType.Issue,
workspacePath: 'workspace/path', issuableAttribute: IssuableAttributeType.Iteration,
}; },
}),
);
const createComponent = () => jest.runOnlyPendingTimers();
shallowMount(IterationSidebarDropdownWidget, { await waitForPromises();
propsData: defaultProps, };
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('SidebarDropdownWidget component', () => { describe('iteration_cadences feature flag is off', () => {
it('renders', () => { describe('when showing the current iteration (dropdown is closed)', () => {
wrapper = createComponent(); it('renders just iteration period for iteration without title', async () => {
await createComponentWithApollo();
expect(findCurrentIterationText()).toContain(getIterationPeriod(mockIteration1));
});
it('renders iteration period with optional title for iteration with title', async () => {
await createComponentWithApollo({
iterationCadences: false,
currentIterationResponse: mockCurrentIterationResponse2,
});
expect(findCurrentIterationText()).toContain(getIterationPeriod(mockIteration2));
expect(findCurrentIterationText()).toContain(mockIteration2.title);
});
});
describe('when listing iterations in the dropdown', () => {
it('renders iterations', async () => {
await createComponentWithApollo();
await clickEdit(wrapper);
// mockIteration1 has no title
expect(findIterationItemsTextAt(0)).toContain(getIterationPeriod(mockIteration1));
// mockIteration2 has a title
expect(findIterationItemsTextAt(1)).toContain(getIterationPeriod(mockIteration2));
expect(findIterationItemsTextAt(1)).toContain(mockIteration2.title);
});
});
});
describe('iteration_cadences feature flag is on', () => {
describe('when showing the current iteration (dropdown is closed)', () => {
it('renders cadence title', async () => {
await createComponentWithApollo({ iterationCadences: true });
expect(findCurrentIterationText()).toContain(mockIteration1.iterationCadence.title);
});
it('renders just iteration period for iteration without title', async () => {
await createComponentWithApollo({ iterationCadences: true });
expect(findCurrentIterationText()).toContain(getIterationPeriod(mockIteration1));
});
it('renders iteration period with optional title for iteration with title', async () => {
await createComponentWithApollo({
iterationCadences: true,
currentIterationResponse: mockCurrentIterationResponse2,
});
expect(findCurrentIterationText()).toContain(getIterationPeriod(mockIteration2));
expect(findCurrentIterationText()).toContain(mockIteration2.title);
});
});
describe('when listing iterations in the dropdown', () => {
it('renders iterations with cadence names', async () => {
await createComponentWithApollo({ iterationCadences: true });
await clickEdit(wrapper);
// mockIteration1 has no title
expect(findIterationCadenceTitleAt(0)).toContain(mockIteration1.iterationCadence.title);
expect(findIterationItemsTextAt(0)).toContain(getIterationPeriod(mockIteration1));
expect(wrapper.findComponent(SidebarDropdownWidget).props()).toEqual({ // mockIteration2 has a title
...defaultProps, expect(findIterationCadenceTitleAt(1)).toContain(mockIteration2.iterationCadence.title);
issuableAttribute: 'iteration', expect(findIterationItemsTextAt(1)).toContain(getIterationPeriod(mockIteration2));
expect(findIterationItemsTextAt(1)).toContain(mockIteration2.title);
}); });
}); });
}); });
......
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue'; import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue';
import { IssuableAttributeType, issuableAttributesQueries } from 'ee/sidebar/constants'; import { IssuableAttributeType, issuableAttributesQueries } from 'ee/sidebar/constants';
...@@ -20,6 +20,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -20,6 +20,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { IssuableType } from '~/issues/constants'; import { IssuableType } from '~/issues/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import { clickEdit, search } from '../helpers';
import { import {
mockIssue, mockIssue,
...@@ -42,44 +43,16 @@ describe('SidebarDropdownWidget', () => { ...@@ -42,44 +43,16 @@ describe('SidebarDropdownWidget', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown); const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownText = () => wrapper.findComponent(GlDropdownText); const findDropdownText = () => wrapper.findComponent(GlDropdownText);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findDropdownItemWithText = (text) => const findDropdownItemWithText = (text) =>
findAllDropdownItems().wrappers.find((x) => x.text() === text); findAllDropdownItems().wrappers.find((x) => x.text() === text);
const findSidebarEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findEditButton = () => findSidebarEditableItem().find('[data-testid="edit-button"]');
const findSelectedAttribute = () => wrapper.findByTestId('select-epic'); const findSelectedAttribute = () => wrapper.findByTestId('select-epic');
const waitForDropdown = async () => {
// BDropdown first changes its `visible` property
// in a requestAnimationFrame callback.
// It then emits `shown` event in a watcher for `visible`
// Hence we need both of these:
await waitForPromises();
await nextTick();
};
const waitForApollo = async () => {
jest.runOnlyPendingTimers();
await waitForPromises();
};
// Used with createComponentWithApollo which uses 'mount'
const clickEdit = async () => {
await findEditButton().trigger('click');
await waitForDropdown();
// We should wait for attributes list to be fetched.
await waitForApollo();
};
// Used with createComponent which shallow mounts components // Used with createComponent which shallow mounts components
const toggleDropdown = async () => { const toggleDropdown = async () => {
wrapper.findComponent(SidebarEditableItem).vm.$emit('open'); wrapper.findComponent(SidebarEditableItem).vm.$emit('open');
await waitForDropdown(); await waitForPromises();
}; };
const createComponentWithApollo = async ({ const createComponentWithApollo = async ({
...@@ -109,7 +82,8 @@ describe('SidebarDropdownWidget', () => { ...@@ -109,7 +82,8 @@ describe('SidebarDropdownWidget', () => {
}), }),
); );
await waitForApollo(); jest.runOnlyPendingTimers();
await waitForPromises();
}; };
const createComponent = ({ const createComponent = ({
...@@ -165,9 +139,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -165,9 +139,7 @@ describe('SidebarDropdownWidget', () => {
await toggleDropdown(); await toggleDropdown();
findSearchBox().vm.$emit('input', 'non existing epics'); await search(wrapper, 'non existing epics');
await nextTick();
expect(findDropdownText().text()).toBe('No open iteration found'); expect(findDropdownText().text()).toBe('No open iteration found');
}); });
...@@ -192,7 +164,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -192,7 +164,7 @@ describe('SidebarDropdownWidget', () => {
requestHandlers: [[projectIssueEpicMutation, epicMutationSpy]], requestHandlers: [[projectIssueEpicMutation, epicMutationSpy]],
}); });
await clickEdit(); await clickEdit(wrapper);
}); });
it('renders the dropdown on clicking edit', async () => { it('renders the dropdown on clicking edit', async () => {
...@@ -231,7 +203,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -231,7 +203,7 @@ describe('SidebarDropdownWidget', () => {
groupEpicsSpy: jest.fn().mockRejectedValue(error), groupEpicsSpy: jest.fn().mockRejectedValue(error),
}); });
await clickEdit(); await clickEdit(wrapper);
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to fetch the epic for this issue. Please try again.', message: 'Failed to fetch the epic for this issue. Please try again.',
...@@ -246,7 +218,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -246,7 +218,7 @@ describe('SidebarDropdownWidget', () => {
expect(groupEpicsSpy).not.toHaveBeenCalled(); expect(groupEpicsSpy).not.toHaveBeenCalled();
await clickEdit(); await clickEdit(wrapper);
expect(groupEpicsSpy).toHaveBeenNthCalledWith(1, { expect(groupEpicsSpy).toHaveBeenNthCalledWith(1, {
fullPath: mockIssue.groupPath, fullPath: mockIssue.groupPath,
...@@ -262,15 +234,11 @@ describe('SidebarDropdownWidget', () => { ...@@ -262,15 +234,11 @@ describe('SidebarDropdownWidget', () => {
groupEpicsSpy = jest.fn().mockResolvedValueOnce(emptyGroupEpicsResponse); groupEpicsSpy = jest.fn().mockResolvedValueOnce(emptyGroupEpicsResponse);
await createComponentWithApollo({ groupEpicsSpy }); await createComponentWithApollo({ groupEpicsSpy });
await clickEdit(); await clickEdit(wrapper);
}); });
it('sends a groupEpics query with the entered search term "foo" and in TITLE param', async () => { it('sends a groupEpics query with the entered search term "foo" and in TITLE param', async () => {
findSearchBox().vm.$emit('input', mockSearchTerm); await search(wrapper, mockSearchTerm);
await nextTick();
// Account for debouncing
jest.runAllTimers();
expect(groupEpicsSpy).toHaveBeenNthCalledWith(2, { expect(groupEpicsSpy).toHaveBeenNthCalledWith(2, {
fullPath: mockIssue.groupPath, fullPath: mockIssue.groupPath,
...@@ -287,11 +255,11 @@ describe('SidebarDropdownWidget', () => { ...@@ -287,11 +255,11 @@ describe('SidebarDropdownWidget', () => {
groupEpicsSpy = jest.fn().mockResolvedValueOnce(emptyGroupEpicsResponse); groupEpicsSpy = jest.fn().mockResolvedValueOnce(emptyGroupEpicsResponse);
await createComponentWithApollo({ groupEpicsSpy }); await createComponentWithApollo({ groupEpicsSpy });
await clickEdit(); await clickEdit(wrapper);
}); });
it('sends a groupEpics query with empty title and undefined in param', async () => { it('sends a groupEpics query with empty title and undefined in param', async () => {
await nextTick(); await waitForPromises();
// Account for debouncing // Account for debouncing
jest.runAllTimers(); jest.runAllTimers();
...@@ -304,11 +272,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -304,11 +272,7 @@ describe('SidebarDropdownWidget', () => {
}); });
it('sends a groupEpics query for an IID with the entered search term "&1"', async () => { it('sends a groupEpics query for an IID with the entered search term "&1"', async () => {
findSearchBox().vm.$emit('input', '&1'); await search(wrapper, '&1');
await nextTick();
// Account for debouncing
jest.runAllTimers();
expect(groupEpicsSpy).toHaveBeenNthCalledWith(2, { expect(groupEpicsSpy).toHaveBeenNthCalledWith(2, {
fullPath: mockIssue.groupPath, fullPath: mockIssue.groupPath,
......
import { GlSearchBoxByType } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
const findSidebarEditableItem = (wrapper) => wrapper.findComponent(SidebarEditableItem);
const findEditButton = (wrapper) =>
findSidebarEditableItem(wrapper).find('[data-testid="edit-button"]');
const findSearchBox = (wrapper) => wrapper.findComponent(GlSearchBoxByType);
export const search = async (wrapper, searchTerm) => {
findSearchBox(wrapper).vm.$emit('input', searchTerm);
await waitForPromises();
jest.runAllTimers(); // Account for debouncing
};
// Used with createComponentWithApollo which uses 'mount'
export const clickEdit = async (wrapper) => {
await findEditButton(wrapper).trigger('click');
// We should wait for attributes list to be fetched.
jest.runAllTimers();
await waitForPromises();
};
...@@ -9,12 +9,25 @@ export const mockIssue = { ...@@ -9,12 +9,25 @@ export const mockIssue = {
export const mockIssueId = 'gid://gitlab/Issue/1'; export const mockIssueId = 'gid://gitlab/Issue/1';
export const mockCadence1 = {
id: 'gid://gitlab/Iterations::Cadence/1',
title: 'Plan cadence',
};
export const mockCadence2 = {
id: 'gid://gitlab/Iterations::Cadence/2',
title: 'Automatic cadence',
};
export const mockIteration1 = { export const mockIteration1 = {
__typename: 'Iteration', __typename: 'Iteration',
id: 'gid://gitlab/Iteration/1', id: 'gid://gitlab/Iteration/1',
title: 'Foobar Iteration', title: null,
webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/iterations/1', webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/iterations/1',
state: 'opened', state: 'opened',
startDate: '2021-10-05',
dueDate: '2021-10-10',
iterationCadence: mockCadence1,
}; };
export const mockIteration2 = { export const mockIteration2 = {
...@@ -23,6 +36,9 @@ export const mockIteration2 = { ...@@ -23,6 +36,9 @@ export const mockIteration2 = {
title: 'Awesome Iteration', title: 'Awesome Iteration',
webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/iterations/2', webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/iterations/2',
state: 'opened', state: 'opened',
startDate: '2021-10-12',
dueDate: '2021-10-17',
iterationCadence: mockCadence2,
}; };
export const mockEpic1 = { export const mockEpic1 = {
...@@ -45,7 +61,7 @@ export const mockGroupIterationsResponse = { ...@@ -45,7 +61,7 @@ export const mockGroupIterationsResponse = {
data: { data: {
workspace: { workspace: {
id: '1', id: '1',
iterations: { attributes: {
nodes: [mockIteration1, mockIteration2], nodes: [mockIteration1, mockIteration2],
}, },
__typename: 'IterationConnection', __typename: 'IterationConnection',
...@@ -93,6 +109,36 @@ export const emptyGroupEpicsResponse = { ...@@ -93,6 +109,36 @@ export const emptyGroupEpicsResponse = {
}, },
}; };
export const mockCurrentIterationResponse1 = {
data: {
errors: [],
workspace: {
id: '1',
issuable: {
id: mockIssueId,
attribute: mockIteration1,
__typename: 'Issue',
},
__typename: 'Project',
},
},
};
export const mockCurrentIterationResponse2 = {
data: {
errors: [],
workspace: {
id: '1',
issuable: {
id: mockIssueId,
attribute: mockIteration2,
__typename: 'Issue',
},
__typename: 'Project',
},
},
};
export const noCurrentIterationResponse = { export const noCurrentIterationResponse = {
data: { data: {
workspace: { workspace: {
......
# frozen_string_literal: true
module IterationHelpers
def iteration_period(iteration)
"#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
end
end
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