diff options
Diffstat (limited to 'spec/frontend/projects')
7 files changed, 417 insertions, 16 deletions
diff --git a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js index b5ee62f2042..6ef49390c47 100644 --- a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js @@ -60,7 +60,7 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => { expect(chart.props('yAxisTitle')).toBe('Minutes'); expect(chart.props('xAxisTitle')).toBe('Commit'); expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData); - expect(chart.props('option')).toBe(wrapper.vm.$options.timesChartOptions); + expect(chart.props('option')).toBe(wrapper.vm.chartOptions); }); }); 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 5323c1afbb5..eacf858f22c 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 @@ -107,6 +107,29 @@ describe('ServiceDeskSetting', () => { }); }); + describe('project suffix', () => { + it('input is hidden', () => { + wrapper = createComponent({ + props: { customEmailEnabled: false }, + }); + + const input = wrapper.findByTestId('project-suffix'); + + expect(input.exists()).toBe(false); + }); + + it('input is enabled', () => { + wrapper = createComponent({ + props: { customEmailEnabled: true }, + }); + + const input = wrapper.findByTestId('project-suffix'); + + expect(input.exists()).toBe(true); + expect(input.attributes('disabled')).toBeUndefined(); + }); + }); + describe('customEmail is the same as incomingEmail', () => { const email = 'foo@bar.com'; diff --git a/spec/frontend/projects/storage_counter/components/app_spec.js b/spec/frontend/projects/storage_counter/components/app_spec.js new file mode 100644 index 00000000000..f3da01e0602 --- /dev/null +++ b/spec/frontend/projects/storage_counter/components/app_spec.js @@ -0,0 +1,150 @@ +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import StorageCounterApp from '~/projects/storage_counter/components/app.vue'; +import { TOTAL_USAGE_DEFAULT_TEXT } from '~/projects/storage_counter/constants'; +import getProjectStorageCount from '~/projects/storage_counter/queries/project_storage.query.graphql'; +import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue'; +import { + mockGetProjectStorageCountGraphQLResponse, + mockEmptyResponse, + projectData, + defaultProvideValues, +} from '../mock_data'; + +const localVue = createLocalVue(); +localVue.use(VueApollo); + +describe('Storage counter app', () => { + let wrapper; + + const createMockApolloProvider = ({ reject = false, mockedValue } = {}) => { + let response; + + if (reject) { + response = jest.fn().mockRejectedValue(mockedValue || new Error('GraphQL error')); + } else { + response = jest.fn().mockResolvedValue(mockedValue); + } + + const requestHandlers = [[getProjectStorageCount, response]]; + + return createMockApollo(requestHandlers); + }; + + const createComponent = ({ provide = {}, mockApollo } = {}) => { + wrapper = extendedWrapper( + shallowMount(StorageCounterApp, { + localVue, + apolloProvider: mockApollo, + provide: { + ...defaultProvideValues, + ...provide, + }, + }), + ); + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findUsagePercentage = () => wrapper.findByTestId('total-usage'); + const findUsageQuotasHelpLink = () => wrapper.findByTestId('usage-quotas-help-link'); + const findUsageGraph = () => wrapper.findComponent(UsageGraph); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('with apollo fetching successful', () => { + let mockApollo; + + beforeEach(async () => { + mockApollo = createMockApolloProvider({ + mockedValue: mockGetProjectStorageCountGraphQLResponse, + }); + createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('renders correct total usage', () => { + expect(findUsagePercentage().text()).toBe(projectData.storage.totalUsage); + }); + + it('renders correct usage quotas help link', () => { + expect(findUsageQuotasHelpLink().attributes('href')).toBe( + defaultProvideValues.helpLinks.usageQuotasHelpPagePath, + ); + }); + }); + + describe('with apollo loading', () => { + let mockApollo; + + beforeEach(() => { + mockApollo = createMockApolloProvider({ + mockedValue: new Promise(() => {}), + }); + createComponent({ mockApollo }); + }); + + it('should show loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + }); + + describe('with apollo returning empty data', () => { + let mockApollo; + + beforeEach(async () => { + mockApollo = createMockApolloProvider({ + mockedValue: mockEmptyResponse, + }); + createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('shows default text for total usage', () => { + expect(findUsagePercentage().text()).toBe(TOTAL_USAGE_DEFAULT_TEXT); + }); + }); + + describe('with apollo fetching error', () => { + let mockApollo; + + beforeEach(() => { + mockApollo = createMockApolloProvider(); + createComponent({ mockApollo, reject: true }); + }); + + it('renders gl-alert', () => { + expect(findAlert().exists()).toBe(true); + }); + }); + + describe('rendering <usage-graph />', () => { + let mockApollo; + + beforeEach(async () => { + mockApollo = createMockApolloProvider({ + mockedValue: mockGetProjectStorageCountGraphQLResponse, + }); + createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('renders usage-graph component if project.statistics exists', () => { + expect(findUsageGraph().exists()).toBe(true); + }); + + it('passes project.statistics to usage-graph component', () => { + const { + __typename, + ...statistics + } = mockGetProjectStorageCountGraphQLResponse.data.project.statistics; + expect(findUsageGraph().props('rootStorageStatistics')).toMatchObject(statistics); + }); + }); +}); diff --git a/spec/frontend/projects/storage_counter/components/storage_table_spec.js b/spec/frontend/projects/storage_counter/components/storage_table_spec.js new file mode 100644 index 00000000000..14298318fff --- /dev/null +++ b/spec/frontend/projects/storage_counter/components/storage_table_spec.js @@ -0,0 +1,62 @@ +import { GlTable } 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'; +import { projectData, defaultProvideValues } from '../mock_data'; + +describe('StorageTable', () => { + let wrapper; + + const defaultProps = { + storageTypes: projectData.storage.storageTypes, + }; + + const createComponent = (props = {}) => { + wrapper = extendedWrapper( + mount(StorageTable, { + propsData: { + ...defaultProps, + ...props, + }, + }), + ); + }; + + const findTable = () => wrapper.findComponent(GlTable); + + beforeEach(() => { + createComponent(); + }); + afterEach(() => { + wrapper.destroy(); + }); + + describe('with storage types', () => { + it.each(projectData.storage.storageTypes)( + 'renders table row correctly %o', + ({ storageType: { id, name, description } }) => { + expect(wrapper.findByTestId(`${id}-name`).text()).toBe(name); + expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description); + expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe( + defaultProvideValues.helpLinks[id.replace(`Size`, `HelpPagePath`)] + .replace(`Size`, ``) + .replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`), + ); + }, + ); + }); + + describe('without storage types', () => { + beforeEach(() => { + createComponent({ storageTypes: [] }); + }); + + it('should render the table header <th>', () => { + expect(findTable().find('th').exists()).toBe(true); + }); + + it('should not render any table data <td>', () => { + expect(findTable().find('td').exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/projects/storage_counter/mock_data.js b/spec/frontend/projects/storage_counter/mock_data.js new file mode 100644 index 00000000000..b9fa68b3ec7 --- /dev/null +++ b/spec/frontend/projects/storage_counter/mock_data.js @@ -0,0 +1,109 @@ +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', + }, + }, +}; + +export const mockEmptyResponse = { data: { project: null } }; + +export const defaultProvideValues = { + projectPath: '/project-path', + helpLinks: { + usageQuotasHelpPagePath: '/usage-quotas', + buildArtifactsHelpPagePath: '/build-artifacts', + lfsObjectsHelpPagePath: '/lsf-objects', + packagesHelpPagePath: '/packages', + repositoryHelpPagePath: '/repository', + snippetsHelpPagePath: '/snippets', + uploadsHelpPagePath: '/uploads', + wikiHelpPagePath: '/wiki', + }, +}; + +export const projectData = { + storage: { + totalUsage: '14.6 MiB', + storageTypes: [ + { + storageType: { + id: 'buildArtifactsSize', + 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}.', + helpPath: '/build-artifacts', + }, + value: 400000, + }, + { + storageType: { + id: 'lfsObjectsSize', + name: 'LFS Storage', + description: 'Audio samples, videos, datasets, and graphics.', + helpPath: '/lsf-objects', + }, + value: 4800000, + }, + { + storageType: { + id: 'packagesSize', + name: 'Packages', + description: 'Code packages and container images.', + helpPath: '/packages', + }, + value: 3800000, + }, + { + storageType: { + id: 'repositorySize', + name: 'Repository', + description: 'Git repository, managed by the Gitaly service.', + helpPath: '/repository', + }, + value: 3900000, + }, + { + storageType: { + id: 'snippetsSize', + name: 'Snippets', + description: 'Shared bits of code and text.', + helpPath: '/snippets', + }, + value: 1200000, + }, + { + storageType: { + id: 'uploadsSize', + name: 'Uploads', + description: 'File attachments and smaller design graphics.', + helpPath: '/uploads', + }, + value: 900000, + }, + { + storageType: { + id: 'wikiSize', + name: 'Wiki', + description: 'Wiki content.', + helpPath: '/wiki', + }, + value: 300000, + }, + ], + }, +}; diff --git a/spec/frontend/projects/storage_counter/utils_spec.js b/spec/frontend/projects/storage_counter/utils_spec.js new file mode 100644 index 00000000000..57c755266a0 --- /dev/null +++ b/spec/frontend/projects/storage_counter/utils_spec.js @@ -0,0 +1,17 @@ +import { parseGetProjectStorageResults } from '~/projects/storage_counter/utils'; +import { + mockGetProjectStorageCountGraphQLResponse, + projectData, + defaultProvideValues, +} from './mock_data'; + +describe('parseGetProjectStorageResults', () => { + it('parses project statistics correctly', () => { + expect( + parseGetProjectStorageResults( + mockGetProjectStorageCountGraphQLResponse.data, + defaultProvideValues.helpLinks, + ), + ).toMatchObject(projectData); + }); +}); diff --git a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js index 71c22998b08..6576ce70d60 100644 --- a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js +++ b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js @@ -1,51 +1,91 @@ import { GlBanner } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { setCookie, parseBoolean } from '~/lib/utils/common_utils'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; +import { mockTracking } from 'helpers/tracking_helper'; import TerraformNotification from '~/projects/terraform_notification/components/terraform_notification.vue'; - -jest.mock('~/lib/utils/common_utils'); +import { + EVENT_LABEL, + DISMISS_EVENT, + CLICK_EVENT, +} from '~/projects/terraform_notification/constants'; const terraformImagePath = '/path/to/image'; -const bannerDismissedKey = 'terraform_notification_dismissed'; describe('TerraformNotificationBanner', () => { let wrapper; + let trackingSpy; + let userCalloutDismissSpy; const provideData = { terraformImagePath, - bannerDismissedKey, }; const findBanner = () => wrapper.findComponent(GlBanner); - beforeEach(() => { + const createComponent = ({ shouldShowCallout = true } = {}) => { + userCalloutDismissSpy = jest.fn(); + wrapper = shallowMount(TerraformNotification, { provide: provideData, - stubs: { GlBanner }, + stubs: { + GlBanner, + UserCalloutDismisser: makeMockUserCalloutDismisser({ + dismiss: userCalloutDismissSpy, + shouldShowCallout, + }), + }, }); + }; + + beforeEach(() => { + createComponent(); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); afterEach(() => { wrapper.destroy(); - parseBoolean.mockReturnValue(false); }); - describe('when the dismiss cookie is not set', () => { + describe('when user has already dismissed the banner', () => { + beforeEach(() => { + createComponent({ + shouldShowCallout: false, + }); + }); + it('should not render the banner', () => { + expect(findBanner().exists()).toBe(false); + }); + }); + + describe("when user hasn't yet dismissed the banner", () => { it('should render the banner', () => { expect(findBanner().exists()).toBe(true); }); }); describe('when close button is clicked', () => { - beforeEach(async () => { - await findBanner().vm.$emit('close'); + beforeEach(() => { + wrapper.vm.$refs.calloutDismisser.dismiss = userCalloutDismissSpy; + findBanner().vm.$emit('close'); + }); + it('should send the dismiss event', () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, DISMISS_EVENT, { + label: EVENT_LABEL, + }); }); + it('should call the dismiss callback', () => { + expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1); + }); + }); - it('should set the cookie with the bannerDismissedKey', () => { - expect(setCookie).toHaveBeenCalledWith(bannerDismissedKey, true); + describe('when docs link is clicked', () => { + beforeEach(() => { + findBanner().vm.$emit('primary'); }); - it('should remove the banner', () => { - expect(findBanner().exists()).toBe(false); + it('should send button click event', () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, CLICK_EVENT, { + label: EVENT_LABEL, + }); }); }); }); |