Commit ae46a11c authored by Simon Knox's avatar Simon Knox Committed by David O'Regan

Improve board add list interactions

Radio buttons for list type select as there are only
four options max. Regular dropdown for items. Some
other polish issues and little bugfixes that didn't
make it into earlier MRs

Dropdown item text wrapping requires gitlab-ui update
parent 211cccbe
<script>
import {
GlFormRadio,
GlFormRadioGroup,
GlLabel,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { GlFormRadio, GlFormRadioGroup, 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';
......@@ -17,7 +12,6 @@ export default {
BoardAddNewColumnForm,
GlFormRadio,
GlFormRadioGroup,
GlLabel,
},
directives: {
GlTooltip,
......@@ -99,25 +93,25 @@ export default {
<template>
<board-add-new-column-form
:loading="labelsLoading"
:form-description="__('A label list displays issues with the selected label.')"
:search-label="__('Select label')"
:none-selected="__('Select a label')"
:search-placeholder="__('Search labels')"
:selected-id="selectedId"
@filter-items="filterItems"
@add-list="addList"
>
<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 #selected>
<template v-if="selectedLabel">
<span
class="dropdown-label-box gl-top-0 gl-flex-shrink-0"
:style="{
backgroundColor: selectedLabel.color,
}"
></span>
<div class="gl-text-truncate">{{ selectedLabel.title }}</div>
</template>
</template>
<template slot="items">
<template #items>
<gl-form-radio-group
v-if="labels.length > 0"
v-model="selectedId"
......@@ -126,11 +120,11 @@ export default {
<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"
class="gl-display-flex gl-mb-5 gl-font-weight-normal gl-overflow-break-word"
>
<gl-form-radio :value="label.id" class="gl-mb-0" />
<gl-form-radio :value="label.id" />
<span
class="dropdown-label-box gl-top-0"
class="dropdown-label-box gl-top-0 gl-flex-shrink-0"
:style="{
backgroundColor: label.color,
}"
......
<script>
import { GlButton, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import {
GlButton,
GlDropdown,
GlFormGroup,
GlIcon,
GlSearchBoxByType,
GlSkeletonLoader,
} from '@gitlab/ui';
import { mapActions } from 'vuex';
import { __ } from '~/locale';
......@@ -8,13 +15,16 @@ export default {
add: __('Add to board'),
cancel: __('Cancel'),
newList: __('New list'),
noneSelected: __('None'),
noResults: __('No matching results'),
scope: __('Scope'),
scopeDescription: __('Issues must match this scope to appear in this list.'),
selected: __('Selected'),
},
components: {
GlButton,
GlDropdown,
GlFormGroup,
GlIcon,
GlSearchBoxByType,
GlSkeletonLoader,
},
......@@ -23,11 +33,12 @@ export default {
type: Boolean,
required: true,
},
formDescription: {
searchLabel: {
type: String,
required: true,
required: false,
default: null,
},
searchLabel: {
noneSelected: {
type: String,
required: true,
},
......@@ -46,8 +57,23 @@ export default {
searchValue: '',
};
},
watch: {
selectedId(val) {
if (val) {
this.$refs.dropdown.hide(true);
}
},
},
methods: {
...mapActions(['setAddColumnFormVisibility']),
setFocus() {
this.$refs.searchBox.focusInput();
},
onHide() {
this.searchValue = '';
this.$emit('filter-items', '');
this.$emit('hide');
},
},
};
</script>
......@@ -62,51 +88,64 @@ export default {
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"
class="gl-font-size-h2 gl-px-5 gl-py-4 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>
<div
class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-y-auto gl-align-items-flex-start"
>
<div class="gl-px-5">
<h3 class="gl-font-lg gl-mt-5 gl-mb-2">
{{ $options.i18n.scope }}
</h3>
<p class="gl-mb-3">{{ $options.i18n.scopeDescription }}</p>
</div>
<p class="gl-px-5">{{ formDescription }}</p>
<slot name="select-list-type"></slot>
<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-px-5 lg-mb-3 gl-max-w-full" :label="searchLabel">
<gl-dropdown
ref="dropdown"
class="gl-mb-3 gl-max-w-full"
toggle-class="gl-max-w-full gl-display-flex gl-align-items-center gl-text-trunate"
boundary="viewport"
@shown="setFocus"
@hide="onHide"
>
<template #button-content>
<slot name="selected">
<div>{{ noneSelected }}</div>
</slot>
<gl-icon class="dropdown-chevron gl-flex-shrink-0" name="chevron-down" />
</template>
<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"
v-model="searchValue"
debounce="250"
:placeholder="searchPlaceholder"
@input="$emit('filter-items', $event)"
/>
</gl-form-group>
<template #header>
<gl-search-box-by-type
ref="searchBox"
v-model="searchValue"
debounce="250"
class="gl-mt-0!"
:placeholder="searchPlaceholder"
@input="$emit('filter-items', $event)"
/>
</template>
<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>
<div v-if="loading" class="gl-px-5">
<gl-skeleton-loader :width="400" :height="172">
<rect width="380" height="20" x="10" y="15" rx="4" />
<rect width="280" height="20" x="10" y="50" rx="4" />
<rect width="330" height="20" x="10" y="85" rx="4" />
</gl-skeleton-loader>
</div>
<slot v-else name="items">
<p class="gl-mx-5">{{ $options.i18n.noResults }}</p>
</slot>
<slot v-else name="items">
<p class="gl-mx-5">{{ $options.i18n.noResults }}</p>
</slot>
</gl-dropdown>
</gl-form-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-rounded-bottom-left-base gl-rounded-bottom-right-base"
......
......@@ -13,9 +13,9 @@ export default {
</script>
<template>
<span class="gl-ml-3 gl-display-flex gl-align-items-center" data-testid="boards-create-list">
<div class="gl-ml-3 gl-display-flex gl-align-items-center" data-testid="boards-create-list">
<gl-button variant="confirm" @click="setAddColumnFormVisibility(true)"
>{{ __('Create list') }}
</gl-button>
</span>
</div>
</template>
......@@ -134,9 +134,10 @@ export default {
e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
const toBoardType = containerEl.dataset.boardType;
const cloneActions = {
label: ['milestone', 'assignee'],
assignee: ['milestone', 'label'],
milestone: ['label', 'assignee'],
label: ['milestone', 'assignee', 'iteration'],
assignee: ['milestone', 'label', 'iteration'],
milestone: ['label', 'assignee', 'iteration'],
iteration: ['label', 'assignee', 'milestone'],
};
if (toBoardType) {
......
<script>
import {
GlAvatar,
GlAvatarLabeled,
GlIcon,
GlFormGroup,
GlFormRadio,
GlFormRadioGroup,
GlFormSelect,
GlLabel,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
......@@ -21,48 +21,44 @@ export const listTypeInfo = {
listPropertyName: 'labels',
loadingPropertyName: 'labelsLoading',
fetchMethodName: 'fetchLabels',
formDescription: __('A label list displays issues with the selected label.'),
searchLabel: __('Select label'),
noneSelected: __('Select a label'),
searchPlaceholder: __('Search labels'),
},
[ListType.assignee]: {
listPropertyName: 'assignees',
loadingPropertyName: 'assigneesLoading',
fetchMethodName: 'fetchAssignees',
formDescription: __('An assignee list displays issues assigned to the selected user'),
searchLabel: __('Select assignee'),
noneSelected: __('Select an assignee'),
searchPlaceholder: __('Search assignees'),
},
[ListType.milestone]: {
listPropertyName: 'milestones',
loadingPropertyName: 'milestonesLoading',
fetchMethodName: 'fetchMilestones',
formDescription: __('A milestone list displays issues in the selected milestone.'),
searchLabel: __('Select milestone'),
noneSelected: __('Select a milestone'),
searchPlaceholder: __('Search milestones'),
},
[ListType.iteration]: {
listPropertyName: 'iterations',
loadingPropertyName: 'iterationsLoading',
fetchMethodName: 'fetchIterations',
formDescription: __('An iteration list displays issues in the selected iteration.'),
searchLabel: __('Select iteration'),
noneSelected: __('Select an iteration'),
searchPlaceholder: __('Search iterations'),
},
};
export default {
i18n: {
listType: __('List type'),
value: __('Value'),
},
components: {
BoardAddNewColumnForm,
GlAvatar,
GlAvatarLabeled,
GlIcon,
GlFormGroup,
GlFormRadio,
GlFormRadioGroup,
GlFormSelect,
GlLabel,
},
directives: {
GlTooltip,
......@@ -100,6 +96,10 @@ export default {
return this[this.info.listPropertyName] || [];
},
hasItems() {
return this.items.length > 0;
},
labelTypeSelected() {
return this.columnType === ListType.label;
},
......@@ -131,14 +131,20 @@ export default {
},
columnForSelected() {
if (!this.columnType) {
if (!this.columnType || !this.selectedId) {
return false;
}
const key = `${this.columnType}Id`;
return this.getListByTypeId({
[key]: this.selectedId,
});
if (this.shouldUseGraphQL || this.isEpicBoard) {
const key = `${this.columnType}Id`;
return this.getListByTypeId({
[key]: this.selectedId,
});
}
return boardsStore.state.lists.find(
(list) => list[this.columnType]?.id === getIdFromGraphQLId(this.selectedId),
);
},
loading() {
......@@ -163,6 +169,10 @@ export default {
return types;
},
searchLabel() {
return this.showListTypeSelector ? this.$options.i18n.value : null;
},
showListTypeSelector() {
return !this.isEpicBoard && this.columnTypes.length > 1;
},
......@@ -254,6 +264,10 @@ export default {
this.selectedId = null;
this.filterItems();
},
hideDropdown() {
this.$root.$emit('bv::dropdown::hide');
},
},
};
</script>
......@@ -261,68 +275,82 @@ export default {
<template>
<board-add-new-column-form
:loading="loading"
:form-description="info.formDescription"
:search-label="info.searchLabel"
:none-selected="info.noneSelected"
:search-label="searchLabel"
:search-placeholder="info.searchPlaceholder"
:selected-id="selectedId"
@filter-items="filterItems"
@add-list="addList"
>
<template slot="select-list-type">
<template #select-list-type>
<gl-form-group
v-if="showListTypeSelector"
:label="$options.i18n.listType"
class="gl-px-5 gl-py-0 gl-mt-5"
:description="$options.i18n.scopeDescription"
class="gl-px-5 gl-py-0 gl-mb-3"
label-for="list-type"
>
<gl-form-select
id="list-type"
v-model="columnType"
:options="columnTypes"
@change="setColumnType"
/>
<gl-form-radio-group v-model="columnType">
<gl-form-radio
v-for="{ text, value } in columnTypes"
:key="value"
:value="value"
class="gl-mb-0 gl-align-self-center"
@change="setColumnType"
>
{{ text }}
</gl-form-radio>
</gl-form-radio-group>
</gl-form-group>
</template>
<template slot="selected">
<div v-if="hasLabelSelection">
<gl-label
v-gl-tooltip
:title="selectedItem.title"
:description="selectedItem.description"
:background-color="selectedItem.color"
:scoped="showScopedLabels(selectedItem)"
/>
</div>
<div v-else-if="hasAssigneeSelection">
<gl-avatar-labeled
:size="32"
:label="selectedItem.name"
:sub-label="selectedItem.username"
:src="selectedItem.avatarUrl"
/>
</div>
<div v-else-if="hasMilestoneSelection || hasIterationSelection" class="gl-text-truncate">
{{ selectedItem.title }}
</div>
<template #selected>
<template v-if="hasLabelSelection">
<span
class="dropdown-label-box gl-top-0 gl-flex-shrink-0"
:style="{
backgroundColor: selectedItem.color,
}"
></span>
<div class="gl-text-truncate">{{ selectedItem.title }}</div>
</template>
<template v-else-if="hasMilestoneSelection">
<gl-icon class="gl-flex-shrink-0" name="clock" />
<span class="gl-text-truncate">{{ selectedItem.title }}</span>
</template>
<template v-else-if="hasIterationSelection">
<gl-icon class="gl-flex-shrink-0" name="iteration" />
<span class="gl-text-truncate">{{ selectedItem.title }}</span>
</template>
<template v-else-if="hasAssigneeSelection">
<gl-avatar class="gl-mr-2 gl-flex-shrink-0" :size="16" :src="selectedItem.avatarUrl" />
<div class="gl-text-truncate">
<b class="gl-mr-2">{{ selectedItem.name }}</b>
<span class="gl-text-gray-700">@{{ selectedItem.username }}</span>
</div>
</template>
</template>
<template slot="items">
<template v-if="hasItems" #items>
<gl-form-radio-group
v-if="items.length > 0"
v-model="selectedId"
class="gl-overflow-y-auto gl-px-5 gl-pt-3"
class="gl-overflow-y-auto gl-px-5"
@change="hideDropdown"
>
<label
v-for="item in items"
:key="item.id"
class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
class="gl-display-flex gl-font-weight-normal gl-overflow-break-word gl-py-3 gl-mb-0"
>
<gl-form-radio :value="item.id" class="gl-mb-0 gl-align-self-center" />
<gl-form-radio
:value="item.id"
:class="assigneeTypeSelected ? 'gl-align-self-center' : ''"
/>
<span
v-if="labelTypeSelected"
class="dropdown-label-box gl-top-0"
class="dropdown-label-box gl-top-0 gl-flex-shrink-0"
:style="{
backgroundColor: item.color,
}"
......@@ -330,14 +358,17 @@ export default {
<gl-avatar-labeled
v-if="assigneeTypeSelected"
class="gl-display-flex gl-align-items-center"
:size="32"
:label="item.name"
:sub-label="item.username"
:sub-label="`@${item.username}`"
:src="item.avatarUrl"
/>
<span v-else>{{ item.title }}</span>
</label>
</gl-form-radio-group>
<div class="dropdown-content-faded-mask gl-fixed gl-bottom-0 gl-w-full"></div>
</template>
</board-add-new-column-form>
</template>
......@@ -9,7 +9,7 @@ export default {
return Boolean(gon?.licensed_features?.swimlanes && state.isShowingEpicsSwimlanes);
},
getListByTypeId: (state) => ({ assigneeId, labelId, milestoneId }) => {
getListByTypeId: (state) => ({ assigneeId, labelId, milestoneId, iterationId }) => {
if (assigneeId) {
return find(
state.boardLists,
......@@ -31,6 +31,13 @@ export default {
);
}
if (iterationId) {
return find(
state.boardLists,
(l) => l.listType === ListType.iteration && l.iteration?.id === iterationId,
);
}
return null;
},
......
......@@ -74,6 +74,9 @@ export default () => {
? parseInt($boardApp.dataset.boardWeight, 10)
: null,
scopedLabelsAvailable: parseBoolean($boardApp.dataset.scopedLabels),
milestoneListsAvailable: false,
assigneeListsAvailable: false,
iterationListsAvailable: false,
},
store,
apolloProvider,
......
......@@ -37,6 +37,14 @@
}
}
.board-add-new-list .gl-new-dropdown-inner {
max-height: 12rem !important;
.gl-form-radio {
min-height: 1em;
}
}
.tab-pane-labels {
.dropdown-page-one .dropdown-content {
height: 140px;
......
......@@ -98,7 +98,7 @@ RSpec.describe 'User adds milestone lists', :js do
wait_for_all_requests
end
it 'does not show other list types' do
it 'does not show other list types', :aggregate_failures do
click_button 'Create list'
wait_for_all_requests
......@@ -106,7 +106,6 @@ RSpec.describe 'User adds milestone lists', :js do
expect(page).not_to have_text('Iteration')
expect(page).not_to have_text('Assignee')
expect(page).not_to have_text('Milestone')
expect(page).not_to have_text('List type')
end
end
end
......@@ -115,13 +114,16 @@ RSpec.describe 'User adds milestone lists', :js do
click_button 'Create list'
wait_for_all_requests
select(list_type, from: 'List type')
page.choose(list_type)
page.within('.board-add-new-list') do
find_button("Select a").click
page.within('.dropdown-menu') do
find('label', text: title).click
click_button 'Add'
end
click_button 'Add to board'
wait_for_all_requests
end
end
......@@ -73,11 +73,14 @@ RSpec.describe 'epic boards', :js do
click_button 'Create list'
wait_for_all_requests
page.within("[data-testid='board-add-new-column']") do
click_button 'Select a label'
page.within(".dropdown-menu") do
find('label', text: label2.title).click
click_button 'Add'
end
click_button 'Add to board'
wait_for_all_requests
expect(page).to have_selector('.board', text: label2.title)
......
import { GlAvatarLabeled, GlSearchBoxByType, GlFormRadio, GlFormSelect } from '@gitlab/ui';
import { GlAvatarLabeled, GlDropdown, GlFormRadio, GlFormRadioGroup } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
......@@ -40,6 +40,8 @@ describe('BoardAddNewColumn', () => {
shallowMount(BoardAddNewColumn, {
stubs: {
BoardAddNewColumnForm,
GlFormRadio,
GlFormRadioGroup,
},
data() {
return {
......@@ -78,27 +80,24 @@ describe('BoardAddNewColumn', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findForm = () => wrapper.findComponent(BoardAddNewColumnForm);
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');
const listTypeSelect = () => wrapper.findComponent(GlFormSelect);
const listTypeSelect = (type) => {
const radio = wrapper
.findAllComponents(GlFormRadio)
.filter((r) => r.attributes('value') === type)
.at(0);
radio.element.value = type;
radio.vm.$emit('change', type);
};
beforeEach(() => {
shouldUseGraphQL = true;
});
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({
......@@ -174,15 +173,15 @@ describe('BoardAddNewColumn', () => {
},
});
listTypeSelect().vm.$emit('change', ListType.assignee);
listTypeSelect(ListType.assignee);
await nextTick();
});
it('sets assignee placeholder text in form', async () => {
expect(findForm().props()).toMatchObject({
formDescription: listTypeInfo.assignee.formDescription,
searchLabel: listTypeInfo.assignee.searchLabel,
noneSelected: listTypeInfo.assignee.noneSelected,
searchLabel: BoardAddNewColumn.i18n.value,
searchPlaceholder: listTypeInfo.assignee.searchPlaceholder,
});
});
......@@ -195,7 +194,7 @@ describe('BoardAddNewColumn', () => {
expect(userList).toHaveLength(mockAssignees.length);
expect(userList.at(0).props()).toMatchObject({
label: firstUser.name,
subLabel: firstUser.username,
subLabel: `@${firstUser.username}`,
});
});
});
......@@ -209,21 +208,20 @@ describe('BoardAddNewColumn', () => {
},
});
listTypeSelect().vm.$emit('change', ListType.iteration);
listTypeSelect(ListType.iteration);
await nextTick();
});
it('sets iteration placeholder text in form', async () => {
it('sets iteration placeholder text in form', () => {
expect(findForm().props()).toMatchObject({
formDescription: listTypeInfo.iteration.formDescription,
searchLabel: listTypeInfo.iteration.searchLabel,
searchLabel: BoardAddNewColumn.i18n.value,
searchPlaceholder: listTypeInfo.iteration.searchPlaceholder,
});
});
it('shows list of iterations', () => {
const itemList = wrapper.findAllComponents(GlFormRadio);
const itemList = wrapper.findComponent(GlDropdown).findAllComponents(GlFormRadio);
expect(itemList).toHaveLength(mockIterations.length);
expect(itemList.at(0).attributes('value')).toBe(mockIterations[0].id);
......
......@@ -1305,7 +1305,7 @@ describe('fetchIterations', () => {
});
}
it('sets iterationsLoading to true', async () => {
it('sets iterationsLoading to true', () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const store = createStore();
......
......@@ -1365,9 +1365,6 @@ 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 issues with the selected label."
msgstr ""
msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies."
msgstr ""
......@@ -1386,9 +1383,6 @@ msgstr ""
msgid "A merge request hasn't yet been merged"
msgstr ""
msgid "A milestone list displays issues in the selected milestone."
msgstr ""
msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
msgstr ""
......@@ -3262,9 +3256,6 @@ msgstr ""
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
msgstr ""
msgid "An assignee list displays issues assigned to the selected user"
msgstr ""
msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
msgstr ""
......@@ -3652,9 +3643,6 @@ msgstr ""
msgid "An issue title is required"
msgstr ""
msgid "An iteration list displays issues in the selected iteration."
msgstr ""
msgid "An unauthenticated user"
msgstr ""
......@@ -17206,6 +17194,9 @@ msgstr ""
msgid "Issues closed"
msgstr ""
msgid "Issues must match this scope to appear in this list."
msgstr ""
msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
msgstr ""
......@@ -18422,9 +18413,6 @@ msgstr ""
msgid "List the merge requests that must be merged before this one."
msgstr ""
msgid "List type"
msgstr ""
msgid "List view"
msgstr ""
......@@ -27304,6 +27292,9 @@ msgstr ""
msgid "Select a label"
msgstr ""
msgid "Select a milestone"
msgstr ""
msgid "Select a new namespace"
msgstr ""
......@@ -27331,9 +27322,15 @@ msgstr ""
msgid "Select all"
msgstr ""
msgid "Select an assignee"
msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one."
msgstr ""
msgid "Select an iteration"
msgstr ""
msgid "Select assignee"
msgstr ""
......
......@@ -71,10 +71,13 @@ RSpec.describe 'User adds lists', :js do
def select_label(board_new_list_enabled, label)
if board_new_list_enabled
page.within('.board-add-new-list') do
find('label', text: label.title).click
click_button 'Add'
end
click_button 'Select a label'
find('label', text: label.title).click
click_button 'Add to board'
wait_for_all_requests
else
page.within('.dropdown-menu-issues-board-new') do
click_link label.title
......
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([[]]);
......
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