Skip to content
Snippets Groups Projects
Commit 5c42ea6b authored by Jacques Erasmus's avatar Jacques Erasmus :speech_balloon:
Browse files

Merge branch '413500-milestone-combobox' into 'master'

Migrates the milestone combobox to GlListbox

See merge request !134120



Merged-by: default avatarJacques Erasmus <jerasmus@gitlab.com>
Approved-by: default avatarDan Mizzi-Harris <dmizzi-harris@gitlab.com>
Approved-by: default avatarJacques Erasmus <jerasmus@gitlab.com>
Approved-by: default avatarMireya Andres <mandres@gitlab.com>
Reviewed-by: Sam Beckham's avatarSam Beckham <sbeckham@gitlab.com>
Reviewed-by: default avatarMireya Andres <mandres@gitlab.com>
Reviewed-by: default avatarDan Mizzi-Harris <dmizzi-harris@gitlab.com>
Co-authored-by: Sam Beckham's avatarSam Beckham <sbeckham@gitlab.com>
parents 1186b7bb 9417a78a
No related branches found
No related tags found
1 merge request!134120Migrates the milestone combobox to GlListbox
Pipeline #1050146065 passed
<script>
import {
GlDropdown,
GlDropdownDivider,
GlDropdownSectionHeader,
GlDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlIcon,
} from '@gitlab/ui';
import { GlBadge, GlButton, GlCollapsibleListbox } from '@gitlab/ui';
import { debounce, isEqual } from 'lodash';
// eslint-disable-next-line no-restricted-imports
import { mapActions, mapGetters, mapState } from 'vuex';
import { s__, __, sprintf } from '~/locale';
import createStore from '../stores';
import MilestoneResultsSection from './milestone_results_section.vue';
const SEARCH_DEBOUNCE_MS = 250;
......@@ -21,14 +12,9 @@ export default {
name: 'MilestoneCombobox',
store: createStore(),
components: {
GlDropdown,
GlDropdownDivider,
GlDropdownSectionHeader,
GlDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlIcon,
MilestoneResultsSection,
GlCollapsibleListbox,
GlBadge,
GlButton,
},
props: {
value: {
......@@ -56,27 +42,43 @@ export default {
required: false,
},
},
data() {
return {
searchQuery: '',
};
},
translations: {
milestone: s__('MilestoneCombobox|Milestone'),
selectMilestone: s__('MilestoneCombobox|Select milestone'),
noMilestone: s__('MilestoneCombobox|No milestone'),
noResultsLabel: s__('MilestoneCombobox|No matching results'),
searchMilestones: s__('MilestoneCombobox|Search Milestones'),
searchErrorMessage: s__('MilestoneCombobox|An error occurred while searching for milestones'),
projectMilestones: s__('MilestoneCombobox|Project milestones'),
groupMilestones: s__('MilestoneCombobox|Group milestones'),
unselect: __('Unselect'),
},
computed: {
...mapState(['matches', 'selectedMilestones']),
...mapGetters(['isLoading', 'groupMilestonesEnabled']),
...mapGetters(['isLoading']),
allMilestones() {
const { groupMilestones, projectMilestones } = this.matches || {};
const milestones = [];
if (projectMilestones?.totalCount) {
milestones.push({
id: 'project-milestones',
text: this.$options.translations.projectMilestones,
options: projectMilestones.list,
totalCount: projectMilestones.totalCount,
});
}
if (groupMilestones?.totalCount) {
milestones.push({
id: 'group-milestones',
text: this.$options.translations.groupMilestones,
options: groupMilestones.list,
totalCount: groupMilestones.totalCount,
});
}
return milestones;
},
selectedMilestonesLabel() {
const { selectedMilestones } = this;
const firstMilestoneName = selectedMilestones[0];
const [firstMilestoneName] = selectedMilestones;
if (selectedMilestones.length === 0) {
return this.$options.translations.noMilestone;
......@@ -92,20 +94,6 @@ export default {
numberOfOtherMilestones,
});
},
showProjectMilestoneSection() {
return Boolean(
this.matches.projectMilestones.totalCount > 0 || this.matches.projectMilestones.error,
);
},
showGroupMilestoneSection() {
return (
this.groupMilestonesEnabled &&
Boolean(this.matches.groupMilestones.totalCount > 0 || this.matches.groupMilestones.error)
);
},
showNoResults() {
return !this.showProjectMilestoneSection && !this.showGroupMilestoneSection;
},
},
watch: {
// Keep the Vuex store synchronized if the parent
......@@ -127,8 +115,8 @@ export default {
// because we need to access the .cancel() method
// lodash attaches to the function, which is
// made inaccessible by Vue.
this.debouncedSearch = debounce(function search() {
this.search(this.searchQuery);
this.debouncedSearch = debounce(function search(q) {
this.search(q);
}, SEARCH_DEBOUNCE_MS);
this.setProjectId(this.projectId);
......@@ -143,22 +131,14 @@ export default {
'setGroupMilestonesAvailable',
'setSelectedMilestones',
'clearSelectedMilestones',
'toggleMilestones',
'search',
'fetchMilestones',
]),
focusSearchBox() {
this.$refs.searchBox.$el.querySelector('input').focus();
},
onSearchBoxEnter() {
this.debouncedSearch.cancel();
this.search(this.searchQuery);
onSearchBoxInput(q) {
this.debouncedSearch(q);
},
onSearchBoxInput() {
this.debouncedSearch();
},
selectMilestone(milestone) {
this.toggleMilestones(milestone);
selectMilestone(milestones) {
this.setSelectedMilestones(milestones);
this.$emit('input', this.selectedMilestones);
},
selectNoMilestone() {
......@@ -170,84 +150,42 @@ export default {
</script>
<template>
<gl-dropdown v-bind="$attrs" class="milestone-combobox" @shown="focusSearchBox">
<template #button-content>
<span data-testid="milestone-combobox-button-content" class="gl-flex-grow-1 text-muted">{{
selectedMilestonesLabel
}}</span>
<gl-icon name="chevron-down" />
</template>
<gl-dropdown-section-header>
<span class="text-center d-block">{{ $options.translations.selectMilestone }}</span>
</gl-dropdown-section-header>
<gl-dropdown-divider />
<gl-search-box-by-type
ref="searchBox"
v-model.trim="searchQuery"
class="gl-m-3"
:placeholder="$options.translations.searchMilestones"
@input="onSearchBoxInput"
@keydown.enter.prevent="onSearchBoxEnter"
/>
<gl-dropdown-item
:is-checked="selectedMilestones.length === 0"
is-check-item
@click="selectNoMilestone()"
>
{{ $options.translations.noMilestone }}
</gl-dropdown-item>
<gl-dropdown-divider />
<template v-if="isLoading">
<gl-loading-icon size="sm" />
<gl-dropdown-divider />
<gl-collapsible-listbox
:header-text="$options.translations.selectMilestone"
:items="allMilestones"
:reset-button-label="$options.translations.unselect"
:searching="isLoading"
:selected="selectedMilestones"
:toggle-text="selectedMilestonesLabel"
block
multiple
searchable
@reset="selectNoMilestone"
@search="onSearchBoxInput"
@select="selectMilestone"
>
<template #group-label="{ group }">
<span :data-testid="`${group.id}-section`"
>{{ group.text }}<gl-badge size="sm" class="gl-ml-2">{{ group.totalCount }}</gl-badge></span
>
</template>
<template v-else-if="showNoResults">
<div class="dropdown-item-space">
<span data-testid="milestone-combobox-no-results" class="gl-pl-6">{{
$options.translations.noResultsLabel
}}</span>
<template #footer>
<div
class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-200 gl-display-flex gl-flex-direction-column gl-p-2! gl-pt-0!"
>
<gl-button
v-for="(item, idx) in extraLinks"
:key="idx"
:href="item.url"
is-check-item
data-testid="milestone-combobox-extra-links"
category="tertiary"
block
class="gl-justify-content-start! gl-mt-2!"
>
{{ item.text }}
</gl-button>
</div>
<gl-dropdown-divider />
</template>
<template v-else>
<milestone-results-section
v-if="showProjectMilestoneSection"
:section-title="$options.translations.projectMilestones"
:total-count="matches.projectMilestones.totalCount"
:items="matches.projectMilestones.list"
:selected-milestones="selectedMilestones"
:error="matches.projectMilestones.error"
:error-message="$options.translations.searchErrorMessage"
data-testid="project-milestones-section"
@selected="selectMilestone($event)"
/>
<milestone-results-section
v-if="showGroupMilestoneSection"
:section-title="$options.translations.groupMilestones"
:total-count="matches.groupMilestones.totalCount"
:items="matches.groupMilestones.list"
:selected-milestones="selectedMilestones"
:error="matches.groupMilestones.error"
:error-message="$options.translations.searchErrorMessage"
data-testid="group-milestones-section"
@selected="selectMilestone($event)"
/>
</template>
<gl-dropdown-item
v-for="(item, idx) in extraLinks"
:key="idx"
:href="item.url"
is-check-item
data-testid="milestone-combobox-extra-links"
>
{{ item.text }}
</gl-dropdown-item>
</gl-dropdown>
</gl-collapsible-listbox>
</template>
......@@ -37,7 +37,7 @@ export default {
},
[types.RECEIVE_PROJECT_MILESTONES_SUCCESS](state, response) {
state.matches.projectMilestones = {
list: response.data.map(({ title }) => ({ title })),
list: response.data.map(({ title }) => ({ text: title, value: title })),
totalCount: parseInt(response.headers['x-total'], 10) || response.data.length,
error: null,
};
......@@ -51,7 +51,7 @@ export default {
},
[types.RECEIVE_GROUP_MILESTONES_SUCCESS](state, response) {
state.matches.groupMilestones = {
list: response.data.map(({ title }) => ({ title })),
list: response.data.map(({ title }) => ({ text: title, value: title })),
totalCount: parseInt(response.headers['x-total'], 10) || response.data.length,
error: null,
};
......
......@@ -176,8 +176,7 @@ export default {
</p>
<form v-if="showForm" class="js-quick-submit" @submit.prevent="submitForm">
<tag-field />
<gl-form-group>
<label for="release-title">{{ __('Release title') }}</label>
<gl-form-group :label="__('Release title')">
<gl-form-input
id="release-title"
ref="releaseTitleInput"
......@@ -186,17 +185,14 @@ export default {
class="form-control"
/>
</gl-form-group>
<gl-form-group class="w-50" data-testid="milestones-field">
<label>{{ __('Milestones') }}</label>
<div class="d-flex flex-column col-md-6 col-sm-10 pl-0">
<milestone-combobox
v-model="releaseMilestones"
:project-id="projectId"
:group-id="groupId"
:group-milestones-available="groupMilestonesAvailable"
:extra-links="milestoneComboboxExtraLinks"
/>
</div>
<gl-form-group :label="__('Milestones')" class="gl-w-30" data-testid="milestones-field">
<milestone-combobox
v-model="releaseMilestones"
:project-id="projectId"
:group-id="groupId"
:group-milestones-available="groupMilestonesAvailable"
:extra-links="milestoneComboboxExtraLinks"
/>
</gl-form-group>
<gl-form-group :label="__('Release date')" label-for="release-released-at">
<template #label-description>
......@@ -214,8 +210,7 @@ export default {
</template>
<gl-datepicker id="release-released-at" v-model="releasedAt" :default-date="releasedAt" />
</gl-form-group>
<gl-form-group data-testid="release-notes">
<label for="release-notes">{{ __('Release notes') }}</label>
<gl-form-group :label="__('Release notes')" data-testid="release-notes">
<div class="common-note-form">
<markdown-field
:can-attach-file="true"
......
......@@ -30142,27 +30142,15 @@ msgstr ""
msgid "Milestone(s) not found: %{milestones}"
msgstr ""
 
msgid "MilestoneCombobox|An error occurred while searching for milestones"
msgstr ""
msgid "MilestoneCombobox|Group milestones"
msgstr ""
 
msgid "MilestoneCombobox|Milestone"
msgstr ""
msgid "MilestoneCombobox|No matching results"
msgstr ""
msgid "MilestoneCombobox|No milestone"
msgstr ""
 
msgid "MilestoneCombobox|Project milestones"
msgstr ""
 
msgid "MilestoneCombobox|Search Milestones"
msgstr ""
msgid "MilestoneCombobox|Select milestone"
msgstr ""
 
......@@ -50856,6 +50844,9 @@ msgstr ""
msgid "Unschedule job"
msgstr ""
 
msgid "Unselect"
msgstr ""
msgid "Unselect \"Expand variable reference\" if you want to use the variable value as a raw string."
msgstr ""
 
......@@ -97,8 +97,8 @@
context 'Vue.js markdown editor' do
let(:path_to_visit) { new_project_release_path(project) }
let(:markdown_field) { find_field('Release notes') }
let(:non_markdown_field) { find_field('Release title') }
let(:markdown_field) { find_field('release-notes') }
let(:non_markdown_field) { find_field('release-title') }
it_behaves_like 'keyboard shortcuts'
it_behaves_like 'no side effects'
......
......@@ -148,8 +148,7 @@ def fill_out_form_and_submit
fill_release_title(release_title)
select_milestone(milestone_1.title)
select_milestone(milestone_2.title)
select_milestones(milestone_1.title, milestone_2.title)
fill_release_notes(release_notes)
......
......@@ -20,8 +20,8 @@
end
def fill_out_form_and_click(button_to_click)
fill_in 'Release title', with: 'Updated Release title'
fill_in 'Release notes', with: 'Updated Release notes'
fill_in 'release-title', with: 'Updated Release title', fill_options: { clear: :backspace }
fill_in 'release-notes', with: 'Updated Release notes'
click_link_or_button button_to_click
......@@ -44,8 +44,8 @@ def fill_out_form_and_click(button_to_click)
expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example 1.0.0, 2.1.0-pre.')
expect(find_field('Tag name', disabled: true).value).to eq(release.tag)
expect(find_field('Release title').value).to eq(release.name)
expect(find_field('Release notes').value).to eq(release.description)
expect(find_field('release-title').value).to eq(release.name)
expect(find_field('release-notes').value).to eq(release.description)
expect(page).to have_button('Save changes')
expect(page).to have_link('Cancel')
......
import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { GlLoadingIcon, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { ENTER_KEY } from '~/lib/utils/keys';
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
import createStore from '~/milestones/stores/';
import { projectMilestones, groupMilestones } from '../mock_data';
......@@ -52,9 +51,6 @@ describe('Milestone combobox component', () => {
wrapper.setProps({ value: selectedMilestone });
},
},
stubs: {
GlSearchBoxByType: true,
},
store: createStore(),
});
};
......@@ -89,57 +85,25 @@ describe('Milestone combobox component', () => {
//
// Finders
//
const findButtonContent = () => wrapper.find('[data-testid="milestone-combobox-button-content"]');
const findNoResults = () => wrapper.find('[data-testid="milestone-combobox-no-results"]');
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findButtonContent = () => wrapper.find('[data-testid="base-dropdown-toggle"]');
const findNoResults = () => wrapper.find('[data-testid="listbox-no-results-text"]');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findProjectMilestonesSection = () =>
wrapper.find('[data-testid="project-milestones-section"]');
const findProjectMilestonesDropdownItems = () =>
findProjectMilestonesSection().findAllComponents(GlDropdownItem);
const findFirstProjectMilestonesDropdownItem = () => findProjectMilestonesDropdownItems().at(0);
const findGroupMilestonesSection = () => wrapper.find('[data-testid="group-milestones-section"]');
const findGroupMilestonesDropdownItems = () =>
findGroupMilestonesSection().findAllComponents(GlDropdownItem);
const findFirstGroupMilestonesDropdownItem = () => findGroupMilestonesDropdownItems().at(0);
//
// Expecters
//
const projectMilestoneSectionContainsErrorMessage = () => {
const projectMilestoneSection = findProjectMilestonesSection();
return projectMilestoneSection
.text()
.includes('An error occurred while searching for milestones');
};
const groupMilestoneSectionContainsErrorMessage = () => {
const groupMilestoneSection = findGroupMilestonesSection();
return groupMilestoneSection
.text()
.includes('An error occurred while searching for milestones');
};
findGlCollapsibleListbox().find('[data-testid="project-milestones-section"]');
const findGroupMilestonesSection = () =>
findGlCollapsibleListbox().find('[data-testid="group-milestones-section"]');
const findDropdownItems = () => findGlCollapsibleListbox().findAllComponents(GlListboxItem);
//
// Convenience methods
//
const updateQuery = (newQuery) => {
findSearchBox().vm.$emit('input', newQuery);
findGlCollapsibleListbox().vm.$emit('search', newQuery);
};
const selectFirstProjectMilestone = () => {
findFirstProjectMilestonesDropdownItem().vm.$emit('click');
};
const selectFirstGroupMilestone = () => {
findFirstGroupMilestonesDropdownItem().vm.$emit('click');
const selectItem = (item) => {
findGlCollapsibleListbox().vm.$emit('select', item);
};
const waitForRequests = async ({ andClearMocks } = { andClearMocks: false }) => {
......@@ -224,22 +188,6 @@ describe('Milestone combobox component', () => {
});
});
describe('when the Enter is pressed', () => {
beforeEach(() => {
createComponent();
return waitForRequests({ andClearMocks: true });
});
it('requeries the search when Enter is pressed', () => {
findSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
return waitForRequests().then(() => {
expect(searchApiCallSpy).toHaveBeenCalledTimes(1);
});
});
});
describe('when no results are found', () => {
beforeEach(() => {
projectMilestonesApiCallSpy = jest
......@@ -257,7 +205,7 @@ describe('Milestone combobox component', () => {
describe('when the search query is empty', () => {
it('renders a "no results" message', () => {
expect(findNoResults().text()).toBe('No matching results');
expect(findNoResults().text()).toBe('No results found');
});
});
});
......@@ -275,22 +223,14 @@ describe('Milestone combobox component', () => {
});
it('renders the "Project milestones" heading with a total number indicator', () => {
expect(
findProjectMilestonesSection()
.find('[data-testid="milestone-results-section-header"]')
.text(),
).toBe('Project milestones 6');
});
it("does not render an error message in the project milestone section's body", () => {
expect(projectMilestoneSectionContainsErrorMessage()).toBe(false);
expect(findProjectMilestonesSection().text()).toBe('Project milestones 6');
});
it('renders each project milestones as a selectable item', () => {
const dropdownItems = findProjectMilestonesDropdownItems();
const dropdownItems = findDropdownItems();
projectMilestones.forEach((milestone, i) => {
expect(dropdownItems.at(i).text()).toBe(milestone.title);
projectMilestones.forEach((milestone) => {
expect(dropdownItems.filter((x) => x.text() === milestone.title).exists()).toBe(true);
});
});
});
......@@ -323,12 +263,8 @@ describe('Milestone combobox component', () => {
return waitForRequests();
});
it('renders the project milestones section in the dropdown', () => {
expect(findProjectMilestonesSection().exists()).toBe(true);
});
it("renders an error message in the project milestones section's body", () => {
expect(projectMilestoneSectionContainsErrorMessage()).toBe(true);
it('does not render the project milestones section in the dropdown', () => {
expect(findProjectMilestonesSection().exists()).toBe(false);
});
});
......@@ -339,52 +275,24 @@ describe('Milestone combobox component', () => {
return waitForRequests();
});
it('renders a checkmark by the selected item', async () => {
selectFirstProjectMilestone();
await nextTick();
expect(
findFirstProjectMilestonesDropdownItem()
.find('svg')
.classes('gl-dropdown-item-check-icon'),
).toBe(true);
selectFirstProjectMilestone();
await nextTick();
expect(
findFirstProjectMilestonesDropdownItem().find('svg').classes('gl-visibility-hidden'),
).toBe(true);
});
describe('when a project milestone is selected', () => {
const item = 'v1.0';
describe('when a project milestones is selected', () => {
beforeEach(() => {
createComponent();
projectMilestonesApiCallSpy = jest
.fn()
.mockReturnValue([HTTP_STATUS_OK, [{ title: 'v1.0' }], { [X_TOTAL_HEADER]: '1' }]);
selectItem([item]);
return waitForRequests();
});
it("displays the project milestones name in the dropdown's button", async () => {
selectFirstProjectMilestone();
await nextTick();
expect(findButtonContent().text()).toBe('v1.0');
selectFirstProjectMilestone();
await nextTick();
expect(findButtonContent().text()).toBe('No milestone');
it("displays the project milestones name in the dropdown's button", () => {
expect(findButtonContent().text()).toBe(item);
});
it('updates the v-model binding with the project milestone title', async () => {
selectFirstProjectMilestone();
await nextTick();
it('updates the v-model binding with the project milestone title', () => {
expect(wrapper.emitted().input[0][0]).toStrictEqual(['v1.0']);
});
});
......@@ -404,22 +312,14 @@ describe('Milestone combobox component', () => {
});
it('renders the "Group milestones" heading with a total number indicator', () => {
expect(
findGroupMilestonesSection()
.find('[data-testid="milestone-results-section-header"]')
.text(),
).toBe('Group milestones 6');
});
it("does not render an error message in the group milestone section's body", () => {
expect(groupMilestoneSectionContainsErrorMessage()).toBe(false);
expect(findGroupMilestonesSection().text()).toBe('Group milestones 6');
});
it('renders each group milestones as a selectable item', () => {
const dropdownItems = findGroupMilestonesDropdownItems();
const dropdownItems = findDropdownItems();
groupMilestones.forEach((milestone, i) => {
expect(dropdownItems.at(i).text()).toBe(milestone.title);
groupMilestones.forEach((milestone) => {
expect(dropdownItems.filter((x) => x.text() === milestone.title).exists()).toBe(true);
});
});
});
......@@ -452,74 +352,8 @@ describe('Milestone combobox component', () => {
return waitForRequests();
});
it('renders the group milestones section in the dropdown', () => {
expect(findGroupMilestonesSection().exists()).toBe(true);
});
it("renders an error message in the group milestones section's body", () => {
expect(groupMilestoneSectionContainsErrorMessage()).toBe(true);
});
});
describe('selection', () => {
beforeEach(() => {
createComponent();
return waitForRequests();
});
it('renders a checkmark by the selected item', async () => {
selectFirstGroupMilestone();
await nextTick();
expect(
findFirstGroupMilestonesDropdownItem()
.find('svg')
.classes('gl-dropdown-item-check-icon'),
).toBe(true);
selectFirstGroupMilestone();
await nextTick();
expect(
findFirstGroupMilestonesDropdownItem().find('svg').classes('gl-visibility-hidden'),
).toBe(true);
});
describe('when a group milestones is selected', () => {
beforeEach(() => {
createComponent();
groupMilestonesApiCallSpy = jest
.fn()
.mockReturnValue([
HTTP_STATUS_OK,
[{ title: 'group-v1.0' }],
{ [X_TOTAL_HEADER]: '1' },
]);
return waitForRequests();
});
it("displays the group milestones name in the dropdown's button", async () => {
selectFirstGroupMilestone();
await nextTick();
expect(findButtonContent().text()).toBe('group-v1.0');
selectFirstGroupMilestone();
await nextTick();
expect(findButtonContent().text()).toBe('No milestone');
});
it('updates the v-model binding with the group milestone title', async () => {
selectFirstGroupMilestone();
await nextTick();
expect(wrapper.emitted().input[0][0]).toStrictEqual(['group-v1.0']);
});
it('does not render the group milestones section', () => {
expect(findGroupMilestonesSection().exists()).toBe(false);
});
});
});
......
......@@ -163,10 +163,12 @@ describe('Milestones combobox Vuex store mutations', () => {
expect(state.matches.projectMilestones).toEqual({
list: [
{
title: 'v0.1',
text: 'v0.1',
value: 'v0.1',
},
{
title: 'v0.2',
text: 'v0.2',
value: 'v0.2',
},
],
error: null,
......@@ -192,10 +194,12 @@ describe('Milestones combobox Vuex store mutations', () => {
expect(state.matches.projectMilestones).toEqual({
list: [
{
title: 'v0.1',
text: 'v0.1',
value: 'v0.1',
},
{
title: 'v0.2',
text: 'v0.2',
value: 'v0.2',
},
],
error: null,
......@@ -245,10 +249,12 @@ describe('Milestones combobox Vuex store mutations', () => {
expect(state.matches.groupMilestones).toEqual({
list: [
{
title: 'group-0.1',
text: 'group-0.1',
value: 'group-0.1',
},
{
title: 'group-0.2',
text: 'group-0.2',
value: 'group-0.2',
},
],
error: null,
......@@ -274,10 +280,12 @@ describe('Milestones combobox Vuex store mutations', () => {
expect(state.matches.groupMilestones).toEqual({
list: [
{
title: 'group-0.1',
text: 'group-0.1',
value: 'group-0.1',
},
{
title: 'group-0.2',
text: 'group-0.2',
value: 'group-0.2',
},
],
error: null,
......
......@@ -44,20 +44,22 @@ def select_create_from(branch_name)
end
def fill_release_title(release_title)
fill_in('Release title', with: release_title)
fill_in('release-title', with: release_title)
end
def select_milestone(milestone_title)
page.within '[data-testid="milestones-field"]' do
find('button').click
def select_milestones(*milestone_titles)
within_testid 'milestones-field' do
find_by_testid('base-dropdown-toggle').click
wait_for_all_requests
find('input[aria-label="Search Milestones"]').set(milestone_title)
milestone_titles.each do |milestone_title|
find('input[type="search"]').set(milestone_title)
wait_for_all_requests
wait_for_all_requests
find('button', text: milestone_title, match: :first).click
find('[role="option"]', text: milestone_title).click
end
end
end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment