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
0ebb1a5d
Commit
0ebb1a5d
authored
Jun 03, 2021
by
Paul Slaughter
Committed by
Jose Ivan Vargas
Jun 03, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add top_nav_menu_sections component
parent
9641dac4
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
295 additions
and
179 deletions
+295
-179
app/assets/javascripts/nav/components/top_nav_container_view.vue
...ets/javascripts/nav/components/top_nav_container_view.vue
+7
-20
app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue
...sets/javascripts/nav/components/top_nav_dropdown_menu.vue
+21
-62
app/assets/javascripts/nav/components/top_nav_menu_item.vue
app/assets/javascripts/nav/components/top_nav_menu_item.vue
+4
-1
app/assets/javascripts/nav/components/top_nav_menu_sections.vue
...sets/javascripts/nav/components/top_nav_menu_sections.vue
+62
-0
spec/frontend/nav/components/top_nav_container_view_spec.js
spec/frontend/nav/components/top_nav_container_view_spec.js
+13
-24
spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
+56
-65
spec/frontend/nav/components/top_nav_menu_item_spec.js
spec/frontend/nav/components/top_nav_menu_item_spec.js
+25
-7
spec/frontend/nav/components/top_nav_menu_sections_spec.js
spec/frontend/nav/components/top_nav_menu_sections_spec.js
+107
-0
No files found.
app/assets/javascripts/nav/components/top_nav_container_view.vue
View file @
0ebb1a5d
...
@@ -2,12 +2,12 @@
...
@@ -2,12 +2,12 @@
import
FrequentItemsApp
from
'
~/frequent_items/components/app.vue
'
;
import
FrequentItemsApp
from
'
~/frequent_items/components/app.vue
'
;
import
eventHub
from
'
~/frequent_items/event_hub
'
;
import
eventHub
from
'
~/frequent_items/event_hub
'
;
import
VuexModuleProvider
from
'
~/vue_shared/components/vuex_module_provider.vue
'
;
import
VuexModuleProvider
from
'
~/vue_shared/components/vuex_module_provider.vue
'
;
import
TopNavMenu
Item
from
'
./top_nav_menu_item
.vue
'
;
import
TopNavMenu
Sections
from
'
./top_nav_menu_sections
.vue
'
;
export
default
{
export
default
{
components
:
{
components
:
{
FrequentItemsApp
,
FrequentItemsApp
,
TopNavMenu
Item
,
TopNavMenu
Sections
,
VuexModuleProvider
,
VuexModuleProvider
,
},
},
inheritAttrs
:
false
,
inheritAttrs
:
false
,
...
@@ -32,11 +32,11 @@ export default {
...
@@ -32,11 +32,11 @@ export default {
},
},
},
},
computed
:
{
computed
:
{
linkGroup
s
()
{
menuSection
s
()
{
return
[
return
[
{
key
:
'
primary
'
,
link
s
:
this
.
linksPrimary
},
{
id
:
'
primary
'
,
menuItem
s
:
this
.
linksPrimary
},
{
key
:
'
secondary
'
,
link
s
:
this
.
linksSecondary
},
{
id
:
'
secondary
'
,
menuItem
s
:
this
.
linksSecondary
},
].
filter
((
x
)
=>
x
.
link
s
?.
length
);
].
filter
((
x
)
=>
x
.
menuItem
s
?.
length
);
},
},
},
},
mounted
()
{
mounted
()
{
...
@@ -57,19 +57,6 @@ export default {
...
@@ -57,19 +57,6 @@ export default {
</vuex-module-provider>
</vuex-module-provider>
</div>
</div>
</div>
</div>
<div
<top-nav-menu-sections
class=
"gl-mt-auto"
:sections=
"menuSections"
with-top-border
/>
v-for=
"(
{ key, links }, groupIndex) in linkGroups"
:key="key"
:class="{ 'gl-mt-3': groupIndex !== 0 }"
class="gl-mt-auto gl-pt-3 gl-border-1 gl-border-t-solid gl-border-gray-100"
data-testid="menu-item-group"
>
<top-nav-menu-item
v-for=
"(link, linkIndex) in links"
:key=
"link.title"
:menu-item=
"link"
:class=
"
{ 'gl-mt-1': linkIndex !== 0 }"
/>
</div>
</div>
</div>
</
template
>
</
template
>
app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue
View file @
0ebb1a5d
<
script
>
<
script
>
import
{
cloneDeep
}
from
'
lodash
'
;
import
{
FREQUENT_ITEMS_PROJECTS
,
FREQUENT_ITEMS_GROUPS
}
from
'
~/frequent_items/constants
'
;
import
{
FREQUENT_ITEMS_PROJECTS
,
FREQUENT_ITEMS_GROUPS
}
from
'
~/frequent_items/constants
'
;
import
KeepAliveSlots
from
'
~/vue_shared/components/keep_alive_slots.vue
'
;
import
KeepAliveSlots
from
'
~/vue_shared/components/keep_alive_slots.vue
'
;
import
TopNavContainerView
from
'
./top_nav_container_view.vue
'
;
import
TopNavContainerView
from
'
./top_nav_container_view.vue
'
;
import
TopNavMenuItem
from
'
./top_nav_menu_item.vue
'
;
import
TopNavMenuSections
from
'
./top_nav_menu_sections.vue
'
;
const
ACTIVE_CLASS
=
'
gl-shadow-none! gl-font-weight-bold! active
'
;
const
SECONDARY_GROUP_CLASS
=
'
gl-pt-3 gl-mt-3 gl-border-1 gl-border-t-solid gl-border-gray-100
'
;
export
default
{
export
default
{
components
:
{
components
:
{
KeepAliveSlots
,
KeepAliveSlots
,
TopNavContainerView
,
TopNavContainerView
,
TopNavMenu
Item
,
TopNavMenu
Sections
,
},
},
props
:
{
props
:
{
primary
:
{
primary
:
{
...
@@ -31,29 +29,25 @@ export default {
...
@@ -31,29 +29,25 @@ export default {
},
},
},
},
data
()
{
data
()
{
// It's expected that primary & secondary never change, so these are treated as "init" props.
// We need to clone so that we can mutate the data without mutating the props
const
menuSections
=
[
{
id
:
'
primary
'
,
menuItems
:
cloneDeep
(
this
.
primary
)
},
{
id
:
'
secondary
'
,
menuItems
:
cloneDeep
(
this
.
secondary
)
},
].
filter
((
x
)
=>
x
.
menuItems
?.
length
);
return
{
return
{
activeId
:
''
,
menuSections
,
};
};
},
},
computed
:
{
computed
:
{
menuItemGroups
()
{
return
[
{
key
:
'
primary
'
,
items
:
this
.
primary
,
classes
:
''
},
{
key
:
'
secondary
'
,
items
:
this
.
secondary
,
classes
:
SECONDARY_GROUP_CLASS
,
},
].
filter
((
x
)
=>
x
.
items
?.
length
);
},
allMenuItems
()
{
allMenuItems
()
{
return
this
.
menuItemGroups
.
flatMap
((
x
)
=>
x
.
items
);
return
this
.
menuSections
.
flatMap
((
x
)
=>
x
.
menuItems
);
},
activeMenuItem
()
{
return
this
.
allMenuItems
.
find
((
x
)
=>
x
.
id
===
this
.
activeId
);
},
},
activeView
()
{
activeView
()
{
return
this
.
activeMenuItem
?.
view
;
const
active
=
this
.
allMenuItems
.
find
((
x
)
=>
x
.
active
);
return
active
?.
view
;
},
},
menuClass
()
{
menuClass
()
{
if
(
!
this
.
activeView
)
{
if
(
!
this
.
activeView
)
{
...
@@ -63,61 +57,26 @@ export default {
...
@@ -63,61 +57,26 @@ export default {
return
''
;
return
''
;
},
},
},
},
created
()
{
// Initialize activeId based on initialization prop
this
.
activeId
=
this
.
allMenuItems
.
find
((
x
)
=>
x
.
active
)?.
id
;
},
methods
:
{
methods
:
{
onClick
({
id
,
href
})
{
onMenuItemClick
({
id
})
{
// If we're a link, let's just do the default behavior so the view won't change
this
.
allMenuItems
.
forEach
((
menuItem
)
=>
{
if
(
href
)
{
this
.
$set
(
menuItem
,
'
active
'
,
id
===
menuItem
.
id
);
return
;
});
}
this
.
activeId
=
id
;
},
menuItemClasses
(
menuItem
)
{
if
(
menuItem
.
id
===
this
.
activeId
)
{
return
ACTIVE_CLASS
;
}
return
''
;
},
},
},
},
FREQUENT_ITEMS_PROJECTS
,
FREQUENT_ITEMS_PROJECTS
,
FREQUENT_ITEMS_GROUPS
,
FREQUENT_ITEMS_GROUPS
,
// expose for unit tests
ACTIVE_CLASS
,
SECONDARY_GROUP_CLASS
,
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"gl-display-flex gl-align-items-stretch"
>
<div
class=
"gl-display-flex gl-align-items-stretch"
>
<div
<div
class=
"gl-w-grid-size-30 gl-flex-shrink-0 gl-bg-gray-10"
class=
"gl-w-grid-size-30 gl-flex-shrink-0 gl-bg-gray-10
gl-py-3 gl-px-5
"
:class=
"menuClass"
:class=
"menuClass"
data-testid=
"menu-sidebar"
data-testid=
"menu-sidebar"
>
>
<div
<top-nav-menu-sections
:sections=
"menuSections"
@
menu-item-click=
"onMenuItemClick"
/>
class=
"gl-py-3 gl-px-5 gl-h-full gl-display-flex gl-align-items-stretch gl-flex-direction-column"
>
<div
v-for=
"group in menuItemGroups"
:key=
"group.key"
:class=
"group.classes"
data-testid=
"menu-item-group"
>
<top-nav-menu-item
v-for=
"(menu, index) in group.items"
:key=
"menu.id"
data-testid=
"menu-item"
:class=
"[
{ 'gl-mt-1': index !== 0 }, menuItemClasses(menu)]"
:menu-item="menu"
@click="onClick(menu)"
/>
</div>
</div>
</div>
</div>
<keep-alive-slots
<keep-alive-slots
v-show=
"activeView"
v-show=
"activeView"
...
...
app/assets/javascripts/nav/components/top_nav_menu_item.vue
View file @
0ebb1a5d
...
@@ -4,6 +4,8 @@ import { kebabCase, mapKeys } from 'lodash';
...
@@ -4,6 +4,8 @@ import { kebabCase, mapKeys } from 'lodash';
const
getDataKey
=
(
key
)
=>
`data-
${
kebabCase
(
key
)}
`
;
const
getDataKey
=
(
key
)
=>
`data-
${
kebabCase
(
key
)}
`
;
const
ACTIVE_CLASS
=
'
gl-shadow-none! gl-font-weight-bold! active
'
;
export
default
{
export
default
{
components
:
{
components
:
{
GlButton
,
GlButton
,
...
@@ -20,6 +22,7 @@ export default {
...
@@ -20,6 +22,7 @@ export default {
return
mapKeys
(
this
.
menuItem
.
data
||
{},
(
value
,
key
)
=>
getDataKey
(
key
));
return
mapKeys
(
this
.
menuItem
.
data
||
{},
(
value
,
key
)
=>
getDataKey
(
key
));
},
},
},
},
ACTIVE_CLASS
,
};
};
</
script
>
</
script
>
...
@@ -28,7 +31,7 @@ export default {
...
@@ -28,7 +31,7 @@ export default {
category=
"tertiary"
category=
"tertiary"
:href=
"menuItem.href"
:href=
"menuItem.href"
class=
"top-nav-menu-item gl-display-block"
class=
"top-nav-menu-item gl-display-block"
:class=
"
menuItem.css_class
"
:class=
"
[menuItem.css_class,
{ [$options.ACTIVE_CLASS]: menuItem.active }]
"
v-bind="dataAttrs"
v-bind="dataAttrs"
v-on="$listeners"
v-on="$listeners"
>
>
...
...
app/assets/javascripts/nav/components/top_nav_menu_sections.vue
0 → 100644
View file @
0ebb1a5d
<
script
>
import
TopNavMenuItem
from
'
./top_nav_menu_item.vue
'
;
const
BORDER_CLASSES
=
'
gl-pt-3 gl-border-1 gl-border-t-solid gl-border-gray-100
'
;
export
default
{
components
:
{
TopNavMenuItem
,
},
props
:
{
sections
:
{
type
:
Array
,
required
:
true
,
},
withTopBorder
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
methods
:
{
onClick
(
menuItem
)
{
// If we're a link, let's just do the default behavior so the view won't change
if
(
menuItem
.
href
)
{
return
;
}
this
.
$emit
(
'
menu-item-click
'
,
menuItem
);
},
getMenuSectionClasses
(
index
)
{
// This is a method instead of a computed so we don't have to incur the cost of
// creating a whole new array/object.
return
{
[
BORDER_CLASSES
]:
this
.
withTopBorder
||
index
>
0
,
'
gl-mt-3
'
:
index
>
0
,
};
},
},
// Expose for unit tests
BORDER_CLASSES
,
};
</
script
>
<
template
>
<div
class=
"gl-display-flex gl-align-items-stretch gl-flex-direction-column"
>
<div
v-for=
"(
{ id, menuItems }, sectionIndex) in sections"
:key="id"
:class="getMenuSectionClasses(sectionIndex)"
data-testid="menu-section"
>
<top-nav-menu-item
v-for=
"(menuItem, menuItemIndex) in menuItems"
:key=
"menuItem.id"
:menu-item=
"menuItem"
data-testid=
"menu-item"
:class=
"
{ 'gl-mt-1': menuItemIndex > 0 }"
@click="onClick(menuItem)"
/>
</div>
</div>
</
template
>
spec/frontend/nav/components/top_nav_container_view_spec.js
View file @
0ebb1a5d
...
@@ -4,7 +4,7 @@ import FrequentItemsApp from '~/frequent_items/components/app.vue';
...
@@ -4,7 +4,7 @@ import FrequentItemsApp from '~/frequent_items/components/app.vue';
import
{
FREQUENT_ITEMS_PROJECTS
}
from
'
~/frequent_items/constants
'
;
import
{
FREQUENT_ITEMS_PROJECTS
}
from
'
~/frequent_items/constants
'
;
import
eventHub
from
'
~/frequent_items/event_hub
'
;
import
eventHub
from
'
~/frequent_items/event_hub
'
;
import
TopNavContainerView
from
'
~/nav/components/top_nav_container_view.vue
'
;
import
TopNavContainerView
from
'
~/nav/components/top_nav_container_view.vue
'
;
import
TopNavMenu
Item
from
'
~/nav/components/top_nav_menu_item
.vue
'
;
import
TopNavMenu
Sections
from
'
~/nav/components/top_nav_menu_sections
.vue
'
;
import
VuexModuleProvider
from
'
~/vue_shared/components/vuex_module_provider.vue
'
;
import
VuexModuleProvider
from
'
~/vue_shared/components/vuex_module_provider.vue
'
;
import
{
TEST_NAV_DATA
}
from
'
../mock_data
'
;
import
{
TEST_NAV_DATA
}
from
'
../mock_data
'
;
...
@@ -34,11 +34,7 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
...
@@ -34,11 +34,7 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
});
};
};
const
findMenuItems
=
(
parent
=
wrapper
)
=>
parent
.
findAll
(
TopNavMenuItem
);
const
findMenuSections
=
()
=>
wrapper
.
findComponent
(
TopNavMenuSections
);
const
findMenuItemsModel
=
(
parent
=
wrapper
)
=>
findMenuItems
(
parent
).
wrappers
.
map
((
x
)
=>
x
.
props
());
const
findMenuItemGroups
=
()
=>
wrapper
.
findAll
(
'
[data-testid="menu-item-group"]
'
);
const
findMenuItemGroupsModel
=
()
=>
findMenuItemGroups
().
wrappers
.
map
(
findMenuItemsModel
);
const
findFrequentItemsApp
=
()
=>
{
const
findFrequentItemsApp
=
()
=>
{
const
parent
=
wrapper
.
findComponent
(
VuexModuleProvider
);
const
parent
=
wrapper
.
findComponent
(
VuexModuleProvider
);
...
@@ -89,23 +85,16 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
...
@@ -89,23 +85,16 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
});
});
});
it
(
'
renders menu item groups
'
,
()
=>
{
it
(
'
renders menu sections
'
,
()
=>
{
expect
(
findMenuItemGroupsModel
()).
toEqual
([
const
sections
=
[
TEST_NAV_DATA
.
primary
.
map
((
menuItem
)
=>
({
menuItem
})),
{
id
:
'
primary
'
,
menuItems
:
TEST_NAV_DATA
.
primary
},
TEST_NAV_DATA
.
secondary
.
map
((
menuItem
)
=>
({
menuItem
})),
{
id
:
'
secondary
'
,
menuItems
:
TEST_NAV_DATA
.
secondary
},
]);
];
});
it
(
'
only the first group does not have margin top
'
,
()
=>
{
expect
(
findMenuSections
().
props
()).
toEqual
({
expect
(
findMenuItemGroups
().
wrappers
.
map
((
x
)
=>
x
.
classes
(
'
gl-mt-3
'
))).
toEqual
([
false
,
true
]);
sections
,
withTopBorder
:
true
,
});
});
it
(
'
only the first menu item does not have margin top
'
,
()
=>
{
const
actual
=
findMenuItems
(
findMenuItemGroups
().
at
(
1
)).
wrappers
.
map
((
x
)
=>
x
.
classes
(
'
gl-mt-1
'
),
);
expect
(
actual
).
toEqual
([
false
,
...
TEST_NAV_DATA
.
secondary
.
slice
(
1
).
fill
(
true
)]);
});
});
});
});
...
@@ -117,8 +106,8 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
...
@@ -117,8 +106,8 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
});
it
(
'
renders one menu item group
'
,
()
=>
{
it
(
'
renders one menu item group
'
,
()
=>
{
expect
(
findMenu
ItemGroupsModel
(
)).
toEqual
([
expect
(
findMenu
Sections
().
props
(
'
sections
'
)).
toEqual
([
TEST_NAV_DATA
.
primary
.
map
((
menuItem
)
=>
({
menuItem
}))
,
{
id
:
'
primary
'
,
menuItems
:
TEST_NAV_DATA
.
primary
}
,
]);
]);
});
});
});
});
...
...
spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
View file @
0ebb1a5d
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
{
nextTick
}
from
'
vue
'
;
import
{
nextTick
}
from
'
vue
'
;
import
TopNavDropdownMenu
from
'
~/nav/components/top_nav_dropdown_menu.vue
'
;
import
TopNavDropdownMenu
from
'
~/nav/components/top_nav_dropdown_menu.vue
'
;
import
TopNavMenuItem
from
'
~/nav/components/top_nav_menu_item.vue
'
;
import
TopNavMenuSections
from
'
~/nav/components/top_nav_menu_sections.vue
'
;
import
KeepAliveSlots
from
'
~/vue_shared/components/keep_alive_slots.vue
'
;
import
KeepAliveSlots
from
'
~/vue_shared/components/keep_alive_slots.vue
'
;
import
{
TEST_NAV_DATA
}
from
'
../mock_data
'
;
import
{
TEST_NAV_DATA
}
from
'
../mock_data
'
;
const
SECONDARY_GROUP_CLASSES
=
TopNavDropdownMenu
.
SECONDARY_GROUP_CLASS
.
split
(
'
'
);
describe
(
'
~/nav/components/top_nav_dropdown_menu.vue
'
,
()
=>
{
describe
(
'
~/nav/components/top_nav_dropdown_menu.vue
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
const
createComponent
=
(
props
=
{}
,
mountFn
=
shallowMount
)
=>
{
wrapper
=
shallowMount
(
TopNavDropdownMenu
,
{
wrapper
=
mountFn
(
TopNavDropdownMenu
,
{
propsData
:
{
propsData
:
{
primary
:
TEST_NAV_DATA
.
primary
,
primary
:
TEST_NAV_DATA
.
primary
,
secondary
:
TEST_NAV_DATA
.
secondary
,
secondary
:
TEST_NAV_DATA
.
secondary
,
views
:
TEST_NAV_DATA
.
views
,
views
:
TEST_NAV_DATA
.
views
,
...
props
,
...
props
,
},
},
stubs
:
{
// Stub the keep-alive-slots so we don't render frequent items which uses a store
KeepAliveSlots
:
true
,
},
});
});
};
};
const
findMenuItems
=
(
parent
=
wrapper
)
=>
parent
.
findAll
(
'
[data-testid="menu-item"]
'
);
const
findMenuItems
=
()
=>
wrapper
.
findAllComponents
(
TopNavMenuItem
);
const
findMenuItemsModel
=
(
parent
=
wrapper
)
=>
const
findMenuSections
=
()
=>
wrapper
.
find
(
TopNavMenuSections
);
findMenuItems
(
parent
).
wrappers
.
map
((
x
)
=>
({
menuItem
:
x
.
props
(
'
menuItem
'
),
isActive
:
x
.
classes
(
'
active
'
),
}));
const
findMenuItemGroups
=
()
=>
wrapper
.
findAll
(
'
[data-testid="menu-item-group"]
'
);
const
findMenuItemGroupsModel
=
()
=>
findMenuItemGroups
().
wrappers
.
map
((
x
)
=>
({
classes
:
x
.
classes
(),
items
:
findMenuItemsModel
(
x
),
}));
const
findMenuSidebar
=
()
=>
wrapper
.
find
(
'
[data-testid="menu-sidebar"]
'
);
const
findMenuSidebar
=
()
=>
wrapper
.
find
(
'
[data-testid="menu-sidebar"]
'
);
const
findMenuSubview
=
()
=>
wrapper
.
findComponent
(
KeepAliveSlots
);
const
findMenuSubview
=
()
=>
wrapper
.
findComponent
(
KeepAliveSlots
);
const
hasFullWidthMenuSidebar
=
()
=>
findMenuSidebar
().
classes
(
'
gl-w-full
'
);
const
hasFullWidthMenuSidebar
=
()
=>
findMenuSidebar
().
classes
(
'
gl-w-full
'
);
const
createItemsGroupModelExpectation
=
({
const
withActiveIndex
=
(
menuItems
,
activeIndex
)
=>
primary
=
TEST_NAV_DATA
.
primary
,
menuItems
.
map
((
x
,
idx
)
=>
({
secondary
=
TEST_NAV_DATA
.
secondary
,
...
x
,
activeIndex
=
-
1
,
active
:
idx
===
activeIndex
,
}
=
{})
=>
[
}));
{
classes
:
[],
items
:
primary
.
map
((
menuItem
,
index
)
=>
({
isActive
:
index
===
activeIndex
,
menuItem
})),
},
{
classes
:
SECONDARY_GROUP_CLASSES
,
items
:
secondary
.
map
((
menuItem
)
=>
({
isActive
:
false
,
menuItem
})),
},
];
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
});
});
beforeEach
(()
=>
{
jest
.
spyOn
(
console
,
'
error
'
).
mockImplementation
();
});
describe
(
'
default
'
,
()
=>
{
describe
(
'
default
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createComponent
();
createComponent
();
});
});
it
(
'
renders menu item groups
'
,
()
=>
{
it
(
'
renders menu sections
'
,
()
=>
{
expect
(
findMenuItemGroupsModel
()).
toEqual
(
createItemsGroupModelExpectation
());
expect
(
findMenuSections
().
props
()).
toEqual
({
sections
:
[
{
id
:
'
primary
'
,
menuItems
:
TEST_NAV_DATA
.
primary
},
{
id
:
'
secondary
'
,
menuItems
:
TEST_NAV_DATA
.
secondary
},
],
withTopBorder
:
false
,
});
});
});
it
(
'
has full width menu sidebar
'
,
()
=>
{
it
(
'
has full width menu sidebar
'
,
()
=>
{
...
@@ -74,36 +69,25 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
...
@@ -74,36 +69,25 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
expect
(
subview
.
isVisible
()).
toBe
(
false
);
expect
(
subview
.
isVisible
()).
toBe
(
false
);
expect
(
subview
.
props
()).
toEqual
({
slotKey
:
''
});
expect
(
subview
.
props
()).
toEqual
({
slotKey
:
''
});
});
});
it
(
'
the first menu item in a group does not render margin top
'
,
()
=>
{
const
actual
=
findMenuItems
(
findMenuItemGroups
().
at
(
0
)).
wrappers
.
map
((
x
)
=>
x
.
classes
(
'
gl-mt-1
'
),
);
expect
(
actual
).
toEqual
([
false
,
...
TEST_NAV_DATA
.
primary
.
slice
(
1
).
fill
(
true
)]);
});
});
});
describe
(
'
with pre-initialized active view
'
,
()
=>
{
describe
(
'
with pre-initialized active view
'
,
()
=>
{
const
primaryWithActive
=
[
beforeEach
(()
=>
{
TEST_NAV_DATA
.
primary
[
0
],
// We opt for a small integration test, to make sure the event is handled correctly
// as it would in prod.
createComponent
(
{
{
...
TEST_NAV_DATA
.
primary
[
1
],
primary
:
withActiveIndex
(
TEST_NAV_DATA
.
primary
,
1
),
active
:
true
,
},
},
...
TEST_NAV_DATA
.
primary
.
slice
(
2
),
mount
,
];
);
beforeEach
(()
=>
{
createComponent
({
primary
:
primaryWithActive
,
});
});
});
it
(
'
renders menu item groups
'
,
()
=>
{
it
(
'
renders menu sections
'
,
()
=>
{
expect
(
findMenuItemGroupsModel
()).
toEqual
(
expect
(
findMenuSections
().
props
(
'
sections
'
)).
toStrictEqual
([
createItemsGroupModelExpectation
({
primary
:
primaryWithActive
,
activeIndex
:
1
}),
{
id
:
'
primary
'
,
menuItems
:
withActiveIndex
(
TEST_NAV_DATA
.
primary
,
1
)
},
);
{
id
:
'
secondary
'
,
menuItems
:
TEST_NAV_DATA
.
secondary
},
]);
});
});
it
(
'
does not have full width menu sidebar
'
,
()
=>
{
it
(
'
does not have full width menu sidebar
'
,
()
=>
{
...
@@ -114,11 +98,11 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
...
@@ -114,11 +98,11 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
const
subview
=
findMenuSubview
();
const
subview
=
findMenuSubview
();
expect
(
subview
.
isVisible
()).
toBe
(
true
);
expect
(
subview
.
isVisible
()).
toBe
(
true
);
expect
(
subview
.
props
(
'
slotKey
'
)).
toBe
(
primaryWithActive
[
1
].
view
);
expect
(
subview
.
props
(
'
slotKey
'
)).
toBe
(
TEST_NAV_DATA
.
primary
[
1
].
view
);
});
});
it
(
'
does not change view if non-view menu item is clicked
'
,
async
()
=>
{
it
(
'
does not change view if non-view menu item is clicked
'
,
async
()
=>
{
const
secondaryLink
=
findMenuItems
().
at
(
primaryWithActive
.
length
);
const
secondaryLink
=
findMenuItems
().
at
(
TEST_NAV_DATA
.
primary
.
length
);
// Ensure this doesn't have a view
// Ensure this doesn't have a view
expect
(
secondaryLink
.
props
(
'
menuItem
'
).
view
).
toBeUndefined
();
expect
(
secondaryLink
.
props
(
'
menuItem
'
).
view
).
toBeUndefined
();
...
@@ -127,10 +111,10 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
...
@@ -127,10 +111,10 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
await
nextTick
();
await
nextTick
();
expect
(
findMenuSubview
().
props
(
'
slotKey
'
)).
toBe
(
primaryWithActive
[
1
].
view
);
expect
(
findMenuSubview
().
props
(
'
slotKey
'
)).
toBe
(
TEST_NAV_DATA
.
primary
[
1
].
view
);
});
});
describe
(
'
when
other view
menu item is clicked
'
,
()
=>
{
describe
(
'
when menu item is clicked
'
,
()
=>
{
let
primaryLink
;
let
primaryLink
;
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
...
@@ -144,13 +128,20 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
...
@@ -144,13 +128,20 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
});
});
it
(
'
changes active view
'
,
()
=>
{
it
(
'
changes active view
'
,
()
=>
{
expect
(
findMenuSubview
().
props
(
'
slotKey
'
)).
toBe
(
primaryWithActive
[
0
].
view
);
expect
(
findMenuSubview
().
props
(
'
slotKey
'
)).
toBe
(
TEST_NAV_DATA
.
primary
[
0
].
view
);
});
});
it
(
'
changes active status on menu item
'
,
()
=>
{
it
(
'
changes active status on menu item
'
,
()
=>
{
expect
(
findMenuItemGroupsModel
()).
toStrictEqual
(
expect
(
findMenuSections
().
props
(
'
sections
'
)).
toStrictEqual
([
createItemsGroupModelExpectation
({
primary
:
primaryWithActive
,
activeIndex
:
0
}),
{
);
id
:
'
primary
'
,
menuItems
:
withActiveIndex
(
TEST_NAV_DATA
.
primary
,
0
),
},
{
id
:
'
secondary
'
,
menuItems
:
withActiveIndex
(
TEST_NAV_DATA
.
secondary
,
-
1
),
},
]);
});
});
});
});
});
});
...
...
spec/frontend/nav/components/top_nav_menu_item_spec.js
View file @
0ebb1a5d
...
@@ -7,7 +7,6 @@ const TEST_MENU_ITEM = {
...
@@ -7,7 +7,6 @@ const TEST_MENU_ITEM = {
icon
:
'
search
'
,
icon
:
'
search
'
,
href
:
'
/pretty/good/burger
'
,
href
:
'
/pretty/good/burger
'
,
view
:
'
burger-view
'
,
view
:
'
burger-view
'
,
css_class
:
'
test-super-crazy test-class
'
,
data
:
{
qa_selector
:
'
not-a-real-selector
'
,
method
:
'
post
'
,
testFoo
:
'
test
'
},
data
:
{
qa_selector
:
'
not-a-real-selector
'
,
method
:
'
post
'
,
testFoo
:
'
test
'
},
};
};
...
@@ -49,12 +48,6 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
...
@@ -49,12 +48,6 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
expect
(
button
.
text
()).
toBe
(
TEST_MENU_ITEM
.
title
);
expect
(
button
.
text
()).
toBe
(
TEST_MENU_ITEM
.
title
);
});
});
it
(
'
renders button classes
'
,
()
=>
{
const
button
=
findButton
();
expect
(
button
.
classes
()).
toEqual
(
expect
.
arrayContaining
(
TEST_MENU_ITEM
.
css_class
.
split
(
'
'
)));
});
it
(
'
renders button data attributes
'
,
()
=>
{
it
(
'
renders button data attributes
'
,
()
=>
{
const
button
=
findButton
();
const
button
=
findButton
();
...
@@ -89,4 +82,29 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
...
@@ -89,4 +82,29 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
expect
(
findButtonIcons
()).
toEqual
(
expectedIcons
);
expect
(
findButtonIcons
()).
toEqual
(
expectedIcons
);
});
});
});
});
describe
.
each
`
desc | active | cssClass | expectedClasses
${
'
default
'
}
|
${
false
}
|
${
''
}
|
${[]}
${
'
with css class
'
}
|
${
false
}
|
${
'
test-css-class testing-123
'
}
|
${[
'
test-css-class
'
,
'
testing-123
'
]}
${
'
with css class & active
'
}
|
${
true
}
|
${
'
test-css-class
'
}
|
${[
'
test-css-class
'
,
...
TopNavMenuItem
.
ACTIVE_CLASS
.
split
(
'
'
)]}
`
(
'
$desc
'
,
({
active
,
cssClass
,
expectedClasses
})
=>
{
beforeEach
(()
=>
{
createComponent
({
menuItem
:
{
...
TEST_MENU_ITEM
,
active
,
css_class
:
cssClass
,
},
});
});
it
(
'
renders expected classes
'
,
()
=>
{
expect
(
wrapper
.
classes
()).
toStrictEqual
([
'
top-nav-menu-item
'
,
'
gl-display-block
'
,
...
expectedClasses
,
]);
});
});
});
});
spec/frontend/nav/components/top_nav_menu_sections_spec.js
0 → 100644
View file @
0ebb1a5d
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
TopNavMenuSections
from
'
~/nav/components/top_nav_menu_sections.vue
'
;
const
TEST_SECTIONS
=
[
{
id
:
'
primary
'
,
menuItems
:
[{
id
:
'
test
'
,
href
:
'
/test/href
'
},
{
id
:
'
foo
'
},
{
id
:
'
bar
'
}],
},
{
id
:
'
secondary
'
,
menuItems
:
[{
id
:
'
lorem
'
},
{
id
:
'
ipsum
'
}],
},
];
describe
(
'
~/nav/components/top_nav_menu_sections.vue
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
wrapper
=
shallowMount
(
TopNavMenuSections
,
{
propsData
:
{
sections
:
TEST_SECTIONS
,
...
props
,
},
});
};
const
findMenuItemModels
=
(
parent
)
=>
parent
.
findAll
(
'
[data-testid="menu-item"]
'
).
wrappers
.
map
((
x
)
=>
({
menuItem
:
x
.
props
(
'
menuItem
'
),
classes
:
x
.
classes
(),
}));
const
findSectionModels
=
()
=>
wrapper
.
findAll
(
'
[data-testid="menu-section"]
'
).
wrappers
.
map
((
x
)
=>
({
classes
:
x
.
classes
(),
menuItems
:
findMenuItemModels
(
x
),
}));
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
default
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
renders sections with menu items
'
,
()
=>
{
expect
(
findSectionModels
()).
toEqual
([
{
classes
:
[],
menuItems
:
[
{
menuItem
:
TEST_SECTIONS
[
0
].
menuItems
[
0
],
classes
:
[],
},
...
TEST_SECTIONS
[
0
].
menuItems
.
slice
(
1
).
map
((
menuItem
)
=>
({
menuItem
,
classes
:
[
'
gl-mt-1
'
],
})),
],
},
{
classes
:
[...
TopNavMenuSections
.
BORDER_CLASSES
.
split
(
'
'
),
'
gl-mt-3
'
],
menuItems
:
[
{
menuItem
:
TEST_SECTIONS
[
1
].
menuItems
[
0
],
classes
:
[],
},
...
TEST_SECTIONS
[
1
].
menuItems
.
slice
(
1
).
map
((
menuItem
)
=>
({
menuItem
,
classes
:
[
'
gl-mt-1
'
],
})),
],
},
]);
});
it
(
'
when clicked menu item with href, does nothing
'
,
()
=>
{
const
menuItem
=
wrapper
.
findAll
(
'
[data-testid="menu-item"]
'
).
at
(
0
);
menuItem
.
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
()).
toEqual
({});
});
it
(
'
when clicked menu item without href, emits "menu-item-click"
'
,
()
=>
{
const
menuItem
=
wrapper
.
findAll
(
'
[data-testid="menu-item"]
'
).
at
(
1
);
menuItem
.
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
menu-item-click
'
)).
toEqual
([[
TEST_SECTIONS
[
0
].
menuItems
[
1
]]]);
});
});
describe
(
'
with withTopBorder=true
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
withTopBorder
:
true
});
});
it
(
'
renders border classes for top section
'
,
()
=>
{
expect
(
findSectionModels
().
map
((
x
)
=>
x
.
classes
)).
toEqual
([
[...
TopNavMenuSections
.
BORDER_CLASSES
.
split
(
'
'
)],
[...
TopNavMenuSections
.
BORDER_CLASSES
.
split
(
'
'
),
'
gl-mt-3
'
],
]);
});
});
});
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