diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /spec/frontend/analytics | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) | |
download | gitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'spec/frontend/analytics')
-rw-r--r-- | spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js | 139 | ||||
-rw-r--r-- | spec/frontend/analytics/shared/utils_spec.js | 156 |
2 files changed, 278 insertions, 17 deletions
diff --git a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js index 2537b8fb816..5d681c7da4f 100644 --- a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js +++ b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js @@ -1,6 +1,8 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; import { TEST_HOST } from 'helpers/test_constants'; +import waitForPromises from 'helpers/wait_for_promises'; import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue'; import getProjects from '~/analytics/shared/graphql/projects.query.graphql'; @@ -25,6 +27,17 @@ const projects = [ }, ]; +const MockGlDropdown = stubComponent(GlDropdown, { + template: ` + <div> + <div data-testid="vsa-highlighted-items"> + <slot name="highlighted-items"></slot> + </div> + <div data-testid="vsa-default-items"><slot></slot></div> + </div> + `, +}); + const defaultMocks = { $apollo: { query: jest.fn().mockResolvedValue({ @@ -38,22 +51,33 @@ let spyQuery; describe('ProjectsDropdownFilter component', () => { let wrapper; - const createComponent = (props = {}) => { + const createComponent = (props = {}, stubs = {}) => { spyQuery = defaultMocks.$apollo.query; - wrapper = mount(ProjectsDropdownFilter, { + wrapper = mountExtended(ProjectsDropdownFilter, { mocks: { ...defaultMocks }, propsData: { groupId: 1, groupNamespace: 'gitlab-org', ...props, }, + stubs, }); }; + const createWithMockDropdown = (props) => { + createComponent(props, { GlDropdown: MockGlDropdown }); + return waitForPromises(); + }; + afterEach(() => { wrapper.destroy(); }); + const findHighlightedItems = () => wrapper.findByTestId('vsa-highlighted-items'); + const findUnhighlightedItems = () => wrapper.findByTestId('vsa-default-items'); + const findHighlightedItemsTitle = () => wrapper.findByText('Selected'); + const findClearAllButton = () => wrapper.findByText('Clear all'); + const findDropdown = () => wrapper.find(GlDropdown); const findDropdownItems = () => @@ -75,8 +99,19 @@ describe('ProjectsDropdownFilter component', () => { const findDropdownFullPathAtIndex = (index) => findDropdownAtIndex(index).find('[data-testid="project-full-path"]'); - const selectDropdownItemAtIndex = (index) => + const selectDropdownItemAtIndex = (index) => { findDropdownAtIndex(index).find('button').trigger('click'); + return wrapper.vm.$nextTick(); + }; + + // NOTE: Selected items are now visually separated from unselected items + const findSelectedDropdownItems = () => findHighlightedItems().findAll(GlDropdownItem); + + const findSelectedDropdownAtIndex = (index) => findSelectedDropdownItems().at(index); + const findSelectedButtonIdentIconAtIndex = (index) => + findSelectedDropdownAtIndex(index).find('div.gl-avatar-identicon'); + const findSelectedButtonAvatarItemAtIndex = (index) => + findSelectedDropdownAtIndex(index).find('img.gl-avatar'); const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id); @@ -109,7 +144,80 @@ describe('ProjectsDropdownFilter component', () => { }); }); - describe('when passed a an array of defaultProject as prop', () => { + describe('highlighted items', () => { + const blockDefaultProps = { multiSelect: true }; + beforeEach(() => { + createComponent(blockDefaultProps); + }); + + describe('with no project selected', () => { + it('does not render the highlighted items', async () => { + await createWithMockDropdown(blockDefaultProps); + expect(findSelectedDropdownItems().length).toBe(0); + }); + + it('does not render the highlighted items title', () => { + expect(findHighlightedItemsTitle().exists()).toBe(false); + }); + + it('does not render the clear all button', () => { + expect(findClearAllButton().exists()).toBe(false); + }); + }); + + describe('with a selected project', () => { + beforeEach(async () => { + await selectDropdownItemAtIndex(0); + }); + + it('renders the highlighted items', async () => { + await createWithMockDropdown(blockDefaultProps); + await selectDropdownItemAtIndex(0); + + expect(findSelectedDropdownItems().length).toBe(1); + }); + + it('renders the highlighted items title', () => { + expect(findHighlightedItemsTitle().exists()).toBe(true); + }); + + it('renders the clear all button', () => { + expect(findClearAllButton().exists()).toBe(true); + }); + + it('clears all selected items when the clear all button is clicked', async () => { + await selectDropdownItemAtIndex(1); + + expect(wrapper.text()).toContain('2 projects selected'); + + findClearAllButton().trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.text()).not.toContain('2 projects selected'); + expect(wrapper.text()).toContain('Select projects'); + }); + }); + }); + + describe('with a selected project and search term', () => { + beforeEach(async () => { + await createWithMockDropdown({ multiSelect: true }); + + selectDropdownItemAtIndex(0); + wrapper.setData({ searchTerm: 'this is a very long search string' }); + }); + + it('renders the highlighted items', async () => { + expect(findUnhighlightedItems().findAll('li').length).toBe(1); + }); + + it('hides the unhighlighted items that do not match the string', async () => { + expect(findUnhighlightedItems().findAll('li').length).toBe(1); + expect(findUnhighlightedItems().text()).toContain('No matching results'); + }); + }); + + describe('when passed an array of defaultProject as prop', () => { beforeEach(() => { createComponent({ defaultProjects: [projects[0]], @@ -130,8 +238,9 @@ describe('ProjectsDropdownFilter component', () => { }); describe('when multiSelect is false', () => { + const blockDefaultProps = { multiSelect: false }; beforeEach(() => { - createComponent({ multiSelect: false }); + createComponent(blockDefaultProps); }); describe('displays the correct information', () => { @@ -183,21 +292,19 @@ describe('ProjectsDropdownFilter component', () => { }); it('renders an avatar in the dropdown button when the project has an avatarUrl', async () => { - selectDropdownItemAtIndex(0); + await createWithMockDropdown(blockDefaultProps); + await selectDropdownItemAtIndex(0); - await wrapper.vm.$nextTick().then(() => { - expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true); - expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false); - }); + expect(findSelectedButtonAvatarItemAtIndex(0).exists()).toBe(true); + expect(findSelectedButtonIdentIconAtIndex(0).exists()).toBe(false); }); it("renders an identicon in the dropdown button when the project doesn't have an avatarUrl", async () => { - selectDropdownItemAtIndex(1); + await createWithMockDropdown(blockDefaultProps); + await selectDropdownItemAtIndex(1); - await wrapper.vm.$nextTick().then(() => { - expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false); - expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true); - }); + expect(findSelectedButtonAvatarItemAtIndex(0).exists()).toBe(false); + expect(findSelectedButtonIdentIconAtIndex(0).exists()).toBe(true); }); }); }); diff --git a/spec/frontend/analytics/shared/utils_spec.js b/spec/frontend/analytics/shared/utils_spec.js index e3293f2d8bd..0513ccb2890 100644 --- a/spec/frontend/analytics/shared/utils_spec.js +++ b/spec/frontend/analytics/shared/utils_spec.js @@ -1,4 +1,10 @@ -import { filterBySearchTerm } from '~/analytics/shared/utils'; +import { + filterBySearchTerm, + extractFilterQueryParameters, + extractPaginationQueryParameters, + getDataZoomOption, +} from '~/analytics/shared/utils'; +import { objectToQuery } from '~/lib/utils/url_utility'; describe('filterBySearchTerm', () => { const data = [ @@ -22,3 +28,151 @@ describe('filterBySearchTerm', () => { expect(filterBySearchTerm(data, 'ne', 'title')).toEqual([data[0]]); }); }); + +describe('extractFilterQueryParameters', () => { + const selectedAuthor = 'Author 1'; + const selectedMilestone = 'Milestone 1.0'; + const selectedSourceBranch = 'main'; + const selectedTargetBranch = 'feature-1'; + const selectedAssigneeList = ['Alice', 'Bob']; + const selectedLabelList = ['Label 1', 'Label 2']; + + const queryParamsString = objectToQuery({ + source_branch_name: selectedSourceBranch, + target_branch_name: selectedTargetBranch, + author_username: selectedAuthor, + milestone_title: selectedMilestone, + assignee_username: selectedAssigneeList, + label_name: selectedLabelList, + }); + + it('extracts the correct filter parameters from a url', () => { + const result = extractFilterQueryParameters(queryParamsString); + const operator = '='; + const expectedFilters = { + selectedAssigneeList: { operator, value: selectedAssigneeList.join(',') }, + selectedLabelList: { operator, value: selectedLabelList.join(',') }, + selectedAuthor: { operator, value: selectedAuthor }, + selectedMilestone: { operator, value: selectedMilestone }, + selectedSourceBranch: { operator, value: selectedSourceBranch }, + selectedTargetBranch: { operator, value: selectedTargetBranch }, + }; + expect(result).toMatchObject(expectedFilters); + }); + + it('returns null for missing parameters', () => { + const result = extractFilterQueryParameters(''); + const expectedFilters = { + selectedAuthor: null, + selectedMilestone: null, + selectedSourceBranch: null, + selectedTargetBranch: null, + }; + expect(result).toMatchObject(expectedFilters); + }); + + it('only returns the parameters we expect', () => { + const result = extractFilterQueryParameters('foo="one"&bar="two"'); + const resultKeys = Object.keys(result); + ['foo', 'bar'].forEach((key) => { + expect(resultKeys).not.toContain(key); + }); + + [ + 'selectedAuthor', + 'selectedMilestone', + 'selectedSourceBranch', + 'selectedTargetBranch', + 'selectedAssigneeList', + 'selectedLabelList', + ].forEach((key) => { + expect(resultKeys).toContain(key); + }); + }); + + it('returns an empty array for missing list parameters', () => { + const result = extractFilterQueryParameters(''); + const expectedFilters = { selectedAssigneeList: [], selectedLabelList: [] }; + expect(result).toMatchObject(expectedFilters); + }); +}); + +describe('extractPaginationQueryParameters', () => { + const sort = 'title'; + const direction = 'asc'; + const page = '1'; + const queryParamsString = objectToQuery({ sort, direction, page }); + + it('extracts the correct filter parameters from a url', () => { + const result = extractPaginationQueryParameters(queryParamsString); + const expectedFilters = { sort, page, direction }; + expect(result).toMatchObject(expectedFilters); + }); + + it('returns null for missing parameters', () => { + const result = extractPaginationQueryParameters(''); + const expectedFilters = { sort: null, direction: null, page: null }; + expect(result).toMatchObject(expectedFilters); + }); + + it('only returns the parameters we expect', () => { + const result = extractPaginationQueryParameters('foo="one"&bar="two"&qux="three"'); + const resultKeys = Object.keys(result); + ['foo', 'bar', 'qux'].forEach((key) => { + expect(resultKeys).not.toContain(key); + }); + + ['sort', 'page', 'direction'].forEach((key) => { + expect(resultKeys).toContain(key); + }); + }); +}); + +describe('getDataZoomOption', () => { + it('returns an empty object when totalItems <= maxItemsPerPage', () => { + const totalItems = 10; + const maxItemsPerPage = 20; + + expect(getDataZoomOption({ totalItems, maxItemsPerPage })).toEqual({}); + }); + + describe('when totalItems > maxItemsPerPage', () => { + const totalItems = 30; + const maxItemsPerPage = 20; + + it('properly computes the end interval for the default datazoom config', () => { + const expected = [ + { + type: 'slider', + bottom: 10, + start: 0, + end: 67, + }, + ]; + + expect(getDataZoomOption({ totalItems, maxItemsPerPage })).toEqual(expected); + }); + + it('properly computes the end interval for a custom datazoom config', () => { + const dataZoom = [ + { type: 'slider', bottom: 0, start: 0 }, + { type: 'inside', start: 0 }, + ]; + const expected = [ + { + type: 'slider', + bottom: 0, + start: 0, + end: 67, + }, + { + type: 'inside', + start: 0, + end: 67, + }, + ]; + + expect(getDataZoomOption({ totalItems, maxItemsPerPage, dataZoom })).toEqual(expected); + }); + }); +}); |