diff --git a/packages/visx-xychart/src/hooks/useEventEmitter.ts b/packages/visx-xychart/src/hooks/useEventEmitter.ts index b3f991db9..7af1c1883 100644 --- a/packages/visx-xychart/src/hooks/useEventEmitter.ts +++ b/packages/visx-xychart/src/hooks/useEventEmitter.ts @@ -44,7 +44,10 @@ export default function useEventEmitter( if (emitter && eventType && handler) { // register handler, with source filtering as needed const handlerWithSourceFilter: Handler = (params?: HandlerParams) => { - if (!params?.source || !sourcesRef.current || sourcesRef.current?.includes(params.source)) { + if ( + !sourcesRef.current || + (params?.source && sourcesRef.current?.includes(params.source)) + ) { handler(params); } }; diff --git a/packages/visx-xychart/test/hooks/useEventEmitter.test.tsx b/packages/visx-xychart/test/hooks/useEventEmitter.test.tsx index 5eb563ca7..9efd11410 100644 --- a/packages/visx-xychart/test/hooks/useEventEmitter.test.tsx +++ b/packages/visx-xychart/test/hooks/useEventEmitter.test.tsx @@ -3,6 +3,10 @@ import { mount } from 'enzyme'; import useEventEmitter from '../../src/hooks/useEventEmitter'; import { EventEmitterProvider } from '../../src'; +// avoids a lot of coercing of types +const getEvent = (eventType: string) => + (new MouseEvent(eventType) as unknown) as React.PointerEvent; + describe('useEventEmitter', () => { it('should be defined', () => { expect(useEventEmitter).toBeDefined(); @@ -12,8 +16,8 @@ describe('useEventEmitter', () => { expect.assertions(1); const Component = () => { - const registry = useEventEmitter(); - expect(registry).toEqual(expect.any(Function)); + const emitter = useEventEmitter(); + expect(emitter).toEqual(expect.any(Function)); return null; }; @@ -33,9 +37,41 @@ describe('useEventEmitter', () => { useEffect(() => { if (emit) { - // @ts-ignore not a React.MouseEvent - emit('pointermove', new MouseEvent('pointermove')); + emit('pointermove', getEvent('pointermove')); + expect(listener).toHaveBeenCalledTimes(1); + } + }); + + return null; + }; + + mount( + + + , + ); + }); + + it('should filter invalid sources if specified', () => { + expect.assertions(4); + + const Component = () => { + const eventType = 'pointermove'; + const sourceId = 'sourceId'; + const listener = jest.fn(); + const filteredListener = jest.fn(); + const emit = useEventEmitter(); + useEventEmitter('pointermove', listener); + useEventEmitter('pointermove', filteredListener, [sourceId]); + + useEffect(() => { + if (emit) { + emit(eventType, getEvent(eventType)); expect(listener).toHaveBeenCalledTimes(1); + expect(filteredListener).toHaveBeenCalledTimes(0); + emit(eventType, getEvent(eventType), sourceId); + expect(listener).toHaveBeenCalledTimes(2); + expect(filteredListener).toHaveBeenCalledTimes(1); } }); diff --git a/packages/visx-xychart/test/hooks/usePointerEventEmitters.test.tsx b/packages/visx-xychart/test/hooks/usePointerEventEmitters.test.tsx new file mode 100644 index 000000000..c5c162489 --- /dev/null +++ b/packages/visx-xychart/test/hooks/usePointerEventEmitters.test.tsx @@ -0,0 +1,55 @@ +import React, { useEffect } from 'react'; +import { mount } from 'enzyme'; +import { EventEmitterProvider, useEventEmitter } from '../../src'; +import usePointerEventEmitters from '../../src/hooks/usePointerEventEmitters'; + +describe('usePointerEventEmitters', () => { + it('should be defined', () => { + expect(usePointerEventEmitters).toBeDefined(); + }); + + it('should provide an emitter for each callback specified', () => { + expect.assertions(1); + + const Component = () => { + const emitters = usePointerEventEmitters({ source: 'visx', onPointerOut: false }); + expect(emitters).toEqual({ + onPointerMove: expect.any(Function), + onPointerOut: undefined, + onPointerUp: expect.any(Function), + }); + return null; + }; + + mount( + + + , + ); + }); + it('emitters should emit events', () => { + expect.assertions(1); + + const Component = () => { + const source = 'sourceId'; + const listener = jest.fn(); + useEventEmitter('pointerup', listener, [source]); + const emitters = usePointerEventEmitters({ source }); + + useEffect(() => { + if (emitters.onPointerUp) { + emitters.onPointerUp((new MouseEvent('pointerup') as unknown) as React.PointerEvent); + expect(listener).toHaveBeenCalledTimes(1); + } + }); + + return null; + }; + + mount( + + + , + ); + }); +}); diff --git a/packages/visx-xychart/test/hooks/usePointerEventHandlers.test.tsx b/packages/visx-xychart/test/hooks/usePointerEventHandlers.test.tsx new file mode 100644 index 000000000..b33b4a529 --- /dev/null +++ b/packages/visx-xychart/test/hooks/usePointerEventHandlers.test.tsx @@ -0,0 +1,103 @@ +import React, { useEffect } from 'react'; +import { mount } from 'enzyme'; +import { EventEmitterProvider, useEventEmitter, DataContext } from '../../src'; +import usePointerEventHandlers, { + POINTER_EVENTS_ALL, +} from '../../src/hooks/usePointerEventHandlers'; +import getDataContext from '../mocks/getDataContext'; + +const series1 = { key: 'series1', data: [{}], xAccessor: () => 4, yAccessor: () => 7 }; +const series2 = { key: 'series2', data: [{}], xAccessor: () => 4, yAccessor: () => 7 }; +// avoids a lot of coercing of types +const getEvent = (eventType: string) => + (new MouseEvent(eventType) as unknown) as React.PointerEvent; + +describe('usePointerEventHandlers', () => { + function setup(children: React.ReactNode) { + return mount( + + {children} + , + ); + } + + it('should be defined', () => { + expect(usePointerEventHandlers).toBeDefined(); + }); + it('should invoke handlers for each pointer event handler specified', () => { + expect.assertions(3); + + const Component = () => { + const sourceId = 'sourceId'; + const pointerMoveListener = jest.fn(); + const pointerOutListener = jest.fn(); + const pointerUpListener = jest.fn(); + const emit = useEventEmitter(); + + usePointerEventHandlers({ + sources: [sourceId], + dataKey: series1.key, + onPointerMove: pointerMoveListener, + onPointerOut: pointerOutListener, + onPointerUp: pointerUpListener, + }); + + useEffect(() => { + if (emit) { + emit('pointermove', getEvent('pointermove'), sourceId); + emit('pointermove', getEvent('pointermove'), 'invalidSource'); + expect(pointerMoveListener).toHaveBeenCalledTimes(1); + + emit('pointerout', getEvent('pointerout'), sourceId); + emit('pointerout', getEvent('pointerout'), 'invalidSource'); + expect(pointerOutListener).toHaveBeenCalledTimes(1); + + emit('pointerup', getEvent('pointerup'), sourceId); + emit('pointerup', getEvent('pointerup'), 'invalidSource'); + expect(pointerUpListener).toHaveBeenCalledTimes(1); + } + }); + + return null; + }; + + setup(); + }); + + it('should invoke handlers once for each dataKey specified', () => { + expect.assertions(4); + + const Component = () => { + const sourceId = 'sourceId'; + const pointerMoveListenerAll = jest.fn(); + const pointerMoveListenerMultipleKeys = jest.fn(); + const emit = useEventEmitter(); + + usePointerEventHandlers({ + sources: [sourceId], + dataKey: POINTER_EVENTS_ALL, + onPointerMove: pointerMoveListenerAll, + }); + usePointerEventHandlers({ + sources: [sourceId], + dataKey: [series1.key, series2.key], + onPointerMove: pointerMoveListenerMultipleKeys, + }); + + useEffect(() => { + if (emit) { + emit('pointermove', getEvent('pointermove'), sourceId); + expect(pointerMoveListenerAll).toHaveBeenCalledTimes(2); + expect(pointerMoveListenerMultipleKeys).toHaveBeenCalledTimes(2); + emit('pointermove', getEvent('pointermove'), 'invalidSource'); + expect(pointerMoveListenerAll).toHaveBeenCalledTimes(2); + expect(pointerMoveListenerMultipleKeys).toHaveBeenCalledTimes(2); + } + }); + + return null; + }; + + setup(); + }); +});