diff options
author | mfluharty <mfluharty@gitlab.com> | 2019-03-28 23:00:44 +0300 |
---|---|---|
committer | mfluharty <mfluharty@gitlab.com> | 2019-04-03 08:58:20 +0300 |
commit | eb95100c066d2d70a2128ea9ac6776f720b0777a (patch) | |
tree | 9d37033870f8062f5fc63464d811e3c6ae420306 /spec/javascripts/vue_shared | |
parent | 06b88af04657be961a4da97a586706fb99eb6a27 (diff) |
Make corrections to address review feedback
Refactor tests to follow conventions
Add XSS test
Eliminate a few unnecessary lines, comments, and parameters
Use Vue.set for nested state changes
Diffstat (limited to 'spec/javascripts/vue_shared')
-rw-r--r-- | spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js | 104 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js | 84 |
2 files changed, 87 insertions, 101 deletions
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js index 8dbdfe97f8f..b95183747bb 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import { trimText } from 'spec/helpers/vue_component_helper'; @@ -6,99 +5,106 @@ import { trimText } from 'spec/helpers/vue_component_helper'; const localVue = createLocalVue(); describe('ProjectListItem component', () => { + const Component = localVue.extend(ProjectListItem); let wrapper; let vm; + let options; loadJSONFixtures('projects.json'); const project = getJSONFixture('projects.json')[0]; beforeEach(() => { - wrapper = shallowMount(localVue.extend(ProjectListItem), { + options = { propsData: { project, selected: false, }, sync: false, localVue, - }); - - ({ vm } = wrapper); + }; }); afterEach(() => { - vm.$destroy(); + wrapper.vm.$destroy(); }); it('does not render a check mark icon if selected === false', () => { - expect(vm.$el.querySelector('.js-selected-icon.js-unselected')).toBeTruthy(); + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-unselected')).toBe(true); }); - it('renders a check mark icon if selected === true', done => { - wrapper.setProps({ selected: true }); + it('renders a check mark icon if selected === true', () => { + options.propsData.selected = true; - vm.$nextTick(() => { - expect(vm.$el.querySelector('.js-selected-icon.js-selected')).toBeTruthy(); - done(); - }); + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-selected')).toBe(true); }); it(`emits a "clicked" event when clicked`, () => { + wrapper = shallowMount(Component, options); + ({ vm } = wrapper); + spyOn(vm, '$emit'); - vm.onClick(); + wrapper.vm.onClick(); - expect(vm.$emit).toHaveBeenCalledWith('click'); + expect(wrapper.vm.$emit).toHaveBeenCalledWith('click'); }); it(`renders the project avatar`, () => { - expect(vm.$el.querySelector('.js-project-avatar')).toBeTruthy(); + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-project-avatar')).toBe(true); }); - it(`renders a simple namespace name with a trailing slash`, done => { - project.name_with_namespace = 'a / b'; - wrapper.setProps({ project: _.clone(project) }); + it(`renders a simple namespace name with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b'; - vm.$nextTick(() => { - const renderedNamespace = trimText(vm.$el.querySelector('.js-project-namespace').textContent); + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); - expect(renderedNamespace).toBe('a /'); - done(); - }); + expect(renderedNamespace).toBe('a /'); }); - it(`renders a properly truncated namespace with a trailing slash`, done => { - project.name_with_namespace = 'a / b / c / d / e / f'; - wrapper.setProps({ project: _.clone(project) }); + it(`renders a properly truncated namespace with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b / c / d / e / f'; - vm.$nextTick(() => { - const renderedNamespace = trimText(vm.$el.querySelector('.js-project-namespace').textContent); + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); - expect(renderedNamespace).toBe('a / ... / e /'); - done(); - }); + expect(renderedNamespace).toBe('a / ... / e /'); }); - it(`renders the project name`, done => { - project.name = 'my-test-project'; - wrapper.setProps({ project: _.clone(project) }); + it(`renders the project name`, () => { + options.propsData.project.name = 'my-test-project'; - vm.$nextTick(() => { - const renderedName = trimText(vm.$el.querySelector('.js-project-name').innerHTML); + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').text()); - expect(renderedName).toBe('my-test-project'); - done(); - }); + expect(renderedName).toBe('my-test-project'); }); - it(`renders the project name with highlighting in the case of a search query match`, done => { - project.name = 'my-test-project'; - wrapper.setProps({ project: _.clone(project), matcher: 'pro' }); + it(`renders the project name with highlighting in the case of a search query match`, () => { + options.propsData.project.name = 'my-test-project'; + options.propsData.matcher = 'pro'; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-test-<b>p</b><b>r</b><b>o</b>ject'; + + expect(renderedName).toContain(expected); + }); - vm.$nextTick(() => { - const renderedName = trimText(vm.$el.querySelector('.js-project-name').innerHTML); + it('prevents search query and project name XSS', () => { + const alertSpy = spyOn(window, 'alert'); + options.propsData.project.name = "my-xss-pro<script>alert('XSS');</script>ject"; + options.propsData.matcher = "pro<script>alert('XSS');</script>"; - const expected = 'my-test-<b>p</b><b>r</b><b>o</b>ject'; + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-xss-project'; - expect(renderedName).toBe(expected); - done(); - }); + expect(renderedName).toContain(expected); + expect(alertSpy).not.toHaveBeenCalled(); }); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index 88c1dff76a1..ba9ec8f2f19 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -38,28 +38,25 @@ describe('ProjectSelector component', () => { }); it('renders the search results', () => { - expect(vm.$el.querySelectorAll('.js-project-list-item').length).toBe(5); + expect(wrapper.findAll('.js-project-list-item').length).toBe(5); }); - it(`triggers a (debounced) search when the search input value changes`, done => { + it(`triggers a (debounced) search when the search input value changes`, () => { spyOn(vm, '$emit'); const query = 'my test query!'; - const searchInput = vm.$el.querySelector('.js-project-selector-input'); - searchInput.value = query; - searchInput.dispatchEvent(new Event('input')); + const searchInput = wrapper.find('.js-project-selector-input'); + searchInput.setValue(query); + searchInput.trigger('input'); - vm.$nextTick(() => { - expect(vm.$emit).not.toHaveBeenCalledWith(); - jasmine.clock().tick(501); + expect(vm.$emit).not.toHaveBeenCalledWith(); + jasmine.clock().tick(501); - expect(vm.$emit).toHaveBeenCalledWith('searched', query); - done(); - }); + expect(vm.$emit).toHaveBeenCalledWith('searched', query); }); - it(`debounces the search input`, done => { + it(`debounces the search input`, () => { spyOn(vm, '$emit'); - const searchInput = vm.$el.querySelector('.js-project-selector-input'); + const searchInput = wrapper.find('.js-project-selector-input'); const updateSearchQuery = (count = 0) => { if (count === 10) { @@ -67,15 +64,12 @@ describe('ProjectSelector component', () => { expect(vm.$emit).toHaveBeenCalledTimes(1); expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`); - done(); } else { - searchInput.value = `search query #${count}`; - searchInput.dispatchEvent(new Event('input')); + searchInput.setValue(`search query #${count}`); + searchInput.trigger('input'); - vm.$nextTick(() => { - jasmine.clock().tick(400); - updateSearchQuery(count + 1); - }); + jasmine.clock().tick(400); + updateSearchQuery(count + 1); } }; @@ -83,7 +77,7 @@ describe('ProjectSelector component', () => { }); it(`includes a placeholder in the search box`, () => { - expect(vm.$el.querySelector('.js-project-selector-input').placeholder).toBe( + expect(wrapper.find('.js-project-selector-input').attributes('placeholder')).toBe( 'Search your projects', ); }); @@ -95,58 +89,44 @@ describe('ProjectSelector component', () => { expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults)); }); - it(`shows a "no results" message if showNoResultsMessage === true`, done => { + it(`shows a "no results" message if showNoResultsMessage === true`, () => { wrapper.setProps({ showNoResultsMessage: true }); - vm.$nextTick(() => { - const noResultsEl = vm.$el.querySelector('.js-no-results-message'); - - expect(noResultsEl).toBeTruthy(); + expect(wrapper.contains('.js-no-results-message')).toBe(true); - expect(trimText(noResultsEl.textContent)).toEqual('Sorry, no projects matched your search'); + const noResultsEl = wrapper.find('.js-no-results-message'); - done(); - }); + expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search'); }); - it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, done => { + it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, () => { wrapper.setProps({ showMinimumSearchQueryMessage: true }); - vm.$nextTick(() => { - const minimumSearchEl = vm.$el.querySelector('.js-minimum-search-query-message'); - - expect(minimumSearchEl).toBeTruthy(); + expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true); - expect(trimText(minimumSearchEl.textContent)).toEqual( - 'Enter at least three characters to search', - ); + const minimumSearchEl = wrapper.find('.js-minimum-search-query-message'); - done(); - }); + expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search'); }); - it(`shows a error message if showSearchErrorMessage === true`, done => { + it(`shows a error message if showSearchErrorMessage === true`, () => { wrapper.setProps({ showSearchErrorMessage: true }); - vm.$nextTick(() => { - const errorMessageEl = vm.$el.querySelector('.js-search-error-message'); - - expect(errorMessageEl).toBeTruthy(); + expect(wrapper.contains('.js-search-error-message')).toBe(true); - expect(trimText(errorMessageEl.textContent)).toEqual( - 'Something went wrong, unable to search projects', - ); + const errorMessageEl = wrapper.find('.js-search-error-message'); - done(); - }); + expect(trimText(errorMessageEl.text())).toEqual( + 'Something went wrong, unable to search projects', + ); }); it(`focuses the input element when the focusSearchInput() method is called`, () => { - const input = vm.$el.querySelector('.js-project-selector-input'); + const input = wrapper.find('.js-project-selector-input'); - expect(document.activeElement).not.toBe(input); + expect(document.activeElement).not.toBe(input.element); vm.focusSearchInput(); - expect(document.activeElement).toBe(input); + expect(document.activeElement).toBe(input.element); }); }); |