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