diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index d8504dcfb0fba3d51a102ebe44d3ea53fdb57e95..8c354c61fa261dcac10579ea546d4d5a53055981 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -107,7 +107,7 @@ export default { }; }, computed: { - ...mapGetters(['isEpicBoard', 'isGroupBoard', 'isProjectBoard']), + ...mapGetters(['isIssueBoard', 'isGroupBoard', 'isProjectBoard']), isNewForm() { return this.currentPage === formType.new; }, @@ -182,7 +182,7 @@ export default { groupPath: this.isGroupBoard ? this.fullPath : undefined, }; }, - boardScopeMutationVariables() { + issueBoardScopeMutationVariables() { /* eslint-disable @gitlab/require-i18n-strings */ return { weight: this.board.weight, @@ -193,13 +193,18 @@ export default { this.board.milestone?.id || this.board.milestone?.id === 0 ? convertToGraphQLId('Milestone', this.board.milestone.id) : null, - labelIds: this.board.labels.map(fullLabelId), iterationId: this.board.iteration_id ? convertToGraphQLId('Iteration', this.board.iteration_id) : null, }; /* eslint-enable @gitlab/require-i18n-strings */ }, + boardScopeMutationVariables() { + return { + labelIds: this.board.labels.map(fullLabelId), + ...(this.isIssueBoard && this.issueBoardScopeMutationVariables), + }; + }, mutationVariables() { return { ...this.baseMutationVariables, @@ -324,7 +329,7 @@ export default { /> <board-scope - v-if="scopedIssueBoardFeatureEnabled && !isEpicBoard" + v-if="scopedIssueBoardFeatureEnabled" :collapse-scope="isNewForm" :board="board" :can-admin-board="canAdminBoard" diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue index 7ec99e51f5bd4f66286a4c9937125d5f85520565..fdb60d0ae6accb2dc5812ca05e887c05239e0db7 100644 --- a/app/assets/javascripts/boards/components/config_toggle.vue +++ b/app/assets/javascripts/boards/components/config_toggle.vue @@ -15,7 +15,8 @@ export default { props: { boardsStore: { type: Object, - required: true, + required: false, + default: null, }, canAdminList: { type: Boolean, @@ -26,11 +27,6 @@ export default { required: true, }, }, - data() { - return { - state: this.boardsStore.state, - }; - }, computed: { buttonText() { return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope'); @@ -42,7 +38,9 @@ export default { methods: { showPage() { eventHub.$emit('showBoardModal', formType.edit); - return this.boardsStore.showPage(formType.edit); + if (this.boardsStore) { + this.boardsStore.showPage(formType.edit); + } }, }, }; diff --git a/app/assets/javascripts/boards/config_toggle.js b/app/assets/javascripts/boards/config_toggle.js index 7f327c5764d4e83e3a012e50dce4e94832a0a303..41938d8e2848d7d612eab5d8171f17ef0ca18faf 100644 --- a/app/assets/javascripts/boards/config_toggle.js +++ b/app/assets/javascripts/boards/config_toggle.js @@ -2,14 +2,15 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; import ConfigToggle from './components/config_toggle.vue'; -export default (boardsStore) => { +export default (boardsStore = undefined) => { const el = document.querySelector('.js-board-config'); if (!el) { return; } - gl.boardConfigToggle = new Vue({ + // eslint-disable-next-line no-new + new Vue({ el, render(h) { return h(ConfigToggle, { diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js index caa518f91ce2c4964ed8538020978a3c4aee914a..c819717dade104e88d59bebe81201d605b324e7a 100644 --- a/app/assets/javascripts/boards/stores/getters.js +++ b/app/assets/javascripts/boards/stores/getters.js @@ -1,5 +1,5 @@ import { find } from 'lodash'; -import { BoardType, inactiveId } from '../constants'; +import { BoardType, inactiveId, issuableTypes } from '../constants'; export default { isGroupBoard: (state) => state.boardType === BoardType.group, @@ -44,6 +44,10 @@ export default { return find(state.boardLists, (l) => l.title === title); }, + isIssueBoard: (state) => { + return state.issuableType === issuableTypes.issue; + }, + isEpicBoard: () => { return false; }, diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 56e69ab94183f4c39db0e826c75b8aafa7407810..ed08a024b592315cb681cbab4c4c1872d44b630d 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -313,7 +313,11 @@ export default class LabelsSelect { return; } - if ($('html').hasClass('issue-boards-page')) { + if ( + $('html') + .attr('class') + .match(/issue-boards-page|epic-boards-page/) + ) { return; } if ($dropdown.hasClass('js-multiselect')) { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9af45daaca49cce0519b5d4e6b38a5cb9d9c5de1..1115bee0843e0fa14411530ac1f2395a5498c64f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -281,6 +281,7 @@ def conditional_link_to(condition, options, html_options = {}, &block) def page_class class_names = [] class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards) + class_names << 'epic-boards-page' if current_controller?(:epic_boards) class_names << 'environment-logs-page' if current_controller?(:logs) class_names << 'with-performance-bar' if performance_bar_enabled? class_names << system_message_class diff --git a/ee/app/assets/javascripts/boards/components/board_form.vue b/ee/app/assets/javascripts/boards/components/board_form.vue index d8964ea8475494bab28cb347e0cb3a23e1c6e610..3b0c9fcdfa3d4aaf199096381cac898786ab9d6a 100644 --- a/ee/app/assets/javascripts/boards/components/board_form.vue +++ b/ee/app/assets/javascripts/boards/components/board_form.vue @@ -4,7 +4,9 @@ /* eslint-disable @gitlab/no-runtime-template-compiler */ import { mapGetters } from 'vuex'; import BoardFormFoss from '~/boards/components/board_form.vue'; +import { fullEpicBoardId } from '../boards_util'; import createEpicBoardMutation from '../graphql/epic_board_create.mutation.graphql'; +import updateEpicBoardMutation from '../graphql/epic_board_update.mutation.graphql'; export default { extends: BoardFormFoss, @@ -13,11 +15,16 @@ export default { epicBoardCreateQuery() { return createEpicBoardMutation; }, + currentEpicBoardMutation() { + return this.board.id ? updateEpicBoardMutation : createEpicBoardMutation; + }, mutationVariables() { - // TODO: Epic board scope will be added in a future iteration: https://gitlab.com/gitlab-org/gitlab/-/issues/231389 + const { board } = this; + return { ...this.baseMutationVariables, - ...(this.scopedIssueBoardFeatureEnabled && !this.isEpicBoard + ...(board.id && this.isEpicBoard && { id: fullEpicBoardId(board.id) }), + ...(this.scopedIssueBoardFeatureEnabled || this.isEpicBoard ? this.boardScopeMutationVariables : {}), }; @@ -27,9 +34,12 @@ export default { epicBoardCreateResponse(data) { return data.epicBoardCreate.epicBoard.webPath; }, + epicBoardUpdateResponse(data) { + return data.epicBoardUpdate.epicBoard.webPath; + }, async createOrUpdateBoard() { const response = await this.$apollo.mutate({ - mutation: this.isEpicBoard ? this.epicBoardCreateQuery : this.currentMutation, + mutation: this.isEpicBoard ? this.currentEpicBoardMutation : this.currentMutation, variables: { input: this.mutationVariables }, }); @@ -39,7 +49,9 @@ export default { : this.boardCreateResponse(response.data); } - return this.boardUpdateResponse(response.data); + return this.isEpicBoard + ? this.epicBoardUpdateResponse(response.data) + : this.boardUpdateResponse(response.data); }, }, }; diff --git a/ee/app/assets/javascripts/boards/components/board_scope.vue b/ee/app/assets/javascripts/boards/components/board_scope.vue index 5b8ff5f86d638505e694a9bdb4ac47ba449c4bfe..e9ddf2071ba9b02b2a60678bf0a917210be1840d 100644 --- a/ee/app/assets/javascripts/boards/components/board_scope.vue +++ b/ee/app/assets/javascripts/boards/components/board_scope.vue @@ -1,4 +1,5 @@ <script> +import { mapGetters } from 'vuex'; import ListLabel from '~/boards/models/label'; import { __ } from '~/locale'; import BoardLabelsSelect from '~/vue_shared/components/sidebar/labels_select/base.vue'; @@ -66,6 +67,7 @@ export default { }, computed: { + ...mapGetters(['isIssueBoard']), expandButtonText() { return this.expanded ? __('Collapse') : __('Expand'); }, @@ -110,6 +112,7 @@ export default { </p> <div v-if="!collapseScope || expanded"> <board-milestone-select + v-if="isIssueBoard" :board="board" :group-id="groupId" :project-id="projectId" @@ -117,6 +120,7 @@ export default { /> <board-scope-current-iteration + v-if="isIssueBoard" :can-admin-board="canAdminBoard" :iteration-id="board.iteration_id" @set-iteration="$emit('set-iteration', $event)" @@ -136,6 +140,7 @@ export default { > <assignee-select + v-if="isIssueBoard" :board="board" :selected="board.assignee" :can-edit="canAdminBoard" @@ -150,6 +155,7 @@ export default { <!-- eslint-disable vue/no-mutating-props --> <board-weight-select + v-if="isIssueBoard" v-model="board.weight" :board="board" :weights="weights" diff --git a/ee/app/assets/javascripts/boards/graphql/epic_board_update.mutation.graphql b/ee/app/assets/javascripts/boards/graphql/epic_board_update.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..01e312bd49216be6221750af6ecae8922f38deab --- /dev/null +++ b/ee/app/assets/javascripts/boards/graphql/epic_board_update.mutation.graphql @@ -0,0 +1,9 @@ +mutation updateEpicBoard($input: EpicBoardUpdateInput!) { + epicBoardUpdate(input: $input) { + epicBoard { + id + webPath + } + errors + } +} diff --git a/ee/app/assets/javascripts/boards/graphql/lists_epics.query.graphql b/ee/app/assets/javascripts/boards/graphql/lists_epics.query.graphql index 60986726ced0b8597946fad2718586884a27a266..efae37e56858c4fbc0fb39fad26ba07d3a65226e 100644 --- a/ee/app/assets/javascripts/boards/graphql/lists_epics.query.graphql +++ b/ee/app/assets/javascripts/boards/graphql/lists_epics.query.graphql @@ -5,6 +5,7 @@ query ListEpics( $fullPath: ID! $boardId: BoardsEpicBoardID! $id: BoardsEpicListID + $filters: EpicFilters $after: String $first: Int ) { @@ -13,7 +14,7 @@ query ListEpics( lists(id: $id) { nodes { id - epics(first: $first, after: $after) { + epics(first: $first, after: $after, filters: $filters) { edges { node { ...EpicNode diff --git a/ee/app/assets/javascripts/boards/stores/actions.js b/ee/app/assets/javascripts/boards/stores/actions.js index 36e875fb02a6b0884b9609880a608851440d7bb5..6d178d7ac8026c9eab4335e3133f0e01a434969e 100644 --- a/ee/app/assets/javascripts/boards/stores/actions.js +++ b/ee/app/assets/javascripts/boards/stores/actions.js @@ -115,7 +115,7 @@ const fetchAndFormatListEpics = (state, extraVariables) => { export default { ...actionsCE, - setFilters: ({ commit, dispatch }, filters) => { + setFilters: ({ commit, dispatch, getters }, filters) => { const filterParams = pick(filters, [ 'assigneeUsername', 'authorUsername', @@ -128,7 +128,10 @@ export default { 'weight', ]); - filterParams.not = transformNotFilters(filters); + // Temporarily disabled until negated filters are supported for epic boards + if (!getters.isEpicBoard) { + filterParams.not = transformNotFilters(filters); + } if (filters.groupBy === GroupByParamType.epic) { dispatch('setEpicSwimlanes'); @@ -140,7 +143,7 @@ export default { } else if (filterParams.epicId) { filterParams.epicId = fullEpicId(filterParams.epicId); } - if (filterParams.not.epicId) { + if (!getters.isEpicBoard && filterParams.not.epicId) { filterParams.not.epicId = fullEpicId(filterParams.not.epicId); } diff --git a/ee/app/assets/javascripts/epic_boards/index.js b/ee/app/assets/javascripts/epic_boards/index.js index 7e215d3f6a6eb3072b0a1baf4364f8ff37be14e6..de2a09ab91fb39f815e2fe6bb99b8b321815bfc0 100644 --- a/ee/app/assets/javascripts/epic_boards/index.js +++ b/ee/app/assets/javascripts/epic_boards/index.js @@ -4,14 +4,16 @@ /* eslint-disable @gitlab/no-runtime-template-compiler */ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { mapActions } from 'vuex'; +import { mapActions, mapState } from 'vuex'; +import { transformBoardConfig } from 'ee_component/boards/boards_util'; import BoardSidebar from 'ee_component/boards/components/board_sidebar'; import toggleLabels from 'ee_component/boards/toggle_labels'; import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue'; import BoardContent from '~/boards/components/board_content.vue'; import BoardAddIssuesModal from '~/boards/components/modal/index.vue'; +import boardConfigToggle from '~/boards/config_toggle'; import { issuableTypes } from '~/boards/constants'; import mountMultipleBoardsSwitcher from '~/boards/mount_multiple_boards_switcher'; import store from '~/boards/stores'; @@ -19,6 +21,7 @@ import createDefaultClient from '~/lib/graphql'; import '~/boards/filters/due_date_filters'; import { NavigationType, parseBoolean } from '~/lib/utils/common_utils'; +import { updateHistory } from '~/lib/utils/url_utility'; Vue.use(VueApollo); @@ -87,6 +90,9 @@ export default () => { detailIssueVisible: false, }; }, + computed: { + ...mapState(['boardConfig']), + }, created() { this.setInitialBoardData({ boardId: $boardApp.dataset.boardId, @@ -110,6 +116,13 @@ export default () => { }); }, mounted() { + const boardConfigPath = transformBoardConfig(this.boardConfig); + if (boardConfigPath !== '') { + const filterPath = window.location.search ? `${window.location.search}&` : '?'; + updateHistory({ + url: `${filterPath}${transformBoardConfig(this.boardConfig)}`, + }); + } this.performSearch(); }, methods: { @@ -136,6 +149,7 @@ export default () => { } toggleLabels(); + boardConfigToggle(); mountMultipleBoardsSwitcher({ fullPath: $boardApp.dataset.fullPath, diff --git a/ee/app/models/boards/epic_board.rb b/ee/app/models/boards/epic_board.rb index d2890a9f5c0810d338f41ab16ea91f657a93d745..b6925263494ae8a5f9c5bc7675b1a2b5df3b4b99 100644 --- a/ee/app/models/boards/epic_board.rb +++ b/ee/app/models/boards/epic_board.rb @@ -33,7 +33,7 @@ def group_board? end def scoped? - false + labels.any? end def milestone_id diff --git a/ee/spec/features/epic_boards/epic_boards_spec.rb b/ee/spec/features/epic_boards/epic_boards_spec.rb index c7f26f09381bfd0eeb70e3962313483c78cac45f..6fd648978dfbccdd54b66f778faafdf3b3fd5ebe 100644 --- a/ee/spec/features/epic_boards/epic_boards_spec.rb +++ b/ee/spec/features/epic_boards/epic_boards_spec.rb @@ -20,6 +20,9 @@ let_it_be(:epic2) { create(:epic, group: group, title: 'Epic2') } let_it_be(:epic3) { create(:epic, group: group, labels: [label2], title: 'Epic3') } + let(:edit_board) { find('.btn', text: 'Edit board') } + let(:view_scope) { find('.btn', text: 'View scope') } + context 'display epics in board' do before do stub_licensed_features(epics: true) @@ -103,6 +106,25 @@ it "shows 'Create list' button" do expect(page).to have_selector('[data-testid="boards-create-list"]') end + + it 'creates board filtering by one label' do + create_board_label(label.title) + + expect(page).to have_selector('.board-card', count: 1) + end + + it 'adds label to board scope and filters epics' do + label_title = label.title + + update_board_label(label_title) + + aggregate_failures do + expect(page).to have_selector('.board-card', count: 1) + expect(page).to have_content('Epic1') + expect(page).not_to have_content('Epic2') + expect(page).not_to have_content('Epic3') + end + end end context 'when user cannot admin epic boards' do @@ -116,6 +138,21 @@ it "does not show 'Create list'" do expect(page).not_to have_selector('[data-testid="boards-create-list"]') end + + it 'can view board scope' do + view_scope.click + + page.within('.modal') do + aggregate_failures do + expect(find('.modal-header')).to have_content('Board scope') + expect(page).not_to have_content('Board name') + expect(page).not_to have_link('Edit') + expect(page).not_to have_button('Edit') + expect(page).not_to have_button('Save') + expect(page).not_to have_button('Cancel') + end + end + end end def visit_epic_boards_page @@ -139,4 +176,70 @@ def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0 list_to_index: list_to_index, perform_drop: perform_drop) end + + def click_value(filter, value) + page.within(".#{filter}") do + click_button 'Edit' + + if value.is_a?(Array) + value.each { |value| click_link value } + else + click_link value + end + end + end + + def click_on_create_new_board + page.within '.js-boards-selector' do + find('.dropdown-menu-toggle').click + wait_for_requests + + click_button 'Create new board' + end + end + + def create_board_label(label_title) + create_board_scope('labels', label_title) + end + + def create_board_scope(filter, value) + click_on_create_new_board + find('#board-new-name').set 'test' + + click_button 'Expand' + + click_value(filter, value) + + click_on_board_modal + + click_button 'Create board' + + wait_for_requests + + expect(page).not_to have_selector('.board-list-loading') + end + + def update_board_scope(filter, value) + edit_board.click + + click_value(filter, value) + + click_on_board_modal + + click_button 'Save changes' + + wait_for_requests + + expect(page).not_to have_selector('.board-list-loading') + end + + def update_board_label(label_title) + update_board_scope('labels', label_title) + end + + # Click on modal to make sure the dropdown is closed (e.g. label scenario) + # + def click_on_board_modal + find('.board-config-modal .modal-content').click + end end diff --git a/ee/spec/frontend/boards/components/board_form_spec.js b/ee/spec/frontend/boards/components/board_form_spec.js index 86094ea6d05f5206069cecf681969ccd79b0f5a8..10141d44c34aac97e7183d8d0404c134ea827121 100644 --- a/ee/spec/frontend/boards/components/board_form_spec.js +++ b/ee/spec/frontend/boards/components/board_form_spec.js @@ -4,6 +4,7 @@ import Vuex from 'vuex'; import BoardForm from 'ee/boards/components/board_form.vue'; import createEpicBoardMutation from 'ee/boards/graphql/epic_board_create.mutation.graphql'; +import updateEpicBoardMutation from 'ee/boards/graphql/epic_board_update.mutation.graphql'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; @@ -55,6 +56,7 @@ describe('BoardForm', () => { const createStore = () => { return new Vuex.Store({ getters: { + isIssueBoard: () => false, isEpicBoard: () => true, isGroupBoard: () => true, isProjectBoard: () => false, @@ -181,4 +183,48 @@ describe('BoardForm', () => { }); }); }); + + describe('when editing an epic board', () => { + it('calls GraphQL mutation with correct parameters', async () => { + mutate = jest.fn().mockResolvedValue({ + data: { + epicBoardUpdate: { + epicBoard: { id: 'gid://gitlab/Boards::EpicBoard/321', webPath: 'test-path' }, + }, + }, + }); + window.location = new URL('https://test/boards/1'); + createComponent({ canAdminBoard: true, currentPage: formType.edit }); + + findInput().trigger('keyup.enter', { metaKey: true }); + + await waitForPromises(); + + expect(mutate).toHaveBeenCalledWith({ + mutation: updateEpicBoardMutation, + variables: { + input: expect.objectContaining({ + id: `gid://gitlab/Boards::EpicBoard/${currentBoard.id}`, + }), + }, + }); + + await waitForPromises(); + expect(visitUrl).toHaveBeenCalledWith('test-path'); + }); + + it('shows an error flash if GraphQL mutation fails', async () => { + mutate = jest.fn().mockRejectedValue('Houston, we have a problem'); + createComponent({ canAdminBoard: true, currentPage: formType.edit }); + findInput().trigger('keyup.enter', { metaKey: true }); + + await waitForPromises(); + + expect(mutate).toHaveBeenCalled(); + + await waitForPromises(); + expect(visitUrl).not.toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalled(); + }); + }); }); diff --git a/ee/spec/frontend/boards/components/board_scope_spec.js b/ee/spec/frontend/boards/components/board_scope_spec.js index df4517daed2d4ba8205042480fad1520e82eb8c1..c09875b8852ad704994bf56d5f7b2698e9c63a97 100644 --- a/ee/spec/frontend/boards/components/board_scope_spec.js +++ b/ee/spec/frontend/boards/components/board_scope_spec.js @@ -1,7 +1,11 @@ -import { mount } from '@vue/test-utils'; +import { createLocalVue, mount } from '@vue/test-utils'; +import Vuex from 'vuex'; import BoardScope from 'ee/boards/components/board_scope.vue'; import { TEST_HOST } from 'helpers/test_constants'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('BoardScope', () => { let wrapper; let vm; @@ -18,8 +22,21 @@ describe('BoardScope', () => { labelsWebUrl: `${TEST_HOST}/-/labels`, }; + const createStore = () => { + return new Vuex.Store({ + getters: { + isIssueBoard: () => true, + isEpicBoard: () => false, + }, + }); + }; + + const store = createStore(); + wrapper = mount(BoardScope, { + localVue, propsData, + store, }); ({ vm } = wrapper); diff --git a/ee/spec/frontend/boards/stores/getters_spec.js b/ee/spec/frontend/boards/stores/getters_spec.js index 0477709c1858780d8edf8f36890884e7ff4f35cf..0034d3ee568737ced6458c2cfceaa1b81bb71eb8 100644 --- a/ee/spec/frontend/boards/stores/getters_spec.js +++ b/ee/spec/frontend/boards/stores/getters_spec.js @@ -122,4 +122,21 @@ describe('EE Boards Store Getters', () => { ).toBeUndefined(); }); }); + + describe('isEpicBoard', () => { + it.each` + issuableType | expected + ${'epic'} | ${true} + ${'issue'} | ${false} + `( + 'returns $expected when issuableType on state is $issuableType', + ({ issuableType, expected }) => { + const state = { + issuableType, + }; + + expect(getters.isEpicBoard(state)).toBe(expected); + }, + ); + }); }); diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js index 32d73d861bcc4263420e3d57a75cae625131f98b..0541c40061cbeaf4103c4c7f9af3b07adce69a6d 100644 --- a/spec/frontend/boards/stores/getters_spec.js +++ b/spec/frontend/boards/stores/getters_spec.js @@ -177,4 +177,31 @@ describe('Boards - Getters', () => { expect(getters.activeGroupProjects(state)).toEqual([mockGroupProject1]); }); }); + + describe('isIssueBoard', () => { + it.each` + issuableType | expected + ${'issue'} | ${true} + ${'epic'} | ${false} + `( + 'returns $expected when issuableType on state is $issuableType', + ({ issuableType, expected }) => { + const state = { + issuableType, + }; + + expect(getters.isIssueBoard(state)).toBe(expected); + }, + ); + }); + + describe('isEpicBoard', () => { + afterEach(() => { + window.gon = { features: {} }; + }); + + it('returns false', () => { + expect(getters.isEpicBoard()).toBe(false); + }); + }); });