summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 06:09:57 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 06:09:57 +0000
commit93c27b216aa57d57ebd8f5f2581e45dc300324b8 (patch)
treea6cbeb92b9f24baf499cc9ed276e1c0a70ea50c7 /spec/frontend
parent43b91399aeb9f58afc9599b51a941b2bbc745a28 (diff)
downloadgitlab-ce-93c27b216aa57d57ebd8f5f2581e45dc300324b8.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js87
-rw-r--r--spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap30
-rw-r--r--spec/frontend/boards/components/board_blocked_icon_spec.js226
-rw-r--r--spec/frontend/boards/components/board_content_sidebar_spec.js125
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js58
-rw-r--r--spec/frontend/boards/mock_data.js94
-rw-r--r--spec/frontend/boards/stores/actions_spec.js46
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js10
8 files changed, 646 insertions, 30 deletions
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index 4487fc15de6..36043b09636 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -1,11 +1,14 @@
import { GlLabel } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { range } from 'lodash';
+import Vuex from 'vuex';
+import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
+import { issuableTypes } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import defaultStore from '~/boards/stores';
import { updateHistory } from '~/lib/utils/url_utility';
-import { mockLabelList } from './mock_data';
+import { mockLabelList, mockIssue } from './mock_data';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/boards/eventhub');
@@ -29,8 +32,28 @@ describe('Board card component', () => {
let wrapper;
let issue;
let list;
+ let store;
+
+ const findBoardBlockedIcon = () => wrapper.find(BoardBlockedIcon);
+
+ const createStore = () => {
+ store = new Vuex.Store({
+ ...defaultStore,
+ state: {
+ ...defaultStore.state,
+ issuableType: issuableTypes.issue,
+ },
+ getters: {
+ isGroupBoard: () => true,
+ isEpicBoard: () => false,
+ isProjectBoard: () => false,
+ },
+ });
+ };
+
+ const createWrapper = (props = {}) => {
+ createStore();
- const createWrapper = (props = {}, store = defaultStore) => {
wrapper = mount(BoardCardInner, {
store,
propsData: {
@@ -41,6 +64,13 @@ describe('Board card component', () => {
stubs: {
GlLabel: true,
},
+ mocks: {
+ $apollo: {
+ queries: {
+ blockingIssuables: { loading: false },
+ },
+ },
+ },
provide: {
rootPath: '/',
scopedLabelsAvailable: false,
@@ -51,14 +81,9 @@ describe('Board card component', () => {
beforeEach(() => {
list = mockLabelList;
issue = {
- title: 'Testing',
- id: 1,
- iid: 1,
- confidential: false,
+ ...mockIssue,
labels: [list.label],
assignees: [],
- referencePath: '#1',
- webUrl: '/test/1',
weight: 1,
};
@@ -68,6 +93,7 @@ describe('Board card component', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ store = null;
jest.clearAllMocks();
});
@@ -87,18 +113,38 @@ describe('Board card component', () => {
expect(wrapper.find('.confidential-icon').exists()).toBe(false);
});
- it('does not render blocked icon', () => {
- expect(wrapper.find('.issue-blocked-icon').exists()).toBe(false);
- });
-
it('renders issue ID with #', () => {
- expect(wrapper.find('.board-card-number').text()).toContain(`#${issue.id}`);
+ expect(wrapper.find('.board-card-number').text()).toContain(`#${issue.iid}`);
});
it('does not render assignee', () => {
expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(false);
});
+ describe('blocked', () => {
+ it('renders blocked icon if issue is blocked', async () => {
+ createWrapper({
+ item: {
+ ...issue,
+ blocked: true,
+ },
+ });
+
+ expect(findBoardBlockedIcon().exists()).toBe(true);
+ });
+
+ it('does not show blocked icon if issue is not blocked', () => {
+ createWrapper({
+ item: {
+ ...issue,
+ blocked: false,
+ },
+ });
+
+ expect(findBoardBlockedIcon().exists()).toBe(false);
+ });
+ });
+
describe('confidential issue', () => {
beforeEach(() => {
wrapper.setProps({
@@ -303,21 +349,6 @@ describe('Board card component', () => {
});
});
- describe('blocked', () => {
- beforeEach(() => {
- wrapper.setProps({
- item: {
- ...wrapper.props('item'),
- blocked: true,
- },
- });
- });
-
- it('renders blocked icon if issue is blocked', () => {
- expect(wrapper.find('.issue-blocked-icon').exists()).toBe(true);
- });
- });
-
describe('filterByLabel method', () => {
beforeEach(() => {
delete window.location;
diff --git a/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap
new file mode 100644
index 00000000000..c000f300e4d
--- /dev/null
+++ b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`BoardBlockedIcon on mouseenter on blocked icon with more than three blocking issues matches the snapshot 1`] = `
+"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" aria-hidden=\\"true\\" class=\\"issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-icon s16\\" id=\\"blocked-icon-uniqueId\\">
+ <use href=\\"#issue-block\\"></use>
+ </svg>
+ <div class=\\"gl-popover\\">
+ <ul class=\\"gl-list-style-none gl-p-0\\">
+ <li><a href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/6\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">my-project-1#6</a>
+ <p data-testid=\\"issuable-title\\" class=\\"gl-mb-3 gl-display-block!\\">
+ blocking issue title 1
+ </p>
+ </li>
+ <li><a href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/5\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">my-project-1#5</a>
+ <p data-testid=\\"issuable-title\\" class=\\"gl-mb-3 gl-display-block!\\">
+ blocking issue title 2 + blocking issue title 2 + blocking issue title 2 + bloc…
+ </p>
+ </li>
+ <li><a href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/4\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">my-project-1#4</a>
+ <p data-testid=\\"issuable-title\\" class=\\"gl-mb-3 gl-display-block!\\">
+ blocking issue title 3
+ </p>
+ </li>
+ </ul>
+ <div class=\\"gl-mt-4\\">
+ <p data-testid=\\"hidden-blocking-count\\" class=\\"gl-mb-3\\">+ 1 more issue</p> <a data-testid=\\"view-all-issues\\" href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/0#related-issues\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">View all blocking issues</a>
+ </div><span data-testid=\\"popover-title\\">Blocked by 4 issues</span>
+ </div>
+</div>"
+`;
diff --git a/spec/frontend/boards/components/board_blocked_icon_spec.js b/spec/frontend/boards/components/board_blocked_icon_spec.js
new file mode 100644
index 00000000000..7b04942f056
--- /dev/null
+++ b/spec/frontend/boards/components/board_blocked_icon_spec.js
@@ -0,0 +1,226 @@
+import { GlIcon, GlLink, GlPopover, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
+import { blockingIssuablesQueries, issuableTypes } from '~/boards/constants';
+import { truncate } from '~/lib/utils/text_utility';
+import {
+ mockIssue,
+ mockBlockingIssue1,
+ mockBlockingIssue2,
+ mockBlockingIssuablesResponse1,
+ mockBlockingIssuablesResponse2,
+ mockBlockingIssuablesResponse3,
+ mockBlockedIssue1,
+ mockBlockedIssue2,
+} from '../mock_data';
+
+describe('BoardBlockedIcon', () => {
+ let wrapper;
+ let mockApollo;
+
+ const findGlIcon = () => wrapper.find(GlIcon);
+ const findGlPopover = () => wrapper.find(GlPopover);
+ const findGlLink = () => wrapper.find(GlLink);
+ const findPopoverTitle = () => wrapper.findByTestId('popover-title');
+ const findIssuableTitle = () => wrapper.findByTestId('issuable-title');
+ const findHiddenBlockingCount = () => wrapper.findByTestId('hidden-blocking-count');
+ const findViewAllIssuableLink = () => wrapper.findByTestId('view-all-issues');
+
+ const waitForApollo = async () => {
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
+ };
+
+ const mouseenter = async () => {
+ findGlIcon().vm.$emit('mouseenter');
+
+ await wrapper.vm.$nextTick();
+ await waitForApollo();
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const createWrapperWithApollo = ({
+ item = mockBlockedIssue1,
+ blockingIssuablesSpy = jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1),
+ } = {}) => {
+ mockApollo = createMockApollo([
+ [blockingIssuablesQueries[issuableTypes.issue].query, blockingIssuablesSpy],
+ ]);
+
+ Vue.use(VueApollo);
+ wrapper = extendedWrapper(
+ mount(BoardBlockedIcon, {
+ apolloProvider: mockApollo,
+ propsData: {
+ item: {
+ ...mockIssue,
+ ...item,
+ },
+ uniqueId: 'uniqueId',
+ issuableType: issuableTypes.issue,
+ },
+ attachTo: document.body,
+ }),
+ );
+ };
+
+ const createWrapper = ({ item = {}, queries = {}, data = {}, loading = false } = {}) => {
+ wrapper = extendedWrapper(
+ shallowMount(BoardBlockedIcon, {
+ propsData: {
+ item: {
+ ...mockIssue,
+ ...item,
+ },
+ uniqueId: 'uniqueid',
+ issuableType: issuableTypes.issue,
+ },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ blockingIssuables: { loading },
+ ...queries,
+ },
+ },
+ },
+ stubs: {
+ GlPopover,
+ },
+ attachTo: document.body,
+ }),
+ );
+ };
+
+ it('should render blocked icon', () => {
+ createWrapper();
+
+ expect(findGlIcon().exists()).toBe(true);
+ });
+
+ it('should display a loading spinner while loading', () => {
+ createWrapper({ loading: true });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('should not query for blocking issuables by default', async () => {
+ createWrapperWithApollo();
+
+ expect(findGlPopover().text()).not.toContain(mockBlockingIssue1.title);
+ });
+
+ describe('on mouseenter on blocked icon', () => {
+ it('should query for blocking issuables and render the result', async () => {
+ createWrapperWithApollo();
+
+ expect(findGlPopover().text()).not.toContain(mockBlockingIssue1.title);
+
+ await mouseenter();
+
+ expect(findGlPopover().exists()).toBe(true);
+ expect(findIssuableTitle().text()).toContain(mockBlockingIssue1.title);
+ expect(wrapper.vm.skip).toBe(true);
+ });
+
+ it('should emit "blocking-issuables-error" event on query error', async () => {
+ const mockError = new Error('mayday');
+ createWrapperWithApollo({ blockingIssuablesSpy: jest.fn().mockRejectedValue(mockError) });
+
+ await mouseenter();
+
+ const [
+ [
+ {
+ message,
+ error: { networkError },
+ },
+ ],
+ ] = wrapper.emitted('blocking-issuables-error');
+ expect(message).toBe('Failed to fetch blocking issues');
+ expect(networkError).toBe(mockError);
+ });
+
+ describe('with a single blocking issue', () => {
+ beforeEach(async () => {
+ createWrapperWithApollo();
+
+ await mouseenter();
+ });
+
+ it('should render a title of the issuable', async () => {
+ expect(findIssuableTitle().text()).toBe(mockBlockingIssue1.title);
+ });
+
+ it('should render issuable reference and link to the issuable', async () => {
+ const formattedRef = mockBlockingIssue1.reference.split('/')[1];
+
+ expect(findGlLink().text()).toBe(formattedRef);
+ expect(findGlLink().attributes('href')).toBe(mockBlockingIssue1.webUrl);
+ });
+
+ it('should render popover title with correct blocking issuable count', async () => {
+ expect(findPopoverTitle().text()).toBe('Blocked by 1 issue');
+ });
+ });
+
+ describe('when issue has a long title', () => {
+ it('should render a truncated title', async () => {
+ createWrapperWithApollo({
+ blockingIssuablesSpy: jest.fn().mockResolvedValue(mockBlockingIssuablesResponse2),
+ });
+
+ await mouseenter();
+
+ const truncatedTitle = truncate(
+ mockBlockingIssue2.title,
+ wrapper.vm.$options.textTruncateWidth,
+ );
+ expect(findIssuableTitle().text()).toBe(truncatedTitle);
+ });
+ });
+
+ describe('with more than three blocking issues', () => {
+ beforeEach(async () => {
+ createWrapperWithApollo({
+ item: mockBlockedIssue2,
+ blockingIssuablesSpy: jest.fn().mockResolvedValue(mockBlockingIssuablesResponse3),
+ });
+
+ await mouseenter();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+
+ it('should render popover title with correct blocking issuable count', async () => {
+ expect(findPopoverTitle().text()).toBe('Blocked by 4 issues');
+ });
+
+ it('should render the number of hidden blocking issuables', () => {
+ expect(findHiddenBlockingCount().text()).toBe('+ 1 more issue');
+ });
+
+ it('should link to the blocked issue page at the related issue anchor', async () => {
+ expect(findViewAllIssuableLink().text()).toBe('View all blocking issues');
+ expect(findViewAllIssuableLink().attributes('href')).toBe(
+ `${mockBlockedIssue2.webUrl}#related-issues`,
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js
new file mode 100644
index 00000000000..b4a01c78e6b
--- /dev/null
+++ b/spec/frontend/boards/components/board_content_sidebar_spec.js
@@ -0,0 +1,125 @@
+import { GlDrawer } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { stubComponent } from 'helpers/stub_component';
+import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
+import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
+import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue';
+import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
+import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
+import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
+import { ISSUABLE } from '~/boards/constants';
+import { mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data';
+
+describe('BoardContentSidebar', () => {
+ let wrapper;
+ let store;
+
+ const createStore = ({ mockGetters = {}, mockActions = {} } = {}) => {
+ store = new Vuex.Store({
+ state: {
+ sidebarType: ISSUABLE,
+ issues: { [mockIssue.id]: mockIssue },
+ activeId: mockIssue.id,
+ issuableType: 'issue',
+ },
+ getters: {
+ activeIssue: () => mockIssue,
+ groupPathForActiveIssue: () => mockIssueGroupPath,
+ projectPathForActiveIssue: () => mockIssueProjectPath,
+ isSidebarOpen: () => true,
+ ...mockGetters,
+ },
+ actions: mockActions,
+ });
+ };
+
+ const createComponent = () => {
+ wrapper = shallowMount(BoardContentSidebar, {
+ provide: {
+ canUpdate: true,
+ rootPath: '/',
+ groupId: '#',
+ },
+ store,
+ stubs: {
+ GlDrawer: stubComponent(GlDrawer, {
+ template: '<div><slot name="header"></slot><slot></slot></div>',
+ }),
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ participants: {
+ loading: false,
+ },
+ },
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createStore();
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('confirms we render GlDrawer', () => {
+ expect(wrapper.find(GlDrawer).exists()).toBe(true);
+ });
+
+ it('does not render GlDrawer when isSidebarOpen is false', () => {
+ createStore({ mockGetters: { isSidebarOpen: () => false } });
+ createComponent();
+
+ expect(wrapper.find(GlDrawer).exists()).toBe(false);
+ });
+
+ it('applies an open attribute', () => {
+ expect(wrapper.find(GlDrawer).props('open')).toBe(true);
+ });
+
+ it('renders BoardSidebarLabelsSelect', () => {
+ expect(wrapper.find(BoardSidebarLabelsSelect).exists()).toBe(true);
+ });
+
+ it('renders BoardSidebarIssueTitle', () => {
+ expect(wrapper.find(BoardSidebarIssueTitle).exists()).toBe(true);
+ });
+
+ it('renders BoardSidebarDueDate', () => {
+ expect(wrapper.find(BoardSidebarDueDate).exists()).toBe(true);
+ });
+
+ it('renders BoardSidebarSubscription', () => {
+ expect(wrapper.find(BoardSidebarSubscription).exists()).toBe(true);
+ });
+
+ it('renders BoardSidebarMilestoneSelect', () => {
+ expect(wrapper.find(BoardSidebarMilestoneSelect).exists()).toBe(true);
+ });
+
+ describe('when we emit close', () => {
+ let toggleBoardItem;
+
+ beforeEach(() => {
+ toggleBoardItem = jest.fn();
+ createStore({ mockActions: { toggleBoardItem } });
+ createComponent();
+ });
+
+ it('calls toggleBoardItem with correct parameters', async () => {
+ wrapper.find(GlDrawer).vm.$emit('close');
+
+ expect(toggleBoardItem).toHaveBeenCalledTimes(1);
+ expect(toggleBoardItem).toHaveBeenCalledWith(expect.any(Object), {
+ boardItem: mockIssue,
+ sidebarType: ISSUABLE,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
new file mode 100644
index 00000000000..03924bfa8d3
--- /dev/null
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
@@ -0,0 +1,58 @@
+/*
+ To avoid duplicating tests in time_tracker.spec,
+ this spec only contains a simple test to check rendering.
+
+ A detailed feature spec is used to test time tracking feature
+ in swimlanes sidebar.
+*/
+
+import { shallowMount } from '@vue/test-utils';
+import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
+import { createStore } from '~/boards/stores';
+import IssuableTimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
+
+describe('BoardSidebarTimeTracker', () => {
+ let wrapper;
+ let store;
+
+ const createComponent = (options) => {
+ wrapper = shallowMount(BoardSidebarTimeTracker, {
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ store.state.boardItems = {
+ 1: {
+ timeEstimate: 3600,
+ totalTimeSpent: 1800,
+ humanTimeEstimate: '1h',
+ humanTotalTimeSpent: '30min',
+ },
+ };
+ store.state.activeId = '1';
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it.each([[true], [false]])(
+ 'renders IssuableTimeTracker with correct spent and estimated time (timeTrackingLimitToHours=%s)',
+ (timeTrackingLimitToHours) => {
+ createComponent({ provide: { timeTrackingLimitToHours } });
+
+ expect(wrapper.find(IssuableTimeTracker).props()).toEqual({
+ timeEstimate: 3600,
+ timeSpent: 1800,
+ humanTimeEstimate: '1h',
+ humanTimeSpent: '30min',
+ limitToHours: timeTrackingLimitToHours,
+ showCollapsed: false,
+ });
+ },
+ );
+});
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 500240d00fc..a8a6423b47c 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -125,7 +125,7 @@ export const labels = [
export const rawIssue = {
title: 'Issue 1',
id: 'gid://gitlab/Issue/436',
- iid: 27,
+ iid: '27',
dueDate: null,
timeEstimate: 0,
weight: null,
@@ -152,7 +152,7 @@ export const rawIssue = {
export const mockIssue = {
id: 'gid://gitlab/Issue/436',
- iid: 27,
+ iid: '27',
title: 'Issue 1',
dueDate: null,
timeEstimate: 0,
@@ -398,3 +398,93 @@ export const mockActiveGroupProjects = [
{ ...mockGroupProject1, archived: false },
{ ...mockGroupProject2, archived: false },
];
+
+export const mockIssueGroupPath = 'gitlab-org';
+export const mockIssueProjectPath = `${mockIssueGroupPath}/gitlab-test`;
+
+export const mockBlockingIssue1 = {
+ id: 'gid://gitlab/Issue/525',
+ iid: '6',
+ title: 'blocking issue title 1',
+ reference: 'gitlab-org/my-project-1#6',
+ webUrl: 'http://gdk.test:3000/gitlab-org/my-project-1/-/issues/6',
+ __typename: 'Issue',
+};
+
+export const mockBlockingIssue2 = {
+ id: 'gid://gitlab/Issue/524',
+ iid: '5',
+ title:
+ 'blocking issue title 2 + blocking issue title 2 + blocking issue title 2 + blocking issue title 2',
+ reference: 'gitlab-org/my-project-1#5',
+ webUrl: 'http://gdk.test:3000/gitlab-org/my-project-1/-/issues/5',
+ __typename: 'Issue',
+};
+
+export const mockBlockingIssue3 = {
+ id: 'gid://gitlab/Issue/523',
+ iid: '4',
+ title: 'blocking issue title 3',
+ reference: 'gitlab-org/my-project-1#4',
+ webUrl: 'http://gdk.test:3000/gitlab-org/my-project-1/-/issues/4',
+ __typename: 'Issue',
+};
+
+export const mockBlockingIssue4 = {
+ id: 'gid://gitlab/Issue/522',
+ iid: '3',
+ title: 'blocking issue title 4',
+ reference: 'gitlab-org/my-project-1#3',
+ webUrl: 'http://gdk.test:3000/gitlab-org/my-project-1/-/issues/3',
+ __typename: 'Issue',
+};
+
+export const mockBlockingIssuablesResponse1 = {
+ data: {
+ issuable: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/527',
+ blockingIssuables: {
+ __typename: 'IssueConnection',
+ nodes: [mockBlockingIssue1],
+ },
+ },
+ },
+};
+
+export const mockBlockingIssuablesResponse2 = {
+ data: {
+ issuable: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/527',
+ blockingIssuables: {
+ __typename: 'IssueConnection',
+ nodes: [mockBlockingIssue2],
+ },
+ },
+ },
+};
+
+export const mockBlockingIssuablesResponse3 = {
+ data: {
+ issuable: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/527',
+ blockingIssuables: {
+ __typename: 'IssueConnection',
+ nodes: [mockBlockingIssue1, mockBlockingIssue2, mockBlockingIssue3, mockBlockingIssue4],
+ },
+ },
+ },
+};
+
+export const mockBlockedIssue1 = {
+ id: '527',
+ blockedByCount: 1,
+};
+
+export const mockBlockedIssue2 = {
+ id: '527',
+ blockedByCount: 4,
+ webUrl: 'http://gdk.test:3000/gitlab-org/my-project-1/-/issues/0',
+};
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 69d2c8977fb..c0f91d3c629 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,3 +1,4 @@
+import * as Sentry from '@sentry/browser';
import testAction from 'helpers/vuex_action_helper';
import {
fullBoardId,
@@ -1378,6 +1379,51 @@ describe('toggleBoardItem', () => {
});
});
+describe('setError', () => {
+ it('should commit mutation SET_ERROR', () => {
+ testAction({
+ action: actions.setError,
+ payload: { message: 'mayday' },
+ expectedMutations: [
+ {
+ payload: 'mayday',
+ type: types.SET_ERROR,
+ },
+ ],
+ });
+ });
+
+ it('should capture error using Sentry when captureError is true', () => {
+ jest.spyOn(Sentry, 'captureException');
+
+ const mockError = new Error();
+ actions.setError(
+ { commit: () => {} },
+ {
+ message: 'mayday',
+ error: mockError,
+ captureError: true,
+ },
+ );
+
+ expect(Sentry.captureException).toHaveBeenNthCalledWith(1, mockError);
+ });
+});
+
+describe('unsetError', () => {
+ it('should commit mutation SET_ERROR with undefined as payload', () => {
+ testAction({
+ action: actions.unsetError,
+ expectedMutations: [
+ {
+ payload: undefined,
+ type: types.SET_ERROR,
+ },
+ ],
+ });
+ });
+});
+
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index 33897cc0250..d781f403b93 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -666,4 +666,14 @@ describe('Board Store Mutations', () => {
expect(state.selectedBoardItems).toEqual([]);
});
});
+
+ describe('SET_ERROR', () => {
+ it('Should set error state', () => {
+ state.error = undefined;
+
+ mutations[types.SET_ERROR](state, 'mayday');
+
+ expect(state.error).toBe('mayday');
+ });
+ });
});