Commit 7a3b67c6 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch...

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

Convert Jest tests to use VTU in 'spec/frontend/vue_shared'

Closes #197952

See merge request gitlab-org/gitlab!23501
parents 0768943e 6a3b4071
...@@ -59,21 +59,25 @@ export default { ...@@ -59,21 +59,25 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<div v-if="!isLocalStorageAvailable" class="dropdown-info-note"> <div v-if="!isLocalStorageAvailable" ref="localStorageNote" class="dropdown-info-note">
{{ __('This feature requires local storage to be enabled') }} {{ __('This feature requires local storage to be enabled') }}
</div> </div>
<ul v-else-if="hasItems"> <ul v-else-if="hasItems">
<li v-for="(item, index) in processedItems" :key="`processed-items-${index}`"> <li
v-for="(item, index) in processedItems"
ref="dropdownItem"
:key="`processed-items-${index}`"
>
<button <button
type="button" type="button"
class="filtered-search-history-dropdown-item" class="filtered-search-history-dropdown-item js-dropdown-button"
@click="onItemActivated(item.text)" @click="onItemActivated(item.text)"
> >
<span> <span>
<span <span
v-for="(token, tokenIndex) in item.tokens" v-for="(token, tokenIndex) in item.tokens"
:key="`dropdown-token-${tokenIndex}`" :key="`dropdown-token-${tokenIndex}`"
class="filtered-search-history-dropdown-token" class="filtered-search-history-dropdown-token js-dropdown-token"
> >
<span class="name">{{ token.prefix }}</span> <span class="name">{{ token.prefix }}</span>
<span class="name">{{ token.operator }}</span> <span class="name">{{ token.operator }}</span>
...@@ -88,6 +92,7 @@ export default { ...@@ -88,6 +92,7 @@ export default {
<li class="divider"></li> <li class="divider"></li>
<li> <li>
<button <button
ref="clearButton"
type="button" type="button"
class="filtered-search-history-clear-button" class="filtered-search-history-clear-button"
@click="onRequestClearRecentSearches($event)" @click="onRequestClearRecentSearches($event)"
...@@ -96,6 +101,8 @@ export default { ...@@ -96,6 +101,8 @@ export default {
</button> </button>
</li> </li>
</ul> </ul>
<div v-else class="dropdown-info-note">{{ __("You don't have any recent searches") }}</div> <div v-else ref="dropdownNote" class="dropdown-info-note">
{{ __("You don't have any recent searches") }}
</div>
</div> </div>
</template> </template>
...@@ -29,7 +29,7 @@ export default { ...@@ -29,7 +29,7 @@ export default {
</script> </script>
<template> <template>
<div :class="[sizeClass, identiconBackgroundClass]" class="avatar identicon"> <div ref="identicon" :class="[sizeClass, identiconBackgroundClass]" class="avatar identicon">
{{ identiconTitle }} {{ identiconTitle }}
</div> </div>
</template> </template>
...@@ -65,14 +65,14 @@ export default { ...@@ -65,14 +65,14 @@ export default {
<div class="issuable-note-warning"> <div class="issuable-note-warning">
<icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" /> <icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" />
<span v-if="isLockedAndConfidential"> <span v-if="isLockedAndConfidential" ref="lockedAndConfidential">
<span v-html="confidentialAndLockedDiscussionText"></span> <span v-html="confidentialAndLockedDiscussionText"></span>
{{ {{
__("People without permission will never get a notification and won't be able to comment.") __("People without permission will never get a notification and won't be able to comment.")
}} }}
</span> </span>
<span v-else-if="isConfidential"> <span v-else-if="isConfidential" ref="confidential">
{{ __('This is a confidential issue.') }} {{ __('This is a confidential issue.') }}
{{ __('People without permission will never get a notification.') }} {{ __('People without permission will never get a notification.') }}
<gl-link :href="confidentialIssueDocsPath" target="_blank"> <gl-link :href="confidentialIssueDocsPath" target="_blank">
...@@ -80,7 +80,7 @@ export default { ...@@ -80,7 +80,7 @@ export default {
</gl-link> </gl-link>
</span> </span>
<span v-else-if="isLocked"> <span v-else-if="isLocked" ref="locked">
{{ __('This issue is locked.') }} {{ __('This issue is locked.') }}
{{ __('Only project members can comment.') }} {{ __('Only project members can comment.') }}
<gl-link :href="lockedIssueDocsPath" target="_blank"> <gl-link :href="lockedIssueDocsPath" target="_blank">
......
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
:img-size="40" :img-size="40"
/> />
</div> </div>
<div :class="{ discussion: !note.individual_note }" class="timeline-content"> <div ref="note" :class="{ discussion: !note.individual_note }" class="timeline-content">
<div class="note-header"> <div class="note-header">
<div class="note-header-info"> <div class="note-header-info">
<a :href="getUserData.path"> <a :href="getUserData.path">
......
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import eventHub from '~/filtered_search/event_hub'; import eventHub from '~/filtered_search/event_hub';
import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content.vue'; import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content.vue';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
const createComponent = propsData => { describe('Recent Searches Dropdown Content', () => {
const Component = Vue.extend(RecentSearchesDropdownContent); let wrapper;
return new Component({ const findLocalStorageNote = () => wrapper.find({ ref: 'localStorageNote' });
el: document.createElement('div'), const findDropdownItems = () => wrapper.findAll({ ref: 'dropdownItem' });
propsData, const findDropdownNote = () => wrapper.find({ ref: 'dropdownNote' });
});
};
// Remove all the newlines and whitespace from the formatted markup
const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim();
describe('RecentSearchesDropdownContent', () => { const createComponent = props => {
const propsDataWithoutItems = { wrapper = shallowMount(RecentSearchesDropdownContent, {
items: [], propsData: {
allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(),
};
const propsDataWithItems = {
items: ['foo', 'author:@root label:~foo bar'],
allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(), allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(),
items: [],
isLocalStorageAvailable: false,
...props,
},
});
}; };
let vm;
afterEach(() => { afterEach(() => {
if (vm) { wrapper.destroy();
vm.$destroy(); wrapper = null;
}
}); });
describe('with no items', () => { describe('when local storage is not available', () => {
let el;
beforeEach(() => { beforeEach(() => {
vm = createComponent(propsDataWithoutItems); createComponent();
el = vm.$el;
}); });
it('should render empty state', () => { it('renders a note about enabling local storage', () => {
expect(el.querySelector('.dropdown-info-note')).toBeDefined(); expect(findLocalStorageNote().exists()).toBe(true);
const items = el.querySelectorAll('.filtered-search-history-dropdown-item');
expect(items.length).toEqual(propsDataWithoutItems.items.length);
});
}); });
describe('with items', () => { it('does not render dropdown items', () => {
let el; expect(findDropdownItems().exists()).toBe(false);
beforeEach(() => {
vm = createComponent(propsDataWithItems);
el = vm.$el;
}); });
it('should render clear recent searches button', () => { it('does not render dropdownNote', () => {
expect(el.querySelector('.filtered-search-history-clear-button')).toBeDefined(); expect(findDropdownNote().exists()).toBe(false);
});
it('should render recent search items', () => {
const items = el.querySelectorAll('.filtered-search-history-dropdown-item');
expect(items.length).toEqual(propsDataWithItems.items.length);
expect(
trimMarkupWhitespace(
items[0].querySelector('.filtered-search-history-dropdown-search-token').textContent,
),
).toEqual('foo');
const item1Tokens = items[1].querySelectorAll('.filtered-search-history-dropdown-token');
expect(item1Tokens.length).toEqual(2);
expect(item1Tokens[0].querySelector('.name').textContent).toEqual('author:');
expect(item1Tokens[0].querySelector('.value').textContent).toEqual('@root');
expect(item1Tokens[1].querySelector('.name').textContent).toEqual('label:');
expect(item1Tokens[1].querySelector('.value').textContent).toEqual('~foo');
expect(
trimMarkupWhitespace(
items[1].querySelector('.filtered-search-history-dropdown-search-token').textContent,
),
).toEqual('bar');
}); });
}); });
describe('if isLocalStorageAvailable is `false`', () => { describe('when localStorage is available and items array is not empty', () => {
let el; let onRecentSearchesItemSelectedSpy;
let onRequestClearRecentSearchesSpy;
beforeEach(() => {
const props = Object.assign({ isLocalStorageAvailable: false }, propsDataWithItems);
vm = createComponent(props); beforeAll(() => {
el = vm.$el; onRecentSearchesItemSelectedSpy = jest.fn();
onRequestClearRecentSearchesSpy = jest.fn();
eventHub.$on('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy);
eventHub.$on('requestClearRecentSearches', onRequestClearRecentSearchesSpy);
}); });
it('should render an info note', () => { beforeEach(() => {
const note = el.querySelector('.dropdown-info-note'); createComponent({
const items = el.querySelectorAll('.filtered-search-history-dropdown-item'); items: ['foo', 'author:@root label:~foo bar'],
isLocalStorageAvailable: true,
expect(note).toBeDefined();
expect(note.innerText.trim()).toBe('This feature requires local storage to be enabled');
expect(items.length).toEqual(propsDataWithoutItems.items.length);
}); });
}); });
describe('computed', () => { afterAll(() => {
describe('processedItems', () => { eventHub.$off('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy);
it('with items', () => { eventHub.$off('requestClearRecentSearchesSpy', onRequestClearRecentSearchesSpy);
vm = createComponent(propsDataWithItems);
const { processedItems } = vm;
expect(processedItems.length).toEqual(2);
expect(processedItems[0].text).toEqual(propsDataWithItems.items[0]);
expect(processedItems[0].tokens).toEqual([]);
expect(processedItems[0].searchToken).toEqual('foo');
expect(processedItems[1].text).toEqual(propsDataWithItems.items[1]);
expect(processedItems[1].tokens.length).toEqual(2);
expect(processedItems[1].tokens[0].prefix).toEqual('author:');
expect(processedItems[1].tokens[0].suffix).toEqual('@root');
expect(processedItems[1].tokens[1].prefix).toEqual('label:');
expect(processedItems[1].tokens[1].suffix).toEqual('~foo');
expect(processedItems[1].searchToken).toEqual('bar');
}); });
it('with no items', () => { it('does not render a note about enabling local storage', () => {
vm = createComponent(propsDataWithoutItems); expect(findLocalStorageNote().exists()).toBe(false);
const { processedItems } = vm;
expect(processedItems.length).toEqual(0);
});
}); });
describe('hasItems', () => { it('does not render dropdownNote', () => {
it('with items', () => { expect(findDropdownNote().exists()).toBe(false);
vm = createComponent(propsDataWithItems);
const { hasItems } = vm;
expect(hasItems).toEqual(true);
}); });
it('with no items', () => { it('renders a correct amount of dropdown items', () => {
vm = createComponent(propsDataWithoutItems); expect(findDropdownItems()).toHaveLength(2);
const { hasItems } = vm;
expect(hasItems).toEqual(false);
}); });
});
});
describe('methods', () => {
describe('onItemActivated', () => {
let onRecentSearchesItemSelectedSpy;
beforeEach(() => {
onRecentSearchesItemSelectedSpy = jest.fn();
eventHub.$on('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy);
vm = createComponent(propsDataWithItems); it('expect second dropdown to have 2 tokens', () => {
expect(
findDropdownItems()
.at(1)
.findAll('.js-dropdown-token'),
).toHaveLength(2);
}); });
afterEach(() => { it('emits recentSearchesItemSelected on dropdown item click', () => {
eventHub.$off('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy); findDropdownItems()
.at(0)
.find('.js-dropdown-button')
.trigger('click');
expect(onRecentSearchesItemSelectedSpy).toHaveBeenCalledWith('foo');
}); });
it('emits event', () => { it('emits requestClearRecentSearches on Clear resent searches button', () => {
expect(onRecentSearchesItemSelectedSpy).not.toHaveBeenCalled(); wrapper.find({ ref: 'clearButton' }).trigger('click');
vm.onItemActivated('something');
expect(onRecentSearchesItemSelectedSpy).toHaveBeenCalledWith('something'); expect(onRequestClearRecentSearchesSpy).toHaveBeenCalled();
}); });
}); });
describe('onRequestClearRecentSearches', () => { describe('when locale storage is available and items array is empty', () => {
let onRequestClearRecentSearchesSpy;
beforeEach(() => { beforeEach(() => {
onRequestClearRecentSearchesSpy = jest.fn(); createComponent({
eventHub.$on('requestClearRecentSearches', onRequestClearRecentSearchesSpy); isLocalStorageAvailable: true,
vm = createComponent(propsDataWithItems);
}); });
afterEach(() => {
eventHub.$off('requestClearRecentSearches', onRequestClearRecentSearchesSpy);
}); });
it('emits event', () => { it('does not render a note about enabling local storage', () => {
expect(onRequestClearRecentSearchesSpy).not.toHaveBeenCalled(); expect(findLocalStorageNote().exists()).toBe(false);
vm.onRequestClearRecentSearches({ stopPropagation: () => {} }); });
expect(onRequestClearRecentSearchesSpy).toHaveBeenCalled(); it('does not render dropdown items', () => {
expect(findDropdownItems().exists()).toBe(false);
}); });
it('renders dropdown note', () => {
expect(findDropdownNote().exists()).toBe(true);
}); });
}); });
}); });
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Code Block matches snapshot 1`] = `
<pre
class="code-block rounded"
>
<code
class="d-block"
>
test-code
</code>
</pre>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Identicon matches snapshot 1`] = `
<div
class="avatar identicon s40 bg2"
>
E
</div>
`;
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import component from '~/vue_shared/components/code_block.vue'; import CodeBlock from '~/vue_shared/components/code_block.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Code Block', () => { describe('Code Block', () => {
const Component = Vue.extend(component); let wrapper;
let vm;
afterEach(() => { const createComponent = () => {
vm.$destroy(); wrapper = shallowMount(CodeBlock, {
}); propsData: {
code: 'test-code',
it('renders a code block with the provided code', () => { },
const code =
"Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in \u003ctop (required)\u003e'";
vm = mountComponent(Component, {
code,
}); });
};
expect(vm.$el.querySelector('code').textContent).toEqual(code); afterEach(() => {
wrapper.destroy();
wrapper = null;
}); });
it('escapes XSS injections', () => { it('matches snapshot', () => {
const code = 'CCC&lt;img src=x onerror=alert(document.domain)&gt;'; createComponent();
vm = mountComponent(Component, {
code,
});
expect(vm.$el.querySelector('code').textContent).toEqual(code); expect(wrapper.element).toMatchSnapshot();
}); });
}); });
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import identiconComponent from '~/vue_shared/components/identicon.vue'; import IdenticonComponent from '~/vue_shared/components/identicon.vue';
const createComponent = sizeClass => { describe('Identicon', () => {
const Component = Vue.extend(identiconComponent); let wrapper;
return new Component({ const createComponent = () => {
wrapper = shallowMount(IdenticonComponent, {
propsData: { propsData: {
entityId: 1, entityId: 1,
entityName: 'entity-name', entityName: 'entity-name',
sizeClass, sizeClass: 's40',
}, },
}).$mount();
};
describe('IdenticonComponent', () => {
describe('computed', () => {
let vm;
beforeEach(() => {
vm = createComponent();
}); });
};
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); wrapper = null;
describe('identiconBackgroundClass', () => {
it('should return bg class based on entityId', () => {
vm.entityId = 4;
expect(vm.identiconBackgroundClass).toBeDefined();
expect(vm.identiconBackgroundClass).toBe('bg5');
});
}); });
describe('identiconTitle', () => { it('matches snapshot', () => {
it('should return first letter of entity title in uppercase', () => { createComponent();
vm.entityName = 'dummy-group';
expect(vm.identiconTitle).toBeDefined(); expect(wrapper.element).toMatchSnapshot();
expect(vm.identiconTitle).toBe('D');
}); });
});
});
describe('template', () => {
it('should render identicon', () => {
const vm = createComponent();
expect(vm.$el.nodeName).toBe('DIV'); it('adds a correct class to identicon', () => {
expect(vm.$el.classList.contains('identicon')).toBeTruthy(); createComponent();
expect(vm.$el.classList.contains('s40')).toBeTruthy();
expect(vm.$el.classList.contains('bg2')).toBeTruthy();
vm.$destroy();
});
it('should render identicon with provided sizing class', () => {
const vm = createComponent('s32');
expect(vm.$el.classList.contains('s32')).toBeTruthy(); expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2');
vm.$destroy();
});
}); });
}); });
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Issue Warning Component when issue is confidential but not locked renders information about confidential issue 1`] = `
<span>
This is a confidential issue.
People without permission will never get a notification.
<gl-link-stub
href="confidential-path"
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
exports[`Issue Warning Component when issue is locked and confidential renders information about locked and confidential issue 1`] = `
<span>
<span>
This issue is
<a
href=""
rel="noopener noreferrer"
target="_blank"
>
confidential
</a>
and
<a
href=""
rel="noopener noreferrer"
target="_blank"
>
locked
</a>
.
</span>
People without permission will never get a notification and won't be able to comment.
</span>
`;
exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
<span>
This issue is locked.
Only project members can comment.
<gl-link-stub
href="locked-path"
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import mountComponent from 'helpers/vue_mount_component_helper'; import IssueWarning from '~/vue_shared/components/issue/issue_warning.vue';
import issueWarning from '~/vue_shared/components/issue/issue_warning.vue'; import Icon from '~/vue_shared/components/icon.vue';
const IssueWarning = Vue.extend(issueWarning); describe('Issue Warning Component', () => {
let wrapper;
function formatWarning(string) { const findIcon = () => wrapper.find(Icon);
// Replace newlines with a space then replace multiple spaces with one space const findLockedBlock = () => wrapper.find({ ref: 'locked' });
return string const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' });
.trim() const findLockedAndConfidentialBlock = () => wrapper.find({ ref: 'lockedAndConfidential' });
.replace(/\n/g, ' ')
.replace(/\s\s+/g, ' ');
}
describe('Issue Warning Component', () => { const createComponent = props => {
describe('isLocked', () => { wrapper = shallowMount(IssueWarning, {
it('should render locked issue warning information', () => { propsData: {
const props = { ...props,
isLocked: true, },
lockedIssueDocsPath: 'docs/issues/locked', });
}; };
const vm = mountComponent(IssueWarning, props);
expect( afterEach(() => {
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), wrapper.destroy();
).toMatch(/lock$/); wrapper = null;
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( });
'This issue is locked. Only project members can comment. Learn more',
); describe('when issue is locked but not confidential', () => {
expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath); beforeEach(() => {
createComponent({
isLocked: true,
lockedIssueDocsPath: 'locked-path',
isConfidential: false,
});
});
it('renders information about locked issue', () => {
expect(findLockedBlock().exists()).toBe(true);
expect(findLockedBlock().element).toMatchSnapshot();
});
it('renders warning icon', () => {
expect(findIcon().exists()).toBe(true);
});
it('does not render information about locked and confidential issue', () => {
expect(findLockedAndConfidentialBlock().exists()).toBe(false);
});
it('does not render information about confidential issue', () => {
expect(findConfidentialBlock().exists()).toBe(false);
}); });
}); });
describe('isConfidential', () => { describe('when issue is confidential but not locked', () => {
it('should render confidential issue warning information', () => { beforeEach(() => {
const props = { createComponent({
isLocked: false,
isConfidential: true, isConfidential: true,
confidentialIssueDocsPath: '/docs/issues/confidential', confidentialIssueDocsPath: 'confidential-path',
}; });
const vm = mountComponent(IssueWarning, props); });
it('renders information about confidential issue', () => {
expect(findConfidentialBlock().exists()).toBe(true);
expect(findConfidentialBlock().element).toMatchSnapshot();
});
it('renders warning icon', () => {
expect(wrapper.find(Icon).exists()).toBe(true);
});
it('does not render information about locked issue', () => {
expect(findLockedBlock().exists()).toBe(false);
});
expect( it('does not render information about locked and confidential issue', () => {
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), expect(findLockedAndConfidentialBlock().exists()).toBe(false);
).toMatch(/eye-slash$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
'This is a confidential issue. People without permission will never get a notification. Learn more',
);
expect(vm.$el.querySelector('a').href).toContain(props.confidentialIssueDocsPath);
}); });
}); });
describe('isLocked and isConfidential', () => { describe('when issue is locked and confidential', () => {
it('should render locked and confidential issue warning information', () => { beforeEach(() => {
const vm = mountComponent(IssueWarning, { createComponent({
isLocked: true, isLocked: true,
isConfidential: true, isConfidential: true,
}); });
});
it('renders information about locked and confidential issue', () => {
expect(findLockedAndConfidentialBlock().exists()).toBe(true);
expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
});
it('does not render warning icon', () => {
expect(wrapper.find(Icon).exists()).toBe(false);
});
it('does not render information about locked issue', () => {
expect(findLockedBlock().exists()).toBe(false);
});
expect(vm.$el.querySelector('.icon')).toBeFalsy(); it('does not render information about confidential issue', () => {
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( expect(findConfidentialBlock().exists()).toBe(false);
"This issue is confidential and locked. People without permission will never get a notification and won't be able to comment.",
);
}); });
}); });
}); });
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Suggestion Diff component matches snapshot 1`] = `
<div
class="md-suggestion"
>
<suggestion-diff-header-stub
class="qa-suggestion-diff-header js-suggestion-diff-header"
helppagepath="path_to_docs"
/>
<table
class="mb-3 md-suggestion-diff js-syntax-highlight code"
>
<tbody>
<suggestion-diff-row-stub
line="[object Object]"
/>
<suggestion-diff-row-stub
line="[object Object]"
/>
<suggestion-diff-row-stub
line="[object Object]"
/>
</tbody>
</table>
</div>
`;
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue'; import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue';
import { selectDiffLines } from '~/vue_shared/components/lib/utils/diff_utils'; import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue';
import SuggestionDiffRow from '~/vue_shared/components/markdown/suggestion_diff_row.vue';
const MOCK_DATA = { const MOCK_DATA = {
canApply: true,
suggestion: { suggestion: {
id: 1, id: 1,
diff_lines: [ diff_lines: [
...@@ -42,60 +42,45 @@ const MOCK_DATA = { ...@@ -42,60 +42,45 @@ const MOCK_DATA = {
helpPagePath: 'path_to_docs', helpPagePath: 'path_to_docs',
}; };
const lines = selectDiffLines(MOCK_DATA.suggestion.diff_lines);
const newLines = lines.filter(line => line.type === 'new');
describe('Suggestion Diff component', () => { describe('Suggestion Diff component', () => {
let vm; let wrapper;
beforeEach(done => {
const Component = Vue.extend(SuggestionDiffComponent);
vm = new Component({ const createComponent = () => {
propsData: MOCK_DATA, wrapper = shallowMount(SuggestionDiffComponent, {
}).$mount(); propsData: {
...MOCK_DATA,
Vue.nextTick(done); },
}); });
};
describe('init', () => { beforeEach(() => {
it('renders a suggestion header', () => { createComponent();
expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull();
}); });
it('renders a diff table with syntax highlighting', () => { afterEach(() => {
expect(vm.$el.querySelector('.md-suggestion-diff.js-syntax-highlight.code')).not.toBeNull(); wrapper.destroy();
wrapper = null;
}); });
it('renders the oldLineNumber', () => { it('matches snapshot', () => {
const fromLine = vm.$el.querySelector('.old_line').innerHTML; expect(wrapper.element).toMatchSnapshot();
expect(parseInt(fromLine, 10)).toBe(lines[0].old_line);
}); });
it('renders the oldLineContent', () => { it('renders a correct amount of suggestion diff rows', () => {
const fromContent = vm.$el.querySelector('.line_content.old').innerHTML; expect(wrapper.findAll(SuggestionDiffRow)).toHaveLength(3);
expect(fromContent.includes(lines[0].text)).toBe(true);
}); });
it('renders new lines', () => { it('emits apply event on sugestion diff header apply', () => {
const newLinesElements = vm.$el.querySelectorAll('.line_holder.new'); wrapper.find(SuggestionDiffHeader).vm.$emit('apply', 'test-event');
newLinesElements.forEach((line, i) => { expect(wrapper.emitted('apply')).toBeDefined();
expect(newLinesElements[i].innerHTML.includes(newLines[i].new_line)).toBe(true); expect(wrapper.emitted('apply')).toEqual([
expect(newLinesElements[i].innerHTML.includes(newLines[i].text)).toBe(true); [
}); {
}); callback: 'test-event',
}); suggestionId: 1,
},
describe('applySuggestion', () => { ],
it('emits apply event when applySuggestion is called', () => { ]);
const callback = () => {};
jest.spyOn(vm, '$emit').mockImplementation(() => {});
vm.applySuggestion(callback);
expect(vm.$emit).toHaveBeenCalledWith('apply', { suggestionId: vm.suggestion.id, callback });
});
}); });
}); });
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Issue placeholder note component matches snapshot 1`] = `
<timeline-entry-item-stub
class="note note-wrapper being-posted fade-in-half"
>
<div
class="timeline-icon"
>
<user-avatar-link-stub
imgalt=""
imgcssclasses=""
imgsize="40"
imgsrc="mock_path"
linkhref="/root"
tooltipplacement="top"
tooltiptext=""
username=""
/>
</div>
<div
class="timeline-content discussion"
>
<div
class="note-header"
>
<div
class="note-header-info"
>
<a
href="/root"
>
<span
class="d-none d-sm-inline-block bold"
>
Root
</span>
<span
class="note-headline-light"
>
@root
</span>
</a>
</div>
</div>
<div
class="note-body"
>
<div
class="note-text md"
>
<p>
Foo
</p>
</div>
</div>
</div>
</timeline-entry-item-stub>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Placeholder system note component matches snapshot 1`] = `
<timeline-entry-item-stub
class="note system-note being-posted fade-in-half"
>
<div
class="timeline-content"
>
<em>
This is a placeholder
</em>
</div>
</timeline-entry-item-stub>
`;
import Vue from 'vue'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; import Vuex from 'vuex';
import createStore from '~/notes/stores'; import IssuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
import { userDataMock } from '../../../notes/mock_data'; import { userDataMock } from '../../../notes/mock_data';
describe('issue placeholder system note component', () => { const localVue = createLocalVue();
let store; localVue.use(Vuex);
let vm;
const getters = {
beforeEach(() => { getUserData: () => userDataMock,
const Component = Vue.extend(issuePlaceholderNote); };
store = createStore();
store.dispatch('setUserData', userDataMock); describe('Issue placeholder note component', () => {
vm = new Component({ let wrapper;
store,
propsData: { note: { body: 'Foo' } }, const findNote = () => wrapper.find({ ref: 'note' });
}).$mount();
const createComponent = (isIndividual = false) => {
wrapper = shallowMount(IssuePlaceholderNote, {
localVue,
store: new Vuex.Store({
getters,
}),
propsData: {
note: {
body: 'Foo',
individual_note: isIndividual,
},
},
}); });
};
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('user information', () => { it('matches snapshot', () => {
it('should render user avatar with link', () => { createComponent();
expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual(
userDataMock.path,
);
expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual( expect(wrapper.element).toMatchSnapshot();
`${userDataMock.avatar_url}?width=40`,
);
});
}); });
describe('note content', () => { it('does not add "discussion" class to individual notes', () => {
it('should render note header information', () => { createComponent(true);
expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual(
userDataMock.path,
);
expect( expect(findNote().classes()).not.toContain('discussion');
vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(),
).toEqual(`@${userDataMock.username}`);
}); });
it('should render note body', () => { it('adds "discussion" class to non-individual notes', () => {
expect(vm.$el.querySelector('.note-text p').textContent.trim()).toEqual('Foo'); createComponent();
});
expect(findNote().classes()).toContain('discussion');
}); });
}); });
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import mountComponent from 'helpers/vue_mount_component_helper'; import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
describe('placeholder system note component', () => { describe('Placeholder system note component', () => {
let PlaceholderSystemNote; let wrapper;
let vm;
beforeEach(() => { const createComponent = () => {
PlaceholderSystemNote = Vue.extend(placeholderSystemNote); wrapper = shallowMount(PlaceholderSystemNote, {
propsData: {
note: { body: 'This is a placeholder' },
},
}); });
};
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
it('should render system note placeholder with plain text', () => { it('matches snapshot', () => {
vm = mountComponent(PlaceholderSystemNote, { createComponent();
note: { body: 'This is a placeholder' },
});
expect(vm.$el.tagName).toEqual('LI'); expect(wrapper.element).toMatchSnapshot();
expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual(
'This is a placeholder',
);
}); });
}); });
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