summaryrefslogtreecommitdiff
path: root/spec/frontend/sidebar/components
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/sidebar/components')
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js27
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js41
-rw-r--r--spec/frontend/sidebar/components/attention_requested_toggle_spec.js62
-rw-r--r--spec/frontend/sidebar/components/incidents/escalation_status_spec.js52
-rw-r--r--spec/frontend/sidebar/components/incidents/escalation_utils_spec.js18
-rw-r--r--spec/frontend/sidebar/components/incidents/mock_data.js39
-rw-r--r--spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js207
7 files changed, 413 insertions, 33 deletions
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
index def46255994..5fd364afbe4 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
@@ -1,4 +1,4 @@
-import { GlSearchBoxByType, GlDropdown } from '@gitlab/ui';
+import { GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
@@ -76,7 +76,16 @@ describe('Sidebar assignees widget', () => {
SidebarEditableItem,
UserSelect,
GlSearchBoxByType,
- GlDropdown,
+ GlDropdown: {
+ template: `
+ <div>
+ <slot name="footer"></slot>
+ </div>
+ `,
+ methods: {
+ show: jest.fn(),
+ },
+ },
},
});
};
@@ -340,21 +349,9 @@ describe('Sidebar assignees widget', () => {
});
});
- it('when realtime feature flag is disabled', async () => {
+ it('includes the real-time assignees component', async () => {
createComponent();
await waitForPromises();
- expect(findRealtimeAssignees().exists()).toBe(false);
- });
-
- it('when realtime feature flag is enabled', async () => {
- createComponent({
- provide: {
- glFeatures: {
- realTimeIssueSidebar: true,
- },
- },
- });
- await waitForPromises();
expect(findRealtimeAssignees().exists()).toBe(true);
});
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
index 88a5f4ea8b7..71424aaead3 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
@@ -1,5 +1,6 @@
-import { GlAvatarLabeled } from '@gitlab/ui';
+import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { IssuableType } from '~/issues/constants';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
const user = {
@@ -13,14 +14,24 @@ describe('Sidebar participant component', () => {
let wrapper;
const findAvatar = () => wrapper.findComponent(GlAvatarLabeled);
+ const findIcon = () => wrapper.findComponent(GlIcon);
- const createComponent = (status = null) => {
+ const createComponent = ({
+ status = null,
+ issuableType = IssuableType.Issue,
+ canMerge = false,
+ } = {}) => {
wrapper = shallowMount(SidebarParticipant, {
propsData: {
user: {
...user,
+ canMerge,
status,
},
+ issuableType,
+ },
+ stubs: {
+ GlAvatarLabeled,
},
});
};
@@ -29,15 +40,35 @@ describe('Sidebar participant component', () => {
wrapper.destroy();
});
- it('when user is not busy', () => {
+ it('does not show `Busy` status when user is not busy', () => {
createComponent();
expect(findAvatar().props('label')).toBe(user.name);
});
- it('when user is busy', () => {
- createComponent({ availability: 'BUSY' });
+ it('shows `Busy` status when user is busy', () => {
+ createComponent({ status: { availability: 'BUSY' } });
expect(findAvatar().props('label')).toBe(`${user.name} (Busy)`);
});
+
+ it('does not render a warning icon', () => {
+ createComponent();
+
+ expect(findIcon().exists()).toBe(false);
+ });
+
+ describe('when on merge request sidebar', () => {
+ it('when project member cannot merge', () => {
+ createComponent({ issuableType: IssuableType.MergeRequest });
+
+ expect(findIcon().exists()).toBe(true);
+ });
+
+ it('when project member can merge', () => {
+ createComponent({ issuableType: IssuableType.MergeRequest, canMerge: true });
+
+ expect(findIcon().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/sidebar/components/attention_requested_toggle_spec.js b/spec/frontend/sidebar/components/attention_requested_toggle_spec.js
index 0939297a754..a9ae23c1624 100644
--- a/spec/frontend/sidebar/components/attention_requested_toggle_spec.js
+++ b/spec/frontend/sidebar/components/attention_requested_toggle_spec.js
@@ -16,7 +16,10 @@ describe('Attention require toggle', () => {
});
it('renders button', () => {
- factory({ type: 'reviewer', user: { attention_requested: false } });
+ factory({
+ type: 'reviewer',
+ user: { attention_requested: false, can_update_merge_request: true },
+ });
expect(findToggle().exists()).toBe(true);
});
@@ -28,7 +31,10 @@ describe('Attention require toggle', () => {
`(
'renders $icon icon when attention_requested is $attentionRequested',
({ attentionRequested, icon }) => {
- factory({ type: 'reviewer', user: { attention_requested: attentionRequested } });
+ factory({
+ type: 'reviewer',
+ user: { attention_requested: attentionRequested, can_update_merge_request: true },
+ });
expect(findToggle().props('icon')).toBe(icon);
},
@@ -41,27 +47,47 @@ describe('Attention require toggle', () => {
`(
'renders button with variant $variant when attention_requested is $attentionRequested',
({ attentionRequested, variant }) => {
- factory({ type: 'reviewer', user: { attention_requested: attentionRequested } });
+ factory({
+ type: 'reviewer',
+ user: { attention_requested: attentionRequested, can_update_merge_request: true },
+ });
expect(findToggle().props('variant')).toBe(variant);
},
);
it('emits toggle-attention-requested on click', async () => {
- factory({ type: 'reviewer', user: { attention_requested: true } });
+ factory({
+ type: 'reviewer',
+ user: { attention_requested: true, can_update_merge_request: true },
+ });
await findToggle().trigger('click');
expect(wrapper.emitted('toggle-attention-requested')[0]).toEqual([
{
- user: { attention_requested: true },
+ user: { attention_requested: true, can_update_merge_request: true },
callback: expect.anything(),
},
]);
});
+ it('does not emit toggle-attention-requested on click if can_update_merge_request is false', async () => {
+ factory({
+ type: 'reviewer',
+ user: { attention_requested: true, can_update_merge_request: false },
+ });
+
+ await findToggle().trigger('click');
+
+ expect(wrapper.emitted('toggle-attention-requested')).toBe(undefined);
+ });
+
it('sets loading on click', async () => {
- factory({ type: 'reviewer', user: { attention_requested: true } });
+ factory({
+ type: 'reviewer',
+ user: { attention_requested: true, can_update_merge_request: true },
+ });
await findToggle().trigger('click');
@@ -69,14 +95,24 @@ describe('Attention require toggle', () => {
});
it.each`
- type | attentionRequested | tooltip
- ${'reviewer'} | ${true} | ${AttentionRequestedToggle.i18n.removeAttentionRequested}
- ${'reviewer'} | ${false} | ${AttentionRequestedToggle.i18n.attentionRequestedReviewer}
- ${'assignee'} | ${false} | ${AttentionRequestedToggle.i18n.attentionRequestedAssignee}
+ type | attentionRequested | tooltip | canUpdateMergeRequest
+ ${'reviewer'} | ${true} | ${AttentionRequestedToggle.i18n.removeAttentionRequested} | ${true}
+ ${'reviewer'} | ${false} | ${AttentionRequestedToggle.i18n.attentionRequestedReviewer} | ${true}
+ ${'assignee'} | ${false} | ${AttentionRequestedToggle.i18n.attentionRequestedAssignee} | ${true}
+ ${'reviewer'} | ${true} | ${AttentionRequestedToggle.i18n.attentionRequestedNoPermission} | ${false}
+ ${'reviewer'} | ${false} | ${AttentionRequestedToggle.i18n.noAttentionRequestedNoPermission} | ${false}
+ ${'assignee'} | ${true} | ${AttentionRequestedToggle.i18n.attentionRequestedNoPermission} | ${false}
+ ${'assignee'} | ${false} | ${AttentionRequestedToggle.i18n.noAttentionRequestedNoPermission} | ${false}
`(
- 'sets tooltip as $tooltip when attention_requested is $attentionRequested and type is $type',
- ({ type, attentionRequested, tooltip }) => {
- factory({ type, user: { attention_requested: attentionRequested } });
+ 'sets tooltip as $tooltip when attention_requested is $attentionRequested, type is $type and, can_update_merge_request is $canUpdateMergeRequest',
+ ({ type, attentionRequested, tooltip, canUpdateMergeRequest }) => {
+ factory({
+ type,
+ user: {
+ attention_requested: attentionRequested,
+ can_update_merge_request: canUpdateMergeRequest,
+ },
+ });
expect(findToggle().attributes('aria-label')).toBe(tooltip);
},
diff --git a/spec/frontend/sidebar/components/incidents/escalation_status_spec.js b/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
new file mode 100644
index 00000000000..7a736624fc0
--- /dev/null
+++ b/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
@@ -0,0 +1,52 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import EscalationStatus from '~/sidebar/components/incidents/escalation_status.vue';
+import {
+ STATUS_LABELS,
+ STATUS_TRIGGERED,
+ STATUS_ACKNOWLEDGED,
+} from '~/sidebar/components/incidents/constants';
+
+describe('EscalationStatus', () => {
+ let wrapper;
+
+ function createComponent(props) {
+ wrapper = mountExtended(EscalationStatus, {
+ propsData: {
+ value: STATUS_TRIGGERED,
+ ...props,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDropdownComponent = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+
+ describe('status', () => {
+ it('shows the current status', () => {
+ createComponent({ value: STATUS_ACKNOWLEDGED });
+
+ expect(findDropdownComponent().props('text')).toBe(STATUS_LABELS[STATUS_ACKNOWLEDGED]);
+ });
+
+ it('shows the None option when status is null', () => {
+ createComponent({ value: null });
+
+ expect(findDropdownComponent().props('text')).toBe('None');
+ });
+ });
+
+ describe('events', () => {
+ it('selects an item', async () => {
+ createComponent();
+
+ await findDropdownItems().at(1).vm.$emit('click');
+
+ expect(wrapper.emitted().input[0][0]).toBe(STATUS_ACKNOWLEDGED);
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/incidents/escalation_utils_spec.js b/spec/frontend/sidebar/components/incidents/escalation_utils_spec.js
new file mode 100644
index 00000000000..edd65db0325
--- /dev/null
+++ b/spec/frontend/sidebar/components/incidents/escalation_utils_spec.js
@@ -0,0 +1,18 @@
+import { STATUS_ACKNOWLEDGED } from '~/sidebar/components/incidents/constants';
+import { getStatusLabel } from '~/sidebar/components/incidents/utils';
+
+describe('EscalationUtils', () => {
+ describe('getStatusLabel', () => {
+ it('returns a label when provided with a valid status', () => {
+ const label = getStatusLabel(STATUS_ACKNOWLEDGED);
+
+ expect(label).toEqual('Acknowledged');
+ });
+
+ it("returns 'None' when status is null", () => {
+ const label = getStatusLabel(null);
+
+ expect(label).toEqual('None');
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/incidents/mock_data.js b/spec/frontend/sidebar/components/incidents/mock_data.js
new file mode 100644
index 00000000000..bbb6c61b162
--- /dev/null
+++ b/spec/frontend/sidebar/components/incidents/mock_data.js
@@ -0,0 +1,39 @@
+import { STATUS_TRIGGERED, STATUS_ACKNOWLEDGED } from '~/sidebar/components/incidents/constants';
+
+export const fetchData = {
+ workspace: {
+ __typename: 'Project',
+ id: 'gid://gitlab/Project/2',
+ issuable: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/4',
+ escalationStatus: STATUS_TRIGGERED,
+ },
+ },
+};
+
+export const mutationData = {
+ issueSetEscalationStatus: {
+ __typename: 'IssueSetEscalationStatusPayload',
+ errors: [],
+ clientMutationId: null,
+ issue: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/4',
+ escalationStatus: STATUS_ACKNOWLEDGED,
+ },
+ },
+};
+
+export const fetchError = {
+ workspace: {
+ __typename: 'Project',
+ },
+};
+
+export const mutationError = {
+ issueSetEscalationStatus: {
+ __typename: 'IssueSetEscalationStatusPayload',
+ errors: ['hello'],
+ },
+};
diff --git a/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js b/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js
new file mode 100644
index 00000000000..a8dc610672c
--- /dev/null
+++ b/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js
@@ -0,0 +1,207 @@
+import { createLocalVue } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import SidebarEscalationStatus from '~/sidebar/components/incidents/sidebar_escalation_status.vue';
+import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
+import { escalationStatusQuery, escalationStatusMutation } from '~/sidebar/constants';
+import waitForPromises from 'helpers/wait_for_promises';
+import EscalationStatus from 'ee_else_ce/sidebar/components/incidents/escalation_status.vue';
+import { STATUS_ACKNOWLEDGED } from '~/sidebar/components/incidents/constants';
+import { createAlert } from '~/flash';
+import { logError } from '~/lib/logger';
+import { fetchData, fetchError, mutationData, mutationError } from './mock_data';
+
+jest.mock('~/lib/logger');
+jest.mock('~/flash');
+
+const localVue = createLocalVue();
+
+describe('SidebarEscalationStatus', () => {
+ let wrapper;
+ const queryResolverMock = jest.fn();
+ const mutationResolverMock = jest.fn();
+
+ function createMockApolloProvider({ hasFetchError = false, hasMutationError = false } = {}) {
+ localVue.use(VueApollo);
+
+ queryResolverMock.mockResolvedValue({ data: hasFetchError ? fetchError : fetchData });
+ mutationResolverMock.mockResolvedValue({
+ data: hasMutationError ? mutationError : mutationData,
+ });
+
+ const requestHandlers = [
+ [escalationStatusQuery, queryResolverMock],
+ [escalationStatusMutation, mutationResolverMock],
+ ];
+
+ return createMockApollo(requestHandlers);
+ }
+
+ function createComponent({ mockApollo } = {}) {
+ let config;
+
+ if (mockApollo) {
+ config = { apolloProvider: mockApollo };
+ } else {
+ config = { mocks: { $apollo: { queries: { status: { loading: false } } } } };
+ }
+
+ wrapper = mountExtended(SidebarEscalationStatus, {
+ propsData: {
+ iid: '1',
+ projectPath: 'gitlab-org/gitlab',
+ issuableType: 'issue',
+ },
+ provide: {
+ canUpdate: true,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ localVue,
+ ...config,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findSidebarComponent = () => wrapper.findComponent(SidebarEditableItem);
+ const findStatusComponent = () => wrapper.findComponent(EscalationStatus);
+ const findEditButton = () => wrapper.findByTestId('edit-button');
+ const findIcon = () => wrapper.findByTestId('status-icon');
+
+ const clickEditButton = async () => {
+ findEditButton().vm.$emit('click');
+ await nextTick();
+ };
+ const selectAcknowledgedStatus = async () => {
+ findStatusComponent().vm.$emit('input', STATUS_ACKNOWLEDGED);
+ // wait for apollo requests
+ await waitForPromises();
+ };
+
+ describe('sidebar', () => {
+ it('renders the sidebar component', () => {
+ createComponent();
+ expect(findSidebarComponent().exists()).toBe(true);
+ });
+
+ describe('status icon', () => {
+ it('is visible', () => {
+ createComponent();
+
+ expect(findIcon().exists()).toBe(true);
+ expect(findIcon().isVisible()).toBe(true);
+ });
+
+ it('has correct tooltip', async () => {
+ const mockApollo = createMockApolloProvider();
+ createComponent({ mockApollo });
+
+ // wait for apollo requests
+ await waitForPromises();
+
+ const tooltip = getBinding(findIcon().element, 'gl-tooltip');
+
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value).toBe('Status: Triggered');
+ });
+ });
+
+ describe('status dropdown', () => {
+ beforeEach(async () => {
+ const mockApollo = createMockApolloProvider();
+ createComponent({ mockApollo });
+
+ // wait for apollo requests
+ await waitForPromises();
+ });
+
+ it('is closed by default', () => {
+ expect(findStatusComponent().exists()).toBe(true);
+ expect(findStatusComponent().isVisible()).toBe(false);
+ });
+
+ it('is shown after clicking the edit button', async () => {
+ await clickEditButton();
+
+ expect(findStatusComponent().isVisible()).toBe(true);
+ });
+
+ it('is hidden after clicking the edit button, when open already', async () => {
+ await clickEditButton();
+ await clickEditButton();
+
+ expect(findStatusComponent().isVisible()).toBe(false);
+ });
+ });
+
+ describe('update Status event', () => {
+ beforeEach(async () => {
+ const mockApollo = createMockApolloProvider();
+ createComponent({ mockApollo });
+
+ // wait for apollo requests
+ await waitForPromises();
+
+ await clickEditButton();
+ await selectAcknowledgedStatus();
+ });
+
+ it('calls the mutation', () => {
+ const mutationVariables = {
+ iid: '1',
+ projectPath: 'gitlab-org/gitlab',
+ status: STATUS_ACKNOWLEDGED,
+ };
+
+ expect(mutationResolverMock).toHaveBeenCalledWith(mutationVariables);
+ });
+
+ it('closes the dropdown', () => {
+ expect(findStatusComponent().isVisible()).toBe(false);
+ });
+
+ it('updates the status', () => {
+ // Sometimes status has a intermediate wrapping component. A quirk of vue-test-utils
+ // means that in that case 'value' is exposed as a prop. If no wrapping component
+ // exists it is exposed as an attribute.
+ const statusValue =
+ findStatusComponent().props('value') || findStatusComponent().attributes('value');
+ expect(statusValue).toBe(STATUS_ACKNOWLEDGED);
+ });
+ });
+
+ describe('mutation errors', () => {
+ it('should error upon fetch', async () => {
+ const mockApollo = createMockApolloProvider({ hasFetchError: true });
+ createComponent({ mockApollo });
+
+ // wait for apollo requests
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalled();
+ expect(logError).toHaveBeenCalled();
+ });
+
+ it('should error upon mutation', async () => {
+ const mockApollo = createMockApolloProvider({ hasMutationError: true });
+ createComponent({ mockApollo });
+
+ // wait for apollo requests
+ await waitForPromises();
+
+ await clickEditButton();
+ await selectAcknowledgedStatus();
+
+ expect(createAlert).toHaveBeenCalled();
+ expect(logError).toHaveBeenCalled();
+ });
+ });
+ });
+});