Skip to content

Commit

Permalink
Merge pull request #13772 from nextcloud/backport/13736/stable30
Browse files Browse the repository at this point in the history
  • Loading branch information
Antreesy authored Nov 14, 2024
2 parents 7290d29 + c3df4ad commit d6629ff
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 2 deletions.
5 changes: 5 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ export default {
unsubscribe('notifications:action:execute', this.interceptNotificationActions)

window.removeEventListener('beforeunload', this.preventUnload)

EventBus.off('joined-conversation')
EventBus.off('switch-to-conversation')
EventBus.off('conversations-received')
EventBus.off('forbidden-route')
},

beforeMount() {
Expand Down
2 changes: 1 addition & 1 deletion src/components/LeftSidebar/LeftSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ export default {
this.debounceHandleScroll.clear?.()

EventBus.off('should-refresh-conversations', this.handleShouldRefreshConversations)
EventBus.off('conversations-received', this.handleUnreadMention)
EventBus.off('conversations-received', this.handleConversationsReceived)
EventBus.off('route-change', this.onRouteChange)

this.cancelSearchPossibleConversations()
Expand Down
42 changes: 41 additions & 1 deletion src/services/EventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ type GenericEventHandler = Handler<Events[keyof Events]> | WildcardHandler<Event
type ExtendedEmitter = Emitter<Events> & {
once<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void
once(type: '*', handler: WildcardHandler<Events>): void
_onceHandlers: Map<keyof Events | '*', Map<GenericEventHandler, GenericEventHandler>>
}

export const EventBus: ExtendedEmitter = mitt() as ExtendedEmitter

EventBus._onceHandlers = new Map()

/**
* Register a one-time event handler for the given type
*
Expand All @@ -27,7 +31,43 @@ EventBus.once = function<Key extends keyof Events>(type: Key, handler: GenericEv
const fn = (...args: Parameters<GenericEventHandler>) => {
// @ts-expect-error: Vue: A spread argument must either have a tuple type or be passed to a rest parameter.
handler(...args)
this.off(type, fn)
// @ts-expect-error: Vue: No overload matches this call.
this.off(type, handler)
}
this.on(type, fn)

// Store reference to the original handler to be able to remove it later
if (!EventBus._onceHandlers.has(type)) {
EventBus._onceHandlers.set(type, new Map())
}
EventBus._onceHandlers.get(type)!.set(handler, fn)
}

const off = EventBus.off.bind(EventBus)
/**
* OVERRIDING OF ORIGINAL MITT FUNCTION
* Remove an event handler for the given type.
* If `handler` is omitted, all handlers of the given type are removed.
* @param type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler)
* @param [handler] Handler function to remove
*/
EventBus.off = function<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
// @ts-expect-error: Vue: No overload matches this call
off(type, handler)

if (!handler) {
EventBus._onceHandlers.delete(type)
return
}

const typeOnceHandlers = EventBus._onceHandlers.get(type)
const onceHandler = typeOnceHandlers?.get(handler)
if (onceHandler) {
typeOnceHandlers!.delete(handler)
if (!typeOnceHandlers!.size) {
EventBus._onceHandlers.delete(type)
}
// @ts-expect-error: Vue: No overload matches this call
off(type, onceHandler)
}
}
283 changes: 283 additions & 0 deletions src/services/__tests__/EventBus.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { EventBus } from '../EventBus.ts'

describe('EventBus', () => {
const customEvent1 = jest.fn()
const customEvent2 = jest.fn()
const customEvent3 = jest.fn()
const customEventOnce1 = jest.fn()
const customEventOnce2 = jest.fn()
const customEventOnce3 = jest.fn()

const testEventBus = (type, handlers, onceHandlers) => {
expect(EventBus.all.get(type).length).toBe(handlers)
if (!onceHandlers) {
expect(EventBus._onceHandlers.get(type)).toBeUndefined()
} else {
expect(EventBus._onceHandlers.get(type).size).toBe(onceHandlers)
}
}

afterEach(() => {
EventBus.all.clear()
jest.clearAllMocks()
})

describe('on and off', () => {
it('should emit and listen to custom events', () => {
// Arrange
EventBus.on('custom-event', customEvent1)
EventBus.on('custom-event', customEvent2)

// Act
EventBus.emit('custom-event')

// Assert
testEventBus('custom-event', 2)
expect(customEvent1).toHaveBeenCalledTimes(1)
expect(customEvent2).toHaveBeenCalledTimes(1)
})

it('should emit and listen to custom events with wildcard * ', () => {
// Arrange
EventBus.on('*', customEvent1)
EventBus.on('*', customEvent2)

// Act
EventBus.emit('custom-event-1')

// Assert
testEventBus('*', 2)
expect(customEvent1).toHaveBeenCalledTimes(1)
expect(customEvent2).toHaveBeenCalledTimes(1)
})

it('should remove listeners by given type and handler', () => {
// Arrange
EventBus.on('custom-event', customEvent1)
EventBus.on('custom-event', customEvent2)
EventBus.emit('custom-event')
testEventBus('custom-event', 2)

// Act
EventBus.off('custom-event', customEvent1)
EventBus.emit('custom-event')

// Assert
testEventBus('custom-event', 1)
expect(customEvent1).toHaveBeenCalledTimes(1)
expect(customEvent2).toHaveBeenCalledTimes(2)
})

it('should remove listeners by wildcard * and handler', () => {
// Arrange
EventBus.on('custom-event-1', customEvent1)
EventBus.on('custom-event-2', customEvent2)
EventBus.on('*', customEvent3)
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')
testEventBus('custom-event-1', 1)
testEventBus('custom-event-2', 1)
testEventBus('*', 1)
expect(customEvent3).toHaveBeenCalledTimes(2)

// Act
EventBus.off('*', customEvent3)
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')

// Assert
testEventBus('custom-event-1', 1)
testEventBus('custom-event-2', 1)
testEventBus('*', 0)
expect(customEvent1).toHaveBeenCalledTimes(2)
expect(customEvent2).toHaveBeenCalledTimes(2)
expect(customEvent3).toHaveBeenCalledTimes(2)
})

it('should remove listeners by given type only', () => {
// Arrange
EventBus.on('custom-event', customEvent1)
EventBus.on('custom-event', customEvent2)
EventBus.emit('custom-event')
testEventBus('custom-event', 2)

// Act
EventBus.off('custom-event')
EventBus.emit('custom-event')

// Assert
testEventBus('custom-event', 0)
expect(customEvent1).toHaveBeenCalledTimes(1)
expect(customEvent2).toHaveBeenCalledTimes(1)
})

it('should remove listeners by wildcard * only', () => {
// Arrange
EventBus.on('custom-event-1', customEvent1)
EventBus.on('custom-event-2', customEvent2)
EventBus.on('*', customEvent3)
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')
testEventBus('custom-event-1', 1)
testEventBus('custom-event-2', 1)
testEventBus('*', 1)
expect(customEvent3).toHaveBeenCalledTimes(2)

// Act
EventBus.off('*')
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')

// Assert
testEventBus('custom-event-1', 1)
testEventBus('custom-event-2', 1)
testEventBus('*', 0)
expect(customEvent1).toHaveBeenCalledTimes(2)
expect(customEvent2).toHaveBeenCalledTimes(2)
expect(customEvent3).toHaveBeenCalledTimes(2)
})
})

describe('once and off', () => {
it('should emit and listen to custom events', () => {
// Arrange
EventBus.on('custom-event', customEvent1)
EventBus.once('custom-event', customEventOnce1)
EventBus.once('custom-event', customEventOnce2)
testEventBus('custom-event', 3, 2)

// Act
EventBus.emit('custom-event')
EventBus.emit('custom-event')

// Assert
expect(customEvent1).toHaveBeenCalledTimes(2)
expect(customEventOnce1).toHaveBeenCalledTimes(1)
expect(customEventOnce2).toHaveBeenCalledTimes(1)
testEventBus('custom-event', 1, 0)
})

it('should emit and listen to custom events with wildcard * ', () => {
// Arrange
EventBus.on('*', customEvent1)
EventBus.once('*', customEventOnce1)
EventBus.once('*', customEventOnce2)
testEventBus('*', 3, 2)

// Act
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')

// Assert
expect(customEvent1).toHaveBeenCalledTimes(2)
expect(customEventOnce1).toHaveBeenCalledTimes(1)
expect(customEventOnce2).toHaveBeenCalledTimes(1)
testEventBus('*', 1, 0)
})

it('should remove listeners by given type and handler', () => {
// Arrange
EventBus.on('custom-event', customEvent1)
EventBus.once('custom-event', customEventOnce1)
EventBus.once('custom-event', customEventOnce2)
testEventBus('custom-event', 3, 2)

// Act
EventBus.off('custom-event', customEventOnce1)
testEventBus('custom-event', 2, 1)
EventBus.emit('custom-event')
EventBus.emit('custom-event')

// Assert
expect(customEvent1).toHaveBeenCalledTimes(2)
expect(customEventOnce1).toHaveBeenCalledTimes(0)
expect(customEventOnce2).toHaveBeenCalledTimes(1)
testEventBus('custom-event', 1, 0)
})

it('should remove listeners by wildcard * and handler', () => {
// Arrange
EventBus.once('custom-event-1', customEventOnce1)
EventBus.on('custom-event-2', customEvent2)
EventBus.once('custom-event-2', customEventOnce2)
EventBus.on('*', customEvent3)
EventBus.once('*', customEventOnce3)
testEventBus('custom-event-1', 1, 1)
testEventBus('custom-event-2', 2, 1)
testEventBus('*', 2, 1)

// Act
EventBus.off('*', customEventOnce3)
testEventBus('custom-event-1', 1, 1)
testEventBus('custom-event-2', 2, 1)
testEventBus('*', 1, 0)
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')
EventBus.emit('custom-event-3')

// Assert
expect(customEventOnce1).toHaveBeenCalledTimes(1)
expect(customEvent2).toHaveBeenCalledTimes(1)
expect(customEventOnce2).toHaveBeenCalledTimes(1)
expect(customEvent3).toHaveBeenCalledTimes(3)
expect(customEventOnce3).toHaveBeenCalledTimes(0)
testEventBus('custom-event-1', 0, 0)
testEventBus('custom-event-2', 1, 0)
testEventBus('*', 1, 0)
})

it('should remove listeners by given type only', () => {
// Arrange
EventBus.on('custom-event', customEvent1)
EventBus.once('custom-event', customEventOnce1)
EventBus.once('custom-event', customEventOnce2)
testEventBus('custom-event', 3, 2)

// Act
EventBus.off('custom-event')
testEventBus('custom-event', 0, 0)
EventBus.emit('custom-event')
EventBus.emit('custom-event')

// Assert
expect(customEvent1).toHaveBeenCalledTimes(0)
expect(customEventOnce1).toHaveBeenCalledTimes(0)
expect(customEventOnce2).toHaveBeenCalledTimes(0)
})

it('should remove listeners by wildcard * only', () => {
// Arrange
EventBus.once('custom-event-1', customEventOnce1)
EventBus.on('custom-event-2', customEvent2)
EventBus.once('custom-event-2', customEventOnce2)
EventBus.on('*', customEvent3)
EventBus.once('*', customEventOnce3)
testEventBus('custom-event-1', 1, 1)
testEventBus('custom-event-2', 2, 1)
testEventBus('*', 2, 1)

// Act
EventBus.off('*')
testEventBus('custom-event-1', 1, 1)
testEventBus('custom-event-2', 2, 1)
testEventBus('*', 0, 0)
EventBus.emit('custom-event-1')
EventBus.emit('custom-event-2')
EventBus.emit('custom-event-3')

// Assert
expect(customEventOnce1).toHaveBeenCalledTimes(1)
expect(customEvent2).toHaveBeenCalledTimes(1)
expect(customEventOnce2).toHaveBeenCalledTimes(1)
expect(customEvent3).toHaveBeenCalledTimes(0)
expect(customEventOnce3).toHaveBeenCalledTimes(0)
testEventBus('custom-event-1', 0, 0)
testEventBus('custom-event-2', 1, 0)
testEventBus('*', 0, 0)
})
})
})

0 comments on commit d6629ff

Please sign in to comment.