diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2019-04-05 16:25:45 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2019-04-05 16:25:45 +0000 |
commit | 941e00121c30baf0bf4e348d0d2b9b28891754d7 (patch) | |
tree | a23969242113e6656d530ca20c3558ba55938176 /spec/javascripts/vue_shared | |
parent | e36f835d4a648377068be73e6b82d8ffb9994425 (diff) | |
parent | eb95100c066d2d70a2128ea9ac6776f720b0777a (diff) | |
download | gitlab-ce-941e00121c30baf0bf4e348d0d2b9b28891754d7.tar.gz |
Merge branch 'ce-9262-move-project-search-bar-into-modal-dialog-on-operations-dashboard-page' into 'master'
CE backport: Add reusable project_selector component
See merge request gitlab-org/gitlab-ce!25036
Diffstat (limited to 'spec/javascripts/vue_shared')
-rw-r--r-- | spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js | 110 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js | 132 |
2 files changed, 242 insertions, 0 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 new file mode 100644 index 00000000000..b95183747bb --- /dev/null +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -0,0 +1,110 @@ +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'; + +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(() => { + options = { + propsData: { + project, + selected: false, + }, + sync: false, + localVue, + }; + }); + + afterEach(() => { + wrapper.vm.$destroy(); + }); + + it('does not render a check mark icon if selected === false', () => { + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-unselected')).toBe(true); + }); + + it('renders a check mark icon if selected === true', () => { + options.propsData.selected = true; + + 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'); + wrapper.vm.onClick(); + + expect(wrapper.vm.$emit).toHaveBeenCalledWith('click'); + }); + + it(`renders the project avatar`, () => { + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-project-avatar')).toBe(true); + }); + + it(`renders a simple namespace name with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b'; + + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); + + expect(renderedNamespace).toBe('a /'); + }); + + it(`renders a properly truncated namespace with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b / c / d / e / f'; + + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); + + expect(renderedNamespace).toBe('a / ... / e /'); + }); + + it(`renders the project name`, () => { + options.propsData.project.name = 'my-test-project'; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').text()); + + expect(renderedName).toBe('my-test-project'); + }); + + 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); + }); + + 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>"; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-xss-project'; + + 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 new file mode 100644 index 00000000000..ba9ec8f2f19 --- /dev/null +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -0,0 +1,132 @@ +import Vue from 'vue'; +import _ from 'underscore'; +import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue'; +import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; +import { shallowMount } from '@vue/test-utils'; +import { trimText } from 'spec/helpers/vue_component_helper'; + +describe('ProjectSelector component', () => { + let wrapper; + let vm; + loadJSONFixtures('projects.json'); + const allProjects = getJSONFixture('projects.json'); + const searchResults = allProjects.slice(0, 5); + let selected = []; + selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8)); + + beforeEach(() => { + jasmine.clock().install(); + + wrapper = shallowMount(Vue.extend(ProjectSelector), { + propsData: { + projectSearchResults: searchResults, + selectedProjects: selected, + showNoResultsMessage: false, + showMinimumSearchQueryMessage: false, + showLoadingIndicator: false, + showSearchErrorMessage: false, + }, + attachToDocument: true, + }); + + ({ vm } = wrapper); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + vm.$destroy(); + }); + + it('renders the search results', () => { + expect(wrapper.findAll('.js-project-list-item').length).toBe(5); + }); + + it(`triggers a (debounced) search when the search input value changes`, () => { + spyOn(vm, '$emit'); + const query = 'my test query!'; + const searchInput = wrapper.find('.js-project-selector-input'); + searchInput.setValue(query); + searchInput.trigger('input'); + + expect(vm.$emit).not.toHaveBeenCalledWith(); + jasmine.clock().tick(501); + + expect(vm.$emit).toHaveBeenCalledWith('searched', query); + }); + + it(`debounces the search input`, () => { + spyOn(vm, '$emit'); + const searchInput = wrapper.find('.js-project-selector-input'); + + const updateSearchQuery = (count = 0) => { + if (count === 10) { + jasmine.clock().tick(101); + + expect(vm.$emit).toHaveBeenCalledTimes(1); + expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`); + } else { + searchInput.setValue(`search query #${count}`); + searchInput.trigger('input'); + + jasmine.clock().tick(400); + updateSearchQuery(count + 1); + } + }; + + updateSearchQuery(); + }); + + it(`includes a placeholder in the search box`, () => { + expect(wrapper.find('.js-project-selector-input').attributes('placeholder')).toBe( + 'Search your projects', + ); + }); + + it(`triggers a "projectClicked" event when a project is clicked`, () => { + spyOn(vm, '$emit'); + wrapper.find(ProjectListItem).vm.$emit('click', _.first(searchResults)); + + expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults)); + }); + + it(`shows a "no results" message if showNoResultsMessage === true`, () => { + wrapper.setProps({ showNoResultsMessage: true }); + + expect(wrapper.contains('.js-no-results-message')).toBe(true); + + const noResultsEl = wrapper.find('.js-no-results-message'); + + expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search'); + }); + + it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, () => { + wrapper.setProps({ showMinimumSearchQueryMessage: true }); + + expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true); + + const minimumSearchEl = wrapper.find('.js-minimum-search-query-message'); + + expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search'); + }); + + it(`shows a error message if showSearchErrorMessage === true`, () => { + wrapper.setProps({ showSearchErrorMessage: true }); + + expect(wrapper.contains('.js-search-error-message')).toBe(true); + + const errorMessageEl = wrapper.find('.js-search-error-message'); + + 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 = wrapper.find('.js-project-selector-input'); + + expect(document.activeElement).not.toBe(input.element); + vm.focusSearchInput(); + + expect(document.activeElement).toBe(input.element); + }); +}); |