Commit 15d6bb03 authored by Kushal Pandya's avatar Kushal Pandya

Add IssuableBody component

Adds IssuableBody component to use with
Issuable Show app.
parent af8a060f
<script>
import { GlLink } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import IssuableTitle from './issuable_title.vue';
import IssuableDescription from './issuable_description.vue';
import IssuableEditForm from './issuable_edit_form.vue';
export default {
components: {
GlLink,
TimeAgoTooltip,
IssuableTitle,
IssuableDescription,
IssuableEditForm,
},
props: {
issuable: {
type: Object,
required: true,
},
statusBadgeClass: {
type: String,
required: true,
},
statusIcon: {
type: String,
required: true,
},
enableEdit: {
type: Boolean,
required: true,
},
enableAutocomplete: {
type: Boolean,
required: true,
},
editFormVisible: {
type: Boolean,
required: true,
},
descriptionPreviewPath: {
type: String,
required: true,
},
descriptionHelpPath: {
type: String,
required: true,
},
},
computed: {
isUpdated() {
return Boolean(this.issuable.updatedAt);
},
updatedBy() {
return this.issuable.updatedBy;
},
},
};
</script>
<template>
<div class="issue-details issuable-details">
<div class="detail-page-description content-block">
<issuable-edit-form
v-if="editFormVisible"
:issuable="issuable"
:enable-autocomplete="enableAutocomplete"
:description-preview-path="descriptionPreviewPath"
:description-help-path="descriptionHelpPath"
>
<template #edit-form-actions="issuableMeta">
<slot name="edit-form-actions" v-bind="issuableMeta"></slot>
</template>
</issuable-edit-form>
<template v-else>
<issuable-title
:issuable="issuable"
:status-badge-class="statusBadgeClass"
:status-icon="statusIcon"
:enable-edit="enableEdit"
@edit-issuable="$emit('edit-issuable', $event)"
>
<template #status-badge>
<slot name="status-badge"></slot>
</template>
</issuable-title>
<issuable-description v-if="issuable.descriptionHtml" :issuable="issuable" />
<small v-if="isUpdated" class="edited-text gl-font-sm!">
{{ __('Edited') }}
<time-ago-tooltip :time="issuable.updatedAt" tooltip-placement="bottom" />
<span v-if="updatedBy">
{{ __('by') }}
<gl-link :href="updatedBy.webUrl" class="author-link gl-font-sm!">
<span>{{ updatedBy.name }}</span>
</gl-link>
</span>
</small>
</template>
</div>
</div>
</template>
...@@ -9533,6 +9533,9 @@ msgstr "" ...@@ -9533,6 +9533,9 @@ msgstr ""
msgid "Edit your most recent comment in a thread (from an empty textarea)" msgid "Edit your most recent comment in a thread (from an empty textarea)"
msgstr "" msgstr ""
msgid "Edited"
msgstr ""
msgid "Edited %{timeago}" msgid "Edited %{timeago}"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import IssuableBody from '~/issuable_show/components/issuable_body.vue';
import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
import IssuableDescription from '~/issuable_show/components/issuable_description.vue';
import IssuableEditForm from '~/issuable_show/components/issuable_edit_form.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
jest.mock('~/autosave');
const issuableBodyProps = {
...mockIssuableShowProps,
issuable: mockIssuable,
};
const createComponent = (propsData = issuableBodyProps) =>
shallowMount(IssuableBody, {
propsData,
stubs: {
IssuableTitle,
IssuableDescription,
IssuableEditForm,
TimeAgoTooltip,
},
slots: {
'status-badge': 'Open',
'edit-form-actions': `
<button class="js-save">Save changes</button>
<button class="js-cancel">Cancel</button>
`,
},
});
describe('IssuableBody', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('computed', () => {
describe('isUpdated', () => {
it.each`
updatedAt | returnValue
${mockIssuable.updatedAt} | ${true}
${null} | ${false}
${''} | ${false}
`(
'returns $returnValue when value of `updateAt` prop is `$updatedAt`',
async ({ updatedAt, returnValue }) => {
wrapper.setProps({
issuable: {
...mockIssuable,
updatedAt,
},
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.isUpdated).toBe(returnValue);
},
);
});
describe('updatedBy', () => {
it('returns value of `issuable.updatedBy`', () => {
expect(wrapper.vm.updatedBy).toBe(mockIssuable.updatedBy);
});
});
});
describe('template', () => {
it('renders issuable-title component', () => {
const titleEl = wrapper.find(IssuableTitle);
expect(titleEl.exists()).toBe(true);
expect(titleEl.props()).toMatchObject({
issuable: issuableBodyProps.issuable,
statusBadgeClass: issuableBodyProps.statusBadgeClass,
statusIcon: issuableBodyProps.statusIcon,
enableEdit: issuableBodyProps.enableEdit,
});
});
it('renders issuable-description component', () => {
const descriptionEl = wrapper.find(IssuableDescription);
expect(descriptionEl.exists()).toBe(true);
expect(descriptionEl.props('issuable')).toEqual(issuableBodyProps.issuable);
});
it('renders issuable edit info', () => {
const editedEl = wrapper.find('small');
const sanitizedText = editedEl
.text()
.replace(/\n/g, ' ')
.replace(/\s+/g, ' ');
expect(sanitizedText).toContain('Edited');
expect(sanitizedText).toContain('ago');
expect(sanitizedText).toContain(`by ${mockIssuable.updatedBy.name}`);
});
it('renders issuable-edit-form when `editFormVisible` prop is true', async () => {
wrapper.setProps({
editFormVisible: true,
});
await wrapper.vm.$nextTick();
const editFormEl = wrapper.find(IssuableEditForm);
expect(editFormEl.exists()).toBe(true);
expect(editFormEl.props()).toMatchObject({
issuable: issuableBodyProps.issuable,
enableAutocomplete: issuableBodyProps.enableAutocomplete,
descriptionPreviewPath: issuableBodyProps.descriptionPreviewPath,
descriptionHelpPath: issuableBodyProps.descriptionHelpPath,
});
expect(editFormEl.find('button.js-save').exists()).toBe(true);
expect(editFormEl.find('button.js-cancel').exists()).toBe(true);
});
describe('events', () => {
it('component emits `edit-issuable` event bubbled via issuable-title', () => {
const issuableTitle = wrapper.find(IssuableTitle);
issuableTitle.vm.$emit('edit-issuable');
expect(wrapper.emitted('edit-issuable')).toBeTruthy();
});
});
});
});
...@@ -11,6 +11,7 @@ export const mockIssuable = { ...@@ -11,6 +11,7 @@ export const mockIssuable = {
state: 'opened', state: 'opened',
blocked: false, blocked: false,
confidential: false, confidential: false,
updatedBy: issuable.author,
currentUserTodos: { currentUserTodos: {
nodes: [ nodes: [
{ {
......
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