diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-01 15:10:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-01 15:10:10 +0300 |
commit | 79c94e595b13bd4b4522e725e6096a41ff1a27ec (patch) | |
tree | bb55afb6b391b5d28683361024e47d175a18c8e9 /spec/frontend | |
parent | 17a47c3e305567151191ce166702bd4e35e62a7b (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
16 files changed, 118 insertions, 1609 deletions
diff --git a/spec/frontend/ci/job_details/store/actions_spec.js b/spec/frontend/ci/job_details/store/actions_spec.js index 1a24bec092f..3e3e872f698 100644 --- a/spec/frontend/ci/job_details/store/actions_spec.js +++ b/spec/frontend/ci/job_details/store/actions_spec.js @@ -15,7 +15,6 @@ import { fetchJobLog, startPollingJobLog, stopPollingJobLog, - receiveJobLogSuccess, receiveJobLogError, toggleCollapsibleLine, requestJobsForStage, @@ -27,11 +26,15 @@ import { toggleSidebar, receiveTestSummarySuccess, } from '~/ci/job_details/store/actions'; +import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; + import * as types from '~/ci/job_details/store/mutation_types'; import state from '~/ci/job_details/store/state'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; +jest.mock('~/lib/utils/scroll_utils'); + describe('Job State actions', () => { let mockedState; @@ -212,42 +215,39 @@ describe('Job State actions', () => { }); describe('success', () => { - it('dispatches requestJobLog, receiveJobLogSuccess and stopPollingJobLog when job is complete', () => { - mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, { - html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', - complete: true, + let jobLogPayload; + + beforeEach(() => { + isScrolledToBottom.mockReturnValue(false); + }); + + describe('when job is complete', () => { + beforeEach(() => { + jobLogPayload = { + html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', + complete: true, + }; + + mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, jobLogPayload); }); - return testAction( - fetchJobLog, - null, - mockedState, - [], - [ - { - type: 'toggleScrollisInBottom', - payload: true, - }, - { - payload: { - html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', - complete: true, + it('commits RECEIVE_JOB_LOG_SUCCESS, dispatches stopPollingJobLog and requestTestSummary', () => { + return testAction( + fetchJobLog, + null, + mockedState, + [ + { + type: types.RECEIVE_JOB_LOG_SUCCESS, + payload: jobLogPayload, }, - type: 'receiveJobLogSuccess', - }, - { - type: 'stopPollingJobLog', - }, - { - type: 'requestTestSummary', - }, - ], - ); + ], + [{ type: 'stopPollingJobLog' }, { type: 'requestTestSummary' }], + ); + }); }); describe('when job is incomplete', () => { - let jobLogPayload; - beforeEach(() => { jobLogPayload = { html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', @@ -262,12 +262,13 @@ describe('Job State actions', () => { fetchJobLog, null, mockedState, - [], [ - { type: 'toggleScrollisInBottom', payload: true }, - { type: 'receiveJobLogSuccess', payload: jobLogPayload }, - { type: 'startPollingJobLog' }, + { + type: types.RECEIVE_JOB_LOG_SUCCESS, + payload: jobLogPayload, + }, ], + [{ type: 'startPollingJobLog' }], ); }); @@ -278,10 +279,44 @@ describe('Job State actions', () => { fetchJobLog, null, mockedState, + [ + { + type: types.RECEIVE_JOB_LOG_SUCCESS, + payload: jobLogPayload, + }, + ], [], + ); + }); + }); + + describe('when user scrolled to the bottom', () => { + beforeEach(() => { + isScrolledToBottom.mockReturnValue(true); + + jobLogPayload = { + html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', + complete: true, + }; + + mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, jobLogPayload); + }); + + it('should auto scroll to bottom by dispatching scrollBottom', () => { + return testAction( + fetchJobLog, + null, + mockedState, [ - { type: 'toggleScrollisInBottom', payload: true }, - { type: 'receiveJobLogSuccess', payload: jobLogPayload }, + { + type: types.RECEIVE_JOB_LOG_SUCCESS, + payload: jobLogPayload, + }, + ], + [ + { type: 'stopPollingJobLog' }, + { type: 'requestTestSummary' }, + { type: 'scrollBottom' }, ], ); }); @@ -393,18 +428,6 @@ describe('Job State actions', () => { }); }); - describe('receiveJobLogSuccess', () => { - it('should commit RECEIVE_JOB_LOG_SUCCESS mutation', () => { - return testAction( - receiveJobLogSuccess, - 'hello world', - mockedState, - [{ type: types.RECEIVE_JOB_LOG_SUCCESS, payload: 'hello world' }], - [], - ); - }); - }); - describe('receiveJobLogError', () => { it('should commit stop polling job log', () => { return testAction(receiveJobLogError, null, mockedState, [], [{ type: 'stopPollingJobLog' }]); diff --git a/spec/frontend/frequent_items/components/app_spec.js b/spec/frontend/frequent_items/components/app_spec.js deleted file mode 100644 index 122155a5d3f..00000000000 --- a/spec/frontend/frequent_items/components/app_spec.js +++ /dev/null @@ -1,286 +0,0 @@ -import { GlButton, GlIcon } from '@gitlab/ui'; -import MockAdapter from 'axios-mock-adapter'; -import Vue, { nextTick } from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import { useLocalStorageSpy } from 'helpers/local_storage_helper'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import App from '~/frequent_items/components/app.vue'; -import FrequentItemsList from '~/frequent_items/components/frequent_items_list.vue'; -import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants'; -import eventHub from '~/frequent_items/event_hub'; -import { createStore } from '~/frequent_items/store'; -import { getTopFrequentItems } from '~/frequent_items/utils'; -import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import { currentSession, mockFrequentProjects, mockSearchedProjects } from '../mock_data'; - -Vue.use(Vuex); - -useLocalStorageSpy(); - -const TEST_NAMESPACE = 'projects'; -const TEST_VUEX_MODULE = 'frequentProjects'; -const TEST_PROJECT = currentSession[TEST_NAMESPACE].project; -const TEST_STORAGE_KEY = currentSession[TEST_NAMESPACE].storageKey; -const TEST_SEARCH_CLASS = 'test-search-class'; - -describe('Frequent Items App Component', () => { - let wrapper; - let mock; - let store; - - const createComponent = (props = {}) => { - const session = currentSession[TEST_NAMESPACE]; - gon.api_version = session.apiVersion; - - wrapper = mountExtended(App, { - store, - propsData: { - namespace: TEST_NAMESPACE, - currentUserName: session.username, - currentItem: session.project, - ...props, - }, - 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); - store = createStore(); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('default', () => { - beforeEach(() => { - jest.spyOn(store, 'dispatch'); - - createComponent(); - }); - - it('should fetch frequent items', () => { - triggerDropdownOpen(); - - expect(store.dispatch).toHaveBeenCalledWith(`${TEST_VUEX_MODULE}/fetchFrequentItems`); - }); - - it('should not fetch frequent items if detroyed', () => { - wrapper.destroy(); - triggerDropdownOpen(); - - expect(store.dispatch).not.toHaveBeenCalledWith(`${TEST_VUEX_MODULE}/fetchFrequentItems`); - }); - - it('should render search input', () => { - expect(findSearchInput().classes()).toEqual(['search-input-container']); - }); - - it('should render loading animation', async () => { - triggerDropdownOpen(); - store.state[TEST_VUEX_MODULE].isLoadingItems = true; - - await nextTick(); - - const loading = findLoading(); - - expect(loading.exists()).toBe(true); - expect(loading.find('[aria-label="Loading projects"]').exists()).toBe(true); - expect(findSectionHeader().exists()).toBe(false); - }); - - it('should render frequent projects list header', () => { - const sectionHeader = findSectionHeader(); - - expect(sectionHeader.exists()).toBe(true); - expect(sectionHeader.text()).toBe('Frequently visited'); - }); - - it('should render searched projects list', async () => { - mock - .onGet(/\/api\/v4\/projects.json(.*)$/) - .replyOnce(HTTP_STATUS_OK, mockSearchedProjects.data); - - setSearch('gitlab'); - await 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: avatarUrl, - web_url: webUrl, - name_with_namespace: namespace, - ...item - }) => ({ - ...item, - avatarUrl, - webUrl, - namespace, - }), - ), - namespace: TEST_NAMESPACE, - hasSearchQuery: true, - isFetchFailed: false, - matcher: 'gitlab', - }), - ); - }); - - describe('with frequent items list', () => { - const expectedResult = getTopFrequentItems(mockFrequentProjects); - - beforeEach(async () => { - localStorage.setItem(TEST_STORAGE_KEY, JSON.stringify(mockFrequentProjects)); - triggerDropdownOpen(); - await nextTick(); - }); - - it('should render edit button within header', () => { - const itemEditButton = findSectionHeader().findComponent(GlButton); - - expect(itemEditButton.exists()).toBe(true); - expect(itemEditButton.attributes('title')).toBe('Toggle edit mode'); - expect(itemEditButton.findComponent(GlIcon).props('name')).toBe('pencil'); - }); - - it('should render frequent projects list', () => { - expect(findFrequentItems().length).toBe(expectedResult.length); - expect(findFrequentItemsList().props()).toEqual({ - items: expectedResult, - namespace: TEST_NAMESPACE, - hasSearchQuery: false, - isFetchFailed: false, - isItemRemovalFailed: false, - matcher: '', - }); - }); - - it('dispatches action `toggleItemsListEditablity` when edit button is clicked', async () => { - const itemEditButton = findSectionHeader().findComponent(GlButton); - itemEditButton.vm.$emit('click'); - - await nextTick(); - - expect(store.dispatch).toHaveBeenCalledWith( - `${TEST_VUEX_MODULE}/toggleItemsListEditablity`, - ); - }); - }); - }); - - describe('with searchClass', () => { - beforeEach(() => { - createComponent({ searchClass: TEST_SEARCH_CLASS }); - }); - - it('should render search input with searchClass', () => { - expect(findSearchInput().classes()).toEqual(['search-input-container', TEST_SEARCH_CLASS]); - }); - }); - - describe('logging', () => { - it('when created, it should create a project storage entry and adds a project', () => { - createComponent(); - - expect(getStoredProjects()).toEqual([ - expect.objectContaining({ - frequency: 1, - lastAccessedOn: Date.now(), - }), - ]); - }); - - describe('when created multiple times', () => { - beforeEach(() => { - createComponent(); - wrapper.destroy(); - createComponent(); - wrapper.destroy(); - }); - - it('should only log once', () => { - expect(getStoredProjects()).toEqual([ - expect.objectContaining({ - lastAccessedOn: Date.now(), - frequency: 1, - }), - ]); - }); - - it('should increase frequency, when created 15 minutes later', () => { - const fifteenMinutesLater = Date.now() + FIFTEEN_MINUTES_IN_MS + 1; - - jest.spyOn(Date, 'now').mockReturnValue(fifteenMinutesLater); - createComponent({ currentItem: { ...TEST_PROJECT, lastAccessedOn: fifteenMinutesLater } }); - - expect(getStoredProjects()).toEqual([ - expect.objectContaining({ - lastAccessedOn: fifteenMinutesLater, - frequency: 2, - }), - ]); - }); - }); - - it('should always update project metadata', () => { - const oldProject = { - ...TEST_PROJECT, - }; - - const newProject = { - ...oldProject, - name: 'New Name', - avatarUrl: 'new/avatar.png', - namespace: 'New / Namespace', - webUrl: 'http://localhost/new/web/url', - }; - - createComponent({ currentItem: oldProject }); - wrapper.destroy(); - expect(getStoredProjects()).toEqual([expect.objectContaining(oldProject)]); - - createComponent({ currentItem: newProject }); - wrapper.destroy(); - - expect(getStoredProjects()).toEqual([expect.objectContaining(newProject)]); - }); - - 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 deleted file mode 100644 index 55d20ad603c..00000000000 --- a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js +++ /dev/null @@ -1,161 +0,0 @@ -import { GlIcon } from '@gitlab/ui'; -import Vue, { nextTick } from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -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 ProjectAvatar from '~/vue_shared/components/project_avatar.vue'; -import { mockProject } from '../mock_data'; - -Vue.use(Vuex); - -describe('FrequentItemsListItemComponent', () => { - const TEST_VUEX_MODULE = 'frequentProjects'; - let wrapper; - let trackingSpy; - let store; - - const findTitle = () => wrapper.findByTestId('frequent-items-item-title'); - const findAvatar = () => wrapper.findComponent(ProjectAvatar); - const findAllTitles = () => wrapper.findAllByTestId('frequent-items-item-title'); - const findNamespace = () => wrapper.findByTestId('frequent-items-item-namespace'); - const findAllFrequentItems = () => wrapper.findAllByTestId('frequent-item-link'); - const findAllNamespace = () => wrapper.findAllByTestId('frequent-items-item-namespace'); - const findAllAvatars = () => wrapper.findAllComponents(ProjectAvatar); - const findAllMetadataContainers = () => - wrapper.findAllByTestId('frequent-items-item-metadata-container'); - const findRemoveButton = () => wrapper.findByTestId('item-remove'); - - const toggleItemsListEditablity = async () => { - store.dispatch(`${TEST_VUEX_MODULE}/toggleItemsListEditablity`); - - await nextTick(); - }; - - const createComponent = (props = {}) => { - wrapper = shallowMountExtended(frequentItemsListItemComponent, { - store, - propsData: { - itemId: mockProject.id, - itemName: mockProject.name, - namespace: mockProject.namespace, - webUrl: mockProject.webUrl, - avatarUrl: mockProject.avatarUrl, - ...props, - }, - provide: { - vuexModule: TEST_VUEX_MODULE, - }, - }); - }; - - beforeEach(() => { - store = createStore(); - trackingSpy = mockTracking('_category_', document, jest.spyOn); - trackingSpy.mockImplementation(() => {}); - }); - - afterEach(() => { - unmockTracking(); - }); - - describe('computed', () => { - describe('highlightedItemName', () => { - it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => { - createComponent({ matcher: 'lab' }); - - expect(findTitle().element.innerHTML).toContain('<b>L</b><b>a</b><b>b</b>'); - }); - - it('should return project name as it is if `matcher` is not available', () => { - createComponent({ matcher: null }); - - expect(trimText(findTitle().text())).toBe(mockProject.name); - }); - }); - - describe('truncatedNamespace', () => { - it('should truncate project name from namespace string', () => { - createComponent({ namespace: 'platform / nokia-3310' }); - - expect(trimText(findNamespace().text())).toBe('platform'); - }); - - it('should truncate namespace string from the middle if it includes more than two groups in path', () => { - createComponent({ - namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310', - }); - - expect(trimText(findNamespace().text())).toBe('platform / ... / Mobile Chipset'); - }); - }); - }); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders avatar', () => { - expect(findAvatar().exists()).toBe(true); - }); - - it('renders root element with the right classes', () => { - expect(wrapper.classes('frequent-items-list-item-container')).toBe(true); - }); - - it.each` - name | selector | expected - ${'list item'} | ${findAllFrequentItems} | ${1} - ${'avatar container'} | ${findAllAvatars} | ${1} - ${'metadata container'} | ${findAllMetadataContainers} | ${1} - ${'title'} | ${findAllTitles} | ${1} - ${'namespace'} | ${findAllNamespace} | ${1} - `('should render $expected $name', ({ selector, expected }) => { - expect(selector()).toHaveLength(expected); - }); - - it('renders remove button within item when `isItemsListEditable` is true', async () => { - await toggleItemsListEditablity(); - - const removeButton = findRemoveButton(); - expect(removeButton.exists()).toBe(true); - expect(removeButton.attributes('title')).toBe('Remove'); - expect(removeButton.findComponent(GlIcon).props('name')).toBe('close'); - }); - - it('dispatches action `removeFrequentItem` when remove button is clicked', async () => { - await toggleItemsListEditablity(); - - jest.spyOn(store, 'dispatch'); - - const removeButton = findRemoveButton(); - removeButton.vm.$emit( - 'click', - { stopPropagation: jest.fn(), preventDefault: jest.fn() }, - mockProject.id, - ); - - await nextTick(); - - expect(store.dispatch).toHaveBeenCalledWith( - `${TEST_VUEX_MODULE}/removeFrequentItem`, - mockProject.id, - ); - }); - - it('tracks when item link is clicked', () => { - const link = wrapper.findByTestId('frequent-item-link'); - - link.vm.$emit('click'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_link', { - label: 'projects_dropdown_frequent_items_list_item', - property: 'navigation_top', - }); - }); - }); -}); diff --git a/spec/frontend/frequent_items/components/frequent_items_list_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_spec.js deleted file mode 100644 index 8055b7a9c13..00000000000 --- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import Vue, { nextTick } from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -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'; - -Vue.use(Vuex); - -describe('FrequentItemsListComponent', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = mountExtended(frequentItemsListComponent, { - store: createStore(), - propsData: { - namespace: 'projects', - items: mockFrequentProjects, - isFetchFailed: false, - isItemRemovalFailed: false, - hasSearchQuery: false, - matcher: 'lab', - ...props, - }, - provide: { - vuexModule: 'frequentProjects', - }, - }); - }; - - describe('computed', () => { - describe('isListEmpty', () => { - it('should return `true` or `false` representing whether if `items` is empty or not with projects', async () => { - createComponent({ - items: [], - }); - - expect(wrapper.vm.isListEmpty).toBe(true); - - wrapper.setProps({ - items: mockFrequentProjects, - }); - await nextTick(); - - expect(wrapper.vm.isListEmpty).toBe(false); - }); - }); - - describe('fetched item messages', () => { - it('should show default empty list message', () => { - createComponent({ - items: [], - }); - - expect(wrapper.findByTestId('frequent-items-list-empty').text()).toContain( - 'Projects you visit often will appear here', - ); - }); - - it.each` - isFetchFailed | isItemRemovalFailed - ${true} | ${false} - ${false} | ${true} - `( - 'should show failure message when `isFetchFailed` is $isFetchFailed or `isItemRemovalFailed` is $isItemRemovalFailed', - ({ isFetchFailed, isItemRemovalFailed }) => { - createComponent({ - items: [], - isFetchFailed, - isItemRemovalFailed, - }); - - expect(wrapper.findByTestId('frequent-items-list-empty').text()).toContain( - 'This feature requires browser localStorage support', - ); - }, - ); - }); - - describe('searched item messages', () => { - it('should return appropriate empty list message based on value of `searchFailed` prop with projects', async () => { - createComponent({ - hasSearchQuery: true, - isFetchFailed: true, - }); - - expect(wrapper.vm.listEmptyMessage).toBe('Something went wrong on our end.'); - - wrapper.setProps({ - isFetchFailed: false, - }); - await nextTick(); - - expect(wrapper.vm.listEmptyMessage).toBe('Sorry, no projects matched your search'); - }); - }); - }); - - describe('template', () => { - it('should render component element with list of projects', async () => { - createComponent(); - - await nextTick(); - expect(wrapper.classes('frequent-items-list-container')).toBe(true); - expect(wrapper.findAllByTestId('frequent-items-list')).toHaveLength(1); - expect(wrapper.findAllComponents(frequentItemsListItemComponent)).toHaveLength(5); - }); - - it('should render component element with empty message', async () => { - createComponent({ - items: [], - }); - - await nextTick(); - expect(wrapper.vm.$el.querySelectorAll('li.section-empty')).toHaveLength(1); - expect(wrapper.findAllComponents(frequentItemsListItemComponent)).toHaveLength(0); - }); - }); -}); 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 deleted file mode 100644 index d6aa0f4e221..00000000000 --- a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js +++ /dev/null @@ -1,74 +0,0 @@ -import { GlSearchBoxByType } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; -// eslint-disable-next-line no-restricted-imports -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'; - -Vue.use(Vuex); - -describe('FrequentItemsSearchInputComponent', () => { - let wrapper; - let trackingSpy; - let vm; - let store; - - const createComponent = (namespace = 'projects') => - shallowMount(searchComponent, { - store, - propsData: { namespace }, - provide: { - vuexModule: 'frequentProjects', - }, - }); - - const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType); - - beforeEach(() => { - store = createStore(); - jest.spyOn(store, 'dispatch').mockImplementation(() => {}); - - trackingSpy = mockTracking('_category_', document, jest.spyOn); - trackingSpy.mockImplementation(() => {}); - - wrapper = createComponent(); - - ({ vm } = wrapper); - }); - - afterEach(() => { - unmockTracking(); - vm.$destroy(); - }); - - describe('template', () => { - it('should render component element', () => { - expect(wrapper.classes()).toContain('search-input-container'); - expect(findSearchBoxByType().exists()).toBe(true); - expect(findSearchBoxByType().attributes()).toMatchObject({ - placeholder: 'Search your projects', - }); - }); - }); - - describe('tracking', () => { - it('tracks when search query is entered', async () => { - expect(trackingSpy).not.toHaveBeenCalled(); - expect(store.dispatch).not.toHaveBeenCalled(); - - const value = 'my project'; - - findSearchBoxByType().vm.$emit('input', value); - - await nextTick(); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'type_search_query', { - label: 'projects_dropdown_frequent_items_search_input', - property: 'navigation_top', - }); - expect(store.dispatch).toHaveBeenCalledWith('frequentProjects/setSearchQuery', value); - }); - }); -}); diff --git a/spec/frontend/frequent_items/mock_data.js b/spec/frontend/frequent_items/mock_data.js deleted file mode 100644 index 6563daee6c3..00000000000 --- a/spec/frontend/frequent_items/mock_data.js +++ /dev/null @@ -1,169 +0,0 @@ -import { TEST_HOST } from 'helpers/test_constants'; - -export const currentSession = { - groups: { - username: 'root', - storageKey: 'root/frequent-groups', - apiVersion: 'v4', - group: { - id: 1, - name: 'dummy-group', - full_name: 'dummy-parent-group', - webUrl: `${TEST_HOST}/dummy-group`, - avatarUrl: null, - lastAccessedOn: Date.now(), - }, - }, - projects: { - username: 'root', - storageKey: 'root/frequent-projects', - apiVersion: 'v4', - project: { - id: 1, - name: 'dummy-project', - namespace: 'SampleGroup / Dummy-Project', - webUrl: `${TEST_HOST}/samplegroup/dummy-project`, - avatarUrl: null, - lastAccessedOn: Date.now(), - }, - }, -}; - -export const mockNamespace = 'projects'; -export const mockStorageKey = 'test-user/frequent-projects'; - -export const mockGroup = { - id: 1, - name: 'Sub451', - namespace: 'Commit451 / Sub451', - webUrl: `${TEST_HOST}/Commit451/Sub451`, - avatarUrl: null, -}; - -export const mockRawGroup = { - id: 1, - name: 'Sub451', - full_name: 'Commit451 / Sub451', - web_url: `${TEST_HOST}/Commit451/Sub451`, - avatar_url: null, -}; - -export const mockFrequentGroups = [ - { - id: 3, - name: 'Subgroup451', - full_name: 'Commit451 / Subgroup451', - webUrl: '/Commit451/Subgroup451', - avatarUrl: null, - frequency: 7, - lastAccessedOn: 1497979281815, - }, - { - id: 1, - name: 'Commit451', - full_name: 'Commit451', - webUrl: '/Commit451', - avatarUrl: null, - frequency: 3, - lastAccessedOn: 1497979281815, - }, -]; - -export const mockSearchedGroups = { data: [mockRawGroup] }; -export const mockProcessedSearchedGroups = [mockGroup]; - -export const mockProject = { - id: 1, - name: 'GitLab Community Edition', - namespace: 'gitlab-org / gitlab-ce', - webUrl: `${TEST_HOST}/gitlab-org/gitlab-foss`, - avatarUrl: null, -}; - -export const mockRawProject = { - id: 1, - name: 'GitLab Community Edition', - name_with_namespace: 'gitlab-org / gitlab-ce', - web_url: `${TEST_HOST}/gitlab-org/gitlab-foss`, - avatar_url: null, -}; - -export const mockFrequentProjects = [ - { - id: 1, - name: 'GitLab Community Edition', - namespace: 'gitlab-org / gitlab-ce', - webUrl: `${TEST_HOST}/gitlab-org/gitlab-foss`, - avatarUrl: null, - frequency: 1, - lastAccessedOn: Date.now(), - }, - { - id: 2, - name: 'GitLab CI', - namespace: 'gitlab-org / gitlab-ci', - webUrl: `${TEST_HOST}/gitlab-org/gitlab-ci`, - avatarUrl: null, - frequency: 9, - lastAccessedOn: Date.now(), - }, - { - id: 3, - name: 'Typeahead.Js', - namespace: 'twitter / typeahead-js', - webUrl: `${TEST_HOST}/twitter/typeahead-js`, - avatarUrl: '/uploads/-/system/project/avatar/7/TWBS.png', - frequency: 2, - lastAccessedOn: Date.now(), - }, - { - id: 4, - name: 'Intel', - namespace: 'platform / hardware / bsp / intel', - webUrl: `${TEST_HOST}/platform/hardware/bsp/intel`, - avatarUrl: null, - frequency: 3, - lastAccessedOn: Date.now(), - }, - { - id: 5, - name: 'v4.4', - namespace: 'platform / hardware / bsp / kernel / common / v4.4', - webUrl: `${TEST_HOST}/platform/hardware/bsp/kernel/common/v4.4`, - avatarUrl: null, - frequency: 8, - lastAccessedOn: Date.now(), - }, -]; - -export const mockSearchedProjects = { data: [mockRawProject] }; -export const mockProcessedSearchedProjects = [mockProject]; - -export const unsortedFrequentItems = [ - { id: 1, frequency: 12, lastAccessedOn: 1491400843391 }, - { id: 2, frequency: 14, lastAccessedOn: 1488240890738 }, - { id: 3, frequency: 44, lastAccessedOn: 1497675908472 }, - { id: 4, frequency: 8, lastAccessedOn: 1497979281815 }, - { id: 5, frequency: 34, lastAccessedOn: 1488089211943 }, - { id: 6, frequency: 14, lastAccessedOn: 1493517292488 }, - { id: 7, frequency: 42, lastAccessedOn: 1486815299875 }, - { id: 8, frequency: 33, lastAccessedOn: 1500762279114 }, - { id: 10, frequency: 46, lastAccessedOn: 1483251641543 }, -]; - -/** - * This const has a specific order which tests authenticity - * of `getTopFrequentItems` method so - * DO NOT change order of items in this const. - */ -export const sortedFrequentItems = [ - { id: 10, frequency: 46, lastAccessedOn: 1483251641543 }, - { id: 3, frequency: 44, lastAccessedOn: 1497675908472 }, - { id: 7, frequency: 42, lastAccessedOn: 1486815299875 }, - { id: 5, frequency: 34, lastAccessedOn: 1488089211943 }, - { id: 8, frequency: 33, lastAccessedOn: 1500762279114 }, - { id: 6, frequency: 14, lastAccessedOn: 1493517292488 }, - { id: 2, frequency: 14, lastAccessedOn: 1488240890738 }, - { id: 1, frequency: 12, lastAccessedOn: 1491400843391 }, - { id: 4, frequency: 8, lastAccessedOn: 1497979281815 }, -]; diff --git a/spec/frontend/frequent_items/store/actions_spec.js b/spec/frontend/frequent_items/store/actions_spec.js deleted file mode 100644 index 2feb488da2c..00000000000 --- a/spec/frontend/frequent_items/store/actions_spec.js +++ /dev/null @@ -1,304 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import testAction from 'helpers/vuex_action_helper'; -import * as actions from '~/frequent_items/store/actions'; -import * as types from '~/frequent_items/store/mutation_types'; -import state from '~/frequent_items/store/state'; -import AccessorUtilities from '~/lib/utils/accessor'; -import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import { useLocalStorageSpy } from 'helpers/local_storage_helper'; -import { - mockNamespace, - mockStorageKey, - mockFrequentProjects, - mockSearchedProjects, -} from '../mock_data'; - -describe('Frequent Items Dropdown Store Actions', () => { - useLocalStorageSpy(); - let mockedState; - let mock; - - beforeEach(() => { - mockedState = state(); - mock = new MockAdapter(axios); - - mockedState.namespace = mockNamespace; - mockedState.storageKey = mockStorageKey; - }); - - afterEach(() => { - mock.restore(); - }); - - describe('setNamespace', () => { - it('should set namespace', () => { - return testAction( - actions.setNamespace, - mockNamespace, - mockedState, - [{ type: types.SET_NAMESPACE, payload: mockNamespace }], - [], - ); - }); - }); - - describe('setStorageKey', () => { - it('should set storage key', () => { - return testAction( - actions.setStorageKey, - mockStorageKey, - mockedState, - [{ type: types.SET_STORAGE_KEY, payload: mockStorageKey }], - [], - ); - }); - }); - - describe('toggleItemsListEditablity', () => { - it('should toggle items list editablity', () => { - return testAction( - actions.toggleItemsListEditablity, - null, - mockedState, - [{ type: types.TOGGLE_ITEMS_LIST_EDITABILITY }], - [], - ); - }); - }); - - describe('requestFrequentItems', () => { - it('should request frequent items', () => { - return testAction( - actions.requestFrequentItems, - null, - mockedState, - [{ type: types.REQUEST_FREQUENT_ITEMS }], - [], - ); - }); - }); - - describe('receiveFrequentItemsSuccess', () => { - it('should set frequent items', () => { - return testAction( - actions.receiveFrequentItemsSuccess, - mockFrequentProjects, - mockedState, - [{ type: types.RECEIVE_FREQUENT_ITEMS_SUCCESS, payload: mockFrequentProjects }], - [], - ); - }); - }); - - describe('receiveFrequentItemsError', () => { - it('should set frequent items error state', () => { - return testAction( - actions.receiveFrequentItemsError, - null, - mockedState, - [{ type: types.RECEIVE_FREQUENT_ITEMS_ERROR }], - [], - ); - }); - }); - - describe('fetchFrequentItems', () => { - it('should dispatch `receiveFrequentItemsSuccess`', () => { - mockedState.namespace = mockNamespace; - mockedState.storageKey = mockStorageKey; - - return testAction( - actions.fetchFrequentItems, - null, - mockedState, - [], - [{ type: 'requestFrequentItems' }, { type: 'receiveFrequentItemsSuccess', payload: [] }], - ); - }); - - it('should dispatch `receiveFrequentItemsError`', () => { - jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false); - mockedState.namespace = mockNamespace; - mockedState.storageKey = mockStorageKey; - - return testAction( - actions.fetchFrequentItems, - null, - mockedState, - [], - [{ type: 'requestFrequentItems' }, { type: 'receiveFrequentItemsError' }], - ); - }); - }); - - describe('requestSearchedItems', () => { - it('should request searched items', () => { - return testAction( - actions.requestSearchedItems, - null, - mockedState, - [{ type: types.REQUEST_SEARCHED_ITEMS }], - [], - ); - }); - }); - - describe('receiveSearchedItemsSuccess', () => { - it('should set searched items', () => { - return testAction( - actions.receiveSearchedItemsSuccess, - mockSearchedProjects, - mockedState, - [{ type: types.RECEIVE_SEARCHED_ITEMS_SUCCESS, payload: mockSearchedProjects }], - [], - ); - }); - }); - - describe('receiveSearchedItemsError', () => { - it('should set searched items error state', () => { - return testAction( - actions.receiveSearchedItemsError, - null, - mockedState, - [{ type: types.RECEIVE_SEARCHED_ITEMS_ERROR }], - [], - ); - }); - }); - - describe('fetchSearchedItems', () => { - beforeEach(() => { - gon.api_version = 'v4'; - }); - - it('should dispatch `receiveSearchedItemsSuccess`', () => { - mock - .onGet(/\/api\/v4\/projects.json(.*)$/) - .replyOnce(HTTP_STATUS_OK, mockSearchedProjects, {}); - - return testAction( - actions.fetchSearchedItems, - null, - mockedState, - [], - [ - { type: 'requestSearchedItems' }, - { - type: 'receiveSearchedItemsSuccess', - payload: { data: mockSearchedProjects, headers: {} }, - }, - ], - ); - }); - - it('should dispatch `receiveSearchedItemsError`', () => { - gon.api_version = 'v4'; - mock.onGet(/\/api\/v4\/projects.json(.*)$/).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR); - - return testAction( - actions.fetchSearchedItems, - null, - mockedState, - [], - [{ type: 'requestSearchedItems' }, { type: 'receiveSearchedItemsError' }], - ); - }); - }); - - describe('setSearchQuery', () => { - it('should commit query and dispatch `fetchSearchedItems` when query is present', () => { - return testAction( - actions.setSearchQuery, - { query: 'test' }, - mockedState, - [{ type: types.SET_SEARCH_QUERY, payload: { query: 'test' } }], - [{ type: 'fetchSearchedItems', payload: { query: 'test' } }], - ); - }); - - it('should commit query and dispatch `fetchFrequentItems` when query is empty', () => { - return testAction( - actions.setSearchQuery, - null, - mockedState, - [{ type: types.SET_SEARCH_QUERY, payload: null }], - [{ type: 'fetchFrequentItems' }], - ); - }); - }); - - describe('removeFrequentItemSuccess', () => { - it('should remove frequent item on success', () => { - return testAction( - actions.removeFrequentItemSuccess, - { itemId: 1 }, - mockedState, - [ - { - type: types.RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS, - payload: { itemId: 1 }, - }, - ], - [], - ); - }); - }); - - describe('removeFrequentItemError', () => { - it('should should not remove frequent item on failure', () => { - return testAction( - actions.removeFrequentItemError, - null, - mockedState, - [{ type: types.RECEIVE_REMOVE_FREQUENT_ITEM_ERROR }], - [], - ); - }); - }); - - describe('removeFrequentItem', () => { - beforeEach(() => { - mockedState.items = [...mockFrequentProjects]; - window.localStorage.setItem(mockStorageKey, JSON.stringify(mockFrequentProjects)); - }); - - it('should remove provided itemId from localStorage', () => { - jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true); - - actions.removeFrequentItem( - { commit: jest.fn(), dispatch: jest.fn(), state: mockedState }, - mockFrequentProjects[0].id, - ); - - expect(window.localStorage.getItem(mockStorageKey)).toBe( - JSON.stringify(mockFrequentProjects.slice(1)), // First item was removed - ); - }); - - it('should dispatch `removeFrequentItemSuccess` on localStorage update success', () => { - jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true); - - return testAction( - actions.removeFrequentItem, - mockFrequentProjects[0].id, - mockedState, - [], - [{ type: 'removeFrequentItemSuccess', payload: mockFrequentProjects[0].id }], - ); - }); - - it('should dispatch `removeFrequentItemError` on localStorage update failure', () => { - jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false); - - return testAction( - actions.removeFrequentItem, - mockFrequentProjects[0].id, - mockedState, - [], - [{ type: 'removeFrequentItemError' }], - ); - }); - }); -}); diff --git a/spec/frontend/frequent_items/store/getters_spec.js b/spec/frontend/frequent_items/store/getters_spec.js deleted file mode 100644 index 97732cd95fc..00000000000 --- a/spec/frontend/frequent_items/store/getters_spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import * as getters from '~/frequent_items/store/getters'; -import state from '~/frequent_items/store/state'; - -describe('Frequent Items Dropdown Store Getters', () => { - let mockedState; - - beforeEach(() => { - mockedState = state(); - }); - - describe('hasSearchQuery', () => { - it('should return `true` when search query is present', () => { - mockedState.searchQuery = 'test'; - - expect(getters.hasSearchQuery(mockedState)).toBe(true); - }); - - it('should return `false` when search query is empty', () => { - mockedState.searchQuery = ''; - - expect(getters.hasSearchQuery(mockedState)).toBe(false); - }); - }); -}); diff --git a/spec/frontend/frequent_items/store/mutations_spec.js b/spec/frontend/frequent_items/store/mutations_spec.js deleted file mode 100644 index 1e1878c3377..00000000000 --- a/spec/frontend/frequent_items/store/mutations_spec.js +++ /dev/null @@ -1,152 +0,0 @@ -import * as types from '~/frequent_items/store/mutation_types'; -import mutations from '~/frequent_items/store/mutations'; -import state from '~/frequent_items/store/state'; -import { - mockNamespace, - mockStorageKey, - mockFrequentProjects, - mockSearchedProjects, - mockProcessedSearchedProjects, - mockSearchedGroups, - mockProcessedSearchedGroups, -} from '../mock_data'; - -describe('Frequent Items dropdown mutations', () => { - let stateCopy; - - beforeEach(() => { - stateCopy = state(); - }); - - describe('SET_NAMESPACE', () => { - it('should set namespace', () => { - mutations[types.SET_NAMESPACE](stateCopy, mockNamespace); - - expect(stateCopy.namespace).toEqual(mockNamespace); - }); - }); - - describe('SET_STORAGE_KEY', () => { - it('should set storage key', () => { - mutations[types.SET_STORAGE_KEY](stateCopy, mockStorageKey); - - expect(stateCopy.storageKey).toEqual(mockStorageKey); - }); - }); - - describe('SET_SEARCH_QUERY', () => { - it('should set search query', () => { - const searchQuery = 'gitlab-ce'; - - mutations[types.SET_SEARCH_QUERY](stateCopy, searchQuery); - - expect(stateCopy.searchQuery).toEqual(searchQuery); - }); - }); - - describe('TOGGLE_ITEMS_LIST_EDITABILITY', () => { - it('should toggle items list editablity', () => { - mutations[types.TOGGLE_ITEMS_LIST_EDITABILITY](stateCopy); - - expect(stateCopy.isItemsListEditable).toEqual(true); - - mutations[types.TOGGLE_ITEMS_LIST_EDITABILITY](stateCopy); - - expect(stateCopy.isItemsListEditable).toEqual(false); - }); - }); - - describe('REQUEST_FREQUENT_ITEMS', () => { - it('should set view states when requesting frequent items', () => { - mutations[types.REQUEST_FREQUENT_ITEMS](stateCopy); - - expect(stateCopy.isLoadingItems).toEqual(true); - expect(stateCopy.hasSearchQuery).toEqual(false); - }); - }); - - describe('RECEIVE_FREQUENT_ITEMS_SUCCESS', () => { - it('should set view states when receiving frequent items', () => { - mutations[types.RECEIVE_FREQUENT_ITEMS_SUCCESS](stateCopy, mockFrequentProjects); - - expect(stateCopy.items).toEqual(mockFrequentProjects); - expect(stateCopy.isLoadingItems).toEqual(false); - expect(stateCopy.hasSearchQuery).toEqual(false); - expect(stateCopy.isFetchFailed).toEqual(false); - }); - }); - - describe('RECEIVE_FREQUENT_ITEMS_ERROR', () => { - it('should set items and view states when error occurs retrieving frequent items', () => { - mutations[types.RECEIVE_FREQUENT_ITEMS_ERROR](stateCopy); - - expect(stateCopy.items).toEqual([]); - expect(stateCopy.isLoadingItems).toEqual(false); - expect(stateCopy.hasSearchQuery).toEqual(false); - expect(stateCopy.isFetchFailed).toEqual(true); - }); - }); - - describe('REQUEST_SEARCHED_ITEMS', () => { - it('should set view states when requesting searched items', () => { - mutations[types.REQUEST_SEARCHED_ITEMS](stateCopy); - - expect(stateCopy.isLoadingItems).toEqual(true); - expect(stateCopy.hasSearchQuery).toEqual(true); - }); - }); - - describe('RECEIVE_SEARCHED_ITEMS_SUCCESS', () => { - it('should set items and view states when receiving searched items', () => { - mutations[types.RECEIVE_SEARCHED_ITEMS_SUCCESS](stateCopy, mockSearchedProjects); - - expect(stateCopy.items).toEqual(mockProcessedSearchedProjects); - expect(stateCopy.isLoadingItems).toEqual(false); - expect(stateCopy.hasSearchQuery).toEqual(true); - expect(stateCopy.isFetchFailed).toEqual(false); - }); - - it('should also handle the different `full_name` key for namespace in groups payload', () => { - mutations[types.RECEIVE_SEARCHED_ITEMS_SUCCESS](stateCopy, mockSearchedGroups); - - expect(stateCopy.items).toEqual(mockProcessedSearchedGroups); - expect(stateCopy.isLoadingItems).toEqual(false); - expect(stateCopy.hasSearchQuery).toEqual(true); - expect(stateCopy.isFetchFailed).toEqual(false); - }); - }); - - describe('RECEIVE_SEARCHED_ITEMS_ERROR', () => { - it('should set view states when error occurs retrieving searched items', () => { - mutations[types.RECEIVE_SEARCHED_ITEMS_ERROR](stateCopy); - - expect(stateCopy.items).toEqual([]); - expect(stateCopy.isLoadingItems).toEqual(false); - expect(stateCopy.hasSearchQuery).toEqual(true); - expect(stateCopy.isFetchFailed).toEqual(true); - }); - }); - - describe('RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS', () => { - it('should remove item with provided itemId from the items', () => { - stateCopy.isItemRemovalFailed = true; - stateCopy.items = mockFrequentProjects; - - mutations[types.RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS](stateCopy, mockFrequentProjects[0].id); - - expect(stateCopy.items).toHaveLength(mockFrequentProjects.length - 1); - expect(stateCopy.items).toEqual([...mockFrequentProjects.slice(1)]); - expect(stateCopy.isItemRemovalFailed).toBe(false); - }); - }); - - describe('RECEIVE_REMOVE_FREQUENT_ITEM_ERROR', () => { - it('should remove item with provided itemId from the items', () => { - stateCopy.isItemRemovalFailed = false; - - mutations[types.RECEIVE_REMOVE_FREQUENT_ITEM_ERROR](stateCopy); - - expect(stateCopy.isItemRemovalFailed).toBe(true); - }); - }); -}); diff --git a/spec/frontend/frequent_items/utils_spec.js b/spec/frontend/frequent_items/utils_spec.js deleted file mode 100644 index 8d4c89bd48f..00000000000 --- a/spec/frontend/frequent_items/utils_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; -import { FIFTEEN_MINUTES_IN_MS, FREQUENT_ITEMS } from '~/frequent_items/constants'; -import { - isMobile, - getTopFrequentItems, - updateExistingFrequentItem, - sanitizeItem, -} from '~/frequent_items/utils'; -import { mockProject, unsortedFrequentItems, sortedFrequentItems } from './mock_data'; - -describe('Frequent Items utils spec', () => { - describe('isMobile', () => { - it('returns true when the screen is medium', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('md'); - - expect(isMobile()).toBe(true); - }); - - it('returns true when the screen is small', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('sm'); - - expect(isMobile()).toBe(true); - }); - - it('returns true when the screen is extra-small', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('xs'); - - expect(isMobile()).toBe(true); - }); - - it('returns false when the screen is larger than medium', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('lg'); - - expect(isMobile()).toBe(false); - }); - }); - - describe('getTopFrequentItems', () => { - it('returns empty array if no items provided', () => { - const result = getTopFrequentItems(); - - expect(result.length).toBe(0); - }); - - it('returns correct amount of items for mobile', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('md'); - const result = getTopFrequentItems(unsortedFrequentItems); - - expect(result.length).toBe(FREQUENT_ITEMS.LIST_COUNT_MOBILE); - }); - - it('returns correct amount of items for desktop', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('xl'); - const result = getTopFrequentItems(unsortedFrequentItems); - - expect(result.length).toBe(FREQUENT_ITEMS.LIST_COUNT_DESKTOP); - }); - - it('sorts frequent items in order of frequency and lastAccessedOn', () => { - jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('xl'); - const result = getTopFrequentItems(unsortedFrequentItems); - const expectedResult = sortedFrequentItems.slice(0, FREQUENT_ITEMS.LIST_COUNT_DESKTOP); - - expect(result).toEqual(expectedResult); - }); - }); - - describe('updateExistingFrequentItem', () => { - const LAST_ACCESSED = 1497979281815; - const WITHIN_FIFTEEN_MINUTES = LAST_ACCESSED + FIFTEEN_MINUTES_IN_MS; - const OVER_FIFTEEN_MINUTES = WITHIN_FIFTEEN_MINUTES + 1; - const EXISTING_ITEM = Object.freeze({ - ...mockProject, - frequency: 1, - lastAccessedOn: 1497979281815, - }); - - it.each` - desc | existingProps | newProps | expected - ${'updates item if accessed over 15 minutes ago'} | ${{}} | ${{ lastAccessedOn: OVER_FIFTEEN_MINUTES }} | ${{ lastAccessedOn: Date.now(), frequency: 2 }} - ${'does not update is accessed with 15 minutes'} | ${{}} | ${{ lastAccessedOn: WITHIN_FIFTEEN_MINUTES }} | ${{ lastAccessedOn: EXISTING_ITEM.lastAccessedOn, frequency: 1 }} - ${'updates if lastAccessedOn not found'} | ${{ lastAccessedOn: undefined }} | ${{ lastAccessedOn: WITHIN_FIFTEEN_MINUTES }} | ${{ lastAccessedOn: Date.now(), frequency: 2 }} - `('$desc', ({ existingProps, newProps, expected }) => { - const newItem = { - ...EXISTING_ITEM, - ...newProps, - }; - const existingItem = { - ...EXISTING_ITEM, - ...existingProps, - }; - - const result = updateExistingFrequentItem(existingItem, newItem); - - expect(result).toEqual({ - ...newItem, - ...expected, - }); - }); - }); - - describe('sanitizeItem', () => { - it('strips HTML tags for name and namespace', () => { - const input = { - name: '<br><b>test</b>', - namespace: '<br>test', - id: 1, - }; - - expect(sanitizeItem(input)).toEqual({ name: 'test', namespace: 'test', id: 1 }); - }); - - it("skips `name` key if it doesn't exist on the item", () => { - const input = { - namespace: '<br>test', - id: 1, - }; - - expect(sanitizeItem(input)).toEqual({ namespace: 'test', id: 1 }); - }); - - it("skips `namespace` key if it doesn't exist on the item", () => { - const input = { - name: '<br><b>test</b>', - id: 1, - }; - - expect(sanitizeItem(input)).toEqual({ name: 'test', id: 1 }); - }); - }); -}); diff --git a/spec/frontend/import/details/components/bulk_import_details_app_spec.js b/spec/frontend/import/details/components/bulk_import_details_app_spec.js index d32afb7ddcb..18b03ed9802 100644 --- a/spec/frontend/import/details/components/bulk_import_details_app_spec.js +++ b/spec/frontend/import/details/components/bulk_import_details_app_spec.js @@ -1,18 +1,30 @@ import { shallowMount } from '@vue/test-utils'; +import { getParameterValues } from '~/lib/utils/url_utility'; + import BulkImportDetailsApp from '~/import/details/components/bulk_import_details_app.vue'; +jest.mock('~/lib/utils/url_utility'); + describe('Bulk import details app', () => { let wrapper; + const mockId = 151; + const createComponent = () => { wrapper = shallowMount(BulkImportDetailsApp); }; + beforeEach(() => { + getParameterValues.mockReturnValueOnce([mockId]); + }); + describe('template', () => { it('renders heading', () => { createComponent(); - expect(wrapper.find('h1').text()).toBe('GitLab Migration details'); + const headingText = wrapper.find('h1').text(); + + expect(headingText).toBe(`Items that failed to be imported for ${mockId}`); }); }); }); diff --git a/spec/frontend/import_entities/import_groups/utils_spec.js b/spec/frontend/import_entities/import_groups/utils_spec.js index 2892c5c217b..3db57170ed3 100644 --- a/spec/frontend/import_entities/import_groups/utils_spec.js +++ b/spec/frontend/import_entities/import_groups/utils_spec.js @@ -5,7 +5,7 @@ const FINISHED_STATUSES = [STATUSES.FINISHED, STATUSES.FAILED, STATUSES.TIMEOUT] const OTHER_STATUSES = Object.values(STATUSES).filter( (status) => !FINISHED_STATUSES.includes(status), ); -describe('gitlab migration status utils', () => { +describe('Direct transfer status utils', () => { describe('isFinished', () => { it.each(FINISHED_STATUSES.map((s) => [s]))( 'reports group as finished when import status is %s', diff --git a/spec/frontend/lib/utils/vuex_module_mappers_spec.js b/spec/frontend/lib/utils/vuex_module_mappers_spec.js deleted file mode 100644 index 9070903728b..00000000000 --- a/spec/frontend/lib/utils/vuex_module_mappers_spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import { - mapVuexModuleActions, - mapVuexModuleGetters, - mapVuexModuleState, - REQUIRE_STRING_ERROR_MESSAGE, -} from '~/lib/utils/vuex_module_mappers'; - -const TEST_MODULE_NAME = 'testModuleName'; - -Vue.use(Vuex); - -// setup test component and store ---------------------------------------------- -// -// These are used to indirectly test `vuex_module_mappers`. -const TestComponent = { - props: { - vuexModule: { - type: String, - required: true, - }, - }, - computed: { - ...mapVuexModuleState((vm) => vm.vuexModule, { name: 'name', value: 'count' }), - ...mapVuexModuleGetters((vm) => vm.vuexModule, ['hasValue', 'hasName']), - stateJson() { - return JSON.stringify({ - name: this.name, - value: this.value, - }); - }, - gettersJson() { - return JSON.stringify({ - hasValue: this.hasValue, - hasName: this.hasName, - }); - }, - }, - methods: { - ...mapVuexModuleActions((vm) => vm.vuexModule, ['increment']), - }, - template: ` -<div> - <pre data-testid="state">{{ stateJson }}</pre> - <pre data-testid="getters">{{ gettersJson }}</pre> -</div>`, -}; - -const createTestStore = () => { - return new Vuex.Store({ - modules: { - [TEST_MODULE_NAME]: { - namespaced: true, - state: { - name: 'Lorem', - count: 0, - }, - mutations: { - INCREMENT: (state, amount) => { - state.count += amount; - }, - }, - actions: { - increment({ commit }, amount) { - commit('INCREMENT', amount); - }, - }, - getters: { - hasValue: (state) => state.count > 0, - hasName: (state) => Boolean(state.name.length), - }, - }, - }, - }); -}; - -describe('~/lib/utils/vuex_module_mappers', () => { - let store; - let wrapper; - - const getJsonInTemplate = (testId) => - JSON.parse(wrapper.find(`[data-testid="${testId}"]`).text()); - const getMappedState = () => getJsonInTemplate('state'); - const getMappedGetters = () => getJsonInTemplate('getters'); - - beforeEach(() => { - store = createTestStore(); - - wrapper = mount(TestComponent, { - propsData: { - vuexModule: TEST_MODULE_NAME, - }, - store, - }); - }); - - describe('from module defined by prop', () => { - it('maps state', () => { - expect(getMappedState()).toEqual({ - name: store.state[TEST_MODULE_NAME].name, - value: store.state[TEST_MODULE_NAME].count, - }); - }); - - it('maps getters', () => { - expect(getMappedGetters()).toEqual({ - hasName: true, - hasValue: false, - }); - }); - - it('maps action', () => { - jest.spyOn(store, 'dispatch'); - - expect(store.dispatch).not.toHaveBeenCalled(); - - wrapper.vm.increment(10); - - expect(store.dispatch).toHaveBeenCalledWith(`${TEST_MODULE_NAME}/increment`, 10); - }); - }); - - describe('with non-string object value', () => { - it('throws helpful error', () => { - expect(() => mapVuexModuleActions((vm) => vm.bogus, { foo: () => {} })).toThrow( - REQUIRE_STRING_ERROR_MESSAGE, - ); - }); - }); -}); diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js index f6ad304f790..fca3a14d82f 100644 --- a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js @@ -84,7 +84,7 @@ describe('ServiceDeskRoot', () => { const alertBodyLink = alertEl.findComponent(GlLink); expect(alertBodyLink.exists()).toBe(true); expect(alertBodyLink.attributes('href')).toBe( - '/help/user/project/service_desk.html#use-an-additional-service-desk-alias-email', + '/help/user/project/service_desk/configure.html#use-an-additional-service-desk-alias-email', ); expect(alertBodyLink.text()).toBe('How do I create a custom email address?'); }); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index f5e7a7507ea..fc264ad5e0a 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -305,3 +305,32 @@ export const cachedFrequentGroups = JSON.stringify([ frequency: 3, }, ]); + +export const unsortedFrequentItems = [ + { id: 1, frequency: 12, lastAccessedOn: 1491400843391 }, + { id: 2, frequency: 14, lastAccessedOn: 1488240890738 }, + { id: 3, frequency: 44, lastAccessedOn: 1497675908472 }, + { id: 4, frequency: 8, lastAccessedOn: 1497979281815 }, + { id: 5, frequency: 34, lastAccessedOn: 1488089211943 }, + { id: 6, frequency: 14, lastAccessedOn: 1493517292488 }, + { id: 7, frequency: 42, lastAccessedOn: 1486815299875 }, + { id: 8, frequency: 33, lastAccessedOn: 1500762279114 }, + { id: 10, frequency: 46, lastAccessedOn: 1483251641543 }, +]; + +/** + * This const has a specific order which tests authenticity + * of `getTopFrequentItems` method so + * DO NOT change order of items in this const. + */ +export const sortedFrequentItems = [ + { id: 10, frequency: 46, lastAccessedOn: 1483251641543 }, + { id: 3, frequency: 44, lastAccessedOn: 1497675908472 }, + { id: 7, frequency: 42, lastAccessedOn: 1486815299875 }, + { id: 5, frequency: 34, lastAccessedOn: 1488089211943 }, + { id: 8, frequency: 33, lastAccessedOn: 1500762279114 }, + { id: 6, frequency: 14, lastAccessedOn: 1493517292488 }, + { id: 2, frequency: 14, lastAccessedOn: 1488240890738 }, + { id: 1, frequency: 12, lastAccessedOn: 1491400843391 }, + { id: 4, frequency: 8, lastAccessedOn: 1497979281815 }, +]; diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js index 3e99099b663..a9e4345f9cc 100644 --- a/spec/frontend/super_sidebar/utils_spec.js +++ b/spec/frontend/super_sidebar/utils_spec.js @@ -4,10 +4,10 @@ import { getTopFrequentItems, trackContextAccess, ariaCurrent } from '~/super_si import axios from '~/lib/utils/axios_utils'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import AccessorUtilities from '~/lib/utils/accessor'; -import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants'; +import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/super_sidebar/constants'; import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; import waitForPromises from 'helpers/wait_for_promises'; -import { unsortedFrequentItems, sortedFrequentItems } from '../frequent_items/mock_data'; +import { unsortedFrequentItems, sortedFrequentItems } from './mock_data'; jest.mock('~/sentry/sentry_browser_wrapper'); |