Commit a0f7ee43 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'issue-edit-inline-title-field' into 'issue-edit-inline'

Issue inline edit title field

See merge request !11331
parents 47881254 64e159ad
...@@ -7,7 +7,7 @@ import Service from '../services/index'; ...@@ -7,7 +7,7 @@ import Service from '../services/index';
import Store from '../stores'; import Store from '../stores';
import titleComponent from './title.vue'; import titleComponent from './title.vue';
import descriptionComponent from './description.vue'; import descriptionComponent from './description.vue';
import editActions from './edit_actions.vue'; import formComponent from './form.vue';
export default { export default {
props: { props: {
...@@ -41,10 +41,6 @@ export default { ...@@ -41,10 +41,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
showForm: {
type: Boolean,
required: true,
},
}, },
data() { data() {
const store = new Store({ const store = new Store({
...@@ -56,17 +52,31 @@ export default { ...@@ -56,17 +52,31 @@ export default {
return { return {
store, store,
state: store.state, state: store.state,
formState: store.formState, showForm: false,
}; };
}, },
computed: {
formState() {
return this.store.formState;
},
},
components: { components: {
descriptionComponent, descriptionComponent,
titleComponent, titleComponent,
editActions, formComponent,
}, },
methods: { methods: {
openForm() {
this.showForm = true;
this.store.formState = {
title: this.state.titleText,
};
},
closeForm() {
this.showForm = false;
},
updateIssuable() { updateIssuable() {
this.service.updateIssuable(this.formState) this.service.updateIssuable(this.store.formState)
.then(() => { .then(() => {
eventHub.$emit('close.form'); eventHub.$emit('close.form');
}) })
...@@ -117,29 +127,36 @@ export default { ...@@ -117,29 +127,36 @@ export default {
eventHub.$on('delete.issuable', this.deleteIssuable); eventHub.$on('delete.issuable', this.deleteIssuable);
eventHub.$on('update.issuable', this.updateIssuable); eventHub.$on('update.issuable', this.updateIssuable);
eventHub.$on('close.form', this.closeForm);
eventHub.$on('open.form', this.openForm);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('delete.issuable', this.deleteIssuable); eventHub.$off('delete.issuable', this.deleteIssuable);
eventHub.$off('update.issuable', this.updateIssuable); eventHub.$off('update.issuable', this.updateIssuable);
eventHub.$off('close.form', this.closeForm);
eventHub.$off('open.form', this.openForm);
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<title-component <form-component
:issuable-ref="issuableRef"
:title-html="state.titleHtml"
:title-text="state.titleText" />
<description-component
v-if="state.descriptionHtml"
:can-update="canUpdate"
:description-html="state.descriptionHtml"
:description-text="state.descriptionText"
:updated-at="state.updatedAt"
:task-status="state.taskStatus" />
<edit-actions
v-if="canUpdate && showForm" v-if="canUpdate && showForm"
:form-state="formState"
:can-destroy="canDestroy" /> :can-destroy="canDestroy" />
<div v-else>
<title-component
:issuable-ref="issuableRef"
:title-html="state.titleHtml"
:title-text="state.titleText" />
<description-component
v-if="state.descriptionHtml"
:can-update="canUpdate"
:description-html="state.descriptionHtml"
:description-text="state.descriptionText"
:updated-at="state.updatedAt"
:task-status="state.taskStatus" />
</div>
</div> </div>
</template> </template>
<script>
export default {
props: {
formState: {
type: Object,
required: true,
},
},
};
</script>
<template>
<fieldset>
<label
class="sr-only"
for="issue-title">
Title
</label>
<input
id="issue-title"
class="form-control"
type="text"
placeholder="Issue title"
aria-label="Issue title"
v-model="formState.title" />
</fieldset>
</template>
<script>
import titleField from './fields/title.vue';
import editActions from './edit_actions.vue';
export default {
props: {
canDestroy: {
type: Boolean,
required: true,
},
formState: {
type: Object,
required: true,
},
},
components: {
titleField,
editActions,
},
};
</script>
<template>
<form>
<title-field
:form-state="formState" />
<edit-actions
:can-destroy="canDestroy" />
</form>
</template>
...@@ -35,25 +35,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -35,25 +35,8 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: issuableTitleElement.innerHTML, initialTitle: issuableTitleElement.innerHTML,
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '', initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '', initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
showForm: false,
}; };
}, },
methods: {
openForm() {
this.showForm = true;
},
closeForm() {
this.showForm = false;
},
},
created() {
eventHub.$on('open.form', this.openForm);
eventHub.$on('close.form', this.closeForm);
},
beforeDestroy() {
eventHub.$off('open.form', this.openForm);
eventHub.$off('close.form', this.closeForm);
},
render(createElement) { render(createElement) {
return createElement('issuable-app', { return createElement('issuable-app', {
props: { props: {
...@@ -64,7 +47,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -64,7 +47,6 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: this.initialTitle, initialTitle: this.initialTitle,
initialDescriptionHtml: this.initialDescriptionHtml, initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText, initialDescriptionText: this.initialDescriptionText,
showForm: this.showForm,
}, },
}); });
}, },
......
...@@ -12,7 +12,9 @@ export default class Store { ...@@ -12,7 +12,9 @@ export default class Store {
taskStatus: '', taskStatus: '',
updatedAt: '', updatedAt: '',
}; };
this.formState = {}; this.formState = {
title: '',
};
} }
updateState(data) { updateState(data) {
......
...@@ -75,6 +75,18 @@ describe('Issuable output', () => { ...@@ -75,6 +75,18 @@ describe('Issuable output', () => {
}); });
}); });
it('changes element for `form` when open', (done) => {
vm.showForm = true;
Vue.nextTick(() => {
expect(
vm.$el.tagName,
).toBe('FORM');
done();
});
});
it('does not show actions if permissions are incorrect', (done) => { it('does not show actions if permissions are incorrect', (done) => {
vm.showForm = true; vm.showForm = true;
vm.canUpdate = false; vm.canUpdate = false;
......
import Vue from 'vue';
import Store from '~/issue_show/stores';
import titleField from '~/issue_show/components/fields/title.vue';
describe('Title field component', () => {
let vm;
let store;
beforeEach(() => {
const Component = Vue.extend(titleField);
store = new Store({
titleHtml: '',
descriptionHtml: '',
issuableRef: '',
});
store.formState.title = 'test';
vm = new Component({
propsData: {
formState: store.formState,
},
}).$mount();
});
it('renders form control with formState title', () => {
expect(
vm.$el.querySelector('.form-control').value,
).toBe('test');
});
});
import Vue from 'vue'; import Vue from 'vue';
import Store from '~/issue_show/stores';
import titleComponent from '~/issue_show/components/title.vue'; import titleComponent from '~/issue_show/components/title.vue';
describe('Title component', () => { describe('Title component', () => {
...@@ -6,18 +7,25 @@ describe('Title component', () => { ...@@ -6,18 +7,25 @@ describe('Title component', () => {
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(titleComponent); const Component = Vue.extend(titleComponent);
const store = new Store({
titleHtml: '',
descriptionHtml: '',
issuableRef: '',
});
vm = new Component({ vm = new Component({
propsData: { propsData: {
issuableRef: '#1', issuableRef: '#1',
titleHtml: 'Testing <img />', titleHtml: 'Testing <img />',
titleText: 'Testing', titleText: 'Testing',
showForm: false,
formState: store.formState,
}, },
}).$mount(); }).$mount();
}); });
it('renders title HTML', () => { it('renders title HTML', () => {
expect( expect(
vm.$el.innerHTML.trim(), vm.$el.querySelector('h2').innerHTML.trim(),
).toBe('Testing <img>'); ).toBe('Testing <img>');
}); });
...@@ -39,12 +47,12 @@ describe('Title component', () => { ...@@ -39,12 +47,12 @@ describe('Title component', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(
vm.$el.classList.contains('issue-realtime-pre-pulse'), vm.$el.querySelector('h2').classList.contains('issue-realtime-pre-pulse'),
).toBeTruthy(); ).toBeTruthy();
setTimeout(() => { setTimeout(() => {
expect( expect(
vm.$el.classList.contains('issue-realtime-trigger-pulse'), vm.$el.querySelector('h2').classList.contains('issue-realtime-trigger-pulse'),
).toBeTruthy(); ).toBeTruthy();
done(); done();
......
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