diff options
Diffstat (limited to 'spec/frontend/projects')
18 files changed, 398 insertions, 154 deletions
diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js index 0c8089430d0..93e2ae13628 100644 --- a/spec/frontend/projects/commit/components/form_modal_spec.js +++ b/spec/frontend/projects/commit/components/form_modal_spec.js @@ -3,6 +3,7 @@ import { within } from '@testing-library/dom'; import { shallowMount, mount, createWrapper } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import api from '~/api'; import axios from '~/lib/utils/axios_utils'; import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue'; @@ -12,6 +13,8 @@ import eventHub from '~/projects/commit/event_hub'; import createStore from '~/projects/commit/store'; import mockData from '../mock_data'; +jest.mock('~/api'); + describe('CommitFormModal', () => { let wrapper; let store; @@ -167,4 +170,16 @@ describe('CommitFormModal', () => { expect(findTargetProject().attributes('value')).toBe('_changed_project_value_'); }); }); + + it('action primary button triggers Redis HLL tracking api call', async () => { + createComponent(mount, {}, {}, { primaryActionEventName: 'test_event' }); + + await wrapper.vm.$nextTick(); + + jest.spyOn(findForm().element, 'submit'); + + getByText(mockData.modalPropsData.i18n.actionPrimaryText).trigger('click'); + + expect(api.trackRedisHllUserEvent).toHaveBeenCalledWith('test_event'); + }); }); diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js index 9a8f7ff7582..60d36597fda 100644 --- a/spec/frontend/projects/commits/components/author_select_spec.js +++ b/spec/frontend/projects/commits/components/author_select_spec.js @@ -115,7 +115,7 @@ describe('Author Select', () => { }); it('does not have popover text by default', () => { - expect(wrapper.attributes('title')).not.toExist(); + expect(wrapper.attributes('title')).toBeUndefined(); }); }); diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap index c255fcce321..e1e1aac09aa 100644 --- a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap +++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap @@ -52,9 +52,44 @@ exports[`Project remove modal initialized matches the snapshot 1`] = ` title="You are about to permanently delete this project" variant="danger" > - <gl-sprintf-stub - message="Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc." - /> + <p> + This project is + <strong> + NOT + </strong> + a fork, and has the following: + </p> + + <ul> + <li> + 1 issue + </li> + + <li> + 2 merge requests + </li> + + <li> + 3 forks + </li> + + <li> + 4 stars + </li> + </ul> + After a project is permanently deleted, it + <strong> + cannot be recovered + </strong> + . Permanently deleting this project will + <strong> + immediately delete + </strong> + its repositories and + <strong> + all related resources + </strong> + , including issues, merge requests etc. </gl-alert-stub> <p diff --git a/spec/frontend/projects/components/project_delete_button_spec.js b/spec/frontend/projects/components/project_delete_button_spec.js index 444e465ebaa..bb6021fadda 100644 --- a/spec/frontend/projects/components/project_delete_button_spec.js +++ b/spec/frontend/projects/components/project_delete_button_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlSprintf } from '@gitlab/ui'; import ProjectDeleteButton from '~/projects/components/project_delete_button.vue'; import SharedDeleteButton from '~/projects/components/shared/delete_button.vue'; @@ -12,6 +13,11 @@ describe('Project remove modal', () => { const defaultProps = { confirmPhrase: 'foo', formPath: 'some/path', + isFork: false, + issuesCount: 1, + mergeRequestsCount: 2, + forksCount: 3, + starsCount: 4, }; const createComponent = (props = {}) => { @@ -21,6 +27,7 @@ describe('Project remove modal', () => { ...props, }, stubs: { + GlSprintf, SharedDeleteButton, }, }); @@ -41,7 +48,10 @@ describe('Project remove modal', () => { }); it('passes confirmPhrase and formPath props to the shared delete button', () => { - expect(findSharedDeleteButton().props()).toEqual(defaultProps); + expect(findSharedDeleteButton().props()).toEqual({ + confirmPhrase: defaultProps.confirmPhrase, + formPath: defaultProps.formPath, + }); }); }); }); diff --git a/spec/frontend/projects/details/upload_button_spec.js b/spec/frontend/projects/details/upload_button_spec.js index ebb2b499ead..d7308963088 100644 --- a/spec/frontend/projects/details/upload_button_spec.js +++ b/spec/frontend/projects/details/upload_button_spec.js @@ -1,11 +1,8 @@ import { GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import UploadButton from '~/projects/details/upload_button.vue'; -import { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking'; import UploadBlobModal from '~/repository/components/upload_blob_modal.vue'; -jest.mock('~/projects/upload_file_experiment_tracking'); - const MODAL_ID = 'details-modal-upload-blob'; describe('UploadButton', () => { @@ -50,10 +47,6 @@ describe('UploadButton', () => { wrapper.find(GlButton).vm.$emit('click'); }); - it('tracks the click_upload_modal_trigger event', () => { - expect(trackFileUploadEvent).toHaveBeenCalledWith('click_upload_modal_trigger'); - }); - it('opens the modal', () => { expect(glModalDirective).toHaveBeenCalledWith(MODAL_ID); }); diff --git a/spec/frontend/projects/new/components/new_project_url_select_spec.js b/spec/frontend/projects/new/components/new_project_url_select_spec.js index aa16b71172b..b3f177a1f12 100644 --- a/spec/frontend/projects/new/components/new_project_url_select_spec.js +++ b/spec/frontend/projects/new/components/new_project_url_select_spec.js @@ -24,14 +24,23 @@ describe('NewProjectUrlSelect component', () => { { id: 'gid://gitlab/Group/26', fullPath: 'flightjs', + name: 'Flight JS', + visibility: 'public', + webUrl: 'http://127.0.0.1:3000/flightjs', }, { id: 'gid://gitlab/Group/28', fullPath: 'h5bp', + name: 'H5BP', + visibility: 'public', + webUrl: 'http://127.0.0.1:3000/h5bp', }, { id: 'gid://gitlab/Group/30', fullPath: 'h5bp/subgroup', + name: 'H5BP Subgroup', + visibility: 'private', + webUrl: 'http://127.0.0.1:3000/h5bp/subgroup', }, ], }, @@ -79,6 +88,10 @@ describe('NewProjectUrlSelect component', () => { const findDropdown = () => wrapper.findComponent(GlDropdown); const findInput = () => wrapper.findComponent(GlSearchBoxByType); const findHiddenInput = () => wrapper.find('input'); + const clickDropdownItem = async () => { + wrapper.findComponent(GlDropdownItem).vm.$emit('click'); + await wrapper.vm.$nextTick(); + }; afterEach(() => { wrapper.destroy(); @@ -127,7 +140,6 @@ describe('NewProjectUrlSelect component', () => { it('focuses on the input when the dropdown is opened', async () => { wrapper = mountComponent({ mountFn: mount }); - jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); @@ -140,7 +152,6 @@ describe('NewProjectUrlSelect component', () => { it('renders expected dropdown items', async () => { wrapper = mountComponent({ mountFn: mount }); - jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); @@ -160,7 +171,6 @@ describe('NewProjectUrlSelect component', () => { beforeEach(async () => { wrapper = mountComponent({ mountFn: mount }); - jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); @@ -195,23 +205,38 @@ describe('NewProjectUrlSelect component', () => { }; wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount }); - jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); expect(wrapper.find('li').text()).toBe('No matches found'); }); - it('updates hidden input with selected namespace', async () => { + it('emits `update-visibility` event to update the visibility radio options', async () => { wrapper = mountComponent(); - jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); - wrapper.findComponent(GlDropdownItem).vm.$emit('click'); + const spy = jest.spyOn(eventHub, '$emit'); + await clickDropdownItem(); + + const namespace = data.currentUser.groups.nodes[0]; + + expect(spy).toHaveBeenCalledWith('update-visibility', { + name: namespace.name, + visibility: namespace.visibility, + showPath: namespace.webUrl, + editPath: `${namespace.webUrl}/-/edit`, + }); + }); + + it('updates hidden input with selected namespace', async () => { + wrapper = mountComponent(); + jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); + await clickDropdownItem(); + expect(findHiddenInput().attributes()).toMatchObject({ name: 'project[namespace_id]', value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(), diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js index 987a215eb4c..b4067f6a72b 100644 --- a/spec/frontend/projects/pipelines/charts/components/app_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js @@ -11,6 +11,7 @@ jest.mock('~/lib/utils/url_utility'); const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} }; const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} }; +const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} }; describe('ProjectsPipelinesChartsApp', () => { let wrapper; @@ -23,10 +24,12 @@ describe('ProjectsPipelinesChartsApp', () => { { provide: { shouldRenderDoraCharts: true, + shouldRenderQualitySummary: true, }, stubs: { DeploymentFrequencyCharts: DeploymentFrequencyChartsStub, LeadTimeCharts: LeadTimeChartsStub, + ProjectQualitySummary: ProjectQualitySummaryStub, }, }, mountOptions, @@ -44,6 +47,7 @@ describe('ProjectsPipelinesChartsApp', () => { const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub); const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub); const findPipelineCharts = () => wrapper.find(PipelineCharts); + const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub); describe('when all charts are available', () => { beforeEach(() => { @@ -70,6 +74,10 @@ describe('ProjectsPipelinesChartsApp', () => { expect(findLeadTimeCharts().exists()).toBe(true); }); + it('renders the project quality summary', () => { + expect(findProjectQualitySummary().exists()).toBe(true); + }); + it('sets the tab and url when a tab is clicked', async () => { let chartsPath; setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`); @@ -163,9 +171,11 @@ describe('ProjectsPipelinesChartsApp', () => { }); }); - describe('when the dora charts are not available', () => { + describe('when the dora charts are not available and project quality summary is not available', () => { beforeEach(() => { - createComponent({ provide: { shouldRenderDoraCharts: false } }); + createComponent({ + provide: { shouldRenderDoraCharts: false, shouldRenderQualitySummary: false }, + }); }); it('does not render tabs', () => { @@ -176,4 +186,14 @@ describe('ProjectsPipelinesChartsApp', () => { expect(findPipelineCharts().exists()).toBe(true); }); }); + + describe('when the project quality summary is not available', () => { + beforeEach(() => { + createComponent({ provide: { shouldRenderQualitySummary: false } }); + }); + + it('does not render the tab', () => { + expect(findProjectQualitySummary().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/projects/projects_filterable_list_spec.js b/spec/frontend/projects/projects_filterable_list_spec.js index d4dbf85b5ca..a41e8b7bc09 100644 --- a/spec/frontend/projects/projects_filterable_list_spec.js +++ b/spec/frontend/projects/projects_filterable_list_spec.js @@ -1,5 +1,4 @@ -// eslint-disable-next-line import/no-deprecated -import { getJSONFixture, setHTMLFixture } from 'helpers/fixtures'; +import { setHTMLFixture } from 'helpers/fixtures'; import ProjectsFilterableList from '~/projects/projects_filterable_list'; describe('ProjectsFilterableList', () => { @@ -15,8 +14,6 @@ describe('ProjectsFilterableList', () => { </div> <div class="js-projects-list-holder"></div> `); - // eslint-disable-next-line import/no-deprecated - getJSONFixture('static/projects.json'); form = document.querySelector('form#project-filter-form'); filter = document.querySelector('.js-projects-list-filter'); holder = document.querySelector('.js-projects-list-holder'); diff --git a/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js b/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js new file mode 100644 index 00000000000..dbea94cbd53 --- /dev/null +++ b/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js @@ -0,0 +1,98 @@ +import { GlTokenSelector, GlToken } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import TopicsTokenSelector from '~/projects/settings/topics/components/topics_token_selector.vue'; + +const mockTopics = [ + { id: 1, name: 'topic1', avatarUrl: 'avatar.com/topic1.png' }, + { id: 2, name: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' }, +]; + +describe('TopicsTokenSelector', () => { + let wrapper; + let div; + let input; + + const createComponent = (selected) => { + wrapper = mount(TopicsTokenSelector, { + attachTo: div, + propsData: { + selected, + }, + data() { + return { + topics: mockTopics, + }; + }, + mocks: { + $apollo: { + queries: { + topics: { loading: false }, + }, + }, + }, + }); + }; + + const findTokenSelector = () => wrapper.findComponent(GlTokenSelector); + + const findTokenSelectorInput = () => findTokenSelector().find('input[type="text"]'); + + const setTokenSelectorInputValue = (value) => { + const tokenSelectorInput = findTokenSelectorInput(); + + tokenSelectorInput.element.value = value; + tokenSelectorInput.trigger('input'); + + return nextTick(); + }; + + const tokenSelectorTriggerEnter = (event) => { + const tokenSelectorInput = findTokenSelectorInput(); + tokenSelectorInput.trigger('keydown.enter', event); + }; + + beforeEach(() => { + div = document.createElement('div'); + input = document.createElement('input'); + input.setAttribute('type', 'text'); + input.id = 'project_topic_list_field'; + document.body.appendChild(div); + document.body.appendChild(input); + }); + + afterEach(() => { + wrapper.destroy(); + div.remove(); + input.remove(); + }); + + describe('when component is mounted', () => { + it('parses selected into tokens', async () => { + const selected = [ + { id: 11, name: 'topic1' }, + { id: 12, name: 'topic2' }, + { id: 13, name: 'topic3' }, + ]; + createComponent(selected); + await nextTick(); + + wrapper.findAllComponents(GlToken).wrappers.forEach((tokenWrapper, index) => { + expect(tokenWrapper.text()).toBe(selected[index].name); + }); + }); + }); + + describe('when enter key is pressed', () => { + it('does not submit the form if token selector text input has a value', async () => { + createComponent(); + + await setTokenSelectorInputValue('topic'); + + const event = { preventDefault: jest.fn() }; + tokenSelectorTriggerEnter(event); + + expect(event.preventDefault).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/projects/settings_service_desk/components/mock_data.js b/spec/frontend/projects/settings_service_desk/components/mock_data.js new file mode 100644 index 00000000000..934778ff601 --- /dev/null +++ b/spec/frontend/projects/settings_service_desk/components/mock_data.js @@ -0,0 +1,8 @@ +export const TEMPLATES = [ + 'Project #1', + [ + { name: 'Bug', project_id: 1 }, + { name: 'Documentation', project_id: 1 }, + { name: 'Security release', project_id: 1 }, + ], +]; 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 8acf2376860..62224612387 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 @@ -21,6 +21,7 @@ describe('ServiceDeskRoot', () => { outgoingName: 'GitLab Support Bot', projectKey: 'key', selectedTemplate: 'Bug', + selectedFileTemplateProjectId: 42, templates: ['Bug', 'Documentation'], }; @@ -52,6 +53,7 @@ describe('ServiceDeskRoot', () => { initialOutgoingName: provideData.outgoingName, initialProjectKey: provideData.projectKey, initialSelectedTemplate: provideData.selectedTemplate, + initialSelectedFileTemplateProjectId: provideData.selectedFileTemplateProjectId, isEnabled: provideData.initialIsEnabled, isTemplateSaving: false, templates: provideData.templates, diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js index eacf858f22c..0fd3e7446da 100644 --- a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js @@ -1,4 +1,4 @@ -import { GlButton, GlFormSelect, GlLoadingIcon, GlToggle } from '@gitlab/ui'; +import { GlButton, GlDropdown, GlLoadingIcon, GlToggle } from '@gitlab/ui'; import { shallowMount, mount } from '@vue/test-utils'; import { nextTick } from 'vue'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; @@ -13,7 +13,7 @@ describe('ServiceDeskSetting', () => { const findIncomingEmail = () => wrapper.findByTestId('incoming-email'); const findIncomingEmailLabel = () => wrapper.findByTestId('incoming-email-describer'); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findTemplateDropdown = () => wrapper.find(GlFormSelect); + const findTemplateDropdown = () => wrapper.find(GlDropdown); const findToggle = () => wrapper.find(GlToggle); const createComponent = ({ props = {}, mountFunction = shallowMount } = {}) => @@ -128,6 +128,23 @@ describe('ServiceDeskSetting', () => { expect(input.exists()).toBe(true); expect(input.attributes('disabled')).toBeUndefined(); }); + + it('shows error when value contains uppercase or special chars', async () => { + wrapper = createComponent({ + props: { customEmailEnabled: true }, + mountFunction: mount, + }); + + const input = wrapper.findByTestId('project-suffix'); + + input.setValue('abc_A.'); + input.trigger('blur'); + + await wrapper.vm.$nextTick(); + + const errorText = wrapper.find('.text-danger'); + expect(errorText.exists()).toBe(true); + }); }); describe('customEmail is the same as incomingEmail', () => { @@ -144,63 +161,6 @@ describe('ServiceDeskSetting', () => { }); }); }); - - describe('templates dropdown', () => { - it('renders a dropdown to choose a template', () => { - wrapper = createComponent(); - - expect(findTemplateDropdown().exists()).toBe(true); - }); - - it('renders a dropdown with a default value of ""', () => { - wrapper = createComponent({ mountFunction: mount }); - - expect(findTemplateDropdown().element.value).toEqual(''); - }); - - it('renders a dropdown with a value of "Bug" when it is the initial value', () => { - const templates = ['Bug', 'Documentation', 'Security release']; - - wrapper = createComponent({ - props: { initialSelectedTemplate: 'Bug', templates }, - mountFunction: mount, - }); - - expect(findTemplateDropdown().element.value).toEqual('Bug'); - }); - - it('renders a dropdown with no options when the project has no templates', () => { - wrapper = createComponent({ - props: { templates: [] }, - mountFunction: mount, - }); - - // The dropdown by default has one empty option - expect(findTemplateDropdown().element.children).toHaveLength(1); - }); - - it('renders a dropdown with options when the project has templates', () => { - const templates = ['Bug', 'Documentation', 'Security release']; - - wrapper = createComponent({ - props: { templates }, - mountFunction: mount, - }); - - // An empty-named template is prepended so the user can select no template - const expectedTemplates = [''].concat(templates); - - const dropdown = findTemplateDropdown(); - const dropdownList = Array.from(dropdown.element.children).map( - (option) => option.innerText, - ); - - expect(dropdown.element.children).toHaveLength(expectedTemplates.length); - expect(dropdownList.includes('Bug')).toEqual(true); - expect(dropdownList.includes('Documentation')).toEqual(true); - expect(dropdownList.includes('Security release')).toEqual(true); - }); - }); }); describe('save button', () => { @@ -214,6 +174,7 @@ describe('ServiceDeskSetting', () => { wrapper = createComponent({ props: { initialSelectedTemplate: 'Bug', + initialSelectedFileTemplateProjectId: 42, initialOutgoingName: 'GitLab Support Bot', initialProjectKey: 'key', }, @@ -225,6 +186,7 @@ describe('ServiceDeskSetting', () => { const payload = { selectedTemplate: 'Bug', + fileTemplateProjectId: 42, outgoingName: 'GitLab Support Bot', projectKey: 'key', }; diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js new file mode 100644 index 00000000000..cdb355f5a9b --- /dev/null +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_template_dropdown_spec.js @@ -0,0 +1,80 @@ +import { GlDropdown, GlDropdownSectionHeader, GlDropdownItem } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import ServiceDeskTemplateDropdown from '~/projects/settings_service_desk/components/service_desk_setting.vue'; +import { TEMPLATES } from './mock_data'; + +describe('ServiceDeskTemplateDropdown', () => { + let wrapper; + + const findTemplateDropdown = () => wrapper.find(GlDropdown); + + const createComponent = ({ props = {} } = {}) => + extendedWrapper( + mount(ServiceDeskTemplateDropdown, { + propsData: { + isEnabled: true, + ...props, + }, + }), + ); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('templates dropdown', () => { + it('renders a dropdown to choose a template', () => { + wrapper = createComponent(); + + expect(findTemplateDropdown().exists()).toBe(true); + }); + + it('renders a dropdown with a default value of "Choose a template"', () => { + wrapper = createComponent(); + + expect(findTemplateDropdown().props('text')).toEqual('Choose a template'); + }); + + it('renders a dropdown with a value of "Bug" when it is the initial value', () => { + const templates = TEMPLATES; + + wrapper = createComponent({ + props: { initialSelectedTemplate: 'Bug', initialSelectedTemplateProjectId: 1, templates }, + }); + + expect(findTemplateDropdown().props('text')).toEqual('Bug'); + }); + + it('renders a dropdown with header items', () => { + wrapper = createComponent({ + props: { templates: TEMPLATES }, + }); + + const headerItems = wrapper.findAll(GlDropdownSectionHeader); + + expect(headerItems).toHaveLength(1); + expect(headerItems.at(0).text()).toBe(TEMPLATES[0]); + }); + + it('renders a dropdown with options when the project has templates', () => { + const templates = TEMPLATES; + + wrapper = createComponent({ + props: { templates }, + }); + + const expectedTemplates = templates[1]; + + const items = wrapper.findAll(GlDropdownItem); + const dropdownList = expectedTemplates.map((_, index) => items.at(index).text()); + + expect(items).toHaveLength(expectedTemplates.length); + expect(dropdownList.includes('Bug')).toEqual(true); + expect(dropdownList.includes('Documentation')).toEqual(true); + expect(dropdownList.includes('Security release')).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/projects/storage_counter/components/storage_table_spec.js b/spec/frontend/projects/storage_counter/components/storage_table_spec.js index 14298318fff..c9e56d8f033 100644 --- a/spec/frontend/projects/storage_counter/components/storage_table_spec.js +++ b/spec/frontend/projects/storage_counter/components/storage_table_spec.js @@ -1,4 +1,4 @@ -import { GlTable } from '@gitlab/ui'; +import { GlTableLite } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import StorageTable from '~/projects/storage_counter/components/storage_table.vue'; @@ -22,7 +22,7 @@ describe('StorageTable', () => { ); }; - const findTable = () => wrapper.findComponent(GlTable); + const findTable = () => wrapper.findComponent(GlTableLite); beforeEach(() => { createComponent(); @@ -37,6 +37,7 @@ describe('StorageTable', () => { ({ storageType: { id, name, description } }) => { expect(wrapper.findByTestId(`${id}-name`).text()).toBe(name); expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description); + expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id); expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe( defaultProvideValues.helpLinks[id.replace(`Size`, `HelpPagePath`)] .replace(`Size`, ``) diff --git a/spec/frontend/projects/storage_counter/components/storage_type_icon_spec.js b/spec/frontend/projects/storage_counter/components/storage_type_icon_spec.js new file mode 100644 index 00000000000..01efd6f14bd --- /dev/null +++ b/spec/frontend/projects/storage_counter/components/storage_type_icon_spec.js @@ -0,0 +1,41 @@ +import { mount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; +import StorageTypeIcon from '~/projects/storage_counter/components/storage_type_icon.vue'; + +describe('StorageTypeIcon', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = mount(StorageTypeIcon, { + propsData: { + ...props, + }, + }); + }; + + const findGlIcon = () => wrapper.findComponent(GlIcon); + + describe('rendering icon', () => { + afterEach(() => { + wrapper.destroy(); + }); + + it.each` + expected | provided + ${'doc-image'} | ${'lfsObjectsSize'} + ${'snippet'} | ${'snippetsSize'} + ${'infrastructure-registry'} | ${'repositorySize'} + ${'package'} | ${'packagesSize'} + ${'upload'} | ${'uploadsSize'} + ${'disk'} | ${'wikiSize'} + ${'disk'} | ${'anything-else'} + `( + 'renders icon with name of $expected when name prop is $provided', + ({ expected, provided }) => { + createComponent({ name: provided }); + + expect(findGlIcon().props('name')).toBe(expected); + }, + ); + }); +}); diff --git a/spec/frontend/projects/storage_counter/mock_data.js b/spec/frontend/projects/storage_counter/mock_data.js index b9fa68b3ec7..6b3e23ac386 100644 --- a/spec/frontend/projects/storage_counter/mock_data.js +++ b/spec/frontend/projects/storage_counter/mock_data.js @@ -1,23 +1,6 @@ -export const mockGetProjectStorageCountGraphQLResponse = { - data: { - project: { - id: 'gid://gitlab/Project/20', - statistics: { - buildArtifactsSize: 400000.0, - pipelineArtifactsSize: 25000.0, - lfsObjectsSize: 4800000.0, - packagesSize: 3800000.0, - repositorySize: 3900000.0, - snippetsSize: 1200000.0, - storageSize: 15300000.0, - uploadsSize: 900000.0, - wikiSize: 300000.0, - __typename: 'ProjectStatistics', - }, - __typename: 'Project', - }, - }, -}; +import mockGetProjectStorageCountGraphQLResponse from 'test_fixtures/graphql/projects/storage_counter/project_storage.query.graphql.json'; + +export { mockGetProjectStorageCountGraphQLResponse }; export const mockEmptyResponse = { data: { project: null } }; @@ -37,7 +20,7 @@ export const defaultProvideValues = { export const projectData = { storage: { - totalUsage: '14.6 MiB', + totalUsage: '13.8 MiB', storageTypes: [ { storageType: { @@ -45,7 +28,7 @@ export const projectData = { name: 'Artifacts', description: 'Pipeline artifacts and job artifacts, created with CI/CD.', warningMessage: - 'There is a known issue with Artifact storage where the total could be incorrect for some projects. More details and progress are available in %{warningLinkStart}the epic%{warningLinkEnd}.', + 'Because of a known issue, the artifact total for some projects may be incorrect. For more details, read %{warningLinkStart}the epic%{warningLinkEnd}.', helpPath: '/build-artifacts', }, value: 400000, @@ -53,7 +36,7 @@ export const projectData = { { storageType: { id: 'lfsObjectsSize', - name: 'LFS Storage', + name: 'LFS storage', description: 'Audio samples, videos, datasets, and graphics.', helpPath: '/lsf-objects', }, @@ -72,7 +55,7 @@ export const projectData = { storageType: { id: 'repositorySize', name: 'Repository', - description: 'Git repository, managed by the Gitaly service.', + description: 'Git repository.', helpPath: '/repository', }, value: 3900000, @@ -84,7 +67,7 @@ export const projectData = { description: 'Shared bits of code and text.', helpPath: '/snippets', }, - value: 1200000, + value: 0, }, { storageType: { diff --git a/spec/frontend/projects/storage_counter/utils_spec.js b/spec/frontend/projects/storage_counter/utils_spec.js index 57c755266a0..fb91975a3cf 100644 --- a/spec/frontend/projects/storage_counter/utils_spec.js +++ b/spec/frontend/projects/storage_counter/utils_spec.js @@ -14,4 +14,21 @@ describe('parseGetProjectStorageResults', () => { ), ).toMatchObject(projectData); }); + + it('includes storage type with size of 0 in returned value', () => { + const mockedResponse = mockGetProjectStorageCountGraphQLResponse.data; + // ensuring a specific storage type item has size of 0 + mockedResponse.project.statistics.repositorySize = 0; + + const response = parseGetProjectStorageResults(mockedResponse, defaultProvideValues.helpLinks); + + expect(response.storage.storageTypes).toEqual( + expect.arrayContaining([ + { + storageType: expect.any(Object), + value: 0, + }, + ]), + ); + }); }); diff --git a/spec/frontend/projects/upload_file_experiment_tracking_spec.js b/spec/frontend/projects/upload_file_experiment_tracking_spec.js deleted file mode 100644 index 6817529e07e..00000000000 --- a/spec/frontend/projects/upload_file_experiment_tracking_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import ExperimentTracking from '~/experimentation/experiment_tracking'; -import { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking'; - -jest.mock('~/experimentation/experiment_tracking'); - -const eventName = 'click_upload_modal_form_submit'; -const fixture = `<a class='js-upload-file-experiment-trigger'></a><div class='project-home-panel empty-project'></div>`; - -beforeEach(() => { - document.body.innerHTML = fixture; -}); - -afterEach(() => { - document.body.innerHTML = ''; -}); - -describe('trackFileUploadEvent', () => { - it('initializes ExperimentTracking with the correct tracking event', () => { - trackFileUploadEvent(eventName); - - expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(eventName); - }); - - it('calls ExperimentTracking with the correct arguments', () => { - trackFileUploadEvent(eventName); - - expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', { - label: 'blob-upload-modal', - property: 'empty', - }); - }); - - it('calls ExperimentTracking with the correct arguments when the project is not empty', () => { - document.querySelector('.empty-project').remove(); - - trackFileUploadEvent(eventName); - - expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', { - label: 'blob-upload-modal', - property: 'nonempty', - }); - }); -}); |