Commit 30b200d8 authored by Phil Hughes's avatar Phil Hughes

Merge branch '197948-convert-jest-tests-to-use-vtu-in-spec-frontend-notes-components' into 'master'

Convert Jest tests to use VTU in 'spec/frontend/notes/components'

Closes #197948

See merge request gitlab-org/gitlab!23401
parents cc2b47fe ced04cfb
......@@ -38,12 +38,12 @@ export default {
<icon name="comment" />
</div>
<div class="timeline-content">
<div v-html="timelineContent"></div>
<div ref="timelineContent" v-html="timelineContent"></div>
<div class="discussion-filter-actions mt-2">
<gl-button variant="default" @click="selectFilter(0)">
<gl-button ref="showAllActivity" variant="default" @click="selectFilter(0)">
{{ __('Show all activity') }}
</gl-button>
<gl-button variant="default" @click="selectFilter(1)">
<gl-button ref="showComments" variant="default" @click="selectFilter(1)">
{{ __('Show comments only') }}
</gl-button>
</div>
......
......@@ -12,11 +12,23 @@ export default {
<template>
<div class="note-attachment">
<a v-if="attachment.image" :href="attachment.url" target="_blank" rel="noopener noreferrer">
<a
v-if="attachment.image"
ref="attachmentImage"
:href="attachment.url"
target="_blank"
rel="noopener noreferrer"
>
<img :src="attachment.url" class="note-image-attach" />
</a>
<div class="attachment">
<a v-if="attachment.url" :href="attachment.url" target="_blank" rel="noopener noreferrer">
<a
v-if="attachment.url"
ref="attachmentUrl"
:href="attachment.url"
target="_blank"
rel="noopener noreferrer"
>
<i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }}
</a>
</div>
......
......@@ -63,13 +63,13 @@ export default {
<template>
<div class="note-header-info">
<div v-if="includeToggle" class="discussion-actions">
<div v-if="includeToggle" ref="discussionActions" class="discussion-actions">
<button
class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button"
@click="handleToggle"
>
<i :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
<i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle thread') }}
</button>
</div>
......@@ -90,10 +90,11 @@ export default {
<span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span class="system-note-separator">
<span ref="actionText" class="system-note-separator">
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
ref="noteTimestamp"
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
@click="updateTargetNoteHash"
......
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
import eventHub from '~/notes/event_hub';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('DiscussionFilterNote component', () => {
let vm;
let wrapper;
const createComponent = () => {
const Component = Vue.extend(DiscussionFilterNote);
return mountComponent(Component);
wrapper = shallowMount(DiscussionFilterNote);
};
beforeEach(() => {
vm = createComponent();
createComponent();
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
describe('computed', () => {
describe('timelineContent', () => {
it('returns string containing instruction for switching feed type', () => {
expect(vm.timelineContent).toBe(
"You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.",
it('timelineContent renders a string containing instruction for switching feed type', () => {
expect(wrapper.find({ ref: 'timelineContent' }).html()).toBe(
"<div>You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.</div>",
);
});
});
});
describe('methods', () => {
describe('selectFilter', () => {
it('emits `dropdownSelect` event on `eventHub` with provided param', () => {
it('emits `dropdownSelect` event with 0 parameter on clicking Show all activity button', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
wrapper.find({ ref: 'showAllActivity' }).vm.$emit('click');
vm.selectFilter(1);
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
});
});
});
describe('template', () => {
it('renders component container element', () => {
expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true);
});
it('renders comment icon element', () => {
expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain(
'comment',
);
});
it('renders filter information note', () => {
expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain(
"You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
);
});
it('renders filter buttons', () => {
const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions');
expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain(
'Show all activity',
);
expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain(
'Show comments only',
);
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 0);
});
it('clicking `Show all activity` button calls `selectFilter("all")` method', () => {
const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child');
jest.spyOn(vm, 'selectFilter').mockImplementation(() => {});
showAllBtn.dispatchEvent(new Event('click'));
expect(vm.selectFilter).toHaveBeenCalledWith(0);
});
it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => {
const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child');
jest.spyOn(vm, 'selectFilter').mockImplementation(() => {});
showAllBtn.dispatchEvent(new Event('click'));
it('emits `dropdownSelect` event with 1 parameter on clicking Show comments only button', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
wrapper.find({ ref: 'showComments' }).vm.$emit('click');
expect(vm.selectFilter).toHaveBeenCalledWith(1);
});
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
});
});
import Vue from 'vue';
import noteAttachment from '~/notes/components/note_attachment.vue';
describe('issue note attachment', () => {
it('should render properly', () => {
const props = {
attachment: {
filename: 'dk.png',
image: true,
url: '/dk.png',
import { shallowMount } from '@vue/test-utils';
import NoteAttachment from '~/notes/components/note_attachment.vue';
describe('Issue note attachment', () => {
let wrapper;
const findImage = () => wrapper.find({ ref: 'attachmentImage' });
const findUrl = () => wrapper.find({ ref: 'attachmentUrl' });
const createComponent = attachment => {
wrapper = shallowMount(NoteAttachment, {
propsData: {
attachment,
},
});
};
const Component = Vue.extend(noteAttachment);
const vm = new Component({
propsData: props,
}).$mount();
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders attachment image if it is passed in attachment prop', () => {
createComponent({
image: 'test-image',
});
expect(findImage().exists()).toBe(true);
});
it('renders attachment url if it is passed in attachment prop', () => {
createComponent({
url: 'test-url',
});
expect(findUrl().exists()).toBe(true);
});
it('does not render image and url if attachment object is empty', () => {
createComponent({});
expect(vm.$el.classList.contains('note-attachment')).toBeTruthy();
expect(vm.$el.querySelector('img').src).toContain(props.attachment.url);
expect(vm.$el.querySelector('a').href).toContain(props.attachment.url);
expect(findImage().exists()).toBe(false);
expect(findUrl().exists()).toBe(false);
});
});
import Vue from 'vue';
import noteHeader from '~/notes/components/note_header.vue';
import createStore from '~/notes/stores';
describe('note_header component', () => {
let store;
let vm;
let Component;
beforeEach(() => {
Component = Vue.extend(noteHeader);
store = createStore();
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import NoteHeader from '~/notes/components/note_header.vue';
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' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
const createComponent = props => {
wrapper = shallowMount(NoteHeader, {
localVue,
store: new Vuex.Store({
actions,
}),
propsData: {
...props,
actionTextHtml: '',
noteId: '1394',
},
});
};
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
describe('individual note', () => {
beforeEach(() => {
vm = new Component({
store,
propsData: {
actionText: 'commented',
actionTextHtml: '',
author: {
avatar_url: null,
id: 1,
name: 'Root',
path: '/root',
state: 'active',
username: 'root',
},
createdAt: '2017-08-02T10:51:58.559Z',
it('does not render discussion actions when includeToggle is false', () => {
createComponent({
includeToggle: false,
noteId: '1394',
});
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,
},
}).$mount();
});
it('should render user information', () => {
expect(vm.$el.querySelector('.note-header-author-name').textContent.trim()).toEqual('Root');
expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual('/root');
expect(vm.$el.querySelector('.note-header-info a').dataset.userId).toEqual('1');
expect(vm.$el.querySelector('.note-header-info a').dataset.username).toEqual('root');
expect(vm.$el.querySelector('.note-header-info a').classList).toContain('js-user-link');
expect(findChevronIcon().classes()).toContain('fa-chevron-up');
});
it('should render timestamp link', () => {
expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined();
it('has chevron-down icon if expanded prop is false', () => {
createComponent({
includeToggle: true,
expanded: false,
});
it('should not render user information when prop `author` is empty object', done => {
vm.author = {};
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.note-header-author-name')).toBeNull();
})
.then(done)
.catch(done.fail);
expect(findChevronIcon().classes()).toContain('fa-chevron-down');
});
});
describe('discussion', () => {
beforeEach(() => {
vm = new Component({
store,
propsData: {
actionText: 'started a discussion',
actionTextHtml: '',
it('renders an author link if author is passed to props', () => {
createComponent({
author: {
avatar_url: null,
id: 1,
......@@ -77,49 +92,50 @@ describe('note_header component', () => {
state: 'active',
username: 'root',
},
createdAt: '2017-08-02T10:51:58.559Z',
includeToggle: true,
noteId: '1395',
expanded: true,
},
}).$mount();
});
it('should render toggle button', () => {
expect(vm.$el.querySelector('.js-vue-toggle-button')).toBeDefined();
expect(wrapper.find('.js-user-link').exists()).toBe(true);
});
it('emits toggle event on click', done => {
jest.spyOn(vm, '$emit').mockImplementation(() => {});
vm.$el.querySelector('.js-vue-toggle-button').click();
it('renders deleted user text if author is not passed as a prop', () => {
createComponent();
Vue.nextTick(() => {
expect(vm.$emit).toHaveBeenCalledWith('toggleHandler');
done();
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);
expect(findTimestamp().exists()).toBe(false);
});
it('renders up arrow when open', done => {
vm.expanded = true;
describe('when createdAt is passed as a prop', () => {
it('renders action text and a timestamp', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
});
Vue.nextTick(() => {
expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
'fa-chevron-up',
);
done();
expect(findActionText().exists()).toBe(true);
expect(findTimestamp().exists()).toBe(true);
});
it('renders correct actionText if passed', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
actionText: 'Test action text',
});
it('renders down arrow when closed', done => {
vm.expanded = false;
expect(findActionText().text()).toBe('Test action text');
});
Vue.nextTick(() => {
expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
'fa-chevron-down',
);
done();
it('calls an action when timestamp is clicked', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
});
findTimestamp().trigger('click');
expect(actions.setTargetNoteHash).toHaveBeenCalled();
});
});
});
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