From b79c292ef4f76b307131566c24d849c2e35c8089 Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Thu, 19 Dec 2019 17:13:14 +0100 Subject: [PATCH] Fix failing component tests - Extract testing logic to SearchableInput spec - write new tests for SearchResources --- .../SearchResources/SearchResources.spec.js | 158 +++++------------- .../SearchResources/SearchResources.story.js | 2 +- .../SearchableInput/SearchableInput.spec.js | 128 ++++++++++++++ 3 files changed, 174 insertions(+), 114 deletions(-) create mode 100644 webapp/components/generic/SearchableInput/SearchableInput.spec.js diff --git a/webapp/components/features/SearchResources/SearchResources.spec.js b/webapp/components/features/SearchResources/SearchResources.spec.js index cc4d230c2..27a75dbfc 100644 --- a/webapp/components/features/SearchResources/SearchResources.spec.js +++ b/webapp/components/features/SearchResources/SearchResources.spec.js @@ -1,139 +1,71 @@ -import { mount } from '@vue/test-utils' +import { config, mount } from '@vue/test-utils' +import Vuex from 'vuex' import SearchResources from './SearchResources.vue' - +import SearchableInput from '~/components/generic/SearchableInput/SearchableInput' +import { results as searchResults } from './SearchResources.story' const localVue = global.localVue localVue.filter('truncate', () => 'truncated string') localVue.filter('dateTime', () => Date.now) +config.stubs['nuxt-link'] = '' describe('SearchResources.vue', () => { - let mocks - let propsData - + let mocks, wrapper, getters beforeEach(() => { - propsData = {} + mocks = { + $apollo: { + query: jest.fn(), + }, + $t: jest.fn(string => string), + } + getters = { 'auth/isModerator': () => false } + wrapper = Wrapper() }) + const Wrapper = () => { + const store = new Vuex.Store({ + getters, + }) + return mount(SearchResources, { mocks, localVue, store }) + } + describe('mount', () => { - const Wrapper = () => { - mocks = { - $t: () => {}, - } - return mount(SearchResources, { mocks, localVue, propsData }) - } - - it('renders', () => { - expect(Wrapper().is('div')).toBe(true) - }) - - it('has id "nav-search"', () => { - expect(Wrapper().contains('[data-test="search-resources"]')).toBe(true) - }) - - it('defaults to an empty value', () => { - expect(Wrapper().vm.value).toBe('') - }) - - it('defaults to id "nav-search"', () => { - expect(Wrapper().vm.id).toBe('nav-search') - }) - - it('default to a 300 millisecond delay from the time the user stops typing to when the search starts', () => { - expect(Wrapper().vm.delay).toEqual(300) - }) - - it('defaults to an empty array as results', () => { - expect(Wrapper().vm.results).toEqual([]) + it('defaults to an empty array as searchResults', () => { + expect(wrapper.vm.searchResults).toEqual([]) }) it('defaults to pending false, as in the search is not pending', () => { - expect(Wrapper().vm.pending).toBe(false) + expect(wrapper.vm.pending).toBe(false) }) - it('accepts values as a string', () => { - propsData = { value: 'abc' } - const wrapper = Wrapper() - expect(wrapper.vm.value).toEqual('abc') - }) - - describe('testing custom functions', () => { - let select - let wrapper - + describe('Emitted events', () => { + let searchableInputComponent beforeEach(() => { - wrapper = Wrapper() - select = wrapper.find('.ds-select') - select.trigger('focus') - select.element.value = 'abcd' + searchableInputComponent = wrapper.find(SearchableInput) }) - it('opens the select dropdown when focused on', () => { - expect(wrapper.vm.isOpen).toBe(true) + describe('query event', () => { + it('calls an apollo query', () => { + searchableInputComponent.vm.$emit('query', 'abcd') + expect(mocks.$apollo.query).toHaveBeenCalledWith( + expect.objectContaining({ variables: { query: 'abcd' } }), + ) + }) }) - it('opens the select dropdown and blurs after focused on', () => { - select.trigger('blur') - expect(wrapper.vm.isOpen).toBe(false) - }) + describe('clearSearch event', () => { + beforeEach(() => { + wrapper.setData({ searchResults, pending: true }) + searchableInputComponent.vm.$emit('clearSearch') + }) - it('is clearable', () => { - select.trigger('input') - select.trigger('keyup.esc') - expect(wrapper.emitted().clear.length).toBe(1) - }) + it('clears searchResults', () => { + expect(wrapper.vm.searchResults).toEqual([]) + }) - it('changes the unprocessedSearchResources as the value changes', () => { - select.trigger('input') - expect(wrapper.vm.unprocessedSearchResources).toBe('abcd') - }) - - it('searches for the term when enter is pressed', async () => { - select.trigger('input') - select.trigger('keyup.enter') - await expect(wrapper.emitted().search[0]).toEqual(['abcd']) - }) - - it('calls onDelete when the delete key is pressed', () => { - const spy = jest.spyOn(wrapper.vm, 'onDelete') - select.trigger('input') - select.trigger('keyup.delete') - expect(spy).toHaveBeenCalledTimes(1) - }) - - it('calls query when a user starts a search by pressing enter', () => { - const spy = jest.spyOn(wrapper.vm, 'query') - select.trigger('input') - select.trigger('keyup.enter') - expect(spy).toHaveBeenCalledWith('abcd') - }) - - it('calls onSelect when a user selects an item in the search dropdown menu', async () => { - // searched for term in the browser, copied the results from Vuex in Vue dev tools - propsData = { - results: [ - { - __typename: 'Post', - author: { - __typename: 'User', - id: 'u5', - name: 'Trick', - slug: 'trick', - }, - commentsCount: 0, - createdAt: '2019-03-13T11:00:20.835Z', - id: 'p10', - label: 'Eos aut illo omnis quis eaque et iure aut.', - shoutedCount: 0, - slug: 'eos-aut-illo-omnis-quis-eaque-et-iure-aut', - value: 'Eos aut illo omnis quis eaque et iure aut.', - }, - ], - } - wrapper = Wrapper() - select.trigger('input') - const results = wrapper.find('.ds-select-option') - results.trigger('click') - await expect(wrapper.emitted().select[0]).toEqual(propsData.results) + it('set pending to false', () => { + expect(wrapper.vm.pending).toBe(false) + }) }) }) }) diff --git a/webapp/components/features/SearchResources/SearchResources.story.js b/webapp/components/features/SearchResources/SearchResources.story.js index 328259917..c2cd8f4fa 100644 --- a/webapp/components/features/SearchResources/SearchResources.story.js +++ b/webapp/components/features/SearchResources/SearchResources.story.js @@ -7,7 +7,7 @@ helpers.init() export const results = [ { - id: 'de100841-2336-4b01-a574-f1bd2c0b262a', + id: 'post-by-jenny', __typename: 'Post', slug: 'user-post-by-jenny', title: 'User Post by Jenny', diff --git a/webapp/components/generic/SearchableInput/SearchableInput.spec.js b/webapp/components/generic/SearchableInput/SearchableInput.spec.js new file mode 100644 index 000000000..98528a466 --- /dev/null +++ b/webapp/components/generic/SearchableInput/SearchableInput.spec.js @@ -0,0 +1,128 @@ +import { config, mount } from '@vue/test-utils' +import Vuex from 'vuex' +import Vue from 'vue' +import SearchableInput from './SearchableInput' +import { results } from '~/components/features/SearchResources/SearchResources.story' + +const localVue = global.localVue + +localVue.filter('truncate', () => 'truncated string') +localVue.filter('dateTime', () => Date.now) +config.stubs['nuxt-link'] = '' + +describe('SearchableInput.vue', () => { + let mocks, propsData, getters + + beforeEach(() => { + propsData = {} + mocks = { + $router: { + push: jest.fn(), + }, + $t: jest.fn(string => string), + } + getters = { 'auth/isModerator': () => false } + }) + + describe('mount', () => { + const Wrapper = () => { + const store = new Vuex.Store({ + getters, + }) + return mount(SearchableInput, { mocks, localVue, propsData, store }) + } + + it('defaults to an empty value', () => { + expect(Wrapper().vm.value).toBe('') + }) + + it('default to a 300 millisecond delay from the time the user stops typing to when the search starts', () => { + expect(Wrapper().vm.delay).toEqual(300) + }) + + it('defaults to an empty array as options', () => { + expect(Wrapper().vm.options).toEqual([]) + }) + + describe('testing custom functions', () => { + let select, wrapper + + beforeEach(() => { + wrapper = Wrapper() + select = wrapper.find('.ds-select') + select.trigger('focus') + select.element.value = 'abcd' + }) + + it('opens the select dropdown when focused on', () => { + expect(wrapper.vm.isOpen).toBe(true) + }) + + it('opens the select dropdown and blurs after focused on', () => { + select.trigger('blur') + expect(wrapper.vm.isOpen).toBe(false) + }) + + it('is clearable', () => { + select.trigger('input') + select.trigger('keyup.esc') + expect(wrapper.emitted().clearSearch.length).toBe(1) + }) + + it('changes the unprocessedSearchInput as the value changes', () => { + select.trigger('input') + expect(wrapper.vm.unprocessedSearchInput).toBe('abcd') + }) + + it('searches for the term when enter is pressed', async () => { + select.element.value = 'ab' + select.trigger('input') + select.trigger('keyup.enter') + await expect(wrapper.emitted().query[0]).toEqual(['ab']) + }) + + it('calls onDelete when the delete key is pressed', () => { + const spy = jest.spyOn(wrapper.vm, 'onDelete') + select.trigger('input') + select.trigger('keyup.delete') + expect(spy).toHaveBeenCalledTimes(1) + }) + + describe('navigating to resource', () => { + beforeEach(() => { + propsData = { options: results } + wrapper = Wrapper() + select = wrapper.find('.ds-select') + select.trigger('focus') + }) + + it('pushes to post page', async () => { + select.element.value = 'Post' + select.trigger('input') + const post = wrapper.find('.search-post') + post.trigger('click') + await Vue.nextTick().then(() => { + expect(mocks.$router.push).toHaveBeenCalledWith({ + name: 'post-id-slug', + params: { id: 'post-by-jenny', slug: 'user-post-by-jenny' }, + }) + }) + }) + + it("pushes to user's profile", async () => { + select.element.value = 'Bob' + select.trigger('input') + const users = wrapper.findAll('.userinfo') + const bob = users.filter(item => item.text() === '@bob-der-baumeister') + bob.trigger('click') + await Vue.nextTick().then(() => { + expect(mocks.$router.push).toHaveBeenCalledWith({ + name: 'profile-id-slug', + params: { id: 'u2', slug: 'bob-der-baumeister' }, + }) + }) + }) + }) + }) + }) +})