diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 16:18:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 16:18:24 +0300 |
commit | 0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch) | |
tree | 4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /spec/frontend/vue_shared | |
parent | 744144d28e3e7fddc117924fef88de5d9674fe4c (diff) |
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'spec/frontend/vue_shared')
32 files changed, 680 insertions, 847 deletions
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap index bab928318ce..c7758b0faef 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap @@ -3,9 +3,13 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = ` <gl-dropdown-stub category="primary" + clearalltext="Clear all" headertext="" hideheaderborder="true" + highlighteditemstitle="Selected" + highlighteditemstitleclass="gl-px-5" right="true" + showhighlighteditemstitle="true" size="medium" text="Clone" variant="info" diff --git a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap index db174346729..7f655d67ae8 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap @@ -2,7 +2,7 @@ exports[`Code Block with default props renders correctly 1`] = ` <pre - class="code-block rounded" + class="code-block rounded code" > <code class="d-block" @@ -14,7 +14,7 @@ exports[`Code Block with default props renders correctly 1`] = ` exports[`Code Block with maxHeight set to "200px" renders correctly 1`] = ` <pre - class="code-block rounded" + class="code-block rounded code" style="max-height: 200px; overflow-y: auto;" > <code diff --git a/spec/frontend/vue_shared/components/__snapshots__/memory_graph_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/memory_graph_spec.js.snap index f4f9cc288f9..87eaabf4e98 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/memory_graph_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/memory_graph_spec.js.snap @@ -9,7 +9,6 @@ exports[`MemoryGraph Render chart should draw container with chart 1`] = ` data="Nov 12 2019 19:17:33,2.87,Nov 12 2019 19:18:33,2.78,Nov 12 2019 19:19:33,2.78,Nov 12 2019 19:20:33,3.01" height="25" tooltiplabel="MB" - variant="gray900" /> </div> `; diff --git a/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap index c4f351eb58d..f2ff12b2acd 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap @@ -3,9 +3,13 @@ exports[`SplitButton renders actionItems 1`] = ` <gl-dropdown-stub category="primary" + clearalltext="Clear all" headertext="" hideheaderborder="true" + highlighteditemstitle="Selected" + highlighteditemstitleclass="gl-px-5" menu-class="" + showhighlighteditemstitle="true" size="medium" split="true" text="professor" diff --git a/spec/frontend/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js index 6a31742141b..d91853e7b79 100644 --- a/spec/frontend/vue_shared/components/commit_spec.js +++ b/spec/frontend/vue_shared/components/commit_spec.js @@ -162,8 +162,6 @@ describe('Commit component', () => { expect(refEl.attributes('href')).toBe(props.commitRef.ref_url); - expect(refEl.attributes('title')).toBe(props.commitRef.name); - expect(findIcon('branch').exists()).toBe(true); }); }); @@ -195,8 +193,6 @@ describe('Commit component', () => { expect(refEl.attributes('href')).toBe(props.mergeRequestRef.path); - expect(refEl.attributes('title')).toBe(props.mergeRequestRef.title); - expect(findIcon('git-merge').exists()).toBe(true); }); }); diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js new file mode 100644 index 00000000000..04f63b4bd45 --- /dev/null +++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js @@ -0,0 +1,176 @@ +import { + GlSprintf, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, +} from '@gitlab/ui'; +import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import DiffStatsDropdown, { i18n } from '~/vue_shared/components/diff_stats_dropdown.vue'; + +jest.mock('fuzzaldrin-plus', () => ({ + filter: jest.fn().mockReturnValue([]), +})); + +const mockFiles = [ + { + added: 0, + href: '#a5cc2925ca8258af241be7e5b0381edf30266302', + icon: 'file-modified', + iconColor: '', + name: '', + path: '.gitignore', + removed: 3, + title: '.gitignore', + }, + { + added: 1, + href: '#fa288d1472d29beccb489a676f68739ad365fc47', + icon: 'file-modified', + iconColor: 'danger', + name: 'package-lock.json', + path: 'lock/file/path', + removed: 1, + }, +]; + +describe('Diff Stats Dropdown', () => { + let wrapper; + + const createComponent = ({ changed = 0, added = 0, deleted = 0, files = [] } = {}) => { + wrapper = shallowMountExtended(DiffStatsDropdown, { + propsData: { + changed, + added, + deleted, + files, + }, + stubs: { + GlSprintf, + GlDropdown, + }, + }); + }; + + const findChanged = () => wrapper.findComponent(GlDropdown); + const findChangedFiles = () => findChanged().findAllComponents(GlDropdownItem); + const findNoFilesText = () => findChanged().findComponent(GlDropdownText); + const findCollapsed = () => wrapper.findByTestId('diff-stats-additions-deletions-expanded'); + const findExpanded = () => wrapper.findByTestId('diff-stats-additions-deletions-collapsed'); + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + + describe('file item', () => { + beforeEach(() => { + createComponent({ files: mockFiles }); + }); + + it('when no file name provided ', () => { + expect(findChangedFiles().at(0).text()).toContain(i18n.noFileNameAvailable); + }); + + it('when all file data is available', () => { + const fileData = findChangedFiles().at(1); + const fileText = findChangedFiles().at(1).text(); + expect(fileText).toContain(mockFiles[1].name); + expect(fileText).toContain(mockFiles[1].path); + expect(fileData.props()).toMatchObject({ + iconName: mockFiles[1].icon, + iconColor: mockFiles[1].iconColor, + }); + }); + + it('when no files changed', () => { + createComponent({ files: [] }); + expect(findNoFilesText().text()).toContain(i18n.noFilesFound); + }); + }); + + describe.each` + changed | added | deleted | expectedDropdownHeader | expectedAddedDeletedExpanded | expectedAddedDeletedCollapsed + ${0} | ${0} | ${0} | ${'0 changed files'} | ${'+0 -0'} | ${'with 0 additions and 0 deletions'} + ${2} | ${0} | ${2} | ${'2 changed files'} | ${'+0 -2'} | ${'with 0 additions and 2 deletions'} + ${2} | ${2} | ${0} | ${'2 changed files'} | ${'+2 -0'} | ${'with 2 additions and 0 deletions'} + ${2} | ${1} | ${1} | ${'2 changed files'} | ${'+1 -1'} | ${'with 1 addition and 1 deletion'} + ${1} | ${0} | ${1} | ${'1 changed file'} | ${'+0 -1'} | ${'with 0 additions and 1 deletion'} + ${1} | ${1} | ${0} | ${'1 changed file'} | ${'+1 -0'} | ${'with 1 addition and 0 deletions'} + ${4} | ${2} | ${2} | ${'4 changed files'} | ${'+2 -2'} | ${'with 2 additions and 2 deletions'} + `( + 'when there are $changed changed file(s), $added added and $deleted deleted file(s)', + ({ + changed, + added, + deleted, + expectedDropdownHeader, + expectedAddedDeletedExpanded, + expectedAddedDeletedCollapsed, + }) => { + beforeAll(() => { + createComponent({ changed, added, deleted }); + }); + + afterAll(() => { + wrapper.destroy(); + }); + + it(`dropdown header should be '${expectedDropdownHeader}'`, () => { + expect(findChanged().props('text')).toBe(expectedDropdownHeader); + }); + + it(`added and deleted count in expanded section should be '${expectedAddedDeletedExpanded}'`, () => { + expect(findExpanded().text()).toBe(expectedAddedDeletedExpanded); + }); + + it(`added and deleted count in collapsed section should be '${expectedAddedDeletedCollapsed}'`, () => { + expect(findCollapsed().text()).toBe(expectedAddedDeletedCollapsed); + }); + }, + ); + + describe('fuzzy file search', () => { + beforeEach(() => { + createComponent({ files: mockFiles }); + }); + + it('should call `fuzzaldrinPlus.filter` to search for files when the search query is NOT empty', async () => { + const searchStr = 'file name'; + findSearchBox().vm.$emit('input', searchStr); + await nextTick(); + expect(fuzzaldrinPlus.filter).toHaveBeenCalledWith(mockFiles, searchStr, { key: 'name' }); + }); + + it('should NOT call `fuzzaldrinPlus.filter` to search for files when the search query is empty', async () => { + const searchStr = ''; + findSearchBox().vm.$emit('input', searchStr); + await nextTick(); + expect(fuzzaldrinPlus.filter).not.toHaveBeenCalled(); + }); + }); + + describe('selecting file dropdown item', () => { + beforeEach(() => { + createComponent({ files: mockFiles }); + }); + + it('updates the URL ', () => { + findChangedFiles().at(0).vm.$emit('click'); + expect(window.location.hash).toBe(mockFiles[0].href); + findChangedFiles().at(1).vm.$emit('click'); + expect(window.location.hash).toBe(mockFiles[1].href); + }); + }); + + describe('on dropdown open', () => { + beforeEach(() => { + createComponent(); + }); + + it('should set the search input focus', () => { + wrapper.vm.$refs.search.focusInput = jest.fn(); + findChanged().vm.$emit('shown'); + + expect(wrapper.vm.$refs.search.focusInput).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js index 1b97011bf7f..d85b6e6d115 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js @@ -25,7 +25,7 @@ import { const mockStorageKey = 'recent-tokens'; function setLocalStorageAvailability(isAvailable) { - jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(isAvailable); + jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(isAvailable); } describe('Filtered Search Utils', () => { @@ -309,7 +309,7 @@ describe('urlQueryToFilter', () => { { [FILTERED_SEARCH_TERM]: [{ value: 'my' }, { value: 'terms' }], }, - { filteredSearchTermKey: 'search', legacySpacesDecode: false }, + { filteredSearchTermKey: 'search' }, ], [ 'search=my terms&foo=bar&nop=xxx', diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js index 529844817d3..bfb593bf82d 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js @@ -11,7 +11,10 @@ import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { sortMilestonesByDueDate } from '~/milestones/milestone_utils'; -import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants'; +import { + DEFAULT_MILESTONES, + DEFAULT_MILESTONES_GRAPHQL, +} from '~/vue_shared/components/filtered_search_bar/constants'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import { mockMilestoneToken, mockMilestones, mockRegularMilestone } from '../mock_data'; @@ -191,5 +194,22 @@ describe('MilestoneToken', () => { expect(suggestions.at(index).text()).toBe(milestone.text); }); }); + + describe('when getActiveMilestones is called and milestones is empty', () => { + beforeEach(() => { + wrapper = createComponent({ + active: true, + config: { ...mockMilestoneToken, defaultMilestones: DEFAULT_MILESTONES_GRAPHQL }, + }); + }); + + it('finds the correct value from the activeToken', () => { + DEFAULT_MILESTONES_GRAPHQL.forEach(({ value, title }) => { + const activeToken = wrapper.vm.getActiveMilestone([], value); + + expect(activeToken.title).toEqual(title); + }); + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/header_ci_component_spec.js b/spec/frontend/vue_shared/components/header_ci_component_spec.js index b54d120b55b..42f4439df51 100644 --- a/spec/frontend/vue_shared/components/header_ci_component_spec.js +++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js @@ -16,8 +16,6 @@ describe('Header CI Component', () => { text: 'failed', details_path: 'path', }, - itemName: 'job', - itemId: 123, time: '2017-05-08T14:57:39.781Z', user: { web_url: 'path', @@ -55,17 +53,13 @@ describe('Header CI Component', () => { describe('render', () => { beforeEach(() => { - createComponent(); + createComponent({ itemName: 'Pipeline' }); }); it('should render status badge', () => { expect(findIconBadge().exists()).toBe(true); }); - it('should render item name and id', () => { - expect(findHeaderItemText().text()).toBe('job #123'); - }); - it('should render timeago date', () => { expect(findTimeAgo().exists()).toBe(true); }); @@ -83,9 +77,29 @@ describe('Header CI Component', () => { }); }); + describe('with item id', () => { + beforeEach(() => { + createComponent({ itemName: 'Pipeline', itemId: '123' }); + }); + + it('should render item name and id', () => { + expect(findHeaderItemText().text()).toBe('Pipeline #123'); + }); + }); + + describe('without item id', () => { + beforeEach(() => { + createComponent({ itemName: 'Job build_job' }); + }); + + it('should render item name', () => { + expect(findHeaderItemText().text()).toBe('Job build_job'); + }); + }); + describe('slot', () => { it('should render header action buttons', () => { - createComponent({}, { slots: { default: 'Test Actions' } }); + createComponent({ itemName: 'Job build_job' }, { slots: { default: 'Test Actions' } }); expect(findActionButtons().exists()).toBe(true); expect(findActionButtons().text()).toBe('Test Actions'); @@ -94,7 +108,7 @@ describe('Header CI Component', () => { describe('shouldRenderTriggeredLabel', () => { it('should render created keyword when the shouldRenderTriggeredLabel is false', () => { - createComponent({ shouldRenderTriggeredLabel: false }); + createComponent({ shouldRenderTriggeredLabel: false, itemName: 'Job build_job' }); expect(wrapper.text()).toContain('created'); expect(wrapper.text()).not.toContain('triggered'); diff --git a/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js b/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js index 573501233b9..ad8331afcff 100644 --- a/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js +++ b/spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js @@ -1,5 +1,7 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createStore as createMrStore } from '~/mr_notes/stores'; import createIssueStore from '~/notes/stores'; import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue'; @@ -12,52 +14,53 @@ localVue.use(Vuex); describe('IssuableHeaderWarnings', () => { let wrapper; - let store; - const findConfidentialIcon = () => wrapper.find('[data-testid="confidential"]'); - const findLockedIcon = () => wrapper.find('[data-testid="locked"]'); + const findConfidentialIcon = () => wrapper.findByTestId('confidential'); + const findLockedIcon = () => wrapper.findByTestId('locked'); + const findHiddenIcon = () => wrapper.findByTestId('hidden'); const renderTestMessage = (renders) => (renders ? 'renders' : 'does not render'); - const setLock = (locked) => { - store.getters.getNoteableData.discussion_locked = locked; - }; - - const setConfidential = (confidential) => { - store.getters.getNoteableData.confidential = confidential; - }; - - const createComponent = () => { - wrapper = shallowMount(IssuableHeaderWarnings, { store, localVue }); + const createComponent = ({ store, provide }) => { + wrapper = shallowMountExtended(IssuableHeaderWarnings, { + store, + localVue, + provide, + directives: { + GlTooltip: createMockDirective(), + }, + }); }; afterEach(() => { wrapper.destroy(); wrapper = null; - store = null; }); describe.each` issuableType ${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR} `(`when issuableType=$issuableType`, ({ issuableType }) => { - beforeEach(() => { - store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : createMrStore(); - createComponent(); - }); - describe.each` - lockStatus | confidentialStatus - ${true} | ${true} - ${true} | ${false} - ${false} | ${true} - ${false} | ${false} + lockStatus | confidentialStatus | hiddenStatus + ${true} | ${true} | ${false} + ${true} | ${false} | ${false} + ${false} | ${true} | ${false} + ${false} | ${false} | ${false} + ${true} | ${true} | ${true} + ${true} | ${false} | ${true} + ${false} | ${true} | ${true} + ${false} | ${false} | ${true} `( - `when locked=$lockStatus and confidential=$confidentialStatus`, - ({ lockStatus, confidentialStatus }) => { + `when locked=$lockStatus, confidential=$confidentialStatus, and hidden=$hiddenStatus`, + ({ lockStatus, confidentialStatus, hiddenStatus }) => { + const store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : createMrStore(); + beforeEach(() => { - setLock(lockStatus); - setConfidential(confidentialStatus); + store.getters.getNoteableData.confidential = confidentialStatus; + store.getters.getNoteableData.discussion_locked = lockStatus; + + createComponent({ store, provide: { hidden: hiddenStatus } }); }); it(`${renderTestMessage(lockStatus)} the locked icon`, () => { @@ -67,6 +70,19 @@ describe('IssuableHeaderWarnings', () => { it(`${renderTestMessage(confidentialStatus)} the confidential icon`, () => { expect(findConfidentialIcon().exists()).toBe(confidentialStatus); }); + + it(`${renderTestMessage(confidentialStatus)} the hidden icon`, () => { + const hiddenIcon = findHiddenIcon(); + + expect(hiddenIcon.exists()).toBe(hiddenStatus); + + if (hiddenStatus) { + expect(hiddenIcon.attributes('title')).toBe( + 'This issue is hidden because its author has been banned', + ); + expect(getBinding(hiddenIcon.element, 'gl-tooltip')).not.toBeUndefined(); + } + }); }, ); }); diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js index 442032840e1..76e1a1162ad 100644 --- a/spec/frontend/vue_shared/components/markdown/field_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_spec.js @@ -32,7 +32,7 @@ describe('Markdown field component', () => { axiosMock.restore(); }); - function createSubject() { + function createSubject(lines = []) { // We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression // caused by mixing Vanilla JS and Vue. subject = mount( @@ -60,6 +60,7 @@ describe('Markdown field component', () => { markdownPreviewPath, isSubmitting: false, textareaValue, + lines, }, }, ); @@ -243,4 +244,14 @@ describe('Markdown field component', () => { }); }); }); + + describe('suggestions', () => { + it('escapes new line characters', () => { + createSubject([{ rich_text: 'hello world\\n' }]); + + expect(subject.find('[data-testid="markdownHeader"]').props('lineContent')).toBe( + 'hello world%br', + ); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/registry/title_area_spec.js b/spec/frontend/vue_shared/components/registry/title_area_spec.js index fb0009ebb8d..75aa3bc7096 100644 --- a/spec/frontend/vue_shared/components/registry/title_area_spec.js +++ b/spec/frontend/vue_shared/components/registry/title_area_spec.js @@ -135,15 +135,16 @@ describe('title area', () => { }, }); }; + it('shows dynamic slots', async () => { mountComponent(); // we manually add a new slot to simulate dynamic slots being evaluated after the initial mount wrapper.vm.$slots[DYNAMIC_SLOT] = createDynamicSlot(); + // updating the slots like we do on line 141 does not cause the updated lifecycle-hook to be triggered + wrapper.vm.$forceUpdate(); await wrapper.vm.$nextTick(); - expect(findDynamicSlot().exists()).toBe(false); - await wrapper.vm.$nextTick(); expect(findDynamicSlot().exists()).toBe(true); }); @@ -160,10 +161,8 @@ describe('title area', () => { 'metadata-foo': wrapper.vm.$slots['metadata-foo'], }; - await wrapper.vm.$nextTick(); - expect(findDynamicSlot().exists()).toBe(false); - expect(findMetadataSlot('metadata-foo').exists()).toBe(true); - + // updating the slots like we do on line 159 does not cause the updated lifecycle-hook to be triggered + wrapper.vm.$forceUpdate(); await wrapper.vm.$nextTick(); expect(findSlotOrderElements().at(0).attributes('data-testid')).toBe(DYNAMIC_SLOT); diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js index 69db3ec7132..ad692a38e65 100644 --- a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js +++ b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js @@ -21,6 +21,7 @@ describe('RunnerAwsDeploymentsModal', () => { wrapper = shallowMount(RunnerAwsDeploymentsModal, { propsData: { modalId: 'runner-aws-deployments-modal', + imgSrc: '/assets/aws-cloud-formation.png', }, }); }; diff --git a/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap b/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap index ed085fb66dc..165caea2751 100644 --- a/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap +++ b/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap @@ -8,12 +8,25 @@ exports[`Settings Block renders the correct markup 1`] = ` class="settings-header" > <h4> - <div - data-testid="title-slot" - /> + <span + aria-controls="settings_content_3" + aria-expanded="false" + class="gl-cursor-pointer" + data-testid="section-title-button" + id="settings_label_2" + role="button" + tabindex="0" + > + <div + data-testid="title-slot" + /> + </span> </h4> <gl-button-stub + aria-controls="settings_content_3" + aria-expanded="false" + aria-label="Expand settings section" buttontextclasses="" category="primary" icon="" @@ -33,7 +46,11 @@ exports[`Settings Block renders the correct markup 1`] = ` </div> <div + aria-labelledby="settings_label_2" class="settings-content" + id="settings_content_3" + role="region" + tabindex="-1" > <div data-testid="default-slot" diff --git a/spec/frontend/vue_shared/components/settings/settings_block_spec.js b/spec/frontend/vue_shared/components/settings/settings_block_spec.js index be5a15631eb..528dfd89690 100644 --- a/spec/frontend/vue_shared/components/settings/settings_block_spec.js +++ b/spec/frontend/vue_shared/components/settings/settings_block_spec.js @@ -1,12 +1,12 @@ import { GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import component from '~/vue_shared/components/settings/settings_block.vue'; +import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; describe('Settings Block', () => { let wrapper; const mountComponent = (propsData) => { - wrapper = shallowMount(component, { + wrapper = shallowMount(SettingsBlock, { propsData, slots: { title: '<div data-testid="title-slot"></div>', @@ -18,13 +18,25 @@ describe('Settings Block', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]'); const findTitleSlot = () => wrapper.find('[data-testid="title-slot"]'); const findDescriptionSlot = () => wrapper.find('[data-testid="description-slot"]'); - const findExpandButton = () => wrapper.find(GlButton); + const findExpandButton = () => wrapper.findComponent(GlButton); + const findSectionTitleButton = () => wrapper.find('[data-testid="section-title-button"]'); + + const expectExpandedState = ({ expanded = true } = {}) => { + const settingsExpandButton = findExpandButton(); + + expect(wrapper.classes('expanded')).toBe(expanded); + expect(settingsExpandButton.text()).toBe( + expanded ? SettingsBlock.i18n.collapseText : SettingsBlock.i18n.expandText, + ); + expect(settingsExpandButton.attributes('aria-label')).toBe( + expanded ? SettingsBlock.i18n.collapseAriaLabel : SettingsBlock.i18n.expandAriaLabel, + ); + }; it('renders the correct markup', () => { mountComponent(); @@ -75,33 +87,41 @@ describe('Settings Block', () => { it('is collapsed by default', () => { mountComponent(); - expect(wrapper.classes('expanded')).toBe(false); + expectExpandedState({ expanded: false }); }); it('adds expanded class when the expand button is clicked', async () => { mountComponent(); - expect(wrapper.classes('expanded')).toBe(false); - expect(findExpandButton().text()).toBe('Expand'); - await findExpandButton().vm.$emit('click'); - expect(wrapper.classes('expanded')).toBe(true); - expect(findExpandButton().text()).toBe('Collapse'); + expectExpandedState({ expanded: true }); }); - it('is expanded when `defaultExpanded` is true no matter what', async () => { - mountComponent({ defaultExpanded: true }); + it('adds expanded class when the section title is clicked', async () => { + mountComponent(); - expect(wrapper.classes('expanded')).toBe(true); + await findSectionTitleButton().trigger('click'); - await findExpandButton().vm.$emit('click'); + expectExpandedState({ expanded: true }); + }); - expect(wrapper.classes('expanded')).toBe(true); + describe('when `collapsible` is `false`', () => { + beforeEach(() => { + mountComponent({ collapsible: false }); + }); - await findExpandButton().vm.$emit('click'); + it('does not render clickable section title', () => { + expect(findSectionTitleButton().exists()).toBe(false); + }); + + it('contains expanded class', () => { + expect(wrapper.classes('expanded')).toBe(true); + }); - expect(wrapper.classes('expanded')).toBe(true); + it('does not render expand toggle button', () => { + expect(findExpandButton().exists()).toBe(false); + }); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js index a1942e59571..e39e8794fdd 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js @@ -124,7 +124,7 @@ describe('DropdownContentsLabelsView', () => { }); it('returns false when provided `label` param is not one of the selected labels', () => { - expect(wrapper.vm.isLabelSelected(mockLabels[2])).toBe(false); + expect(wrapper.vm.isLabelSelected(mockLabels[1])).toBe(false); }); }); @@ -203,7 +203,7 @@ describe('DropdownContentsLabelsView', () => { it('calls action `updateSelectedLabels` with currently highlighted label when Enter key is pressed', () => { jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation(); wrapper.setData({ - currentHighlightItem: 1, + currentHighlightItem: 2, }); wrapper.vm.handleKeyDown({ @@ -213,7 +213,7 @@ describe('DropdownContentsLabelsView', () => { expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([ { - ...mockLabels[1], + ...mockLabels[2], set: true, }, ]); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js index c90e63313b2..960ea77cb6e 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_spec.js @@ -6,7 +6,7 @@ import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dro import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; -import { mockConfig, mockRegularLabel, mockScopedLabel } from './mock_data'; +import { mockConfig, mockLabels, mockRegularLabel, mockScopedLabel } from './mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -14,6 +14,9 @@ localVue.use(Vuex); describe('DropdownValue', () => { let wrapper; + const findAllLabels = () => wrapper.findAllComponents(GlLabel); + const findLabel = (index) => findAllLabels().at(index).props('title'); + const createComponent = (initialState = {}, slots = {}) => { const store = new Vuex.Store(labelsSelectModule()); @@ -28,7 +31,6 @@ describe('DropdownValue', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); describe('methods', () => { @@ -82,7 +84,17 @@ describe('DropdownValue', () => { it('renders labels when `selectedLabels` is not empty', () => { createComponent(); - expect(wrapper.findAll(GlLabel).length).toBe(2); + expect(findAllLabels()).toHaveLength(2); + }); + + it('orders scoped labels first', () => { + createComponent({ selectedLabels: mockLabels }); + + expect(findAllLabels()).toHaveLength(mockLabels.length); + expect(findLabel(0)).toBe('Foo::Bar'); + expect(findLabel(1)).toBe('Boog'); + expect(findLabel(2)).toBe('Bug'); + expect(findLabel(3)).toBe('Foo Label'); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js index 730afcbecab..1faa3b0af1d 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/mock_data.js @@ -15,22 +15,22 @@ export const mockScopedLabel = { }; export const mockLabels = [ - mockRegularLabel, - mockScopedLabel, { - id: 28, - title: 'Bug', + id: 29, + title: 'Boog', description: 'Label for bugs', color: '#FF0000', textColor: '#FFFFFF', }, { - id: 29, - title: 'Boog', + id: 28, + title: 'Bug', description: 'Label for bugs', color: '#FF0000', textColor: '#FFFFFF', }, + mockRegularLabel, + mockScopedLabel, ]; export const mockCollapsedLabels = [ diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_button_spec.js deleted file mode 100644 index 0a42d389b67..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_button_spec.js +++ /dev/null @@ -1,91 +0,0 @@ -import { GlIcon, GlButton } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; - -import DropdownButton from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_button.vue'; - -import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_widget/store'; - -import { mockConfig } from './mock_data'; - -let store; -const localVue = createLocalVue(); -localVue.use(Vuex); - -const createComponent = (initialState = mockConfig) => { - store = new Vuex.Store(labelSelectModule()); - - store.dispatch('setInitialState', initialState); - - return shallowMount(DropdownButton, { - localVue, - store, - }); -}; - -describe('DropdownButton', () => { - let wrapper; - - beforeEach(() => { - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const findDropdownButton = () => wrapper.find(GlButton); - const findDropdownText = () => wrapper.find('.dropdown-toggle-text'); - const findDropdownIcon = () => wrapper.find(GlIcon); - - describe('methods', () => { - describe('handleButtonClick', () => { - it.each` - variant | expectPropagationStopped - ${'standalone'} | ${true} - ${'embedded'} | ${false} - `( - 'toggles dropdown content and handles event propagation when `state.variant` is "$variant"', - ({ variant, expectPropagationStopped }) => { - const event = { stopPropagation: jest.fn() }; - - wrapper = createComponent({ ...mockConfig, variant }); - - findDropdownButton().vm.$emit('click', event); - - expect(store.state.showDropdownContents).toBe(true); - expect(event.stopPropagation).toHaveBeenCalledTimes(expectPropagationStopped ? 1 : 0); - }, - ); - }); - }); - - describe('template', () => { - it('renders component container element', () => { - expect(wrapper.find(GlButton).element).toBe(wrapper.element); - }); - - it('renders default button text element', () => { - const dropdownTextEl = findDropdownText(); - - expect(dropdownTextEl.exists()).toBe(true); - expect(dropdownTextEl.text()).toBe('Label'); - }); - - it('renders provided button text element', () => { - store.state.dropdownButtonText = 'Custom label'; - const dropdownTextEl = findDropdownText(); - - return wrapper.vm.$nextTick().then(() => { - expect(dropdownTextEl.text()).toBe('Custom label'); - }); - }); - - it('renders chevron icon element', () => { - const iconEl = findDropdownIcon(); - - expect(iconEl.exists()).toBe(true); - expect(iconEl.props('name')).toBe('chevron-down'); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js index 90bc1980ac3..843298a1406 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js @@ -7,7 +7,12 @@ import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue'; import createLabelMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql'; -import { mockSuggestedColors, createLabelSuccessfulResponse } from './mock_data'; +import projectLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql'; +import { + mockSuggestedColors, + createLabelSuccessfulResponse, + labelsQueryResponse, +} from './mock_data'; jest.mock('~/flash'); @@ -44,6 +49,14 @@ describe('DropdownContentsCreateView', () => { const createComponent = ({ mutationHandler = createLabelSuccessHandler } = {}) => { const mockApollo = createMockApollo([[createLabelMutation, mutationHandler]]); + mockApollo.clients.defaultClient.cache.writeQuery({ + query: projectLabelsQuery, + data: labelsQueryResponse.data, + variables: { + fullPath: '', + searchTerm: '', + }, + }); wrapper = shallowMount(DropdownContentsCreateView, { localVue, diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js index 8bd944a3d54..537bbc8e71e 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js @@ -45,8 +45,6 @@ describe('DropdownContentsLabelsView', () => { provide: { projectPath: 'test', iid: 1, - allowLabelCreate: true, - labelsManagePath: '/gitlab-org/my-project/-/labels', variant: DropdownVariant.Sidebar, ...injected, }, @@ -69,10 +67,7 @@ describe('DropdownContentsLabelsView', () => { const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findLabelsList = () => wrapper.find('[data-testid="labels-list"]'); - const findDropdownWrapper = () => wrapper.find('[data-testid="dropdown-wrapper"]'); - const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]'); const findNoResultsMessage = () => wrapper.find('[data-testid="no-results"]'); - const findCreateLabelButton = () => wrapper.find('[data-testid="create-label-button"]'); describe('when loading labels', () => { it('renders disabled search input field', async () => { @@ -109,40 +104,6 @@ describe('DropdownContentsLabelsView', () => { expect(findLabelsList().exists()).toBe(true); expect(findLabels()).toHaveLength(2); }); - - it('changes highlighted label correctly on pressing down button', async () => { - expect(findLabels().at(0).attributes('highlight')).toBeUndefined(); - - await findDropdownWrapper().trigger('keydown.down'); - expect(findLabels().at(0).attributes('highlight')).toBe('true'); - - await findDropdownWrapper().trigger('keydown.down'); - expect(findLabels().at(1).attributes('highlight')).toBe('true'); - expect(findLabels().at(0).attributes('highlight')).toBeUndefined(); - }); - - it('changes highlighted label correctly on pressing up button', async () => { - await findDropdownWrapper().trigger('keydown.down'); - await findDropdownWrapper().trigger('keydown.down'); - expect(findLabels().at(1).attributes('highlight')).toBe('true'); - - await findDropdownWrapper().trigger('keydown.up'); - expect(findLabels().at(0).attributes('highlight')).toBe('true'); - }); - - it('changes label selected state when Enter is pressed', async () => { - expect(findLabels().at(0).attributes('islabelset')).toBeUndefined(); - await findDropdownWrapper().trigger('keydown.down'); - await findDropdownWrapper().trigger('keydown.enter'); - - expect(findLabels().at(0).attributes('islabelset')).toBe('true'); - }); - - it('emits `closeDropdown event` when Esc button is pressed', () => { - findDropdownWrapper().trigger('keydown.esc'); - - expect(wrapper.emitted('closeDropdown')).toEqual([[selectedLabels]]); - }); }); it('when search returns 0 results', async () => { @@ -170,44 +131,4 @@ describe('DropdownContentsLabelsView', () => { await waitForPromises(); expect(createFlash).toHaveBeenCalled(); }); - - it('does not render footer on standalone dropdown', () => { - createComponent({ injected: { variant: DropdownVariant.Standalone } }); - - expect(findDropdownFooter().exists()).toBe(false); - }); - - it('renders footer on sidebar dropdown', () => { - createComponent(); - - expect(findDropdownFooter().exists()).toBe(true); - }); - - it('renders footer on embedded dropdown', () => { - createComponent({ injected: { variant: DropdownVariant.Embedded } }); - - expect(findDropdownFooter().exists()).toBe(true); - }); - - it('does not render create label button if `allowLabelCreate` is false', () => { - createComponent({ injected: { allowLabelCreate: false } }); - - expect(findCreateLabelButton().exists()).toBe(false); - }); - - describe('when `allowLabelCreate` is true', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders create label button', () => { - expect(findCreateLabelButton().exists()).toBe(true); - }); - - it('emits `toggleDropdownContentsCreateView` event on create label button click', () => { - findCreateLabelButton().vm.$emit('click'); - - expect(wrapper.emitted('toggleDropdownContentsCreateView')).toEqual([[]]); - }); - }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js index 3c2fd0c5acc..a1b40a891ec 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js @@ -1,77 +1,127 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; +import { GlDropdown } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; import DropdownContents from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue'; -import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_widget/store'; - -import { mockConfig, mockLabels } from './mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -const createComponent = (initialState = mockConfig, defaultProps = {}) => { - const store = new Vuex.Store(labelsSelectModule()); - - store.dispatch('setInitialState', initialState); - - return shallowMount(DropdownContents, { - propsData: { - ...defaultProps, - labelsCreateTitle: 'test', - selectedLabels: mockLabels, - allowMultiselect: true, - labelsListTitle: 'Assign labels', - footerCreateLabelTitle: 'create', - footerManageLabelTitle: 'manage', - }, - localVue, - store, - }); -}; +import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue'; +import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue'; + +import { mockLabels } from './mock_data'; describe('DropdownContent', () => { let wrapper; + const createComponent = ({ props = {}, injected = {} } = {}) => { + wrapper = shallowMount(DropdownContents, { + propsData: { + labelsCreateTitle: 'test', + selectedLabels: mockLabels, + allowMultiselect: true, + labelsListTitle: 'Assign labels', + footerCreateLabelTitle: 'create', + footerManageLabelTitle: 'manage', + dropdownButtonText: 'Labels', + variant: 'sidebar', + ...props, + }, + provide: { + allowLabelCreate: true, + labelsManagePath: 'foo/bar', + ...injected, + }, + stubs: { + GlDropdown, + }, + }); + }; + beforeEach(() => { - wrapper = createComponent(); + createComponent(); }); afterEach(() => { wrapper.destroy(); }); - describe('computed', () => { - describe('dropdownContentsView', () => { - it('returns string "dropdown-contents-create-view" when `showDropdownContentsCreateView` prop is `true`', () => { - wrapper.vm.$store.dispatch('toggleDropdownContentsCreateView'); + const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]'); + const findCreateLabelButton = () => wrapper.find('[data-testid="create-label-button"]'); + const findGoBackButton = () => wrapper.find('[data-testid="go-back-button"]'); - expect(wrapper.vm.dropdownContentsView).toBe('dropdown-contents-create-view'); - }); + describe('Create view', () => { + beforeEach(() => { + wrapper.vm.toggleDropdownContentsCreateView(); + }); - it('returns string "dropdown-contents-labels-view" when `showDropdownContentsCreateView` prop is `false`', () => { - expect(wrapper.vm.dropdownContentsView).toBe('dropdown-contents-labels-view'); - }); + it('renders create view when `showDropdownContentsCreateView` prop is `true`', () => { + expect(wrapper.findComponent(DropdownContentsCreateView).exists()).toBe(true); + }); + + it('does not render footer', () => { + expect(findDropdownFooter().exists()).toBe(false); + }); + + it('does not render create label button', () => { + expect(findCreateLabelButton().exists()).toBe(false); + }); + + it('renders go back button', () => { + expect(findGoBackButton().exists()).toBe(true); }); }); - describe('template', () => { - it('renders component container element with class `labels-select-dropdown-contents` and no styles', () => { - expect(wrapper.attributes('class')).toContain('labels-select-dropdown-contents'); - expect(wrapper.attributes('style')).toBeUndefined(); + describe('Labels view', () => { + it('renders labels view when `showDropdownContentsCreateView` when `showDropdownContentsCreateView` prop is `false`', () => { + expect(wrapper.findComponent(DropdownContentsLabelsView).exists()).toBe(true); }); - describe('when `renderOnTop` is true', () => { - it.each` - variant | expected - ${DropdownVariant.Sidebar} | ${'bottom: 3rem'} - ${DropdownVariant.Standalone} | ${'bottom: 2rem'} - ${DropdownVariant.Embedded} | ${'bottom: 2rem'} - `('renders upward for $variant variant', ({ variant, expected }) => { - wrapper = createComponent({ ...mockConfig, variant }, { renderOnTop: true }); + it('renders footer on sidebar dropdown', () => { + expect(findDropdownFooter().exists()).toBe(true); + }); + + it('does not render footer on standalone dropdown', () => { + createComponent({ props: { variant: DropdownVariant.Standalone } }); + + expect(findDropdownFooter().exists()).toBe(false); + }); - expect(wrapper.attributes('style')).toContain(expected); + it('renders footer on embedded dropdown', () => { + createComponent({ props: { variant: DropdownVariant.Embedded } }); + + expect(findDropdownFooter().exists()).toBe(true); + }); + + it('does not render go back button', () => { + expect(findGoBackButton().exists()).toBe(false); + }); + + it('does not render create label button if `allowLabelCreate` is false', () => { + createComponent({ injected: { allowLabelCreate: false } }); + + expect(findCreateLabelButton().exists()).toBe(false); + }); + + describe('when `allowLabelCreate` is true', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders create label button', () => { + expect(findCreateLabelButton().exists()).toBe(true); }); + + it('triggers `toggleDropdownContent` method on create label button click', () => { + jest.spyOn(wrapper.vm, 'toggleDropdownContent').mockImplementation(() => {}); + findCreateLabelButton().trigger('click'); + + expect(wrapper.vm.toggleDropdownContent).toHaveBeenCalled(); + }); + }); + }); + + describe('template', () => { + it('renders component container element with classes `gl-w-full gl-mt-2` and no styles', () => { + expect(wrapper.attributes('class')).toContain('gl-w-full gl-mt-2'); + expect(wrapper.attributes('style')).toBeUndefined(); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_title_spec.js deleted file mode 100644 index d2401a1f725..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_title_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; - -import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue'; - -import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_widget/store'; - -import { mockConfig } from './mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -const createComponent = (initialState = mockConfig) => { - const store = new Vuex.Store(labelsSelectModule()); - - store.dispatch('setInitialState', initialState); - - return shallowMount(DropdownTitle, { - localVue, - store, - propsData: { - labelsSelectInProgress: false, - }, - }); -}; - -describe('DropdownTitle', () => { - let wrapper; - - beforeEach(() => { - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('template', () => { - it('renders component container element with string "Labels"', () => { - expect(wrapper.text()).toContain('Labels'); - }); - - it('renders edit link', () => { - const editBtnEl = wrapper.find(GlButton); - - expect(editBtnEl.exists()).toBe(true); - expect(editBtnEl.text()).toBe('Edit'); - }); - - it('renders loading icon element when `labelsSelectInProgress` prop is true', () => { - wrapper.setProps({ - labelsSelectInProgress: true, - }); - - return wrapper.vm.$nextTick(() => { - expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js index b3ffee2d020..e7e78cd7a33 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js @@ -9,8 +9,8 @@ describe('DropdownValue', () => { let wrapper; const findAllLabels = () => wrapper.findAllComponents(GlLabel); - const findRegularLabel = () => findAllLabels().at(0); - const findScopedLabel = () => findAllLabels().at(1); + const findRegularLabel = () => findAllLabels().at(1); + const findScopedLabel = () => findAllLabels().at(0); const findWrapper = () => wrapper.find('[data-testid="value-wrapper"]'); const findEmptyPlaceholder = () => wrapper.find('[data-testid="empty-placeholder"]'); @@ -20,11 +20,13 @@ describe('DropdownValue', () => { propsData: { selectedLabels: [mockRegularLabel, mockScopedLabel], allowLabelRemove: true, - allowScopedLabels: true, labelsFilterBasePath: '/gitlab-org/my-project/issues', labelsFilterParam: 'label_name', ...props, }, + provide: { + allowScopedLabels: true, + }, }); }; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/label_item_spec.js index 23810339833..6e8841411a2 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/label_item_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/label_item_spec.js @@ -1,4 +1,3 @@ -import { GlIcon, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import LabelItem from '~/vue_shared/components/sidebar/labels_select_widget/label_item.vue'; @@ -6,16 +5,10 @@ import { mockRegularLabel } from './mock_data'; const mockLabel = { ...mockRegularLabel, set: true }; -const createComponent = ({ - label = mockLabel, - isLabelSet = mockLabel.set, - highlight = true, -} = {}) => +const createComponent = ({ label = mockLabel } = {}) => shallowMount(LabelItem, { propsData: { label, - isLabelSet, - highlight, }, }); @@ -31,45 +24,6 @@ describe('LabelItem', () => { }); describe('template', () => { - it('renders gl-link component', () => { - expect(wrapper.find(GlLink).exists()).toBe(true); - }); - - it('renders component root with class `is-focused` when `highlight` prop is true', () => { - const wrapperTemp = createComponent({ - highlight: true, - }); - - expect(wrapperTemp.classes()).toContain('is-focused'); - - wrapperTemp.destroy(); - }); - - it('renders visible gl-icon component when `isLabelSet` prop is true', () => { - const wrapperTemp = createComponent({ - isLabelSet: true, - }); - - const iconEl = wrapperTemp.find(GlIcon); - - expect(iconEl.isVisible()).toBe(true); - expect(iconEl.props('name')).toBe('mobile-issue-close'); - - wrapperTemp.destroy(); - }); - - it('renders visible span element as placeholder instead of gl-icon when `isLabelSet` prop is false', () => { - const wrapperTemp = createComponent({ - isLabelSet: false, - }); - - const placeholderEl = wrapperTemp.find('[data-testid="no-icon"]'); - - expect(placeholderEl.isVisible()).toBe(true); - - wrapperTemp.destroy(); - }); - it('renders label color element', () => { const colorEl = wrapper.find('[data-testid="label-color-box"]'); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js index e17dfd93efc..a18511fa21d 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js @@ -1,193 +1,74 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; - -import { isInViewport } from '~/lib/utils/common_utils'; -import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; -import DropdownButton from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_button.vue'; +import { shallowMount } from '@vue/test-utils'; +import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; import DropdownContents from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue'; -import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue'; import DropdownValue from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue'; import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue'; import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue'; -import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_widget/store'; - import { mockConfig } from './mock_data'; -jest.mock('~/lib/utils/common_utils', () => ({ - isInViewport: jest.fn().mockReturnValue(true), -})); - -const localVue = createLocalVue(); -localVue.use(Vuex); - describe('LabelsSelectRoot', () => { let wrapper; - let store; const createComponent = (config = mockConfig, slots = {}) => { wrapper = shallowMount(LabelsSelectRoot, { - localVue, slots, - store, propsData: config, stubs: { - 'dropdown-contents': DropdownContents, + DropdownContents, + SidebarEditableItem, }, provide: { iid: '1', projectPath: 'test', + canUpdate: true, + allowLabelEdit: true, }, }); }; - beforeEach(() => { - store = new Vuex.Store(labelsSelectModule()); - }); - afterEach(() => { wrapper.destroy(); }); - describe('methods', () => { - describe('handleDropdownClose', () => { - beforeEach(() => { - createComponent(); - }); - - it('emits `updateSelectedLabels` & `onDropdownClose` events on component when provided `labels` param is not empty', () => { - wrapper.vm.handleDropdownClose([{ id: 1 }, { id: 2 }]); - - expect(wrapper.emitted().updateSelectedLabels).toBeTruthy(); - expect(wrapper.emitted().onDropdownClose).toBeTruthy(); - }); - - it('emits only `onDropdownClose` event on component when provided `labels` param is empty', () => { - wrapper.vm.handleDropdownClose([]); - - expect(wrapper.emitted().updateSelectedLabels).toBeFalsy(); - expect(wrapper.emitted().onDropdownClose).toBeTruthy(); - }); - }); - - describe('handleCollapsedValueClick', () => { - it('emits `toggleCollapse` event on component', () => { - createComponent(); - wrapper.vm.handleCollapsedValueClick(); - - expect(wrapper.emitted().toggleCollapse).toBeTruthy(); - }); - }); + it('renders component with classes `labels-select-wrapper position-relative`', () => { + createComponent(); + expect(wrapper.classes()).toEqual(['labels-select-wrapper', 'position-relative']); }); - describe('template', () => { - it('renders component with classes `labels-select-wrapper position-relative`', () => { - createComponent(); - expect(wrapper.attributes('class')).toContain('labels-select-wrapper position-relative'); - }); - - it.each` - variant | cssClass - ${'standalone'} | ${'is-standalone'} - ${'embedded'} | ${'is-embedded'} - `( - 'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"', - ({ variant, cssClass }) => { - createComponent({ - ...mockConfig, - variant, - }); - - return wrapper.vm.$nextTick(() => { - expect(wrapper.classes()).toContain(cssClass); - }); - }, - ); - - it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => { - createComponent(); - await wrapper.vm.$nextTick; - expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true); - }); - - it('renders `dropdown-title` component', async () => { - createComponent(); - await wrapper.vm.$nextTick; - expect(wrapper.find(DropdownTitle).exists()).toBe(true); - }); - - it('renders `dropdown-value` component', async () => { - createComponent(mockConfig, { - default: 'None', + it.each` + variant | cssClass + ${'standalone'} | ${'is-standalone'} + ${'embedded'} | ${'is-embedded'} + `( + 'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"', + ({ variant, cssClass }) => { + createComponent({ + ...mockConfig, + variant, }); - await wrapper.vm.$nextTick; - - const valueComp = wrapper.find(DropdownValue); - - expect(valueComp.exists()).toBe(true); - expect(valueComp.text()).toBe('None'); - }); - - it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', async () => { - createComponent(); - wrapper.vm.$store.dispatch('toggleDropdownButton'); - await wrapper.vm.$nextTick; - expect(wrapper.find(DropdownButton).exists()).toBe(true); - }); - - it('renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`', async () => { - createComponent(); - wrapper.vm.$store.dispatch('toggleDropdownContents'); - await wrapper.vm.$nextTick; - expect(wrapper.find(DropdownContents).exists()).toBe(true); - }); - describe('sets content direction based on viewport', () => { - describe.each(Object.values(DropdownVariant))( - 'when labels variant is "%s"', - ({ variant }) => { - beforeEach(() => { - createComponent({ ...mockConfig, variant }); - wrapper.vm.$store.dispatch('toggleDropdownContents'); - }); - - it('set direction when out of viewport', () => { - isInViewport.mockImplementation(() => false); - wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); - - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true); - }); - }); - - it('does not set direction when inside of viewport', () => { - isInViewport.mockImplementation(() => true); - wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); - - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false); - }); - }); - }, - ); - }); - }); + return wrapper.vm.$nextTick(() => { + expect(wrapper.classes()).toContain(cssClass); + }); + }, + ); - it('calls toggleDropdownContents action when isEditing prop is changing to true', async () => { + it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => { createComponent(); - - jest.spyOn(store, 'dispatch').mockResolvedValue(); - await wrapper.setProps({ isEditing: true }); - - expect(store.dispatch).toHaveBeenCalledWith('toggleDropdownContents'); + await wrapper.vm.$nextTick; + expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true); }); - it('does not call toggleDropdownContents action when isEditing prop is changing to false', async () => { - createComponent(); + it('renders `dropdown-value` component', async () => { + createComponent(mockConfig, { + default: 'None', + }); + await wrapper.vm.$nextTick; - jest.spyOn(store, 'dispatch').mockResolvedValue(); - await wrapper.setProps({ isEditing: false }); + const valueComp = wrapper.find(DropdownValue); - expect(store.dispatch).not.toHaveBeenCalled(); + expect(valueComp.exists()).toBe(true); + expect(valueComp.text()).toBe('None'); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js index 5dd8fc1b8b2..fceaabec2d0 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js @@ -34,18 +34,12 @@ export const mockLabels = [ ]; export const mockConfig = { - allowLabelEdit: true, - allowLabelCreate: true, - allowScopedLabels: true, allowMultiselect: true, labelsListTitle: 'Assign labels', labelsCreateTitle: 'Create label', variant: 'sidebar', - dropdownOnly: false, selectedLabels: [mockRegularLabel, mockScopedLabel], labelsSelectInProgress: false, - labelsFetchPath: '/gitlab-org/my-project/-/labels.json', - labelsManagePath: '/gitlab-org/my-project/-/labels', labelsFilterBasePath: '/gitlab-org/my-project/issues', labelsFilterParam: 'label_name', footerCreateLabelTitle: 'create', @@ -83,9 +77,7 @@ export const createLabelSuccessfulResponse = { id: 'gid://gitlab/ProjectLabel/126', color: '#dc143c', description: null, - descriptionHtml: '', title: 'ewrwrwer', - textColor: '#FFFFFF', __typename: 'Label', }, errors: [], diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js deleted file mode 100644 index ee905410ffa..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import testAction from 'helpers/vuex_action_helper'; -import * as actions from '~/vue_shared/components/sidebar/labels_select_widget/store/actions'; -import * as types from '~/vue_shared/components/sidebar/labels_select_widget/store/mutation_types'; -import defaultState from '~/vue_shared/components/sidebar/labels_select_widget/store/state'; - -jest.mock('~/flash'); - -describe('LabelsSelect Actions', () => { - let state; - const mockInitialState = { - labels: [], - selectedLabels: [], - }; - - beforeEach(() => { - state = { ...defaultState() }; - }); - - describe('setInitialState', () => { - it('sets initial store state', (done) => { - testAction( - actions.setInitialState, - mockInitialState, - state, - [{ type: types.SET_INITIAL_STATE, payload: mockInitialState }], - [], - done, - ); - }); - }); - - describe('toggleDropdownButton', () => { - it('toggles dropdown button', (done) => { - testAction( - actions.toggleDropdownButton, - {}, - state, - [{ type: types.TOGGLE_DROPDOWN_BUTTON }], - [], - done, - ); - }); - }); - - describe('toggleDropdownContents', () => { - it('toggles dropdown contents', (done) => { - testAction( - actions.toggleDropdownContents, - {}, - state, - [{ type: types.TOGGLE_DROPDOWN_CONTENTS }], - [], - done, - ); - }); - }); - - describe('toggleDropdownContentsCreateView', () => { - it('toggles dropdown create view', (done) => { - testAction( - actions.toggleDropdownContentsCreateView, - {}, - state, - [{ type: types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW }], - [], - done, - ); - }); - }); - - describe('updateSelectedLabels', () => { - it('updates `state.labels` based on provided `labels` param', (done) => { - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - - testAction( - actions.updateSelectedLabels, - labels, - state, - [{ type: types.UPDATE_SELECTED_LABELS, payload: { labels } }], - [], - done, - ); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/getters_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/getters_spec.js deleted file mode 100644 index 40eb0323146..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/getters_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import * as getters from '~/vue_shared/components/sidebar/labels_select_widget/store/getters'; - -describe('LabelsSelect Getters', () => { - describe('dropdownButtonText', () => { - it.each` - labelType | dropdownButtonText | expected - ${'default'} | ${''} | ${'Label'} - ${'custom'} | ${'Custom label'} | ${'Custom label'} - `( - 'returns $labelType text when state.labels has no selected labels', - ({ dropdownButtonText, expected }) => { - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - const selectedLabels = []; - const state = { labels, selectedLabels, dropdownButtonText }; - - expect(getters.dropdownButtonText(state, {})).toBe(expected); - }, - ); - - it('returns label title when state.labels has only 1 label', () => { - const labels = [{ id: 1, title: 'Foobar', set: true }]; - - expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe( - 'Foobar', - ); - }); - - it('returns first label title and remaining labels count when state.labels has more than 1 label', () => { - const labels = [ - { id: 1, title: 'Foo', set: true }, - { id: 2, title: 'Bar', set: true }, - ]; - - expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe( - 'Foo +1 more', - ); - }); - }); - - describe('selectedLabelsList', () => { - it('returns array of IDs of all labels within `state.selectedLabels`', () => { - const selectedLabels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - - expect(getters.selectedLabelsList({ selectedLabels })).toEqual([1, 2, 3, 4]); - }); - }); - - describe('isDropdownVariantSidebar', () => { - it('returns `true` when `state.variant` is "sidebar"', () => { - expect(getters.isDropdownVariantSidebar({ variant: 'sidebar' })).toBe(true); - }); - }); - - describe('isDropdownVariantStandalone', () => { - it('returns `true` when `state.variant` is "standalone"', () => { - expect(getters.isDropdownVariantStandalone({ variant: 'standalone' })).toBe(true); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js deleted file mode 100644 index 1f0e0eee420..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import * as types from '~/vue_shared/components/sidebar/labels_select_widget/store/mutation_types'; -import mutations from '~/vue_shared/components/sidebar/labels_select_widget/store/mutations'; - -describe('LabelsSelect Mutations', () => { - describe(`${types.SET_INITIAL_STATE}`, () => { - it('initializes provided props to store state', () => { - const state = {}; - mutations[types.SET_INITIAL_STATE](state, { - labels: 'foo', - }); - - expect(state.labels).toEqual('foo'); - }); - }); - - describe(`${types.TOGGLE_DROPDOWN_BUTTON}`, () => { - it('toggles value of `state.showDropdownButton`', () => { - const state = { - showDropdownButton: false, - }; - mutations[types.TOGGLE_DROPDOWN_BUTTON](state); - - expect(state.showDropdownButton).toBe(true); - }); - }); - - describe(`${types.TOGGLE_DROPDOWN_CONTENTS}`, () => { - it('toggles value of `state.showDropdownButton` when `state.dropdownOnly` is false', () => { - const state = { - dropdownOnly: false, - showDropdownButton: false, - variant: 'sidebar', - }; - mutations[types.TOGGLE_DROPDOWN_CONTENTS](state); - - expect(state.showDropdownButton).toBe(true); - }); - - it('toggles value of `state.showDropdownContents`', () => { - const state = { - showDropdownContents: false, - }; - mutations[types.TOGGLE_DROPDOWN_CONTENTS](state); - - expect(state.showDropdownContents).toBe(true); - }); - - it('sets value of `state.showDropdownContentsCreateView` to `false` when `showDropdownContents` is true', () => { - const state = { - showDropdownContents: false, - showDropdownContentsCreateView: true, - }; - mutations[types.TOGGLE_DROPDOWN_CONTENTS](state); - - expect(state.showDropdownContentsCreateView).toBe(false); - }); - }); - - describe(`${types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW}`, () => { - it('toggles value of `state.showDropdownContentsCreateView`', () => { - const state = { - showDropdownContentsCreateView: false, - }; - mutations[types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW](state); - - expect(state.showDropdownContentsCreateView).toBe(true); - }); - }); - - describe(`${types.UPDATE_SELECTED_LABELS}`, () => { - let labels; - - beforeEach(() => { - labels = [ - { id: 1, title: 'scoped::test', set: true }, - { id: 2, set: false, title: 'scoped::one' }, - { id: 3, title: '' }, - { id: 4, title: '' }, - ]; - }); - - it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => { - const updatedLabelIds = [2]; - const state = { - labels, - }; - mutations[types.UPDATE_SELECTED_LABELS](state, { labels: [{ id: 2 }] }); - - state.labels.forEach((label) => { - if (updatedLabelIds.includes(label.id)) { - expect(label.touched).toBe(true); - expect(label.set).toBe(true); - } - }); - }); - - describe('when label is scoped', () => { - it('unsets the currently selected scoped label and sets the current label', () => { - const state = { - labels, - }; - mutations[types.UPDATE_SELECTED_LABELS](state, { - labels: [{ id: 2, title: 'scoped::one' }], - }); - - expect(state.labels).toEqual([ - { id: 1, title: 'scoped::test', set: false }, - { id: 2, set: true, title: 'scoped::one', touched: true }, - { id: 3, title: '' }, - { id: 4, title: '' }, - ]); - }); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/storage_counter/usage_graph_spec.js b/spec/frontend/vue_shared/components/storage_counter/usage_graph_spec.js new file mode 100644 index 00000000000..103eee4b9a8 --- /dev/null +++ b/spec/frontend/vue_shared/components/storage_counter/usage_graph_spec.js @@ -0,0 +1,137 @@ +import { shallowMount } from '@vue/test-utils'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; +import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue'; + +let data; +let wrapper; + +function mountComponent({ rootStorageStatistics, limit }) { + wrapper = shallowMount(UsageGraph, { + propsData: { + rootStorageStatistics, + limit, + }, + }); +} +function findStorageTypeUsagesSerialized() { + return wrapper + .findAll('[data-testid="storage-type-usage"]') + .wrappers.map((wp) => wp.element.style.flex); +} + +describe('Storage Counter usage graph component', () => { + beforeEach(() => { + data = { + rootStorageStatistics: { + wikiSize: 5000, + repositorySize: 4000, + packagesSize: 3000, + lfsObjectsSize: 2000, + buildArtifactsSize: 500, + pipelineArtifactsSize: 500, + snippetsSize: 2000, + storageSize: 17000, + uploadsSize: 1000, + }, + limit: 2000, + }; + mountComponent(data); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the legend in order', () => { + const types = wrapper.findAll('[data-testid="storage-type-legend"]'); + + const { + buildArtifactsSize, + pipelineArtifactsSize, + lfsObjectsSize, + packagesSize, + repositorySize, + wikiSize, + snippetsSize, + uploadsSize, + } = data.rootStorageStatistics; + + expect(types.at(0).text()).toMatchInterpolatedText(`Wikis ${numberToHumanSize(wikiSize)}`); + expect(types.at(1).text()).toMatchInterpolatedText( + `Repositories ${numberToHumanSize(repositorySize)}`, + ); + expect(types.at(2).text()).toMatchInterpolatedText( + `Packages ${numberToHumanSize(packagesSize)}`, + ); + expect(types.at(3).text()).toMatchInterpolatedText( + `LFS Objects ${numberToHumanSize(lfsObjectsSize)}`, + ); + expect(types.at(4).text()).toMatchInterpolatedText( + `Snippets ${numberToHumanSize(snippetsSize)}`, + ); + expect(types.at(5).text()).toMatchInterpolatedText( + `Artifacts ${numberToHumanSize(buildArtifactsSize + pipelineArtifactsSize)}`, + ); + expect(types.at(6).text()).toMatchInterpolatedText(`Uploads ${numberToHumanSize(uploadsSize)}`); + }); + + describe('when storage type is not used', () => { + beforeEach(() => { + data.rootStorageStatistics.wikiSize = 0; + mountComponent(data); + }); + + it('filters the storage type', () => { + expect(wrapper.text()).not.toContain('Wikis'); + }); + }); + + describe('when there is no storage usage', () => { + beforeEach(() => { + data.rootStorageStatistics.storageSize = 0; + mountComponent(data); + }); + + it('it does not render', () => { + expect(wrapper.html()).toEqual(''); + }); + }); + + describe('when limit is 0', () => { + beforeEach(() => { + data.limit = 0; + mountComponent(data); + }); + + it('sets correct flex values', () => { + expect(findStorageTypeUsagesSerialized()).toStrictEqual([ + '0.29411764705882354', + '0.23529411764705882', + '0.17647058823529413', + '0.11764705882352941', + '0.11764705882352941', + '0.058823529411764705', + '0.058823529411764705', + ]); + }); + }); + + describe('when storage exceeds limit', () => { + beforeEach(() => { + data.limit = data.rootStorageStatistics.storageSize - 1; + mountComponent(data); + }); + + it('it does render correclty', () => { + expect(findStorageTypeUsagesSerialized()).toStrictEqual([ + '0.29411764705882354', + '0.23529411764705882', + '0.17647058823529413', + '0.11764705882352941', + '0.11764705882352941', + '0.058823529411764705', + '0.058823529411764705', + ]); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index 538e67ef354..926223e0670 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -94,7 +94,7 @@ describe('User Popover Component', () => { const bio = 'My super interesting bio'; it('should show only bio if work information is not available', () => { - const user = { ...DEFAULT_PROPS.user, bio, bioHtml: bio }; + const user = { ...DEFAULT_PROPS.user, bio }; createWrapper({ user }); @@ -117,7 +117,6 @@ describe('User Popover Component', () => { const user = { ...DEFAULT_PROPS.user, bio, - bioHtml: bio, workInformation: 'Frontend Engineer at GitLab', }; @@ -127,16 +126,15 @@ describe('User Popover Component', () => { expect(findWorkInformation().text()).toBe('Frontend Engineer at GitLab'); }); - it('should not encode special characters in bio', () => { + it('should encode special characters in bio', () => { const user = { ...DEFAULT_PROPS.user, - bio: 'I like CSS', - bioHtml: 'I like <b>CSS</b>', + bio: 'I like <b>CSS</b>', }; createWrapper({ user }); - expect(findBio().html()).toContain('I like <b>CSS</b>'); + expect(findBio().html()).toContain('I like <b>CSS</b>'); }); it('shows icon for bio', () => { @@ -250,6 +248,13 @@ describe('User Popover Component', () => { const securityBotDocsLink = findSecurityBotDocsLink(); expect(securityBotDocsLink.exists()).toBe(true); expect(securityBotDocsLink.attributes('href')).toBe(SECURITY_BOT_USER.websiteUrl); + expect(securityBotDocsLink.text()).toBe('Learn more about GitLab Security Bot'); + }); + + it("doesn't escape user's name", () => { + createWrapper({ user: { ...SECURITY_BOT_USER, name: '%<>\';"' } }); + const securityBotDocsLink = findSecurityBotDocsLink(); + expect(securityBotDocsLink.text()).toBe('Learn more about %<>\';"'); }); }); }); |