mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
fix(webapp): added timer (#8658)
This commit is contained in:
parent
915091286a
commit
2b457a5823
@ -15,8 +15,9 @@
|
||||
:show-avatar="showAvatar"
|
||||
:date-time="dateTime"
|
||||
:show-popover="showPopover"
|
||||
:injectedText="injectedText"
|
||||
:injectedDate="injectedDate"
|
||||
:injected-text="injectedText"
|
||||
:injected-date="injectedDate"
|
||||
:hover-delay="hoverDelay"
|
||||
@close="closeMenu"
|
||||
/>
|
||||
</client-only>
|
||||
@ -45,6 +46,7 @@ export default {
|
||||
showPopover: { type: Boolean, default: true },
|
||||
injectedText: { type: String, default: null },
|
||||
injectedDate: { type: Boolean, default: false },
|
||||
hoverDelay: { type: Number, default: 500 },
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { render } from '@testing-library/vue'
|
||||
import { render, waitFor, fireEvent } from '@testing-library/vue'
|
||||
import { RouterLinkStub } from '@vue/test-utils'
|
||||
import UserTeaserHelper from './UserTeaserHelper.vue'
|
||||
|
||||
@ -20,6 +20,7 @@ describe('UserTeaserHelper', () => {
|
||||
withLinkToProfile = true,
|
||||
onTouchScreen = false,
|
||||
withPopoverEnabled = true,
|
||||
hoverDelay = 500,
|
||||
}) => {
|
||||
mockIsTouchDevice = onTouchScreen
|
||||
|
||||
@ -29,13 +30,109 @@ describe('UserTeaserHelper', () => {
|
||||
userLink,
|
||||
linkToProfile: withLinkToProfile,
|
||||
showPopover: withPopoverEnabled,
|
||||
hoverDelay: hoverDelay,
|
||||
},
|
||||
stubs: {
|
||||
NuxtLink: RouterLinkStub,
|
||||
},
|
||||
slots: {
|
||||
default: '<div>Test Content</div>',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function for tests that need timers
|
||||
const withFakeTimers = (testFn) => {
|
||||
return async () => {
|
||||
jest.useFakeTimers()
|
||||
try {
|
||||
await testFn()
|
||||
} finally {
|
||||
jest.runOnlyPendingTimers()
|
||||
jest.useRealTimers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('hover delay functionality', () => {
|
||||
it(
|
||||
'should emit open-menu after hover delay on desktop',
|
||||
withFakeTimers(async () => {
|
||||
const wrapper = Wrapper({
|
||||
withLinkToProfile: true,
|
||||
onTouchScreen: false,
|
||||
withPopoverEnabled: true,
|
||||
hoverDelay: 1000,
|
||||
})
|
||||
|
||||
// Find the NuxtLink stub element
|
||||
const link = wrapper.container.firstChild
|
||||
expect(link).toBeTruthy()
|
||||
|
||||
// Trigger mouseenter
|
||||
fireEvent.mouseEnter(link)
|
||||
|
||||
// Menu should not be opened yet
|
||||
expect(wrapper.emitted()['open-menu']).toBeFalsy()
|
||||
|
||||
// Advance time
|
||||
jest.advanceTimersByTime(1000)
|
||||
|
||||
// Now open-menu should have been emitted
|
||||
await waitFor(() => {
|
||||
expect(wrapper.emitted()['open-menu']).toBeTruthy()
|
||||
expect(wrapper.emitted()['open-menu']).toHaveLength(1)
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it(
|
||||
'should not emit open-menu if mouse leaves before delay',
|
||||
withFakeTimers(async () => {
|
||||
const wrapper = Wrapper({
|
||||
withLinkToProfile: true,
|
||||
onTouchScreen: false,
|
||||
withPopoverEnabled: true,
|
||||
hoverDelay: 1000,
|
||||
})
|
||||
|
||||
const link = wrapper.container.firstChild
|
||||
|
||||
// Mouseenter + mouseleave
|
||||
fireEvent.mouseEnter(link)
|
||||
jest.advanceTimersByTime(500)
|
||||
fireEvent.mouseLeave(link)
|
||||
|
||||
// Let the rest of the time pass
|
||||
jest.advanceTimersByTime(500)
|
||||
|
||||
// open-menu should not have been emitted
|
||||
expect(wrapper.emitted()['open-menu']).toBeFalsy()
|
||||
// But close-menu should have been emitted
|
||||
expect(wrapper.emitted()['close-menu']).toBeTruthy()
|
||||
expect(wrapper.emitted()['close-menu']).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it('should emit open-menu immediately on touch device button click', async () => {
|
||||
const wrapper = Wrapper({
|
||||
withLinkToProfile: true,
|
||||
onTouchScreen: true,
|
||||
withPopoverEnabled: true,
|
||||
})
|
||||
|
||||
const button = wrapper.container.querySelector('button')
|
||||
expect(button).toBeTruthy()
|
||||
|
||||
// Click on button
|
||||
fireEvent.click(button)
|
||||
|
||||
// Should be emitted immediately, without timer
|
||||
expect(wrapper.emitted()['open-menu']).toBeTruthy()
|
||||
expect(wrapper.emitted()['open-menu']).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with linkToProfile and popover enabled, on touch screen', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
@ -43,6 +140,7 @@ describe('UserTeaserHelper', () => {
|
||||
})
|
||||
|
||||
it('renders button', () => {
|
||||
expect(wrapper.container.querySelector('button')).toBeTruthy()
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@ -54,6 +152,7 @@ describe('UserTeaserHelper', () => {
|
||||
})
|
||||
|
||||
it('renders span', () => {
|
||||
expect(wrapper.container.querySelector('span')).toBeTruthy()
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@ -65,7 +164,55 @@ describe('UserTeaserHelper', () => {
|
||||
})
|
||||
|
||||
it('renders link', () => {
|
||||
const routerLinkStub = wrapper.container.firstChild
|
||||
expect(routerLinkStub).toBeTruthy()
|
||||
expect(routerLinkStub.tagName).toBeTruthy()
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('timer cleanup', () => {
|
||||
it(
|
||||
'should clear timer on component unmount',
|
||||
withFakeTimers(async () => {
|
||||
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout')
|
||||
|
||||
const wrapper = Wrapper({
|
||||
withLinkToProfile: true,
|
||||
onTouchScreen: false,
|
||||
withPopoverEnabled: true,
|
||||
hoverDelay: 5000,
|
||||
})
|
||||
|
||||
// Start hover timer
|
||||
fireEvent.mouseEnter(wrapper.container.firstChild)
|
||||
|
||||
// Unmount while timer is running
|
||||
wrapper.unmount()
|
||||
|
||||
// clearTimeout should have been called
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled()
|
||||
|
||||
clearTimeoutSpy.mockRestore()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('popover behavior without showPopover', () => {
|
||||
it('should not emit events when showPopover is false', () => {
|
||||
const wrapper = Wrapper({
|
||||
withLinkToProfile: true,
|
||||
onTouchScreen: false,
|
||||
withPopoverEnabled: false,
|
||||
})
|
||||
|
||||
const link = wrapper.container.firstChild
|
||||
|
||||
fireEvent.mouseEnter(link)
|
||||
fireEvent.mouseLeave(link)
|
||||
|
||||
expect(wrapper.emitted()['open-menu']).toBeUndefined()
|
||||
expect(wrapper.emitted()['close-menu']).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
</button>
|
||||
<span
|
||||
v-else-if="!linkToProfile || !userLink"
|
||||
@mouseover="() => showPopover && openMenu()"
|
||||
@mouseleave="closeMenu"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
<nuxt-link
|
||||
v-else
|
||||
:to="userLink"
|
||||
@mouseover.native="() => showPopover && openMenu()"
|
||||
@mouseleave.native="closeMenu"
|
||||
@mouseenter.native="handleMouseEnter"
|
||||
@mouseleave.native="handleMouseLeave"
|
||||
>
|
||||
<slot />
|
||||
</nuxt-link>
|
||||
@ -28,6 +28,13 @@ export default {
|
||||
userLink: { type: Object, default: null },
|
||||
linkToProfile: { type: Boolean, default: true },
|
||||
showPopover: { type: Boolean, default: false },
|
||||
hoverDelay: { type: Number, default: 500 },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hoverTimer: null,
|
||||
isHovering: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isTouchDevice() {
|
||||
@ -35,12 +42,46 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleMouseEnter() {
|
||||
if (!this.showPopover) return
|
||||
|
||||
this.isHovering = true
|
||||
|
||||
this.clearHoverTimer()
|
||||
this.hoverTimer = setTimeout(() => {
|
||||
// Only open if still hovering
|
||||
if (this.isHovering) {
|
||||
this.openMenu()
|
||||
}
|
||||
}, this.hoverDelay)
|
||||
},
|
||||
|
||||
handleMouseLeave() {
|
||||
if (!this.showPopover) return
|
||||
|
||||
this.isHovering = false
|
||||
this.clearHoverTimer()
|
||||
this.closeMenu()
|
||||
},
|
||||
|
||||
clearHoverTimer() {
|
||||
if (this.hoverTimer) {
|
||||
clearTimeout(this.hoverTimer)
|
||||
this.hoverTimer = null
|
||||
}
|
||||
},
|
||||
|
||||
openMenu() {
|
||||
this.$emit('open-menu')
|
||||
},
|
||||
|
||||
closeMenu() {
|
||||
this.$emit('close-menu')
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.clearHoverTimer()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
:link-to-profile="linkToProfile"
|
||||
:show-popover="showPopover"
|
||||
:user-link="userLink"
|
||||
:hover-delay="hoverDelay"
|
||||
@open-menu="loadPopover(openMenu)"
|
||||
@close-menu="closeMenu(false)"
|
||||
data-test="avatarUserLink"
|
||||
@ -18,6 +19,7 @@
|
||||
:link-to-profile="linkToProfile"
|
||||
:show-popover="showPopover"
|
||||
:user-link="userLink"
|
||||
:hover-delay="hoverDelay"
|
||||
@open-menu="loadPopover(openMenu)"
|
||||
@close-menu="closeMenu(false)"
|
||||
>
|
||||
@ -67,7 +69,7 @@ import UserTeaserPopover from './UserTeaserPopover'
|
||||
import UserTeaserHelper from './UserTeaserHelper.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaser',
|
||||
name: 'UserTeaserNonAnonymous',
|
||||
components: {
|
||||
ProfileAvatar,
|
||||
UserTeaserPopover,
|
||||
@ -85,6 +87,7 @@ export default {
|
||||
showPopover: { type: Boolean, default: true },
|
||||
injectedText: { type: String, default: null },
|
||||
injectedDate: { type: Boolean, default: false },
|
||||
hoverDelay: { type: Number, default: 500 },
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
|
||||
@ -353,7 +353,6 @@ exports[`UserTeaser given an user with linkToProfile, on desktop when hovering t
|
||||
delay="0"
|
||||
handleresize="true"
|
||||
offset="16"
|
||||
open="true"
|
||||
openclass="open"
|
||||
opengroup="0"
|
||||
placement="bottom-end"
|
||||
@ -421,12 +420,7 @@ exports[`UserTeaser given an user with linkToProfile, on desktop when hovering t
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<user-teaser-popover-stub
|
||||
user-id="user1"
|
||||
user-link="[object Object]"
|
||||
/>
|
||||
</div>
|
||||
<div />
|
||||
</v-popover-stub>
|
||||
</client-only-stub>
|
||||
</div>
|
||||
@ -717,7 +711,6 @@ exports[`UserTeaser given an user without linkToProfile, on desktop when hoverin
|
||||
delay="0"
|
||||
handleresize="true"
|
||||
offset="16"
|
||||
open="true"
|
||||
openclass="open"
|
||||
opengroup="0"
|
||||
placement="bottom-end"
|
||||
@ -785,11 +778,7 @@ exports[`UserTeaser given an user without linkToProfile, on desktop when hoverin
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<user-teaser-popover-stub
|
||||
user-id="user1"
|
||||
/>
|
||||
</div>
|
||||
<div />
|
||||
</v-popover-stub>
|
||||
</client-only-stub>
|
||||
</div>
|
||||
@ -810,7 +799,6 @@ exports[`UserTeaser given an user without linkToProfile, on desktop when hoverin
|
||||
delay="0"
|
||||
handleresize="true"
|
||||
offset="16"
|
||||
open="true"
|
||||
openclass="open"
|
||||
opengroup="0"
|
||||
placement="bottom-end"
|
||||
@ -878,11 +866,7 @@ exports[`UserTeaser given an user without linkToProfile, on desktop when hoverin
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<user-teaser-popover-stub
|
||||
user-id="user1"
|
||||
/>
|
||||
</div>
|
||||
<div />
|
||||
</v-popover-stub>
|
||||
</client-only-stub>
|
||||
</div>
|
||||
|
||||
@ -2,18 +2,30 @@
|
||||
|
||||
exports[`UserTeaserHelper with linkToProfile and popover enabled, on touch screen renders button 1`] = `
|
||||
<div>
|
||||
<button />
|
||||
<button>
|
||||
<div>
|
||||
Test Content
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserHelper with linkToProfile, on desktop renders link 1`] = `
|
||||
<div>
|
||||
<a />
|
||||
<a>
|
||||
<div>
|
||||
Test Content
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserTeaserHelper without linkToProfile renders span 1`] = `
|
||||
<div>
|
||||
<span />
|
||||
<span>
|
||||
<div>
|
||||
Test Content
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user