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
0547fd80
Commit
0547fd80
authored
Jun 10, 2021
by
Zack Cuddy
Committed by
Olena Horal-Koretska
Jun 10, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Global Search - SearchableDropdownItem Component
parent
2abc6539
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
211 additions
and
93 deletions
+211
-93
app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
...ascripts/search/topbar/components/searchable_dropdown.vue
+15
-37
app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
...pts/search/topbar/components/searchable_dropdown_item.vue
+73
-0
spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js
...search/topbar/components/searchable_dropdown_item_spec.js
+97
-0
spec/frontend/search/topbar/components/searchable_dropdown_spec.js
...tend/search/topbar/components/searchable_dropdown_spec.js
+26
-56
No files found.
app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
View file @
0547fd80
...
@@ -8,12 +8,10 @@ import {
...
@@ -8,12 +8,10 @@ import {
GlButton
,
GlButton
,
GlSkeletonLoader
,
GlSkeletonLoader
,
GlTooltipDirective
,
GlTooltipDirective
,
GlAvatar
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
highlight
from
'
~/lib/utils/highlight
'
;
import
{
truncateNamespace
}
from
'
~/lib/utils/text_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
ANY_OPTION
}
from
'
../constants
'
;
import
{
ANY_OPTION
}
from
'
../constants
'
;
import
SearchableDropdownItem
from
'
./searchable_dropdown_item.vue
'
;
export
default
{
export
default
{
i18n
:
{
i18n
:
{
...
@@ -28,7 +26,7 @@ export default {
...
@@ -28,7 +26,7 @@ export default {
GlIcon
,
GlIcon
,
GlButton
,
GlButton
,
GlSkeletonLoader
,
GlSkeletonLoader
,
GlAvatar
,
SearchableDropdownItem
,
},
},
directives
:
{
directives
:
{
GlTooltip
:
GlTooltipDirective
,
GlTooltip
:
GlTooltipDirective
,
...
@@ -79,11 +77,8 @@ export default {
...
@@ -79,11 +77,8 @@ export default {
resetDropdown
()
{
resetDropdown
()
{
this
.
$emit
(
'
change
'
,
ANY_OPTION
);
this
.
$emit
(
'
change
'
,
ANY_OPTION
);
},
},
truncateNamespace
(
namespace
)
{
updateDropdown
(
item
)
{
return
truncateNamespace
(
namespace
);
this
.
$emit
(
'
change
'
,
item
);
},
highlightedItemName
(
name
)
{
return
highlight
(
name
,
this
.
searchText
);
},
},
},
},
ANY_OPTION
,
ANY_OPTION
,
...
@@ -97,7 +92,7 @@ export default {
...
@@ -97,7 +92,7 @@ export default {
toggle-class=
"gl-text-truncate"
toggle-class=
"gl-text-truncate"
:header-text=
"headerText"
:header-text=
"headerText"
:right=
"true"
:right=
"true"
@
show=
"
$emit('search', searchText)
"
@
show=
"
openDropdown
"
@
shown=
"$refs.searchBox.focusInput()"
@
shown=
"$refs.searchBox.focusInput()"
>
>
<template
#button-content
>
<template
#button-content
>
...
@@ -126,46 +121,29 @@ export default {
...
@@ -126,46 +121,29 @@ export default {
v-model=
"searchText"
v-model=
"searchText"
class=
"gl-m-3"
class=
"gl-m-3"
:debounce=
"500"
:debounce=
"500"
@
input=
"
$emit('search', searchText)
"
@
input=
"
openDropdown
"
/>
/>
<gl-dropdown-item
<gl-dropdown-item
class=
"gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2"
class=
"gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2"
:is-check-item=
"true"
:is-check-item=
"true"
:is-checked=
"isSelected($options.ANY_OPTION)"
:is-checked=
"isSelected($options.ANY_OPTION)"
:is-check-centered=
"true"
:is-check-centered=
"true"
@
click=
"
resetDropdown
"
@
click=
"
updateDropdown($options.ANY_OPTION)
"
>
>
<span
data-testid=
"item-title"
>
{{ $options.ANY_OPTION.name }}
</span>
<span
data-testid=
"item-title"
>
{{ $options.ANY_OPTION.name }}
</span>
</gl-dropdown-item>
</gl-dropdown-item>
</div>
</div>
<div
v-if=
"!loading"
>
<div
v-if=
"!loading"
>
<
gl
-dropdown-item
<
searchable
-dropdown-item
v-for=
"item in items"
v-for=
"item in items"
:key=
"item.id"
:key=
"item.id"
:is-check-item=
"true"
:item=
"item"
:is-checked=
"isSelected(item)"
:selected-item=
"selectedItem"
:is-check-centered=
"true"
:search-text=
"searchText"
@
click=
"$emit('change', item)"
:name=
"name"
>
:full-name=
"fullName"
<div
class=
"gl-display-flex gl-align-items-center"
>
@
change=
"updateDropdown"
<gl-avatar
/>
:src=
"item.avatar_url"
:entity-id=
"item.id"
:entity-name=
"item[name]"
shape=
"rect"
:size=
"32"
/>
<div
class=
"gl-display-flex gl-flex-direction-column"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<span
data-testid=
"item-title"
v-html=
"highlightedItemName(item[name])"
>
{{
item[name]
}}
</span>
<span
class=
"gl-font-sm gl-text-gray-700"
data-testid=
"item-namespace"
>
{{
truncateNamespace(item[fullName])
}}
</span>
</div>
</div>
</gl-dropdown-item>
</div>
</div>
<div
v-if=
"loading"
class=
"gl-mx-4 gl-mt-3"
>
<div
v-if=
"loading"
class=
"gl-mx-4 gl-mt-3"
>
<gl-skeleton-loader
:height=
"100"
>
<gl-skeleton-loader
:height=
"100"
>
...
...
app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
0 → 100644
View file @
0547fd80
<
script
>
import
{
GlDropdownItem
,
GlAvatar
}
from
'
@gitlab/ui
'
;
import
highlight
from
'
~/lib/utils/highlight
'
;
import
{
truncateNamespace
}
from
'
~/lib/utils/text_utility
'
;
export
default
{
name
:
'
SearchableDropdownItem
'
,
components
:
{
GlDropdownItem
,
GlAvatar
,
},
props
:
{
item
:
{
type
:
Object
,
required
:
true
,
},
selectedItem
:
{
type
:
Object
,
required
:
true
,
},
searchText
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
name
:
{
type
:
String
,
required
:
true
,
},
fullName
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
isSelected
()
{
return
this
.
item
.
id
===
this
.
selectedItem
.
id
;
},
truncatedNamespace
()
{
return
truncateNamespace
(
this
.
item
[
this
.
fullName
]);
},
highlightedItemName
()
{
return
highlight
(
this
.
item
[
this
.
name
],
this
.
searchText
);
},
},
};
</
script
>
<
template
>
<gl-dropdown-item
:is-check-item=
"true"
:is-checked=
"isSelected"
:is-check-centered=
"true"
@
click=
"$emit('change', item)"
>
<div
class=
"gl-display-flex gl-align-items-center"
>
<gl-avatar
:src=
"item.avatar_url"
:entity-id=
"item.id"
:entity-name=
"item[name]"
shape=
"rect"
:size=
"32"
/>
<div
class=
"gl-display-flex gl-flex-direction-column"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<span
data-testid=
"item-title"
v-html=
"highlightedItemName"
>
{{
item
[
name
]
}}
</span>
<span
class=
"gl-font-sm gl-text-gray-700"
data-testid=
"item-namespace"
>
{{
truncatedNamespace
}}
</span>
</div>
</div>
</gl-dropdown-item>
</
template
>
spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js
0 → 100644
View file @
0547fd80
import
{
GlDropdownItem
,
GlAvatar
}
from
'
@gitlab/ui
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
MOCK_GROUPS
}
from
'
jest/search/mock_data
'
;
import
{
truncateNamespace
}
from
'
~/lib/utils/text_utility
'
;
import
SearchableDropdownItem
from
'
~/search/topbar/components/searchable_dropdown_item.vue
'
;
import
{
GROUP_DATA
}
from
'
~/search/topbar/constants
'
;
describe
(
'
Global Search Searchable Dropdown Item
'
,
()
=>
{
let
wrapper
;
const
defaultProps
=
{
item
:
MOCK_GROUPS
[
0
],
selectedItem
:
MOCK_GROUPS
[
0
],
name
:
GROUP_DATA
.
name
,
fullName
:
GROUP_DATA
.
fullName
,
};
const
createComponent
=
(
props
)
=>
{
wrapper
=
shallowMountExtended
(
SearchableDropdownItem
,
{
propsData
:
{
...
defaultProps
,
...
props
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
const
findGlDropdownItem
=
()
=>
wrapper
.
findComponent
(
GlDropdownItem
);
const
findGlAvatar
=
()
=>
wrapper
.
findComponent
(
GlAvatar
);
const
findDropdownTitle
=
()
=>
wrapper
.
findByTestId
(
'
item-title
'
);
const
findDropdownSubtitle
=
()
=>
wrapper
.
findByTestId
(
'
item-namespace
'
);
describe
(
'
template
'
,
()
=>
{
describe
(
'
always
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
renders GlDropdownItem
'
,
()
=>
{
expect
(
findGlDropdownItem
().
exists
()).
toBe
(
true
);
});
it
(
'
renders GlAvatar
'
,
()
=>
{
expect
(
findGlAvatar
().
exists
()).
toBe
(
true
);
});
it
(
'
renders Dropdown Title correctly
'
,
()
=>
{
const
titleEl
=
findDropdownTitle
();
expect
(
titleEl
.
exists
()).
toBe
(
true
);
expect
(
titleEl
.
text
()).
toBe
(
MOCK_GROUPS
[
0
][
GROUP_DATA
.
name
]);
});
it
(
'
renders Dropdown Subtitle correctly
'
,
()
=>
{
const
subtitleEl
=
findDropdownSubtitle
();
expect
(
subtitleEl
.
exists
()).
toBe
(
true
);
expect
(
subtitleEl
.
text
()).
toBe
(
truncateNamespace
(
MOCK_GROUPS
[
0
][
GROUP_DATA
.
fullName
]));
});
});
describe
(
'
when item === selectedItem
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
item
:
MOCK_GROUPS
[
0
],
selectedItem
:
MOCK_GROUPS
[
0
]
});
});
it
(
'
marks the dropdown as checked
'
,
()
=>
{
expect
(
findGlDropdownItem
().
attributes
(
'
ischecked
'
)).
toBe
(
'
true
'
);
});
});
describe
(
'
when item !== selectedItem
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
item
:
MOCK_GROUPS
[
0
],
selectedItem
:
MOCK_GROUPS
[
1
]
});
});
it
(
'
marks the dropdown as not checked
'
,
()
=>
{
expect
(
findGlDropdownItem
().
attributes
(
'
ischecked
'
)).
toBeUndefined
();
});
});
});
describe
(
'
actions
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
clicking the dropdown item $emits change with the item
'
,
()
=>
{
findGlDropdownItem
().
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
change
'
)[
0
]).
toEqual
([
MOCK_GROUPS
[
0
]]);
});
});
});
spec/frontend/search/topbar/components/searchable_dropdown_spec.js
View file @
0547fd80
import
{
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlSkeletonLoader
}
from
'
@gitlab/ui
'
;
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlSkeletonLoader
,
GlAvatar
,
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
{
extendedWrapper
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
MOCK_GROUPS
,
MOCK_GROUP
,
MOCK_QUERY
}
from
'
jest/search/mock_data
'
;
import
{
MOCK_GROUPS
,
MOCK_GROUP
,
MOCK_QUERY
}
from
'
jest/search/mock_data
'
;
import
{
truncateNamespace
}
from
'
~/lib/utils/text_utility
'
;
import
SearchableDropdown
from
'
~/search/topbar/components/searchable_dropdown.vue
'
;
import
SearchableDropdown
from
'
~/search/topbar/components/searchable_dropdown.vue
'
;
import
SearchableDropdownItem
from
'
~/search/topbar/components/searchable_dropdown_item.vue
'
;
import
{
ANY_OPTION
,
GROUP_DATA
}
from
'
~/search/topbar/constants
'
;
import
{
ANY_OPTION
,
GROUP_DATA
}
from
'
~/search/topbar/constants
'
;
Vue
.
use
(
Vuex
);
Vue
.
use
(
Vuex
);
...
@@ -36,37 +29,27 @@ describe('Global Search Searchable Dropdown', () => {
...
@@ -36,37 +29,27 @@ describe('Global Search Searchable Dropdown', () => {
},
},
});
});
wrapper
=
extendedWrapper
(
wrapper
=
mountFn
(
SearchableDropdown
,
{
mountFn
(
SearchableDropdown
,
{
store
,
store
,
propsData
:
{
propsData
:
{
...
defaultProps
,
...
defaultProps
,
...
props
,
...
props
,
},
},
});
}),
);
};
};
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
wrapper
=
null
;
});
});
const
findGlDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
findGlDropdown
=
()
=>
wrapper
.
find
Component
(
GlDropdown
);
const
findGlDropdownSearch
=
()
=>
findGlDropdown
().
find
(
GlSearchBoxByType
);
const
findGlDropdownSearch
=
()
=>
findGlDropdown
().
find
Component
(
GlSearchBoxByType
);
const
findDropdownText
=
()
=>
findGlDropdown
().
find
(
'
.dropdown-toggle-text
'
);
const
findDropdownText
=
()
=>
findGlDropdown
().
find
(
'
.dropdown-toggle-text
'
);
const
findDropdownItems
=
()
=>
findGlDropdown
().
findAll
(
GlDropdownItem
);
const
findSearchableDropdownItems
=
()
=>
const
findDropdownItemTitles
=
()
=>
wrapper
.
findAllByTestId
(
'
item-title
'
);
findGlDropdown
().
findAllComponents
(
SearchableDropdownItem
);
const
findDropdownItemNamespaces
=
()
=>
wrapper
.
findAllByTestId
(
'
item-namespace
'
);
const
findAnyDropdownItem
=
()
=>
findGlDropdown
().
findComponent
(
GlDropdownItem
);
const
findDropdownAvatars
=
()
=>
wrapper
.
findAllComponents
(
GlAvatar
);
const
findFirstGroupDropdownItem
=
()
=>
findSearchableDropdownItems
().
at
(
0
);
const
findAnyDropdownItem
=
()
=>
findDropdownItems
().
at
(
0
);
const
findLoader
=
()
=>
wrapper
.
findComponent
(
GlSkeletonLoader
);
const
findFirstGroupDropdownItem
=
()
=>
findDropdownItems
().
at
(
1
);
const
findLoader
=
()
=>
wrapper
.
find
(
GlSkeletonLoader
);
const
findDropdownItemTitlesText
=
()
=>
findDropdownItemTitles
().
wrappers
.
map
((
w
)
=>
w
.
text
());
const
findDropdownItemNamespacesText
=
()
=>
findDropdownItemNamespaces
().
wrappers
.
map
((
w
)
=>
w
.
text
());
const
findDropdownAvatarUrls
=
()
=>
findDropdownAvatars
().
wrappers
.
map
((
w
)
=>
w
.
props
(
'
src
'
));
describe
(
'
template
'
,
()
=>
{
describe
(
'
template
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
...
@@ -109,19 +92,12 @@ describe('Global Search Searchable Dropdown', () => {
...
@@ -109,19 +92,12 @@ describe('Global Search Searchable Dropdown', () => {
expect
(
findLoader
().
exists
()).
toBe
(
false
);
expect
(
findLoader
().
exists
()).
toBe
(
false
);
});
});
it
(
'
renders titles correctly including Any
'
,
()
=>
{
it
(
'
renders the Any Dropdown
'
,
()
=>
{
const
resultsIncludeAny
=
[
'
Any
'
].
concat
(
MOCK_GROUPS
.
map
((
n
)
=>
n
[
GROUP_DATA
.
name
]));
expect
(
findAnyDropdownItem
().
exists
()).
toBe
(
true
);
expect
(
findDropdownItemTitlesText
()).
toStrictEqual
(
resultsIncludeAny
);
});
it
(
'
renders namespaces truncated correctly
'
,
()
=>
{
const
namespaces
=
MOCK_GROUPS
.
map
((
n
)
=>
truncateNamespace
(
n
[
GROUP_DATA
.
fullName
]));
expect
(
findDropdownItemNamespacesText
()).
toStrictEqual
(
namespaces
);
});
});
it
(
'
renders GlAvatar for each item
'
,
()
=>
{
it
(
'
renders SearchableDropdownItem for each item
'
,
()
=>
{
const
avatars
=
MOCK_GROUPS
.
map
((
n
)
=>
n
.
avatar_url
);
expect
(
findSearchableDropdownItems
()).
toHaveLength
(
MOCK_GROUPS
.
length
);
expect
(
findDropdownAvatarUrls
()).
toStrictEqual
(
avatars
);
});
});
});
});
...
@@ -134,18 +110,12 @@ describe('Global Search Searchable Dropdown', () => {
...
@@ -134,18 +110,12 @@ describe('Global Search Searchable Dropdown', () => {
expect
(
findLoader
().
exists
()).
toBe
(
true
);
expect
(
findLoader
().
exists
()).
toBe
(
true
);
});
});
it
(
'
renders only Any in dropdown
'
,
()
=>
{
it
(
'
renders the Any Dropdown
'
,
()
=>
{
expect
(
findDropdownItemTitlesText
()).
toStrictEqual
([
'
Any
'
]);
expect
(
findAnyDropdownItem
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when item is selected
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({},
{
items
:
MOCK_GROUPS
,
selectedItem
:
MOCK_GROUPS
[
0
]
});
});
});
it
(
'
marks the dropdown as checked
'
,
()
=>
{
it
(
'
does not render SearchableDropdownItem
'
,
()
=>
{
expect
(
find
FirstGroupDropdownItem
().
attributes
(
'
ischecked
'
)).
toBe
(
'
true
'
);
expect
(
find
SearchableDropdownItems
()).
toHaveLength
(
0
);
});
});
});
});
});
});
...
@@ -184,8 +154,8 @@ describe('Global Search Searchable Dropdown', () => {
...
@@ -184,8 +154,8 @@ describe('Global Search Searchable Dropdown', () => {
expect
(
wrapper
.
emitted
(
'
change
'
)[
0
]).
toEqual
([
ANY_OPTION
]);
expect
(
wrapper
.
emitted
(
'
change
'
)[
0
]).
toEqual
([
ANY_OPTION
]);
});
});
it
(
'
clicking result dropdown item $emits @change with result
'
,
()
=>
{
it
(
'
on SearchableDropdownItem @change, the wrapper $emits change with the item
'
,
()
=>
{
findFirstGroupDropdownItem
().
vm
.
$emit
(
'
c
lick
'
);
findFirstGroupDropdownItem
().
vm
.
$emit
(
'
c
hange
'
,
MOCK_GROUPS
[
0
]
);
expect
(
wrapper
.
emitted
(
'
change
'
)[
0
]).
toEqual
([
MOCK_GROUPS
[
0
]]);
expect
(
wrapper
.
emitted
(
'
change
'
)[
0
]).
toEqual
([
MOCK_GROUPS
[
0
]]);
});
});
...
...
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