-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/lite 29476 create menu component #48
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
border-color = #e0e0e0; | ||
border-color = #e0e0e0; | ||
base-text-color = #212121; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Menu from '~widgets/menu/widget.vue'; | ||
import Button from '~widgets/button/widget.vue'; | ||
import registerWidget from '~core/registerWidget'; | ||
|
||
registerWidget('ui-menu', Menu); | ||
registerWidget('ui-button', Button); | ||
|
||
export const Component = { | ||
render: (args) => ({ | ||
setup() { | ||
return {args}; | ||
}, | ||
template: `<ui-menu> | ||
<ui-button | ||
slot="trigger" | ||
text="open menu" | ||
/> | ||
<div style="padding:8px 16px; width:300px; border:1px solid black;" slot="content"> | ||
<p>item</p> | ||
</div> | ||
</ui-menu>` | ||
}), | ||
}; | ||
|
||
export default { | ||
title: 'Components/Menu', | ||
component: Menu, | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { mount } from '@vue/test-utils' | ||
import Menu from './widget'; | ||
|
||
describe('Menu component', () => { | ||
describe('methods', () => { | ||
describe('#toggle', () => { | ||
it('toggles menu to true when clicking', () => { | ||
const wrapper = mount(Menu); | ||
wrapper.vm.showMenu = false; | ||
wrapper.vm.toggle(wrapper.vm.showMenu); | ||
|
||
expect(wrapper.vm.showMenu).toBe(true); | ||
}); | ||
|
||
it('toggles menu back to false when clicking', () => { | ||
const wrapper = mount(Menu); | ||
wrapper.vm.showMenu = true; | ||
wrapper.vm.toggle(wrapper.vm.showMenu); | ||
|
||
expect(wrapper.vm.showMenu).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('#handleClickOutside', () => { | ||
|
||
it('hides menu content when clicked outside menu', () => { | ||
const event = { target: 'another value'}; | ||
const wrapper = mount(Menu); | ||
wrapper.vm.menu = { contains: jest.fn().mockReturnValue(false) }; | ||
wrapper.vm.showMenu = true; | ||
wrapper.vm.handleClickOutside(event); | ||
|
||
expect(wrapper.vm.showMenu).toBe(false); | ||
}); | ||
|
||
it('does not hide menu content when clicked inside menu', () => { | ||
const event = { target: 'some value'}; | ||
const wrapper = mount(Menu); | ||
wrapper.vm.menu = { contains: jest.fn().mockReturnValue(true) }; | ||
wrapper.vm.showMenu = true; | ||
wrapper.vm.handleClickOutside(event); | ||
|
||
expect(wrapper.vm.showMenu).toBe(true); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('onMounted', () => { | ||
it('adds up event listener on component mount', () => { | ||
const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); | ||
|
||
mount(Menu); | ||
|
||
expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function)); | ||
|
||
addEventListenerSpy.mockRestore(); | ||
}); | ||
}); | ||
|
||
describe('onUnmounted', () => { | ||
it('cleans up event listener on component unmount', async () => { | ||
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener'); | ||
|
||
const wrapper = mount(Menu); | ||
await wrapper.unmount(); | ||
|
||
expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function)); | ||
|
||
removeEventListenerSpy.mockRestore(); | ||
}); | ||
}) | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<template> | ||
<div | ||
ref="menu" | ||
class="menu" | ||
> | ||
<div | ||
class="menu-trigger" | ||
@click.stop="toggle" | ||
> | ||
<slot name="trigger" /> | ||
</div> | ||
|
||
<div class="menu-content-wrapper"> | ||
<div | ||
v-if="showMenu" | ||
class="menu-content" | ||
@click.stop | ||
> | ||
<slot name="content" /> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup> | ||
import { onMounted, onUnmounted, ref } from 'vue' | ||
|
||
const showMenu = ref(false) | ||
const menu = ref(null) | ||
|
||
const toggle = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These mutations are like some hooks under the hood, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's just syntax for writing method in composition API (opposed to option API that we are used to see more :)) |
||
showMenu.value = !showMenu.value; | ||
} | ||
|
||
const handleClickOutside = (event) => { | ||
if (menu.value && !menu.value.contains(event.target)) { | ||
showMenu.value = false; | ||
} | ||
} | ||
|
||
onMounted(() => { | ||
document.addEventListener("click", handleClickOutside) | ||
}) | ||
|
||
onUnmounted(() => { | ||
document.removeEventListener("click", handleClickOutside) | ||
}) | ||
</script> | ||
|
||
<style lang="stylus" scoped> | ||
|
||
.menu-content-wrapper { | ||
position: relative; | ||
} | ||
|
||
.menu-content { | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
} | ||
</style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just fyi, dom events can be triggered directly instead of calling the fn attached to the listener.
Eg:
wrapper.find('.menu-trigger').trigger('click');
will trigger the event listener, calling your function. You could check then that the 'menu-content' element is rendered as a consequence instead of checking the variable's value. Eg:expect(wrapper.find('.menu-content').exists()).toBeTruthy()
No need to change, just a different type of test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I will leave it as it is and try to use this way when appropriate in the future :)