Commit f372ec6f authored by Phil Hughes's avatar Phil Hughes

Merge branch '322755-add-releases-filtering' into 'master'

Add release filter and title sort to issues page refactor

See merge request gitlab-org/gitlab!72894
parents 45c31365 33597ad8
......@@ -36,6 +36,7 @@ import {
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT,
UPDATED_DESC,
......@@ -65,6 +66,7 @@ import {
TOKEN_TITLE_LABEL,
TOKEN_TITLE_MILESTONE,
TOKEN_TITLE_MY_REACTION,
TOKEN_TITLE_RELEASE,
TOKEN_TITLE_TYPE,
TOKEN_TITLE_WEIGHT,
} from '~/vue_shared/components/filtered_search_bar/constants';
......@@ -88,6 +90,8 @@ const LabelToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue');
const MilestoneToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue');
const ReleaseToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/release_token.vue');
const WeightToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue');
......@@ -165,6 +169,9 @@ export default {
newIssuePath: {
default: '',
},
releasesPath: {
default: '',
},
rssPath: {
default: '',
},
......@@ -317,7 +324,6 @@ export default {
title: TOKEN_TITLE_MILESTONE,
icon: 'clock',
token: MilestoneToken,
unique: true,
fetchMilestones: this.fetchMilestones,
},
{
......@@ -341,6 +347,16 @@ export default {
},
];
if (this.isProject) {
tokens.push({
type: TOKEN_TYPE_RELEASE,
title: TOKEN_TITLE_RELEASE,
icon: 'rocket',
token: ReleaseToken,
fetchReleases: this.fetchReleases,
});
}
if (this.isSignedIn) {
tokens.push({
type: TOKEN_TYPE_MY_REACTION,
......@@ -371,7 +387,6 @@ export default {
title: TOKEN_TITLE_ITERATION,
icon: 'iteration',
token: IterationToken,
unique: true,
fetchIterations: this.fetchIterations,
});
}
......@@ -457,6 +472,9 @@ export default {
fetchEmojis(search) {
return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search);
},
fetchReleases(search) {
return this.fetchWithCache(this.releasesPath, 'releases', 'tag', search);
},
fetchLabels(search) {
return this.$apollo
.query({
......
......@@ -166,6 +166,7 @@ const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc';
const POPULARITY_ASC_SORT = 'popularity_asc';
const WEIGHT_DESC_SORT = 'weight_desc';
const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc';
const TITLE_ASC_SORT = 'title_asc';
const TITLE_DESC_SORT = 'title_desc';
export const urlSortParams = {
......@@ -187,7 +188,7 @@ export const urlSortParams = {
[WEIGHT_ASC]: WEIGHT,
[WEIGHT_DESC]: WEIGHT_DESC_SORT,
[BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT,
[TITLE_ASC]: TITLE,
[TITLE_ASC]: TITLE_ASC_SORT,
[TITLE_DESC]: TITLE_DESC_SORT,
};
......@@ -211,6 +212,7 @@ export const TOKEN_TYPE_ASSIGNEE = 'assignee_username';
export const TOKEN_TYPE_MILESTONE = 'milestone';
export const TOKEN_TYPE_LABEL = 'labels';
export const TOKEN_TYPE_TYPE = 'type';
export const TOKEN_TYPE_RELEASE = 'release';
export const TOKEN_TYPE_MY_REACTION = 'my_reaction_emoji';
export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
export const TOKEN_TYPE_ITERATION = 'iteration';
......@@ -291,6 +293,21 @@ export const filters = {
},
},
},
[TOKEN_TYPE_RELEASE]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'releaseTag',
[SPECIAL_FILTER]: 'releaseTagWildcardId',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'release_tag',
[SPECIAL_FILTER]: 'release_tag',
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[release_tag]',
},
},
},
[TOKEN_TYPE_MY_REACTION]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'myReactionEmoji',
......
......@@ -137,6 +137,7 @@ export function mountIssuesListApp() {
newIssuePath,
projectImportJiraPath,
quickActionsHelpPath,
releasesPath,
resetPath,
rssPath,
showNewIssueLink,
......@@ -164,6 +165,7 @@ export function mountIssuesListApp() {
isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath,
newIssuePath,
releasesPath,
rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink),
signInPath,
......
......@@ -16,6 +16,8 @@ query getIssues(
$milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
$not: NegatedIssueFilterInput
$beforeCursor: String
......@@ -66,6 +68,8 @@ query getIssues(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
before: $beforeCursor
......
......@@ -10,6 +10,8 @@ query getIssuesCount(
$milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
$not: NegatedIssueFilterInput
) {
......@@ -78,6 +80,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
) {
......@@ -94,6 +98,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
) {
......@@ -110,6 +116,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
) {
......
......@@ -21,10 +21,13 @@ import {
RELATIVE_POSITION_ASC,
SPECIAL_FILTER,
SPECIAL_FILTER_VALUES,
TITLE_ASC,
TITLE_DESC,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_CONFIDENTIAL,
TOKEN_TYPE_ITERATION,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
UPDATED_ASC,
UPDATED_DESC,
......@@ -114,11 +117,19 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
descending: RELATIVE_POSITION_ASC,
},
},
{
id: 9,
title: __('Title'),
sortDirection: {
ascending: TITLE_ASC,
descending: TITLE_DESC,
},
},
];
if (hasIssueWeightsFeature) {
sortOptions.push({
id: 9,
id: sortOptions.length + 1,
title: __('Weight'),
sortDirection: {
ascending: WEIGHT_ASC,
......@@ -129,7 +140,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
if (hasBlockedIssuesFeature) {
sortOptions.push({
id: 10,
id: sortOptions.length + 1,
title: __('Blocking'),
sortDirection: {
ascending: BLOCKING_ISSUES_DESC,
......@@ -194,9 +205,10 @@ const getFilterType = (data, tokenType = '') =>
? SPECIAL_FILTER
: NORMAL_FILTER;
const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE];
const isWildcardValue = (tokenType, value) =>
(tokenType === TOKEN_TYPE_ITERATION || tokenType === TOKEN_TYPE_MILESTONE) &&
SPECIAL_FILTER_VALUES.includes(value);
wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value);
const requiresUpperCaseValue = (tokenType, value) =>
tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value);
......
......@@ -53,6 +53,7 @@ export const TOKEN_TITLE_ASSIGNEE = __('Assignee');
export const TOKEN_TITLE_MILESTONE = __('Milestone');
export const TOKEN_TITLE_LABEL = __('Label');
export const TOKEN_TITLE_TYPE = __('Type');
export const TOKEN_TITLE_RELEASE = __('Release');
export const TOKEN_TITLE_MY_REACTION = __('My-Reaction');
export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential');
export const TOKEN_TITLE_ITERATION = __('Iteration');
......
<script>
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { DEFAULT_NONE_ANY } from '../constants';
export default {
components: {
BaseToken,
GlFilteredSearchSuggestion,
},
props: {
active: {
type: Boolean,
required: true,
},
config: {
type: Object,
required: true,
},
value: {
type: Object,
required: true,
},
},
data() {
return {
releases: this.config.initialReleases || [],
loading: false,
};
},
computed: {
defaultReleases() {
return this.config.defaultReleases || DEFAULT_NONE_ANY;
},
},
methods: {
getActiveRelease(releases, data) {
return releases.find((release) => release.tag.toLowerCase() === data.toLowerCase());
},
fetchReleases(searchTerm) {
this.loading = true;
this.config
.fetchReleases(searchTerm)
.then((response) => {
this.releases = response;
})
.catch(() => {
createFlash({ message: __('There was a problem fetching releases.') });
})
.finally(() => {
this.loading = false;
});
},
},
};
</script>
<template>
<base-token
:active="active"
:config="config"
:value="value"
:default-suggestions="defaultReleases"
:suggestions="releases"
:suggestions-loading="loading"
:get-active-token-value="getActiveRelease"
@fetch-suggestions="fetchReleases"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
{{ activeTokenValue ? activeTokenValue.tag : inputValue }}
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
v-for="release in suggestions"
:key="release.id"
:value="release.tag"
>
{{ release.tag }}
</gl-filtered-search-suggestion>
</template>
</base-token>
</template>
......@@ -233,6 +233,7 @@ module IssuesHelper
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project),
quick_actions_help_path: help_page_path('user/project/quick_actions'),
releases_path: project_releases_path(project, format: :json),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
show_new_issue_link: show_new_issue_link?(project).to_s
)
......
......@@ -16,6 +16,8 @@ query getIssues(
$milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
$epicId: String
$iterationId: [ID]
......@@ -79,6 +81,8 @@ query getIssues(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
epicId: $epicId
iterationId: $iterationId
......
......@@ -10,6 +10,8 @@ query getIssuesCount(
$milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
$epicId: String
$iterationId: [ID]
......@@ -98,6 +100,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
epicId: $epicId
iterationId: $iterationId
......@@ -119,6 +123,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
epicId: $epicId
iterationId: $iterationId
......@@ -140,6 +146,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
epicId: $epicId
iterationId: $iterationId
......
......@@ -34507,6 +34507,9 @@ msgstr ""
msgid "There was a problem fetching recent projects."
msgstr ""
msgid "There was a problem fetching releases."
msgstr ""
msgid "There was a problem fetching the job token scope value"
msgstr ""
......
......@@ -37,6 +37,7 @@ import {
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT,
urlSortParams,
......@@ -581,6 +582,7 @@ describe('IssuesListApp component', () => {
{ type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_TYPE },
{ type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_ITERATION },
......
......@@ -95,12 +95,18 @@ export const locationSearch = [
'assignee_username[]=lisa',
'not[assignee_username][]=patty',
'not[assignee_username][]=selma',
'milestone_title=season+3',
'milestone_title=season+4',
'not[milestone_title]=season+20',
'not[milestone_title]=season+30',
'label_name[]=cartoon',
'label_name[]=tv',
'not[label_name][]=live action',
'not[label_name][]=drama',
'release_tag=v3',
'release_tag=v4',
'not[release_tag]=v20',
'not[release_tag]=v30',
'type[]=issue',
'type[]=feature',
'not[type][]=bug',
......@@ -109,7 +115,9 @@ export const locationSearch = [
'not[my_reaction_emoji]=thumbsdown',
'confidential=yes',
'iteration_id=4',
'iteration_id=12',
'not[iteration_id]=20',
'not[iteration_id]=42',
'epic_id=12',
'not[epic_id]=34',
'weight=1',
......@@ -122,6 +130,7 @@ export const locationSearchWithSpecialValues = [
'my_reaction_emoji=None',
'iteration_id=Current',
'label_name[]=None',
'release_tag=None',
'milestone_title=Upcoming',
'epic_id=None',
'weight=None',
......@@ -134,12 +143,18 @@ export const filteredTokens = [
{ type: 'assignee_username', value: { data: 'lisa', operator: OPERATOR_IS } },
{ type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } },
{ type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } },
{ type: 'milestone', value: { data: 'season 3', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } },
{ type: 'milestone', value: { data: 'season 30', operator: OPERATOR_IS_NOT } },
{ type: 'labels', value: { data: 'cartoon', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } },
{ type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } },
{ type: 'release', value: { data: 'v3', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'v4', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'v20', operator: OPERATOR_IS_NOT } },
{ type: 'release', value: { data: 'v30', operator: OPERATOR_IS_NOT } },
{ type: 'type', value: { data: 'issue', operator: OPERATOR_IS } },
{ type: 'type', value: { data: 'feature', operator: OPERATOR_IS } },
{ type: 'type', value: { data: 'bug', operator: OPERATOR_IS_NOT } },
......@@ -148,7 +163,9 @@ export const filteredTokens = [
{ type: 'my_reaction_emoji', value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } },
{ type: 'confidential', value: { data: 'yes', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '4', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '12', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '20', operator: OPERATOR_IS_NOT } },
{ type: 'iteration', value: { data: '42', operator: OPERATOR_IS_NOT } },
{ type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } },
{ type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } },
{ type: 'weight', value: { data: '1', operator: OPERATOR_IS } },
......@@ -163,6 +180,7 @@ export const filteredTokensWithSpecialValues = [
{ type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'Upcoming', operator: OPERATOR_IS } },
{ type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'weight', value: { data: 'None', operator: OPERATOR_IS } },
......@@ -171,22 +189,24 @@ export const filteredTokensWithSpecialValues = [
export const apiParams = {
authorUsername: 'homer',
assigneeUsernames: ['bart', 'lisa'],
milestoneTitle: 'season 4',
milestoneTitle: ['season 3', 'season 4'],
labelName: ['cartoon', 'tv'],
releaseTag: ['v3', 'v4'],
types: ['ISSUE', 'FEATURE'],
myReactionEmoji: 'thumbsup',
confidential: true,
iterationId: '4',
iterationId: ['4', '12'],
epicId: '12',
weight: '1',
not: {
authorUsername: 'marge',
assigneeUsernames: ['patty', 'selma'],
milestoneTitle: 'season 20',
milestoneTitle: ['season 20', 'season 30'],
labelName: ['live action', 'drama'],
releaseTag: ['v20', 'v30'],
types: ['BUG', 'INCIDENT'],
myReactionEmoji: 'thumbsdown',
iterationId: '20',
iterationId: ['20', '42'],
epicId: '34',
weight: '3',
},
......@@ -197,6 +217,7 @@ export const apiParamsWithSpecialValues = {
assigneeUsernames: 'bart',
labelName: 'None',
myReactionEmoji: 'None',
releaseTagWildcardId: 'NONE',
iterationWildcardId: 'CURRENT',
milestoneWildcardId: 'UPCOMING',
epicId: 'None',
......@@ -208,17 +229,19 @@ export const urlParams = {
'not[author_username]': 'marge',
'assignee_username[]': ['bart', 'lisa'],
'not[assignee_username][]': ['patty', 'selma'],
milestone_title: 'season 4',
'not[milestone_title]': 'season 20',
milestone_title: ['season 3', 'season 4'],
'not[milestone_title]': ['season 20', 'season 30'],
'label_name[]': ['cartoon', 'tv'],
'not[label_name][]': ['live action', 'drama'],
release_tag: ['v3', 'v4'],
'not[release_tag]': ['v20', 'v30'],
'type[]': ['issue', 'feature'],
'not[type][]': ['bug', 'incident'],
my_reaction_emoji: 'thumbsup',
'not[my_reaction_emoji]': 'thumbsdown',
confidential: 'yes',
iteration_id: '4',
'not[iteration_id]': '20',
iteration_id: ['4', '12'],
'not[iteration_id]': ['20', '42'],
epic_id: '12',
'not[epic_id]': '34',
weight: '1',
......@@ -229,6 +252,7 @@ export const urlParamsWithSpecialValues = {
assignee_id: '123',
'assignee_username[]': 'bart',
'label_name[]': 'None',
release_tag: 'None',
my_reaction_emoji: 'None',
iteration_id: 'Current',
milestone_title: 'Upcoming',
......
......@@ -58,10 +58,10 @@ describe('getDueDateValue', () => {
describe('getSortOptions', () => {
describe.each`
hasIssueWeightsFeature | hasBlockedIssuesFeature | length | containsWeight | containsBlocking
${false} | ${false} | ${8} | ${false} | ${false}
${true} | ${false} | ${9} | ${true} | ${false}
${false} | ${true} | ${9} | ${false} | ${true}
${true} | ${true} | ${10} | ${true} | ${true}
${false} | ${false} | ${9} | ${false} | ${false}
${true} | ${false} | ${10} | ${true} | ${false}
${false} | ${true} | ${10} | ${false} | ${true}
${true} | ${true} | ${11} | ${true} | ${true}
`(
'when hasIssueWeightsFeature=$hasIssueWeightsFeature and hasBlockedIssuesFeature=$hasBlockedIssuesFeature',
({
......
......@@ -9,6 +9,7 @@ import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_t
import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
export const mockAuthor1 = {
......@@ -132,6 +133,14 @@ export const mockMilestoneToken = {
fetchMilestones: () => Promise.resolve({ data: mockMilestones }),
};
export const mockReleaseToken = {
type: 'release',
icon: 'rocket',
title: 'Release',
token: ReleaseToken,
fetchReleases: () => Promise.resolve(),
};
export const mockEpicToken = {
type: 'epic_iid',
icon: 'clock',
......
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import { mockReleaseToken } from '../mock_data';
jest.mock('~/flash');
describe('ReleaseToken', () => {
const id = 123;
let wrapper;
const createComponent = ({ config = mockReleaseToken, value = { data: '' } } = {}) =>
mount(ReleaseToken, {
propsData: {
active: false,
config,
value,
},
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: () => 'custom-class',
},
});
afterEach(() => {
wrapper.destroy();
});
it('renders release value', async () => {
wrapper = createComponent({ value: { data: id } });
await wrapper.vm.$nextTick();
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
expect(tokenSegments).toHaveLength(3); // `Release` `=` `v1`
expect(tokenSegments.at(2).text()).toBe(id.toString());
});
it('fetches initial values', () => {
const fetchReleasesSpy = jest.fn().mockResolvedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
value: { data: id },
});
expect(fetchReleasesSpy).toHaveBeenCalledWith(id);
});
it('fetches releases on user input', () => {
const search = 'hello';
const fetchReleasesSpy = jest.fn().mockResolvedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
});
wrapper.findComponent(GlFilteredSearchToken).vm.$emit('input', { data: search });
expect(fetchReleasesSpy).toHaveBeenCalledWith(search);
});
it('renders error message when request fails', async () => {
const fetchReleasesSpy = jest.fn().mockRejectedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: 'There was a problem fetching releases.',
});
});
});
......@@ -326,6 +326,7 @@ RSpec.describe IssuesHelper do
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project),
quick_actions_help_path: help_page_path('user/project/quick_actions'),
releases_path: project_releases_path(project, format: :json),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: '#',
show_new_issue_link: 'true',
......
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