Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
0bb768ba
Commit
0bb768ba
authored
Mar 30, 2021
by
Florie Guibert
Committed by
Kushal Pandya
Mar 30, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support reaction emogi for GlFilteredSearch
Make epics filterable by reaction emoji on epics list and roadmap
parent
6a96e3a5
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
482 additions
and
48 deletions
+482
-48
app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
...red/components/filtered_search_bar/tokens/emoji_token.vue
+105
-0
ee/app/assets/javascripts/epics_list/queries/group_epics.query.graphql
.../javascripts/epics_list/queries/group_epics.query.graphql
+2
-0
ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js
...ssets/javascripts/roadmap/mixins/filtered_search_mixin.js
+41
-3
ee/app/assets/javascripts/roadmap/queries/groupEpics.query.graphql
...sets/javascripts/roadmap/queries/groupEpics.query.graphql
+2
-0
ee/app/graphql/resolvers/epics_resolver.rb
ee/app/graphql/resolvers/epics_resolver.rb
+4
-0
ee/changelogs/unreleased/325630-gl-filtered-search-reaction-emoji.yml
...s/unreleased/325630-gl-filtered-search-reaction-emoji.yml
+5
-0
ee/spec/frontend/roadmap/components/roadmap_filters_spec.js
ee/spec/frontend/roadmap/components/roadmap_filters_spec.js
+71
-45
ee/spec/graphql/resolvers/epics_resolver_spec.rb
ee/spec/graphql/resolvers/epics_resolver_spec.rb
+11
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
...nd/vue_shared/components/filtered_search_bar/mock_data.js
+21
-0
spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
...components/filtered_search_bar/tokens/emoji_token_spec.js
+217
-0
No files found.
app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
0 → 100644
View file @
0bb768ba
<
script
>
import
{
GlFilteredSearchToken
,
GlFilteredSearchSuggestion
,
GlDropdownDivider
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
import
{
debounce
}
from
'
lodash
'
;
import
{
deprecatedCreateFlash
as
createFlash
}
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
DEFAULT_LABEL_NONE
,
DEFAULT_LABEL_ANY
,
DEBOUNCE_DELAY
}
from
'
../constants
'
;
import
{
stripQuotes
}
from
'
../filtered_search_utils
'
;
export
default
{
components
:
{
GlFilteredSearchToken
,
GlFilteredSearchSuggestion
,
GlDropdownDivider
,
GlLoadingIcon
,
},
props
:
{
config
:
{
type
:
Object
,
required
:
true
,
},
value
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
emojis
:
this
.
config
.
initialEmojis
||
[],
defaultEmojis
:
this
.
config
.
defaultEmojis
||
[
DEFAULT_LABEL_NONE
,
DEFAULT_LABEL_ANY
],
loading
:
true
,
};
},
computed
:
{
currentValue
()
{
return
this
.
value
.
data
.
toLowerCase
();
},
activeEmoji
()
{
return
this
.
emojis
.
find
(
(
emoji
)
=>
emoji
.
name
.
toLowerCase
()
===
stripQuotes
(
this
.
currentValue
),
);
},
},
methods
:
{
fetchEmojiBySearchTerm
(
searchTerm
)
{
this
.
loading
=
true
;
this
.
config
.
fetchEmojis
(
searchTerm
)
.
then
((
res
)
=>
{
this
.
emojis
=
Array
.
isArray
(
res
)
?
res
:
res
.
data
;
})
.
catch
(()
=>
createFlash
(
__
(
'
There was a problem fetching emojis.
'
)))
.
finally
(()
=>
{
this
.
loading
=
false
;
});
},
searchEmojis
:
debounce
(
function
debouncedSearch
({
data
})
{
this
.
fetchEmojiBySearchTerm
(
data
);
},
DEBOUNCE_DELAY
),
},
};
</
script
>
<
template
>
<gl-filtered-search-token
:config=
"config"
v-bind=
"
{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchEmojis"
>
<template
#view
="
{ inputValue }">
<gl-emoji
v-if=
"activeEmoji"
:data-name=
"activeEmoji.name"
/>
<span
v-else
>
{{
inputValue
}}
</span>
</
template
>
<
template
#suggestions
>
<gl-filtered-search-suggestion
v-for=
"emoji in defaultEmojis"
:key=
"emoji.value"
:value=
"emoji.value"
>
{{
emoji
.
value
}}
</gl-filtered-search-suggestion>
<gl-dropdown-divider
v-if=
"defaultEmojis.length"
/>
<gl-loading-icon
v-if=
"loading"
/>
<template
v-else
>
<gl-filtered-search-suggestion
v-for=
"emoji in emojis"
:key=
"emoji.name"
:value=
"emoji.name"
>
<div
class=
"gl-display-flex"
>
<gl-emoji
:data-name=
"emoji.name"
/>
<span
class=
"gl-ml-3"
>
{{
emoji
.
name
}}
</span>
</div>
</gl-filtered-search-suggestion>
</
template
>
</template>
</gl-filtered-search-token>
</template>
ee/app/assets/javascripts/epics_list/queries/group_epics.query.graphql
View file @
0bb768ba
...
@@ -8,6 +8,7 @@ query groupEpics(
...
@@ -8,6 +8,7 @@ query groupEpics(
$authorUsername
:
String
$authorUsername
:
String
$labelName
:
[
String
!]
$labelName
:
[
String
!]
$milestoneTitle
:
String
=
""
$milestoneTitle
:
String
=
""
$myReactionEmoji
:
String
$confidential
:
Boolean
$confidential
:
Boolean
$search
:
String
=
""
$search
:
String
=
""
$sortBy
:
EpicSort
$sortBy
:
EpicSort
...
@@ -22,6 +23,7 @@ query groupEpics(
...
@@ -22,6 +23,7 @@ query groupEpics(
authorUsername
:
$authorUsername
authorUsername
:
$authorUsername
labelName
:
$labelName
labelName
:
$labelName
milestoneTitle
:
$milestoneTitle
milestoneTitle
:
$milestoneTitle
myReactionEmoji
:
$myReactionEmoji
confidential
:
$confidential
confidential
:
$confidential
search
:
$search
search
:
$search
sort
:
$sortBy
sort
:
$sortBy
...
...
ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js
View file @
0bb768ba
...
@@ -5,6 +5,7 @@ import axios from '~/lib/utils/axios_utils';
...
@@ -5,6 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
EmojiToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
'
;
import
LabelToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/label_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
MilestoneToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
'
;
...
@@ -14,7 +15,7 @@ export default {
...
@@ -14,7 +15,7 @@ export default {
inject
:
[
'
groupFullPath
'
,
'
groupMilestonesPath
'
],
inject
:
[
'
groupFullPath
'
,
'
groupMilestonesPath
'
],
computed
:
{
computed
:
{
urlParams
()
{
urlParams
()
{
const
{
search
,
authorUsername
,
labelName
,
milestoneTitle
,
confidential
}
=
const
{
search
,
authorUsername
,
labelName
,
milestoneTitle
,
confidential
,
myReactionEmoji
}
=
this
.
filterParams
||
{};
this
.
filterParams
||
{};
return
{
return
{
...
@@ -27,13 +28,14 @@ export default {
...
@@ -27,13 +28,14 @@ export default {
'
label_name[]
'
:
labelName
,
'
label_name[]
'
:
labelName
,
milestone_title
:
milestoneTitle
,
milestone_title
:
milestoneTitle
,
confidential
,
confidential
,
my_reaction_emoji
:
myReactionEmoji
,
search
,
search
,
};
};
},
},
},
},
methods
:
{
methods
:
{
getFilteredSearchTokens
()
{
getFilteredSearchTokens
()
{
return
[
const
tokens
=
[
{
{
type
:
'
author_username
'
,
type
:
'
author_username
'
,
icon
:
'
user
'
,
icon
:
'
user
'
,
...
@@ -103,9 +105,35 @@ export default {
...
@@ -103,9 +105,35 @@ export default {
],
],
},
},
];
];
if
(
gon
.
current_user_id
)
{
// Appending to tokens only when logged-in
tokens
.
push
({
type
:
'
my_reaction_emoji
'
,
icon
:
'
thumb-up
'
,
title
:
__
(
'
My-Reaction
'
),
unique
:
true
,
token
:
EmojiToken
,
operators
:
FilterTokenOperators
,
fetchEmojis
:
(
search
=
''
)
=>
{
return
axios
.
get
(
`
${
gon
.
relative_url_root
||
''
}
/-/autocomplete/award_emojis`
)
.
then
(({
data
})
=>
{
if
(
search
)
{
return
{
data
:
data
.
filter
((
e
)
=>
e
.
name
.
toLowerCase
().
includes
(
search
.
toLowerCase
())),
};
}
return
{
data
};
});
},
});
}
return
tokens
;
},
},
getFilteredSearchValue
()
{
getFilteredSearchValue
()
{
const
{
authorUsername
,
labelName
,
milestoneTitle
,
confidential
,
search
}
=
const
{
authorUsername
,
labelName
,
milestoneTitle
,
confidential
,
myReactionEmoji
,
search
}
=
this
.
filterParams
||
{};
this
.
filterParams
||
{};
const
filteredSearchValue
=
[];
const
filteredSearchValue
=
[];
...
@@ -139,6 +167,13 @@ export default {
...
@@ -139,6 +167,13 @@ export default {
});
});
}
}
if
(
myReactionEmoji
)
{
filteredSearchValue
.
push
({
type
:
'
my_reaction_emoji
'
,
value
:
{
data
:
myReactionEmoji
},
});
}
if
(
search
)
{
if
(
search
)
{
filteredSearchValue
.
push
(
search
);
filteredSearchValue
.
push
(
search
);
}
}
...
@@ -164,6 +199,9 @@ export default {
...
@@ -164,6 +199,9 @@ export default {
case
'
confidential
'
:
case
'
confidential
'
:
filterParams
.
confidential
=
filter
.
value
.
data
;
filterParams
.
confidential
=
filter
.
value
.
data
;
break
;
break
;
case
'
my_reaction_emoji
'
:
filterParams
.
myReactionEmoji
=
filter
.
value
.
data
;
break
;
case
'
filtered-search-term
'
:
case
'
filtered-search-term
'
:
if
(
filter
.
value
.
data
)
plainText
.
push
(
filter
.
value
.
data
);
if
(
filter
.
value
.
data
)
plainText
.
push
(
filter
.
value
.
data
);
break
;
break
;
...
...
ee/app/assets/javascripts/roadmap/queries/groupEpics.query.graphql
View file @
0bb768ba
...
@@ -9,6 +9,7 @@ query groupEpics(
...
@@ -9,6 +9,7 @@ query groupEpics(
$labelName
:
[
String
!]
=
[]
$labelName
:
[
String
!]
=
[]
$authorUsername
:
String
=
""
$authorUsername
:
String
=
""
$milestoneTitle
:
String
=
""
$milestoneTitle
:
String
=
""
$myReactionEmoji
:
String
$confidential
:
Boolean
$confidential
:
Boolean
$search
:
String
=
""
$search
:
String
=
""
$first
:
Int
=
1001
$first
:
Int
=
1001
...
@@ -24,6 +25,7 @@ query groupEpics(
...
@@ -24,6 +25,7 @@ query groupEpics(
labelName
:
$labelName
labelName
:
$labelName
authorUsername
:
$authorUsername
authorUsername
:
$authorUsername
milestoneTitle
:
$milestoneTitle
milestoneTitle
:
$milestoneTitle
myReactionEmoji
:
$myReactionEmoji
confidential
:
$confidential
confidential
:
$confidential
search
:
$search
search
:
$search
first
:
$first
first
:
$first
...
...
ee/app/graphql/resolvers/epics_resolver.rb
View file @
0bb768ba
...
@@ -50,6 +50,10 @@ module Resolvers
...
@@ -50,6 +50,10 @@ module Resolvers
required:
false
,
required:
false
,
description:
'Filter epics by given confidentiality.'
description:
'Filter epics by given confidentiality.'
argument
:my_reaction_emoji
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'Filter by reaction emoji applied by the current user.'
type
Types
::
EpicType
,
null:
true
type
Types
::
EpicType
,
null:
true
def
ready?
(
**
args
)
def
ready?
(
**
args
)
...
...
ee/changelogs/unreleased/325630-gl-filtered-search-reaction-emoji.yml
0 → 100644
View file @
0bb768ba
---
title
:
Support reaction emoji on Epics Roadmap
merge_request
:
57452
author
:
type
:
added
ee/spec/frontend/roadmap/components/roadmap_filters_spec.js
View file @
0bb768ba
...
@@ -12,6 +12,7 @@ import { TEST_HOST } from 'helpers/test_constants';
...
@@ -12,6 +12,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import
{
visitUrl
,
mergeUrlParams
,
updateHistory
}
from
'
~/lib/utils/url_utility
'
;
import
{
visitUrl
,
mergeUrlParams
,
updateHistory
}
from
'
~/lib/utils/url_utility
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
EmojiToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
'
;
import
LabelToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/label_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
MilestoneToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
'
;
...
@@ -159,6 +160,53 @@ describe('RoadmapFilters', () => {
...
@@ -159,6 +160,53 @@ describe('RoadmapFilters', () => {
];
];
let
filteredSearchBar
;
let
filteredSearchBar
;
const
operators
=
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}];
const
filterTokens
=
[
{
type
:
'
author_username
'
,
icon
:
'
user
'
,
title
:
'
Author
'
,
unique
:
true
,
symbol
:
'
@
'
,
token
:
AuthorToken
,
operators
,
fetchAuthors
:
expect
.
any
(
Function
),
},
{
type
:
'
label_name
'
,
icon
:
'
labels
'
,
title
:
'
Label
'
,
unique
:
false
,
symbol
:
'
~
'
,
token
:
LabelToken
,
operators
,
fetchLabels
:
expect
.
any
(
Function
),
},
{
type
:
'
milestone_title
'
,
icon
:
'
clock
'
,
title
:
'
Milestone
'
,
unique
:
true
,
symbol
:
'
%
'
,
token
:
MilestoneToken
,
operators
,
fetchMilestones
:
expect
.
any
(
Function
),
},
{
type
:
'
confidential
'
,
icon
:
'
eye-slash
'
,
title
:
'
Confidential
'
,
unique
:
true
,
token
:
GlFilteredSearchToken
,
operators
,
options
:
[
{
icon
:
'
eye-slash
'
,
value
:
true
,
title
:
'
Yes
'
},
{
icon
:
'
eye
'
,
value
:
false
,
title
:
'
No
'
},
],
},
];
beforeEach
(()
=>
{
beforeEach
(()
=>
{
filteredSearchBar
=
wrapper
.
find
(
FilteredSearchBar
);
filteredSearchBar
=
wrapper
.
find
(
FilteredSearchBar
);
});
});
...
@@ -169,51 +217,8 @@ describe('RoadmapFilters', () => {
...
@@ -169,51 +217,8 @@ describe('RoadmapFilters', () => {
expect
(
filteredSearchBar
.
props
(
'
recentSearchesStorageKey
'
)).
toBe
(
'
epics
'
);
expect
(
filteredSearchBar
.
props
(
'
recentSearchesStorageKey
'
)).
toBe
(
'
epics
'
);
});
});
it
(
'
includes `Author` and `Label` tokens
'
,
()
=>
{
it
(
'
includes `Author`, `Milestone`, `Confidential` and `Label` tokens when user is not logged in
'
,
()
=>
{
expect
(
filteredSearchBar
.
props
(
'
tokens
'
)).
toEqual
([
expect
(
filteredSearchBar
.
props
(
'
tokens
'
)).
toEqual
(
filterTokens
);
{
type
:
'
author_username
'
,
icon
:
'
user
'
,
title
:
'
Author
'
,
unique
:
true
,
symbol
:
'
@
'
,
token
:
AuthorToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
fetchAuthors
:
expect
.
any
(
Function
),
},
{
type
:
'
label_name
'
,
icon
:
'
labels
'
,
title
:
'
Label
'
,
unique
:
false
,
symbol
:
'
~
'
,
token
:
LabelToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
fetchLabels
:
expect
.
any
(
Function
),
},
{
type
:
'
milestone_title
'
,
icon
:
'
clock
'
,
title
:
'
Milestone
'
,
unique
:
true
,
symbol
:
'
%
'
,
token
:
MilestoneToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
fetchMilestones
:
expect
.
any
(
Function
),
},
{
type
:
'
confidential
'
,
icon
:
'
eye-slash
'
,
title
:
'
Confidential
'
,
unique
:
true
,
token
:
GlFilteredSearchToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
options
:
[
{
icon
:
'
eye-slash
'
,
value
:
true
,
title
:
'
Yes
'
},
{
icon
:
'
eye
'
,
value
:
false
,
title
:
'
No
'
},
],
},
]);
});
});
it
(
'
includes "Start date" and "Due date" sort options
'
,
()
=>
{
it
(
'
includes "Start date" and "Due date" sort options
'
,
()
=>
{
...
@@ -282,6 +287,27 @@ describe('RoadmapFilters', () => {
...
@@ -282,6 +287,27 @@ describe('RoadmapFilters', () => {
expect
(
wrapper
.
vm
.
setSortedBy
).
toHaveBeenCalledWith
(
'
end_date_asc
'
);
expect
(
wrapper
.
vm
.
setSortedBy
).
toHaveBeenCalledWith
(
'
end_date_asc
'
);
expect
(
wrapper
.
vm
.
fetchEpics
).
toHaveBeenCalled
();
expect
(
wrapper
.
vm
.
fetchEpics
).
toHaveBeenCalled
();
});
});
describe
(
'
when user is logged in
'
,
()
=>
{
beforeAll
(()
=>
{
gon
.
current_user_id
=
1
;
});
it
(
'
includes `Author`, `Milestone`, `Confidential`, `Label` and `My-Reaction` tokens
'
,
()
=>
{
expect
(
filteredSearchBar
.
props
(
'
tokens
'
)).
toEqual
([
...
filterTokens
,
{
type
:
'
my_reaction_emoji
'
,
icon
:
'
thumb-up
'
,
title
:
'
My-Reaction
'
,
unique
:
true
,
token
:
EmojiToken
,
operators
,
fetchEmojis
:
expect
.
any
(
Function
),
},
]);
});
});
});
});
});
});
});
});
ee/spec/graphql/resolvers/epics_resolver_spec.rb
View file @
0bb768ba
...
@@ -134,6 +134,17 @@ RSpec.describe Resolvers::EpicsResolver do
...
@@ -134,6 +134,17 @@ RSpec.describe Resolvers::EpicsResolver do
end
end
end
end
context
'with my_reaction_emoji'
do
it
'filters epics by reaction emoji'
do
create
(
:award_emoji
,
name:
'thumbsup'
,
user:
current_user
,
awardable:
epic1
)
create
(
:award_emoji
,
name:
'star'
,
user:
current_user
,
awardable:
epic2
)
epics
=
resolve_epics
(
my_reaction_emoji:
'thumbsup'
)
expect
(
epics
).
to
match_array
([
epic1
])
end
end
context
'with milestone_title'
do
context
'with milestone_title'
do
let_it_be
(
:milestone1
)
{
create
(
:milestone
,
group:
group
)
}
let_it_be
(
:milestone1
)
{
create
(
:milestone
,
group:
group
)
}
...
...
locale/gitlab.pot
View file @
0bb768ba
...
@@ -30687,6 +30687,9 @@ msgstr ""
...
@@ -30687,6 +30687,9 @@ msgstr ""
msgid "There was a problem fetching branches."
msgid "There was a problem fetching branches."
msgstr ""
msgstr ""
msgid "There was a problem fetching emojis."
msgstr ""
msgid "There was a problem fetching groups."
msgid "There was a problem fetching groups."
msgstr ""
msgstr ""
...
...
spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
View file @
0bb768ba
...
@@ -3,6 +3,7 @@ import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue
...
@@ -3,6 +3,7 @@ import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue
import
Api
from
'
~/api
'
;
import
Api
from
'
~/api
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
BranchToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
'
;
import
BranchToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
'
;
import
EmojiToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
'
;
import
LabelToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/label_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
MilestoneToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
'
;
...
@@ -59,6 +60,16 @@ export const mockMilestones = [
...
@@ -59,6 +60,16 @@ export const mockMilestones = [
mockEscapedMilestone
,
mockEscapedMilestone
,
];
];
export
const
mockEmoji1
=
{
name
:
'
thumbsup
'
,
};
export
const
mockEmoji2
=
{
name
:
'
star
'
,
};
export
const
mockEmojis
=
[
mockEmoji1
,
mockEmoji2
];
export
const
mockBranchToken
=
{
export
const
mockBranchToken
=
{
type
:
'
source_branch
'
,
type
:
'
source_branch
'
,
icon
:
'
branch
'
,
icon
:
'
branch
'
,
...
@@ -103,6 +114,16 @@ export const mockMilestoneToken = {
...
@@ -103,6 +114,16 @@ export const mockMilestoneToken = {
fetchMilestones
:
()
=>
Promise
.
resolve
({
data
:
mockMilestones
}),
fetchMilestones
:
()
=>
Promise
.
resolve
({
data
:
mockMilestones
}),
};
};
export
const
mockReactionEmojiToken
=
{
type
:
'
my_reaction_emoji
'
,
icon
:
'
thumb-up
'
,
title
:
'
My-Reaction
'
,
unique
:
true
,
token
:
EmojiToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
fetchEmojis
:
()
=>
Promise
.
resolve
(
mockEmojis
),
};
export
const
mockMembershipToken
=
{
export
const
mockMembershipToken
=
{
type
:
'
with_inherited_permissions
'
,
type
:
'
with_inherited_permissions
'
,
icon
:
'
group
'
,
icon
:
'
group
'
,
...
...
spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
0 → 100644
View file @
0bb768ba
import
{
GlFilteredSearchToken
,
GlFilteredSearchSuggestion
,
GlFilteredSearchTokenSegment
,
GlDropdownDivider
,
}
from
'
@gitlab/ui
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
deprecatedCreateFlash
as
createFlash
}
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
DEFAULT_LABEL_NONE
,
DEFAULT_LABEL_ANY
,
}
from
'
~/vue_shared/components/filtered_search_bar/constants
'
;
import
EmojiToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
'
;
import
{
mockReactionEmojiToken
,
mockEmojis
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
const
GlEmoji
=
{
template
:
'
<img/>
'
};
const
defaultStubs
=
{
Portal
:
true
,
GlFilteredSearchSuggestionList
:
{
template
:
'
<div></div>
'
,
methods
:
{
getValue
:
()
=>
'
=
'
,
},
},
GlEmoji
,
};
function
createComponent
(
options
=
{})
{
const
{
config
=
mockReactionEmojiToken
,
value
=
{
data
:
''
},
active
=
false
,
stubs
=
defaultStubs
,
}
=
options
;
return
mount
(
EmojiToken
,
{
propsData
:
{
config
,
value
,
active
,
},
provide
:
{
portalName
:
'
fake target
'
,
alignSuggestions
:
function
fakeAlignSuggestions
()
{},
suggestionsListClass
:
'
custom-class
'
,
},
stubs
,
});
}
describe
(
'
EmojiToken
'
,
()
=>
{
let
mock
;
let
wrapper
;
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
wrapper
.
destroy
();
});
describe
(
'
computed
'
,
()
=>
{
beforeEach
(
async
()
=>
{
wrapper
=
createComponent
({
value
:
{
data
:
mockEmojis
[
0
].
name
}
});
wrapper
.
setData
({
emojis
:
mockEmojis
,
});
await
wrapper
.
vm
.
$nextTick
();
});
describe
(
'
currentValue
'
,
()
=>
{
it
(
'
returns lowercase string for `value.data`
'
,
()
=>
{
expect
(
wrapper
.
vm
.
currentValue
).
toBe
(
mockEmojis
[
0
].
name
);
});
});
describe
(
'
activeEmoji
'
,
()
=>
{
it
(
'
returns object for currently present `value.data`
'
,
()
=>
{
expect
(
wrapper
.
vm
.
activeEmoji
).
toEqual
(
mockEmojis
[
0
]);
});
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
describe
(
'
fetchEmojiBySearchTerm
'
,
()
=>
{
it
(
'
calls `config.fetchEmojis` with provided searchTerm param
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
config
,
'
fetchEmojis
'
);
wrapper
.
vm
.
fetchEmojiBySearchTerm
(
'
foo
'
);
expect
(
wrapper
.
vm
.
config
.
fetchEmojis
).
toHaveBeenCalledWith
(
'
foo
'
);
});
it
(
'
sets response to `emojis` when request is successful
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
config
,
'
fetchEmojis
'
).
mockResolvedValue
(
mockEmojis
);
wrapper
.
vm
.
fetchEmojiBySearchTerm
(
'
foo
'
);
return
waitForPromises
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
emojis
).
toEqual
(
mockEmojis
);
});
});
it
(
'
calls `createFlash` with flash error message when request fails
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
config
,
'
fetchEmojis
'
).
mockRejectedValue
({});
wrapper
.
vm
.
fetchEmojiBySearchTerm
(
'
foo
'
);
return
waitForPromises
().
then
(()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
(
'
There was a problem fetching emojis.
'
);
});
});
it
(
'
sets `loading` to false when request completes
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
config
,
'
fetchEmojis
'
).
mockRejectedValue
({});
wrapper
.
vm
.
fetchEmojiBySearchTerm
(
'
foo
'
);
return
waitForPromises
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
loading
).
toBe
(
false
);
});
});
});
});
describe
(
'
template
'
,
()
=>
{
const
defaultEmojis
=
[
DEFAULT_LABEL_NONE
,
DEFAULT_LABEL_ANY
];
beforeEach
(
async
()
=>
{
wrapper
=
createComponent
({
value
:
{
data
:
`"
${
mockEmojis
[
0
].
name
}
"`
},
});
wrapper
.
setData
({
emojis
:
mockEmojis
,
});
await
wrapper
.
vm
.
$nextTick
();
});
it
(
'
renders gl-filtered-search-token component
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlFilteredSearchToken
).
exists
()).
toBe
(
true
);
});
it
(
'
renders token item when value is selected
'
,
()
=>
{
const
tokenSegments
=
wrapper
.
findAll
(
GlFilteredSearchTokenSegment
);
expect
(
tokenSegments
).
toHaveLength
(
3
);
// My Reaction, =, "thumbsup"
expect
(
tokenSegments
.
at
(
2
).
find
(
GlEmoji
).
attributes
(
'
data-name
'
)).
toEqual
(
'
thumbsup
'
);
});
it
(
'
renders provided defaultEmojis as suggestions
'
,
async
()
=>
{
wrapper
=
createComponent
({
active
:
true
,
config
:
{
...
mockReactionEmojiToken
,
defaultEmojis
},
stubs
:
{
Portal
:
true
,
GlEmoji
},
});
const
tokenSegments
=
wrapper
.
findAll
(
GlFilteredSearchTokenSegment
);
const
suggestionsSegment
=
tokenSegments
.
at
(
2
);
suggestionsSegment
.
vm
.
$emit
(
'
activate
'
);
await
wrapper
.
vm
.
$nextTick
();
const
suggestions
=
wrapper
.
findAll
(
GlFilteredSearchSuggestion
);
expect
(
suggestions
).
toHaveLength
(
defaultEmojis
.
length
);
defaultEmojis
.
forEach
((
emoji
,
index
)
=>
{
expect
(
suggestions
.
at
(
index
).
text
()).
toBe
(
emoji
.
text
);
});
});
it
(
'
does not render divider when no defaultEmojis
'
,
async
()
=>
{
wrapper
=
createComponent
({
active
:
true
,
config
:
{
...
mockReactionEmojiToken
,
defaultEmojis
:
[]
},
stubs
:
{
Portal
:
true
,
GlEmoji
},
});
const
tokenSegments
=
wrapper
.
findAll
(
GlFilteredSearchTokenSegment
);
const
suggestionsSegment
=
tokenSegments
.
at
(
2
);
suggestionsSegment
.
vm
.
$emit
(
'
activate
'
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
GlFilteredSearchSuggestion
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
GlDropdownDivider
).
exists
()).
toBe
(
false
);
});
it
(
'
renders `DEFAULT_LABEL_NONE` and `DEFAULT_LABEL_ANY` as default suggestions
'
,
async
()
=>
{
wrapper
=
createComponent
({
active
:
true
,
config
:
{
...
mockReactionEmojiToken
},
stubs
:
{
Portal
:
true
,
GlEmoji
},
});
const
tokenSegments
=
wrapper
.
findAll
(
GlFilteredSearchTokenSegment
);
const
suggestionsSegment
=
tokenSegments
.
at
(
2
);
suggestionsSegment
.
vm
.
$emit
(
'
activate
'
);
await
wrapper
.
vm
.
$nextTick
();
const
suggestions
=
wrapper
.
findAll
(
GlFilteredSearchSuggestion
);
expect
(
suggestions
).
toHaveLength
(
2
);
expect
(
suggestions
.
at
(
0
).
text
()).
toBe
(
DEFAULT_LABEL_NONE
.
text
);
expect
(
suggestions
.
at
(
1
).
text
()).
toBe
(
DEFAULT_LABEL_ANY
.
text
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment