Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnesen committed Dec 9, 2024
1 parent 41643de commit b546085
Show file tree
Hide file tree
Showing 4 changed files with 358 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {SingleTextLine} from '../Labels';
import {ChartElements} from '../ChartElements';

import {FunnelChartXAxisLabels, Tooltip} from './components/';
import {calculateDropOff} from './utilities/calculate-dropoff';
import type {FunnelChartNextProps} from './FunnelChartNext';
import {FunnelTooltip} from './components/FunnelTooltip/FunnelTooltip';
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {mount} from '@shopify/react-testing';
import {ChartContext} from '@shopify/polaris-viz-core';
import type {DataSeries} from '@shopify/polaris-viz-core';
import React from 'react';

import {Chart} from '../Chart';
import {FunnelChartConnector, FunnelChartSegment} from '../../shared';
import {FunnelTooltip} from '../components/FunnelTooltip/FunnelTooltip';
import {SingleTextLine} from '../../Labels';

const mockData: DataSeries[] = [
{
name: 'Group 1',
data: [
{key: 'Step 1', value: 100},
{key: 'Step 2', value: 75},
{key: 'Step 3', value: 50},
],
},
];

const mockContext = {
containerBounds: {
width: 500,
height: 300,
x: 0,
y: 0,
},
};

const defaultProps = {
data: mockData,
showConnectionPercentage: false,
tooltipLabels: {
dropoff: 'Dropoff',
total: 'Total',
},
xAxisOptions: {
hide: false,
labelFormatter: (value: string) => value,
},
yAxisOptions: {
labelFormatter: (value: number) => value.toString(),
},
};

describe('<Chart />', () => {
it('renders funnel segments for each data point', () => {
const chart = mount(
<ChartContext.Provider value={mockContext}>
<Chart {...defaultProps} />
</ChartContext.Provider>,
);

expect(chart).toContainReactComponentTimes(FunnelChartSegment, 3);
});

it('renders n-1 connectors for n funnel segments, excluding the last segment', () => {
const chart = mount(
<ChartContext.Provider value={mockContext}>
<Chart {...defaultProps} />
</ChartContext.Provider>,
);

expect(chart).toContainReactComponentTimes(FunnelChartConnector, 2);
});

it('formats labels using the provided formatters', () => {
const customFormatter = (value: string) => `Custom ${value}`;
const chart = mount(
<ChartContext.Provider value={mockContext}>
<Chart
{...defaultProps}
xAxisOptions={{
...defaultProps.xAxisOptions,
labelFormatter: customFormatter,
}}
/>
</ChartContext.Provider>,
);

expect(chart).toContainReactComponent(SingleTextLine, {
text: 'Custom Step 1',
});
});

it('shows tooltip when hovering over a segment', () => {
const chart = mount(
<ChartContext.Provider value={mockContext}>
<Chart {...defaultProps} />
</ChartContext.Provider>,
);

const firstSegment = chart.find(FunnelChartSegment);
firstSegment?.trigger('onMouseEnter', 0);

expect(chart).toContainReactComponent(FunnelTooltip);
});

it('hides tooltip when mouse leaves a segment', () => {
const chart = mount(
<ChartContext.Provider value={mockContext}>
<Chart {...defaultProps} />
</ChartContext.Provider>,
);

const firstSegment = chart.find(FunnelChartSegment);
firstSegment?.trigger('onMouseEnter', 0);
firstSegment?.trigger('onMouseLeave');

expect(chart).not.toContainReactComponent(FunnelTooltip); // Tooltip container
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';
import {mount} from '@shopify/react-testing';
import {
ChartState,
ChartContext,
DEFAULT_THEME_NAME,
} from '@shopify/polaris-viz-core';
import {act} from 'react-dom/test-utils';

import {FunnelChartNext} from '../FunnelChartNext';
import {Chart} from '../Chart';
import {ChartSkeleton} from '../../ChartSkeleton';
import {FunnelChartSegment} from '../../shared';
import {FunnelConnector} from '../components';

const mockData = [
{
name: 'Funnel',
data: [
{key: 'Step 1', value: 1000},
{key: 'Step 2', value: 750},
{key: 'Step 3', value: 500},
{key: 'Step 4', value: 250},
],
},
];

const mockTooltipLabels = {
reached: 'Reached',
dropped: 'Dropped',
};

describe('<FunnelChartNext />', () => {
describe('rendering states', () => {
it('renders a Chart when state is Success', () => {
const funnel = mount(
<FunnelChartNext
data={mockData}
tooltipLabels={mockTooltipLabels}
state={ChartState.Success}
/>,
);

expect(funnel).toContainReactComponent(Chart);
});

it('renders a ChartSkeleton when state is Loading', () => {
const funnel = mount(
<FunnelChartNext
data={mockData}
tooltipLabels={mockTooltipLabels}
state={ChartState.Loading}
/>,
);

expect(funnel).toContainReactComponent(ChartSkeleton, {
type: 'Funnel',
state: ChartState.Loading,
});
});

it('renders a ChartSkeleton with error text when state is Error', () => {
const errorText = 'Something went wrong';
const funnel = mount(
<FunnelChartNext
data={mockData}
tooltipLabels={mockTooltipLabels}
state={ChartState.Error}
errorText={errorText}
/>,
);

expect(funnel).toContainReactComponent(ChartSkeleton, {
type: 'Funnel',
errorText,
state: ChartState.Error,
});
});
});

describe('chart configuration', () => {
it('passes theme to Chart component', () => {
const funnel = mount(
<FunnelChartNext
data={mockData}
tooltipLabels={mockTooltipLabels}
theme={DEFAULT_THEME_NAME}
state={ChartState.Success}
/>,
);

expect(funnel).toContainReactComponent(Chart);
});

it('passes xAxisOptions to Chart', () => {
const xAxisOptions = {hide: true};
const funnel = mount(
<FunnelChartNext
data={mockData}
tooltipLabels={mockTooltipLabels}
xAxisOptions={xAxisOptions}
state={ChartState.Success}
/>,
);

expect(funnel).toContainReactComponent(Chart, {
xAxisOptions: expect.objectContaining({hide: true}),
});
});

it('passes yAxisOptions to Chart', () => {
const labelFormatter = (value: number) => `$${value}`;
const funnel = mount(
<FunnelChartNext
data={mockData}
tooltipLabels={mockTooltipLabels}
yAxisOptions={{labelFormatter}}
state={ChartState.Success}
/>,
);

expect(funnel).toContainReactComponent(Chart, {
yAxisOptions: expect.objectContaining({labelFormatter}),
});
});
});
});
118 changes: 118 additions & 0 deletions packages/polaris-viz/src/hooks/tests/useFunnelBarScaling.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type {Root} from '@shopify/react-testing';
import {mount} from '@shopify/react-testing';
import {scaleLinear} from 'd3-scale';
import React from 'react';

import {
useFunnelBarScaling,
SCALING_RATIO_THRESHOLD,
MINIMUM_SEGMENT_HEIGHT_RATIO,
} from '../useFunnelBarScaling';

const mockYScale = scaleLinear().domain([0, 100]).range([0, 400]);

function parseData(result: Root<any>) {
return JSON.parse(result.domNode?.dataset.data ?? '');
}

describe('useFunnelBarScaling', () => {
it('returns shouldApplyScaling=false when ratio above threshold', () => {
function TestComponent() {
const data = useFunnelBarScaling({
yScale: mockYScale,
values: [90, 100],
});

return <span data-data={`${JSON.stringify(data)}`} />;
}

const result = mount(<TestComponent />);
const data = parseData(result);

expect(data.shouldApplyScaling).toBe(false);
});

it('returns shouldApplyScaling=true when ratio below threshold', () => {
function TestComponent() {
const data = useFunnelBarScaling({
yScale: mockYScale,
values: [5, 100],
});

return <span data-data={`${JSON.stringify(data)}`} />;
}

const result = mount(<TestComponent />);
const data = parseData(result);

expect(data.shouldApplyScaling).toBe(true);
});

describe('getBarHeight', () => {
it('returns original height when scaling not needed', () => {
function TestComponent() {
const data = useFunnelBarScaling({
yScale: mockYScale,
values: [90, 100],
});

const height = data.getBarHeight(90);
return <span data-data={`${JSON.stringify({height})}`} />;
}

const result = mount(<TestComponent />);
const data = parseData(result);

expect(data.height).toBe(mockYScale(90));
});

it('returns scaled height when scaling needed', () => {
function TestComponent() {
const data = useFunnelBarScaling({
yScale: mockYScale,
values: [5, 100],
});

const scaledHeight = data.getBarHeight(5);
const originalHeight = mockYScale(5);
const tallestHeight = mockYScale(100);

return (
<span
data-data={`${JSON.stringify({
scaledHeight,
originalHeight,
tallestHeight,
})}`}
/>
);
}

const result = mount(<TestComponent />);
const data = parseData(result);

expect(data.scaledHeight).toBeGreaterThan(data.originalHeight);
expect(data.scaledHeight).toBeLessThan(data.tallestHeight);
expect(data.scaledHeight / data.tallestHeight).toBeGreaterThanOrEqual(
MINIMUM_SEGMENT_HEIGHT_RATIO,
);
});

it('returns original height for tallest bar even when scaling applied', () => {
function TestComponent() {
const data = useFunnelBarScaling({
yScale: mockYScale,
values: [5, 100],
});

const height = data.getBarHeight(100);
return <span data-data={`${JSON.stringify({height})}`} />;
}

const result = mount(<TestComponent />);
const data = parseData(result);

expect(data.height).toBe(mockYScale(100));
});
});
});

0 comments on commit b546085

Please sign in to comment.