Commit dd5a87ca authored by Florie Guibert's avatar Florie Guibert Committed by Natalia Tepluhina

Milestone widget for Issue and MR page sidebars [RUN AS-IF-FOSS]

parent c2c448c0
...@@ -29,6 +29,7 @@ export default { ...@@ -29,6 +29,7 @@ export default {
issuableAttributesQueries, issuableAttributesQueries,
i18n: { i18n: {
[IssuableAttributeType.Milestone]: __('Milestone'), [IssuableAttributeType.Milestone]: __('Milestone'),
expired: __('(expired)'),
none: __('None'), none: __('None'),
}, },
directives: { directives: {
...@@ -74,9 +75,14 @@ export default { ...@@ -74,9 +75,14 @@ export default {
type: String, type: String,
required: true, required: true,
validator(value) { validator(value) {
return value === IssuableType.Issue; return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
}, },
}, },
icon: {
type: String,
required: false,
default: undefined,
},
}, },
apollo: { apollo: {
currentAttribute: { currentAttribute: {
...@@ -172,6 +178,9 @@ export default { ...@@ -172,6 +178,9 @@ export default {
attributeTypeTitle() { attributeTypeTitle() {
return this.$options.i18n[this.issuableAttribute]; return this.$options.i18n[this.issuableAttribute];
}, },
attributeTypeIcon() {
return this.icon || this.issuableAttribute;
},
i18n() { i18n() {
return { return {
noAttribute: sprintf(s__('DropdownWidget|No %{issuableAttribute}'), { noAttribute: sprintf(s__('DropdownWidget|No %{issuableAttribute}'), {
...@@ -224,7 +233,8 @@ export default { ...@@ -224,7 +233,8 @@ export default {
variables: { variables: {
fullPath: this.workspacePath, fullPath: this.workspacePath,
attributeId: attributeId:
this.issuableAttribute === IssuableAttributeType.Milestone this.issuableAttribute === IssuableAttributeType.Milestone &&
this.issuableType === IssuableType.Issue
? getIdFromGraphQLId(attributeId) ? getIdFromGraphQLId(attributeId)
: attributeId, : attributeId,
iid: this.iid, iid: this.iid,
...@@ -255,6 +265,11 @@ export default { ...@@ -255,6 +265,11 @@ export default {
attributeId === this.currentAttribute?.id || (!this.currentAttribute?.id && !attributeId) attributeId === this.currentAttribute?.id || (!this.currentAttribute?.id && !attributeId)
); );
}, },
isAttributeOverdue(attribute) {
return this.issuableAttribute === IssuableAttributeType.Milestone
? attribute?.expired
: false;
},
showDropdown() { showDropdown() {
this.$refs.newDropdown.show(); this.$refs.newDropdown.show();
}, },
...@@ -284,8 +299,10 @@ export default { ...@@ -284,8 +299,10 @@ export default {
> >
<template #collapsed> <template #collapsed>
<div v-if="isClassicSidebar" v-gl-tooltip class="sidebar-collapsed-icon"> <div v-if="isClassicSidebar" v-gl-tooltip class="sidebar-collapsed-icon">
<gl-icon :size="16" :aria-label="attributeTypeTitle" :name="issuableAttribute" /> <gl-icon :size="16" :aria-label="attributeTypeTitle" :name="attributeTypeIcon" />
<span class="collapse-truncated-title">{{ attributeTitle }}</span> <span class="collapse-truncated-title">
{{ attributeTitle }}
</span>
</div> </div>
<div <div
:data-testid="`select-${issuableAttribute}`" :data-testid="`select-${issuableAttribute}`"
...@@ -308,6 +325,7 @@ export default { ...@@ -308,6 +325,7 @@ export default {
:data-qa-selector="`${issuableAttribute}_link`" :data-qa-selector="`${issuableAttribute}_link`"
> >
{{ attributeTitle }} {{ attributeTitle }}
<span v-if="isAttributeOverdue(currentAttribute)">{{ $options.i18n.expired }}</span>
</gl-link> </gl-link>
</slot> </slot>
</div> </div>
...@@ -357,6 +375,7 @@ export default { ...@@ -357,6 +375,7 @@ export default {
@click="updateAttribute(attrItem.id)" @click="updateAttribute(attrItem.id)"
> >
{{ attrItem.title }} {{ attrItem.title }}
<span v-if="isAttributeOverdue(attrItem)">{{ $options.i18n.expired }}</span>
</gl-dropdown-item> </gl-dropdown-item>
</slot> </slot>
</template> </template>
......
...@@ -12,6 +12,7 @@ import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql'; ...@@ -12,6 +12,7 @@ import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql'; import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import issueSubscribedQuery from '~/sidebar/queries/issue_subscribed.query.graphql'; import issueSubscribedQuery from '~/sidebar/queries/issue_subscribed.query.graphql';
import issueTimeTrackingQuery from '~/sidebar/queries/issue_time_tracking.query.graphql'; import issueTimeTrackingQuery from '~/sidebar/queries/issue_time_tracking.query.graphql';
import mergeRequestMilestone from '~/sidebar/queries/merge_request_milestone.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql'; import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
import mergeRequestSubscribed from '~/sidebar/queries/merge_request_subscribed.query.graphql'; import mergeRequestSubscribed from '~/sidebar/queries/merge_request_subscribed.query.graphql';
import mergeRequestTimeTrackingQuery from '~/sidebar/queries/merge_request_time_tracking.query.graphql'; import mergeRequestTimeTrackingQuery from '~/sidebar/queries/merge_request_time_tracking.query.graphql';
...@@ -24,6 +25,7 @@ import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscr ...@@ -24,6 +25,7 @@ import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscr
import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql'; import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql';
import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql'; import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql';
import updateIssueSubscriptionMutation from '~/sidebar/queries/update_issue_subscription.mutation.graphql'; import updateIssueSubscriptionMutation from '~/sidebar/queries/update_issue_subscription.mutation.graphql';
import mergeRequestMilestoneMutation from '~/sidebar/queries/update_merge_request_milestone.mutation.graphql';
import updateMergeRequestSubscriptionMutation from '~/sidebar/queries/update_merge_request_subscription.mutation.graphql'; import updateMergeRequestSubscriptionMutation from '~/sidebar/queries/update_merge_request_subscription.mutation.graphql';
import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql'; import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
import getAlertAssignees from '~/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql'; import getAlertAssignees from '~/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql';
...@@ -171,12 +173,19 @@ export const issuableMilestoneQueries = { ...@@ -171,12 +173,19 @@ export const issuableMilestoneQueries = {
query: projectIssueMilestoneQuery, query: projectIssueMilestoneQuery,
mutation: projectIssueMilestoneMutation, mutation: projectIssueMilestoneMutation,
}, },
[IssuableType.MergeRequest]: {
query: mergeRequestMilestone,
mutation: mergeRequestMilestoneMutation,
},
}; };
export const milestonesQueries = { export const milestonesQueries = {
[IssuableType.Issue]: { [IssuableType.Issue]: {
query: projectMilestonesQuery, query: projectMilestonesQuery,
}, },
[IssuableType.MergeRequest]: {
query: projectMilestonesQuery,
},
}; };
export const IssuableAttributeType = { export const IssuableAttributeType = {
......
...@@ -18,6 +18,7 @@ import SidebarConfidentialityWidget from '~/sidebar/components/confidential/side ...@@ -18,6 +18,7 @@ import SidebarConfidentialityWidget from '~/sidebar/components/confidential/side
import SidebarDueDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue'; import SidebarDueDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue'; import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue'; import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import SidebarDropdownWidget from '~/sidebar/components/sidebar_dropdown_widget.vue';
import { apolloProvider } from '~/sidebar/graphql'; import { apolloProvider } from '~/sidebar/graphql';
import trackShowInviteMemberLink from '~/sidebar/track_invite_members'; import trackShowInviteMemberLink from '~/sidebar/track_invite_members';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
...@@ -29,6 +30,7 @@ import SidebarReviewers from './components/reviewers/sidebar_reviewers.vue'; ...@@ -29,6 +30,7 @@ import SidebarReviewers from './components/reviewers/sidebar_reviewers.vue';
import SidebarSeverity from './components/severity/sidebar_severity.vue'; import SidebarSeverity from './components/severity/sidebar_severity.vue';
import SidebarSubscriptionsWidget from './components/subscriptions/sidebar_subscriptions_widget.vue'; import SidebarSubscriptionsWidget from './components/subscriptions/sidebar_subscriptions_widget.vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue'; import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
import { IssuableAttributeType } from './constants';
import SidebarMoveIssue from './lib/sidebar_move_issue'; import SidebarMoveIssue from './lib/sidebar_move_issue';
Vue.use(Translate); Vue.use(Translate);
...@@ -154,7 +156,8 @@ function mountReviewersComponent(mediator) { ...@@ -154,7 +156,8 @@ function mountReviewersComponent(mediator) {
issuableIid: String(iid), issuableIid: String(iid),
projectPath: fullPath, projectPath: fullPath,
field: el.dataset.field, field: el.dataset.field,
issuableType: isInIssuePage() || isInDesignPage() ? 'issue' : 'merge_request', issuableType:
isInIssuePage() || isInDesignPage() ? IssuableType.Issue : IssuableType.MergeRequest,
}, },
}), }),
}); });
...@@ -166,6 +169,40 @@ function mountReviewersComponent(mediator) { ...@@ -166,6 +169,40 @@ function mountReviewersComponent(mediator) {
} }
} }
function mountMilestoneSelect() {
const el = document.querySelector('.js-milestone-select');
if (!el) {
return false;
}
const { canEdit, projectPath, issueIid } = el.dataset;
return new Vue({
el,
apolloProvider,
components: {
SidebarDropdownWidget,
},
provide: {
canUpdate: parseBoolean(canEdit),
isClassicSidebar: true,
},
render: (createElement) =>
createElement('sidebar-dropdown-widget', {
props: {
attrWorkspacePath: projectPath,
workspacePath: projectPath,
iid: issueIid,
issuableType:
isInIssuePage() || isInDesignPage() ? IssuableType.Issue : IssuableType.MergeRequest,
issuableAttribute: IssuableAttributeType.Milestone,
icon: 'clock',
},
}),
});
}
export function mountSidebarLabels() { export function mountSidebarLabels() {
const el = document.querySelector('.js-sidebar-labels'); const el = document.querySelector('.js-sidebar-labels');
...@@ -466,6 +503,7 @@ export function mountSidebar(mediator) { ...@@ -466,6 +503,7 @@ export function mountSidebar(mediator) {
mountAssigneesComponentDeprecated(mediator); mountAssigneesComponentDeprecated(mediator);
} }
mountReviewersComponent(mediator); mountReviewersComponent(mediator);
mountMilestoneSelect();
mountConfidentialComponent(mediator); mountConfidentialComponent(mediator);
mountDueDateComponent(mediator); mountDueDateComponent(mediator);
mountReferenceComponent(mediator); mountReferenceComponent(mediator);
......
#import "./milestone.fragment.graphql"
query mergeRequestMilestone($fullPath: ID!, $iid: String!) {
workspace: project(fullPath: $fullPath) {
__typename
issuable: mergeRequest(iid: $iid) {
__typename
id
attribute: milestone {
...MilestoneFragment
}
}
}
}
...@@ -2,4 +2,5 @@ fragment MilestoneFragment on Milestone { ...@@ -2,4 +2,5 @@ fragment MilestoneFragment on Milestone {
id id
title title
webUrl: webPath webUrl: webPath
expired
} }
...@@ -11,6 +11,7 @@ mutation projectIssueMilestoneMutation($fullPath: ID!, $iid: String!, $attribute ...@@ -11,6 +11,7 @@ mutation projectIssueMilestoneMutation($fullPath: ID!, $iid: String!, $attribute
title title
id id
state state
expired
} }
} }
} }
......
...@@ -3,7 +3,13 @@ ...@@ -3,7 +3,13 @@
query projectMilestones($fullPath: ID!, $title: String, $state: MilestoneStateEnum) { query projectMilestones($fullPath: ID!, $title: String, $state: MilestoneStateEnum) {
workspace: project(fullPath: $fullPath) { workspace: project(fullPath: $fullPath) {
__typename __typename
attributes: milestones(searchTitle: $title, state: $state) { attributes: milestones(
searchTitle: $title
state: $state
sort: EXPIRED_LAST_DUE_DATE_ASC
first: 20
includeAncestors: true
) {
nodes { nodes {
...MilestoneFragment ...MilestoneFragment
state state
......
mutation mergeRequestSetMilestone($fullPath: ID!, $iid: String!, $attributeId: ID) {
issuableSetAttribute: mergeRequestSetMilestone(
input: { projectPath: $fullPath, iid: $iid, milestoneId: $attributeId }
) {
__typename
errors
issuable: mergeRequest {
__typename
id
attribute: milestone {
title
id
state
}
}
}
}
...@@ -34,31 +34,8 @@ ...@@ -34,31 +34,8 @@
= render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type
- if issuable_sidebar[:supports_milestone] - if issuable_sidebar[:supports_milestone]
- milestone = issuable_sidebar[:milestone] || {}
.block.milestone{ :class => ("gl-border-b-0!" if issuable_sidebar[:supports_iterations]), data: { qa_selector: 'milestone_block' } } .block.milestone{ :class => ("gl-border-b-0!" if issuable_sidebar[:supports_iterations]), data: { qa_selector: 'milestone_block' } }
.sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } .js-milestone-select{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } }
= sprite_icon('clock')
%span.milestone-title.collapse-truncated-title
- if milestone.present?
= milestone[:title]
- else
= _('None')
.hide-collapsed.gl-line-height-20.gl-mb-2.gl-text-gray-900{ data: { testid: "milestone_title" } }
= _('Milestone')
= loading_icon(css_class: 'gl-vertical-align-text-bottom hidden block-loading')
- if can_edit_issuable
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "edit_milestone_link", track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
.value.hide-collapsed
- if milestone.present?
- milestone_title = milestone[:expired] ? _("%{milestone_name} (Past due)").html_safe % { milestone_name: milestone[:title] } : milestone[:title]
= link_to milestone_title, milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] }
- else
%span.no-value
= _('None')
.selectbox.hide-collapsed
= f.hidden_field 'milestone_id', value: milestone[:id], id: nil
= dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
- if @project.group.present? && issuable_sidebar[:supports_iterations] - if @project.group.present? && issuable_sidebar[:supports_iterations]
.block{ class: 'gl-pt-0!', data: { qa_selector: 'iteration_container' } } .block{ class: 'gl-pt-0!', data: { qa_selector: 'iteration_container' } }
......
...@@ -708,9 +708,6 @@ msgstr "" ...@@ -708,9 +708,6 @@ msgstr ""
msgid "%{message} showing first %{warnings_displayed}" msgid "%{message} showing first %{warnings_displayed}"
msgstr "" msgstr ""
msgid "%{milestone_name} (Past due)"
msgstr ""
msgid "%{milestone} (expired)" msgid "%{milestone} (expired)"
msgstr "" msgstr ""
...@@ -1097,6 +1094,9 @@ msgstr "" ...@@ -1097,6 +1094,9 @@ msgstr ""
msgid "(deleted)" msgid "(deleted)"
msgstr "" msgstr ""
msgid "(expired)"
msgstr ""
msgid "(leave blank if you don't want to change it)" msgid "(leave blank if you don't want to change it)"
msgstr "" msgstr ""
......
...@@ -40,16 +40,22 @@ module QA ...@@ -40,16 +40,22 @@ module QA
base.view 'app/views/shared/issuable/_sidebar.html.haml' do base.view 'app/views/shared/issuable/_sidebar.html.haml' do
element :assignee_block element :assignee_block
element :edit_milestone_link
element :milestone_block element :milestone_block
element :milestone_link end
base.view 'app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue' do
element :milestone_link, 'data-qa-selector="`${issuableAttribute}_link`"' # rubocop:disable QA/ElementWithPattern
end
base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do
element :edit_link
end end
end end
def assign_milestone(milestone) def assign_milestone(milestone)
click_element(:edit_milestone_link)
within_element(:milestone_block) do within_element(:milestone_block) do
click_link("#{milestone.title}") click_element(:edit_link)
click_on(milestone.title)
end end
wait_until(reload: false) do wait_until(reload: false) do
...@@ -89,7 +95,7 @@ module QA ...@@ -89,7 +95,7 @@ module QA
def has_milestone?(milestone_title) def has_milestone?(milestone_title)
wait_milestone_block_finish_loading do wait_milestone_block_finish_loading do
has_element?(:milestone_link, title: milestone_title) has_element?(:milestone_link, text: milestone_title)
end end
end end
......
...@@ -259,37 +259,35 @@ RSpec.describe 'Issue Sidebar' do ...@@ -259,37 +259,35 @@ RSpec.describe 'Issue Sidebar' do
end end
context 'editing issue milestone', :js do context 'editing issue milestone', :js do
let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } let_it_be(:milestone_expired) { create(:milestone, project: project, title: 'Foo - expired', due_date: 5.days.ago) }
let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') }
let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) }
let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) }
let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) }
before do before do
page.within('[data-testid="milestone_title"]') do page.within('.block.milestone') do
click_on 'Edit' click_button 'Edit'
end end
wait_for_all_requests
end end
it 'shows milestons list in the dropdown' do it 'shows milestones list in the dropdown' do
page.within('.block.milestone .dropdown-content') do page.within('.block.milestone') do
# 5 milestones + "No milestone" = 6 items # 5 milestones + "No milestone" = 6 items
expect(page.find('ul')).to have_selector('li[data-milestone-id]', count: 6) expect(page.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 6)
end end
end end
it 'shows expired milestone at the bottom of the list' do it 'shows expired milestone at the bottom of the list and milestone due earliest at the top of the list', :aggregate_failures do
page.within('.block.milestone .dropdown-content ul') do page.within('.block.milestone .gl-new-dropdown-contents') do
expect(page.find('li:last-child')).to have_content milestone_expired.title expect(page.find('li:last-child')).to have_content milestone_expired.title
end
end
it 'shows milestone due earliest at the top of the list' do expect(page.all('li.gl-new-dropdown-item')[1]).to have_content milestone3.title
page.within('.block.milestone .dropdown-content ul') do expect(page.all('li.gl-new-dropdown-item')[2]).to have_content milestone2.title
expect(page.all('li[data-milestone-id]')[1]).to have_content milestone3.title expect(page.all('li.gl-new-dropdown-item')[3]).to have_content milestone1.title
expect(page.all('li[data-milestone-id]')[2]).to have_content milestone2.title expect(page.all('li.gl-new-dropdown-item')[4]).to have_content milestone_no_duedate.title
expect(page.all('li[data-milestone-id]')[3]).to have_content milestone1.title
expect(page.all('li[data-milestone-id]')[4]).to have_content milestone_no_duedate.title
end end
end end
end end
......
...@@ -333,37 +333,40 @@ RSpec.describe "Issues > User edits issue", :js do ...@@ -333,37 +333,40 @@ RSpec.describe "Issues > User edits issue", :js do
describe 'update milestone' do describe 'update milestone' do
context 'by authorized user' do context 'by authorized user' do
it 'allows user to select unassigned' do it 'allows user to select no milestone' do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
page.within('.milestone') do page.within('.block.milestone') do
expect(page).to have_content "None" expect(page).to have_content 'None'
end
click_button 'Edit'
wait_for_requests
click_button 'No milestone'
wait_for_requests
find('.block.milestone .edit-link').click
sleep 2 # wait for ajax stuff to complete
first('.dropdown-content li').click
sleep 2
page.within('.milestone') do
expect(page).to have_content 'None' expect(page).to have_content 'None'
end end
end end
it 'allows user to de-select milestone' do it 'allows user to de-select milestone' do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
page.within('.milestone') do page.within('.milestone') do
click_link 'Edit' click_button 'Edit'
click_link milestone.title wait_for_requests
click_button milestone.title
page.within '.value' do page.within '[data-testid="select-milestone"]' do
expect(page).to have_content milestone.title expect(page).to have_content milestone.title
end end
click_link 'Edit' click_button 'Edit'
click_link milestone.title wait_for_requests
click_button 'No milestone'
page.within '.value' do page.within '[data-testid="select-milestone"]' do
expect(page).to have_content 'None' expect(page).to have_content 'None'
end end
end end
...@@ -371,16 +374,17 @@ RSpec.describe "Issues > User edits issue", :js do ...@@ -371,16 +374,17 @@ RSpec.describe "Issues > User edits issue", :js do
it 'allows user to search milestone' do it 'allows user to search milestone' do
visit project_issue_path(project_with_milestones, issue_with_milestones) visit project_issue_path(project_with_milestones, issue_with_milestones)
wait_for_requests
page.within('.milestone') do page.within('.milestone') do
click_link 'Edit' click_button 'Edit'
wait_for_requests wait_for_requests
# We need to enclose search string in quotes for exact match as all the milestone titles # We need to enclose search string in quotes for exact match as all the milestone titles
# within tests are prefixed with `My title`. # within tests are prefixed with `My title`.
find('.dropdown-input-field', visible: true).send_keys "\"#{milestones[0].title}\"" find('.gl-form-input', visible: true).send_keys "\"#{milestones[0].title}\""
wait_for_requests wait_for_requests
page.within '.dropdown-content' do page.within '.gl-new-dropdown-contents' do
expect(page).to have_content milestones[0].title expect(page).to have_content milestones[0].title
end end
end end
......
...@@ -530,6 +530,7 @@ export const mockMilestone1 = { ...@@ -530,6 +530,7 @@ export const mockMilestone1 = {
title: 'Foobar Milestone', title: 'Foobar Milestone',
webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/milestones/1', webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/milestones/1',
state: 'active', state: 'active',
expired: false,
}; };
export const mockMilestone2 = { export const mockMilestone2 = {
...@@ -538,6 +539,7 @@ export const mockMilestone2 = { ...@@ -538,6 +539,7 @@ export const mockMilestone2 = {
title: 'Awesome Milestone', title: 'Awesome Milestone',
webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/milestones/2', webUrl: 'http://gdk.test:3000/groups/gitlab-org/-/milestones/2',
state: 'active', state: 'active',
expired: false,
}; };
export const mockProjectMilestonesResponse = { export const mockProjectMilestonesResponse = {
...@@ -571,6 +573,7 @@ export const mockMilestoneMutationResponse = { ...@@ -571,6 +573,7 @@ export const mockMilestoneMutationResponse = {
id: 'gid://gitlab/Milestone/2', id: 'gid://gitlab/Milestone/2',
title: 'Awesome Milestone', title: 'Awesome Milestone',
state: 'active', state: 'active',
expired: false,
__typename: 'Milestone', __typename: 'Milestone',
}, },
__typename: 'Issue', __typename: 'Issue',
......
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