Commit b3b25cce authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'ss/mv-board-sidebar-to-ce' into 'master'

Move board settings sidebar to ce

See merge request gitlab-org/gitlab!38001
parents bb716181 37dcb167
<script> <script>
import { import { GlDrawer, GlLabel, GlAvatarLink, GlAvatarLabeled, GlLink } from '@gitlab/ui';
GlDrawer,
GlLabel,
GlButton,
GlFormInput,
GlAvatarLink,
GlAvatarLabeled,
GlLink,
} from '@gitlab/ui';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { __, n__ } from '~/locale'; import { __ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import boardsStore from '~/boards/stores/boards_store';
import boardsStoreEE from '../stores/boards_store_ee';
import eventHub from '~/sidebar/event_hub'; import eventHub from '~/sidebar/event_hub';
import flash from '~/flash';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import { inactiveId } from '~/boards/constants'; import { inactiveId } from '~/boards/constants';
...@@ -27,29 +17,14 @@ export default { ...@@ -27,29 +17,14 @@ export default {
labelListText: __('Label'), labelListText: __('Label'),
labelMilestoneText: __('Milestone'), labelMilestoneText: __('Milestone'),
labelAssigneeText: __('Assignee'), labelAssigneeText: __('Assignee'),
editLinkText: __('Edit'),
noneText: __('None'),
wipLimitText: __('Work in progress Limit'),
removeLimitText: __('Remove limit'),
inputPlaceholderText: __('Enter number of issues'),
components: { components: {
GlDrawer, GlDrawer,
GlLabel, GlLabel,
GlButton,
GlFormInput,
GlAvatarLink, GlAvatarLink,
GlAvatarLabeled, GlAvatarLabeled,
GlLink, GlLink,
}, BoardSettingsSidebarWipLimit: () =>
directives: { import('ee_component/boards/components/board_settings_wip_limit.vue'),
autofocusonshow,
},
data() {
return {
edit: false,
currentWipLimit: null,
updating: false,
};
}, },
computed: { computed: {
...mapState(['activeId']), ...mapState(['activeId']),
...@@ -58,7 +33,7 @@ export default { ...@@ -58,7 +33,7 @@ export default {
Warning: Though a computed property it is not reactive because we are Warning: Though a computed property it is not reactive because we are
referencing a List Model class. Reactivity only applies to plain JS objects referencing a List Model class. Reactivity only applies to plain JS objects
*/ */
return boardsStoreEE.store.state.lists.find(({ id }) => id === this.activeId); return boardsStore.state.lists.find(({ id }) => id === this.activeId);
}, },
isSidebarOpen() { isSidebarOpen() {
return this.activeId !== inactiveId; return this.activeId !== inactiveId;
...@@ -72,15 +47,6 @@ export default { ...@@ -72,15 +47,6 @@ export default {
activeListAssignee() { activeListAssignee() {
return this.activeList.assignee; return this.activeList.assignee;
}, },
wipLimitTypeText() {
return n__('%d issue', '%d issues', this.activeList.maxIssueCount);
},
wipLimitIsSet() {
return this.activeList.maxIssueCount !== 0;
},
activeListWipLimit() {
return this.activeList.maxIssueCount === 0 ? this.$options.noneText : this.wipLimitTypeText;
},
boardListType() { boardListType() {
return this.activeList.type || null; return this.activeList.type || null;
}, },
...@@ -108,66 +74,12 @@ export default { ...@@ -108,66 +74,12 @@ export default {
eventHub.$off('sidebar.closeAll', this.closeSidebar); eventHub.$off('sidebar.closeAll', this.closeSidebar);
}, },
methods: { methods: {
...mapActions(['setActiveId', 'updateListWipLimit']), ...mapActions(['setActiveId']),
closeSidebar() { closeSidebar() {
this.edit = false;
this.setActiveId(inactiveId); this.setActiveId(inactiveId);
}, },
showInput() {
this.edit = true;
this.currentWipLimit =
this.activeList.maxIssueCount > 0 ? this.activeList.maxIssueCount : null;
},
resetStateAfterUpdate() {
this.edit = false;
this.updating = false;
this.currentWipLimit = null;
},
offFocus() {
if (this.currentWipLimit !== this.activeList.maxIssueCount && this.currentWipLimit !== null) {
this.updating = true;
// need to reassign bc were clearing the ref in resetStateAfterUpdate.
const wipLimit = this.currentWipLimit;
const id = this.activeId;
this.updateListWipLimit({ maxIssueCount: this.currentWipLimit, id })
.then(() => {
boardsStoreEE.setMaxIssueCountOnList(id, wipLimit);
this.resetStateAfterUpdate();
})
.catch(() => {
this.resetStateAfterUpdate();
this.setActiveId(inactiveId);
flash(__('Something went wrong while updating your list settings'));
});
} else {
this.edit = false;
}
},
clearWipLimit() {
this.updateListWipLimit({ maxIssueCount: 0, id: this.activeId })
.then(() => {
boardsStoreEE.setMaxIssueCountOnList(this.activeId, 0);
this.resetStateAfterUpdate();
})
.catch(() => {
this.resetStateAfterUpdate();
this.setActiveId(inactiveId);
flash(__('Something went wrong while updating your list settings'));
});
},
handleWipLimitChange(wipLimit) {
if (wipLimit === '') {
this.currentWipLimit = null;
} else {
this.currentWipLimit = Number(wipLimit);
}
},
onEnter() {
this.offFocus();
},
showScopedLabels(label) { showScopedLabels(label) {
return boardsStoreEE.store.scopedLabels.enabled && isScopedLabel(label); return boardsStore.scopedLabels.enabled && isScopedLabel(label);
}, },
}, },
}; };
...@@ -207,43 +119,8 @@ export default { ...@@ -207,43 +119,8 @@ export default {
</gl-link> </gl-link>
</template> </template>
</div> </div>
<div class="d-flex justify-content-between flex-column">
<div class="d-flex justify-content-between align-items-center mb-2"> <board-settings-sidebar-wip-limit :max-issue-count="activeList.maxIssueCount" />
<label class="m-0">{{ $options.wipLimitText }}</label>
<gl-button
class="js-edit-button h-100 border-0 text-dark"
variant="link"
@click="showInput"
>{{ $options.editLinkText }}</gl-button
>
</div>
<gl-form-input
v-if="edit"
v-autofocusonshow
:value="currentWipLimit"
:disabled="updating"
:placeholder="$options.inputPlaceholderText"
trim
number
type="number"
min="0"
@input="handleWipLimitChange"
@keydown.enter.native="onEnter"
@blur="offFocus"
/>
<div v-else class="d-flex align-items-center">
<p class="js-wip-limit bold m-0 text-secondary">{{ activeListWipLimit }}</p>
<template v-if="wipLimitIsSet">
<span class="m-1">-</span>
<gl-button
class="js-remove-limit h-100 border-0 text-secondary"
variant="link"
@click="clearWipLimit"
>{{ $options.removeLimitText }}</gl-button
>
</template>
</div>
</div>
</template> </template>
</gl-drawer> </gl-drawer>
</template> </template>
...@@ -83,8 +83,7 @@ export default () => { ...@@ -83,8 +83,7 @@ export default () => {
Board: () => import('ee_else_ce/boards/components/board_column.vue'), Board: () => import('ee_else_ce/boards/components/board_column.vue'),
BoardSidebar, BoardSidebar,
BoardAddIssuesModal, BoardAddIssuesModal,
BoardSettingsSidebar: () => BoardSettingsSidebar: () => import('~/boards/components/board_settings_sidebar.vue'),
import('ee_component/boards/components/board_settings_sidebar.vue'),
}, },
store, store,
apolloProvider, apolloProvider,
......
...@@ -10,6 +10,10 @@ export default { ...@@ -10,6 +10,10 @@ export default {
commit(types.SET_ENDPOINTS, endpoints); commit(types.SET_ENDPOINTS, endpoints);
}, },
setActiveId({ commit }, id) {
commit(types.SET_ACTIVE_ID, id);
},
fetchLists: () => { fetchLists: () => {
notImplemented(); notImplemented();
}, },
......
...@@ -19,3 +19,4 @@ export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS'; ...@@ -19,3 +19,4 @@ export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR'; export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE'; export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE'; export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
...@@ -10,6 +10,10 @@ export default { ...@@ -10,6 +10,10 @@ export default {
state.endpoints = endpoints; state.endpoints = endpoints;
}, },
[mutationTypes.SET_ACTIVE_ID](state, id) {
state.activeId = id;
},
[mutationTypes.REQUEST_ADD_LIST]: () => { [mutationTypes.REQUEST_ADD_LIST]: () => {
notImplemented(); notImplemented();
}, },
......
<script>
import { mapActions, mapState } from 'vuex';
import { GlButton, GlFormInput } from '@gitlab/ui';
import flash from '~/flash';
import { __, n__ } from '~/locale';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { inactiveId } from '~/boards/constants';
export default {
i18n: {
wipLimitText: __('Work in progress Limit'),
editLinkText: __('Edit'),
noneText: __('None'),
inputPlaceholderText: __('Enter number of issues'),
removeLimitText: __('Remove limit'),
},
components: {
GlButton,
GlFormInput,
},
directives: {
autofocusonshow,
},
props: {
maxIssueCount: {
type: Number,
required: true,
},
},
data() {
return {
currentWipLimit: null,
edit: false,
updating: false,
};
},
computed: {
...mapState(['activeId']),
wipLimitTypeText() {
return n__('%d issue', '%d issues', this.maxIssueCount);
},
wipLimitIsSet() {
return this.maxIssueCount !== 0;
},
activeListWipLimit() {
return this.wipLimitIsSet ? this.wipLimitTypeText : this.$options.i18n.noneText;
},
},
methods: {
...mapActions(['setActiveId', 'updateListWipLimit']),
showInput() {
this.edit = true;
this.currentWipLimit = this.maxIssueCount > 0 ? this.maxIssueCount : null;
},
handleWipLimitChange(wipLimit) {
if (wipLimit === '') {
this.currentWipLimit = null;
} else {
this.currentWipLimit = Number(wipLimit);
}
},
onEnter() {
this.offFocus();
},
resetStateAfterUpdate() {
this.edit = false;
this.updating = false;
this.currentWipLimit = null;
},
offFocus() {
if (this.currentWipLimit !== this.maxIssueCount && this.currentWipLimit !== null) {
this.updating = true;
// need to reassign bc were clearing the ref in resetStateAfterUpdate.
const wipLimit = this.currentWipLimit;
const id = this.activeId;
this.updateListWipLimit({ maxIssueCount: this.currentWipLimit, id })
.then(() => {
boardsStoreEE.setMaxIssueCountOnList(id, wipLimit);
})
.catch(() => {
this.setActiveId(0);
flash(__('Something went wrong while updating your list settings'));
})
.finally(() => {
this.resetStateAfterUpdate();
});
} else {
this.edit = false;
}
},
clearWipLimit() {
this.updateListWipLimit({ maxIssueCount: 0, id: this.activeId })
.then(() => {
boardsStoreEE.setMaxIssueCountOnList(this.activeId, inactiveId);
})
.catch(() => {
this.setActiveId(inactiveId);
flash(__('Something went wrong while updating your list settings'));
})
.finally(() => {
this.resetStateAfterUpdate();
});
},
},
};
</script>
<template>
<div class="gl-display-flex gl-justify-content-space-between gl-flex-direction-column">
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-mb-2">
<label class="m-0">{{ $options.i18n.wipLimitText }}</label>
<gl-button
class="js-edit-button gl-h-full gl-border-0 text-dark"
variant="link"
@click="showInput"
>{{ $options.i18n.editLinkText }}</gl-button
>
</div>
<gl-form-input
v-if="edit"
v-autofocusonshow
:value="currentWipLimit"
:disabled="updating"
:placeholder="$options.i18n.inputPlaceholderText"
trim
number
type="number"
min="0"
@input="handleWipLimitChange"
@keydown.enter.native="onEnter"
@blur="offFocus"
/>
<div v-else class="gl-display-flex gl-align-items-center">
<p class="js-wip-limit bold gl-m-0 text-secondary">{{ activeListWipLimit }}</p>
<template v-if="wipLimitIsSet">
<span class="m-1">-</span>
<gl-button
class="js-remove-limit gl-h-full gl-border-0 text-secondary"
variant="link"
@click="clearWipLimit"
>{{ $options.i18n.removeLimitText }}</gl-button
>
</template>
</div>
</div>
</template>
...@@ -67,9 +67,6 @@ export default { ...@@ -67,9 +67,6 @@ export default {
commit(types.SET_SHOW_LABELS, val); commit(types.SET_SHOW_LABELS, val);
}, },
setActiveId({ commit }, listId) {
commit(types.SET_ACTIVE_LIST_ID, listId);
},
updateListWipLimit({ state }, { maxIssueCount }) { updateListWipLimit({ state }, { maxIssueCount }) {
const { activeId } = state; const { activeId } = state;
......
...@@ -15,5 +15,4 @@ export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES'; ...@@ -15,5 +15,4 @@ export const TOGGLE_EPICS_SWIMLANES = 'TOGGLE_EPICS_SWIMLANES';
export const RECEIVE_SWIMLANES_SUCCESS = 'RECEIVE_SWIMLANES_SUCCESS'; export const RECEIVE_SWIMLANES_SUCCESS = 'RECEIVE_SWIMLANES_SUCCESS';
export const RECEIVE_SWIMLANES_FAILURE = 'RECEIVE_SWIMLANES_FAILURE'; export const RECEIVE_SWIMLANES_FAILURE = 'RECEIVE_SWIMLANES_FAILURE';
export const RECEIVE_EPICS_SUCCESS = 'RECEIVE_EPICS_SUCCESS'; export const RECEIVE_EPICS_SUCCESS = 'RECEIVE_EPICS_SUCCESS';
export const SET_ACTIVE_LIST_ID = 'SET_ACTIVE_LIST_ID';
export const SET_SHOW_LABELS = 'SET_SHOW_LABELS'; export const SET_SHOW_LABELS = 'SET_SHOW_LABELS';
...@@ -11,9 +11,6 @@ export default { ...@@ -11,9 +11,6 @@ export default {
[mutationTypes.SET_SHOW_LABELS]: (state, val) => { [mutationTypes.SET_SHOW_LABELS]: (state, val) => {
state.isShowingLabels = val; state.isShowingLabels = val;
}, },
[mutationTypes.SET_ACTIVE_LIST_ID]: (state, id) => {
state.activeId = id;
},
[mutationTypes.REQUEST_AVAILABLE_BOARDS]: () => { [mutationTypes.REQUEST_AVAILABLE_BOARDS]: () => {
notImplemented(); notImplemented();
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BoardSettingsSideBar when activeList is present when activeListWipLimit is 0 renders "None" in the block 1`] = `"None"`;
exports[`BoardSettingsSideBar when activeList is present when activeListWipLimit is greater than 0 it renders 1 1`] = `"1 issue"`;
exports[`BoardSettingsSideBar when activeList is present when activeListWipLimit is greater than 0 it renders 11 1`] = `"11 issues"`;
exports[`BoardSettingsSideBar when activeList is present when activeListWipLimit is greater than 0 when list type is "assignee" renders the correct list type text 1`] = `"Assignee"`;
exports[`BoardSettingsSideBar when activeList is present when activeListWipLimit is greater than 0 when list type is "milestone" renders the correct list type text 1`] = `"Milestone"`;
exports[`BoardSettingsSideBar when activeList is present when activeListWipLimit is greater than 0 when list type is "milestone" renders the correct milestone text 1`] = `"Backlog"`;
import '~/boards/models/list';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
import { noop } from 'lodash';
import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue';
import boardsStore from '~/boards/stores/boards_store';
import flash from '~/flash';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/flash');
const localVue = createLocalVue();
localVue.use(Vuex);
describe('BoardSettingsWipLimit', () => {
let wrapper;
let storeActions;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
const currentWipLimit = 1; // Needs to be other than null to trigger requests
let mock;
const addList = (maxIssueCount = 0) => {
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
max_issue_count: maxIssueCount,
list_type: 'label',
});
};
const clickEdit = () => wrapper.find('.js-edit-button').vm.$emit('click');
const findRemoveWipLimit = () => wrapper.find('.js-remove-limit');
const findWipLimit = () => wrapper.find('.js-wip-limit');
const findInput = () => wrapper.find(GlFormInput);
const createComponent = ({
vuexState = { activeId: listId },
actions = {},
localState = {},
props = { maxIssueCount: 0 },
}) => {
storeActions = actions;
const store = new Vuex.Store({
state: vuexState,
actions: storeActions,
});
wrapper = shallowMount(BoardSettingsWipLimit, {
propsData: props,
store,
localVue,
data() {
return localState;
},
});
};
const triggerBlur = type => {
if (type === 'blur') {
findInput().vm.$emit('blur');
}
if (type === 'enter') {
findInput().trigger('keydown.enter');
}
};
beforeEach(() => {
boardsStore.create();
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
jest.restoreAllMocks();
wrapper.destroy();
});
describe('when activeList is present', () => {
describe('when activeListWipLimit is 0', () => {
it('renders "None" in the block', () => {
createComponent({ vuexState: { activeId: listId } });
expect(findWipLimit().text()).toBe('None');
});
});
describe('when activeId is greater than 0', () => {
afterEach(() => {
boardsStore.removeList(listId);
});
it.each`
num | expected
${1} | ${'1 issue'}
${11} | ${'11 issues'}
`('it renders $num', ({ num, expected }) => {
addList(4);
createComponent({ vuexState: { activeId: num }, props: { maxIssueCount: num } });
expect(findWipLimit().text()).toBe(expected);
});
});
});
describe('when clicking edit', () => {
const maxIssueCount = 4;
beforeEach(async () => {
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: noop },
props: { maxIssueCount },
});
clickEdit();
await wrapper.vm.$nextTick();
});
it('renders an input', () => {
expect(findInput().exists()).toBe(true);
});
it('does not render current wipLimit text', () => {
expect(findWipLimit().exists()).toBe(false);
});
it('sets wipLimit to be the value of list.maxIssueCount', () => {
expect(findInput().attributes('value')).toBe(String(maxIssueCount));
});
});
describe('remove limit', () => {
describe('when wipLimit is set', () => {
beforeEach(() => {
addList(4);
const spy = jest.fn().mockResolvedValue({
config: { data: JSON.stringify({ list: { max_issue_count: 0 } }) },
});
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: spy },
props: { maxIssueCount: 4 },
});
});
it('resets wipLimit to 0', async () => {
expect(findWipLimit().text()).toContain(4);
findRemoveWipLimit().vm.$emit('click');
await wrapper.vm.$nextTick();
// WARNING: https://gitlab.com/gitlab-org/gitlab/-/issues/232573
expect(boardsStore.findList('id', listId).maxIssueCount).toBe(0);
});
});
describe('when wipLimit is not set', () => {
beforeEach(() => {
addList();
createComponent({ vuexState: { activeId: listId }, actions: { updateListWipLimit: noop } });
});
it('does not render the remove limit button', () => {
expect(findRemoveWipLimit().exists()).toBe(false);
});
});
});
describe('when edit is true', () => {
beforeEach(() => {
addList(2);
});
afterEach(() => {
flash.mockReset();
boardsStore.removeList(listId, 'label');
});
describe.each`
blurMethod
${'enter'}
${'blur'}
`('$blurMethod', ({ blurMethod }) => {
describe(`when blur is triggered by ${blurMethod}`, () => {
it('calls updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({
config: { data: JSON.stringify({ list: { max_issue_count: '4' } }) },
});
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit },
});
triggerBlur(blurMethod);
await wrapper.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(1);
});
describe('when component wipLimit and List.maxIssueCount are equal', () => {
it('doesnt call updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({});
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: 2 },
props: { maxIssueCount: 2 },
});
triggerBlur(blurMethod);
await wrapper.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(0);
});
});
describe('when currentWipLimit is null', () => {
it('doesnt call updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({});
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: null },
});
triggerBlur(blurMethod);
await wrapper.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(0);
});
});
describe('when response is successful', () => {
const maxIssueCount = 11;
beforeEach(() => {
const spy = jest.fn().mockResolvedValue({});
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: maxIssueCount },
});
triggerBlur(blurMethod);
return waitForPromises();
});
it('sets activeWipLimit to new maxIssueCount value', () => {
/*
* DANGER: bad coupling to the computed prop of the component because the
* computed prop relys on the list from boardStore, for now this is the way around
* stale values from boardsStore being updated, when we move List and BoardsStore to Vuex
* or Graphql we will be able to query the DOM for the new value.
*/
expect(boardsStore.findList('id', 1).maxIssueCount).toBe(maxIssueCount);
});
it('toggles GlFormInput on blur', () => {
expect(findInput().exists()).toBe(false);
expect(findWipLimit().exists()).toBe(true);
expect(wrapper.vm.updating).toBe(false);
});
});
describe('when response fails', () => {
beforeEach(() => {
const spy = jest.fn().mockRejectedValue();
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: spy, setActiveId: noop },
localState: { edit: true, currentWipLimit },
});
triggerBlur(blurMethod);
return waitForPromises();
});
it('calls flash with expected error', () => {
expect(flash).toHaveBeenCalledTimes(1);
});
});
});
});
describe('passing of props to gl-form-input', () => {
beforeEach(() => {
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: noop },
localState: { edit: true },
});
});
it('passes `trim`', () => {
expect(findInput().attributes().trim).toBeDefined();
});
it('passes `number`', () => {
expect(findInput().attributes().number).toBeDefined();
});
});
});
});
...@@ -3,7 +3,6 @@ import boardsStoreEE from 'ee/boards/stores/boards_store_ee'; ...@@ -3,7 +3,6 @@ import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import actions from 'ee/boards/stores/actions'; import actions from 'ee/boards/stores/actions';
import * as types from 'ee/boards/stores/mutation_types'; import * as types from 'ee/boards/stores/mutation_types';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { inactiveId } from '~/boards/constants';
jest.mock('axios'); jest.mock('axios');
...@@ -30,23 +29,6 @@ describe('setShowLabels', () => { ...@@ -30,23 +29,6 @@ describe('setShowLabels', () => {
}); });
}); });
describe('setActiveId', () => {
it('should commit mutation SET_ACTIVE_LIST_ID', done => {
const state = {
activeId: inactiveId,
};
testAction(
actions.setActiveId,
1,
state,
[{ type: types.SET_ACTIVE_LIST_ID, payload: 1 }],
[],
done,
);
});
});
describe('updateListWipLimit', () => { describe('updateListWipLimit', () => {
let storeMock; let storeMock;
......
import mutations from 'ee/boards/stores/mutations'; import mutations from 'ee/boards/stores/mutations';
import { inactiveId } from '~/boards/constants';
import { mockLists, mockEpics } from '../mock_data'; import { mockLists, mockEpics } from '../mock_data';
const expectNotImplemented = action => { const expectNotImplemented = action => {
...@@ -20,19 +19,6 @@ describe('SET_SHOW_LABELS', () => { ...@@ -20,19 +19,6 @@ describe('SET_SHOW_LABELS', () => {
}); });
}); });
describe('SET_ACTIVE_LIST_ID', () => {
it('updates aciveListId to be the value that is passed', () => {
const expectedId = 1;
const state = {
activeId: inactiveId,
};
mutations.SET_ACTIVE_LIST_ID(state, expectedId);
expect(state.activeId).toBe(expectedId);
});
});
describe('REQUEST_AVAILABLE_BOARDS', () => { describe('REQUEST_AVAILABLE_BOARDS', () => {
expectNotImplemented(mutations.REQUEST_AVAILABLE_BOARDS); expectNotImplemented(mutations.REQUEST_AVAILABLE_BOARDS);
}); });
......
import '~/boards/models/list';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDrawer, GlLabel, GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import boardsStore from '~/boards/stores/boards_store';
import sidebarEventHub from '~/sidebar/event_hub';
import { inactiveId } from '~/boards/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('BoardSettingsSidebar', () => {
let wrapper;
let mock;
let storeActions;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
const createComponent = (state = { activeId: inactiveId }, actions = {}) => {
storeActions = actions;
const store = new Vuex.Store({
state,
actions: storeActions,
});
wrapper = shallowMount(BoardSettingsSidebar, {
store,
localVue,
});
};
const findLabel = () => wrapper.find(GlLabel);
const findDrawer = () => wrapper.find(GlDrawer);
beforeEach(() => {
boardsStore.create();
});
afterEach(() => {
jest.restoreAllMocks();
wrapper.destroy();
});
it('finds a GlDrawer component', () => {
createComponent();
expect(findDrawer().exists()).toBe(true);
});
describe('on close', () => {
it('calls closeSidebar', async () => {
const spy = jest.fn();
createComponent({ activeId: inactiveId }, { setActiveId: spy });
findDrawer().vm.$emit('close');
await wrapper.vm.$nextTick();
expect(storeActions.setActiveId).toHaveBeenCalledWith(
expect.anything(),
inactiveId,
undefined,
);
});
it('calls closeSidebar on sidebar.closeAll event', async () => {
createComponent({ activeId: inactiveId }, { setActiveId: jest.fn() });
sidebarEventHub.$emit('sidebar.closeAll');
await wrapper.vm.$nextTick();
expect(storeActions.setActiveId).toHaveBeenCalledWith(
expect.anything(),
inactiveId,
undefined,
);
});
});
describe('when activeId is zero', () => {
it('renders GlDrawer with open false', () => {
createComponent();
expect(findDrawer().props('open')).toBe(false);
});
});
describe('when activeId is greater than zero', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
});
afterEach(() => {
boardsStore.removeList(listId);
});
it('renders GlDrawer with open false', () => {
createComponent({ activeId: 1 });
expect(findDrawer().props('open')).toBe(true);
});
});
describe('when activeId is in boardsStore', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
createComponent({ activeId: listId });
});
afterEach(() => {
mock.restore();
});
it('renders label title', () => {
expect(findLabel().props('title')).toBe(labelTitle);
});
it('renders label background color', () => {
expect(findLabel().props('backgroundColor')).toBe(labelColor);
});
});
describe('when activeId is not in boardsStore', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } });
createComponent({ activeId: inactiveId });
});
afterEach(() => {
mock.restore();
});
it('does not render GlLabel', () => {
expect(findLabel().exists()).toBe(false);
});
});
describe('when activeList is present', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
boardsStore.removeList(listId);
});
describe('when list type is "milestone"', () => {
beforeEach(() => {
boardsStore.addList({
id: 1,
milestone: {
webUrl: 'https://gitlab.com/h5bp/html5-boilerplate/-/milestones/1',
title: 'Backlog',
},
max_issue_count: 1,
list_type: 'milestone',
});
});
afterEach(() => {
boardsStore.removeList(1, 'milestone');
wrapper.destroy();
});
it('renders the correct milestone text', () => {
createComponent({ activeId: 1 });
expect(wrapper.find('.js-milestone').text()).toBe('Backlog');
});
it('renders the correct list type text', () => {
createComponent({ activeId: 1 });
expect(wrapper.find('.js-list-label').text()).toBe('Milestone');
});
});
describe('when list type is "assignee"', () => {
beforeEach(() => {
boardsStore.addList({
id: 1,
user: { username: 'root', avatar: '', name: 'Test', webUrl: 'https://gitlab.com/root' },
max_issue_count: 1,
list_type: 'assignee',
});
});
afterEach(() => {
boardsStore.removeList(1, 'assignee');
wrapper.destroy();
});
it('renders gl-avatar-link with correct href', () => {
createComponent({ activeId: 1 });
expect(wrapper.find(GlAvatarLink).exists()).toBe(true);
expect(wrapper.find(GlAvatarLink).attributes('href')).toBe('https://gitlab.com/root');
});
it('renders gl-avatar-labeled with "root" as username and name as "Test"', () => {
createComponent({ activeId: 1 });
expect(wrapper.find(GlAvatarLabeled).exists()).toBe(true);
expect(wrapper.find(GlAvatarLabeled).attributes('label')).toBe('Test');
expect(wrapper.find(GlAvatarLabeled).attributes('sublabel')).toBe('@root');
});
it('renders the correct list type text', () => {
createComponent({ activeId: 1 });
expect(wrapper.find('.js-list-label').text()).toBe('Assignee');
});
});
});
});
import actions from '~/boards/stores/actions'; import actions from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types'; import * as types from '~/boards/stores/mutation_types';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { inactiveId } from '~/boards/constants';
const expectNotImplemented = action => { const expectNotImplemented = action => {
it('is not implemented', () => { it('is not implemented', () => {
...@@ -25,6 +26,23 @@ describe('setEndpoints', () => { ...@@ -25,6 +26,23 @@ describe('setEndpoints', () => {
}); });
}); });
describe('setActiveId', () => {
it('should commit mutation SET_ACTIVE_ID', done => {
const state = {
activeId: inactiveId,
};
testAction(
actions.setActiveId,
1,
state,
[{ type: types.SET_ACTIVE_ID, payload: 1 }],
[],
done,
);
});
});
describe('fetchLists', () => { describe('fetchLists', () => {
expectNotImplemented(actions.fetchLists); expectNotImplemented(actions.fetchLists);
}); });
......
...@@ -32,6 +32,16 @@ describe('Board Store Mutations', () => { ...@@ -32,6 +32,16 @@ describe('Board Store Mutations', () => {
}); });
}); });
describe('SET_ACTIVE_ID', () => {
it('updates aciveListId to be the value that is passed', () => {
const expectedId = 1;
mutations.SET_ACTIVE_ID(state, expectedId);
expect(state.activeId).toBe(expectedId);
});
});
describe('REQUEST_ADD_LIST', () => { describe('REQUEST_ADD_LIST', () => {
expectNotImplemented(mutations.REQUEST_ADD_LIST); expectNotImplemented(mutations.REQUEST_ADD_LIST);
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment