Commit 29c6a47c authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '353349-update-iteration-form' into 'master'

Deprecate manual iteration management for automatic iteration cadences

See merge request gitlab-org/gitlab!83716
parents 1a7d4adb b8e814ed
<script>
import { GlAlert, GlButton, GlForm, GlFormInput } from '@gitlab/ui';
import initDatePicker from '~/behaviors/date_picker';
import { GlAlert, GlButton, GlDatepicker, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
import createFlash from '~/flash';
import { dayAfter, formatDate } from '~/lib/utils/datetime_utility';
import { TYPE_ITERATIONS_CADENCE } from '~/graphql_shared/constants';
import { TYPE_ITERATION, TYPE_ITERATIONS_CADENCE } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
......@@ -14,14 +13,33 @@ import updateIteration from '../queries/update_iteration.mutation.graphql';
import iterationsInCadence from '../queries/group_iterations_in_cadence.query.graphql';
export default {
i18n: {
title: {
edit: s__('Iterations|Edit iteration'),
new: s__('Iterations|New iteration'),
},
form: {
title: s__('Iterations|Title'),
startDate: s__('Iterations|Start date'),
dueDate: s__('Iterations|Due date'),
description: s__('Iterations|Description'),
},
submitButton: {
save: s__('Iterations|Save changes'),
create: s__('Iterations|Create iteration'),
},
cancelButton: s__('Iterations|Cancel'),
},
cadencesList: {
name: 'index',
},
components: {
GlAlert,
GlDatepicker,
GlButton,
GlForm,
GlFormInput,
GlFormGroup,
MarkdownField,
},
apollo: {
......@@ -30,15 +48,13 @@ export default {
skip() {
return !this.iterationId;
},
/* eslint-disable @gitlab/require-i18n-strings */
variables() {
return {
fullPath: this.fullPath,
id: convertToGraphQLId('Iteration', this.iterationId),
id: convertToGraphQLId(TYPE_ITERATION, this.iterationId),
isGroup: true,
};
},
/* eslint-enable @gitlab/require-i18n-strings */
result({ data }) {
const iteration = data.group.iterations?.nodes[0];
......@@ -49,8 +65,9 @@ export default {
this.title = iteration.title;
this.description = iteration.description;
this.startDate = iteration.startDate;
this.dueDate = iteration.dueDate;
this.startDate = new Date(iteration.startDate);
this.dueDate = new Date(iteration.dueDate);
this.automatic = iteration.iterationCadence.automatic;
return iteration;
},
......@@ -68,8 +85,9 @@ export default {
cadence: {},
title: '',
description: '',
startDate: '',
dueDate: '',
startDate: null,
dueDate: null,
automatic: null,
};
},
computed: {
......@@ -82,20 +100,42 @@ export default {
isEditing() {
return Boolean(this.iterationId);
},
variables() {
isAutoModeEdit() {
return this.isEditing && this.automatic;
},
formattedStartDate() {
return formatDate(this.startDate, 'yyyy-mm-dd');
},
formattedDueDate() {
return formatDate(this.dueDate, 'yyyy-mm-dd');
},
formattedDates() {
return {
startDate: this.formattedStartDate,
dueDate: this.formattedDueDate,
};
},
createVariables() {
return {
groupPath: this.fullPath,
title: this.title,
description: this.description,
startDate: this.startDate,
dueDate: this.dueDate,
...this.formattedDates,
};
},
updateVariables() {
const baseVariables = {
id: this.iterationId,
groupPath: this.fullPath,
description: this.description,
};
return this.automatic
? baseVariables
: { ...baseVariables, title: this.title, ...this.formattedDates };
},
},
async mounted() {
// TODO: utilize GlDatepicker instead of relying on this jQuery behavior
initDatePicker();
// prefill start date for the New cadence form
// if there's iterations in the cadence, use last end_date + 1
// else use cadence startDate
......@@ -112,10 +152,7 @@ export default {
const iteration = data.workspace.iterations?.nodes[0];
if (iteration) {
this.startDate = formatDate(
dayAfter(new Date(iteration.dueDate), { utc: true }),
'yyyy-mm-dd',
);
this.startDate = dayAfter(new Date(iteration.dueDate), { utc: true });
} else {
const { data: cadenceData } = await this.$apollo.query({
query: readCadence,
......@@ -133,7 +170,7 @@ export default {
return;
}
this.startDate = cadence.startDate;
this.startDate = cadence.startDate ? new Date(cadence.startDate) : null;
}
}
}
......@@ -149,8 +186,8 @@ export default {
mutation: createIteration,
variables: {
input: {
...this.variables,
iterationsCadenceId: convertToGraphQLId('Iterations::Cadence', this.cadenceId),
...this.createVariables,
iterationsCadenceId: convertToGraphQLId(TYPE_ITERATIONS_CADENCE, this.cadenceId),
},
},
})
......@@ -185,10 +222,7 @@ export default {
.mutate({
mutation: updateIteration,
variables: {
input: {
...this.variables,
id: this.iterationId,
},
input: this.updateVariables,
},
})
.then(({ data }) => {
......@@ -231,7 +265,7 @@ export default {
<div>
<div class="gl-display-flex">
<h3 ref="pageTitle" class="page-title">
{{ isEditing ? s__('Iterations|Edit iteration') : s__('Iterations|New iteration') }}
{{ isEditing ? $options.i18n.title.edit : $options.i18n.title.new }}
</h3>
</div>
<hr class="gl-mt-0" />
......@@ -239,96 +273,79 @@ export default {
<gl-alert v-if="error" class="gl-mb-5" variant="danger" @dismiss="error = ''">{{
error
}}</gl-alert>
<gl-form class="row common-note-form">
<div class="col-md-6">
<div class="form-group row">
<div class="col-form-label col-sm-2">
<label for="iteration-title">{{ __('Title') }}</label>
</div>
<div class="col-sm-10">
<gl-form-input
id="iteration-title"
v-model="title"
autocomplete="off"
data-qa-selector="iteration_title_field"
/>
</div>
</div>
<div class="form-group row">
<div class="col-form-label col-sm-2">
<label for="iteration-description">{{ __('Description') }}</label>
</div>
<div class="col-sm-10">
<markdown-field
:markdown-preview-path="previewMarkdownPath"
:can-attach-file="false"
:enable-autocomplete="true"
label="Description"
:textarea-value="description"
markdown-docs-path="/help/user/markdown"
:add-spacing-classes="false"
class="md-area"
<gl-form class="common-note-form">
<p v-if="isAutoModeEdit && title">
<span class="gl-font-weight-bold gl-display-block gl-pb-3">{{
$options.i18n.form.title
}}</span>
<span>{{ title }}</span>
</p>
<gl-form-group
v-else-if="!automatic || !isEditing"
:label="$options.i18n.form.title"
label-for="iteration-title"
>
<gl-form-input
id="iteration-title"
v-model="title"
autocomplete="off"
data-qa-selector="iteration_title_field"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.form.startDate" label-for="iteration-start-date">
<span v-if="isAutoModeEdit">{{ formattedStartDate }}</span>
<gl-datepicker
v-else
input-id="iteration-start-date"
data-testid="start-date"
:value="startDate"
show-clear-button
autocomplete="off"
data-qa-selector="iteration_start_date_field"
@input="updateStartDate"
@clear="updateStartDate(null)"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.form.dueDate" label-for="iteration-due-date">
<span v-if="isAutoModeEdit">{{ formattedDueDate }}</span>
<gl-datepicker
v-else
input-id="iteration-due-date"
data-testid="due-date"
:value="dueDate"
show-clear-button
autocomplete="off"
data-qa-selector="iteration_due_date_field"
@input="updateDueDate"
@clear="updateDueDate(null)"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.form.description" label-for="iteration-description">
<markdown-field
:markdown-preview-path="previewMarkdownPath"
:can-attach-file="false"
:enable-autocomplete="true"
label="Description"
:textarea-value="description"
markdown-docs-path="/help/user/markdown"
:add-spacing-classes="false"
class="md-area"
>
<template #textarea>
<textarea
id="iteration-description"
v-model="description"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
:aria-label="$options.i18n.form.description"
data-qa-selector="iteration_description_field"
>
<template #textarea>
<textarea
id="iteration-description"
v-model="description"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
:aria-label="__('Description')"
data-qa-selector="iteration_description_field"
>
</textarea>
</template>
</markdown-field>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group row">
<div class="col-form-label col-sm-2">
<label for="iteration-start-date">{{ __('Start date') }}</label>
</div>
<div class="col-sm-10">
<gl-form-input
id="iteration-start-date"
v-model="startDate"
class="datepicker form-control"
:placeholder="__('Select start date')"
autocomplete="off"
data-qa-selector="iteration_start_date_field"
@change="updateStartDate"
/>
<a class="inline float-right gl-mt-2 js-clear-start-date" href="#">{{
__('Clear start date')
}}</a>
</div>
</div>
<div class="form-group row">
<div class="col-form-label col-sm-2">
<label for="iteration-due-date">{{ __('Due date') }}</label>
</div>
<div class="col-sm-10">
<gl-form-input
id="iteration-due-date"
v-model="dueDate"
class="datepicker form-control"
:placeholder="__('Select due date')"
autocomplete="off"
data-qa-selector="iteration_due_date_field"
@change="updateDueDate"
/>
<a class="inline float-right gl-mt-2 js-clear-due-date" href="#">{{
__('Clear due date')
}}</a>
</div>
</div>
</div>
</textarea>
</template>
</markdown-field>
</gl-form-group>
</gl-form>
<div class="form-actions d-flex">
<gl-button
:loading="loading"
......@@ -337,10 +354,10 @@ export default {
data-qa-selector="save_iteration_button"
@click="save"
>
{{ isEditing ? __('Update iteration') : __('Create iteration') }}
{{ isEditing ? $options.i18n.submitButton.save : $options.i18n.submitButton.create }}
</gl-button>
<gl-button class="gl-ml-3" data-testid="cancel-iteration" :to="$options.cadencesList">
{{ __('Cancel') }}
{{ $options.i18n.cancelButton }}
</gl-button>
</div>
</div>
......
......@@ -268,7 +268,7 @@ export default {
data-qa-selector="save_iteration_button"
@click="save"
>
{{ isEditing ? __('Update iteration') : __('Create iteration') }}
{{ isEditing ? __('Save changes') : __('Create iteration') }}
</gl-button>
<gl-button class="ml-auto" data-testid="cancel-iteration" @click="cancel">
{{ __('Cancel') }}
......
......@@ -14,7 +14,7 @@ import { TYPE_ITERATION } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import { s__ } from '~/locale';
import { Namespace, iterationStates } from '../constants';
import { Namespace } from '../constants';
import deleteIteration from '../queries/destroy_iteration.mutation.graphql';
import query from '../queries/iteration.query.graphql';
import { getIterationPeriod } from '../utils';
......@@ -95,7 +95,8 @@ export default {
return getIterationPeriod(this.iteration);
},
showDelete() {
return this.iteration.state !== iterationStates.closed;
// We only support deleting iterations for manual cadences.
return !this.iteration.iterationCadence.automatic;
},
},
methods: {
......
......@@ -8,4 +8,8 @@ fragment IterationReport on Iteration {
state
title
webPath
iterationCadence {
id
automatic
}
}
......@@ -8,7 +8,7 @@ RSpec.describe 'User edits iteration' do
let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
let_it_be(:guest_user) { create(:group_member, :guest, user: create(:user), group: group ).user }
let_it_be(:cadence) { create(:iterations_cadence, group: group) }
let_it_be(:iteration) { create(:iteration, :skip_future_date_validation, group: group, title: 'Correct Iteration', description: 'Iteration description', start_date: now - 1.day, due_date: now, iterations_cadence: cadence) }
let_it_be(:iteration) { create(:iteration, :skip_future_date_validation, description: 'Iteration description', start_date: now - 1.day, due_date: now, iterations_cadence: cadence) }
let_it_be(:new_start_date) { now + 4.days }
let_it_be(:new_due_date) { now + 5.days }
......@@ -26,62 +26,10 @@ RSpec.describe 'User edits iteration' do
sign_in(user)
end
let(:start_date_with_cadences_input) do
page.find('#iteration-start-date')
end
let(:due_date_with_cadences_input) do
page.find('#iteration-due-date')
end
let(:start_date_without_cadences_input) do
input = page.first('[data-testid="gl-datepicker-input"]')
input.set(now - 1.day)
input
end
let(:due_date_without_cadences_input) do
input = all('[data-testid="gl-datepicker-input"]').last
input.set(now)
input
end
let(:updated_start_date_with_cadences) do
fill_in('Start date', with: new_start_date.strftime('%Y-%m-%d'))
new_start_date.strftime('%b %-d, %Y')
end
let(:updated_due_date_with_cadences) do
fill_in('Due date', with: new_due_date.strftime('%Y-%m-%d'))
new_due_date.strftime('%b %-d, %Y')
end
let(:updated_start_date_without_cadences) do
start_date_without_cadences_input.set(new_start_date)
new_start_date.strftime('%b %-d, %Y')
end
let(:updated_due_date_without_cadences) do
# TODO: Reported issue with Capybara
# Use fill_in instead, update datepicker to have labels
due_date_without_cadences_input.set('')
due_date_without_cadences_input.set(new_due_date)
new_due_date.strftime('%b %-d, %Y')
end
shared_examples 'manually managed iteration' do
let_it_be(:manual_cadence) { build(:iterations_cadence, group: group, automatic: false).tap { |cadence| cadence.save!(validate: false) } }
let_it_be(:iteration) { create(:iteration, :skip_future_date_validation, title: 'Correct Iteration', description: 'Iteration description', start_date: now - 1.day, due_date: now, iterations_cadence: manual_cadence) }
let(:iteration_with_cadences_page) { group_iteration_cadence_iteration_path(group, iteration_cadence_id: cadence.id, id: iteration.id) }
let(:iteration_without_cadences_page) { group_iteration_path(iteration.group, iteration.id) }
let(:edit_iteration_with_cadences_page) { edit_group_iteration_cadence_iteration_path(group, iteration_cadence_id: cadence.id, id: iteration.id) }
let(:edit_iteration_without_cadences_page) { edit_group_iteration_path(iteration.group, iteration.id) }
where(:using_cadences, :start_date_input, :due_date_input, :updated_start_date, :updated_due_date, :iteration_page, :edit_iteration_page) do
true | ref(:start_date_with_cadences_input) | ref(:due_date_with_cadences_input) | ref(:updated_start_date_with_cadences) | ref(:updated_due_date_with_cadences) | ref(:iteration_with_cadences_page) | ref(:edit_iteration_with_cadences_page)
false | ref(:start_date_without_cadences_input) | ref(:due_date_without_cadences_input) | ref(:updated_start_date_without_cadences) | ref(:updated_due_date_without_cadences) | ref(:iteration_without_cadences_page) | ref(:edit_iteration_without_cadences_page)
end
with_them do
context 'load edit page directly', :js do
before do
visit edit_iteration_page
......@@ -91,10 +39,10 @@ RSpec.describe 'User edits iteration' do
it 'prefills fields and allows updating all values' do
aggregate_failures do
expect(title_input.value).to eq(iteration.title)
expect(description_input.value).to eq(iteration.description)
expect(start_date_input.value).to have_content(iteration.start_date)
expect(due_date_input.value).to have_content(iteration.due_date)
expect(find_field('Title').value).to eq(iteration.title)
expect(find_field('Description').value).to eq(iteration.description)
expect(start_date_field.value).to have_content(iteration.start_date)
expect(due_date_field.value).to have_content(iteration.due_date)
end
updated_title = 'Updated iteration title'
......@@ -102,16 +50,16 @@ RSpec.describe 'User edits iteration' do
fill_in('Title', with: updated_title)
fill_in('Description', with: updated_desc)
start_date = updated_start_date
due_date = updated_due_date
start_date_input
due_date_input
click_button('Update iteration')
click_button('Save changes')
aggregate_failures do
expect(page).to have_content(updated_title)
expect(page).to have_content(updated_desc)
expect(page).to have_content(start_date)
expect(page).to have_content(due_date)
expect(page).to have_content(new_start_date.strftime('%b %-d, %Y'))
expect(page).to have_content(new_due_date.strftime('%b %-d, %Y'))
expect(page).to have_current_path(iteration_page)
end
end
......@@ -127,15 +75,87 @@ RSpec.describe 'User edits iteration' do
click_link_or_button('Edit')
aggregate_failures do
expect(title_input.value).to eq(iteration.title)
expect(description_input.value).to eq(iteration.description)
expect(start_date_input.value).to have_content(iteration.start_date)
expect(due_date_input.value).to have_content(iteration.due_date)
expect(find_field('Title').value).to eq(iteration.title)
expect(find_field('Description').value).to eq(iteration.description)
expect(start_date_field.value).to have_content(iteration.start_date)
expect(due_date_field.value).to have_content(iteration.due_date)
expect(page).to have_current_path(edit_iteration_page)
end
end
end
end
context 'using manual iteration cadences' do
let(:iteration_page) { group_iteration_cadence_iteration_path(group, iteration_cadence_id: manual_cadence.id, id: iteration.id) }
let(:edit_iteration_page) { edit_group_iteration_cadence_iteration_path(group, iteration_cadence_id: manual_cadence.id, id: iteration.id) }
let(:start_date_field) { find_field('Start date') }
let(:due_date_field) { find_field('Due date') }
let(:start_date_input) do
fill_in 'Start date', with: new_start_date
start_date_field.native.send_keys :enter
end
let(:due_date_input) do
fill_in 'Due date', with: new_due_date
due_date_field.native.send_keys :enter
end
it_behaves_like 'manually managed iteration'
end
context 'using automatic iteration cadences' do
let(:iteration_page) { group_iteration_cadence_iteration_path(group, iteration_cadence_id: cadence.id, id: iteration.id) }
let(:edit_iteration_page) { edit_group_iteration_cadence_iteration_path(group, iteration_cadence_id: cadence.id, id: iteration.id) }
context 'load edit page directly', :js do
before do
visit edit_iteration_page
wait_for_requests
end
it 'prefills and allows updating description', :aggregate_failures do
expect(find_field("Description").value).to eq(iteration.description)
updated_desc = 'Updated iteration desc'
fill_in('Description', with: updated_desc)
click_button('Save changes')
expect(page).to have_content(updated_desc)
end
end
context 'load edit page from report', :js do
before do
visit iteration_page
end
it 'prefills description and updates URL' do
find(dropdown_selector).click
click_link_or_button('Edit')
expect(find_field("Description").value).to eq(iteration.description)
expect(page).to have_current_path(edit_iteration_page)
end
end
end
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/354878
# Remove with :iteration_cadences FF rollout
context 'using iteration without cadences' do
let(:iteration_page) { group_iteration_path(iteration.group, iteration.id) }
let(:edit_iteration_page) { edit_group_iteration_path(iteration.group, iteration.id) }
let(:new_start_date) { now - 1.day }
let(:new_due_date) { now }
let(:start_date_field) { page.first('[data-testid="gl-datepicker-input"]') }
let(:due_date_field) { all('[data-testid="gl-datepicker-input"]').last }
let(:start_date_input) { start_date_field.set(new_start_date) }
let(:due_date_input) { due_date_field.set(new_due_date) }
it_behaves_like 'manually managed iteration'
end
end
context 'as guest user' do
......@@ -174,13 +194,5 @@ RSpec.describe 'User edits iteration' do
end
end
end
def title_input
page.find('#iteration-title')
end
def description_input
page.find('#iteration-description')
end
end
end
import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlFormInput } from '@gitlab/ui';
import IterationForm from 'ee/iterations/components/iteration_form.vue';
import readIteration from 'ee/iterations/queries/iteration.query.graphql';
import createIteration from 'ee/iterations/queries/iteration_create.mutation.graphql';
......@@ -15,14 +16,13 @@ import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { dayAfter, formatDate } from '~/lib/utils/datetime_utility';
import {
manualIterationCadence as cadence,
mockGroupIterations,
mockIterationNode as iteration,
mockManualIterationNode as iteration,
createMutationSuccess,
createMutationFailure,
updateMutationSuccess,
emptyGroupIterationsSuccess,
nonEmptyGroupIterationsSuccess,
readCadenceSuccess,
readManualCadenceSuccess,
} from '../mock_data';
const baseUrl = '/cadences/';
......@@ -35,6 +35,28 @@ function createMockApolloProvider(requestHandlers) {
return createMockApollo(requestHandlers);
}
const mockGroupIterationsFactory = (nodes = [iteration]) => {
return {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterations: {
nodes,
pageInfo: {
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'first-item',
endCursor: 'last-item',
__typename: 'PageInfo',
},
__typename: 'IterationConnection',
},
__typename: 'Group',
},
},
};
};
describe('Iteration Form', () => {
let wrapper;
let router;
......@@ -44,7 +66,7 @@ describe('Iteration Form', () => {
mutationQuery = createIteration,
mutationResult = createMutationSuccess,
query = readIteration,
result = mockGroupIterations,
result = mockGroupIterationsFactory(),
resolverMock = jest.fn().mockResolvedValue(mutationResult),
groupIterationsSuccess = emptyGroupIterationsSuccess,
} = {}) {
......@@ -52,7 +74,7 @@ describe('Iteration Form', () => {
[query, jest.fn().mockResolvedValue(result)],
[mutationQuery, resolverMock],
[groupIterationsInCadenceQuery, jest.fn().mockResolvedValue(groupIterationsSuccess)],
[readCadence, jest.fn().mockResolvedValue(readCadenceSuccess)],
[readCadence, jest.fn().mockResolvedValue(readManualCadenceSuccess)],
]);
wrapper = extendedWrapper(
mount(IterationForm, {
......@@ -78,10 +100,12 @@ describe('Iteration Form', () => {
});
const findPageTitle = () => wrapper.findComponent({ ref: 'pageTitle' });
const findTitle = () => wrapper.find('#iteration-title');
const findDescription = () => wrapper.find('#iteration-description');
const findStartDate = () => wrapper.find('#iteration-start-date');
const findDueDate = () => wrapper.find('#iteration-due-date');
const findTitle = () => wrapper.findByLabelText('Title');
const findDescription = () => wrapper.findByLabelText('Description');
const findStartDate = () => wrapper.findByTestId('start-date');
const findStartDateInputText = () => findStartDate().find(GlFormInput).element.value;
const findDueDate = () => wrapper.findByTestId('due-date');
const findDueDateInputText = () => findDueDate().find(GlFormInput).element.value;
const findSaveButton = () => wrapper.findByTestId('save-iteration');
const findCancelButton = () => wrapper.findByTestId('cancel-iteration');
const clickSave = () => findSaveButton().trigger('click');
......@@ -112,11 +136,10 @@ describe('Iteration Form', () => {
const startDate = '2020-05-05';
const dueDate = '2020-05-25';
findTitle().vm.$emit('input', title);
findTitle().setValue(title);
findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate);
findDueDate().vm.$emit('input', dueDate);
findStartDate().vm.$emit('input', new Date(startDate));
findDueDate().vm.$emit('input', new Date(dueDate));
await clickSave();
expect(resolverMock).toHaveBeenCalledWith({
......@@ -172,7 +195,7 @@ describe('Iteration Form', () => {
'yyyy-mm-dd',
);
expect(findStartDate().element.value).toBe(expectedDate);
expect(findStartDateInputText()).toBe(expectedDate);
});
});
......@@ -188,17 +211,17 @@ describe('Iteration Form', () => {
it('uses cadence start date', () => {
const expectedDate = cadence.startDate;
expect(findStartDate().element.value).toBe(expectedDate);
expect(findStartDateInputText()).toBe(expectedDate);
});
});
});
});
describe('Edit iteration', () => {
describe('Edit iteration for manual cadence', () => {
beforeEach(() => {
router.replace({
name: 'editIteration',
params: { cadenceId: cadence.id, iterationId: iteration.id },
params: { cadenceId, iterationId },
});
});
......@@ -219,29 +242,31 @@ describe('Iteration Form', () => {
expect(findTitle().element.value).toBe(iteration.title);
expect(findDescription().element.value).toBe(iteration.description);
expect(findStartDate().element.value).toBe(iteration.startDate);
expect(findDueDate().element.value).toBe(iteration.dueDate);
expect(findStartDateInputText()).toBe(iteration.startDate);
expect(findDueDateInputText()).toBe(iteration.dueDate);
});
it('shows update text on submit button', () => {
createComponent();
expect(findSaveButton().text()).toBe('Update iteration');
expect(findSaveButton().text()).toBe('Save changes');
});
it('triggers mutation with form data', async () => {
const resolverMock = jest.fn().mockResolvedValue(updateMutationSuccess);
createComponent({ mutationQuery: updateIteration, resolverMock });
await waitForPromises();
const title = 'Updated title';
const description = 'Updated description';
const startDate = '2020-05-06';
const dueDate = '2020-05-26';
findTitle().vm.$emit('input', title);
findTitle().setValue(title);
findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate);
findDueDate().vm.$emit('input', dueDate);
findStartDate().vm.$emit('input', new Date(startDate));
findDueDate().vm.$emit('input', new Date(dueDate));
clickSave();
await waitForPromises();
......@@ -249,7 +274,7 @@ describe('Iteration Form', () => {
expect(resolverMock).toHaveBeenCalledWith({
input: {
groupPath,
id: iteration.id,
id: iterationId,
title,
description,
startDate,
......@@ -264,6 +289,7 @@ describe('Iteration Form', () => {
mutationQuery: updateIteration,
resolverMock,
});
await waitForPromises();
clickSave();
await nextTick();
......@@ -274,11 +300,11 @@ describe('Iteration Form', () => {
expect(resolverMock).toHaveBeenCalledWith({
input: {
groupPath,
id: iteration.id,
startDate: '',
dueDate: '',
title: '',
description: '',
id: iterationId,
startDate: iteration.startDate,
dueDate: iteration.dueDate,
title: iteration.title,
description: iteration.description,
},
});
});
......
......@@ -181,7 +181,7 @@ describe('Iteration Form', () => {
props: propsWithIteration,
});
expect(findSaveButton().text()).toBe('Update iteration');
expect(findSaveButton().text()).toBe('Save changes');
});
it('triggers mutation with form data', () => {
......
......@@ -17,7 +17,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { __ } from '~/locale';
import {
mockIterationNode,
mockPastIterationNode,
mockManualIterationNode,
createMockGroupIterations,
mockIterationNodeWithoutTitle,
mockProjectIterations,
......@@ -155,8 +155,16 @@ describe('Iterations report', () => {
});
describe('delete iteration', () => {
it('does not show delete option for past iterations', async () => {
mountComponent({ mockQueryResponse: createMockGroupIterations(mockPastIterationNode) });
it('does not show delete option when iteration belongs to automatic cadence', async () => {
mountComponent({ mockQueryResponse: createMockGroupIterations(mockIterationNode) });
await waitForPromises();
expect(findDeleteButton().exists()).toBe(false);
});
it('shows delete option when iteration belongs to automatic cadence', async () => {
mountComponent({ mockQueryResponse: createMockGroupIterations(mockManualIterationNode) });
await waitForPromises();
......
......@@ -11,12 +11,21 @@ export const mockIterationNode = {
title: 'top-level-iteration',
webPath: '/groups/top-level-group/-/iterations/4',
scopedPath: '/groups/top-level-group/-/iterations/4',
iterationCadence: {
__typename: 'IterationCadence',
id: 'gid://gitlab/Iterations::Cadence/72',
automatic: true,
},
__typename: 'Iteration',
};
export const mockPastIterationNode = {
export const mockManualIterationNode = {
...mockIterationNode,
state: iterationStates.closed,
iterationCadence: {
__typename: 'IterationCadence',
id: 'gid://gitlab/Iterations::Cadence/72',
automatic: false,
},
};
export const mockIterationNodeWithoutTitle = {
......@@ -171,7 +180,18 @@ export const nonEmptyGroupIterationsSuccess = {
},
};
export const readCadenceSuccess = {
export const readAutomaticCadenceSuccess = {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterationCadences: {
nodes: [automaticIterationCadence],
},
},
},
};
export const readManualCadenceSuccess = {
data: {
group: {
id: 'gid://gitlab/Group/114',
......
......@@ -21125,12 +21125,18 @@ msgstr ""
msgid "Iterations|Cadence name"
msgstr ""
msgid "Iterations|Cancel"
msgstr ""
msgid "Iterations|Couldn't find iteration cadence"
msgstr ""
msgid "Iterations|Create cadence"
msgstr ""
msgid "Iterations|Create iteration"
msgstr ""
msgid "Iterations|Delete cadence"
msgstr ""
......@@ -21140,9 +21146,15 @@ msgstr ""
msgid "Iterations|Delete iteration?"
msgstr ""
msgid "Iterations|Description"
msgstr ""
msgid "Iterations|Done"
msgstr ""
msgid "Iterations|Due date"
msgstr ""
msgid "Iterations|Duration"
msgstr ""
......@@ -40274,9 +40286,6 @@ msgstr ""
msgid "Update it"
msgstr ""
msgid "Update iteration"
msgstr ""
msgid "Update milestone"
msgstr ""
......
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