From 1d59a6fb13d21d8d5c3ff82ebdfc5c7e04b26366 Mon Sep 17 00:00:00 2001
From: Simon Knox <simon@gitlab.com>
Date: Thu, 4 Mar 2021 06:49:11 +0000
Subject: [PATCH] Split new column form into data/display components

Makes it easier to extend for different types of list
in EE
---
 .../components/board_add_new_column.vue       | 216 ++++++------------
 .../components/board_add_new_column_form.vue  | 122 ++++++++++
 .../javascripts/boards/stores/actions.js      |   4 +-
 .../javascripts/boards/stores/mutations.js    |   7 +-
 .../javascripts/boards/stores/actions.js      |   2 +-
 .../frontend/boards/stores/actions_spec.js    |   4 +-
 locale/gitlab.pot                             |   7 +-
 .../board_add_new_column_form_spec.js         | 166 ++++++++++++++
 .../components/board_add_new_column_spec.js   |  70 ++----
 spec/frontend/boards/stores/actions_spec.js   |   4 +-
 10 files changed, 383 insertions(+), 219 deletions(-)
 create mode 100644 app/assets/javascripts/boards/components/board_add_new_column_form.vue
 create mode 100644 spec/frontend/boards/components/board_add_new_column_form_spec.js

diff --git a/app/assets/javascripts/boards/components/board_add_new_column.vue b/app/assets/javascripts/boards/components/board_add_new_column.vue
index 7818d45507e..ce4590bd755 100644
--- a/app/assets/javascripts/boards/components/board_add_new_column.vue
+++ b/app/assets/javascripts/boards/components/board_add_new_column.vue
@@ -1,38 +1,23 @@
 <script>
 import {
-  GlButton,
-  GlFormGroup,
   GlFormRadio,
   GlFormRadioGroup,
   GlLabel,
-  GlSearchBoxByType,
-  GlSkeletonLoader,
   GlTooltipDirective as GlTooltip,
 } from '@gitlab/ui';
 import { mapActions, mapGetters, mapState } from 'vuex';
+import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
+import { ListType } from '~/boards/constants';
+import boardsStore from '~/boards/stores/boards_store';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import { isScopedLabel } from '~/lib/utils/common_utils';
-import { __ } from '~/locale';
-import boardsStore from '../stores/boards_store';
 
 export default {
-  i18n: {
-    add: __('Add'),
-    cancel: __('Cancel'),
-    formDescription: __('A label list displays all issues with the selected label.'),
-    newLabelList: __('New label list'),
-    noLabelSelected: __('No label selected'),
-    searchPlaceholder: __('Search labels'),
-    selectLabel: __('Select label'),
-    selected: __('Selected'),
-  },
   components: {
-    GlButton,
-    GlFormGroup,
+    BoardAddNewColumnForm,
     GlFormRadio,
     GlFormRadioGroup,
     GlLabel,
-    GlSearchBoxByType,
-    GlSkeletonLoader,
   },
   directives: {
     GlTooltip,
@@ -40,31 +25,27 @@ export default {
   inject: ['scopedLabelsAvailable'],
   data() {
     return {
-      searchTerm: '',
-      selectedLabelId: null,
+      selectedId: null,
     };
   },
   computed: {
     ...mapState(['labels', 'labelsLoading', 'isEpicBoard']),
     ...mapGetters(['getListByLabelId', 'shouldUseGraphQL']),
     selectedLabel() {
-      return this.labels.find(({ id }) => id === this.selectedLabelId);
+      if (!this.selectedId) {
+        return null;
+      }
+      return this.labels.find(({ id }) => id === this.selectedId);
+    },
+    columnForSelected() {
+      return this.getListByLabelId(this.selectedId);
     },
   },
   created() {
-    this.filterLabels();
+    this.filterItems();
   },
   methods: {
     ...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']),
-    getListByLabel(label) {
-      if (this.shouldUseGraphQL || this.isEpicBoard) {
-        return this.getListByLabelId(label);
-      }
-      return boardsStore.findListByLabelId(label.id);
-    },
-    columnExists(label) {
-      return Boolean(this.getListByLabel(label));
-    },
     highlight(listId) {
       if (this.shouldUseGraphQL || this.isEpicBoard) {
         this.highlightList(listId);
@@ -77,44 +58,35 @@ export default {
       }
     },
     addList() {
-      if (!this.selectedLabelId) {
-        return;
-      }
-
-      const label = this.selectedLabel;
-
-      if (!label) {
+      if (!this.selectedLabel) {
         return;
       }
 
       this.setAddColumnFormVisibility(false);
 
-      if (this.columnExists({ id: this.selectedLabelId })) {
-        const listId = this.getListByLabel(label).id;
+      if (this.columnForSelected) {
+        const listId = this.columnForSelected.id;
         this.highlight(listId);
         return;
       }
 
       if (this.shouldUseGraphQL || this.isEpicBoard) {
-        this.createList({ labelId: this.selectedLabelId });
+        this.createList({ labelId: this.selectedId });
       } else {
-        boardsStore.new({
-          title: label.title,
+        const listObj = {
+          labelId: getIdFromGraphQLId(this.selectedId),
+          title: this.selectedLabel.title,
           position: boardsStore.state.lists.length - 2,
-          list_type: 'label',
-          label: {
-            id: label.id,
-            title: label.title,
-            color: label.color,
-          },
-        });
+          list_type: ListType.label,
+          label: this.selectedLabel,
+        };
 
-        this.highlight(boardsStore.findListByLabelId(label.id).id);
+        boardsStore.new(listObj);
       }
     },
 
-    filterLabels() {
-      this.fetchLabels(this.searchTerm);
+    filterItems(searchTerm) {
+      this.fetchLabels(searchTerm);
     },
 
     showScopedLabels(label) {
@@ -125,103 +97,43 @@ export default {
 </script>
 
 <template>
-  <div
-    class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
-    data-testid="board-add-new-column"
-    data-qa-selector="board_add_new_list"
+  <board-add-new-column-form
+    :loading="labelsLoading"
+    :form-description="__('A label list displays issues with the selected label.')"
+    :search-label="__('Select label')"
+    :search-placeholder="__('Search labels')"
+    :selected-id="selectedId"
+    @filter-items="filterItems"
+    @add-list="addList"
   >
-    <div
-      class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
-    >
-      <h3
-        class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
-        data-testid="board-add-column-form-title"
-      >
-        {{ $options.i18n.newLabelList }}
-      </h3>
-
-      <div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden">
-        <!-- selectbox is here in EE -->
-
-        <p class="gl-m-5">{{ $options.i18n.formDescription }}</p>
-
-        <div class="gl-px-5 gl-pb-4">
-          <label class="gl-mb-2">{{ $options.i18n.selected }}</label>
-          <div>
-            <gl-label
-              v-if="selectedLabel"
-              v-gl-tooltip
-              :title="selectedLabel.title"
-              :description="selectedLabel.description"
-              :background-color="selectedLabel.color"
-              :scoped="showScopedLabels(selectedLabel)"
-            />
-            <div v-else class="gl-text-gray-500">{{ $options.i18n.noLabelSelected }}</div>
-          </div>
-        </div>
-
-        <gl-form-group
-          class="gl-mx-5 gl-mb-3"
-          :label="$options.i18n.selectLabel"
-          label-for="board-available-labels"
-        >
-          <gl-search-box-by-type
-            id="board-available-labels"
-            v-model.trim="searchTerm"
-            debounce="250"
-            :placeholder="$options.i18n.searchPlaceholder"
-            @input="filterLabels"
-          />
-        </gl-form-group>
-
-        <div v-if="labelsLoading" class="gl-m-5">
-          <gl-skeleton-loader :width="500" :height="172">
-            <rect width="480" height="20" x="10" y="15" rx="4" />
-            <rect width="380" height="20" x="10" y="50" rx="4" />
-            <rect width="430" height="20" x="10" y="85" rx="4" />
-          </gl-skeleton-loader>
-        </div>
-
-        <gl-form-radio-group
-          v-else
-          v-model="selectedLabelId"
-          class="gl-overflow-y-auto gl-px-5 gl-pt-3"
-        >
-          <label
-            v-for="label in labels"
-            :key="label.id"
-            class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
-          >
-            <gl-form-radio :value="label.id" class="gl-mb-0 gl-mr-3" />
-            <span
-              class="dropdown-label-box gl-top-0"
-              :style="{
-                backgroundColor: label.color,
-              }"
-            ></span>
-            <span>{{ label.title }}</span>
-          </label>
-        </gl-form-radio-group>
-      </div>
-
-      <div
-        class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10"
-      >
-        <gl-button
-          data-testid="cancelAddNewColumn"
-          class="gl-ml-auto gl-mr-3"
-          @click="setAddColumnFormVisibility(false)"
-          >{{ $options.i18n.cancel }}</gl-button
-        >
-        <gl-button
-          data-testid="addNewColumnButton"
-          :disabled="!selectedLabelId"
-          variant="success"
-          class="gl-mr-4"
-          @click="addList"
-          >{{ $options.i18n.add }}</gl-button
+    <template slot="selected">
+      <gl-label
+        v-if="selectedLabel"
+        v-gl-tooltip
+        :title="selectedLabel.title"
+        :description="selectedLabel.description"
+        :background-color="selectedLabel.color"
+        :scoped="showScopedLabels(selectedLabel)"
+      />
+    </template>
+
+    <template slot="items">
+      <gl-form-radio-group v-model="selectedId" class="gl-overflow-y-auto gl-px-5 gl-pt-3">
+        <label
+          v-for="label in labels"
+          :key="label.id"
+          class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
         >
-      </div>
-    </div>
-  </div>
+          <gl-form-radio :value="label.id" class="gl-mb-0 gl-mr-3" />
+          <span
+            class="dropdown-label-box gl-top-0"
+            :style="{
+              backgroundColor: label.color,
+            }"
+          ></span>
+          <span>{{ label.title }}</span>
+        </label>
+      </gl-form-radio-group>
+    </template>
+  </board-add-new-column-form>
 </template>
diff --git a/app/assets/javascripts/boards/components/board_add_new_column_form.vue b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
new file mode 100644
index 00000000000..99699943e02
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
@@ -0,0 +1,122 @@
+<script>
+import { GlButton, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
+import { mapActions } from 'vuex';
+import { __ } from '~/locale';
+
+export default {
+  i18n: {
+    add: __('Add'),
+    cancel: __('Cancel'),
+    newList: __('New list'),
+    noneSelected: __('None'),
+    selected: __('Selected'),
+  },
+  components: {
+    GlButton,
+    GlFormGroup,
+    GlSearchBoxByType,
+    GlSkeletonLoader,
+  },
+  props: {
+    loading: {
+      type: Boolean,
+      required: true,
+    },
+    formDescription: {
+      type: String,
+      required: true,
+    },
+    searchLabel: {
+      type: String,
+      required: true,
+    },
+    searchPlaceholder: {
+      type: String,
+      required: true,
+    },
+    selectedId: {
+      type: [Number, String],
+      required: false,
+      default: null,
+    },
+  },
+  methods: {
+    ...mapActions(['setAddColumnFormVisibility']),
+  },
+};
+</script>
+
+<template>
+  <div
+    class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
+    data-testid="board-add-new-column"
+    data-qa-selector="board_add_new_list"
+  >
+    <div
+      class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
+    >
+      <h3
+        class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
+        data-testid="board-add-column-form-title"
+      >
+        {{ $options.i18n.newList }}
+      </h3>
+
+      <div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden">
+        <slot name="select-list-type">
+          <div class="gl-mb-5"></div>
+        </slot>
+
+        <p class="gl-px-5">{{ formDescription }}</p>
+
+        <div class="gl-px-5 gl-pb-4">
+          <label class="gl-mb-2">{{ $options.i18n.selected }}</label>
+          <slot name="selected">
+            <div class="gl-text-gray-500">{{ $options.i18n.noneSelected }}</div>
+          </slot>
+        </div>
+
+        <gl-form-group
+          class="gl-mx-5 gl-mb-3"
+          :label="searchLabel"
+          label-for="board-available-column-entities"
+        >
+          <gl-search-box-by-type
+            id="board-available-column-entities"
+            debounce="250"
+            :placeholder="searchPlaceholder"
+            @input="$emit('filter-items', $event)"
+          />
+        </gl-form-group>
+
+        <div v-if="loading" class="gl-px-5">
+          <gl-skeleton-loader :width="500" :height="172">
+            <rect width="480" height="20" x="10" y="15" rx="4" />
+            <rect width="380" height="20" x="10" y="50" rx="4" />
+            <rect width="430" height="20" x="10" y="85" rx="4" />
+          </gl-skeleton-loader>
+        </div>
+
+        <slot v-else name="items"></slot>
+      </div>
+      <div
+        class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10"
+      >
+        <gl-button
+          data-testid="cancelAddNewColumn"
+          class="gl-ml-auto gl-mr-3"
+          @click="setAddColumnFormVisibility(false)"
+          >{{ $options.i18n.cancel }}</gl-button
+        >
+        <gl-button
+          data-testid="addNewColumnButton"
+          :disabled="!selectedId"
+          variant="success"
+          class="gl-mr-4"
+          @click="$emit('add-list')"
+          >{{ $options.i18n.add }}</gl-button
+        >
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index f5f2bd6b74c..e84f1b3359a 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -157,8 +157,8 @@ export default {
         },
       })
       .then(({ data }) => {
-        if (data?.boardListCreate?.errors.length) {
-          commit(types.CREATE_LIST_FAILURE);
+        if (data.boardListCreate?.errors.length) {
+          commit(types.CREATE_LIST_FAILURE, data.boardListCreate.errors[0]);
         } else {
           const list = data.boardListCreate?.list;
           dispatch('addList', list);
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index 99a3f3c8f86..96e7812fbe2 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -60,8 +60,11 @@ export default {
     state.filterParams = filterParams;
   },
 
-  [mutationTypes.CREATE_LIST_FAILURE]: (state) => {
-    state.error = s__('Boards|An error occurred while creating the list. Please try again.');
+  [mutationTypes.CREATE_LIST_FAILURE]: (
+    state,
+    error = s__('Boards|An error occurred while creating the list. Please try again.'),
+  ) => {
+    state.error = error;
   },
 
   [mutationTypes.RECEIVE_LABELS_REQUEST]: (state) => {
diff --git a/ee/app/assets/javascripts/boards/stores/actions.js b/ee/app/assets/javascripts/boards/stores/actions.js
index 8afff0d93cc..38f04c4b192 100644
--- a/ee/app/assets/javascripts/boards/stores/actions.js
+++ b/ee/app/assets/javascripts/boards/stores/actions.js
@@ -587,7 +587,7 @@ export default {
       })
       .then(({ data }) => {
         if (data?.epicBoardListCreate?.errors.length) {
-          commit(types.CREATE_LIST_FAILURE);
+          commit(types.CREATE_LIST_FAILURE, data.epicBoardListCreate.errors[0]);
         } else {
           const list = data.epicBoardListCreate?.list;
           dispatch('addList', list);
diff --git a/ee/spec/frontend/boards/stores/actions_spec.js b/ee/spec/frontend/boards/stores/actions_spec.js
index 694f9e3c624..8e96a22c4df 100644
--- a/ee/spec/frontend/boards/stores/actions_spec.js
+++ b/ee/spec/frontend/boards/stores/actions_spec.js
@@ -1026,14 +1026,14 @@ describe('moveIssue', () => {
         data: {
           epicBoardListCreate: {
             list: {},
-            errors: [{ foo: 'bar' }],
+            errors: ['foo'],
           },
         },
       });
 
       await actions.createEpicList({ getters, state, commit, dispatch }, { backlog: true });
 
-      expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE);
+      expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE, 'foo');
     });
 
     it('highlights list and does not re-query if it already exists', async () => {
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e7916e963f7..31e844d7d31 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1330,7 +1330,7 @@ msgstr ""
 msgid "A job artifact is an archive of files and directories saved by a job when it finishes."
 msgstr ""
 
-msgid "A label list displays all issues with the selected label."
+msgid "A label list displays issues with the selected label."
 msgstr ""
 
 msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies."
@@ -20298,7 +20298,7 @@ msgstr ""
 msgid "New label"
 msgstr ""
 
-msgid "New label list"
+msgid "New list"
 msgstr ""
 
 msgid "New merge request"
@@ -20520,9 +20520,6 @@ msgstr ""
 msgid "No label"
 msgstr ""
 
-msgid "No label selected"
-msgstr ""
-
 msgid "No labels with such name or description"
 msgstr ""
 
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
new file mode 100644
index 00000000000..3702f55f17b
--- /dev/null
+++ b/spec/frontend/boards/components/board_add_new_column_form_spec.js
@@ -0,0 +1,166 @@
+import { GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } 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';
+import defaultState from '~/boards/stores/state';
+import { mockLabelList } from '../mock_data';
+
+Vue.use(Vuex);
+
+describe('Board card layout', () => {
+  let wrapper;
+
+  const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
+    return new Vuex.Store({
+      state: {
+        ...defaultState,
+        ...state,
+      },
+      actions,
+      getters,
+    });
+  };
+
+  const mountComponent = ({
+    loading = false,
+    formDescription = '',
+    searchLabel = '',
+    searchPlaceholder = '',
+    selectedId,
+    actions,
+    slots,
+  } = {}) => {
+    wrapper = extendedWrapper(
+      shallowMount(BoardAddNewColumnForm, {
+        stubs: {
+          GlFormGroup: true,
+        },
+        propsData: {
+          loading,
+          formDescription,
+          searchLabel,
+          searchPlaceholder,
+          selectedId,
+        },
+        slots,
+        store: createStore({
+          actions: {
+            setAddColumnFormVisibility: jest.fn(),
+            ...actions,
+          },
+        }),
+      }),
+    );
+  };
+
+  afterEach(() => {
+    wrapper.destroy();
+    wrapper = null;
+  });
+
+  const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
+  const findSearchInput = () => wrapper.find(GlSearchBoxByType);
+  const findSearchLabel = () => wrapper.find(GlFormGroup);
+  const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
+  const submitButton = () => wrapper.findByTestId('addNewColumnButton');
+
+  it('shows form title & search input', () => {
+    mountComponent();
+
+    expect(formTitle()).toEqual(BoardAddNewColumnForm.i18n.newList);
+    expect(findSearchInput().exists()).toBe(true);
+  });
+
+  it('clicking cancel hides the form', () => {
+    const setAddColumnFormVisibility = jest.fn();
+    mountComponent({
+      actions: {
+        setAddColumnFormVisibility,
+      },
+    });
+
+    cancelButton().vm.$emit('click');
+
+    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({
+        loading,
+        slots: {
+          items: '<div class="item-slot">Some kind of list</div>',
+        },
+      });
+
+    it('hides items slot and shows skeleton while loading', () => {
+      mountWithItems(true);
+
+      expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
+      expect(wrapper.find('.item-slot').exists()).toBe(false);
+    });
+
+    it('shows items slot and hides skeleton while not loading', () => {
+      mountWithItems(false);
+
+      expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
+      expect(wrapper.find('.item-slot').exists()).toBe(true);
+    });
+  });
+
+  describe('search box', () => {
+    it('sets label and placeholder text from props', () => {
+      const props = {
+        searchLabel: 'Some items',
+        searchPlaceholder: 'Search for an item',
+      };
+
+      mountComponent(props);
+
+      expect(findSearchLabel().attributes('label')).toEqual(props.searchLabel);
+      expect(findSearchInput().attributes('placeholder')).toEqual(props.searchPlaceholder);
+    });
+
+    it('emits filter event on input', () => {
+      mountComponent();
+
+      const searchText = 'some text';
+
+      findSearchInput().vm.$emit('input', searchText);
+
+      expect(wrapper.emitted('filter-items')).toEqual([[searchText]]);
+    });
+  });
+
+  describe('Add list button', () => {
+    it('is disabled if no item is selected', () => {
+      mountComponent();
+
+      expect(submitButton().props('disabled')).toBe(true);
+    });
+
+    it('emits add-list event on click', async () => {
+      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 84b6d7abb1e..60584eaf6cf 100644
--- a/spec/frontend/boards/components/board_add_new_column_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_spec.js
@@ -1,9 +1,9 @@
-import { GlSearchBoxByType } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
 import Vue, { nextTick } from 'vue';
 import Vuex from 'vuex';
 import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue';
+import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
 import defaultState from '~/boards/stores/state';
 import { mockLabelList } from '../mock_data';
 
@@ -11,7 +11,6 @@ Vue.use(Vuex);
 
 describe('Board card layout', () => {
   let wrapper;
-  let shouldUseGraphQL;
 
   const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
     return new Vuex.Store({
@@ -25,19 +24,16 @@ describe('Board card layout', () => {
   };
 
   const mountComponent = ({
-    selectedLabelId,
+    selectedId,
     labels = [],
     getListByLabelId = jest.fn(),
     actions = {},
   } = {}) => {
     wrapper = extendedWrapper(
       shallowMount(BoardAddNewColumn, {
-        stubs: {
-          GlFormGroup: true,
-        },
         data() {
           return {
-            selectedLabelId,
+            selectedId,
           };
         },
         store: createStore({
@@ -47,12 +43,13 @@ describe('Board card layout', () => {
             ...actions,
           },
           getters: {
-            shouldUseGraphQL: () => shouldUseGraphQL,
+            shouldUseGraphQL: () => true,
             getListByLabelId: () => getListByLabelId,
           },
           state: {
             labels,
             labelsLoading: false,
+            isEpicBoard: false,
           },
         }),
         provide: {
@@ -64,65 +61,32 @@ describe('Board card layout', () => {
 
   afterEach(() => {
     wrapper.destroy();
-    wrapper = null;
-  });
-
-  const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
-  const findSearchInput = () => wrapper.find(GlSearchBoxByType);
-  const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
-  const submitButton = () => wrapper.findByTestId('addNewColumnButton');
-
-  beforeEach(() => {
-    shouldUseGraphQL = true;
-  });
-
-  it('shows form title & search input', () => {
-    mountComponent();
-
-    expect(formTitle()).toEqual(BoardAddNewColumn.i18n.newLabelList);
-    expect(findSearchInput().exists()).toBe(true);
-  });
-
-  it('clicking cancel hides the form', () => {
-    const setAddColumnFormVisibility = jest.fn();
-    mountComponent({
-      actions: {
-        setAddColumnFormVisibility,
-      },
-    });
-
-    cancelButton().vm.$emit('click');
-
-    expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
   });
 
   describe('Add list button', () => {
-    it('is disabled if no item is selected', () => {
-      mountComponent();
-
-      expect(submitButton().props('disabled')).toBe(true);
-    });
-
-    it('adds a new list on click', async () => {
-      const labelId = mockLabelList.label.id;
+    it('calls addList', async () => {
+      const getListByLabelId = jest.fn().mockReturnValue(null);
       const highlightList = jest.fn();
       const createList = jest.fn();
 
       mountComponent({
         labels: [mockLabelList.label],
-        selectedLabelId: labelId,
+        selectedId: mockLabelList.label.id,
+        getListByLabelId,
         actions: {
           createList,
           highlightList,
         },
       });
 
-      await nextTick();
+      wrapper.findComponent(BoardAddNewColumnForm).vm.$emit('add-list');
 
-      submitButton().vm.$emit('click');
+      await nextTick();
 
       expect(highlightList).not.toHaveBeenCalled();
-      expect(createList).toHaveBeenCalledWith(expect.anything(), { labelId });
+      expect(createList).toHaveBeenCalledWith(expect.anything(), {
+        labelId: mockLabelList.label.id,
+      });
     });
 
     it('highlights existing list if trying to re-add', async () => {
@@ -132,7 +96,7 @@ describe('Board card layout', () => {
 
       mountComponent({
         labels: [mockLabelList.label],
-        selectedLabelId: mockLabelList.label.id,
+        selectedId: mockLabelList.label.id,
         getListByLabelId,
         actions: {
           createList,
@@ -140,9 +104,9 @@ describe('Board card layout', () => {
         },
       });
 
-      await nextTick();
+      wrapper.findComponent(BoardAddNewColumnForm).vm.$emit('add-list');
 
-      submitButton().vm.$emit('click');
+      await nextTick();
 
       expect(highlightList).toHaveBeenCalledWith(expect.anything(), mockLabelList.id);
       expect(createList).not.toHaveBeenCalled();
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 9e1b5018cc1..377c606ac92 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -293,7 +293,7 @@ describe('createIssueList', () => {
         data: {
           boardListCreate: {
             list: {},
-            errors: [{ foo: 'bar' }],
+            errors: ['foo'],
           },
         },
       }),
@@ -301,7 +301,7 @@ describe('createIssueList', () => {
 
     await actions.createIssueList({ getters, state, commit, dispatch }, { backlog: true });
 
-    expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE);
+    expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE, 'foo');
   });
 
   it('highlights list and does not re-query if it already exists', async () => {
-- 
2.30.9