summaryrefslogtreecommitdiff
path: root/spec/frontend/boards/components
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/boards/components')
-rw-r--r--spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap30
-rw-r--r--spec/frontend/boards/components/board_add_new_column_form_spec.js32
-rw-r--r--spec/frontend/boards/components/board_add_new_column_spec.js10
-rw-r--r--spec/frontend/boards/components/board_blocked_icon_spec.js226
-rw-r--r--spec/frontend/boards/components/board_content_sidebar_spec.js140
-rw-r--r--spec/frontend/boards/components/board_content_spec.js13
-rw-r--r--spec/frontend/boards/components/board_form_spec.js2
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js6
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js43
-rw-r--r--spec/frontend/boards/components/filtered_search_spec.js65
-rw-r--r--spec/frontend/boards/components/issue_time_estimate_spec.js6
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js14
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js28
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js58
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js (renamed from spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js)36
15 files changed, 569 insertions, 140 deletions
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_add_new_column_form_spec.js b/spec/frontend/boards/components/board_add_new_column_form_spec.js
index 3702f55f17b..3b26ca57d6f 100644
--- a/spec/frontend/boards/components/board_add_new_column_form_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_form_spec.js
@@ -1,6 +1,6 @@
-import { GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
+import { GlDropdown, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
@@ -25,7 +25,7 @@ describe('Board card layout', () => {
const mountComponent = ({
loading = false,
- formDescription = '',
+ noneSelected = '',
searchLabel = '',
searchPlaceholder = '',
selectedId,
@@ -34,12 +34,9 @@ describe('Board card layout', () => {
} = {}) => {
wrapper = extendedWrapper(
shallowMount(BoardAddNewColumnForm, {
- stubs: {
- GlFormGroup: true,
- },
propsData: {
loading,
- formDescription,
+ noneSelected,
searchLabel,
searchPlaceholder,
selectedId,
@@ -51,13 +48,15 @@ describe('Board card layout', () => {
...actions,
},
}),
+ stubs: {
+ GlDropdown,
+ },
}),
);
};
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
@@ -65,10 +64,13 @@ describe('Board card layout', () => {
const findSearchLabel = () => wrapper.find(GlFormGroup);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton');
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
it('shows form title & search input', () => {
mountComponent();
+ findDropdown().vm.$emit('show');
+
expect(formTitle()).toEqual(BoardAddNewColumnForm.i18n.newList);
expect(findSearchInput().exists()).toBe(true);
});
@@ -86,16 +88,6 @@ describe('Board card layout', () => {
expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
});
- it('sets placeholder and description from props', () => {
- const props = {
- formDescription: 'Some description of a list',
- };
-
- mountComponent(props);
-
- expect(wrapper.html()).toHaveText(props.formDescription);
- });
-
describe('items', () => {
const mountWithItems = (loading) =>
mountComponent({
@@ -151,13 +143,11 @@ describe('Board card layout', () => {
expect(submitButton().props('disabled')).toBe(true);
});
- it('emits add-list event on click', async () => {
+ it('emits add-list event on click', () => {
mountComponent({
selectedId: mockLabelList.label.id,
});
- await nextTick();
-
submitButton().vm.$emit('click');
expect(wrapper.emitted('add-list')).toEqual([[]]);
diff --git a/spec/frontend/boards/components/board_add_new_column_spec.js b/spec/frontend/boards/components/board_add_new_column_spec.js
index 60584eaf6cf..61f210f566b 100644
--- a/spec/frontend/boards/components/board_add_new_column_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_spec.js
@@ -1,3 +1,4 @@
+import { GlFormRadioGroup } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
@@ -12,6 +13,10 @@ Vue.use(Vuex);
describe('Board card layout', () => {
let wrapper;
+ const selectLabel = (id) => {
+ wrapper.findComponent(GlFormRadioGroup).vm.$emit('change', id);
+ };
+
const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
return new Vuex.Store({
state: {
@@ -57,6 +62,11 @@ describe('Board card layout', () => {
},
}),
);
+
+ // trigger change event
+ if (selectedId) {
+ selectLabel(selectedId);
+ }
};
afterEach(() => {
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..7f949739891
--- /dev/null
+++ b/spec/frontend/boards/components/board_content_sidebar_spec.js
@@ -0,0 +1,140 @@
+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 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 BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.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, epic: null } },
+ activeId: mockIssue.id,
+ issuableType: 'issue',
+ },
+ getters: {
+ activeBoardItem: () => {
+ return { ...mockIssue, epic: null };
+ },
+ groupPathForActiveIssue: () => mockIssueGroupPath,
+ projectPathForActiveIssue: () => mockIssueProjectPath,
+ isSidebarOpen: () => true,
+ ...mockGetters,
+ },
+ actions: mockActions,
+ });
+ };
+
+ const createComponent = () => {
+ /*
+ Dynamically imported components (in our case ee imports)
+ aren't stubbed automatically in VTU v1:
+ https://github.com/vuejs/vue-test-utils/issues/1279.
+
+ This requires us to additionally mock apollo or vuex stores.
+ */
+ wrapper = shallowMount(BoardContentSidebar, {
+ provide: {
+ canUpdate: true,
+ rootPath: '/',
+ groupId: 1,
+ },
+ store,
+ stubs: {
+ GlDrawer: stubComponent(GlDrawer, {
+ template: '<div><slot name="header"></slot><slot></slot></div>',
+ }),
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ participants: {
+ loading: false,
+ },
+ currentIteration: {
+ loading: false,
+ },
+ iterations: {
+ 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 BoardSidebarTitle', () => {
+ expect(wrapper.find(BoardSidebarTitle).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, epic: null },
+ sidebarType: ISSUABLE,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index 159b67ccc67..8c1a7bd3947 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -33,7 +33,12 @@ describe('BoardContent', () => {
});
};
- const createComponent = ({ state, props = {}, graphqlBoardListsEnabled = false } = {}) => {
+ const createComponent = ({
+ state,
+ props = {},
+ graphqlBoardListsEnabled = false,
+ canAdminList = true,
+ } = {}) => {
const store = createStore({
...defaultState,
...state,
@@ -42,11 +47,11 @@ describe('BoardContent', () => {
localVue,
propsData: {
lists: mockListsWithModel,
- canAdminList: true,
disabled: false,
...props,
},
provide: {
+ canAdminList,
glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled },
},
store,
@@ -82,7 +87,7 @@ describe('BoardContent', () => {
describe('can admin list', () => {
beforeEach(() => {
- createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: true } });
+ createComponent({ graphqlBoardListsEnabled: true, canAdminList: true });
});
it('renders draggable component', () => {
@@ -92,7 +97,7 @@ describe('BoardContent', () => {
describe('can not admin list', () => {
beforeEach(() => {
- createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: false } });
+ createComponent({ graphqlBoardListsEnabled: true, canAdminList: false });
});
it('does not render draggable component', () => {
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 32499bd5480..24fcdd528d5 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -226,7 +226,7 @@ describe('BoardForm', () => {
it('passes correct primary action text and variant', () => {
expect(findModalActionPrimary().text).toBe('Save changes');
- expect(findModalActionPrimary().attributes[0].variant).toBe('info');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('confirm');
});
it('does not render delete confirmation message', () => {
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index 737a18294bc..e6405bbcff3 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -86,7 +86,7 @@ describe('Issue boards new issue form', () => {
describe('submit success', () => {
it('creates new issue', async () => {
- wrapper.setData({ title: 'submit issue' });
+ wrapper.setData({ title: 'create issue' });
await vm.$nextTick();
await submitIssue();
@@ -95,7 +95,7 @@ describe('Issue boards new issue form', () => {
it('enables button after submit', async () => {
jest.spyOn(wrapper.vm, 'submit').mockImplementation();
- wrapper.setData({ title: 'submit issue' });
+ wrapper.setData({ title: 'create issue' });
await vm.$nextTick();
await submitIssue();
@@ -103,7 +103,7 @@ describe('Issue boards new issue form', () => {
});
it('clears title after submit', async () => {
- wrapper.setData({ title: 'submit issue' });
+ wrapper.setData({ title: 'create issue' });
await vm.$nextTick();
await submitIssue();
diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js
index 52b4d71f7b9..464331b6e30 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -4,6 +4,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import { inactiveId, LIST } from '~/boards/constants';
import { createStore } from '~/boards/stores';
@@ -22,11 +23,18 @@ describe('BoardSettingsSidebar', () => {
const labelColor = '#FFFF';
const listId = 1;
- const createComponent = () => {
- wrapper = shallowMount(BoardSettingsSidebar, {
- store,
- localVue,
- });
+ const findRemoveButton = () => wrapper.findByTestId('remove-list');
+
+ const createComponent = ({ canAdminList = false } = {}) => {
+ wrapper = extendedWrapper(
+ shallowMount(BoardSettingsSidebar, {
+ store,
+ localVue,
+ provide: {
+ canAdminList,
+ },
+ }),
+ );
};
const findLabel = () => wrapper.find(GlLabel);
const findDrawer = () => wrapper.find(GlDrawer);
@@ -164,4 +172,29 @@ describe('BoardSettingsSidebar', () => {
expect(findDrawer().exists()).toBe(false);
});
});
+
+ it('does not render "Remove list" when user cannot admin the boards list', () => {
+ createComponent();
+
+ expect(findRemoveButton().exists()).toBe(false);
+ });
+
+ describe('when user can admin the boards list', () => {
+ beforeEach(() => {
+ store.state.activeId = listId;
+ store.state.sidebarType = LIST;
+
+ boardsStore.addList({
+ id: listId,
+ label: { title: labelTitle, color: labelColor },
+ list_type: 'label',
+ });
+
+ createComponent({ canAdminList: true });
+ });
+
+ it('renders "Remove list" button', () => {
+ expect(findRemoveButton().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/boards/components/filtered_search_spec.js b/spec/frontend/boards/components/filtered_search_spec.js
deleted file mode 100644
index 7f238aa671f..00000000000
--- a/spec/frontend/boards/components/filtered_search_spec.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import Vuex from 'vuex';
-import FilteredSearch from '~/boards/components/filtered_search.vue';
-import { createStore } from '~/boards/stores';
-import * as commonUtils from '~/lib/utils/common_utils';
-import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('FilteredSearch', () => {
- let wrapper;
- let store;
-
- const createComponent = () => {
- wrapper = shallowMount(FilteredSearch, {
- localVue,
- propsData: { search: '' },
- store,
- attachTo: document.body,
- });
- };
-
- beforeEach(() => {
- // this needed for actions call for performSearch
- window.gon = { features: {} };
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('default', () => {
- beforeEach(() => {
- store = createStore();
-
- jest.spyOn(store, 'dispatch');
-
- createComponent();
- });
-
- it('finds FilteredSearch', () => {
- expect(wrapper.find(FilteredSearchBarRoot).exists()).toBe(true);
- });
-
- describe('when onFilter is emitted', () => {
- it('calls performSearch', () => {
- wrapper.find(FilteredSearchBarRoot).vm.$emit('onFilter', [{ value: { data: '' } }]);
-
- expect(store.dispatch).toHaveBeenCalledWith('performSearch');
- });
-
- it('calls historyPushState', () => {
- commonUtils.historyPushState = jest.fn();
- wrapper
- .find(FilteredSearchBarRoot)
- .vm.$emit('onFilter', [{ value: { data: 'searchQuery' } }]);
-
- expect(commonUtils.historyPushState).toHaveBeenCalledWith(
- 'http://test.host/?search=searchQuery',
- );
- });
- });
- });
-});
diff --git a/spec/frontend/boards/components/issue_time_estimate_spec.js b/spec/frontend/boards/components/issue_time_estimate_spec.js
index 2e253d24125..635964b6b4a 100644
--- a/spec/frontend/boards/components/issue_time_estimate_spec.js
+++ b/spec/frontend/boards/components/issue_time_estimate_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { config as vueConfig } from 'vue';
+import Vue from 'vue';
import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue';
describe('Issue Time Estimate component', () => {
@@ -34,10 +34,10 @@ describe('Issue Time Estimate component', () => {
try {
// This will raise props validating warning by Vue, silencing it
- vueConfig.silent = true;
+ Vue.config.silent = true;
await wrapper.setProps({ estimate: 'Foo <script>alert("XSS")</script>' });
} finally {
- vueConfig.silent = false;
+ Vue.config.silent = false;
}
expect(alertSpy).not.toHaveBeenCalled();
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
index 98ac211238c..153d0640b23 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
@@ -64,7 +64,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper();
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => TEST_LABELS);
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => TEST_LABELS);
findLabelsSelect().vm.$emit('updateSelectedLabels', TEST_LABELS_PAYLOAD);
store.state.boardItems[TEST_ISSUE.id].labels = TEST_LABELS;
await wrapper.vm.$nextTick();
@@ -76,7 +76,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
});
it('commits change to the server', () => {
- expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
addLabelIds: TEST_LABELS.map((label) => label.id),
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
removeLabelIds: [],
@@ -94,13 +94,13 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper({ labels: TEST_LABELS });
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => expectedLabels);
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => expectedLabels);
findLabelsSelect().vm.$emit('updateSelectedLabels', testLabelsPayload);
await wrapper.vm.$nextTick();
});
it('commits change to the server', () => {
- expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
addLabelIds: [5, 7],
removeLabelIds: [6],
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
@@ -114,13 +114,13 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper({ labels: [testLabel] });
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {});
});
it('commits change to the server', () => {
wrapper.find(GlLabel).vm.$emit('close', testLabel);
- expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
removeLabelIds: [getIdFromGraphQLId(testLabel.id)],
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
});
@@ -131,7 +131,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper({ labels: TEST_LABELS });
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {
throw new Error(['failed mutation']);
});
findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]);
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
index cfd7f32b2cc..7976e73ff2f 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
@@ -1,5 +1,6 @@
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
import { createStore } from '~/boards/stores';
@@ -9,8 +10,7 @@ import { mockActiveIssue } from '../../mock_data';
jest.mock('~/flash.js');
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () => {
let wrapper;
@@ -20,14 +20,16 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
const findToggle = () => wrapper.find(GlToggle);
const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const createComponent = (activeIssue = { ...mockActiveIssue }) => {
+ const createComponent = (activeBoardItem = { ...mockActiveIssue }) => {
store = createStore();
- store.state.boardItems = { [activeIssue.id]: activeIssue };
- store.state.activeId = activeIssue.id;
+ store.state.boardItems = { [activeBoardItem.id]: activeBoardItem };
+ store.state.activeId = activeBoardItem.id;
wrapper = mount(BoardSidebarSubscription, {
- localVue,
store,
+ provide: {
+ emailsDisabled: false,
+ },
});
};
@@ -90,9 +92,9 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
describe('Board sidebar subscription component `behavior`', () => {
const mockSetActiveIssueSubscribed = (subscribedState) => {
- jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => {
- store.commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: mockActiveIssue.id,
+ jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => {
+ store.commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: mockActiveIssue.id,
prop: 'subscribed',
value: subscribedState,
});
@@ -110,7 +112,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
await wrapper.vm.$nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
- expect(wrapper.vm.setActiveIssueSubscribed).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveItemSubscribed).toHaveBeenCalledWith({
subscribed: true,
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
});
@@ -134,7 +136,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
await wrapper.vm.$nextTick();
- expect(wrapper.vm.setActiveIssueSubscribed).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveItemSubscribed).toHaveBeenCalledWith({
subscribed: false,
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
});
@@ -148,7 +150,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
it('flashes an error message when setting the subscribed state fails', async () => {
createComponent();
- jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => {
+ jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => {
throw new Error();
});
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/components/sidebar/board_sidebar_issue_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
index 723d0345f76..c8ccd4c88a5 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
@@ -1,11 +1,11 @@
import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue';
+import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
-const TEST_TITLE = 'New issue title';
+const TEST_TITLE = 'New item title';
const TEST_ISSUE_A = {
id: 'gid://gitlab/Issue/1',
iid: 8,
@@ -21,7 +21,7 @@ const TEST_ISSUE_B = {
jest.mock('~/flash');
-describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
+describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
let wrapper;
let store;
@@ -32,12 +32,12 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
wrapper = null;
});
- const createWrapper = (issue = TEST_ISSUE_A) => {
+ const createWrapper = (item = TEST_ISSUE_A) => {
store = createStore();
- store.state.boardItems = { [issue.id]: { ...issue } };
- store.dispatch('setActiveId', { id: issue.id });
+ store.state.boardItems = { [item.id]: { ...item } };
+ store.dispatch('setActiveId', { id: item.id });
- wrapper = shallowMount(BoardSidebarIssueTitle, {
+ wrapper = shallowMount(BoardSidebarTitle, {
store,
provide: {
canUpdate: true,
@@ -53,7 +53,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
const findFormInput = () => wrapper.find(GlFormInput);
const findEditableItem = () => wrapper.find(BoardEditableItem);
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
- const findTitle = () => wrapper.find('[data-testid="issue-title"]');
+ const findTitle = () => wrapper.find('[data-testid="item-title"]');
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
it('renders title and reference', () => {
@@ -73,7 +73,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
beforeEach(async () => {
createWrapper();
- jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {
+ jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {
store.state.boardItems[TEST_ISSUE_A.id].title = TEST_TITLE;
});
findFormInput().vm.$emit('input', TEST_TITLE);
@@ -87,7 +87,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
});
it('commits change to the server', () => {
- expect(wrapper.vm.setActiveIssueTitle).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveItemTitle).toHaveBeenCalledWith({
title: TEST_TITLE,
projectPath: 'h/b',
});
@@ -98,14 +98,14 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
beforeEach(async () => {
createWrapper();
- jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {});
findFormInput().vm.$emit('input', '');
findForm().vm.$emit('submit', { preventDefault: () => {} });
await wrapper.vm.$nextTick();
});
it('commits change to the server', () => {
- expect(wrapper.vm.setActiveIssueTitle).not.toHaveBeenCalled();
+ expect(wrapper.vm.setActiveItemTitle).not.toHaveBeenCalled();
});
});
@@ -122,7 +122,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
it('does not collapses sidebar and shows alert', () => {
expect(findCollapsed().isVisible()).toBe(false);
expect(findAlert().exists()).toBe(true);
- expect(localStorage.getItem(`${TEST_ISSUE_A.id}/issue-title-pending-changes`)).toBe(
+ expect(localStorage.getItem(`${TEST_ISSUE_A.id}/item-title-pending-changes`)).toBe(
TEST_TITLE,
);
});
@@ -130,7 +130,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
describe('when accessing the form with pending changes', () => {
beforeAll(() => {
- localStorage.setItem(`${TEST_ISSUE_A.id}/issue-title-pending-changes`, TEST_TITLE);
+ localStorage.setItem(`${TEST_ISSUE_A.id}/item-title-pending-changes`, TEST_TITLE);
createWrapper();
});
@@ -146,7 +146,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
beforeEach(async () => {
createWrapper(TEST_ISSUE_B);
- jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {
+ jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {
store.state.boardItems[TEST_ISSUE_B.id].title = TEST_TITLE;
});
findFormInput().vm.$emit('input', TEST_TITLE);
@@ -155,7 +155,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
});
it('collapses sidebar and render former title', () => {
- expect(wrapper.vm.setActiveIssueTitle).not.toHaveBeenCalled();
+ expect(wrapper.vm.setActiveItemTitle).not.toHaveBeenCalled();
expect(findCollapsed().isVisible()).toBe(true);
expect(findTitle().text()).toBe(TEST_ISSUE_B.title);
});
@@ -165,7 +165,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
beforeEach(async () => {
createWrapper(TEST_ISSUE_B);
- jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {
+ jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {
throw new Error(['failed mutation']);
});
findFormInput().vm.$emit('input', 'Invalid title');
@@ -173,7 +173,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
await wrapper.vm.$nextTick();
});
- it('collapses sidebar and renders former issue title', () => {
+ it('collapses sidebar and renders former item title', () => {
expect(findCollapsed().isVisible()).toBe(true);
expect(findTitle().text()).toContain(TEST_ISSUE_B.title);
expect(createFlash).toHaveBeenCalled();