Commit 326a388a authored by Simon Knox's avatar Simon Knox

Merge branch '349612-deemphasize-iteration-title' into 'master'

Use iteration dates as primary display attribute

See merge request gitlab-org/gitlab!78039
parents 8c582454 d72a96bf
...@@ -89,10 +89,6 @@ export default { ...@@ -89,10 +89,6 @@ export default {
listTitle() { listTitle() {
return this.list?.label?.description || this.list?.assignee?.name || this.list.title || ''; return this.list?.label?.description || this.list?.assignee?.name || this.list.title || '';
}, },
listIterationPeriod() {
const iteration = this.list?.iteration;
return iteration ? this.getIterationPeriod(iteration) : '';
},
isIterationList() { isIterationList() {
return this.listType === ListType.iteration; return this.listType === ListType.iteration;
}, },
...@@ -108,9 +104,6 @@ export default { ...@@ -108,9 +104,6 @@ export default {
showIterationListDetails() { showIterationListDetails() {
return this.isIterationList && this.showListDetails; return this.isIterationList && this.showListDetails;
}, },
iterationCadencesAvailable() {
return this.isIterationList && this.glFeatures.iterationCadences;
},
showListDetails() { showListDetails() {
return !this.list.collapsed || !this.isSwimlanesHeader; return !this.list.collapsed || !this.isSwimlanesHeader;
}, },
...@@ -344,13 +337,6 @@ export default { ...@@ -344,13 +337,6 @@ export default {
class="board-title-main-text gl-text-truncate" class="board-title-main-text gl-text-truncate"
> >
{{ listTitle }} {{ listTitle }}
<span
v-if="iterationCadencesAvailable"
class="gl-display-inline-block gl-text-gray-400"
data-testid="board-list-iteration-period"
>
{{ listIterationPeriod }}</span
>
</span> </span>
<span <span
v-if="listType === 'assignee'" v-if="listType === 'assignee'"
......
...@@ -13,7 +13,6 @@ import { ...@@ -13,7 +13,6 @@ import {
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import IterationPeriod from 'ee/iterations/components/iteration_period.vue';
import { getIterationPeriod } from '../utils'; import { getIterationPeriod } from '../utils';
import { Namespace } from '../constants'; import { Namespace } from '../constants';
import groupQuery from '../queries/group_iterations_in_cadence.query.graphql'; import groupQuery from '../queries/group_iterations_in_cadence.query.graphql';
...@@ -53,7 +52,6 @@ export default { ...@@ -53,7 +52,6 @@ export default {
GlModal, GlModal,
GlSkeletonLoader, GlSkeletonLoader,
TimeboxStatusBadge, TimeboxStatusBadge,
IterationPeriod,
}, },
apollo: { apollo: {
workspace: { workspace: {
...@@ -305,16 +303,12 @@ export default { ...@@ -305,16 +303,12 @@ export default {
v-for="iteration in iterations" v-for="iteration in iterations"
:key="iteration.id" :key="iteration.id"
class="gl-bg-gray-10 gl-p-5 gl-border-t-solid gl-border-gray-100 gl-border-t-1" class="gl-bg-gray-10 gl-p-5 gl-border-t-solid gl-border-gray-100 gl-border-t-1"
data-testid="iteration-item"
> >
<router-link :to="path(iteration.id)"> <router-link :to="path(iteration.id)">
{{ iteration.title }} {{ getIterationPeriod(iteration) }}
</router-link> </router-link>
<IterationPeriod class="gl-pt-2">{{ getIterationPeriod(iteration) }}</IterationPeriod> <timebox-status-badge v-if="showStateBadge" :state="iteration.state" />
<timebox-status-badge
v-if="showStateBadge"
class="gl-mt-2"
:state="iteration.state"
/>
</li> </li>
</ol> </ol>
<div v-if="loading" class="gl-p-5"> <div v-if="loading" class="gl-p-5">
......
<template>
<div class="gl-text-gray-400">
<slot></slot>
</div>
</template>
...@@ -18,8 +18,10 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; ...@@ -18,8 +18,10 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { Namespace } from '../constants'; import { Namespace } from '../constants';
import deleteIteration from '../queries/destroy_iteration.mutation.graphql'; import deleteIteration from '../queries/destroy_iteration.mutation.graphql';
import query from '../queries/iteration.query.graphql'; import query from '../queries/iteration.query.graphql';
import { getIterationPeriod } from '../utils';
import IterationReportTabs from './iteration_report_tabs.vue'; import IterationReportTabs from './iteration_report_tabs.vue';
import TimeboxStatusBadge from './timebox_status_badge.vue'; import TimeboxStatusBadge from './timebox_status_badge.vue';
import IterationTitle from './iteration_title.vue';
export default { export default {
components: { components: {
...@@ -33,6 +35,7 @@ export default { ...@@ -33,6 +35,7 @@ export default {
GlModal, GlModal,
IterationReportTabs, IterationReportTabs,
TimeboxStatusBadge, TimeboxStatusBadge,
IterationTitle,
}, },
directives: { directives: {
SafeHtml: GlSafeHtmlDirective, SafeHtml: GlSafeHtmlDirective,
...@@ -83,13 +86,16 @@ export default { ...@@ -83,13 +86,16 @@ export default {
return this.$router.currentRoute.params.iterationId; return this.$router.currentRoute.params.iterationId;
}, },
showEmptyState() { showEmptyState() {
return !this.loading && this.iteration && !this.iteration.title; return !this.loading && this.iteration && !this.iteration.startDate;
}, },
editPage() { editPage() {
return { return {
name: 'editIteration', name: 'editIteration',
}; };
}, },
iterationPeriod() {
return getIterationPeriod(this.iteration);
},
}, },
methods: { methods: {
formatDate(date) { formatDate(date) {
...@@ -143,9 +149,7 @@ export default { ...@@ -143,9 +149,7 @@ export default {
class="gl-display-flex gl-justify-items-center gl-align-items-center gl-py-3 gl-border-1 gl-border-b-solid gl-border-gray-100" class="gl-display-flex gl-justify-items-center gl-align-items-center gl-py-3 gl-border-1 gl-border-b-solid gl-border-gray-100"
> >
<timebox-status-badge :state="iteration.state" /> <timebox-status-badge :state="iteration.state" />
<span class="gl-ml-4" <span class="gl-ml-4">{{ iterationPeriod }}</span>
>{{ formatDate(iteration.startDate) }}{{ formatDate(iteration.dueDate) }}</span
>
<gl-dropdown <gl-dropdown
v-if="canEdit" v-if="canEdit"
ref="menu" ref="menu"
...@@ -181,7 +185,10 @@ export default { ...@@ -181,7 +185,10 @@ export default {
}} }}
</gl-modal> </gl-modal>
</div> </div>
<h3 ref="title" class="page-title">{{ iteration.title }}</h3> <div ref="heading">
<h3 class="page-title gl-mb-1" data-testid="">{{ iterationPeriod }}</h3>
<iteration-title v-if="iteration.title" :title="iteration.title" class="text-secondary" />
</div>
<div <div
ref="description" ref="description"
v-safe-html:[$options.safeHtmlConfig]="iteration.descriptionHtml" v-safe-html:[$options.safeHtmlConfig]="iteration.descriptionHtml"
......
...@@ -20,8 +20,10 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; ...@@ -20,8 +20,10 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { Namespace } from '../constants'; import { Namespace } from '../constants';
import deleteIteration from '../queries/destroy_iteration.mutation.graphql'; import deleteIteration from '../queries/destroy_iteration.mutation.graphql';
import query from '../queries/iteration.query.graphql'; import query from '../queries/iteration.query.graphql';
import { getIterationPeriod } from '../utils';
import IterationForm from './iteration_form_without_vue_router.vue'; import IterationForm from './iteration_form_without_vue_router.vue';
import IterationReportTabs from './iteration_report_tabs.vue'; import IterationReportTabs from './iteration_report_tabs.vue';
import IterationTitle from './iteration_title.vue';
const iterationStates = { const iterationStates = {
closed: 'closed', closed: 'closed',
...@@ -46,6 +48,7 @@ export default { ...@@ -46,6 +48,7 @@ export default {
GlLoadingIcon, GlLoadingIcon,
IterationForm, IterationForm,
IterationReportTabs, IterationReportTabs,
IterationTitle,
GlModal, GlModal,
}, },
directives: { directives: {
...@@ -134,7 +137,7 @@ export default { ...@@ -134,7 +137,7 @@ export default {
return this.$apollo.queries.iteration.loading; return this.$apollo.queries.iteration.loading;
}, },
showEmptyState() { showEmptyState() {
return !this.loading && this.iteration && !this.iteration.title; return !this.loading && this.iteration && !this.iteration.startDate;
}, },
status() { status() {
switch (this.iteration.state) { switch (this.iteration.state) {
...@@ -151,6 +154,9 @@ export default { ...@@ -151,6 +154,9 @@ export default {
return { text: __('Open'), variant: 'success' }; return { text: __('Open'), variant: 'success' };
} }
}, },
iterationPeriod() {
return getIterationPeriod(this.iteration);
},
}, },
mounted() { mounted() {
this.boundOnPopState = this.onPopState.bind(this); this.boundOnPopState = this.onPopState.bind(this);
...@@ -243,9 +249,7 @@ export default { ...@@ -243,9 +249,7 @@ export default {
<gl-badge :variant="status.variant"> <gl-badge :variant="status.variant">
{{ status.text }} {{ status.text }}
</gl-badge> </gl-badge>
<span class="gl-ml-4" <span class="gl-ml-4">{{ iterationPeriod }}</span>
>{{ formatDate(iteration.startDate) }}{{ formatDate(iteration.dueDate) }}</span
>
<gl-dropdown <gl-dropdown
v-if="canEditIteration" v-if="canEditIteration"
ref="menu" ref="menu"
...@@ -280,7 +284,10 @@ export default { ...@@ -280,7 +284,10 @@ export default {
}} }}
</gl-modal> </gl-modal>
</div> </div>
<h3 ref="title" class="page-title">{{ iteration.title }}</h3> <div ref="heading">
<h3 class="page-title gl-mb-1" data-testid="iteration-period">{{ iterationPeriod }}</h3>
<iteration-title v-if="iteration.title" :title="iteration.title" class="text-secondary" />
</div>
<div <div
ref="description" ref="description"
v-safe-html:[$options.safeHtmlConfig]="iteration.descriptionHtml" v-safe-html:[$options.safeHtmlConfig]="iteration.descriptionHtml"
......
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { Namespace } from 'ee/iterations/constants'; import { Namespace } from 'ee/iterations/constants';
import { formatDate } from '~/lib/utils/datetime_utility'; import { getIterationPeriod } from 'ee/iterations/utils';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
export default { export default {
components: { components: {
GlLink, GlLink,
IterationTitle,
}, },
props: { props: {
iterations: { iterations: {
...@@ -21,9 +23,7 @@ export default { ...@@ -21,9 +23,7 @@ export default {
}, },
}, },
methods: { methods: {
formatDate(date) { getIterationPeriod,
return formatDate(date, 'mmm d, yyyy', true);
},
}, },
}; };
</script> </script>
...@@ -31,14 +31,16 @@ export default { ...@@ -31,14 +31,16 @@ export default {
<template> <template>
<ul v-if="iterations.length > 0" class="content-list"> <ul v-if="iterations.length > 0" class="content-list">
<li v-for="iteration in iterations" :key="iteration.id" class="gl-p-4!"> <li v-for="iteration in iterations" :key="iteration.id" class="gl-p-4!">
<div class="gl-mb-3"> <div>
<gl-link :href="iteration.scopedPath || iteration.webPath"> <gl-link :href="iteration.scopedPath || iteration.webPath">
<strong>{{ iteration.title }}</strong> <strong>{{ getIterationPeriod(iteration) }}</strong>
</gl-link> </gl-link>
</div> </div>
<div class="text-secondary"> <iteration-title
{{ formatDate(iteration.startDate) }}{{ formatDate(iteration.dueDate) }} v-if="iteration.title"
</div> :title="iteration.title"
class="text-secondary gl-mt-3"
/>
</li> </li>
</ul> </ul>
<div v-else class="nothing-here-block"> <div v-else class="nothing-here-block">
......
<script> <script>
import { GlDropdownDivider, GlDropdownSectionHeader, GlFilteredSearchSuggestion } from '@gitlab/ui'; import { GlDropdownDivider, GlDropdownSectionHeader, GlFilteredSearchSuggestion } from '@gitlab/ui';
import { groupByIterationCadences } from 'ee/iterations/utils'; import { groupByIterationCadences, getIterationPeriod } from 'ee/iterations/utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import { DEFAULT_ITERATIONS } from '../constants'; import { DEFAULT_ITERATIONS } from '../constants';
export default { export default {
...@@ -14,6 +15,7 @@ export default { ...@@ -14,6 +15,7 @@ export default {
GlDropdownDivider, GlDropdownDivider,
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
IterationTitle,
}, },
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
props: { props: {
...@@ -65,6 +67,10 @@ export default { ...@@ -65,6 +67,10 @@ export default {
getId(iteration) { getId(iteration) {
return getIdFromGraphQLId(iteration.id).toString(); return getIdFromGraphQLId(iteration.id).toString();
}, },
iterationTokenText(iteration) {
const cadenceTitle = iteration.iterationCadence.title;
return `${cadenceTitle} ${getIterationPeriod(iteration)}`;
},
}, },
}; };
</script> </script>
...@@ -82,7 +88,7 @@ export default { ...@@ -82,7 +88,7 @@ export default {
v-on="$listeners" v-on="$listeners"
> >
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }"> <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
{{ activeTokenValue ? activeTokenValue.title : inputValue }} {{ activeTokenValue ? iterationTokenText(activeTokenValue) : inputValue }}
</template> </template>
<template #suggestions-list="{ suggestions }"> <template #suggestions-list="{ suggestions }">
<template v-for="(cadence, index) in groupIterationsByCadence(suggestions)"> <template v-for="(cadence, index) in groupIterationsByCadence(suggestions)">
...@@ -99,10 +105,8 @@ export default { ...@@ -99,10 +105,8 @@ export default {
:key="iteration.id" :key="iteration.id"
:value="getId(iteration)" :value="getId(iteration)"
> >
{{ iteration.title }} {{ iteration.period }}
<div v-if="glFeatures.iterationCadences" class="gl-text-gray-400"> <iteration-title v-if="iteration.title" :title="iteration.title" />
{{ iteration.period }}
</div>
</gl-filtered-search-suggestion> </gl-filtered-search-suggestion>
</template> </template>
</template> </template>
......
...@@ -172,6 +172,10 @@ module EE ...@@ -172,6 +172,10 @@ module EE
end end
end end
def period
"#{start_date.to_s(:medium)} - #{due_date.to_s(:medium)}"
end
def display_text def display_text
return period unless group.iteration_cadences_feature_flag_enabled? return period unless group.iteration_cadences_feature_flag_enabled?
...@@ -324,9 +328,5 @@ module EE ...@@ -324,9 +328,5 @@ module EE
errors.add(:title, _('already being used for another iteration within this cadence.')) if title_exists errors.add(:title, _('already being used for another iteration within this cadence.')) if title_exists
end end
def period
"#{start_date.to_s(:medium)} - #{due_date.to_s(:medium)}"
end
end end
end end
...@@ -51,12 +51,14 @@ RSpec.describe 'Issue board filters', :js do ...@@ -51,12 +51,14 @@ RSpec.describe 'Issue board filters', :js do
set_filter('iteration') set_filter('iteration')
end end
it 'loads all the iterations when opened and submit one as filter', :aggregate_failures, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348301' do it 'loads all the iterations when opened and submit one as filter', :aggregate_failures do
expect(find('.board:nth-child(1)')).to have_selector('.board-card', count: 2) expect(find('.board:nth-child(1)')).to have_selector('.board-card', count: 2)
expect_filtered_search_dropdown_results(filter_dropdown, 3) # 4 dropdown items must be shown
# None, Any, Current and iteration
expect_filtered_search_dropdown_results(filter_dropdown, 4)
click_on iteration.title click_on iteration.period
filter_submit.click filter_submit.click
expect(find('.board:nth-child(1)')).to have_selector('.board-card', count: 1) expect(find('.board:nth-child(1)')).to have_selector('.board-card', count: 1)
...@@ -86,6 +88,8 @@ RSpec.describe 'Issue board filters', :js do ...@@ -86,6 +88,8 @@ RSpec.describe 'Issue board filters', :js do
filter_input.click filter_input.click
filter_input.set("#{filter}:") filter_input.set("#{filter}:")
filter_first_suggestion.click # Select `=` operator filter_first_suggestion.click # Select `=` operator
wait_for_requests
end end
def expect_filtered_search_dropdown_results(filter_dropdown, count) def expect_filtered_search_dropdown_results(filter_dropdown, count)
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User adds milestone/iterations lists', :js do RSpec.describe 'User adds milestone/iterations lists', :js, :aggregate_failures 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) }
...@@ -64,10 +62,9 @@ RSpec.describe 'User adds milestone/iterations lists', :js do ...@@ -64,10 +62,9 @@ RSpec.describe 'User adds milestone/iterations lists', :js do
end end
it 'creates iteration column' do it 'creates iteration column' do
period = iteration_period(iteration) add_list('Iteration', iteration.period)
add_list('Iteration', period)
expect(page).to have_selector('.board', text: period) expect(page).to have_selector('.board', text: iteration.title)
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
......
...@@ -25,37 +25,37 @@ RSpec.describe 'Iterations list', :js do ...@@ -25,37 +25,37 @@ RSpec.describe 'Iterations list', :js do
it 'shows iterations on each tab' do it 'shows iterations on each tab' do
aggregate_failures do aggregate_failures do
expect(page).to have_link(current_iteration.title) expect(page).to have_link(current_iteration.period)
expect(page).to have_link(upcoming_iteration.title) expect(page).to have_link(upcoming_iteration.period)
expect(page).not_to have_link(closed_iteration.title) expect(page).not_to have_link(closed_iteration.period)
expect(page).not_to have_link(subgroup_iteration.title) expect(page).not_to have_link(subgroup_iteration.period)
expect(page).not_to have_link(subgroup_closed_iteration.title) expect(page).not_to have_link(subgroup_closed_iteration.period)
end end
click_link('Closed') click_link('Closed')
aggregate_failures do aggregate_failures do
expect(page).to have_link(closed_iteration.title) expect(page).to have_link(closed_iteration.period)
expect(page).not_to have_link(current_iteration.title) expect(page).not_to have_link(current_iteration.period)
expect(page).not_to have_link(upcoming_iteration.title) expect(page).not_to have_link(upcoming_iteration.period)
expect(page).not_to have_link(subgroup_iteration.title) expect(page).not_to have_link(subgroup_iteration.period)
expect(page).not_to have_link(subgroup_closed_iteration.title) expect(page).not_to have_link(subgroup_closed_iteration.period)
end end
click_link('All') click_link('All')
aggregate_failures do aggregate_failures do
expect(page).to have_link(current_iteration.title) expect(page).to have_link(current_iteration.period)
expect(page).to have_link(upcoming_iteration.title) expect(page).to have_link(upcoming_iteration.period)
expect(page).to have_link(closed_iteration.title) expect(page).to have_link(closed_iteration.period)
expect(page).not_to have_link(subgroup_iteration.title) expect(page).not_to have_link(subgroup_iteration.period)
expect(page).not_to have_link(subgroup_closed_iteration.title) expect(page).not_to have_link(subgroup_closed_iteration.period)
end end
end end
context 'when an iteration is clicked' do context 'when an iteration is clicked' do
it 'redirects to an iteration report within the group context' do it 'redirects to an iteration report within the group context' do
click_link('Started iteration') click_link(current_iteration.period)
wait_for_requests wait_for_requests
...@@ -71,31 +71,31 @@ RSpec.describe 'Iterations list', :js do ...@@ -71,31 +71,31 @@ RSpec.describe 'Iterations list', :js do
it 'shows iterations on each tab including ancestor iterations' do it 'shows iterations on each tab including ancestor iterations' do
aggregate_failures do aggregate_failures do
expect(page).to have_link(current_iteration.title) expect(page).to have_link(current_iteration.period)
expect(page).to have_link(upcoming_iteration.title) expect(page).to have_link(upcoming_iteration.period)
expect(page).not_to have_link(closed_iteration.title) expect(page).not_to have_link(closed_iteration.period)
expect(page).to have_link(subgroup_iteration.title) expect(page).to have_link(subgroup_iteration.period)
expect(page).not_to have_link(subgroup_closed_iteration.title) expect(page).not_to have_link(subgroup_closed_iteration.period)
end end
click_link('Closed') click_link('Closed')
aggregate_failures do aggregate_failures do
expect(page).to have_link(closed_iteration.title) expect(page).to have_link(closed_iteration.period)
expect(page).to have_link(subgroup_closed_iteration.title) expect(page).to have_link(subgroup_closed_iteration.period)
expect(page).not_to have_link(current_iteration.title) expect(page).not_to have_link(current_iteration.period)
expect(page).not_to have_link(upcoming_iteration.title) expect(page).not_to have_link(upcoming_iteration.period)
expect(page).not_to have_link(subgroup_iteration.title) expect(page).not_to have_link(subgroup_iteration.period)
end end
click_link('All') click_link('All')
aggregate_failures do aggregate_failures do
expect(page).to have_link(current_iteration.title) expect(page).to have_link(current_iteration.period)
expect(page).to have_link(upcoming_iteration.title) expect(page).to have_link(upcoming_iteration.period)
expect(page).to have_link(closed_iteration.title) expect(page).to have_link(closed_iteration.period)
expect(page).to have_link(subgroup_iteration.title) expect(page).to have_link(subgroup_iteration.period)
expect(page).to have_link(subgroup_closed_iteration.title) expect(page).to have_link(subgroup_closed_iteration.period)
end end
end end
end end
......
...@@ -23,15 +23,15 @@ RSpec.describe 'User views iteration cadences', :js do ...@@ -23,15 +23,15 @@ RSpec.describe 'User views iteration cadences', :js do
expect(page).to have_title('Iteration cadences') expect(page).to have_title('Iteration cadences')
expect(page).to have_content(cadence.title) expect(page).to have_content(cadence.title)
expect(page).to have_content(other_cadence.title) expect(page).to have_content(other_cadence.title)
expect(page).not_to have_content(iteration_in_cadence.title) expect(page).not_to have_content(iteration_in_cadence.period)
expect(page).not_to have_content(iteration_in_other_cadence.title) expect(page).not_to have_content(iteration_in_other_cadence.period)
click_button cadence.title click_button cadence.title
expect(page).to have_content(iteration_in_cadence.title) expect(page).to have_content(iteration_in_cadence.period)
expect(page).not_to have_content(subgroup_cadence.title) expect(page).not_to have_content(subgroup_cadence.title)
expect(page).not_to have_content(iteration_in_other_cadence.title) expect(page).not_to have_content(iteration_in_other_cadence.period)
expect(page).not_to have_content(closed_iteration_in_cadence.title) expect(page).not_to have_content(closed_iteration_in_cadence.period)
end end
it 'only shows completed iterations on Done tab', :aggregate_failures do it 'only shows completed iterations on Done tab', :aggregate_failures do
...@@ -39,8 +39,8 @@ RSpec.describe 'User views iteration cadences', :js do ...@@ -39,8 +39,8 @@ RSpec.describe 'User views iteration cadences', :js do
click_link 'Done' click_link 'Done'
click_button cadence.title click_button cadence.title
expect(page).not_to have_content(iteration_in_cadence.title) expect(page).not_to have_content(iteration_in_cadence.period)
expect(page).to have_content(closed_iteration_in_cadence.title) expect(page).to have_content(closed_iteration_in_cadence.period)
end end
it 'shows inherited cadences in subgroup', :aggregate_failures do it 'shows inherited cadences in subgroup', :aggregate_failures do
......
...@@ -126,18 +126,14 @@ RSpec.describe 'Filter issues by iteration', :js do ...@@ -126,18 +126,14 @@ RSpec.describe 'Filter issues by iteration', :js do
it 'shows cadence titles, and iteration titles and dates', :aggregate_failures do it 'shows cadence titles, and iteration titles and dates', :aggregate_failures do
within '.gl-filtered-search-suggestion-list' do within '.gl-filtered-search-suggestion-list' do
# cadence 1 grouping # cadence 1 grouping
expect(page).to have_css('li:nth-child(6)', text: "#{iteration_1.title} #{iteration_period(iteration_1)}") expect(page).to have_css('li:nth-child(6)', text: "#{iteration_1.period} #{iteration_1.title}")
expect(page).to have_css('li:nth-child(7)', text: "#{iteration_3.title} #{iteration_period(iteration_3)}") expect(page).to have_css('li:nth-child(7)', text: "#{iteration_3.period} #{iteration_3.title}")
# cadence 2 grouping # cadence 2 grouping
expect(page).to have_css('li:nth-child(9)', text: cadence_2.title) expect(page).to have_css('li:nth-child(9)', text: cadence_2.title)
expect(page).to have_css('li:nth-child(10)', text: "#{iteration_2.title} #{iteration_period(iteration_2)}") expect(page).to have_css('li:nth-child(10)', text: "#{iteration_2.period} #{iteration_2.title}")
end end
end end
end end
def iteration_period(iteration)
"#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
end
end end
context 'project issues list' do context 'project issues list' do
......
...@@ -27,14 +27,14 @@ RSpec.describe 'User views project iteration cadences', :js do ...@@ -27,14 +27,14 @@ RSpec.describe 'User views project iteration cadences', :js do
expect(page).to have_title('Iteration cadences') expect(page).to have_title('Iteration cadences')
expect(page).to have_content(cadence.title) expect(page).to have_content(cadence.title)
expect(page).to have_content(other_cadence.title) expect(page).to have_content(other_cadence.title)
expect(page).not_to have_content(iteration_in_cadence.title) expect(page).not_to have_content(iteration_in_cadence.period)
expect(page).not_to have_content(iteration_in_other_cadence.title) expect(page).not_to have_content(iteration_in_other_cadence.period)
click_button cadence.title click_button cadence.title
expect(page).to have_content(iteration_in_cadence.title) expect(page).to have_content(iteration_in_cadence.period)
expect(page).not_to have_content(iteration_in_other_cadence.title) expect(page).not_to have_content(iteration_in_other_cadence.period)
expect(page).not_to have_content(closed_iteration_in_cadence.title) expect(page).not_to have_content(closed_iteration_in_cadence.period)
expect(page).not_to have_link('New iteration cadence') expect(page).not_to have_link('New iteration cadence')
end end
end end
......
...@@ -18,21 +18,21 @@ RSpec.describe 'Iterations list', :js do ...@@ -18,21 +18,21 @@ RSpec.describe 'Iterations list', :js do
end end
it 'shows iterations on each tab', :aggregate_failures do it 'shows iterations on each tab', :aggregate_failures do
expect(page).to have_link(current_iteration.title, href: project_iteration_path(project, current_iteration.id)) expect(page).to have_link(current_iteration.period, href: project_iteration_path(project, current_iteration.id))
expect(page).to have_link(upcoming_iteration.title, href: project_iteration_path(project, upcoming_iteration.id)) expect(page).to have_link(upcoming_iteration.period, href: project_iteration_path(project, upcoming_iteration.id))
expect(page).not_to have_link(closed_iteration.title) expect(page).not_to have_link(closed_iteration.period)
click_link('Closed') click_link('Closed')
expect(page).to have_link(closed_iteration.title, href: project_iteration_path(project, closed_iteration.id)) expect(page).to have_link(closed_iteration.period, href: project_iteration_path(project, closed_iteration.id))
expect(page).not_to have_link(current_iteration.title) expect(page).not_to have_link(current_iteration.period)
expect(page).not_to have_link(upcoming_iteration.title) expect(page).not_to have_link(upcoming_iteration.period)
click_link('All') click_link('All')
expect(page).to have_link(current_iteration.title, href: project_iteration_path(project, current_iteration.id)) expect(page).to have_link(current_iteration.period, href: project_iteration_path(project, current_iteration.id))
expect(page).to have_link(upcoming_iteration.title, href: project_iteration_path(project, upcoming_iteration.id)) expect(page).to have_link(upcoming_iteration.period, href: project_iteration_path(project, upcoming_iteration.id))
expect(page).to have_link(closed_iteration.title, href: project_iteration_path(project, closed_iteration.id)) expect(page).to have_link(closed_iteration.period, href: project_iteration_path(project, closed_iteration.id))
end end
end end
......
...@@ -3,7 +3,6 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,7 +3,6 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardListHeader from 'ee/boards/components/board_list_header.vue'; import BoardListHeader from 'ee/boards/components/board_list_header.vue';
import defaultGetters from 'ee/boards/stores/getters'; import defaultGetters from 'ee/boards/stores/getters';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -21,12 +20,6 @@ const listMocks = { ...@@ -21,12 +20,6 @@ const listMocks = {
[ListType.assignee]: { [ListType.assignee]: {
assignee: {}, assignee: {},
}, },
[ListType.iteration]: {
iteration: {
startDate: '2021-11-01',
dueDate: '2021-11-05',
},
},
[ListType.label]: { [ListType.label]: {
...mockLabelList, ...mockLabelList,
}, },
...@@ -61,7 +54,6 @@ describe('Board List Header Component', () => { ...@@ -61,7 +54,6 @@ describe('Board List Header Component', () => {
currentUserId = 1, currentUserId = 1,
state = { activeId: inactiveId }, state = { activeId: inactiveId },
getters = {}, getters = {},
glFeatures = {},
} = {}) => { } = {}) => {
const boardId = '1'; const boardId = '1';
...@@ -101,13 +93,11 @@ describe('Board List Header Component', () => { ...@@ -101,13 +93,11 @@ describe('Board List Header Component', () => {
boardId, boardId,
weightFeatureAvailable, weightFeatureAvailable,
currentUserId, currentUserId,
glFeatures,
}, },
}); });
}; };
const findSettingsButton = () => wrapper.findComponent({ ref: 'settingsBtn' }); const findSettingsButton = () => wrapper.findComponent({ ref: 'settingsBtn' });
const findIterationPeriod = () => wrapper.find('[data-testid="board-list-iteration-period"]');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -223,28 +213,4 @@ describe('Board List Header Component', () => { ...@@ -223,28 +213,4 @@ describe('Board List Header Component', () => {
expect(wrapper.findComponent({ ref: 'weightTooltip' }).exists()).toBe(false); expect(wrapper.findComponent({ ref: 'weightTooltip' }).exists()).toBe(false);
}); });
}); });
describe('iteration cadence', () => {
describe('iteration_cadences feature flag is on', () => {
it('displays iteration period', () => {
createComponent({
listType: ListType.iteration,
glFeatures: {
iterationCadences: true,
},
});
expect(findIterationPeriod().text()).toContain('Nov 1, 2021 - Nov 5, 2021');
expect(findIterationPeriod().isVisible()).toBe(true);
});
});
describe('iteration_cadences feature flag is off', () => {
it('does not display iteration period', () => {
createComponent({ listType: ListType.iteration });
expect(findIterationPeriod().exists()).toBe(false);
});
});
});
}); });
...@@ -6,6 +6,7 @@ import VueApollo from 'vue-apollo'; ...@@ -6,6 +6,7 @@ import VueApollo from 'vue-apollo';
import IterationCadenceListItem from 'ee/iterations/components/iteration_cadence_list_item.vue'; import IterationCadenceListItem from 'ee/iterations/components/iteration_cadence_list_item.vue';
import TimeboxStatusBadge from 'ee/iterations/components/timebox_status_badge.vue'; import TimeboxStatusBadge from 'ee/iterations/components/timebox_status_badge.vue';
import { Namespace } from 'ee/iterations/constants'; import { Namespace } from 'ee/iterations/constants';
import { getIterationPeriod } from 'ee/iterations/utils';
import groupIterationsInCadenceQuery from 'ee/iterations/queries/group_iterations_in_cadence.query.graphql'; import groupIterationsInCadenceQuery from 'ee/iterations/queries/group_iterations_in_cadence.query.graphql';
import projectIterationsInCadenceQuery from 'ee/iterations/queries/project_iterations_in_cadence.query.graphql'; import projectIterationsInCadenceQuery from 'ee/iterations/queries/project_iterations_in_cadence.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -41,10 +42,18 @@ describe('Iteration cadence list item', () => { ...@@ -41,10 +42,18 @@ describe('Iteration cadence list item', () => {
webPath: '/groups/group1/-/iterations/41', webPath: '/groups/group1/-/iterations/41',
__typename: 'Iteration', __typename: 'Iteration',
}, },
{
id: 'gid://gitlab/Iteration/42',
scopedPath: '/groups/group1/-/iterations/42',
startDate: '2021-08-15',
dueDate: '2021-08-20',
state: 'upcoming',
title: null,
webPath: '/groups/group1/-/iterations/42',
__typename: 'Iteration',
},
]; ];
const iterationPeriods = ['Aug 13, 2021 - Aug 14, 2021'];
const cadence = { const cadence = {
id: 'gid://gitlab/Iterations::Cadence/561', id: 'gid://gitlab/Iterations::Cadence/561',
title: 'Weekly cadence', title: 'Weekly cadence',
...@@ -129,6 +138,7 @@ describe('Iteration cadence list item', () => { ...@@ -129,6 +138,7 @@ describe('Iteration cadence list item', () => {
const findLoader = () => wrapper.findComponent(GlSkeletonLoader); const findLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findCreateIterationButton = () => const findCreateIterationButton = () =>
wrapper.findByRole('link', { text: i18n.createIteration }); wrapper.findByRole('link', { text: i18n.createIteration });
const findIterationItemText = (i) => wrapper.findAllByTestId('iteration-item').at(i).text();
const expand = () => wrapper.findByRole('button', { text: cadence.title }).trigger('click'); const expand = () => wrapper.findByRole('button', { text: cadence.title }).trigger('click');
afterEach(() => { afterEach(() => {
...@@ -181,17 +191,22 @@ describe('Iteration cadence list item', () => { ...@@ -181,17 +191,22 @@ describe('Iteration cadence list item', () => {
expect(findCreateIterationButton().exists()).toBe(canCreateIteration); expect(findCreateIterationButton().exists()).toBe(canCreateIteration);
}); });
it('shows iterations with dates after loading', async () => { const expectIterationItemToHavePeriod = () => {
iterations.forEach(({ startDate, dueDate }, i) => {
const containedText = findIterationItemText(i);
expect(containedText).toContain(getIterationPeriod({ startDate, dueDate }));
});
};
it('shows iteration dates after loading', async () => {
await createComponent(); await createComponent();
expand(); expand();
await waitForPromises(); await waitForPromises();
iterations.forEach(({ title }, i) => { expectIterationItemToHavePeriod();
expect(wrapper.text()).toContain(title);
expect(wrapper.text()).toContain(iterationPeriods[i]);
});
}); });
it('automatically expands for newly created cadence', async () => { it('automatically expands for newly created cadence', async () => {
...@@ -201,9 +216,7 @@ describe('Iteration cadence list item', () => { ...@@ -201,9 +216,7 @@ describe('Iteration cadence list item', () => {
await waitForPromises(); await waitForPromises();
iterations.forEach(({ title }) => { expectIterationItemToHavePeriod();
expect(wrapper.text()).toContain(title);
});
}); });
it('loads project iterations for Project namespaceType', async () => { it('loads project iterations for Project namespaceType', async () => {
...@@ -216,9 +229,7 @@ describe('Iteration cadence list item', () => { ...@@ -216,9 +229,7 @@ describe('Iteration cadence list item', () => {
await waitForPromises(); await waitForPromises();
iterations.forEach(({ title }) => { expectIterationItemToHavePeriod();
expect(wrapper.text()).toContain(title);
});
}); });
it('shows alert on query error', async () => { it('shows alert on query error', async () => {
......
...@@ -11,7 +11,14 @@ import query from 'ee/iterations/queries/iteration.query.graphql'; ...@@ -11,7 +11,14 @@ import query from 'ee/iterations/queries/iteration.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { mockIterationNode, mockGroupIterations, mockProjectIterations } from '../mock_data'; import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import { getIterationPeriod } from 'ee/iterations/utils';
import {
mockIterationNode,
createMockGroupIterations,
mockIterationNodeWithoutTitle,
mockProjectIterations,
} from '../mock_data';
const $router = { const $router = {
push: jest.fn(), push: jest.fn(),
...@@ -36,7 +43,7 @@ describe('Iterations report', () => { ...@@ -36,7 +43,7 @@ describe('Iterations report', () => {
const labelsFetchPath = '/labels.json'; const labelsFetchPath = '/labels.json';
const findTopbar = () => wrapper.findComponent({ ref: 'topbar' }); const findTopbar = () => wrapper.findComponent({ ref: 'topbar' });
const findTitle = () => wrapper.findComponent({ ref: 'title' }); const findHeading = () => wrapper.findComponent({ ref: 'heading' });
const findDescription = () => wrapper.findComponent({ ref: 'description' }); const findDescription = () => wrapper.findComponent({ ref: 'description' });
const findActionsDropdown = () => wrapper.find('[data-testid="actions-dropdown"]'); const findActionsDropdown = () => wrapper.find('[data-testid="actions-dropdown"]');
...@@ -45,7 +52,7 @@ describe('Iterations report', () => { ...@@ -45,7 +52,7 @@ describe('Iterations report', () => {
const mountComponent = ({ const mountComponent = ({
props = defaultProps, props = defaultProps,
mockQueryResponse = mockGroupIterations, mockQueryResponse = createMockGroupIterations(),
iterationQueryHandler = jest.fn().mockResolvedValue(mockQueryResponse), iterationQueryHandler = jest.fn().mockResolvedValue(mockQueryResponse),
deleteMutationResponse = { data: { iterationDelete: { errors: [] } } }, deleteMutationResponse = { data: { iterationDelete: { errors: [] } } },
deleteMutationMock = jest.fn().mockResolvedValue(deleteMutationResponse), deleteMutationMock = jest.fn().mockResolvedValue(deleteMutationResponse),
...@@ -80,6 +87,7 @@ describe('Iterations report', () => { ...@@ -80,6 +87,7 @@ describe('Iterations report', () => {
GlLoadingIcon, GlLoadingIcon,
GlTab, GlTab,
GlTabs, GlTabs,
IterationTitle,
}, },
}); });
}; };
...@@ -87,55 +95,53 @@ describe('Iterations report', () => { ...@@ -87,55 +95,53 @@ describe('Iterations report', () => {
describe('with mock apollo', () => { describe('with mock apollo', () => {
describe.each([ describe.each([
[ [
'group', Namespace.Group,
{ 'group-name',
fullPath: 'group-name', mockIterationNodeWithoutTitle,
iterationId: String(getIdFromGraphQLId(mockIterationNode.id)), createMockGroupIterations(mockIterationNodeWithoutTitle),
namespaceType: Namespace.Group,
},
mockGroupIterations,
{
fullPath: 'group-name',
id: mockIterationNode.id,
isGroup: true,
},
], ],
[ [
'project', Namespace.Group,
{ 'group-name',
fullPath: 'group-name/project-name', mockIterationNode,
iterationId: String(getIdFromGraphQLId(mockIterationNode.id)), createMockGroupIterations(mockIterationNode),
namespaceType: Namespace.Project,
},
mockProjectIterations,
{
fullPath: 'group-name/project-name',
id: mockIterationNode.id,
isGroup: false,
},
], ],
])('when viewing an iteration in a %s', (_, props, mockIteration, expectedParams) => { [Namespace.Project, 'group-name/project-name', mockIterationNode, mockProjectIterations],
it('calls a query with correct parameters', () => { ])(
const iterationQueryHandler = jest.fn().mockResolvedValue(mockIteration); 'when viewing an iteration in a %s',
mountComponent({ (namespaceType, fullPath, mockIteration, mockIterations) => {
props, let iterationQueryHandler;
iterationQueryHandler,
beforeEach(() => {
iterationQueryHandler = jest.fn().mockResolvedValue(mockIterations);
mountComponent({
props: {
namespaceType,
fullPath,
iterationId: String(getIdFromGraphQLId(mockIteration.id)),
},
iterationQueryHandler,
});
}); });
expect(iterationQueryHandler).toHaveBeenNthCalledWith(1, expectedParams); it('calls a query with correct parameters', () => {
}); expect(iterationQueryHandler).toHaveBeenNthCalledWith(1, {
fullPath,
it('renders an iteration title', async () => { id: mockIteration.id,
mountComponent({ isGroup: namespaceType === Namespace.Group,
props, });
iterationQueryHandler: jest.fn().mockResolvedValue(mockIteration),
}); });
await waitForPromises(); it('renders iteration dates optionally with title', async () => {
await waitForPromises();
expect(findTitle().text()).toContain(mockIterationNode.title); expect(findHeading().text()).toContain(getIterationPeriod(mockIteration));
});
}); if (mockIteration.title) expect(findHeading().text()).toContain(mockIteration.title);
});
},
);
}); });
afterEach(() => { afterEach(() => {
...@@ -215,7 +221,7 @@ describe('Iterations report', () => { ...@@ -215,7 +221,7 @@ describe('Iterations report', () => {
await waitForPromises(); await waitForPromises();
expect(findEmptyState().props('title')).toBe('Could not find iteration'); expect(findEmptyState().props('title')).toBe('Could not find iteration');
expect(findTitle().exists()).toBe(false); expect(findHeading().exists()).toBe(false);
expect(findDescription().exists()).toBe(false); expect(findDescription().exists()).toBe(false);
expect(findActionsDropdown().exists()).toBe(false); expect(findActionsDropdown().exists()).toBe(false);
}); });
...@@ -225,7 +231,9 @@ describe('Iterations report', () => { ...@@ -225,7 +231,9 @@ describe('Iterations report', () => {
describe('user without edit permission', () => { describe('user without edit permission', () => {
beforeEach(async () => { beforeEach(async () => {
mountComponent({ mountComponent({
iterationQueryHandler: jest.fn().mockResolvedValue(mockGroupIterations), iterationQueryHandler: jest
.fn()
.mockResolvedValue(createMockGroupIterations(mockIterationNode)),
}); });
await waitForPromises(); await waitForPromises();
...@@ -246,8 +254,8 @@ describe('Iterations report', () => { ...@@ -246,8 +254,8 @@ describe('Iterations report', () => {
expect(findEmptyState().exists()).toBe(false); expect(findEmptyState().exists()).toBe(false);
}); });
it('shows title', () => { it('shows iteration dates', () => {
expect(findTitle().text()).toContain(mockIterationNode.title); expect(findHeading().text()).toContain(getIterationPeriod(mockIterationNode));
}); });
it('shows description', () => { it('shows description', () => {
...@@ -282,7 +290,7 @@ describe('Iterations report', () => { ...@@ -282,7 +290,7 @@ describe('Iterations report', () => {
({ canEdit, namespaceType, canEditIteration }) => { ({ canEdit, namespaceType, canEditIteration }) => {
beforeEach(async () => { beforeEach(async () => {
const mockQueryResponse = { const mockQueryResponse = {
[Namespace.Group]: mockGroupIterations, [Namespace.Group]: createMockGroupIterations(mockIterationNode),
[Namespace.Project]: mockProjectIterations, [Namespace.Project]: mockProjectIterations,
}[namespaceType]; }[namespaceType];
......
...@@ -10,7 +10,14 @@ import query from 'ee/iterations/queries/iteration.query.graphql'; ...@@ -10,7 +10,14 @@ import query from 'ee/iterations/queries/iteration.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { mockIterationNode, mockGroupIterations, mockProjectIterations } from '../mock_data'; import { getIterationPeriod } from 'ee/iterations/utils';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import {
mockIterationNode,
mockIterationNodeWithoutTitle,
createMockGroupIterations,
mockProjectIterations,
} from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -24,7 +31,7 @@ describe('Iterations report', () => { ...@@ -24,7 +31,7 @@ describe('Iterations report', () => {
}; };
const findTopbar = () => wrapper.findComponent({ ref: 'topbar' }); const findTopbar = () => wrapper.findComponent({ ref: 'topbar' });
const findTitle = () => wrapper.findComponent({ ref: 'title' }); const findHeading = () => wrapper.findComponent({ ref: 'heading' });
const findDescription = () => wrapper.findComponent({ ref: 'description' }); const findDescription = () => wrapper.findComponent({ ref: 'description' });
const findActionsDropdown = () => wrapper.find('[data-testid="actions-dropdown"]'); const findActionsDropdown = () => wrapper.find('[data-testid="actions-dropdown"]');
const clickEditButton = () => { const clickEditButton = () => {
...@@ -53,6 +60,7 @@ describe('Iterations report', () => { ...@@ -53,6 +60,7 @@ describe('Iterations report', () => {
GlLoadingIcon, GlLoadingIcon,
GlTab, GlTab,
GlTabs, GlTabs,
IterationTitle,
}, },
}); });
}; };
...@@ -60,54 +68,53 @@ describe('Iterations report', () => { ...@@ -60,54 +68,53 @@ describe('Iterations report', () => {
describe('with mock apollo', () => { describe('with mock apollo', () => {
describe.each([ describe.each([
[ [
'group', Namespace.Group,
{ 'group-name',
fullPath: 'group-name', mockIterationNodeWithoutTitle,
iterationId: String(getIdFromGraphQLId(mockIterationNode.id)), createMockGroupIterations(mockIterationNodeWithoutTitle),
},
mockGroupIterations,
{
fullPath: 'group-name',
id: mockIterationNode.id,
isGroup: true,
},
], ],
[ [
'project', Namespace.Group,
{ 'group-name',
fullPath: 'group-name/project-name', mockIterationNode,
iterationId: String(getIdFromGraphQLId(mockIterationNode.id)), createMockGroupIterations(mockIterationNode),
namespaceType: Namespace.Project,
},
mockProjectIterations,
{
fullPath: 'group-name/project-name',
id: mockIterationNode.id,
isGroup: false,
},
], ],
])('when viewing an iteration in a %s', (_, props, mockIteration, expectedParams) => { [Namespace.Project, 'group-name/project-name', mockIterationNode, mockProjectIterations],
it('calls a query with correct parameters', () => { ])(
const iterationQueryHandler = jest.fn(); 'when viewing an iteration in a %s',
mountComponentWithApollo({ (namespaceType, fullPath, mockIteration, mockIterations) => {
props, let iterationQueryHandler;
iterationQueryHandler,
});
expect(iterationQueryHandler).toHaveBeenNthCalledWith(1, expectedParams); beforeEach(() => {
}); iterationQueryHandler = jest.fn().mockResolvedValue(mockIterations);
mountComponentWithApollo({
props: {
namespaceType,
fullPath,
iterationId: String(getIdFromGraphQLId(mockIteration.id)),
},
iterationQueryHandler,
});
});
it('renders an iteration title', async () => { it('calls a query with correct parameters', () => {
mountComponentWithApollo({ expect(iterationQueryHandler).toHaveBeenNthCalledWith(1, {
props, fullPath,
iterationQueryHandler: jest.fn().mockResolvedValue(mockIteration), id: mockIteration.id,
isGroup: namespaceType === Namespace.Group,
});
}); });
await waitForPromises(); it('renders iteration dates optionally with title', async () => {
await waitForPromises();
expect(findTitle().text()).toContain(mockIterationNode.title); expect(findHeading().text()).toContain(getIterationPeriod(mockIteration));
});
}); if (mockIteration.title) expect(findHeading().text()).toContain(mockIteration.title);
});
},
);
}); });
const mountComponent = ({ props = defaultProps, loading = false } = {}) => { const mountComponent = ({ props = defaultProps, loading = false } = {}) => {
...@@ -149,7 +156,7 @@ describe('Iterations report', () => { ...@@ -149,7 +156,7 @@ describe('Iterations report', () => {
}); });
expect(findEmptyState().props('title')).toBe('Could not find iteration'); expect(findEmptyState().props('title')).toBe('Could not find iteration');
expect(findTitle().exists()).toBe(false); expect(findHeading().exists()).toBe(false);
expect(findDescription().exists()).toBe(false); expect(findDescription().exists()).toBe(false);
expect(findActionsDropdown().exists()).toBe(false); expect(findActionsDropdown().exists()).toBe(false);
}); });
...@@ -157,7 +164,7 @@ describe('Iterations report', () => { ...@@ -157,7 +164,7 @@ describe('Iterations report', () => {
describe('item loaded', () => { describe('item loaded', () => {
const iteration = { const iteration = {
title: 'June week 1', title: null,
id: 'gid://gitlab/Iteration/2', id: 'gid://gitlab/Iteration/2',
descriptionHtml: 'The first week of June', descriptionHtml: 'The first week of June',
startDate: '2020-06-02', startDate: '2020-06-02',
...@@ -189,8 +196,8 @@ describe('Iterations report', () => { ...@@ -189,8 +196,8 @@ describe('Iterations report', () => {
expect(findEmptyState().exists()).toBe(false); expect(findEmptyState().exists()).toBe(false);
}); });
it('shows title and description', () => { it('shows period and description', () => {
expect(findTitle().text()).toContain(iteration.title); expect(findHeading().text()).toContain('Jun 2, 2020 - Jun 8, 2020');
expect(findDescription().text()).toContain(iteration.descriptionHtml); expect(findDescription().text()).toContain(iteration.descriptionHtml);
}); });
......
...@@ -2,6 +2,8 @@ import { GlLink } from '@gitlab/ui'; ...@@ -2,6 +2,8 @@ import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock'; import timezoneMock from 'timezone-mock';
import IterationsList from 'ee/iterations/components/iterations_list.vue'; import IterationsList from 'ee/iterations/components/iterations_list.vue';
import IterationTitle from 'ee/iterations/components/iteration_title.vue';
import { getIterationPeriod } from 'ee/iterations/utils';
describe('Iterations list', () => { describe('Iterations list', () => {
let wrapper; let wrapper;
...@@ -11,6 +13,9 @@ describe('Iterations list', () => { ...@@ -11,6 +13,9 @@ describe('Iterations list', () => {
const mountComponent = (propsData = { iterations: [] }) => { const mountComponent = (propsData = { iterations: [] }) => {
wrapper = shallowMount(IterationsList, { wrapper = shallowMount(IterationsList, {
propsData, propsData,
stubs: {
IterationTitle,
},
}); });
}; };
...@@ -29,7 +34,7 @@ describe('Iterations list', () => { ...@@ -29,7 +34,7 @@ describe('Iterations list', () => {
describe('with iterations', () => { describe('with iterations', () => {
const iteration = { const iteration = {
id: '123', id: '123',
title: 'Iteration #1', title: null,
startDate: '2020-05-27', startDate: '2020-05-27',
dueDate: '2020-06-04', dueDate: '2020-06-04',
scopedPath: null, scopedPath: null,
...@@ -42,7 +47,19 @@ describe('Iterations list', () => { ...@@ -42,7 +47,19 @@ describe('Iterations list', () => {
}); });
expect(wrapper.html()).not.toHaveText('No iterations to show'); expect(wrapper.html()).not.toHaveText('No iterations to show');
expect(wrapper.html()).toHaveText(iteration.title); expect(findGlLink().text()).toBe(getIterationPeriod(iteration));
});
describe('when iteration has a title', () => {
it('shows iteration with title', () => {
mountComponent({
iterations: [{ ...iteration, title: 'Iteration #1' }],
});
expect(wrapper.html()).not.toHaveText('No iterations to show');
expect(findGlLink().text()).toBe(getIterationPeriod(iteration));
expect(wrapper.html()).toHaveText('Iteration #1');
});
}); });
it('displays dates in UTC time, regardless of user timezone', () => { it('displays dates in UTC time, regardless of user timezone', () => {
......
...@@ -11,6 +11,11 @@ export const mockIterationNode = { ...@@ -11,6 +11,11 @@ export const mockIterationNode = {
__typename: 'Iteration', __typename: 'Iteration',
}; };
export const mockIterationNodeWithoutTitle = {
...mockIterationNode,
title: null,
};
export const mockGroupIterations = { export const mockGroupIterations = {
data: { data: {
group: { group: {
...@@ -24,6 +29,21 @@ export const mockGroupIterations = { ...@@ -24,6 +29,21 @@ export const mockGroupIterations = {
}, },
}; };
export const createMockGroupIterations = (mockIteration = mockIterationNode) => {
return {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterations: {
nodes: [mockIteration],
__typename: 'IterationConnection',
},
__typename: 'Group',
},
},
};
};
export const mockProjectIterations = { export const mockProjectIterations = {
data: { data: {
project: { project: {
......
import { import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
GlFilteredSearchToken,
GlFilteredSearchTokenSegment,
GlFilteredSearchSuggestion,
} from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash'; import createFlash from '~/flash';
import IterationToken from 'ee/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import IterationToken from 'ee/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue';
import { mockIterationToken, mockIterations } from '../mock_data'; import { mockIterationToken } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -42,32 +38,6 @@ describe('IterationToken', () => { ...@@ -42,32 +38,6 @@ describe('IterationToken', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('when iteration cadence feature is available', () => {
beforeEach(async () => {
wrapper = createComponent({
active: true,
config: { ...mockIterationToken, initialIterations: mockIterations },
value: { data: 'i' },
stubs: { Portal: true },
provide: {
glFeatures: {
iterationCadences: true,
},
},
});
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
await wrapper.setData({ loading: false });
});
it('renders iteration start date and due date', () => {
const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion);
expect(suggestions.at(3).text()).toContain('Nov 5, 2021 - Nov 10, 2021');
});
});
it('renders iteration value', async () => { it('renders iteration value', async () => {
wrapper = createComponent({ value: { data: id } }); wrapper = createComponent({ value: { data: id } });
......
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