Commit b8e814ed authored by Eulyeon Ko's avatar Eulyeon Ko Committed by Kushal Pandya

Update iteration form and report

Iteration form:
- For manual cadences, allow updating all fields.
- For automatic cadences, only allow updating description.

Iteration report:
- Only allow deleting iterations for manual cadences.

Fix lint errors

Fix mock data

Apply 3 suggestion(s) to 1 file(s)
parent 296e97cb
......@@ -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
}
}
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 ""
......@@ -40277,9 +40289,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