2021-03-11 19:13:27 +05:30
|
|
|
import { GlSprintf } from '@gitlab/ui';
|
2020-03-13 15:44:24 +05:30
|
|
|
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
2020-05-24 23:13:21 +05:30
|
|
|
import { nextTick } from 'vue';
|
2020-03-13 15:44:24 +05:30
|
|
|
import Vuex from 'vuex';
|
|
|
|
import NoteHeader from '~/notes/components/note_header.vue';
|
2021-01-29 00:20:46 +05:30
|
|
|
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
|
2021-03-11 19:13:27 +05:30
|
|
|
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
const localVue = createLocalVue();
|
|
|
|
localVue.use(Vuex);
|
|
|
|
|
|
|
|
const actions = {
|
|
|
|
setTargetNoteHash: jest.fn(),
|
|
|
|
};
|
|
|
|
|
|
|
|
describe('NoteHeader component', () => {
|
|
|
|
let wrapper;
|
|
|
|
|
|
|
|
const findActionsWrapper = () => wrapper.find({ ref: 'discussionActions' });
|
|
|
|
const findChevronIcon = () => wrapper.find({ ref: 'chevronIcon' });
|
|
|
|
const findActionText = () => wrapper.find({ ref: 'actionText' });
|
2020-04-22 19:07:51 +05:30
|
|
|
const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
|
2020-03-13 15:44:24 +05:30
|
|
|
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
|
2020-05-24 23:13:21 +05:30
|
|
|
const findConfidentialIndicator = () => wrapper.find('[data-testid="confidentialIndicator"]');
|
2020-04-22 19:07:51 +05:30
|
|
|
const findSpinner = () => wrapper.find({ ref: 'spinner' });
|
2021-02-22 17:27:13 +05:30
|
|
|
const findAuthorStatus = () => wrapper.find({ ref: 'authorStatus' });
|
|
|
|
|
|
|
|
const statusHtml =
|
|
|
|
'"<span class="user-status-emoji has-tooltip" title="foo bar" data-html="true" data-placement="top"><gl-emoji title="basketball and hoop" data-name="basketball" data-unicode-version="6.0">🏀</gl-emoji></span>"';
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
const author = {
|
|
|
|
avatar_url: null,
|
|
|
|
id: 1,
|
|
|
|
name: 'Root',
|
|
|
|
path: '/root',
|
|
|
|
state: 'active',
|
|
|
|
username: 'root',
|
2021-02-22 17:27:13 +05:30
|
|
|
show_status: true,
|
|
|
|
status_tooltip_html: statusHtml,
|
2021-03-11 19:13:27 +05:30
|
|
|
availability: '',
|
2020-04-22 19:07:51 +05:30
|
|
|
};
|
2020-03-13 15:44:24 +05:30
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
const createComponent = (props) => {
|
2020-03-13 15:44:24 +05:30
|
|
|
wrapper = shallowMount(NoteHeader, {
|
|
|
|
localVue,
|
|
|
|
store: new Vuex.Store({
|
|
|
|
actions,
|
|
|
|
}),
|
2020-04-22 19:07:51 +05:30
|
|
|
propsData: { ...props },
|
2021-03-11 19:13:27 +05:30
|
|
|
stubs: { GlSprintf, UserNameWithStatus },
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
wrapper.destroy();
|
|
|
|
wrapper = null;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render discussion actions when includeToggle is false', () => {
|
|
|
|
createComponent({
|
|
|
|
includeToggle: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findActionsWrapper().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when includes a toggle', () => {
|
|
|
|
it('renders discussion actions', () => {
|
|
|
|
createComponent({
|
|
|
|
includeToggle: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findActionsWrapper().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('emits toggleHandler event on button click', () => {
|
|
|
|
createComponent({
|
|
|
|
includeToggle: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
wrapper.find('.note-action-button').trigger('click');
|
|
|
|
expect(wrapper.emitted('toggleHandler')).toBeDefined();
|
|
|
|
expect(wrapper.emitted('toggleHandler')).toHaveLength(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('has chevron-up icon if expanded prop is true', () => {
|
|
|
|
createComponent({
|
|
|
|
includeToggle: true,
|
|
|
|
expanded: true,
|
|
|
|
});
|
|
|
|
|
2021-01-29 00:20:46 +05:30
|
|
|
expect(findChevronIcon().props('name')).toBe('chevron-up');
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('has chevron-down icon if expanded prop is false', () => {
|
|
|
|
createComponent({
|
|
|
|
includeToggle: true,
|
|
|
|
expanded: false,
|
|
|
|
});
|
|
|
|
|
2021-01-29 00:20:46 +05:30
|
|
|
expect(findChevronIcon().props('name')).toBe('chevron-down');
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders an author link if author is passed to props', () => {
|
2020-04-22 19:07:51 +05:30
|
|
|
createComponent({ author });
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
expect(wrapper.find('.js-user-link').exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-01-29 00:20:46 +05:30
|
|
|
it('renders busy status if author availability is set', () => {
|
2021-03-11 19:13:27 +05:30
|
|
|
createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } });
|
2021-01-29 00:20:46 +05:30
|
|
|
|
|
|
|
expect(wrapper.find('.js-user-link').text()).toContain('(Busy)');
|
|
|
|
});
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
it('renders author status', () => {
|
|
|
|
createComponent({ author });
|
|
|
|
|
|
|
|
expect(findAuthorStatus().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render author status if show_status=false', () => {
|
|
|
|
createComponent({
|
|
|
|
author: { ...author, status: { availability: AVAILABILITY_STATUS.BUSY }, show_status: false },
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findAuthorStatus().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render author status if status_tooltip_html=null', () => {
|
|
|
|
createComponent({
|
|
|
|
author: {
|
|
|
|
...author,
|
|
|
|
status: { availability: AVAILABILITY_STATUS.BUSY },
|
|
|
|
status_tooltip_html: null,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findAuthorStatus().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
it('renders deleted user text if author is not passed as a prop', () => {
|
|
|
|
createComponent();
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain('A deleted user');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render created at information if createdAt is not passed as a prop', () => {
|
|
|
|
createComponent();
|
|
|
|
|
|
|
|
expect(findActionText().exists()).toBe(false);
|
2020-04-22 19:07:51 +05:30
|
|
|
expect(findTimestampLink().exists()).toBe(false);
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('when createdAt is passed as a prop', () => {
|
|
|
|
it('renders action text and a timestamp', () => {
|
|
|
|
createComponent({
|
|
|
|
createdAt: '2017-08-02T10:51:58.559Z',
|
2020-04-22 19:07:51 +05:30
|
|
|
noteId: 123,
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
expect(findActionText().exists()).toBe(true);
|
2020-04-22 19:07:51 +05:30
|
|
|
expect(findTimestampLink().exists()).toBe(true);
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('renders correct actionText if passed', () => {
|
|
|
|
createComponent({
|
|
|
|
createdAt: '2017-08-02T10:51:58.559Z',
|
|
|
|
actionText: 'Test action text',
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findActionText().text()).toBe('Test action text');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('calls an action when timestamp is clicked', () => {
|
|
|
|
createComponent({
|
|
|
|
createdAt: '2017-08-02T10:51:58.559Z',
|
2020-04-22 19:07:51 +05:30
|
|
|
noteId: 123,
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|
2020-04-22 19:07:51 +05:30
|
|
|
findTimestampLink().trigger('click');
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
expect(actions.setTargetNoteHash).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
describe('loading spinner', () => {
|
|
|
|
it('shows spinner when showSpinner is true', () => {
|
|
|
|
createComponent();
|
|
|
|
expect(findSpinner().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not show spinner when showSpinner is false', () => {
|
|
|
|
createComponent({ showSpinner: false });
|
|
|
|
expect(findSpinner().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('timestamp', () => {
|
|
|
|
it('shows timestamp as a link if a noteId was provided', () => {
|
|
|
|
createComponent({ createdAt: new Date().toISOString(), noteId: 123 });
|
|
|
|
expect(findTimestampLink().exists()).toBe(true);
|
|
|
|
expect(findTimestamp().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('shows timestamp as plain text if a noteId was not provided', () => {
|
|
|
|
createComponent({ createdAt: new Date().toISOString() });
|
|
|
|
expect(findTimestampLink().exists()).toBe(false);
|
|
|
|
expect(findTimestamp().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
describe('author username link', () => {
|
|
|
|
it('proxies `mouseenter` event to author name link', () => {
|
|
|
|
createComponent({ author });
|
|
|
|
|
|
|
|
const dispatchEvent = jest.spyOn(wrapper.vm.$refs.authorNameLink, 'dispatchEvent');
|
|
|
|
|
|
|
|
wrapper.find({ ref: 'authorUsernameLink' }).trigger('mouseenter');
|
|
|
|
|
|
|
|
expect(dispatchEvent).toHaveBeenCalledWith(new Event('mouseenter'));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('proxies `mouseleave` event to author name link', () => {
|
|
|
|
createComponent({ author });
|
|
|
|
|
|
|
|
const dispatchEvent = jest.spyOn(wrapper.vm.$refs.authorNameLink, 'dispatchEvent');
|
|
|
|
|
|
|
|
wrapper.find({ ref: 'authorUsernameLink' }).trigger('mouseleave');
|
|
|
|
|
|
|
|
expect(dispatchEvent).toHaveBeenCalledWith(new Event('mouseleave'));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when author status tooltip is opened', () => {
|
|
|
|
it('removes `title` attribute from emoji to prevent duplicate tooltips', () => {
|
|
|
|
createComponent({
|
|
|
|
author: {
|
|
|
|
...author,
|
2021-02-22 17:27:13 +05:30
|
|
|
status_tooltip_html: statusHtml,
|
2020-05-24 23:13:21 +05:30
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return nextTick().then(() => {
|
2021-02-22 17:27:13 +05:30
|
|
|
const authorStatus = findAuthorStatus();
|
2020-05-24 23:13:21 +05:30
|
|
|
authorStatus.trigger('mouseenter');
|
|
|
|
|
|
|
|
expect(authorStatus.find('gl-emoji').attributes('title')).toBeUndefined();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when author username link is hovered', () => {
|
2021-03-08 18:12:59 +05:30
|
|
|
it('toggles hover specific CSS classes on author name link', (done) => {
|
2020-05-24 23:13:21 +05:30
|
|
|
createComponent({ author });
|
|
|
|
|
|
|
|
const authorUsernameLink = wrapper.find({ ref: 'authorUsernameLink' });
|
|
|
|
const authorNameLink = wrapper.find({ ref: 'authorNameLink' });
|
|
|
|
|
|
|
|
authorUsernameLink.trigger('mouseenter');
|
|
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
expect(authorNameLink.classes()).toContain('hover');
|
|
|
|
expect(authorNameLink.classes()).toContain('text-underline');
|
|
|
|
|
|
|
|
authorUsernameLink.trigger('mouseleave');
|
|
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
expect(authorNameLink.classes()).not.toContain('hover');
|
|
|
|
expect(authorNameLink.classes()).not.toContain('text-underline');
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with confidentiality indicator', () => {
|
|
|
|
it.each`
|
|
|
|
status | condition
|
|
|
|
${true} | ${'shows'}
|
|
|
|
${false} | ${'hides'}
|
|
|
|
`('$condition icon indicator when isConfidential is $status', ({ status }) => {
|
|
|
|
createComponent({ isConfidential: status });
|
|
|
|
expect(findConfidentialIndicator().exists()).toBe(status);
|
|
|
|
});
|
|
|
|
});
|
2020-03-13 15:44:24 +05:30
|
|
|
});
|