Commit ba8f1edd authored by minahilnichols's avatar minahilnichols Committed by Ezekiel Kigbo

Update new iterations form to match pajamas

parent a6f4e0bb
<script> <script>
import { GlButton, GlForm, GlFormInput } from '@gitlab/ui'; import { GlButton, GlForm, GlFormGroup, GlFormInput, GlDatepicker } from '@gitlab/ui';
import initDatePicker from '~/behaviors/date_picker';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
import createIteration from '../queries/create_iteration.mutation.graphql'; import createIteration from '../queries/create_iteration.mutation.graphql';
import updateIteration from '../queries/update_iteration.mutation.graphql'; import updateIteration from '../queries/update_iteration.mutation.graphql';
...@@ -12,8 +12,10 @@ export default { ...@@ -12,8 +12,10 @@ export default {
components: { components: {
GlButton, GlButton,
GlForm, GlForm,
GlFormGroup,
GlFormInput, GlFormInput,
MarkdownField, MarkdownField,
GlDatepicker,
}, },
props: { props: {
groupPath: { groupPath: {
...@@ -49,6 +51,8 @@ export default { ...@@ -49,6 +51,8 @@ export default {
description: this.iteration.description ?? '', description: this.iteration.description ?? '',
startDate: this.iteration.startDate, startDate: this.iteration.startDate,
dueDate: this.iteration.dueDate, dueDate: this.iteration.dueDate,
isValidTitle: true,
isValidStartDate: true,
}; };
}, },
computed: { computed: {
...@@ -58,18 +62,39 @@ export default { ...@@ -58,18 +62,39 @@ export default {
groupPath: this.groupPath, groupPath: this.groupPath,
title: this.title, title: this.title,
description: this.description, description: this.description,
startDate: this.startDate, startDate: this.startDate ? formatDate(this.startDate, 'yyyy-mm-dd') : null,
dueDate: this.dueDate, dueDate: this.dueDate ? formatDate(this.dueDate, 'yyyy-mm-dd') : null,
}, },
}; };
}, },
}, invalidFeedback() {
mounted() { return __('This field is required.');
// TODO: utilize GlDatepicker instead of relying on this jQuery behavior },
initDatePicker();
}, },
methods: { methods: {
checkValidations() {
let isValid = true;
if (!this.title) {
this.isValidTitle = false;
isValid = false;
} else {
this.isValidTitle = true;
}
if (!this.startDate) {
this.isValidStartDate = false;
isValid = false;
} else {
this.isValidStartDate = true;
}
return isValid;
},
save() { save() {
if (!this.checkValidations()) {
return {};
}
this.loading = true; this.loading = true;
return this.isEditing ? this.updateIteration() : this.createIteration(); return this.isEditing ? this.updateIteration() : this.createIteration();
}, },
...@@ -154,93 +179,89 @@ export default { ...@@ -154,93 +179,89 @@ export default {
</h3> </h3>
</div> </div>
<hr class="gl-mt-0" /> <hr class="gl-mt-0" />
<gl-form class="row common-note-form"> <gl-form class="row common-note-form" novalidate>
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group row"> <gl-form-group
<div class="col-form-label col-sm-2"> :label="__('Title')"
<label for="iteration-title">{{ __('Title') }}</label> class="gl-flex-grow-1"
</div> label-for="iteration-title"
<div class="col-sm-10"> :state="isValidTitle"
<gl-form-input :invalid-feedback="invalidFeedback"
id="iteration-title" >
v-model="title" <gl-form-input
autocomplete="off" id="iteration-title"
data-qa-selector="iteration_title_field" v-model="title"
/> autocomplete="off"
</div> data-qa-selector="iteration_title_field"
</div> :state="isValidTitle"
required
/>
</gl-form-group>
<div class="form-group row"> <gl-form-group :label="__('Description')" label-for="iteration-description">
<div class="col-form-label col-sm-2"> <markdown-field
<label for="iteration-description">{{ __('Description') }}</label> :markdown-preview-path="previewMarkdownPath"
</div> :can-attach-file="false"
<div class="col-sm-10"> :enable-autocomplete="true"
<markdown-field label="__('Description')"
:markdown-preview-path="previewMarkdownPath" :textarea-value="description"
:can-attach-file="false" markdown-docs-path="/help/user/markdown"
:enable-autocomplete="true" :add-spacing-classes="false"
label="Description" class="md-area"
:textarea-value="description" >
markdown-docs-path="/help/user/markdown" <template #textarea>
:add-spacing-classes="false" <textarea
class="md-area" id="iteration-description"
> v-model="description"
<template #textarea> class="note-textarea js-gfm-input js-autosize markdown-area"
<textarea dir="auto"
id="iteration-description" data-supports-quick-actions="false"
v-model="description" :aria-label="__('Description')"
class="note-textarea js-gfm-input js-autosize markdown-area" data-qa-selector="iteration_description_field"
dir="auto" >
data-supports-quick-actions="false" </textarea>
:aria-label="__('Description')" </template>
data-qa-selector="iteration_description_field" </markdown-field>
> </gl-form-group>
</textarea>
</template>
</markdown-field>
</div>
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group row"> <gl-form-group
<div class="col-form-label col-sm-2"> :label="__('Start date')"
<label for="iteration-start-date">{{ __('Start date') }}</label> :state="isValidStartDate"
</div> :invalid-feedback="invalidFeedback"
<div class="col-sm-10"> >
<gl-form-input <div class="gl-display-inline-block gl-mr-2">
<gl-datepicker
id="iteration-start-date" id="iteration-start-date"
v-model="startDate" v-model="startDate"
class="datepicker form-control" :state="isValidStartDate"
:placeholder="__('Select start date')" required
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>
<div class="col-sm-10"> <gl-button
<gl-form-input v-show="startDate"
id="iteration-due-date" variant="link"
v-model="dueDate" class="gl-white-space-nowrap"
class="datepicker form-control" @click="updateStartDate(null)"
:placeholder="__('Select due date')" >
autocomplete="off" {{ __('Clear start date') }}
data-qa-selector="iteration_due_date_field" </gl-button>
@change="updateDueDate" </gl-form-group>
/>
<a class="inline float-right gl-mt-2 js-clear-due-date" href="#">{{ <gl-form-group :label="__('Due date')">
__('Clear due date') <div class="gl-display-inline-block gl-mr-2">
}}</a> <gl-datepicker id="iteration-due-date" v-model="dueDate" />
</div> </div>
</div> <gl-button
v-show="dueDate"
variant="link"
class="gl-white-space-nowrap"
@click="updateDueDate(null)"
>
{{ __('Clear due date') }}
</gl-button>
</gl-form-group>
</div> </div>
</gl-form> </gl-form>
......
...@@ -19,8 +19,8 @@ describe('Iteration Form', () => { ...@@ -19,8 +19,8 @@ describe('Iteration Form', () => {
id: `gid://gitlab/Iteration/${id}`, id: `gid://gitlab/Iteration/${id}`,
title: 'An iteration', title: 'An iteration',
description: 'The words', description: 'The words',
startDate: '2020-06-28', startDate: new Date('2020-06-28'),
dueDate: '2020-07-05', dueDate: new Date('2020-07-05'),
}; };
const createMutationSuccess = { data: { createIteration: { iteration, errors: [] } } }; const createMutationSuccess = { data: { createIteration: { iteration, errors: [] } } };
...@@ -88,8 +88,11 @@ describe('Iteration Form', () => { ...@@ -88,8 +88,11 @@ describe('Iteration Form', () => {
findTitle().vm.$emit('input', title); findTitle().vm.$emit('input', title);
findDescription().setValue(description); findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate); findStartDate().vm.$emit('input', startDate ? new Date(startDate) : null);
findDueDate().vm.$emit('input', dueDate); findDueDate().vm.$emit('input', dueDate ? new Date(dueDate) : null);
findTitle().trigger('change');
findStartDate().trigger('change');
clickSave(); clickSave();
...@@ -110,6 +113,19 @@ describe('Iteration Form', () => { ...@@ -110,6 +113,19 @@ describe('Iteration Form', () => {
it('redirects to Iteration page on success', async () => { it('redirects to Iteration page on success', async () => {
createComponent(); createComponent();
const title = 'Iteration 5';
const description = 'The fifth iteration';
const startDate = '2020-05-05';
const dueDate = '2020-05-25';
findTitle().vm.$emit('input', title);
findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate ? new Date(startDate) : null);
findDueDate().vm.$emit('input', dueDate ? new Date(dueDate) : null);
findTitle().trigger('change');
findStartDate().trigger('change');
clickSave(); clickSave();
await nextTick(); await nextTick();
...@@ -117,6 +133,20 @@ describe('Iteration Form', () => { ...@@ -117,6 +133,20 @@ describe('Iteration Form', () => {
expect(visitUrl).toHaveBeenCalled(); expect(visitUrl).toHaveBeenCalled();
}); });
it('validates required fields and sets isValid state to false', async () => {
createComponent();
clickSave();
await nextTick();
expect(findSaveButton().props('loading')).toBe(false);
expect(wrapper.vm.checkValidations()).toBe(false);
expect(wrapper.vm.isValidTitle).toBe(false);
expect(wrapper.vm.isValidStartDate).toBe(false);
expect(visitUrl).not.toHaveBeenCalled();
});
it('loading=false on error', () => { it('loading=false on error', () => {
createComponent({ mutationResult: createMutationFailure }); createComponent({ mutationResult: createMutationFailure });
...@@ -149,10 +179,13 @@ describe('Iteration Form', () => { ...@@ -149,10 +179,13 @@ describe('Iteration Form', () => {
props: propsWithIteration, props: propsWithIteration,
}); });
const startDate = new Date(findStartDate().attributes('value'));
const dueDate = new Date(findDueDate().attributes('value'));
expect(findTitle().attributes('value')).toBe(iteration.title); expect(findTitle().attributes('value')).toBe(iteration.title);
expect(findDescription().element.value).toBe(iteration.description); expect(findDescription().element.value).toBe(iteration.description);
expect(findStartDate().attributes('value')).toBe(iteration.startDate); expect(startDate).toEqual(iteration.startDate);
expect(findDueDate().attributes('value')).toBe(iteration.dueDate); expect(dueDate).toEqual(iteration.dueDate);
}); });
it('shows update text on submit button', () => { it('shows update text on submit button', () => {
...@@ -175,8 +208,8 @@ describe('Iteration Form', () => { ...@@ -175,8 +208,8 @@ describe('Iteration Form', () => {
findTitle().vm.$emit('input', title); findTitle().vm.$emit('input', title);
findDescription().setValue(description); findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate); findStartDate().vm.$emit('input', startDate ? new Date(startDate) : null);
findDueDate().vm.$emit('input', dueDate); findDueDate().vm.$emit('input', dueDate ? new Date(dueDate) : null);
clickSave(); clickSave();
...@@ -220,6 +253,27 @@ describe('Iteration Form', () => { ...@@ -220,6 +253,27 @@ describe('Iteration Form', () => {
expect(wrapper.emitted('updated')).toBeUndefined(); expect(wrapper.emitted('updated')).toBeUndefined();
}); });
it('validates required fields and sets isValid state to false', async () => {
createComponent({
props: propsWithIteration,
});
findTitle().vm.$emit('input', '');
findStartDate().vm.$emit('input', null);
findTitle().trigger('change');
findStartDate().trigger('change');
clickSave();
await nextTick();
expect(findSaveButton().props('loading')).toBe(false);
expect(wrapper.vm.checkValidations()).toBe(false);
expect(wrapper.vm.isValidTitle).toBe(false);
expect(wrapper.vm.isValidStartDate).toBe(false);
expect(visitUrl).not.toHaveBeenCalled();
});
it('emits cancel when cancel clicked', async () => { it('emits cancel when cancel clicked', async () => {
createComponent({ createComponent({
props: propsWithIteration, props: propsWithIteration,
......
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