diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/frequent_items/components | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/frequent_items/components')
4 files changed, 208 insertions, 199 deletions
diff --git a/spec/frontend/frequent_items/components/app_spec.js b/spec/frontend/frequent_items/components/app_spec.js index 80059c4c87f..7a1026e8bfc 100644 --- a/spec/frontend/frequent_items/components/app_spec.js +++ b/spec/frontend/frequent_items/components/app_spec.js @@ -1,10 +1,11 @@ import MockAdapter from 'axios-mock-adapter'; import Vue from 'vue'; -import { useRealDate } from 'helpers/fake_date'; +import Vuex from 'vuex'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; -import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import appComponent from '~/frequent_items/components/app.vue'; +import App from '~/frequent_items/components/app.vue'; +import FrequentItemsList from '~/frequent_items/components/frequent_items_list.vue'; import { FREQUENT_ITEMS, HOUR_IN_MS } from '~/frequent_items/constants'; import eventHub from '~/frequent_items/event_hub'; import { createStore } from '~/frequent_items/store'; @@ -12,246 +13,230 @@ import { getTopFrequentItems } from '~/frequent_items/utils'; import axios from '~/lib/utils/axios_utils'; import { currentSession, mockFrequentProjects, mockSearchedProjects } from '../mock_data'; +Vue.use(Vuex); + useLocalStorageSpy(); -let session; -const createComponentWithStore = (namespace = 'projects') => { - session = currentSession[namespace]; - gon.api_version = session.apiVersion; - const Component = Vue.extend(appComponent); - const store = createStore(); - - return mountComponentWithStore(Component, { - store, - props: { - namespace, - currentUserName: session.username, - currentItem: session.project || session.group, - }, - }); -}; +const TEST_NAMESPACE = 'projects'; +const TEST_VUEX_MODULE = 'frequentProjects'; +const TEST_PROJECT = currentSession[TEST_NAMESPACE].project; +const TEST_STORAGE_KEY = currentSession[TEST_NAMESPACE].storageKey; describe('Frequent Items App Component', () => { - let vm; + let wrapper; let mock; + let store; + + const createComponent = ({ currentItem = null } = {}) => { + const session = currentSession[TEST_NAMESPACE]; + gon.api_version = session.apiVersion; + + wrapper = mountExtended(App, { + store, + propsData: { + namespace: TEST_NAMESPACE, + currentUserName: session.username, + currentItem: currentItem || session.project, + }, + provide: { + vuexModule: TEST_VUEX_MODULE, + }, + }); + }; + + const triggerDropdownOpen = () => eventHub.$emit(`${TEST_NAMESPACE}-dropdownOpen`); + const getStoredProjects = () => JSON.parse(localStorage.getItem(TEST_STORAGE_KEY)); + const findSearchInput = () => wrapper.findByTestId('frequent-items-search-input'); + const findLoading = () => wrapper.findByTestId('loading'); + const findSectionHeader = () => wrapper.findByTestId('header'); + const findFrequentItemsList = () => wrapper.findComponent(FrequentItemsList); + const findFrequentItems = () => findFrequentItemsList().findAll('li'); + const setSearch = (search) => { + const searchInput = wrapper.find('input'); + + searchInput.setValue(search); + }; beforeEach(() => { mock = new MockAdapter(axios); - vm = createComponentWithStore(); + store = createStore(); }); afterEach(() => { mock.restore(); - vm.$destroy(); + wrapper.destroy(); }); - describe('methods', () => { - describe('dropdownOpenHandler', () => { - it('should fetch frequent items when no search has been previously made on desktop', () => { - jest.spyOn(vm, 'fetchFrequentItems').mockImplementation(() => {}); - - vm.dropdownOpenHandler(); + describe('default', () => { + beforeEach(() => { + jest.spyOn(store, 'dispatch'); - expect(vm.fetchFrequentItems).toHaveBeenCalledWith(); - }); + createComponent(); }); - describe('logItemAccess', () => { - let storage; - - beforeEach(() => { - storage = {}; - - localStorage.setItem.mockImplementation((storageKey, value) => { - storage[storageKey] = value; - }); - - localStorage.getItem.mockImplementation((storageKey) => { - if (storage[storageKey]) { - return storage[storageKey]; - } - - return null; - }); - }); + it('should fetch frequent items', () => { + triggerDropdownOpen(); - it('should create a project store if it does not exist and adds a project', () => { - vm.logItemAccess(session.storageKey, session.project); - - const projects = JSON.parse(storage[session.storageKey]); - - expect(projects.length).toBe(1); - expect(projects[0].frequency).toBe(1); - expect(projects[0].lastAccessedOn).toBeDefined(); - }); - - it('should prevent inserting same report multiple times into store', () => { - vm.logItemAccess(session.storageKey, session.project); - vm.logItemAccess(session.storageKey, session.project); - - const projects = JSON.parse(storage[session.storageKey]); - - expect(projects.length).toBe(1); - }); - - describe('with real date', () => { - useRealDate(); - - it('should increase frequency of report if it was logged multiple times over the course of an hour', () => { - let projects; - const newTimestamp = Date.now() + HOUR_IN_MS + 1; + expect(store.dispatch).toHaveBeenCalledWith(`${TEST_VUEX_MODULE}/fetchFrequentItems`); + }); - vm.logItemAccess(session.storageKey, session.project); - projects = JSON.parse(storage[session.storageKey]); + it('should not fetch frequent items if detroyed', () => { + wrapper.destroy(); + triggerDropdownOpen(); - expect(projects[0].frequency).toBe(1); + expect(store.dispatch).not.toHaveBeenCalledWith(`${TEST_VUEX_MODULE}/fetchFrequentItems`); + }); - vm.logItemAccess(session.storageKey, { - ...session.project, - lastAccessedOn: newTimestamp, - }); - projects = JSON.parse(storage[session.storageKey]); + it('should render search input', () => { + expect(findSearchInput().exists()).toBe(true); + }); - expect(projects[0].frequency).toBe(2); - expect(projects[0].lastAccessedOn).not.toBe(session.project.lastAccessedOn); - }); - }); + it('should render loading animation', async () => { + triggerDropdownOpen(); + store.state[TEST_VUEX_MODULE].isLoadingItems = true; - it('should always update project metadata', () => { - let projects; - const oldProject = { - ...session.project, - }; + await wrapper.vm.$nextTick(); - const newProject = { - ...session.project, - name: 'New Name', - avatarUrl: 'new/avatar.png', - namespace: 'New / Namespace', - webUrl: 'http://localhost/new/web/url', - }; + const loading = findLoading(); - vm.logItemAccess(session.storageKey, oldProject); - projects = JSON.parse(storage[session.storageKey]); + expect(loading.exists()).toBe(true); + expect(loading.find('[aria-label="Loading projects"]').exists()).toBe(true); + }); - expect(projects[0].name).toBe(oldProject.name); - expect(projects[0].avatarUrl).toBe(oldProject.avatarUrl); - expect(projects[0].namespace).toBe(oldProject.namespace); - expect(projects[0].webUrl).toBe(oldProject.webUrl); + it('should render frequent projects list header', () => { + const sectionHeader = findSectionHeader(); - vm.logItemAccess(session.storageKey, newProject); - projects = JSON.parse(storage[session.storageKey]); + expect(sectionHeader.exists()).toBe(true); + expect(sectionHeader.text()).toBe('Frequently visited'); + }); - expect(projects[0].name).toBe(newProject.name); - expect(projects[0].avatarUrl).toBe(newProject.avatarUrl); - expect(projects[0].namespace).toBe(newProject.namespace); - expect(projects[0].webUrl).toBe(newProject.webUrl); - }); + it('should render frequent projects list', async () => { + const expectedResult = getTopFrequentItems(mockFrequentProjects); + localStorage.setItem(TEST_STORAGE_KEY, JSON.stringify(mockFrequentProjects)); - it('should not add more than 20 projects in store', () => { - for (let id = 0; id < FREQUENT_ITEMS.MAX_COUNT; id += 1) { - const project = { - ...session.project, - id, - }; - vm.logItemAccess(session.storageKey, project); - } + expect(findFrequentItems().length).toBe(1); - const projects = JSON.parse(storage[session.storageKey]); + triggerDropdownOpen(); + await wrapper.vm.$nextTick(); - expect(projects.length).toBe(FREQUENT_ITEMS.MAX_COUNT); + expect(findFrequentItems().length).toBe(expectedResult.length); + expect(findFrequentItemsList().props()).toEqual({ + items: expectedResult, + namespace: TEST_NAMESPACE, + hasSearchQuery: false, + isFetchFailed: false, + matcher: '', }); }); - }); - - describe('created', () => { - it('should bind event listeners on eventHub', (done) => { - jest.spyOn(eventHub, '$on').mockImplementation(() => {}); - createComponentWithStore().$mount(); - - Vue.nextTick(() => { - expect(eventHub.$on).toHaveBeenCalledWith('projects-dropdownOpen', expect.any(Function)); - done(); - }); + it('should render searched projects list', async () => { + mock.onGet(/\/api\/v4\/projects.json(.*)$/).replyOnce(200, mockSearchedProjects.data); + + setSearch('gitlab'); + await wrapper.vm.$nextTick(); + + expect(findLoading().exists()).toBe(true); + + await waitForPromises(); + + expect(findFrequentItems().length).toBe(mockSearchedProjects.data.length); + expect(findFrequentItemsList().props()).toEqual( + expect.objectContaining({ + items: mockSearchedProjects.data.map( + ({ avatar_url, web_url, name_with_namespace, ...item }) => ({ + ...item, + avatarUrl: avatar_url, + webUrl: web_url, + namespace: name_with_namespace, + }), + ), + namespace: TEST_NAMESPACE, + hasSearchQuery: true, + isFetchFailed: false, + matcher: 'gitlab', + }), + ); }); }); - describe('beforeDestroy', () => { - it('should unbind event listeners on eventHub', (done) => { - jest.spyOn(eventHub, '$off').mockImplementation(() => {}); + describe('logging', () => { + it('when created, it should create a project storage entry and adds a project', () => { + createComponent(); - vm.$mount(); - vm.$destroy(); + expect(getStoredProjects()).toEqual([ + expect.objectContaining({ + frequency: 1, + lastAccessedOn: Date.now(), + }), + ]); + }); - Vue.nextTick(() => { - expect(eventHub.$off).toHaveBeenCalledWith('projects-dropdownOpen', expect.any(Function)); - done(); + describe('when created multiple times', () => { + beforeEach(() => { + createComponent(); + wrapper.destroy(); + createComponent(); + wrapper.destroy(); }); - }); - }); - describe('template', () => { - it('should render search input', () => { - expect(vm.$el.querySelector('.search-input-container')).toBeDefined(); - }); + it('should only log once', () => { + expect(getStoredProjects()).toEqual([ + expect.objectContaining({ + lastAccessedOn: Date.now(), + frequency: 1, + }), + ]); + }); - it('should render loading animation', (done) => { - vm.$store.dispatch('fetchSearchedItems'); + it('should increase frequency, when created an hour later', () => { + const hourLater = Date.now() + HOUR_IN_MS + 1; - Vue.nextTick(() => { - const loadingEl = vm.$el.querySelector('.loading-animation'); + jest.spyOn(Date, 'now').mockReturnValue(hourLater); + createComponent({ currentItem: { ...TEST_PROJECT, lastAccessedOn: hourLater } }); - expect(loadingEl).toBeDefined(); - expect(loadingEl.classList.contains('prepend-top-20')).toBe(true); - expect(loadingEl.querySelector('span').getAttribute('aria-label')).toBe('Loading projects'); - done(); + expect(getStoredProjects()).toEqual([ + expect.objectContaining({ + lastAccessedOn: hourLater, + frequency: 2, + }), + ]); }); }); - it('should render frequent projects list header', (done) => { - Vue.nextTick(() => { - const sectionHeaderEl = vm.$el.querySelector('.section-header'); + it('should always update project metadata', () => { + const oldProject = { + ...TEST_PROJECT, + }; - expect(sectionHeaderEl).toBeDefined(); - expect(sectionHeaderEl.innerText.trim()).toBe('Frequently visited'); - done(); - }); - }); + const newProject = { + ...oldProject, + name: 'New Name', + avatarUrl: 'new/avatar.png', + namespace: 'New / Namespace', + webUrl: 'http://localhost/new/web/url', + }; - it('should render frequent projects list', (done) => { - const expectedResult = getTopFrequentItems(mockFrequentProjects); - localStorage.getItem.mockImplementation(() => JSON.stringify(mockFrequentProjects)); + createComponent({ currentItem: oldProject }); + wrapper.destroy(); + expect(getStoredProjects()).toEqual([expect.objectContaining(oldProject)]); - expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(1); + createComponent({ currentItem: newProject }); + wrapper.destroy(); - vm.fetchFrequentItems(); - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe( - expectedResult.length, - ); - done(); - }); + expect(getStoredProjects()).toEqual([expect.objectContaining(newProject)]); }); - it('should render searched projects list', (done) => { - mock.onGet(/\/api\/v4\/projects.json(.*)$/).replyOnce(200, mockSearchedProjects); - - expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(1); - - vm.$store.dispatch('setSearchQuery', 'gitlab'); - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelector('.loading-animation')).toBeDefined(); - }) - .then(waitForPromises) - .then(() => { - expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe( - mockSearchedProjects.data.length, - ); - }) - .then(done) - .catch(done.fail); + it('should not add more than 20 projects in store', () => { + for (let id = 0; id < FREQUENT_ITEMS.MAX_COUNT + 10; id += 1) { + const project = { + ...TEST_PROJECT, + id, + }; + createComponent({ currentItem: project }); + wrapper.destroy(); + } + + expect(getStoredProjects().length).toBe(FREQUENT_ITEMS.MAX_COUNT); }); }); }); diff --git a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js index 66fb346cb38..9a68115e4f6 100644 --- a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js +++ b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js @@ -1,14 +1,18 @@ -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import { trimText } from 'helpers/text_helper'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; import { createStore } from '~/frequent_items/store'; import { mockProject } from '../mock_data'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('FrequentItemsListItemComponent', () => { let wrapper; let trackingSpy; - let store = createStore(); + let store; const findTitle = () => wrapper.find({ ref: 'frequentItemsItemTitle' }); const findAvatar = () => wrapper.find({ ref: 'frequentItemsItemAvatar' }); @@ -31,11 +35,15 @@ describe('FrequentItemsListItemComponent', () => { avatarUrl: mockProject.avatarUrl, ...props, }, + provide: { + vuexModule: 'frequentProjects', + }, + localVue, }); }; beforeEach(() => { - store = createStore({ dropdownType: 'project' }); + store = createStore(); trackingSpy = mockTracking('_category_', document, jest.spyOn); trackingSpy.mockImplementation(() => {}); }); @@ -119,7 +127,7 @@ describe('FrequentItemsListItemComponent', () => { }); link.trigger('click'); expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_link', { - label: 'project_dropdown_frequent_items_list_item', + label: 'projects_dropdown_frequent_items_list_item', }); }); }); diff --git a/spec/frontend/frequent_items/components/frequent_items_list_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_spec.js index bd0711005b3..c015914c991 100644 --- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js +++ b/spec/frontend/frequent_items/components/frequent_items_list_spec.js @@ -1,9 +1,13 @@ -import { mount } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import frequentItemsListComponent from '~/frequent_items/components/frequent_items_list.vue'; import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; import { createStore } from '~/frequent_items/store'; import { mockFrequentProjects } from '../mock_data'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('FrequentItemsListComponent', () => { let wrapper; @@ -18,6 +22,10 @@ describe('FrequentItemsListComponent', () => { matcher: 'lab', ...props, }, + localVue, + provide: { + vuexModule: 'frequentProjects', + }, }); }; diff --git a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js index 0280fdb0ca2..c9b7e0f3d13 100644 --- a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js +++ b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js @@ -1,9 +1,13 @@ import { GlSearchBoxByType } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue'; import { createStore } from '~/frequent_items/store'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('FrequentItemsSearchInputComponent', () => { let wrapper; let trackingSpy; @@ -14,12 +18,16 @@ describe('FrequentItemsSearchInputComponent', () => { shallowMount(searchComponent, { store, propsData: { namespace }, + localVue, + provide: { + vuexModule: 'frequentProjects', + }, }); const findSearchBoxByType = () => wrapper.find(GlSearchBoxByType); beforeEach(() => { - store = createStore({ dropdownType: 'project' }); + store = createStore(); jest.spyOn(store, 'dispatch').mockImplementation(() => {}); trackingSpy = mockTracking('_category_', document, jest.spyOn); @@ -57,9 +65,9 @@ describe('FrequentItemsSearchInputComponent', () => { await wrapper.vm.$nextTick(); expect(trackingSpy).toHaveBeenCalledWith(undefined, 'type_search_query', { - label: 'project_dropdown_frequent_items_search_input', + label: 'projects_dropdown_frequent_items_search_input', }); - expect(store.dispatch).toHaveBeenCalledWith('setSearchQuery', value); + expect(store.dispatch).toHaveBeenCalledWith('frequentProjects/setSearchQuery', value); }); }); }); |