Commit bcbb31f6 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '229843-move-slot-content-into-related-issues-component' into 'master'

Move slot content from RelatedIssuesList to RelatedIssuableItem

See merge request gitlab-org/gitlab!38181
parents 932d5efc 191b422c
......@@ -4,6 +4,7 @@ import { GlIcon, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
import { sprintf } from '~/locale';
import IssueMilestone from './issue_milestone.vue';
import IssueAssignees from './issue_assignees.vue';
import IssueDueDate from '~/boards/components/issue_due_date.vue';
import relatedIssuableMixin from '../../mixins/related_issuable_mixin';
import CiIcon from '../ci_icon.vue';
......@@ -15,6 +16,8 @@ export default {
CiIcon,
GlIcon,
GlTooltip,
IssueWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
IssueDueDate,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -120,8 +123,21 @@ export default {
/>
<!-- Flex order for slots is defined in the parent component: e.g. related_issues_block.vue -->
<slot name="dueDate"></slot>
<slot name="weight"></slot>
<span v-if="weight > 0" class="order-md-1">
<issue-weight
:weight="weight"
class="item-weight gl-display-flex gl-align-items-center"
tag-name="span"
/>
</span>
<span v-if="dueDate" class="order-md-1">
<issue-due-date
:date="dueDate"
tooltip-placement="top"
css-class="item-due-date gl-display-flex gl-align-items-center"
/>
</span>
<issue-assignees
v-if="hasAssignees"
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import Sortable from 'sortablejs';
import IssueWeight from 'ee/boards/components/issue_card_weight.vue';
import sortableConfig from 'ee/sortable/sortable_config';
import IssueDueDate from '~/boards/components/issue_due_date.vue';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import tooltip from '~/vue_shared/directives/tooltip';
......@@ -14,8 +12,6 @@ export default {
},
components: {
GlLoadingIcon,
IssueDueDate,
IssueWeight,
RelatedIssuableItem,
},
props: {
......@@ -132,29 +128,15 @@ export default {
:assignees="issue.assignees"
:created-at="issue.createdAt"
:closed-at="issue.closedAt"
:weight="issue.weight"
:due-date="issue.dueDate"
:can-remove="canAdmin"
:can-reorder="canReorder"
:path-id-separator="pathIdSeparator"
event-namespace="relatedIssue"
class="qa-related-issuable-item"
@relatedIssueRemoveRequest="$emit('relatedIssueRemoveRequest', $event)"
>
<span v-if="issue.weight > 0" slot="weight" class="order-md-1">
<issue-weight
:weight="issue.weight"
class="item-weight d-flex align-items-center"
tag-name="span"
/>
</span>
<span v-if="issue.dueDate" slot="dueDate" class="order-md-1">
<issue-due-date
:date="issue.dueDate"
tooltip-placement="top"
css-class="item-due-date d-flex align-items-center"
/>
</span>
</related-issuable-item>
</li>
</ul>
</div>
......
import { isAbsolute, isSafeURL } from '~/lib/utils/url_utility';
import { REGEXES } from './constants';
window.isAbsolute = isAbsolute;
window.isSafeURL = isSafeURL;
// Get the issue in the format expected by the descendant components of related_issues_block.vue.
export const getFormattedIssue = issue => ({
...issue,
......
import { mount } from '@vue/test-utils';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import IssueWeight from 'ee_component/boards/components/issue_card_weight.vue';
import {
defaultAssignees,
defaultMilestone,
} from 'jest/vue_shared/components/issue/related_issuable_mock_data';
import { TEST_HOST } from 'jest/helpers/test_constants';
describe('RelatedIssuableItem', () => {
let wrapper;
function mountComponent({ mountMethod = mount, stubs = {}, props = {}, slots = {} } = {}) {
wrapper = mountMethod(RelatedIssuableItem, {
propsData: props,
slots,
stubs,
});
}
const props = {
idKey: 1,
displayReference: 'gitlab-org/gitlab-test#1',
pathIdSeparator: '#',
path: `${TEST_HOST}/path`,
title: 'title',
confidential: true,
dueDate: '1990-12-31',
weight: 10,
createdAt: '2018-12-01T00:00:00.00Z',
milestone: defaultMilestone,
assignees: defaultAssignees,
eventNamespace: 'relatedIssue',
};
const slots = {
dueDate: '<div class="js-due-date-slot"></div>',
weight: '<div class="js-weight-slot"></div>',
};
beforeEach(() => {
mountComponent({ props, slots });
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders weight component with correct weight', () => {
expect(wrapper.find(IssueWeight).props('weight')).toBe(props.weight);
});
});
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import IssueDueDate from '~/boards/components/issue_due_date.vue';
import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data';
import { TEST_HOST } from 'jest/helpers/test_constants';
......@@ -71,85 +71,65 @@ describe('RelatedIssuableItem', () => {
});
describe('token state', () => {
let tokenState;
const tokenState = () => wrapper.find({ ref: 'iconElementXL' });
beforeEach(done => {
beforeEach(() => {
wrapper.setProps({ state: 'opened' });
Vue.nextTick(() => {
tokenState = wrapper.find('.issue-token-state-icon-open');
done();
});
});
it('renders if hasState', () => {
expect(tokenState.exists()).toBe(true);
expect(tokenState().exists()).toBe(true);
});
it('renders state title', () => {
const stateTitle = tokenState.attributes('title');
const stateTitle = tokenState().attributes('title');
const formattedCreateDate = formatDate(props.createdAt);
expect(stateTitle).toContain('<span class="bold">Opened</span>');
expect(stateTitle).toContain(`<span class="text-tertiary">${formattedCreateDate}</span>`);
});
it('renders aria label', () => {
expect(tokenState.attributes('aria-label')).toEqual('opened');
expect(tokenState().attributes('aria-label')).toEqual('opened');
});
it('renders open icon when open state', () => {
expect(tokenState.classes('issue-token-state-icon-open')).toBe(true);
expect(tokenState().classes('issue-token-state-icon-open')).toBe(true);
});
it('renders close icon when close state', done => {
it('renders close icon when close state', async () => {
wrapper.setProps({
state: 'closed',
closedAt: '2018-12-01T00:00:00.00Z',
});
await wrapper.vm.$nextTick();
Vue.nextTick(() => {
expect(tokenState.classes('issue-token-state-icon-closed')).toBe(true);
done();
});
expect(tokenState().classes('issue-token-state-icon-closed')).toBe(true);
});
});
describe('token metadata', () => {
let tokenMetadata;
beforeEach(done => {
Vue.nextTick(() => {
tokenMetadata = wrapper.find('.item-meta');
done();
});
});
const tokenMetadata = () => wrapper.find('.item-meta');
it('renders item path and ID', () => {
const pathAndID = tokenMetadata.find('.item-path-id').text();
const pathAndID = tokenMetadata()
.find('.item-path-id')
.text();
expect(pathAndID).toContain('gitlab-org/gitlab-test');
expect(pathAndID).toContain('#1');
});
it('renders milestone icon and name', () => {
const milestoneIcon = tokenMetadata.find('.item-milestone svg use');
const milestoneTitle = tokenMetadata.find('.item-milestone .milestone-title');
const milestoneIcon = tokenMetadata().find('.item-milestone svg use');
const milestoneTitle = tokenMetadata().find('.item-milestone .milestone-title');
expect(milestoneIcon.attributes('href')).toContain('clock');
expect(milestoneTitle.text()).toContain('Milestone title');
});
it('renders due date component', () => {
expect(tokenMetadata.find('.js-due-date-slot').exists()).toBe(true);
});
it('renders weight component', () => {
expect(tokenMetadata.find('.js-weight-slot').exists()).toBe(true);
it('renders due date component with correct due date', () => {
expect(wrapper.find(IssueDueDate).props('date')).toBe(props.dueDate);
});
});
......@@ -163,40 +143,30 @@ describe('RelatedIssuableItem', () => {
});
describe('remove button', () => {
let removeBtn;
const removeButton = () => wrapper.find({ ref: 'removeButton' });
beforeEach(done => {
beforeEach(() => {
wrapper.setProps({ canRemove: true });
Vue.nextTick(() => {
removeBtn = wrapper.find({ ref: 'removeButton' });
done();
});
});
it('renders if canRemove', () => {
expect(removeBtn.exists()).toBe(true);
expect(removeButton().exists()).toBe(true);
});
it('renders disabled button when removeDisabled', done => {
wrapper.vm.removeDisabled = true;
Vue.nextTick(() => {
expect(removeBtn.attributes('disabled')).toEqual('disabled');
it('renders disabled button when removeDisabled', async () => {
wrapper.setData({ removeDisabled: true });
await wrapper.vm.$nextTick();
done();
});
expect(removeButton().attributes('disabled')).toEqual('disabled');
});
it('triggers onRemoveRequest when clicked', () => {
removeBtn.trigger('click');
return wrapper.vm.$nextTick().then(() => {
it('triggers onRemoveRequest when clicked', async () => {
removeButton().trigger('click');
await wrapper.vm.$nextTick();
const { relatedIssueRemoveRequest } = wrapper.emitted();
expect(relatedIssueRemoveRequest.length).toBe(1);
expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]);
});
});
});
});
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