diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /spec/frontend/vue_shared | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'spec/frontend/vue_shared')
124 files changed, 2239 insertions, 844 deletions
diff --git a/spec/frontend/vue_shared/alert_details/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js new file mode 100644 index 00000000000..dd9a7be6268 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js @@ -0,0 +1,361 @@ +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { mount, shallowMount } from '@vue/test-utils'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { joinPaths } from '~/lib/utils/url_utility'; +import Tracking from '~/tracking'; +import AlertDetails from '~/vue_shared/alert_details/components/alert_details.vue'; +import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue'; +import { SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants'; +import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql'; +import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; +import mockAlerts from './mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; +const environmentName = 'Production'; +const environmentPath = '/fake/path'; + +describe('AlertDetails', () => { + let environmentData = { name: environmentName, path: environmentPath }; + let mock; + let wrapper; + const projectPath = 'root/alerts'; + const projectIssuesPath = 'root/alerts/-/issues'; + const projectId = '1'; + const $router = { replace: jest.fn() }; + + function mountComponent({ + data, + loading = false, + mountMethod = shallowMount, + provide = {}, + stubs = {}, + } = {}) { + wrapper = extendedWrapper( + mountMethod(AlertDetails, { + provide: { + alertId: 'alertId', + projectPath, + projectIssuesPath, + projectId, + ...provide, + }, + data() { + return { + alert: { + ...mockAlert, + environment: environmentData, + }, + sidebarStatus: false, + ...data, + }; + }, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + sidebarStatus: {}, + }, + }, + $router, + $route: { params: {} }, + }, + stubs: { + ...stubs, + AlertSummaryRow, + }, + }), + ); + } + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + mock.restore(); + }); + + const findCreateIncidentBtn = () => wrapper.findByTestId('createIncidentBtn'); + const findViewIncidentBtn = () => wrapper.findByTestId('viewIncidentBtn'); + const findIncidentCreationAlert = () => wrapper.findByTestId('incidentCreationError'); + const findEnvironmentName = () => wrapper.findByTestId('environmentName'); + const findEnvironmentPath = () => wrapper.findByTestId('environmentPath'); + const findDetailsTable = () => wrapper.find(AlertDetailsTable); + const findMetricsTab = () => wrapper.findByTestId('metrics'); + + describe('Alert details', () => { + describe('when alert is null', () => { + beforeEach(() => { + mountComponent({ data: { alert: null } }); + }); + + it('shows an empty state', () => { + expect(wrapper.findByTestId('alertDetailsTabs').exists()).toBe(false); + }); + }); + + describe('when alert is present', () => { + beforeEach(() => { + mountComponent({ data: { alert: mockAlert } }); + }); + + it('renders a tab with overview information', () => { + expect(wrapper.findByTestId('overview').exists()).toBe(true); + }); + + it('renders a tab with an activity feed', () => { + expect(wrapper.findByTestId('activity').exists()).toBe(true); + }); + + it('renders severity', () => { + expect(wrapper.findByTestId('severity').text()).toBe(SEVERITY_LEVELS[mockAlert.severity]); + }); + + it('renders a title', () => { + expect(wrapper.findByTestId('title').text()).toBe(mockAlert.title); + }); + + it('renders a start time', () => { + expect(wrapper.findByTestId('startTimeItem').exists()).toBe(true); + expect(wrapper.findByTestId('startTimeItem').props('time')).toBe(mockAlert.startedAt); + }); + }); + + describe('individual alert fields', () => { + describe.each` + field | data | isShown + ${'eventCount'} | ${1} | ${true} + ${'eventCount'} | ${undefined} | ${false} + ${'monitoringTool'} | ${'New Relic'} | ${true} + ${'monitoringTool'} | ${undefined} | ${false} + ${'service'} | ${'Prometheus'} | ${true} + ${'service'} | ${undefined} | ${false} + ${'runbook'} | ${undefined} | ${false} + ${'runbook'} | ${'run.com'} | ${true} + `(`$desc`, ({ field, data, isShown }) => { + beforeEach(() => { + mountComponent({ data: { alert: { ...mockAlert, [field]: data } } }); + }); + + it(`${field} is ${isShown ? 'displayed' : 'hidden'} correctly`, () => { + const element = wrapper.findByTestId(field); + if (isShown) { + expect(element.text()).toContain(data.toString()); + } else { + expect(wrapper.findByTestId(field).exists()).toBe(false); + } + }); + }); + }); + + describe('environment fields', () => { + it('should show the environment name with a link to the path', () => { + mountComponent(); + const path = findEnvironmentPath(); + + expect(findEnvironmentName().exists()).toBe(false); + expect(path.text()).toBe(environmentName); + expect(path.attributes('href')).toBe(environmentPath); + }); + + it('should only show the environment name if the path is not provided', () => { + environmentData = { name: environmentName, path: null }; + mountComponent(); + + expect(findEnvironmentPath().exists()).toBe(false); + expect(findEnvironmentName().text()).toBe(environmentName); + }); + }); + + describe('Threat Monitoring details', () => { + it('should not render the metrics tab', () => { + mountComponent({ + data: { alert: mockAlert, provide: { isThreatMonitoringPage: true } }, + }); + expect(findMetricsTab().exists()).toBe(false); + }); + }); + + describe('Create incident from alert', () => { + it('should display "View incident" button that links the incident page when incident exists', () => { + const issueIid = '3'; + mountComponent({ + data: { alert: { ...mockAlert, issueIid }, sidebarStatus: false }, + }); + + expect(findViewIncidentBtn().exists()).toBe(true); + expect(findViewIncidentBtn().attributes('href')).toBe( + joinPaths(projectIssuesPath, issueIid), + ); + expect(findCreateIncidentBtn().exists()).toBe(false); + }); + + it('should display "Create incident" button when incident doesn\'t exist yet', () => { + const issueIid = null; + mountComponent({ + mountMethod: mount, + data: { alert: { ...mockAlert, issueIid } }, + }); + + return wrapper.vm.$nextTick().then(() => { + expect(findViewIncidentBtn().exists()).toBe(false); + expect(findCreateIncidentBtn().exists()).toBe(true); + }); + }); + + it('calls `$apollo.mutate` with `createIssueQuery`', () => { + const issueIid = '10'; + mountComponent({ + mountMethod: mount, + data: { alert: { ...mockAlert } }, + }); + + jest + .spyOn(wrapper.vm.$apollo, 'mutate') + .mockResolvedValue({ data: { createAlertIssue: { issue: { iid: issueIid } } } }); + findCreateIncidentBtn().trigger('click'); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: createIssueMutation, + variables: { + iid: mockAlert.iid, + projectPath, + }, + }); + }); + + it('shows error alert when incident creation fails ', async () => { + const errorMsg = 'Something went wrong'; + mountComponent({ + mountMethod: mount, + data: { alert: { ...mockAlert, alertIid: 1 } }, + }); + + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg); + findCreateIncidentBtn().trigger('click'); + + await waitForPromises(); + expect(findIncidentCreationAlert().text()).toBe(errorMsg); + }); + }); + + describe('View full alert details', () => { + beforeEach(() => { + mountComponent({ data: { alert: mockAlert } }); + }); + + it('should display a table of raw alert details data', () => { + expect(findDetailsTable().exists()).toBe(true); + }); + }); + + describe('loading state', () => { + beforeEach(() => { + mountComponent({ loading: true }); + }); + + it('displays a loading state when loading', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + }); + + describe('error state', () => { + it('displays a error state correctly', () => { + mountComponent({ data: { errored: true } }); + expect(wrapper.find(GlAlert).exists()).toBe(true); + }); + + it('renders html-errors correctly', () => { + mountComponent({ + data: { errored: true, sidebarErrorMessage: '<span data-testid="htmlError" />' }, + }); + expect(wrapper.findByTestId('htmlError').exists()).toBe(true); + }); + + it('does not display an error when dismissed', () => { + mountComponent({ data: { errored: true, isErrorDismissed: true } }); + expect(wrapper.find(GlAlert).exists()).toBe(false); + }); + }); + + describe('header', () => { + const findHeader = () => wrapper.findByTestId('alert-header'); + const stubs = { TimeAgoTooltip: { template: '<span>now</span>' } }; + + describe('individual header fields', () => { + describe.each` + createdAt | monitoringTool | result + ${'2020-04-17T23:18:14.996Z'} | ${null} | ${'Alert Reported now'} + ${'2020-04-17T23:18:14.996Z'} | ${'Datadog'} | ${'Alert Reported now by Datadog'} + `( + `When createdAt=$createdAt, monitoringTool=$monitoringTool`, + ({ createdAt, monitoringTool, result }) => { + beforeEach(() => { + mountComponent({ + data: { alert: { ...mockAlert, createdAt, monitoringTool } }, + mountMethod: mount, + stubs, + }); + }); + + it('header text is shown correctly', () => { + expect(findHeader().text()).toBe(result); + }); + }, + ); + }); + }); + + describe('tab navigation', () => { + beforeEach(() => { + mountComponent({ data: { alert: mockAlert } }); + }); + + it.each` + index | tabId + ${0} | ${'overview'} + ${1} | ${'metrics'} + ${2} | ${'activity'} + `('will navigate to the correct tab via $tabId', ({ index, tabId }) => { + wrapper.setData({ currentTabIndex: index }); + expect($router.replace).toHaveBeenCalledWith({ name: 'tab', params: { tabId } }); + }); + }); + }); + + describe('Snowplow tracking', () => { + const mountOptions = { + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alert: mockAlert }, + loading: false, + }; + + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + }); + + it('should not track alert details page views when the tracking options do not exist', () => { + mountComponent(mountOptions); + expect(Tracking.event).not.toHaveBeenCalled(); + }); + + it('should track alert details page views when the tracking options exist', () => { + const trackAlertsDetailsViewsOptions = { + category: 'Alert Management', + action: 'view_alert_details', + }; + mountComponent({ ...mountOptions, provide: { trackAlertsDetailsViewsOptions } }); + const { category, action } = trackAlertsDetailsViewsOptions; + expect(Tracking.event).toHaveBeenCalledWith(category, action); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js new file mode 100644 index 00000000000..87ad5e36564 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js @@ -0,0 +1,112 @@ +import { mount } from '@vue/test-utils'; +import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql'; +import SidebarTodo from '~/vue_shared/alert_details/components/sidebar/sidebar_todo.vue'; +import createAlertTodoMutation from '~/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql'; +import mockAlerts from './mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('Alert Details Sidebar To Do', () => { + let wrapper; + + function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) { + wrapper = mount(SidebarTodo, { + propsData: { + alert: { ...mockAlert }, + ...data, + sidebarCollapsed, + projectPath: 'projectPath', + }, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + }, + }, + }, + stubs, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + const findToDoButton = () => wrapper.find('[data-testid="alert-todo-button"]'); + + describe('updating the alert to do', () => { + const mockUpdatedMutationResult = { + data: { + updateAlertTodo: { + errors: [], + alert: {}, + }, + }, + }; + + describe('adding a todo', () => { + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('renders a button for adding a To-Do', async () => { + await wrapper.vm.$nextTick(); + + expect(findToDoButton().text()).toBe('Add a to do'); + }); + + it('calls `$apollo.mutate` with `createAlertTodoMutation` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + + findToDoButton().trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: createAlertTodoMutation, + variables: { + iid: '1527542', + projectPath: 'projectPath', + }, + }); + }); + }); + + describe('removing a todo', () => { + beforeEach(() => { + mountComponent({ + data: { alert: { ...mockAlert, todos: { nodes: [{ id: '1234' }] } } }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('renders a Mark As Done button when todo is present', async () => { + await wrapper.vm.$nextTick(); + + expect(findToDoButton().text()).toBe('Mark as done'); + }); + + it('calls `$apollo.mutate` with `todoMarkDoneMutation` mutation and variables containing `id`', async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + + findToDoButton().trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: todoMarkDoneMutation, + update: expect.anything(), + variables: { + id: '1234', + }, + }); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js new file mode 100644 index 00000000000..b5a61a4adc1 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js @@ -0,0 +1,62 @@ +import { shallowMount } from '@vue/test-utils'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue'; +import AlertMetrics from '~/vue_shared/alert_details/components/alert_metrics.vue'; + +jest.mock('~/monitoring/stores', () => ({ + monitoringDashboard: {}, +})); + +jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({ + render(h) { + return h('div'); + }, +})); + +describe('Alert Metrics', () => { + let wrapper; + const mock = new MockAdapter(axios); + + function mountComponent({ props } = {}) { + wrapper = shallowMount(AlertMetrics, { + propsData: { + ...props, + }, + }); + } + + const findChart = () => wrapper.find(MetricEmbed); + const findEmptyState = () => wrapper.find({ ref: 'emptyState' }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + afterAll(() => { + mock.restore(); + }); + + describe('Empty state', () => { + it('should display a message when metrics dashboard url is not provided ', () => { + mountComponent(); + expect(findChart().exists()).toBe(false); + expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload."); + }); + }); + + describe('Chart', () => { + it('should be rendered when dashboard url is provided', async () => { + mountComponent({ props: { dashboardUrl: 'metrics.url' } }); + + await waitForPromises(); + await wrapper.vm.$nextTick(); + + expect(findEmptyState().exists()).toBe(false); + expect(findChart().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/alert_status_spec.js b/spec/frontend/vue_shared/alert_details/alert_status_spec.js new file mode 100644 index 00000000000..a866fc13539 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/alert_status_spec.js @@ -0,0 +1,166 @@ +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import updateAlertStatusMutation from '~/graphql_shared//mutations/alert_status_update.mutation.graphql'; +import Tracking from '~/tracking'; +import AlertManagementStatus from '~/vue_shared/alert_details/components/alert_status.vue'; +import mockAlerts from './mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('AlertManagementStatus', () => { + let wrapper; + const findStatusDropdown = () => wrapper.find(GlDropdown); + const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem); + + const selectFirstStatusOption = () => { + findFirstStatusOption().vm.$emit('click'); + + return waitForPromises(); + }; + + function mountComponent({ props = {}, provide = {}, loading = false, stubs = {} } = {}) { + wrapper = shallowMount(AlertManagementStatus, { + propsData: { + alert: { ...mockAlert }, + projectPath: 'gitlab-org/gitlab', + isSidebar: false, + ...props, + }, + provide, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + }, + }, + }, + stubs, + }); + } + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + describe('updating the alert status', () => { + const iid = '1527542'; + const mockUpdatedMutationResult = { + data: { + updateAlertStatus: { + errors: [], + alert: { + iid, + status: 'acknowledged', + }, + }, + }, + }; + + beforeEach(() => { + mountComponent({}); + }); + + it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + findFirstStatusOption().vm.$emit('click'); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: updateAlertStatusMutation, + variables: { + iid, + status: 'TRIGGERED', + projectPath: 'gitlab-org/gitlab', + }, + }); + }); + + describe('when a request fails', () => { + beforeEach(() => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); + }); + + it('emits an error', async () => { + await selectFirstStatusOption(); + + expect(wrapper.emitted('alert-error')[0]).toEqual([ + 'There was an error while updating the status of the alert. Please try again.', + ]); + }); + + it('emits an error when triggered a second time', async () => { + await selectFirstStatusOption(); + await wrapper.vm.$nextTick(); + await selectFirstStatusOption(); + // Should emit two errors [0,1] + expect(wrapper.emitted('alert-error').length > 1).toBe(true); + }); + }); + + it('shows an error when response includes HTML errors', async () => { + const mockUpdatedMutationErrorResult = { + data: { + updateAlertStatus: { + errors: ['<span data-testid="htmlError" />'], + alert: { + iid, + status: 'acknowledged', + }, + }, + }, + }; + + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationErrorResult); + + await selectFirstStatusOption(); + + expect(wrapper.emitted('alert-error').length > 0).toBe(true); + expect(wrapper.emitted('alert-error')[0]).toEqual([ + 'There was an error while updating the status of the alert. <span data-testid="htmlError" />', + ]); + }); + }); + + describe('Snowplow tracking', () => { + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + }); + + it('should not track alert status updates when the tracking options do not exist', () => { + mountComponent({}); + Tracking.event.mockClear(); + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); + findFirstStatusOption().vm.$emit('click'); + setImmediate(() => { + expect(Tracking.event).not.toHaveBeenCalled(); + }); + }); + + it('should track alert status updates when the tracking options exist', () => { + const trackAlertStatusUpdateOptions = { + category: 'Alert Management', + action: 'update_alert_status', + label: 'Status', + }; + mountComponent({ provide: { trackAlertStatusUpdateOptions } }); + Tracking.event.mockClear(); + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); + findFirstStatusOption().vm.$emit('click'); + const status = findFirstStatusOption().text(); + setImmediate(() => { + const { category, action, label } = trackAlertStatusUpdateOptions; + expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status }); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js b/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js new file mode 100644 index 00000000000..a2981478954 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js @@ -0,0 +1,40 @@ +import { shallowMount } from '@vue/test-utils'; +import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue'; + +const label = 'a label'; +const value = 'a value'; + +describe('AlertSummaryRow', () => { + let wrapper; + + function mountComponent({ mountMethod = shallowMount, props, defaultSlot } = {}) { + wrapper = mountMethod(AlertSummaryRow, { + propsData: props, + scopedSlots: { + default: defaultSlot, + }, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + describe('Alert Summary Row', () => { + beforeEach(() => { + mountComponent({ + props: { + label, + }, + defaultSlot: `<span class="value">${value}</span>`, + }); + }); + + it('should display a label and a value', () => { + expect(wrapper.text()).toBe(`${label} ${value}`); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/mocks/alerts.json b/spec/frontend/vue_shared/alert_details/mocks/alerts.json new file mode 100644 index 00000000000..5267a4fe50d --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/mocks/alerts.json @@ -0,0 +1,71 @@ +[ + { + "iid": "1527542", + "title": "SyntaxError: Invalid or unexpected token", + "severity": "CRITICAL", + "eventCount": 7, + "createdAt": "2020-04-17T23:18:14.996Z", + "startedAt": "2020-04-17T23:18:14.996Z", + "endedAt": "2020-04-17T23:18:14.996Z", + "status": "TRIGGERED", + "assignees": { "nodes": [] }, + "notes": { "nodes": [] }, + "todos": { "nodes": [] } + }, + { + "iid": "1527543", + "title": "Some other alert Some other alert Some other alert Some other alert Some other alert Some other alert", + "severity": "MEDIUM", + "eventCount": 1, + "startedAt": "2020-04-17T23:18:14.996Z", + "endedAt": "2020-04-17T23:18:14.996Z", + "status": "ACKNOWLEDGED", + "assignees": { "nodes": [{ "username": "root", "avatarUrl": "/url", "name": "root" }] }, + "issueIid": "1", + "notes": { + "nodes": [ + { + "id": "gid://gitlab/Note/1628", + "author": { + "id": "gid://gitlab/User/1", + "state": "active", + "__typename": "User", + "avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", + "name": "Administrator", + "username": "root", + "webUrl": "http://192.168.1.4:3000/root" + }, + "systemNoteIconName": "user" + } + ] + }, + "todos": { "nodes": [] } + }, + { + "iid": "1527544", + "title": "SyntaxError: Invalid or unexpected token", + "severity": "LOW", + "eventCount": 4, + "startedAt": "2020-04-17T23:18:14.996Z", + "endedAt": "2020-04-17T23:18:14.996Z", + "status": "RESOLVED", + "assignees": { "nodes": [{ "username": "root", "avatarUrl": "/url", "name": "root" }] }, + "notes": { + "nodes": [ + { + "id": "gid://gitlab/Note/1629", + "author": { + "id": "gid://gitlab/User/2", + "state": "active", + "__typename": "User", + "avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", + "name": "Administrator", + "username": "root", + "webUrl": "http://192.168.1.4:3000/root" + } + } + ] + }, + "todos": { "nodes": [] } + } +] diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js new file mode 100644 index 00000000000..28646994ed1 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js @@ -0,0 +1,173 @@ +import { GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue'; +import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue'; +import AlertSetAssignees from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql'; +import mockAlerts from '../mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('Alert Details Sidebar Assignees', () => { + let wrapper; + let mock; + + function mountComponent({ + data, + users = [], + isDropdownSearching = false, + sidebarCollapsed = true, + loading = false, + stubs = {}, + } = {}) { + wrapper = shallowMount(SidebarAssignees, { + data() { + return { + users, + isDropdownSearching, + }; + }, + propsData: { + alert: { ...mockAlert }, + ...data, + sidebarCollapsed, + projectPath: 'projectPath', + projectId: '1', + }, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + }, + }, + }, + stubs, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + mock.restore(); + }); + + const findAssigned = () => wrapper.find('[data-testid="assigned-users"]'); + const findUnassigned = () => wrapper.find('[data-testid="unassigned-users"]'); + + describe('updating the alert status', () => { + const mockUpdatedMutationResult = { + data: { + alertSetAssignees: { + errors: [], + alert: { + assigneeUsernames: ['root'], + }, + }, + }, + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + const path = '/-/autocomplete/users.json'; + const users = [ + { + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: 1, + name: 'User 1', + username: 'root', + }, + { + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: 2, + name: 'User 2', + username: 'not-root', + }, + ]; + + mock.onGet(path).replyOnce(200, users); + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + users, + stubs: { + SidebarAssignee, + }, + }); + }); + + it('renders a unassigned option', async () => { + wrapper.setData({ isDropdownSearching: false }); + await wrapper.vm.$nextTick(); + expect(wrapper.find(GlDropdownItem).text()).toBe('Unassigned'); + }); + + it('calls `$apollo.mutate` with `AlertSetAssignees` mutation and variables containing `iid`, `assigneeUsernames`, & `projectPath`', async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + wrapper.setData({ isDropdownSearching: false }); + + await wrapper.vm.$nextTick(); + wrapper.find(SidebarAssignee).vm.$emit('update-alert-assignees', 'root'); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: AlertSetAssignees, + variables: { + iid: '1527542', + assigneeUsernames: ['root'], + projectPath: 'projectPath', + }, + }); + }); + + it('emits an error when request contains error messages', () => { + wrapper.setData({ isDropdownSearching: false }); + const errorMutationResult = { + data: { + alertSetAssignees: { + errors: ['There was a problem for sure.'], + alert: {}, + }, + }, + }; + + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult); + return wrapper.vm + .$nextTick() + .then(() => { + const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0); + SideBarAssigneeItem.vm.$emit('update-alert-assignees'); + }) + .then(() => { + expect(wrapper.emitted('alert-error')).toBeDefined(); + }); + }); + + it('stops updating and cancels loading when the request fails', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); + wrapper.vm.updateAlertAssignees('root'); + expect(findUnassigned().text()).toBe('assign yourself'); + }); + + it('shows a user avatar, username and full name when a user is set', () => { + mountComponent({ + data: { alert: mockAlerts[1] }, + sidebarCollapsed: false, + loading: false, + stubs: { + SidebarAssignee, + }, + }); + + expect(findAssigned().find('img').attributes('src')).toBe('/url'); + expect(findAssigned().find('.dropdown-menu-user-full-name').text()).toBe('root'); + expect(findAssigned().find('.dropdown-menu-user-username').text()).toBe('@root'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js new file mode 100644 index 00000000000..70cf2597963 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js @@ -0,0 +1,95 @@ +import { shallowMount, mount } from '@vue/test-utils'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import AlertSidebar from '~/vue_shared/alert_details/components/alert_sidebar.vue'; +import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue'; +import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue'; +import mockAlerts from '../mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('Alert Details Sidebar', () => { + let wrapper; + let mock; + + function mountComponent({ + mountMethod = shallowMount, + stubs = {}, + alert = {}, + provide = {}, + } = {}) { + wrapper = mountMethod(AlertSidebar, { + data() { + return { + sidebarStatus: false, + }; + }, + propsData: { + alert, + }, + provide: { + projectPath: 'projectPath', + projectId: '1', + ...provide, + }, + stubs, + mocks: { + $apollo: { + queries: { + sidebarStatus: {}, + }, + }, + }, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + mock.restore(); + }); + + describe('the sidebar renders', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mountComponent(); + }); + + it('open as default', () => { + expect(wrapper.classes('right-sidebar-expanded')).toBe(true); + }); + + it('should render side bar assignee dropdown', () => { + mountComponent({ + mountMethod: mount, + alert: mockAlert, + }); + expect(wrapper.find(SidebarAssignees).exists()).toBe(true); + }); + + it('should render side bar status dropdown', () => { + mountComponent({ + mountMethod: mount, + alert: mockAlert, + }); + expect(wrapper.find(SidebarStatus).exists()).toBe(true); + }); + }); + + describe('the sidebar renders for threat monitoring', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mountComponent(); + }); + + it('should not render side bar status dropdown', () => { + mountComponent({ + mountMethod: mount, + alert: mockAlert, + provide: { isThreatMonitoringPage: true }, + }); + expect(wrapper.find(SidebarStatus).exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js new file mode 100644 index 00000000000..f5b9efb4d98 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js @@ -0,0 +1,103 @@ +import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql'; +import AlertSidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue'; +import mockAlerts from '../mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('Alert Details Sidebar Status', () => { + let wrapper; + const findStatusDropdown = () => wrapper.find(GlDropdown); + const findStatusDropdownItem = () => wrapper.find(GlDropdownItem); + const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findStatusDropdownHeader = () => wrapper.find('[data-testid="dropdown-header"]'); + + function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) { + wrapper = mount(AlertSidebarStatus, { + propsData: { + alert: { ...mockAlert }, + ...data, + sidebarCollapsed, + projectPath: 'projectPath', + }, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + }, + }, + }, + stubs, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('Alert Sidebar Dropdown Status', () => { + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('displays status dropdown', () => { + expect(findStatusDropdown().exists()).toBe(true); + }); + + it('displays the dropdown status header', () => { + expect(findStatusDropdownHeader().exists()).toBe(true); + }); + + describe('updating the alert status', () => { + const mockUpdatedMutationResult = { + data: { + updateAlertStatus: { + errors: [], + alert: { + status: 'acknowledged', + }, + }, + }, + }; + + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + findStatusDropdownItem().vm.$emit('click'); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: updateAlertStatusMutation, + variables: { + iid: '1527542', + status: 'TRIGGERED', + projectPath: 'projectPath', + }, + }); + }); + + it('stops updating when the request fails', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); + findStatusDropdownItem().vm.$emit('click'); + expect(findStatusLoadingIcon().exists()).toBe(false); + expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered'); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js new file mode 100644 index 00000000000..a5a9fb55737 --- /dev/null +++ b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js @@ -0,0 +1,40 @@ +import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import SystemNote from '~/vue_shared/alert_details/components/system_notes/system_note.vue'; +import mockAlerts from '../mocks/alerts.json'; + +const mockAlert = mockAlerts[1]; + +describe('Alert Details System Note', () => { + let wrapper; + + function mountComponent({ stubs = {} } = {}) { + wrapper = shallowMount(SystemNote, { + propsData: { + note: { ...mockAlert.notes.nodes[0] }, + }, + stubs, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + describe('System notes', () => { + beforeEach(() => { + mountComponent({}); + }); + + it('renders the correct system note', () => { + const noteId = wrapper.find('.note-wrapper').attributes('id'); + const iconName = wrapper.find(GlIcon).attributes('name'); + + expect(noteId).toBe('note_1628'); + expect(iconName).toBe(mockAlert.notes.nodes[0].systemNoteIconName); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap index 20ea897e29c..3be609f0dad 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap @@ -18,13 +18,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="thumbsup" /> - - </span> <span @@ -52,13 +48,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="thumbsdown" /> - - </span> <span @@ -86,13 +78,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="smile" /> - - </span> <span @@ -120,13 +108,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="ok_hand" /> - - </span> <span @@ -154,13 +138,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="cactus" /> - - </span> <span @@ -188,13 +168,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="a" /> - - </span> <span @@ -222,13 +198,9 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="award-emoji-block" data-testid="award-html" > - - <gl-emoji data-name="b" /> - - </span> <span diff --git a/spec/frontend/vue_shared/components/actions_button_spec.js b/spec/frontend/vue_shared/components/actions_button_spec.js index 2ac4bfda29a..e5b7b693cb5 100644 --- a/spec/frontend/vue_shared/components/actions_button_spec.js +++ b/spec/frontend/vue_shared/components/actions_button_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlDropdown, GlDropdownDivider, GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import ActionsButton from '~/vue_shared/components/actions_button.vue'; diff --git a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js index 22643a17b2b..f592db935ec 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; +import { handleBlobRichViewer } from '~/blob/viewer'; import RichViewer from '~/vue_shared/components/blob_viewers/rich_viewer.vue'; import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue'; -import { handleBlobRichViewer } from '~/blob/viewer'; jest.mock('~/blob/viewer'); diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js index 8434fdaccde..9a0616343fe 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue'; import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants'; +import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue'; describe('Blob Simple Viewer component', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js index 80918c5e771..6b9658a6d18 100644 --- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js +++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue'; const changedFile = () => ({ changed: true }); diff --git a/spec/frontend/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js index a633ef65aa4..a943d931f67 100644 --- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js +++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js @@ -1,10 +1,9 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import ciBadge from '~/vue_shared/components/ci_badge_link.vue'; +import { shallowMount } from '@vue/test-utils'; +import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; describe('CI Badge Link Component', () => { - let CIBadge; - let vm; + let wrapper; const statuses = { canceled: { @@ -72,29 +71,30 @@ describe('CI Badge Link Component', () => { }, }; - beforeEach(() => { - CIBadge = Vue.extend(ciBadge); - }); + const findIcon = () => wrapper.findComponent(CiIcon); + + const createComponent = (propsData) => { + wrapper = shallowMount(CiBadge, { propsData }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - it('should render each status badge', () => { - Object.keys(statuses).map((status) => { - vm = mountComponent(CIBadge, { status: statuses[status] }); + it.each(Object.keys(statuses))('should render badge for status: %s', (status) => { + createComponent({ status: statuses[status] }); - expect(vm.$el.getAttribute('href')).toEqual(statuses[status].details_path); - expect(vm.$el.textContent.trim()).toEqual(statuses[status].text); - expect(vm.$el.getAttribute('class')).toContain(`ci-status ci-${statuses[status].group}`); - expect(vm.$el.querySelector('svg')).toBeDefined(); - return vm; - }); + expect(wrapper.attributes('href')).toBe(statuses[status].details_path); + expect(wrapper.text()).toBe(statuses[status].text); + expect(wrapper.classes()).toContain('ci-status'); + expect(wrapper.classes()).toContain(`ci-${statuses[status].group}`); + expect(findIcon().exists()).toBe(true); }); it('should not render label', () => { - vm = mountComponent(CIBadge, { status: statuses.canceled, showText: false }); + createComponent({ status: statuses.canceled, showText: false }); - expect(vm.$el.textContent.trim()).toEqual(''); + expect(wrapper.text()).toBe(''); }); }); diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js index 63afe631063..6d52db7ae65 100644 --- a/spec/frontend/vue_shared/components/ci_icon_spec.js +++ b/spec/frontend/vue_shared/components/ci_icon_spec.js @@ -1,122 +1,51 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; +import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import ciIcon from '~/vue_shared/components/ci_icon.vue'; describe('CI Icon component', () => { - const Component = Vue.extend(ciIcon); - let vm; + let wrapper; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); it('should render a span element with an svg', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_success', - }, - }); - - expect(vm.$el.tagName).toEqual('SPAN'); - expect(vm.$el.querySelector('span > svg')).toBeDefined(); - }); - - it('should render a success status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_success', - group: 'success', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-success')).toEqual(true); - }); - - it('should render a failed status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_failed', - group: 'failed', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-failed')).toEqual(true); - }); - - it('should render success with warnings status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_warning', - group: 'warning', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-warning')).toEqual(true); - }); - - it('should render pending status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_pending', - group: 'pending', - }, + wrapper = shallowMount(ciIcon, { + propsData: { + status: { + icon: 'status_success', + }, + }, + }); + + expect(wrapper.find('span').exists()).toBe(true); + expect(wrapper.find(GlIcon).exists()).toBe(true); + }); + + describe('rendering a status', () => { + it.each` + icon | group | cssClass + ${'status_success'} | ${'success'} | ${'ci-status-icon-success'} + ${'status_failed'} | ${'failed'} | ${'ci-status-icon-failed'} + ${'status_warning'} | ${'warning'} | ${'ci-status-icon-warning'} + ${'status_pending'} | ${'pending'} | ${'ci-status-icon-pending'} + ${'status_running'} | ${'running'} | ${'ci-status-icon-running'} + ${'status_created'} | ${'created'} | ${'ci-status-icon-created'} + ${'status_skipped'} | ${'skipped'} | ${'ci-status-icon-skipped'} + ${'status_canceled'} | ${'canceled'} | ${'ci-status-icon-canceled'} + ${'status_manual'} | ${'manual'} | ${'ci-status-icon-manual'} + `('should render a $group status', ({ icon, group, cssClass }) => { + wrapper = shallowMount(ciIcon, { + propsData: { + status: { + icon, + group, + }, + }, + }); + + expect(wrapper.classes()).toContain(cssClass); }); - - expect(vm.$el.classList.contains('ci-status-icon-pending')).toEqual(true); - }); - - it('should render running status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_running', - group: 'running', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-running')).toEqual(true); - }); - - it('should render created status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_created', - group: 'created', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-created')).toEqual(true); - }); - - it('should render skipped status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_skipped', - group: 'skipped', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-skipped')).toEqual(true); - }); - - it('should render canceled status', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_canceled', - group: 'canceled', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-canceled')).toEqual(true); - }); - - it('should render status for manual action', () => { - vm = mountComponent(Component, { - status: { - icon: 'status_manual', - group: 'manual', - }, - }); - - expect(vm.$el.classList.contains('ci-status-icon-manual')).toEqual(true); }); }); diff --git a/spec/frontend/vue_shared/components/clipboard_button_spec.js b/spec/frontend/vue_shared/components/clipboard_button_spec.js index 0d4266ce82f..ab4008484e5 100644 --- a/spec/frontend/vue_shared/components/clipboard_button_spec.js +++ b/spec/frontend/vue_shared/components/clipboard_button_spec.js @@ -1,7 +1,7 @@ -import { mount } from '@vue/test-utils'; import { GlButton } from '@gitlab/ui'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { mount } from '@vue/test-utils'; import initCopyToClipboard from '~/behaviors/copy_to_clipboard'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; describe('clipboard button', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/clone_dropdown_spec.js b/spec/frontend/vue_shared/components/clone_dropdown_spec.js index 5b8576ad761..eefd1838988 100644 --- a/spec/frontend/vue_shared/components/clone_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/clone_dropdown_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlFormInputGroup, GlDropdownSectionHeader } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import CloneDropdown from '~/vue_shared/components/clone_dropdown.vue'; describe('Clone Dropdown Button', () => { diff --git a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js index c8fe6c3131c..d30f36ec63c 100644 --- a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js +++ b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js @@ -13,6 +13,7 @@ describe('ColorPicker', () => { }; const setColor = '#000000'; + const invalidText = 'Please enter a valid hex (#RRGGBB or #RGB) color value'; const label = () => wrapper.find(GlFormGroup).attributes('label'); const colorPreview = () => wrapper.find('[data-testid="color-preview"]'); const colorPicker = () => wrapper.find(GlFormInput); @@ -28,8 +29,6 @@ describe('ColorPicker', () => { '#428BCA': 'Moderate blue', '#44AD8E': 'Lime green', }; - - createComponent(shallowMount); }); afterEach(() => { @@ -38,6 +37,8 @@ describe('ColorPicker', () => { describe('label', () => { it('hides the label if the label is not passed', () => { + createComponent(shallowMount); + expect(label()).toBe(''); }); @@ -55,43 +56,37 @@ describe('ColorPicker', () => { expect(colorPreview().attributes('style')).toBe(undefined); expect(colorPicker().attributes('value')).toBe(undefined); expect(colorInput().props('value')).toBe(''); + expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-gray-400'); }); it('has a color set on initialization', () => { - createComponent(shallowMount, { setColor }); + createComponent(mount, { value: setColor }); - expect(wrapper.vm.$data.selectedColor).toBe(setColor); + expect(colorInput().props('value')).toBe(setColor); }); it('emits input event from component when a color is selected', async () => { createComponent(); await colorInput().setValue(setColor); - expect(wrapper.emitted().input[0]).toEqual([setColor]); + expect(wrapper.emitted().input[0]).toStrictEqual([setColor]); }); it('trims spaces from submitted colors', async () => { createComponent(); await colorInput().setValue(` ${setColor} `); - expect(wrapper.vm.$data.selectedColor).toBe(setColor); + expect(wrapper.emitted().input[0]).toStrictEqual([setColor]); + expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-gray-400'); + expect(colorInput().attributes('class')).not.toContain('is-invalid'); }); - it('shows invalid feedback when an invalid color is used', async () => { - createComponent(); - await colorInput().setValue('abcd'); - - expect(invalidFeedback().text()).toBe( - 'Please enter a valid hex (#RRGGBB or #RGB) color value', - ); - expect(wrapper.emitted().input).toBe(undefined); - }); - - it('shows an invalid feedback border on the preview when an invalid color is used', async () => { - createComponent(); - await colorInput().setValue('abcd'); + it('shows invalid feedback when the state is marked as invalid', async () => { + createComponent(mount, { invalidFeedback: invalidText, state: false }); + expect(invalidFeedback().text()).toBe(invalidText); expect(colorPreview().attributes('class')).toContain('gl-inset-border-1-red-500'); + expect(colorInput().attributes('class')).toContain('is-invalid'); }); }); @@ -100,14 +95,14 @@ describe('ColorPicker', () => { createComponent(); await colorInput().setValue(setColor); - expect(wrapper.vm.$data.selectedColor).toBe(setColor); + expect(wrapper.emitted().input[0]).toStrictEqual([setColor]); }); it('has color picker value entered', async () => { createComponent(); await colorPicker().setValue(setColor); - expect(wrapper.vm.$data.selectedColor).toBe(setColor); + expect(wrapper.emitted().input[0]).toStrictEqual([setColor]); }); }); @@ -132,7 +127,7 @@ describe('ColorPicker', () => { createComponent(); await presetColors().at(0).trigger('click'); - expect(wrapper.vm.$data.selectedColor).toBe(setColor); + expect(wrapper.emitted().input[0]).toStrictEqual([setColor]); }); }); }); diff --git a/spec/frontend/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js index 6f3c97f7194..66ceebed489 100644 --- a/spec/frontend/vue_shared/components/commit_spec.js +++ b/spec/frontend/vue_shared/components/commit_spec.js @@ -1,5 +1,6 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import CommitComponent from '~/vue_shared/components/commit.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; @@ -13,11 +14,14 @@ describe('Commit component', () => { }; const findUserAvatar = () => wrapper.find(UserAvatarLink); + const findRefName = () => wrapper.findByTestId('ref-name'); const createComponent = (propsData) => { - wrapper = shallowMount(CommitComponent, { - propsData, - }); + wrapper = extendedWrapper( + shallowMount(CommitComponent, { + propsData, + }), + ); }; afterEach(() => { @@ -223,4 +227,20 @@ describe('Commit component', () => { expect(wrapper.find('.ref-name').exists()).toBe(false); }); }); + + describe('When commitRef has a path property instead of ref_url property', () => { + it('should render path as href attribute', () => { + props = { + commitRef: { + name: 'master', + path: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + }; + + createComponent(props); + + expect(findRefName().exists()).toBe(true); + expect(findRefName().attributes('href')).toBe(props.commitRef.path); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js index 31e843297fa..af3b63ad7e5 100644 --- a/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js +++ b/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js @@ -33,4 +33,14 @@ describe('Image Viewer', () => { }, ); }); + + describe('file path', () => { + it('should output a valid URL path for the image', () => { + wrapper = mount(ImageViewer, { + propsData: { path: '/url/hello#1.jpg' }, + }); + + expect(wrapper.find('img').attributes('src')).toBe('/url/hello%231.jpg'); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js index 22ee6acfed8..3ffb23dc7a0 100644 --- a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js +++ b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js @@ -1,6 +1,6 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import $ from 'jquery'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import MarkdownViewer from '~/vue_shared/components/content_viewer/viewers/markdown_viewer.vue'; diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js index b6bffbcc6f3..eacc41ccdad 100644 --- a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js +++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js @@ -1,6 +1,6 @@ +import { mount } from '@vue/test-utils'; import Vue from 'vue'; import { compileToFunctions } from 'vue-template-compiler'; -import { mount } from '@vue/test-utils'; import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants'; import imageDiffViewer from '~/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue'; diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js index e91e6577aaf..9f433816b34 100644 --- a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js +++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js @@ -1,6 +1,5 @@ -import Vuex from 'vuex'; import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; -import Renamed from '~/vue_shared/components/diff_viewer/viewers/renamed.vue'; +import Vuex from 'vuex'; import { TRANSITION_LOAD_START, TRANSITION_LOAD_ERROR, @@ -10,6 +9,7 @@ import { STATE_LOADING, STATE_ERRORED, } from '~/diffs/constants'; +import Renamed from '~/vue_shared/components/diff_viewer/viewers/renamed.vue'; const localVue = createLocalVue(); localVue.use(Vuex); diff --git a/spec/frontend/vue_shared/components/dismissible_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_alert_spec.js index 17905254292..cfa6d1064e5 100644 --- a/spec/frontend/vue_shared/components/dismissible_alert_spec.js +++ b/spec/frontend/vue_shared/components/dismissible_alert_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlAlert } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import DismissibleAlert from '~/vue_shared/components/dismissible_alert.vue'; const TEST_HTML = 'Hello World! <strong>Foo</strong>'; diff --git a/spec/frontend/vue_shared/components/dismissible_container_spec.js b/spec/frontend/vue_shared/components/dismissible_container_spec.js index 2c0e363fa0e..b8aeea38e77 100644 --- a/spec/frontend/vue_shared/components/dismissible_container_spec.js +++ b/spec/frontend/vue_shared/components/dismissible_container_spec.js @@ -1,5 +1,5 @@ -import MockAdapter from 'axios-mock-adapter'; import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import dismissibleContainer from '~/vue_shared/components/dismissible_container.vue'; diff --git a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js index 4c4baf23120..175d79dd1c2 100644 --- a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js +++ b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js @@ -1,5 +1,5 @@ -import { mount, shallowMount } from '@vue/test-utils'; import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; +import { mount, shallowMount } from '@vue/test-utils'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import Component from '~/vue_shared/components/dismissible_feedback_alert.vue'; diff --git a/spec/frontend/vue_shared/components/editor_lite_spec.js b/spec/frontend/vue_shared/components/editor_lite_spec.js index 70fdd8e24a5..badd5aed0e3 100644 --- a/spec/frontend/vue_shared/components/editor_lite_spec.js +++ b/spec/frontend/vue_shared/components/editor_lite_spec.js @@ -1,7 +1,8 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import { EDITOR_READY_EVENT } from '~/editor/constants'; import Editor from '~/editor/editor_lite'; +import EditorLite from '~/vue_shared/components/editor_lite.vue'; jest.mock('~/editor/editor_lite'); @@ -110,13 +111,13 @@ describe('Editor Lite component', () => { expect(wrapper.emitted().input).toEqual([[value]]); }); - it('emits editor-ready event when the Editor Lite is ready', async () => { + it('emits EDITOR_READY_EVENT event when the Editor Lite is ready', async () => { const el = wrapper.find({ ref: 'editor' }).element; - expect(wrapper.emitted()['editor-ready']).toBeUndefined(); + expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeUndefined(); - await el.dispatchEvent(new Event('editor-ready')); + await el.dispatchEvent(new Event(EDITOR_READY_EVENT)); - expect(wrapper.emitted()['editor-ready']).toBeDefined(); + expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeDefined(); }); it('component API `getEditor()` returns the editor instance', () => { diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js index 724405a109f..7874658cc0f 100644 --- a/spec/frontend/vue_shared/components/expand_button_spec.js +++ b/spec/frontend/vue_shared/components/expand_button_spec.js @@ -1,5 +1,5 @@ -import Vue from 'vue'; import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import ExpandButton from '~/vue_shared/components/expand_button.vue'; const text = { diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js index 238a5440664..d757b7fac72 100644 --- a/spec/frontend/vue_shared/components/file_finder/index_spec.js +++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js @@ -1,9 +1,9 @@ -import Vue from 'vue'; import Mousetrap from 'mousetrap'; -import { file } from 'jest/ide/helpers'; +import Vue from 'vue'; import waitForPromises from 'helpers/wait_for_promises'; -import FindFileComponent from '~/vue_shared/components/file_finder/index.vue'; +import { file } from 'jest/ide/helpers'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; +import FindFileComponent from '~/vue_shared/components/file_finder/index.vue'; describe('File finder item spec', () => { const Component = Vue.extend(FindFileComponent); diff --git a/spec/frontend/vue_shared/components/file_finder/item_spec.js b/spec/frontend/vue_shared/components/file_finder/item_spec.js index c60e6335389..1a4a97efb95 100644 --- a/spec/frontend/vue_shared/components/file_finder/item_spec.js +++ b/spec/frontend/vue_shared/components/file_finder/item_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import { file } from 'jest/ide/helpers'; import createComponent from 'helpers/vue_mount_component_helper'; +import { file } from 'jest/ide/helpers'; import ItemComponent from '~/vue_shared/components/file_finder/item.vue'; describe('File finder item spec', () => { diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js index e55449dc684..c10663f6c14 100644 --- a/spec/frontend/vue_shared/components/file_icon_spec.js +++ b/spec/frontend/vue_shared/components/file_icon_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import FileIcon from '~/vue_shared/components/file_icon.vue'; import { FILE_SYMLINK_MODE } from '~/vue_shared/constants'; diff --git a/spec/frontend/vue_shared/components/file_row_spec.js b/spec/frontend/vue_shared/components/file_row_spec.js index bd6a18bf704..62fb29c455c 100644 --- a/spec/frontend/vue_shared/components/file_row_spec.js +++ b/spec/frontend/vue_shared/components/file_row_spec.js @@ -1,10 +1,10 @@ -import { file } from 'jest/ide/helpers'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; +import { file } from 'jest/ide/helpers'; +import { escapeFileUrl } from '~/lib/utils/url_utility'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileRow from '~/vue_shared/components/file_row.vue'; import FileHeader from '~/vue_shared/components/file_row_header.vue'; -import FileIcon from '~/vue_shared/components/file_icon.vue'; -import { escapeFileUrl } from '~/lib/utils/url_utility'; describe('File row component', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/file_tree_spec.js b/spec/frontend/vue_shared/components/file_tree_spec.js index 7a4982fd29b..39a7c7a2b3a 100644 --- a/spec/frontend/vue_shared/components/file_tree_spec.js +++ b/spec/frontend/vue_shared/components/file_tree_spec.js @@ -1,5 +1,5 @@ -import { pick } from 'lodash'; import { shallowMount } from '@vue/test-utils'; +import { pick } from 'lodash'; import FileTree from '~/vue_shared/components/file_tree.vue'; const MockFileRow = { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js index b58ce0083c0..9fa9d35e3e2 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js @@ -1,4 +1,3 @@ -import { shallowMount, mount } from '@vue/test-utils'; import { GlFilteredSearch, GlButtonGroup, @@ -7,13 +6,13 @@ import { GlDropdownItem, GlFormCheckbox, } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; +import RecentSearchesService from '~/filtered_search/services/recent_searches_service'; +import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store'; +import { SortDirection } from '~/vue_shared/components/filtered_search_bar/constants'; import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import { uniqueTokens } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; -import { SortDirection } from '~/vue_shared/components/filtered_search_bar/constants'; - -import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store'; -import RecentSearchesService from '~/filtered_search/services/recent_searches_service'; import { mockAvailableTokens, diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js index 1dd5f08e76a..05bad572472 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js @@ -2,12 +2,12 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data'; +import Api from '~/api'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; +import httpStatusCodes from '~/lib/utils/http_status'; import * as actions from '~/vue_shared/components/filtered_search_bar/store/modules/filters/actions'; import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types'; import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state'; -import httpStatusCodes from '~/lib/utils/http_status'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; -import Api from '~/api'; import { filterMilestones, filterUsers, filterLabels } from './mock_data'; const milestonesEndpoint = 'fake_milestones_endpoint'; diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js index 263a4ee178f..66c6267027b 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/mutations_spec.js @@ -1,9 +1,9 @@ import { get } from 'lodash'; import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data'; -import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state'; -import mutations from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutations'; -import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types'; +import mutations from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutations'; +import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state'; import { filterMilestones, filterUsers, filterLabels } from './mock_data'; let state = null; diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js index 3997d6a99a6..765e576914c 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js @@ -1,15 +1,15 @@ -import { mount } from '@vue/test-utils'; import { GlFilteredSearchToken, GlFilteredSearchTokenSegment, GlFilteredSearchSuggestion, GlDropdownDivider, } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; import { DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY, diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js index 35f487330be..a20bc4986fc 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js @@ -1,15 +1,15 @@ -import { mount } from '@vue/test-utils'; import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlFilteredSearchTokenSegment, GlDropdownDivider, } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; -import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; import { DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY, diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js index dda0ad39bbc..7676ce10ce0 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js @@ -1,19 +1,19 @@ -import { mount } from '@vue/test-utils'; import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlFilteredSearchTokenSegment, GlDropdownDivider, } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import { mockRegularLabel, mockLabels, } from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; import { DEFAULT_LABELS, DEFAULT_LABEL_NONE, 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 164561f6244..9f550ac9afc 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 @@ -1,15 +1,15 @@ -import { mount } from '@vue/test-utils'; import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlFilteredSearchTokenSegment, GlDropdownDivider, } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import createFlash from '~/flash'; import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap index d8e6e37bb89..370b6eb01bc 100644 --- a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap +++ b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap @@ -1,12 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = ` -"raised_hands - <gl-emoji - - data-name=\\"raised_hands\\"></gl-emoji> - " -`; +exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `"raised_hands <gl-emoji data-name=\\"raised_hands\\"></gl-emoji>"`; exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title <script>alert('hi')</script>"`; @@ -21,10 +15,10 @@ exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1 exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = ` " <div class=\\"gl-display-flex gl-align-items-center\\"> - <div class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-rounded-small + <div class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-rounded-small gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\"> G</div> - <div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\"> + <div class=\\"gl-line-height-normal gl-ml-4\\"> <div>1-1s <script>alert('hi')</script> (2)</div> <div class=\\"gl-text-gray-700\\">GitLab Support Team</div> </div> @@ -36,8 +30,8 @@ exports[`gfm_autocomplete/utils members config shows an avatar character, name, exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = ` " <div class=\\"gl-display-flex gl-align-items-center\\"> - <img class=\\"gl-avatar gl-avatar-s24 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" /> - <div class=\\"gl-font-sm gl-line-height-normal gl-ml-3\\"> + <img class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" /> + <div class=\\"gl-line-height-normal gl-ml-4\\"> <div>My Name <script>alert('hi')</script></div> <div class=\\"gl-text-gray-700\\">@myusername</div> </div> diff --git a/spec/frontend/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js index fcc5c0cd310..82d18c7fd3f 100644 --- a/spec/frontend/vue_shared/components/gl_countdown_spec.js +++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js @@ -1,5 +1,5 @@ -import mountComponent from 'helpers/vue_mount_component_helper'; import Vue from 'vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; describe('GlCountdown', () => { diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js index 6802499ed52..390a70792f3 100644 --- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js +++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js @@ -1,6 +1,7 @@ +import { GlModal } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlModal } from '@gitlab/ui'; +import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants'; import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue'; import createState from '~/vuex_shared/modules/modal/state'; @@ -129,7 +130,7 @@ describe('GlModalVuex', () => { wrapper.vm .$nextTick() .then(() => { - expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', TEST_MODAL_ID); + expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, TEST_MODAL_ID); }) .then(done) .catch(done.fail); @@ -146,7 +147,7 @@ describe('GlModalVuex', () => { wrapper.vm .$nextTick() .then(() => { - expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', TEST_MODAL_ID); + expect(rootEmit).toHaveBeenCalledWith(BV_HIDE_MODAL, TEST_MODAL_ID); }) .then(done) .catch(done.fail); diff --git a/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js b/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js index 30afb044bbf..ac670b622b1 100644 --- a/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js +++ b/spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js @@ -1,6 +1,6 @@ -import Vuex from 'vuex'; import { GlToggle } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import GlToggleVuex from '~/vue_shared/components/gl_toggle_vuex.vue'; const localVue = createLocalVue(); 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 5233a64ce5e..b54d120b55b 100644 --- a/spec/frontend/vue_shared/components/header_ci_component_spec.js +++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js @@ -1,93 +1,103 @@ -import Vue from 'vue'; -import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper'; -import headerCi from '~/vue_shared/components/header_ci_component.vue'; +import { GlButton, GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import CiIconBadge from '~/vue_shared/components/ci_badge_link.vue'; +import HeaderCi from '~/vue_shared/components/header_ci_component.vue'; +import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; describe('Header CI Component', () => { - let HeaderCi; - let vm; - let props; - - beforeEach(() => { - HeaderCi = Vue.extend(headerCi); - props = { - status: { - group: 'failed', - icon: 'status_failed', - label: 'failed', - text: 'failed', - details_path: 'path', - }, - itemName: 'job', - itemId: 123, - time: '2017-05-08T14:57:39.781Z', - user: { - web_url: 'path', - name: 'Foo', - username: 'foobar', - email: 'foo@bar.com', - avatar_url: 'link', - }, - hasSidebarButton: true, - }; - }); + let wrapper; + + const defaultProps = { + status: { + group: 'failed', + icon: 'status_failed', + label: 'failed', + text: 'failed', + details_path: 'path', + }, + itemName: 'job', + itemId: 123, + time: '2017-05-08T14:57:39.781Z', + user: { + web_url: 'path', + name: 'Foo', + username: 'foobar', + email: 'foo@bar.com', + avatar_url: 'link', + }, + hasSidebarButton: true, + }; + + const findIconBadge = () => wrapper.findComponent(CiIconBadge); + const findTimeAgo = () => wrapper.findComponent(TimeagoTooltip); + const findUserLink = () => wrapper.findComponent(GlLink); + const findSidebarToggleBtn = () => wrapper.findComponent(GlButton); + const findActionButtons = () => wrapper.findByTestId('ci-header-action-buttons'); + const findHeaderItemText = () => wrapper.findByTestId('ci-header-item-text'); + + const createComponent = (props, slots) => { + wrapper = extendedWrapper( + shallowMount(HeaderCi, { + propsData: { + ...defaultProps, + ...props, + }, + ...slots, + }), + ); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - const findActionButtons = () => vm.$el.querySelector('[data-testid="headerButtons"]'); - describe('render', () => { beforeEach(() => { - vm = mountComponent(HeaderCi, props); + createComponent(); }); it('should render status badge', () => { - expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); - expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); - expect(vm.$el.querySelector('.ci-failed').getAttribute('href')).toEqual( - props.status.details_path, - ); + expect(findIconBadge().exists()).toBe(true); }); it('should render item name and id', () => { - expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); + expect(findHeaderItemText().text()).toBe('job #123'); }); it('should render timeago date', () => { - expect(vm.$el.querySelector('time')).toBeDefined(); + expect(findTimeAgo().exists()).toBe(true); }); it('should render user icon and name', () => { - expect(vm.$el.querySelector('.js-user-link').innerText.trim()).toContain(props.user.name); + expect(findUserLink().text()).toContain(defaultProps.user.name); }); it('should render sidebar toggle button', () => { - expect(vm.$el.querySelector('.js-sidebar-build-toggle')).not.toBeNull(); + expect(findSidebarToggleBtn().exists()).toBe(true); }); - it('should not render header action buttons when empty', () => { - expect(findActionButtons()).toBeNull(); + it('should not render header action buttons when slot is empty', () => { + expect(findActionButtons().exists()).toBe(false); }); }); describe('slot', () => { it('should render header action buttons', () => { - vm = mountComponentWithSlots(HeaderCi, { props, slots: { default: 'Test Actions' } }); - - const buttons = findActionButtons(); + createComponent({}, { slots: { default: 'Test Actions' } }); - expect(buttons).not.toBeNull(); - expect(buttons.textContent).toEqual('Test Actions'); + expect(findActionButtons().exists()).toBe(true); + expect(findActionButtons().text()).toBe('Test Actions'); }); }); describe('shouldRenderTriggeredLabel', () => { - it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => { - vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false }); + it('should render created keyword when the shouldRenderTriggeredLabel is false', () => { + createComponent({ shouldRenderTriggeredLabel: false }); - expect(vm.$el.textContent).toContain('created'); - expect(vm.$el.textContent).not.toContain('triggered'); + expect(wrapper.text()).toContain('created'); + expect(wrapper.text()).not.toContain('triggered'); }); }); }); diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js new file mode 100644 index 00000000000..baf80a8a04e --- /dev/null +++ b/spec/frontend/vue_shared/components/help_popover_spec.js @@ -0,0 +1,65 @@ +import { GlButton, GlPopover } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; + +describe('HelpPopover', () => { + let wrapper; + const title = 'popover <strong>title</strong>'; + const content = 'popover <b>content</b>'; + + const findQuestionButton = () => wrapper.find(GlButton); + const findPopover = () => wrapper.find(GlPopover); + const buildWrapper = (options = {}) => { + wrapper = mount(HelpPopover, { + propsData: { + options: { + title, + content, + ...options, + }, + }, + }); + }; + + beforeEach(() => { + buildWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders a link button with an icon question', () => { + expect(findQuestionButton().props()).toMatchObject({ + icon: 'question', + variant: 'link', + }); + expect(findQuestionButton().attributes().tabindex).toBe('0'); + }); + + it('renders popover that uses the question button as target', () => { + expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el); + }); + + it('triggers popover on hover and focus', () => { + expect(findPopover().props().triggers).toBe('hover focus'); + }); + + it('allows rendering title with HTML tags', () => { + expect(findPopover().find('strong').exists()).toBe(true); + }); + + it('allows rendering content with HTML tags', () => { + expect(findPopover().find('b').exists()).toBe(true); + }); + + it('binds other popover options to the popover instance', () => { + const placement = 'bottom'; + + wrapper.destroy(); + buildWrapper({ placement }); + + expect(findPopover().props().placement).toBe(placement); + }); +}); diff --git a/spec/frontend/vue_shared/components/integration_help_text_spec.js b/spec/frontend/vue_shared/components/integration_help_text_spec.js index 4269d36d0e2..c0e8b719007 100644 --- a/spec/frontend/vue_shared/components/integration_help_text_spec.js +++ b/spec/frontend/vue_shared/components/integration_help_text_spec.js @@ -1,6 +1,6 @@ +import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue'; describe('IntegrationHelpText component', () => { 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 a03a3915e1b..573501233b9 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,8 +1,8 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue'; -import createIssueStore from '~/notes/stores'; import { createStore as createMrStore } from '~/mr_notes/stores'; +import createIssueStore from '~/notes/stores'; +import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue'; const ISSUABLE_TYPE_ISSUE = 'issue'; const ISSUABLE_TYPE_MR = 'merge request'; diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js index 5f614bfc751..5c29c267c99 100644 --- a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js +++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import { mockAssigneesList } from 'jest/boards/mock_data'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; const TEST_CSS_CLASSES = 'test-classes'; const TEST_MAX_VISIBLE = 4; diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js index ffcb891c4fc..9a121050225 100644 --- a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js +++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js @@ -1,8 +1,8 @@ -import Vue from 'vue'; +import { GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import { mockMilestone } from 'jest/boards/mock_data'; -import { GlIcon } from '@gitlab/ui'; import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue'; const createComponent = (milestone = mockMilestone) => { diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js index 3dc34583118..f34a2db0851 100644 --- a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js +++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js @@ -1,8 +1,8 @@ import { mount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; +import IssueDueDate from '~/boards/components/issue_due_date.vue'; import { formatDate } from '~/lib/utils/datetime_utility'; import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; -import IssueDueDate from '~/boards/components/issue_due_date.vue'; import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data'; describe('RelatedIssuableItem', () => { diff --git a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js index b9f0d88548d..c56628fcbcd 100644 --- a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js +++ b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlDropdown, GlFormTextarea, GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import ApplySuggestionComponent from '~/vue_shared/components/markdown/apply_suggestion.vue'; describe('Apply Suggestion component', () => { diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js index a2ce6f40193..442032840e1 100644 --- a/spec/frontend/vue_shared/components/markdown/field_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_spec.js @@ -1,9 +1,9 @@ import { mount } from '@vue/test-utils'; -import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants'; import AxiosMockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; -import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants'; import axios from '~/lib/utils/axios_utils'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; const markdownPreviewPath = `${TEST_HOST}/preview`; const markdownDocsPath = `${TEST_HOST}/docs`; diff --git a/spec/frontend/vue_shared/components/markdown/field_view_spec.js b/spec/frontend/vue_shared/components/markdown/field_view_spec.js index 80cf1f655c6..be1d840dd29 100644 --- a/spec/frontend/vue_shared/components/markdown/field_view_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_view_spec.js @@ -1,5 +1,5 @@ -import $ from 'jquery'; import { shallowMount } from '@vue/test-utils'; +import $ from 'jquery'; import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue'; diff --git a/spec/frontend/vue_shared/components/memory_graph_spec.js b/spec/frontend/vue_shared/components/memory_graph_spec.js index 9a5ee544d8f..53b96bd1b98 100644 --- a/spec/frontend/vue_shared/components/memory_graph_spec.js +++ b/spec/frontend/vue_shared/components/memory_graph_spec.js @@ -1,6 +1,6 @@ -import Vue from 'vue'; -import { shallowMount } from '@vue/test-utils'; import { GlSparklineChart } from '@gitlab/ui/dist/charts'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import MemoryGraph from '~/vue_shared/components/memory_graph.vue'; describe('MemoryGraph', () => { diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js index ca9f8ff54d4..adb72c3ef85 100644 --- a/spec/frontend/vue_shared/components/modal_copy_button_spec.js +++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js @@ -1,4 +1,5 @@ import { shallowMount, createWrapper } from '@vue/test-utils'; +import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; describe('modal copy button', () => { @@ -31,7 +32,7 @@ describe('modal copy button', () => { return wrapper.vm.$nextTick().then(() => { expect(wrapper.emitted().success).not.toBeEmpty(); expect(document.execCommand).toHaveBeenCalledWith('copy'); - expect(root.emitted('bv::hide::tooltip')).toEqual([['test-id']]); + expect(root.emitted(BV_HIDE_TOOLTIP)).toEqual([['test-id']]); }); }); it("should propagate the clipboard error event if execCommand doesn't work", () => { diff --git a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js index 233c488b60b..99671f1ffb7 100644 --- a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { getByText } from '@testing-library/dom'; +import { shallowMount } from '@vue/test-utils'; import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue'; describe('MultiSelectDropdown Component', () => { diff --git a/spec/frontend/vue_shared/components/navigation_tabs_spec.js b/spec/frontend/vue_shared/components/navigation_tabs_spec.js index b1119bfb150..30a89fed12f 100644 --- a/spec/frontend/vue_shared/components/navigation_tabs_spec.js +++ b/spec/frontend/vue_shared/components/navigation_tabs_spec.js @@ -1,5 +1,5 @@ -import { mount } from '@vue/test-utils'; import { GlTab } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; describe('navigation tabs component', () => { diff --git a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js index cc9f05beb06..835759b1f20 100644 --- a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js +++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue'; describe('Issue Warning Component', () => { diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js index d5eac7c2aa3..48dacc50923 100644 --- a/spec/frontend/vue_shared/components/notes/system_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js @@ -1,7 +1,7 @@ import { mount } from '@vue/test-utils'; -import IssueSystemNote from '~/vue_shared/components/notes/system_note.vue'; -import createStore from '~/notes/stores'; import initMRPopovers from '~/mr_popover/index'; +import createStore from '~/notes/stores'; +import IssueSystemNote from '~/vue_shared/components/notes/system_note.vue'; jest.mock('~/mr_popover/index', () => jest.fn()); diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js index 491f783622a..74e9cbcbb53 100644 --- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js +++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js @@ -1,9 +1,9 @@ -import { mount } from '@vue/test-utils'; import { GlAlert, GlBadge, GlPagination, GlTabs, GlTab } from '@gitlab/ui'; -import PageWrapper from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue'; +import { mount } from '@vue/test-utils'; +import Tracking from '~/tracking'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; -import Tracking from '~/tracking'; +import PageWrapper from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue'; import mockItems from './mocks/items.json'; import mockFilters from './mocks/items_filters.json'; diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js index ad82aee0098..83f1e2844f9 100644 --- a/spec/frontend/vue_shared/components/pagination_links_spec.js +++ b/spec/frontend/vue_shared/components/pagination_links_spec.js @@ -1,6 +1,5 @@ -import { mount } from '@vue/test-utils'; import { GlPagination } from '@gitlab/ui'; -import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; +import { mount } from '@vue/test-utils'; import { PREV, NEXT, @@ -9,6 +8,7 @@ import { LABEL_NEXT_PAGE, LABEL_LAST_PAGE, } from '~/vue_shared/components/pagination/constants'; +import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; describe('Pagination links component', () => { const pageInfo = { diff --git a/spec/frontend/vue_shared/components/pikaday_spec.js b/spec/frontend/vue_shared/components/pikaday_spec.js index 1c6876c282c..fed4ce5e696 100644 --- a/spec/frontend/vue_shared/components/pikaday_spec.js +++ b/spec/frontend/vue_shared/components/pikaday_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlDatepicker } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import datePicker from '~/vue_shared/components/pikaday.vue'; describe('datePicker', () => { diff --git a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js index 016622fd0bb..06b00a8e196 100644 --- a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js @@ -1,8 +1,7 @@ -import Vue from 'vue'; -import { head } from 'lodash'; - import { GlSearchBoxByType, GlInfiniteScroll } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; +import { head } from 'lodash'; +import Vue from 'vue'; import { trimText } from 'helpers/text_helper'; import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue'; diff --git a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js index 84c738764a3..4ec608aaf07 100644 --- a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js +++ b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js @@ -1,7 +1,7 @@ import { mount } from '@vue/test-utils'; import Tracking from '~/tracking'; -import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; describe('Package code instruction', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/registry/details_row_spec.js b/spec/frontend/vue_shared/components/registry/details_row_spec.js index 09dacfae363..3134e0d3e21 100644 --- a/spec/frontend/vue_shared/components/registry/details_row_spec.js +++ b/spec/frontend/vue_shared/components/registry/details_row_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import component from '~/vue_shared/components/registry/details_row.vue'; describe('DetailsRow', () => { diff --git a/spec/frontend/vue_shared/components/registry/history_item_spec.js b/spec/frontend/vue_shared/components/registry/history_item_spec.js index d51ddda2e3e..f146f87342f 100644 --- a/spec/frontend/vue_shared/components/registry/history_item_spec.js +++ b/spec/frontend/vue_shared/components/registry/history_item_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import component from '~/vue_shared/components/registry/history_item.vue'; diff --git a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js index 3d3cfbe13e3..1ccf3ddc5a5 100644 --- a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js +++ b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon, GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import component from '~/vue_shared/components/registry/metadata_item.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; diff --git a/spec/frontend/vue_shared/components/registry/registry_search_spec.js b/spec/frontend/vue_shared/components/registry/registry_search_spec.js new file mode 100644 index 00000000000..28bdb275756 --- /dev/null +++ b/spec/frontend/vue_shared/components/registry/registry_search_spec.js @@ -0,0 +1,105 @@ +import { GlSorting, GlSortingItem, GlFilteredSearch } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import component from '~/vue_shared/components/registry/registry_search.vue'; + +describe('Registry Search', () => { + let wrapper; + + const findPackageListSorting = () => wrapper.find(GlSorting); + const findSortingItems = () => wrapper.findAll(GlSortingItem); + const findFilteredSearch = () => wrapper.find(GlFilteredSearch); + + const defaultProps = { + filter: [], + sorting: { sort: 'asc', orderBy: 'name' }, + tokens: ['foo'], + sortableFields: [{ label: 'name', orderBy: 'name' }, { label: 'baz' }], + }; + + const mountComponent = (propsData = defaultProps) => { + wrapper = shallowMount(component, { + propsData, + stubs: { + GlSortingItem, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('searching', () => { + it('has a filtered-search component', () => { + mountComponent(); + + expect(findFilteredSearch().exists()).toBe(true); + }); + + it('binds the correct props to filtered-search', () => { + mountComponent(); + + expect(findFilteredSearch().props()).toMatchObject({ + value: [], + placeholder: 'Filter results', + availableTokens: wrapper.vm.tokens, + }); + }); + + it('emits filter:changed when value changes', () => { + mountComponent(); + + findFilteredSearch().vm.$emit('input', 'foo'); + + expect(wrapper.emitted('filter:changed')).toEqual([['foo']]); + }); + + it('emits filter:submit on submit event', () => { + mountComponent(); + + findFilteredSearch().vm.$emit('submit'); + expect(wrapper.emitted('filter:submit')).toEqual([[]]); + }); + + it('emits filter:changed and filter:submit on clear event', () => { + mountComponent(); + + findFilteredSearch().vm.$emit('clear'); + + expect(wrapper.emitted('filter:changed')).toEqual([[[]]]); + expect(wrapper.emitted('filter:submit')).toEqual([[]]); + }); + + it('binds tokens prop', () => { + mountComponent(); + + expect(findFilteredSearch().props('availableTokens')).toEqual(defaultProps.tokens); + }); + }); + + describe('sorting', () => { + it('has all the sortable items', () => { + mountComponent(); + + expect(findSortingItems()).toHaveLength(defaultProps.sortableFields.length); + }); + + it('on sort change emits sorting:changed event', () => { + mountComponent(); + + findPackageListSorting().vm.$emit('sortDirectionChange'); + expect(wrapper.emitted('sorting:changed')).toEqual([[{ sort: 'desc' }]]); + }); + + it('on sort item click emits sorting:changed event ', () => { + mountComponent(); + + findSortingItems().at(0).vm.$emit('click'); + + expect(wrapper.emitted('sorting:changed')).toEqual([ + [{ orderBy: defaultProps.sortableFields[0].orderBy }], + ]); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js index 3a5514ef318..1fce3c5d0b0 100644 --- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js +++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js @@ -1,6 +1,6 @@ -import Vue from 'vue'; import { mount } from '@vue/test-utils'; import $ from 'jquery'; +import Vue from 'vue'; import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue'; jest.mock('~/lib/utils/common_utils', () => ({ diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js index 51619cd9578..ce2b0d1ddc1 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js @@ -1,3 +1,5 @@ +import buildCustomRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer'; +import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer'; import { generateToolbarItem, addCustomEventListener, @@ -8,8 +10,6 @@ import { getMarkdown, getEditorOptions, } from '~/vue_shared/components/rich_content_editor/services/editor_service'; -import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer'; -import buildCustomRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer'; import sanitizeHTML from '~/vue_shared/components/rich_content_editor/services/sanitize_html'; jest.mock('~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer'); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js index 16370a7aaad..97aecda97d2 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js @@ -1,8 +1,8 @@ -import { shallowMount } from '@vue/test-utils'; import { GlModal, GlTabs } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { IMAGE_TABS } from '~/vue_shared/components/rich_content_editor/constants'; import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue'; import UploadImageTab from '~/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue'; -import { IMAGE_TABS } from '~/vue_shared/components/rich_content_editor/constants'; describe('Add Image Modal', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js index d59d4cc1de9..3e9eaf58181 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/insert_video_modal_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlModal } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue'; describe('Insert Video Modal', () => { diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js index b9b93b274d2..47b1abd2ad2 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_integration_spec.js @@ -1,6 +1,6 @@ import Editor from '@toast-ui/editor'; -import { registerHTMLToMarkdownRenderer } from '~/vue_shared/components/rich_content_editor/services/editor_service'; import buildMarkdownToHTMLRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer'; +import { registerHTMLToMarkdownRenderer } from '~/vue_shared/components/rich_content_editor/services/editor_service'; describe('vue_shared/components/rich_content_editor', () => { let editor; diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js index 2eb353a1801..8eb880b3984 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js @@ -1,14 +1,14 @@ -import { shallowMount } from '@vue/test-utils'; import { Editor, mockEditorApi } from '@toast-ui/vue-editor'; -import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; -import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue'; -import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue'; +import { shallowMount } from '@vue/test-utils'; import { EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE, CUSTOM_EVENTS, } from '~/vue_shared/components/rich_content_editor/constants'; +import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue'; +import InsertVideoModal from '~/vue_shared/components/rich_content_editor/modals/insert_video_modal.vue'; +import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; import { addCustomEventListener, diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js index d6bb01259bb..c1aaed6f0c3 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js @@ -1,5 +1,5 @@ -import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline'; import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; +import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline'; import { normalTextNode } from './mock_data'; diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js index b31684a400e..234f6a4d4ca 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js @@ -1,5 +1,5 @@ -import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block'; import { buildUneditableHtmlAsTextTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; +import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block'; describe('rich_content_editor/services/renderers/render_html_block', () => { const htmlBlockNode = { diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js index 521885f5687..425d0f41bcd 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js @@ -1,5 +1,5 @@ -import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text'; import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; +import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text'; import { buildMockTextNode, normalTextNode } from './mock_data'; diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js index 774f830f421..7c1809c290c 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_utils_spec.js @@ -1,15 +1,14 @@ import { + buildUneditableBlockTokens, + buildUneditableOpenTokens, +} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; +import { renderUneditableLeaf, renderUneditableBranch, renderWithAttributeDefinitions, willAlwaysRender, } from '~/vue_shared/components/rich_content_editor/services/renderers/render_utils'; -import { - buildUneditableBlockTokens, - buildUneditableOpenTokens, -} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; - import { originToken, uneditableCloseToken, attributeDefinition } from './mock_data'; describe('rich_content_editor/renderers/render_utils', () => { diff --git a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js index 0e6f951bd53..5a56b499769 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js @@ -1,6 +1,6 @@ +import { GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import { GlIcon } from '@gitlab/ui'; import ToolbarItem from '~/vue_shared/components/rich_content_editor/toolbar_item.vue'; describe('Toolbar Item', () => { diff --git a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js new file mode 100644 index 00000000000..01f7f3d49c7 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js @@ -0,0 +1,107 @@ +export const mockGraphqlRunnerPlatforms = { + data: { + runnerPlatforms: { + nodes: [ + { + name: 'linux', + humanReadableName: 'Linux', + architectures: { + nodes: [ + { + name: 'amd64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64', + __typename: 'RunnerArchitecture', + }, + { + name: '386', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386', + __typename: 'RunnerArchitecture', + }, + { + name: 'arm', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm', + __typename: 'RunnerArchitecture', + }, + { + name: 'arm64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64', + __typename: 'RunnerArchitecture', + }, + ], + __typename: 'RunnerArchitectureConnection', + }, + __typename: 'RunnerPlatform', + }, + { + name: 'osx', + humanReadableName: 'macOS', + architectures: { + nodes: [ + { + name: 'amd64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64', + __typename: 'RunnerArchitecture', + }, + ], + __typename: 'RunnerArchitectureConnection', + }, + __typename: 'RunnerPlatform', + }, + { + name: 'windows', + humanReadableName: 'Windows', + architectures: { + nodes: [ + { + name: 'amd64', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe', + __typename: 'RunnerArchitecture', + }, + { + name: '386', + downloadLocation: + 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe', + __typename: 'RunnerArchitecture', + }, + ], + __typename: 'RunnerArchitectureConnection', + }, + __typename: 'RunnerPlatform', + }, + { + name: 'docker', + humanReadableName: 'Docker', + architectures: null, + __typename: 'RunnerPlatform', + }, + { + name: 'kubernetes', + humanReadableName: 'Kubernetes', + architectures: null, + __typename: 'RunnerPlatform', + }, + ], + __typename: 'RunnerPlatformConnection', + }, + project: { id: 'gid://gitlab/Project/1', __typename: 'Project' }, + group: null, + }, +}; + +export const mockGraphqlInstructions = { + data: { + runnerSetup: { + installInstructions: + "# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n", + registerInstructions: + 'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz', + __typename: 'RunnerSetup', + }, + }, +}; diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js new file mode 100644 index 00000000000..48db60bfd33 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js @@ -0,0 +1,113 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql'; +import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql'; +import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; + +import { mockGraphqlRunnerPlatforms, mockGraphqlInstructions } from './mock_data'; + +const projectPath = 'gitlab-org/gitlab'; +const localVue = createLocalVue(); +localVue.use(VueApollo); + +describe('RunnerInstructions component', () => { + let wrapper; + let fakeApollo; + + const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]'); + const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]'); + const findArchitectureDropdownItems = () => + wrapper.findAll('[data-testid="architecture-dropdown-item"]'); + const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]'); + const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]'); + + beforeEach(async () => { + const requestHandlers = [ + [getRunnerPlatforms, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)], + [getRunnerSetupInstructions, jest.fn().mockResolvedValue(mockGraphqlInstructions)], + ]; + + fakeApollo = createMockApollo(requestHandlers); + + wrapper = shallowMount(RunnerInstructions, { + provide: { + projectPath, + }, + localVue, + apolloProvider: fakeApollo, + }); + + await wrapper.vm.$nextTick(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should show the "Show Runner installation instructions" button', () => { + const button = findModalButton(); + + expect(button.exists()).toBe(true); + expect(button.text()).toBe('Show Runner installation instructions'); + }); + + it('should contain a number of platforms buttons', () => { + const buttons = findPlatformButtons(); + + expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length); + }); + + it('should contain a number of dropdown items for the architecture options', () => { + const platformButton = findPlatformButtons().at(0); + platformButton.vm.$emit('click'); + + return wrapper.vm.$nextTick(() => { + const dropdownItems = findArchitectureDropdownItems(); + + expect(dropdownItems).toHaveLength( + mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length, + ); + }); + }); + + it('should display the binary installation instructions for a selected architecture', async () => { + const platformButton = findPlatformButtons().at(0); + platformButton.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const dropdownItem = findArchitectureDropdownItems().at(0); + dropdownItem.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const runner = findBinaryInstructionsSection(); + + expect(runner.text()).toMatch('sudo chmod +x /usr/local/bin/gitlab-runner'); + expect(runner.text()).toMatch( + `sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`, + ); + expect(runner.text()).toMatch( + 'sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner', + ); + expect(runner.text()).toMatch('sudo gitlab-runner start'); + }); + + it('should display the runner register instructions for a selected architecture', async () => { + const platformButton = findPlatformButtons().at(0); + platformButton.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const dropdownItem = findArchitectureDropdownItems().at(0); + dropdownItem.vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + const runner = findRunnerInstructionsSection(); + + expect(runner.text()).toMatch(mockGraphqlInstructions.data.runnerSetup.registerInstructions); + }); +}); 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 new file mode 100644 index 00000000000..51b8aa162bc --- /dev/null +++ b/spec/frontend/vue_shared/components/settings/__snapshots__/settings_block_spec.js.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Settings Block renders the correct markup 1`] = ` +<section + class="settings no-animate" +> + <div + class="settings-header" + > + <h4> + <div + data-testid="title-slot" + /> + </h4> + + <gl-button-stub + buttontextclasses="" + category="primary" + icon="" + size="medium" + variant="default" + > + + Expand + + </gl-button-stub> + + <p> + <div + data-testid="description-slot" + /> + </p> + </div> + + <div + class="settings-content" + > + <div + data-testid="default-slot" + /> + </div> +</section> +`; diff --git a/spec/frontend/vue_shared/components/settings/settings_block_spec.js b/spec/frontend/vue_shared/components/settings/settings_block_spec.js new file mode 100644 index 00000000000..2db0b001b5b --- /dev/null +++ b/spec/frontend/vue_shared/components/settings/settings_block_spec.js @@ -0,0 +1,86 @@ +import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import component from '~/vue_shared/components/settings/settings_block.vue'; + +describe('Settings Block', () => { + let wrapper; + + const mountComponent = (propsData) => { + wrapper = shallowMount(component, { + propsData, + slots: { + title: '<div data-testid="title-slot"></div>', + description: '<div data-testid="description-slot"></div>', + default: '<div data-testid="default-slot"></div>', + }, + }); + }; + + 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); + + it('renders the correct markup', () => { + mountComponent(); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('has a default slot', () => { + mountComponent(); + + expect(findDefaultSlot().exists()).toBe(true); + }); + + it('has a title slot', () => { + mountComponent(); + + expect(findTitleSlot().exists()).toBe(true); + }); + + it('has a description slot', () => { + mountComponent(); + + expect(findDescriptionSlot().exists()).toBe(true); + }); + + describe('expanded behaviour', () => { + it('is collapsed by default', () => { + mountComponent(); + + expect(wrapper.classes('expanded')).toBe(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'); + }); + + it('is expanded when `defaultExpanded` is true no matter what', async () => { + mountComponent({ defaultExpanded: true }); + + expect(wrapper.classes('expanded')).toBe(true); + + await findExpandButton().vm.$emit('click'); + + expect(wrapper.classes('expanded')).toBe(true); + + await findExpandButton().vm.$emit('click'); + + expect(wrapper.classes('expanded')).toBe(true); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js index fc1fa3fc1c1..3221e88192b 100644 --- a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; -import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue'; import DatePicker from '~/vue_shared/components/pikaday.vue'; +import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue'; describe('SidebarDatePicker', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js index 256b3cff525..a5a099d803a 100644 --- a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js @@ -1,5 +1,3 @@ -import { shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; import { GlIcon, GlLoadingIcon, @@ -9,6 +7,8 @@ import { GlSearchBoxByType, GlButton, } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import IssuableMoveDropdown from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js index a55ad37c498..68ea94e72ce 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js @@ -1,6 +1,6 @@ +import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; -import { shallowMount } from '@vue/test-utils'; import LabelsSelect from '~/labels_select'; import BaseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js index 4b4d265800b..322e632da02 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js @@ -79,7 +79,7 @@ describe('DropdownCreateLabelComponent', () => { const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0]; expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0].colorCode); - expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);'); + expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 153, 102);'); }); it('renders color input element', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js index 5cbbb99eaef..30dd92b72a4 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue'; const createComponent = (canEdit = true) => diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js index ecb3c3a42c8..37f59c108df 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js @@ -1,5 +1,5 @@ -import { mount } from '@vue/test-utils'; import { GlLabel } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import DropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; import { mockConfig, mockLabels } from './mock_data'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js index 648ba84fe8f..73716d4edf3 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js @@ -16,27 +16,27 @@ export const mockLabels = [ ]; export const mockSuggestedColors = { - '#0033CC': 'UA blue', - '#428BCA': 'Moderate blue', - '#44AD8E': 'Lime green', - '#A8D695': 'Feijoa', - '#5CB85C': 'Slightly desaturated green', - '#69D100': 'Bright green', - '#004E00': 'Very dark lime green', - '#34495E': 'Very dark desaturated blue', - '#7F8C8D': 'Dark grayish cyan', - '#A295D6': 'Slightly desaturated blue', - '#5843AD': 'Dark moderate blue', - '#8E44AD': 'Dark moderate violet', - '#FFECDB': 'Very pale orange', - '#AD4363': 'Dark moderate pink', - '#D10069': 'Strong pink', - '#CC0033': 'Strong red', - '#FF0000': 'Pure red', - '#D9534F': 'Soft red', - '#D1D100': 'Strong yellow', - '#F0AD4E': 'Soft orange', - '#AD8D43': 'Dark moderate orange', + '#009966': 'Green-cyan', + '#8fbc8f': 'Dark sea green', + '#3cb371': 'Medium sea green', + '#00b140': 'Green screen', + '#013220': 'Dark green', + '#6699cc': 'Blue-gray', + '#0000ff': 'Blue', + '#e6e6fa': 'Lavendar', + '#9400d3': 'Dark violet', + '#330066': 'Deep violet', + '#808080': 'Gray', + '#36454f': 'Charcoal grey', + '#f7e7ce': 'Champagne', + '#c21e56': 'Rose red', + '#cc338b': 'Magenta-pink', + '#dc143c': 'Crimson', + '#ff0000': 'Red', + '#cd5b45': 'Dark coral', + '#eee600': 'Titanium yellow', + '#ed9121': 'Carrot orange', + '#c39953': 'Aztec Gold', }; export const mockConfig = { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js index 951f706421f..59b170bfba9 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js @@ -1,7 +1,7 @@ -import Vuex from 'vuex'; +import { GlIcon, GlButton } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; -import { GlIcon, GlButton } from '@gitlab/ui'; import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue'; import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js index 0f49fe4fc5b..c4a645082e6 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js @@ -1,7 +1,7 @@ -import Vuex from 'vuex'; +import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; -import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui'; import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue'; import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; 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 989cd256e26..60903933505 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 @@ -1,6 +1,3 @@ -import Vuex from 'vuex'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; - import { GlIntersectionObserver, GlButton, @@ -8,14 +5,16 @@ import { GlSearchBoxByType, GlLink, } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue'; import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue'; -import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; -import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions'; import * as getters from '~/vue_shared/components/sidebar/labels_select_vue/store/getters'; +import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; +import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; import { mockConfig, mockLabels, mockRegularLabel } from './mock_data'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js index 97946993857..1175d183c6c 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js @@ -1,5 +1,5 @@ -import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js index c1d9be7393c..726a113dbd9 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js @@ -1,7 +1,7 @@ -import Vuex from 'vuex'; +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; 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 70311f8235f..0d1d6ebcfe5 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 @@ -1,7 +1,7 @@ -import Vuex from 'vuex'; +import { GlLabel } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; -import { GlLabel } from '@gitlab/ui'; import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js index a6ec01ad7e1..bd1705e7693 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js @@ -1,6 +1,6 @@ +import { GlIcon, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { GlIcon, GlLink } from '@gitlab/ui'; import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue'; import { mockRegularLabel } from './mock_data'; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js index 1206450bbeb..4cf36df2502 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js @@ -1,15 +1,15 @@ -import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; -import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; -import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue'; -import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue'; +import { isInViewport } from '~/lib/utils/common_utils'; import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue'; import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue'; import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue'; +import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue'; +import DropdownValue from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue'; +import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; -import { isInViewport } from '~/lib/utils/common_utils'; import { mockConfig } from './mock_data'; 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 9697d6c30f2..85a14226585 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 @@ -50,25 +50,25 @@ export const mockConfig = { }; export const mockSuggestedColors = { - '#0033CC': 'UA blue', - '#428BCA': 'Moderate blue', - '#44AD8E': 'Lime green', - '#A8D695': 'Feijoa', - '#5CB85C': 'Slightly desaturated green', - '#69D100': 'Bright green', - '#004E00': 'Very dark lime green', - '#34495E': 'Very dark desaturated blue', - '#7F8C8D': 'Dark grayish cyan', - '#A295D6': 'Slightly desaturated blue', - '#5843AD': 'Dark moderate blue', - '#8E44AD': 'Dark moderate violet', - '#FFECDB': 'Very pale orange', - '#AD4363': 'Dark moderate pink', - '#D10069': 'Strong pink', - '#CC0033': 'Strong red', - '#FF0000': 'Pure red', - '#D9534F': 'Soft red', - '#D1D100': 'Strong yellow', - '#F0AD4E': 'Soft orange', - '#AD8D43': 'Dark moderate orange', + '#009966': 'Green-cyan', + '#8fbc8f': 'Dark sea green', + '#3cb371': 'Medium sea green', + '#00b140': 'Green screen', + '#013220': 'Dark green', + '#6699cc': 'Blue-gray', + '#0000ff': 'Blue', + '#e6e6fa': 'Lavendar', + '#9400d3': 'Dark violet', + '#330066': 'Deep violet', + '#808080': 'Gray', + '#36454f': 'Charcoal grey', + '#f7e7ce': 'Champagne', + '#c21e56': 'Rose red', + '#cc338b': 'Magenta-pink', + '#dc143c': 'Crimson', + '#ff0000': 'Red', + '#cd5b45': 'Dark coral', + '#eee600': 'Titanium yellow', + '#ed9121': 'Carrot orange', + '#c39953': 'Aztec Gold', }; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js index 4909c43bc96..3f11095cb04 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js @@ -1,11 +1,10 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; -import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types'; -import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions'; - import axios from '~/lib/utils/axios_utils'; +import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions'; +import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types'; +import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; describe('LabelsSelect Actions', () => { let state; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js index 208f2f2d42d..ab266ac8aed 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js @@ -1,5 +1,5 @@ -import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types'; +import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; describe('LabelsSelect Mutations', () => { describe(`${types.SET_INITIAL_STATE}`, () => { diff --git a/spec/frontend/vue_shared/components/smart_virtual_list_spec.js b/spec/frontend/vue_shared/components/smart_virtual_list_spec.js index e5f9b94128e..8802a832781 100644 --- a/spec/frontend/vue_shared/components/smart_virtual_list_spec.js +++ b/spec/frontend/vue_shared/components/smart_virtual_list_spec.js @@ -1,5 +1,5 @@ -import Vue from 'vue'; import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue'; describe('Toggle Button', () => { diff --git a/spec/frontend/vue_shared/components/table_pagination_spec.js b/spec/frontend/vue_shared/components/table_pagination_spec.js index 12c47637358..ed23a47c328 100644 --- a/spec/frontend/vue_shared/components/table_pagination_spec.js +++ b/spec/frontend/vue_shared/components/table_pagination_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlPagination } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; describe('Pagination component', () => { diff --git a/spec/frontend/vue_shared/components/tabs/tabs_spec.js b/spec/frontend/vue_shared/components/tabs/tabs_spec.js index 49d92094b34..fe7be5be899 100644 --- a/spec/frontend/vue_shared/components/tabs/tabs_spec.js +++ b/spec/frontend/vue_shared/components/tabs/tabs_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import Tabs from '~/vue_shared/components/tabs/tabs'; import Tab from '~/vue_shared/components/tabs/tab.vue'; +import Tabs from '~/vue_shared/components/tabs/tabs'; describe('Tabs component', () => { let vm; diff --git a/spec/frontend/vue_shared/components/todo_button_spec.js b/spec/frontend/vue_shared/components/todo_button_spec.js index 1f8a214d632..8043bb7785b 100644 --- a/spec/frontend/vue_shared/components/todo_button_spec.js +++ b/spec/frontend/vue_shared/components/todo_button_spec.js @@ -1,5 +1,5 @@ -import { shallowMount, mount } from '@vue/test-utils'; import { GlButton } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; import TodoButton from '~/vue_shared/components/todo_button.vue'; describe('Todo Button', () => { @@ -33,7 +33,7 @@ describe('Todo Button', () => { it.each` label | isTodo ${'Mark as done'} | ${true} - ${'Add a To Do'} | ${false} + ${'Add a to do'} | ${false} `('sets correct label when isTodo is $isTodo', ({ label, isTodo }) => { createComponent({ isTodo }); diff --git a/spec/frontend/vue_shared/components/toggle_button_spec.js b/spec/frontend/vue_shared/components/toggle_button_spec.js deleted file mode 100644 index 2822b1999bc..00000000000 --- a/spec/frontend/vue_shared/components/toggle_button_spec.js +++ /dev/null @@ -1,96 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlIcon } from '@gitlab/ui'; -import ToggleButton from '~/vue_shared/components/toggle_button.vue'; - -describe('Toggle Button component', () => { - let wrapper; - - function createComponent(propsData = {}) { - wrapper = shallowMount(ToggleButton, { - propsData, - }); - } - - const findInput = () => wrapper.find('input'); - const findButton = () => wrapper.find('button'); - const findToggleIcon = () => wrapper.find(GlIcon); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('renders input with provided name', () => { - createComponent({ - name: 'foo', - }); - - expect(findInput().attributes('name')).toBe('foo'); - }); - - describe.each` - value | iconName - ${true} | ${'status_success_borderless'} - ${false} | ${'status_failed_borderless'} - `('when `value` prop is `$value`', ({ value, iconName }) => { - beforeEach(() => { - createComponent({ - value, - name: 'foo', - }); - }); - - it('renders input with correct value attribute', () => { - expect(findInput().attributes('value')).toBe(`${value}`); - }); - - it('renders correct icon', () => { - const icon = findToggleIcon(); - expect(icon.isVisible()).toBe(true); - expect(icon.props('name')).toBe(iconName); - expect(findButton().classes('is-checked')).toBe(value); - }); - - describe('when clicked', () => { - it('emits `change` event with correct event', async () => { - findButton().trigger('click'); - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted('change')).toStrictEqual([[!value]]); - }); - }); - }); - - describe('when `disabledInput` prop is `true`', () => { - beforeEach(() => { - createComponent({ - value: true, - disabledInput: true, - }); - }); - - it('renders disabled button', () => { - expect(findButton().classes()).toContain('is-disabled'); - }); - - it('does not emit change event when clicked', async () => { - findButton().trigger('click'); - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted('change')).toBeFalsy(); - }); - }); - - describe('when `isLoading` prop is `true`', () => { - beforeEach(() => { - createComponent({ - value: true, - isLoading: true, - }); - }); - - it('renders loading class', () => { - expect(findButton().classes()).toContain('is-loading'); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js index 11982eb513d..ace486b1f32 100644 --- a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js +++ b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; jest.mock('~/flash'); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js index d151cd15bc4..d62c4a98b10 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js @@ -1,10 +1,10 @@ +import { GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { each } from 'lodash'; import { trimText } from 'helpers/text_helper'; -import { shallowMount } from '@vue/test-utils'; -import { GlLink } from '@gitlab/ui'; import { TEST_HOST } from 'spec/test_constants'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; describe('User Avatar Link Component', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js index e3cd2bb9aaa..1d15da491cd 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js @@ -1,8 +1,8 @@ -import { shallowMount } from '@vue/test-utils'; import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { TEST_HOST } from 'spec/test_constants'; -import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; const TEST_IMAGE_SIZE = 7; const TEST_BREAKPOINT = 5; 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 435c3a5406e..a6c5e23ae14 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 @@ -1,8 +1,8 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf, GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue'; -import UserAvailabilityStatus from '~/set_status_modal/components/user_availability_status.vue'; import { AVAILABILITY_STATUS } from '~/set_status_modal/utils'; +import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; +import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue'; const DEFAULT_PROPS = { user: { @@ -36,7 +36,7 @@ describe('User Popover Component', () => { const findByTestId = (testid) => wrapper.find(`[data-testid="${testid}"]`); const findUserStatus = () => wrapper.find('.js-user-status'); const findTarget = () => document.querySelector('.js-user-link'); - const findAvailabilityStatus = () => wrapper.find(UserAvailabilityStatus); + const findUserName = () => wrapper.find(UserNameWithStatus); const createWrapper = (props = {}, options = {}) => { wrapper = shallowMount(UserPopover, { @@ -47,7 +47,7 @@ describe('User Popover Component', () => { }, stubs: { GlSprintf, - UserAvailabilityStatus, + UserNameWithStatus, }, ...options, }); @@ -213,7 +213,7 @@ describe('User Popover Component', () => { createWrapper({ user }); - expect(findAvailabilityStatus().exists()).toBe(true); + expect(findUserName().exists()).toBe(true); expect(wrapper.text()).toContain(user.name); expect(wrapper.text()).toContain('(Busy)'); }); diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js index 8ed072bed13..eb23a8ef457 100644 --- a/spec/frontend/vue_shared/components/web_ide_link_spec.js +++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; +import ActionsButton from '~/vue_shared/components/actions_button.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import WebIdeLink from '~/vue_shared/components/web_ide_link.vue'; -import ActionsButton from '~/vue_shared/components/actions_button.vue'; const TEST_EDIT_URL = '/gitlab-test/test/-/edit/master/'; const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/master/-/'; diff --git a/spec/frontend/vue_shared/directives/tooltip_spec.js b/spec/frontend/vue_shared/directives/tooltip_spec.js index 28ec23ad4c1..99e8b5b552b 100644 --- a/spec/frontend/vue_shared/directives/tooltip_spec.js +++ b/spec/frontend/vue_shared/directives/tooltip_spec.js @@ -1,6 +1,6 @@ +import { mount } from '@vue/test-utils'; import $ from 'jquery'; import { escape } from 'lodash'; -import { mount } from '@vue/test-utils'; import tooltip from '~/vue_shared/directives/tooltip'; const DEFAULT_TOOLTIP_TEMPLATE = '<div v-tooltip :title="tooltip"></div>'; diff --git a/spec/frontend/vue_shared/directives/track_event_spec.js b/spec/frontend/vue_shared/directives/track_event_spec.js index 8d867c8e3fc..d7d7f4edc3f 100644 --- a/spec/frontend/vue_shared/directives/track_event_spec.js +++ b/spec/frontend/vue_shared/directives/track_event_spec.js @@ -1,11 +1,11 @@ -import Vue from 'vue'; import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Tracking from '~/tracking'; import TrackEvent from '~/vue_shared/directives/track_event'; jest.mock('~/tracking'); -const Component = Vue.component('dummy-element', { +const Component = Vue.component('DummyElement', { directives: { TrackEvent, }, diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js index b3ff7daef2b..7918f70d702 100644 --- a/spec/frontend/vue_shared/security_reports/mock_data.js +++ b/spec/frontend/vue_shared/security_reports/mock_data.js @@ -322,6 +322,23 @@ export const secretScanningDiffSuccessMock = { head_report_created_at: '2020-01-10T10:00:00.000Z', }; +export const securityReportDownloadPathsQueryNoArtifactsResponse = { + project: { + mergeRequest: { + headPipeline: { + id: 'gid://gitlab/Ci::Pipeline/176', + jobs: { + nodes: [], + __typename: 'CiJobConnection', + }, + __typename: 'Pipeline', + }, + __typename: 'MergeRequest', + }, + __typename: 'Project', + }, +}; + export const securityReportDownloadPathsQueryResponse = { project: { mergeRequest: { diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js index 50d1d130675..0b4816a951e 100644 --- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js +++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js @@ -1,6 +1,7 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { merge } from 'lodash'; +import Vue from 'vue'; import VueApollo from 'vue-apollo'; import Vuex from 'vuex'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -8,26 +9,26 @@ import { trimText } from 'helpers/text_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { expectedDownloadDropdownProps, + securityReportDownloadPathsQueryNoArtifactsResponse, securityReportDownloadPathsQueryResponse, sastDiffSuccessMock, secretScanningDiffSuccessMock, } from 'jest/vue_shared/security_reports/mock_data'; -import Api from '~/api'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; +import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue'; +import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; import { REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION, } from '~/vue_shared/security_reports/constants'; -import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue'; -import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; -import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue'; import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql'; +import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue'; jest.mock('~/flash'); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(VueApollo); +Vue.use(Vuex); const SAST_COMPARISON_PATH = '/sast.json'; const SECRET_SCANNING_COMPARISON_PATH = '/secret_detection.json'; @@ -47,7 +48,6 @@ describe('Security reports app', () => { SecurityReportsApp, merge( { - localVue, propsData: { ...props }, stubs: { HelpIcon: true, @@ -60,187 +60,94 @@ describe('Security reports app', () => { const pendingHandler = () => new Promise(() => {}); const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse }); + const successEmptyHandler = () => + Promise.resolve({ data: securityReportDownloadPathsQueryNoArtifactsResponse }); const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] }); const createMockApolloProvider = (handler) => { - localVue.use(VueApollo); - const requestHandlers = [[securityReportDownloadPathsQuery, handler]]; return createMockApollo(requestHandlers); }; - const anyParams = expect.any(Object); - const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown); - const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]'); const findHelpIconComponent = () => wrapper.find(HelpIcon); - const setupMockJobArtifact = (reportType) => { - jest - .spyOn(Api, 'pipelineJobs') - .mockResolvedValue({ data: [{ artifacts: [{ file_type: reportType }] }] }); - }; - const expectPipelinesTabAnchor = () => { - const mrTabsMock = { tabShown: jest.fn() }; - window.mrTabs = mrTabsMock; - findPipelinesTabAnchor().trigger('click'); - expect(mrTabsMock.tabShown.mock.calls).toEqual([['pipelines']]); - }; afterEach(() => { wrapper.destroy(); - delete window.mrTabs; }); - describe.each([false, true])( - 'given the coreSecurityMrWidgetCounts feature flag is %p', - (coreSecurityMrWidgetCounts) => { - const createComponentWithFlag = (options) => - createComponent( - merge( - { - provide: { - glFeatures: { - coreSecurityMrWidgetCounts, - }, - }, - }, - options, - ), - ); - - describe.each(SecurityReportsApp.reportTypes)('given a report type %p', (reportType) => { - beforeEach(() => { - window.mrTabs = { tabShown: jest.fn() }; - setupMockJobArtifact(reportType); - createComponentWithFlag(); - return wrapper.vm.$nextTick(); - }); - - it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); - expect(Api.pipelineJobs).toHaveBeenCalledWith( - props.projectId, - props.pipelineId, - anyParams, - ); - }); - - it('renders the expected message', () => { - expect(wrapper.text()).toMatchInterpolatedText( - SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance, - ); - }); - - describe('clicking the anchor to the pipelines tab', () => { - it('calls the mrTabs.tabShown global', () => { - expectPipelinesTabAnchor(); - }); - }); - - it('renders a help link', () => { - expect(findHelpIconComponent().props()).toEqual({ - helpPath: props.securityReportsDocsPath, - discoverProjectSecurityPath: props.discoverProjectSecurityPath, - }); - }); + describe('given the artifacts query is loading', () => { + beforeEach(() => { + createComponent({ + apolloProvider: createMockApolloProvider(pendingHandler), }); + }); - describe('given a report type "foo"', () => { - beforeEach(() => { - setupMockJobArtifact('foo'); - createComponentWithFlag(); - return wrapper.vm.$nextTick(); - }); - - it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); - expect(Api.pipelineJobs).toHaveBeenCalledWith( - props.projectId, - props.pipelineId, - anyParams, - ); - }); + // TODO: Remove this assertion as part of + // https://gitlab.com/gitlab-org/gitlab/-/issues/273431 + it('initially renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); + }); - it('renders nothing', () => { - expect(wrapper.html()).toBe(''); - }); + describe('given the artifacts query loads successfully', () => { + beforeEach(() => { + createComponent({ + apolloProvider: createMockApolloProvider(successHandler), }); + }); - describe('security artifacts on last page of multi-page response', () => { - const numPages = 3; - - beforeEach(() => { - jest - .spyOn(Api, 'pipelineJobs') - .mockImplementation(async (projectId, pipelineId, { page }) => { - const requestedPage = parseInt(page, 10); - if (requestedPage < numPages) { - return { - // Some jobs with no relevant artifacts - data: [{}, {}], - headers: { 'x-next-page': String(requestedPage + 1) }, - }; - } else if (requestedPage === numPages) { - return { - data: [{ artifacts: [{ file_type: SecurityReportsApp.reportTypes[0] }] }], - }; - } - - throw new Error('Test failed due to request of non-existent jobs page'); - }); - - createComponentWithFlag(); - return wrapper.vm.$nextTick(); - }); + it('renders the download dropdown', () => { + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + }); - it('fetches all pages', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(numPages); - }); + it('renders the expected message', () => { + expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun); + }); - it('renders the expected message', () => { - expect(wrapper.text()).toMatchInterpolatedText( - SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance, - ); - }); + it('renders a help link', () => { + expect(findHelpIconComponent().props()).toEqual({ + helpPath: props.securityReportsDocsPath, + discoverProjectSecurityPath: props.discoverProjectSecurityPath, }); + }); + }); - describe('given an error from the API', () => { - let error; - - beforeEach(() => { - error = new Error('an error'); - jest.spyOn(Api, 'pipelineJobs').mockRejectedValue(error); - createComponentWithFlag(); - return wrapper.vm.$nextTick(); - }); + describe('given the artifacts query loads successfully with no artifacts', () => { + beforeEach(() => { + createComponent({ + apolloProvider: createMockApolloProvider(successEmptyHandler), + }); + }); - it('calls the pipelineJobs API correctly', () => { - expect(Api.pipelineJobs).toHaveBeenCalledTimes(1); - expect(Api.pipelineJobs).toHaveBeenCalledWith( - props.projectId, - props.pipelineId, - anyParams, - ); - }); + // TODO: Remove this assertion as part of + // https://gitlab.com/gitlab-org/gitlab/-/issues/273431 + it('initially renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); + }); - it('renders nothing', () => { - expect(wrapper.html()).toBe(''); - }); + describe('given the artifacts query fails', () => { + beforeEach(() => { + createComponent({ + apolloProvider: createMockApolloProvider(failureHandler), + }); + }); - it('calls createFlash correctly', () => { - expect(createFlash.mock.calls).toEqual([ - [ - { - message: SecurityReportsApp.i18n.apiError, - captureError: true, - error, - }, - ], - ]); - }); + it('calls createFlash correctly', () => { + expect(createFlash).toHaveBeenCalledWith({ + message: SecurityReportsApp.i18n.apiError, + captureError: true, + error: expect.any(Error), }); - }, - ); + }); + + // TODO: Remove this assertion as part of + // https://gitlab.com/gitlab-org/gitlab/-/issues/273431 + it('renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); + }); describe('given the coreSecurityMrWidgetCounts feature flag is enabled', () => { let mock; @@ -253,6 +160,7 @@ describe('Security reports app', () => { coreSecurityMrWidgetCounts: true, }, }, + apolloProvider: createMockApolloProvider(successHandler), }), ); @@ -274,11 +182,7 @@ describe('Security reports app', () => { ${REPORT_TYPE_SECRET_DETECTION} | ${'secretScanningComparisonPath'} | ${SECRET_SCANNING_COMPARISON_PATH} | ${secretScanningDiffSuccessMock} | ${SECRET_SCANNING_SUCCESS_MESSAGE} `( 'given a $pathProp and $reportType artifact', - ({ reportType, pathProp, path, successResponse, successMessage }) => { - beforeEach(() => { - setupMockJobArtifact(reportType); - }); - + ({ pathProp, path, successResponse, successMessage }) => { describe('when loading', () => { beforeEach(() => { mock = new MockAdapter(axios, { delayResponse: 1 }); @@ -294,11 +198,11 @@ describe('Security reports app', () => { }); it('should have loading message', () => { - expect(wrapper.text()).toBe('Security scanning is loading'); + expect(wrapper.text()).toContain('Security scanning is loading'); }); - it('should not render the pipeline tab anchor', () => { - expect(findPipelinesTabAnchor().exists()).toBe(false); + it('renders the download dropdown', () => { + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); }); }); @@ -319,8 +223,8 @@ describe('Security reports app', () => { expect(trimText(wrapper.text())).toContain(successMessage); }); - it('should render the pipeline tab anchor', () => { - expectPipelinesTabAnchor(); + it('renders the download dropdown', () => { + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); }); }); @@ -341,125 +245,25 @@ describe('Security reports app', () => { expect(trimText(wrapper.text())).toContain('Loading resulted in an error'); }); - it('should render the pipeline tab anchor', () => { - expectPipelinesTabAnchor(); + it('renders the download dropdown', () => { + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); }); }); - }, - ); - }); - - describe('given coreSecurityMrWidgetDownloads feature flag is enabled', () => { - const createComponentWithFlagEnabled = (options) => - createComponent( - merge(options, { - provide: { - glFeatures: { - coreSecurityMrWidgetDownloads: true, - }, - }, - }), - ); - - describe('given the query is loading', () => { - beforeEach(() => { - createComponentWithFlagEnabled({ - apolloProvider: createMockApolloProvider(pendingHandler), - }); - }); - // TODO: Remove this assertion as part of - // https://gitlab.com/gitlab-org/gitlab/-/issues/273431 - it('initially renders nothing', () => { - expect(wrapper.html()).toBe(''); - }); - }); - - describe('given the query loads successfully', () => { - beforeEach(() => { - createComponentWithFlagEnabled({ - apolloProvider: createMockApolloProvider(successHandler), - }); - }); - - it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); - }); - - it('renders the expected message', () => { - const text = wrapper.text(); - expect(text).not.toContain(SecurityReportsApp.i18n.scansHaveRunWithDownloadGuidance); - expect(text).toContain(SecurityReportsApp.i18n.scansHaveRun); - }); + describe('when the comparison endpoint is not provided', () => { + beforeEach(() => { + mock.onGet(path).replyOnce(500); - it('should not render the pipeline tab anchor', () => { - expect(findPipelinesTabAnchor().exists()).toBe(false); - }); - }); + createComponentWithFlagEnabled(); - describe('given the query fails', () => { - beforeEach(() => { - createComponentWithFlagEnabled({ - apolloProvider: createMockApolloProvider(failureHandler), - }); - }); + return waitForPromises(); + }); - it('calls createFlash correctly', () => { - expect(createFlash).toHaveBeenCalledWith({ - message: SecurityReportsApp.i18n.apiError, - captureError: true, - error: expect.any(Error), + it('renders the basic scansHaveRun message', () => { + expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun); + }); }); - }); - - // TODO: Remove this assertion as part of - // https://gitlab.com/gitlab-org/gitlab/-/issues/273431 - it('renders nothing', () => { - expect(wrapper.html()).toBe(''); - }); - }); - }); - - describe('given coreSecurityMrWidgetCounts and coreSecurityMrWidgetDownloads feature flags are enabled', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet(SAST_COMPARISON_PATH).replyOnce(200, sastDiffSuccessMock); - mock.onGet(SECRET_SCANNING_COMPARISON_PATH).replyOnce(200, secretScanningDiffSuccessMock); - createComponent({ - propsData: { - sastComparisonPath: SAST_COMPARISON_PATH, - secretScanningComparisonPath: SECRET_SCANNING_COMPARISON_PATH, - }, - provide: { - glFeatures: { - coreSecurityMrWidgetCounts: true, - coreSecurityMrWidgetDownloads: true, - }, - }, - apolloProvider: createMockApolloProvider(successHandler), - }); - - return waitForPromises(); - }); - - afterEach(() => { - mock.restore(); - }); - - it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); - }); - - it('renders the expected counts message', () => { - expect(trimText(wrapper.text())).toContain( - 'Security scanning detected 3 potential vulnerabilities 2 Critical 1 High and 0 Others', - ); - }); - - it('should not render the pipeline tab anchor', () => { - expect(findPipelinesTabAnchor().exists()).toBe(false); - }); + }, + ); }); }); diff --git a/spec/frontend/vue_shared/security_reports/store/getters_spec.js b/spec/frontend/vue_shared/security_reports/store/getters_spec.js index b146a281d7b..97746c7c38b 100644 --- a/spec/frontend/vue_shared/security_reports/store/getters_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/getters_spec.js @@ -1,7 +1,3 @@ -import createState from '~/vue_shared/security_reports/store/state'; -import createSastState from '~/vue_shared/security_reports/store/modules/sast/state'; -import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; -import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils'; import { groupedSummaryText, allReportsHaveError, @@ -11,6 +7,10 @@ import { anyReportHasIssues, summaryCounts, } from '~/vue_shared/security_reports/store/getters'; +import createSastState from '~/vue_shared/security_reports/store/modules/sast/state'; +import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; +import createState from '~/vue_shared/security_reports/store/state'; +import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils'; import { CRITICAL, HIGH, LOW } from '~/vulnerabilities/constants'; const generateVuln = (severity) => ({ severity }); diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js index c9d1db8a504..6af07273cf6 100644 --- a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js @@ -1,10 +1,10 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import createState from '~/vue_shared/security_reports/store/modules/sast/state'; -import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types'; -import * as actions from '~/vue_shared/security_reports/store/modules/sast/actions'; import axios from '~/lib/utils/axios_utils'; +import * as actions from '~/vue_shared/security_reports/store/modules/sast/actions'; +import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types'; +import createState from '~/vue_shared/security_reports/store/modules/sast/state'; const diffEndpoint = 'diff-endpoint.json'; const blobPath = 'blob-path.json'; diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js index fd611f38a34..d6119f44619 100644 --- a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js @@ -1,6 +1,6 @@ import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types'; -import createState from '~/vue_shared/security_reports/store/modules/sast/state'; import mutations from '~/vue_shared/security_reports/store/modules/sast/mutations'; +import createState from '~/vue_shared/security_reports/store/modules/sast/state'; const createIssue = ({ ...config }) => ({ changed: false, ...config }); diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js index beda1a55438..d22fee864e7 100644 --- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js @@ -1,10 +1,10 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; -import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types'; -import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions'; import axios from '~/lib/utils/axios_utils'; +import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions'; +import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types'; +import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; const diffEndpoint = 'diff-endpoint.json'; const blobPath = 'blob-path.json'; diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js index 13fcc0f47a3..42da7476a40 100644 --- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js @@ -1,6 +1,6 @@ import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types'; -import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; import mutations from '~/vue_shared/security_reports/store/modules/secret_detection/mutations'; +import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; const createIssue = ({ ...config }) => ({ changed: false, ...config }); diff --git a/spec/frontend/vue_shared/security_reports/utils_spec.js b/spec/frontend/vue_shared/security_reports/utils_spec.js index 7e5a27694ef..aa9e54fa10c 100644 --- a/spec/frontend/vue_shared/security_reports/utils_spec.js +++ b/spec/frontend/vue_shared/security_reports/utils_spec.js @@ -1,9 +1,9 @@ -import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils'; import { REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION, REPORT_FILE_TYPES, } from '~/vue_shared/security_reports/constants'; +import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils'; import { securityReportDownloadPathsQueryResponse, sastArtifacts, |