diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-23 21:08:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-23 21:08:53 +0300 |
commit | d933bc5a8738d24898c5a82cc72ee9bd050425e6 (patch) | |
tree | 6d4c5ffedc32dc82c3fd6e4e3031f7981505655a /spec | |
parent | 3f9e1b261121f4dbd045341241f81b47356c99cf (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
13 files changed, 492 insertions, 387 deletions
diff --git a/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js index 2543fb8768b..c0851096d8e 100644 --- a/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js +++ b/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js @@ -1,201 +1,125 @@ -import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; import eventHub from '~/filtered_search/event_hub'; import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content.vue'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; -const createComponent = propsData => { - const Component = Vue.extend(RecentSearchesDropdownContent); - - return new Component({ - el: document.createElement('div'), - propsData, - }); -}; - -// Remove all the newlines and whitespace from the formatted markup -const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim(); - -describe('RecentSearchesDropdownContent', () => { - const propsDataWithoutItems = { - items: [], - allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(), - }; - const propsDataWithItems = { - items: ['foo', 'author:@root label:~foo bar'], - allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(), +describe('Recent Searches Dropdown Content', () => { + let wrapper; + + const findLocalStorageNote = () => wrapper.find({ ref: 'localStorageNote' }); + const findDropdownItems = () => wrapper.findAll({ ref: 'dropdownItem' }); + const findDropdownNote = () => wrapper.find({ ref: 'dropdownNote' }); + + const createComponent = props => { + wrapper = shallowMount(RecentSearchesDropdownContent, { + propsData: { + allowedKeys: IssuableFilteredSearchTokenKeys.getKeys(), + items: [], + isLocalStorageAvailable: false, + ...props, + }, + }); }; - let vm; afterEach(() => { - if (vm) { - vm.$destroy(); - } + wrapper.destroy(); + wrapper = null; }); - describe('with no items', () => { - let el; - + describe('when local storage is not available', () => { beforeEach(() => { - vm = createComponent(propsDataWithoutItems); - el = vm.$el; + createComponent(); }); - it('should render empty state', () => { - expect(el.querySelector('.dropdown-info-note')).toBeDefined(); - - const items = el.querySelectorAll('.filtered-search-history-dropdown-item'); - - expect(items.length).toEqual(propsDataWithoutItems.items.length); + it('renders a note about enabling local storage', () => { + expect(findLocalStorageNote().exists()).toBe(true); }); - }); - - describe('with items', () => { - let el; - beforeEach(() => { - vm = createComponent(propsDataWithItems); - el = vm.$el; + it('does not render dropdown items', () => { + expect(findDropdownItems().exists()).toBe(false); }); - it('should render clear recent searches button', () => { - expect(el.querySelector('.filtered-search-history-clear-button')).toBeDefined(); + it('does not render dropdownNote', () => { + 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); + describe('when localStorage is available and items array is not empty', () => { + let onRecentSearchesItemSelectedSpy; + let onRequestClearRecentSearchesSpy; - 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'); + beforeAll(() => { + onRecentSearchesItemSelectedSpy = jest.fn(); + onRequestClearRecentSearchesSpy = jest.fn(); + eventHub.$on('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy); + eventHub.$on('requestClearRecentSearches', onRequestClearRecentSearchesSpy); }); - }); - - describe('if isLocalStorageAvailable is `false`', () => { - let el; beforeEach(() => { - const props = Object.assign({ isLocalStorageAvailable: false }, propsDataWithItems); - - vm = createComponent(props); - el = vm.$el; + createComponent({ + items: ['foo', 'author:@root label:~foo bar'], + isLocalStorageAvailable: true, + }); }); - it('should render an info note', () => { - const note = el.querySelector('.dropdown-info-note'); - const items = el.querySelectorAll('.filtered-search-history-dropdown-item'); + afterAll(() => { + eventHub.$off('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy); + eventHub.$off('requestClearRecentSearchesSpy', onRequestClearRecentSearchesSpy); + }); - expect(note).toBeDefined(); - expect(note.innerText.trim()).toBe('This feature requires local storage to be enabled'); - expect(items.length).toEqual(propsDataWithoutItems.items.length); + it('does not render a note about enabling local storage', () => { + expect(findLocalStorageNote().exists()).toBe(false); }); - }); - describe('computed', () => { - describe('processedItems', () => { - it('with items', () => { - 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('does not render dropdownNote', () => { + expect(findDropdownNote().exists()).toBe(false); + }); - it('with no items', () => { - vm = createComponent(propsDataWithoutItems); - const { processedItems } = vm; + it('renders a correct amount of dropdown items', () => { + expect(findDropdownItems()).toHaveLength(2); + }); - expect(processedItems.length).toEqual(0); - }); + it('expect second dropdown to have 2 tokens', () => { + expect( + findDropdownItems() + .at(1) + .findAll('.js-dropdown-token'), + ).toHaveLength(2); }); - describe('hasItems', () => { - it('with items', () => { - vm = createComponent(propsDataWithItems); - const { hasItems } = vm; + it('emits recentSearchesItemSelected on dropdown item click', () => { + findDropdownItems() + .at(0) + .find('.js-dropdown-button') + .trigger('click'); - expect(hasItems).toEqual(true); - }); + expect(onRecentSearchesItemSelectedSpy).toHaveBeenCalledWith('foo'); + }); - it('with no items', () => { - vm = createComponent(propsDataWithoutItems); - const { hasItems } = vm; + it('emits requestClearRecentSearches on Clear resent searches button', () => { + wrapper.find({ ref: 'clearButton' }).trigger('click'); - expect(hasItems).toEqual(false); - }); + expect(onRequestClearRecentSearchesSpy).toHaveBeenCalled(); }); }); - describe('methods', () => { - describe('onItemActivated', () => { - let onRecentSearchesItemSelectedSpy; - - beforeEach(() => { - onRecentSearchesItemSelectedSpy = jest.fn(); - eventHub.$on('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy); - - vm = createComponent(propsDataWithItems); - }); - - afterEach(() => { - eventHub.$off('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy); - }); - - it('emits event', () => { - expect(onRecentSearchesItemSelectedSpy).not.toHaveBeenCalled(); - vm.onItemActivated('something'); - - expect(onRecentSearchesItemSelectedSpy).toHaveBeenCalledWith('something'); + describe('when locale storage is available and items array is empty', () => { + beforeEach(() => { + createComponent({ + isLocalStorageAvailable: true, }); }); - describe('onRequestClearRecentSearches', () => { - let onRequestClearRecentSearchesSpy; - - beforeEach(() => { - onRequestClearRecentSearchesSpy = jest.fn(); - eventHub.$on('requestClearRecentSearches', onRequestClearRecentSearchesSpy); - - vm = createComponent(propsDataWithItems); - }); - - afterEach(() => { - eventHub.$off('requestClearRecentSearches', onRequestClearRecentSearchesSpy); - }); + it('does not render a note about enabling local storage', () => { + expect(findLocalStorageNote().exists()).toBe(false); + }); - it('emits event', () => { - expect(onRequestClearRecentSearchesSpy).not.toHaveBeenCalled(); - vm.onRequestClearRecentSearches({ stopPropagation: () => {} }); + it('does not render dropdown items', () => { + expect(findDropdownItems().exists()).toBe(false); + }); - expect(onRequestClearRecentSearchesSpy).toHaveBeenCalled(); - }); + it('renders dropdown note', () => { + expect(findDropdownNote().exists()).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap new file mode 100644 index 00000000000..5347d1efc48 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap @@ -0,0 +1,16 @@ +// 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> +`; diff --git a/spec/frontend/vue_shared/components/__snapshots__/identicon_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/identicon_spec.js.snap new file mode 100644 index 00000000000..72370cb5b52 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/identicon_spec.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Identicon matches snapshot 1`] = ` +<div + class="avatar identicon s40 bg2" +> + + E + +</div> +`; diff --git a/spec/frontend/vue_shared/components/code_block_spec.js b/spec/frontend/vue_shared/components/code_block_spec.js index 6b91a20ff76..0d21dd94f7c 100644 --- a/spec/frontend/vue_shared/components/code_block_spec.js +++ b/spec/frontend/vue_shared/components/code_block_spec.js @@ -1,33 +1,25 @@ -import Vue from 'vue'; -import component from '~/vue_shared/components/code_block.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; +import CodeBlock from '~/vue_shared/components/code_block.vue'; describe('Code Block', () => { - const Component = Vue.extend(component); - let vm; + let wrapper; - afterEach(() => { - vm.$destroy(); - }); - - 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, + const createComponent = () => { + wrapper = shallowMount(CodeBlock, { + propsData: { + code: 'test-code', + }, }); + }; - expect(vm.$el.querySelector('code').textContent).toEqual(code); + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - it('escapes XSS injections', () => { - const code = 'CCC<img src=x onerror=alert(document.domain)>'; - - vm = mountComponent(Component, { - code, - }); + it('matches snapshot', () => { + createComponent(); - expect(vm.$el.querySelector('code').textContent).toEqual(code); + expect(wrapper.element).toMatchSnapshot(); }); }); diff --git a/spec/frontend/vue_shared/components/identicon_spec.js b/spec/frontend/vue_shared/components/identicon_spec.js index 0b3dbb61c96..5e8b013d480 100644 --- a/spec/frontend/vue_shared/components/identicon_spec.js +++ b/spec/frontend/vue_shared/components/identicon_spec.js @@ -1,65 +1,33 @@ -import Vue from 'vue'; -import identiconComponent from '~/vue_shared/components/identicon.vue'; - -const createComponent = sizeClass => { - const Component = Vue.extend(identiconComponent); - - return new Component({ - propsData: { - entityId: 1, - entityName: 'entity-name', - sizeClass, - }, - }).$mount(); -}; - -describe('IdenticonComponent', () => { - describe('computed', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('identiconBackgroundClass', () => { - it('should return bg class based on entityId', () => { - vm.entityId = 4; - - expect(vm.identiconBackgroundClass).toBeDefined(); - expect(vm.identiconBackgroundClass).toBe('bg5'); - }); +import { shallowMount } from '@vue/test-utils'; +import IdenticonComponent from '~/vue_shared/components/identicon.vue'; + +describe('Identicon', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(IdenticonComponent, { + propsData: { + entityId: 1, + entityName: 'entity-name', + sizeClass: 's40', + }, }); + }; - describe('identiconTitle', () => { - it('should return first letter of entity title in uppercase', () => { - vm.entityName = 'dummy-group'; - - expect(vm.identiconTitle).toBeDefined(); - expect(vm.identiconTitle).toBe('D'); - }); - }); + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - describe('template', () => { - it('should render identicon', () => { - const vm = createComponent(); + it('matches snapshot', () => { + createComponent(); - expect(vm.$el.nodeName).toBe('DIV'); - expect(vm.$el.classList.contains('identicon')).toBeTruthy(); - expect(vm.$el.classList.contains('s40')).toBeTruthy(); - expect(vm.$el.classList.contains('bg2')).toBeTruthy(); - vm.$destroy(); - }); + expect(wrapper.element).toMatchSnapshot(); + }); - it('should render identicon with provided sizing class', () => { - const vm = createComponent('s32'); + it('adds a correct class to identicon', () => { + createComponent(); - expect(vm.$el.classList.contains('s32')).toBeTruthy(); - vm.$destroy(); - }); + expect(wrapper.find({ ref: 'identicon' }).classes()).toContain('bg2'); }); }); diff --git a/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap b/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap new file mode 100644 index 00000000000..49b18d3e106 --- /dev/null +++ b/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap @@ -0,0 +1,62 @@ +// 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> +`; diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js index 7bb054b4e6c..891c70bcb5c 100644 --- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js +++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js @@ -1,65 +1,105 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import issueWarning from '~/vue_shared/components/issue/issue_warning.vue'; +import { shallowMount } from '@vue/test-utils'; +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) { - // Replace newlines with a space then replace multiple spaces with one space - return string - .trim() - .replace(/\n/g, ' ') - .replace(/\s\s+/g, ' '); -} + const findIcon = () => wrapper.find(Icon); + const findLockedBlock = () => wrapper.find({ ref: 'locked' }); + const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' }); + const findLockedAndConfidentialBlock = () => wrapper.find({ ref: 'lockedAndConfidential' }); -describe('Issue Warning Component', () => { - describe('isLocked', () => { - it('should render locked issue warning information', () => { - const props = { + const createComponent = props => { + wrapper = shallowMount(IssueWarning, { + propsData: { + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('when issue is locked but not confidential', () => { + beforeEach(() => { + createComponent({ isLocked: true, - lockedIssueDocsPath: 'docs/issues/locked', - }; - const vm = mountComponent(IssueWarning, props); - - expect( - vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), - ).toMatch(/lock$/); - expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This issue is locked. Only project members can comment. Learn more', - ); - expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath); + 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', () => { - it('should render confidential issue warning information', () => { - const props = { + describe('when issue is confidential but not locked', () => { + beforeEach(() => { + createComponent({ + isLocked: false, isConfidential: true, - confidentialIssueDocsPath: '/docs/issues/confidential', - }; - const vm = mountComponent(IssueWarning, props); - - expect( - vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), - ).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); + confidentialIssueDocsPath: 'confidential-path', + }); + }); + + 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); + }); + + it('does not render information about locked and confidential issue', () => { + expect(findLockedAndConfidentialBlock().exists()).toBe(false); }); }); - describe('isLocked and isConfidential', () => { - it('should render locked and confidential issue warning information', () => { - const vm = mountComponent(IssueWarning, { + describe('when issue is locked and confidential', () => { + beforeEach(() => { + createComponent({ isLocked: 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(); - expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - "This issue is confidential and locked. People without permission will never get a notification and won't be able to comment.", - ); + it('does not render information about confidential issue', () => { + expect(findConfidentialBlock().exists()).toBe(false); }); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap b/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap new file mode 100644 index 00000000000..29ac754de49 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap @@ -0,0 +1,28 @@ +// 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> +`; diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js index 3c5e7500ba7..162ac495385 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js @@ -1,9 +1,9 @@ -import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; 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 = { - canApply: true, suggestion: { id: 1, diff_lines: [ @@ -42,60 +42,45 @@ const MOCK_DATA = { helpPagePath: 'path_to_docs', }; -const lines = selectDiffLines(MOCK_DATA.suggestion.diff_lines); -const newLines = lines.filter(line => line.type === 'new'); - describe('Suggestion Diff component', () => { - let vm; - - beforeEach(done => { - const Component = Vue.extend(SuggestionDiffComponent); - - vm = new Component({ - propsData: MOCK_DATA, - }).$mount(); - - Vue.nextTick(done); - }); - - describe('init', () => { - it('renders a suggestion header', () => { - expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull(); - }); - - it('renders a diff table with syntax highlighting', () => { - expect(vm.$el.querySelector('.md-suggestion-diff.js-syntax-highlight.code')).not.toBeNull(); - }); + let wrapper; - it('renders the oldLineNumber', () => { - const fromLine = vm.$el.querySelector('.old_line').innerHTML; - - expect(parseInt(fromLine, 10)).toBe(lines[0].old_line); + const createComponent = () => { + wrapper = shallowMount(SuggestionDiffComponent, { + propsData: { + ...MOCK_DATA, + }, }); + }; - it('renders the oldLineContent', () => { - const fromContent = vm.$el.querySelector('.line_content.old').innerHTML; - - expect(fromContent.includes(lines[0].text)).toBe(true); - }); + beforeEach(() => { + createComponent(); + }); - it('renders new lines', () => { - const newLinesElements = vm.$el.querySelectorAll('.line_holder.new'); + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); - newLinesElements.forEach((line, i) => { - expect(newLinesElements[i].innerHTML.includes(newLines[i].new_line)).toBe(true); - expect(newLinesElements[i].innerHTML.includes(newLines[i].text)).toBe(true); - }); - }); + it('matches snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); }); - describe('applySuggestion', () => { - it('emits apply event when applySuggestion is called', () => { - const callback = () => {}; - jest.spyOn(vm, '$emit').mockImplementation(() => {}); - vm.applySuggestion(callback); + it('renders a correct amount of suggestion diff rows', () => { + expect(wrapper.findAll(SuggestionDiffRow)).toHaveLength(3); + }); - expect(vm.$emit).toHaveBeenCalledWith('apply', { suggestionId: vm.suggestion.id, callback }); - }); + it('emits apply event on sugestion diff header apply', () => { + wrapper.find(SuggestionDiffHeader).vm.$emit('apply', 'test-event'); + + expect(wrapper.emitted('apply')).toBeDefined(); + expect(wrapper.emitted('apply')).toEqual([ + [ + { + callback: 'test-event', + suggestionId: 1, + }, + ], + ]); }); }); diff --git a/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap b/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap new file mode 100644 index 00000000000..f3ce03796f9 --- /dev/null +++ b/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap @@ -0,0 +1,62 @@ +// 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> +`; diff --git a/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_system_note_spec.js.snap b/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_system_note_spec.js.snap new file mode 100644 index 00000000000..10c33269107 --- /dev/null +++ b/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_system_note_spec.js.snap @@ -0,0 +1,15 @@ +// 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> +`; diff --git a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js index 45f131194ca..0f30b50da0b 100644 --- a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js @@ -1,51 +1,55 @@ -import Vue from 'vue'; -import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; -import createStore from '~/notes/stores'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import IssuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; import { userDataMock } from '../../../notes/mock_data'; -describe('issue placeholder system note component', () => { - let store; - let vm; - - beforeEach(() => { - const Component = Vue.extend(issuePlaceholderNote); - store = createStore(); - store.dispatch('setUserData', userDataMock); - vm = new Component({ - store, - propsData: { note: { body: 'Foo' } }, - }).$mount(); - }); +const localVue = createLocalVue(); +localVue.use(Vuex); + +const getters = { + getUserData: () => userDataMock, +}; + +describe('Issue placeholder note component', () => { + let wrapper; + + const findNote = () => wrapper.find({ ref: 'note' }); + + const createComponent = (isIndividual = false) => { + wrapper = shallowMount(IssuePlaceholderNote, { + localVue, + store: new Vuex.Store({ + getters, + }), + propsData: { + note: { + body: 'Foo', + individual_note: isIndividual, + }, + }, + }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - describe('user information', () => { - it('should render user avatar with link', () => { - expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual( - userDataMock.path, - ); + it('matches snapshot', () => { + createComponent(); - expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual( - `${userDataMock.avatar_url}?width=40`, - ); - }); + expect(wrapper.element).toMatchSnapshot(); }); - describe('note content', () => { - it('should render note header information', () => { - expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual( - userDataMock.path, - ); + it('does not add "discussion" class to individual notes', () => { + createComponent(true); - expect( - vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(), - ).toEqual(`@${userDataMock.username}`); - }); + expect(findNote().classes()).not.toContain('discussion'); + }); - it('should render note body', () => { - expect(vm.$el.querySelector('.note-text p').textContent.trim()).toEqual('Foo'); - }); + it('adds "discussion" class to non-individual notes', () => { + createComponent(); + + expect(findNote().classes()).toContain('discussion'); }); }); diff --git a/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js index 81c5cd6a057..de6ab43bc41 100644 --- a/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js @@ -1,27 +1,25 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; +import { shallowMount } from '@vue/test-utils'; +import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; -describe('placeholder system note component', () => { - let PlaceholderSystemNote; - let vm; +describe('Placeholder system note component', () => { + let wrapper; - beforeEach(() => { - PlaceholderSystemNote = Vue.extend(placeholderSystemNote); - }); + const createComponent = () => { + wrapper = shallowMount(PlaceholderSystemNote, { + propsData: { + note: { body: 'This is a placeholder' }, + }, + }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - it('should render system note placeholder with plain text', () => { - vm = mountComponent(PlaceholderSystemNote, { - note: { body: 'This is a placeholder' }, - }); + it('matches snapshot', () => { + createComponent(); - expect(vm.$el.tagName).toEqual('LI'); - expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual( - 'This is a placeholder', - ); + expect(wrapper.element).toMatchSnapshot(); }); }); |