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