diff --git a/packages/visx-xychart/src/components/series/BarGroup.tsx b/packages/visx-xychart/src/components/series/BarGroup.tsx index c6dd6a8d6..3e0c01536 100644 --- a/packages/visx-xychart/src/components/series/BarGroup.tsx +++ b/packages/visx-xychart/src/components/series/BarGroup.tsx @@ -83,7 +83,6 @@ export default function BarGroup< // and let Tooltip find the nearest point among them dataKeys.forEach(key => { const entry = dataRegistry.get(key); - if (entry && svgPoint && width && height && showTooltip) { const datum = (horizontal ? findNearestDatumY : findNearestDatumX)({ point: svgPoint, diff --git a/packages/visx-xychart/src/index.ts b/packages/visx-xychart/src/index.ts index 32574bcbd..fe996eeb5 100644 --- a/packages/visx-xychart/src/index.ts +++ b/packages/visx-xychart/src/index.ts @@ -24,6 +24,9 @@ export { default as EventEmitterProvider } from './providers/EventEmitterProvide export { default as ThemeProvider } from './providers/ThemeProvider'; export { default as TooltipProvider } from './providers/TooltipProvider'; +// hooks +export { default as useEventEmitter } from './hooks/useEventEmitter'; + // themes export { default as lightTheme } from './theme/themes/light'; export { default as darkTheme } from './theme/themes/dark'; diff --git a/packages/visx-xychart/test/__mocks__/@visx/event.ts b/packages/visx-xychart/test/__mocks__/@visx/event.ts new file mode 100644 index 000000000..dad74649a --- /dev/null +++ b/packages/visx-xychart/test/__mocks__/@visx/event.ts @@ -0,0 +1,8 @@ +export function localPoint() { + return { + x: 5, + y: 3, + value: () => ({ x: 5, y: 3 }), + toArray: () => [5, 3], + }; +} diff --git a/packages/visx-xychart/test/components/BarGroup.test.tsx b/packages/visx-xychart/test/components/BarGroup.test.tsx new file mode 100644 index 000000000..a492b53d5 --- /dev/null +++ b/packages/visx-xychart/test/components/BarGroup.test.tsx @@ -0,0 +1,89 @@ +import React, { useEffect } from 'react'; +import { mount } from 'enzyme'; +import { BarGroup, BarSeries, DataProvider, useEventEmitter } from '../../src'; +import setupTooltipTest from '../mocks/setupTooltipTest'; + +const providerProps = { + initialDimensions: { width: 100, height: 100 }, + xScale: { type: 'linear' }, + yScale: { type: 'linear' }, +} as const; + +const accessors = { + xAccessor: (d: { x: number }) => d.x, + yAccessor: (d: { y: number }) => d.y, +}; + +const series1 = { + key: 'bar1', + data: [ + { x: 10, y: 5 }, + { x: 7, y: 5 }, + ], + ...accessors, +}; + +const series2 = { + key: 'bar2', + data: [ + { x: 10, y: 5 }, + { x: 7, y: 20 }, + ], + ...accessors, +}; + +describe('', () => { + it('should be defined', () => { + expect(BarSeries).toBeDefined(); + }); + + it('should render rects', () => { + const wrapper = mount( + + + + + + + + , + ); + expect(wrapper.find('rect')).toHaveLength(4); + }); + + it('should invoke showTooltip/hideTooltip on mousemove/mouseout', () => { + expect.assertions(2); + + const showTooltip = jest.fn(); + const hideTooltip = jest.fn(); + + const EventEmitter = () => { + const emit = useEventEmitter(); + + useEffect(() => { + if (emit) { + // @ts-ignore not a React.MouseEvent + emit('mousemove', new MouseEvent('mousemove')); + expect(showTooltip).toHaveBeenCalledTimes(2); // one per key + + // @ts-ignore not a React.MouseEvent + emit('mouseout', new MouseEvent('mouseout')); + expect(showTooltip).toHaveBeenCalled(); + } + }); + + return null; + }; + + setupTooltipTest( + <> + + + + + + , + { showTooltip, hideTooltip }, + ); + }); +}); diff --git a/packages/visx-xychart/test/components/BarSeries.test.tsx b/packages/visx-xychart/test/components/BarSeries.test.tsx index 467ce976f..e6633ad56 100644 --- a/packages/visx-xychart/test/components/BarSeries.test.tsx +++ b/packages/visx-xychart/test/components/BarSeries.test.tsx @@ -1,15 +1,17 @@ -import React from 'react'; +import React, { useContext, useEffect } from 'react'; import { mount } from 'enzyme'; -import { DataContext, BarSeries } from '../../src'; +import { DataContext, BarSeries, useEventEmitter } from '../../src'; import getDataContext from '../mocks/getDataContext'; +import setupTooltipTest from '../mocks/setupTooltipTest'; + +const series = { key: 'bar', data: [{}, {}], xAccessor: () => 0, yAccessor: () => 10 }; describe('', () => { it('should be defined', () => { expect(BarSeries).toBeDefined(); }); - it('should render a LinePath', () => { - const series = { key: 'bar', data: [{}, {}], xAccessor: () => 0, yAccessor: () => 10 }; + it('should render rects', () => { const wrapper = mount( @@ -19,4 +21,44 @@ describe('', () => { ); expect(wrapper.find('rect')).toHaveLength(2); }); + + it('should invoke showTooltip/hideTooltip on mousemove/mouseout', () => { + expect.assertions(2); + + const showTooltip = jest.fn(); + const hideTooltip = jest.fn(); + + const ConditionalEventEmitter = () => { + const { dataRegistry } = useContext(DataContext); + // BarSeries won't render until its data is registered + // wait for that to emit the events + return dataRegistry?.get(series.key) ? : null; + }; + + const EventEmitter = () => { + const emit = useEventEmitter(); + + useEffect(() => { + if (emit) { + // @ts-ignore not a React.MouseEvent + emit('mousemove', new MouseEvent('mousemove')); + expect(showTooltip).toHaveBeenCalledTimes(1); + + // @ts-ignore not a React.MouseEvent + emit('mouseout', new MouseEvent('mouseout')); + expect(showTooltip).toHaveBeenCalledTimes(1); + } + }); + + return null; + }; + + setupTooltipTest( + <> + + + , + { showTooltip, hideTooltip }, + ); + }); }); diff --git a/packages/visx-xychart/test/components/BarStack.test.tsx b/packages/visx-xychart/test/components/BarStack.test.tsx index 1a71a3c75..a34edaafa 100644 --- a/packages/visx-xychart/test/components/BarStack.test.tsx +++ b/packages/visx-xychart/test/components/BarStack.test.tsx @@ -1,6 +1,7 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { mount } from 'enzyme'; -import { BarStack, BarSeries, DataProvider, DataContext } from '../../src'; +import { BarStack, BarSeries, DataProvider, DataContext, useEventEmitter } from '../../src'; +import setupTooltipTest from '../mocks/setupTooltipTest'; const providerProps = { initialDimensions: { width: 100, height: 100 }, @@ -80,4 +81,40 @@ describe('', () => { , ); }); + + it('should invoke showTooltip/hideTooltip on mousemove/mouseout', () => { + expect.assertions(2); + + const showTooltip = jest.fn(); + const hideTooltip = jest.fn(); + + const EventEmitter = () => { + const emit = useEventEmitter(); + + useEffect(() => { + if (emit) { + // @ts-ignore not a React.MouseEvent + emit('mousemove', new MouseEvent('mousemove')); + expect(showTooltip).toHaveBeenCalledTimes(2); // one per key + + // @ts-ignore not a React.MouseEvent + emit('mouseout', new MouseEvent('mouseout')); + expect(showTooltip).toHaveBeenCalled(); + } + }); + + return null; + }; + + setupTooltipTest( + <> + + + + + + , + { showTooltip, hideTooltip }, + ); + }); }); diff --git a/packages/visx-xychart/test/components/LineSeries.test.tsx b/packages/visx-xychart/test/components/LineSeries.test.tsx index 08ad575df..7a0ab8890 100644 --- a/packages/visx-xychart/test/components/LineSeries.test.tsx +++ b/packages/visx-xychart/test/components/LineSeries.test.tsx @@ -1,8 +1,11 @@ -import React from 'react'; +import React, { useContext, useEffect } from 'react'; import { mount } from 'enzyme'; import { LinePath } from '@visx/shape'; -import { DataContext, LineSeries } from '../../src'; +import { DataContext, LineSeries, useEventEmitter } from '../../src'; import getDataContext from '../mocks/getDataContext'; +import setupTooltipTest from '../mocks/setupTooltipTest'; + +const series = { key: 'line', data: [{}], xAccessor: () => 4, yAccessor: () => 7 }; describe('', () => { it('should be defined', () => { @@ -10,7 +13,6 @@ describe('', () => { }); it('should render a LinePath', () => { - const series = { key: 'line', data: [], xAccessor: () => 'x', yAccessor: () => '7' }; const wrapper = mount( @@ -21,4 +23,44 @@ describe('', () => { // @ts-ignore produces a union type that is too complex to represent.ts(2590) expect(wrapper.find(LinePath)).toHaveLength(1); }); + + it('should invoke showTooltip/hideTooltip on mousemove/mouseout', () => { + expect.assertions(2); + + const showTooltip = jest.fn(); + const hideTooltip = jest.fn(); + + const ConditionalEventEmitter = () => { + const { dataRegistry } = useContext(DataContext); + // LineSeries won't render until its data is registered + // wait for that to emit the events + return dataRegistry?.get(series.key) ? : null; + }; + + const EventEmitter = () => { + const emit = useEventEmitter(); + + useEffect(() => { + if (emit) { + // @ts-ignore not a React.MouseEvent + emit('mousemove', new MouseEvent('mousemove')); + expect(showTooltip).toHaveBeenCalledTimes(1); + + // @ts-ignore not a React.MouseEvent + emit('mouseout', new MouseEvent('mouseout')); + expect(showTooltip).toHaveBeenCalledTimes(1); + } + }); + + return null; + }; + + setupTooltipTest( + <> + + + , + { showTooltip, hideTooltip }, + ); + }); }); diff --git a/packages/visx-xychart/test/mocks/setupTooltipTest.tsx b/packages/visx-xychart/test/mocks/setupTooltipTest.tsx new file mode 100644 index 000000000..20cf74472 --- /dev/null +++ b/packages/visx-xychart/test/mocks/setupTooltipTest.tsx @@ -0,0 +1,35 @@ +/* eslint import/no-extraneous-dependencies: 'off' */ +import React from 'react'; +import { mount } from 'enzyme'; +import { DataProvider, EventEmitterProvider, TooltipContext, TooltipContextType } from '../../src'; + +const providerProps = { + initialDimensions: { width: 100, height: 100 }, + xScale: { type: 'linear' }, + yScale: { type: 'linear' }, +} as const; + +const defaultTooltipContext = { + tooltipOpen: false, + /* eslint-disable no-undef */ + showTooltip: jest.fn(), // eslint doesn't know jest is in context in non-.test file + updateTooltip: jest.fn(), + hideTooltip: jest.fn(), + /* eslint-enable no-undef */ +}; + +// sets up boilerplate context for testing tooltips +export default function setupTooltipTest( + children: React.ReactNode, + tooltipContext?: Partial>, +) { + return mount( + + + + {children} + + + , + ); +}