Skip to content

Commit

Permalink
Add tests for HiddenLegendTooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
mollerjorge committed Dec 12, 2024
1 parent 36f8614 commit 69065a7
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ export function useOverflowLegend(props: UseOverflowLegendProps) {
let lastVisibleIndex = allData.length;
const containerWidth =
width - leftMargin - horizontalMargin - activatorWidth;

legendItemDimensions.current
.filter((val) => Boolean(val))
.filter((dimension) => Boolean(dimension))
.reduce((totalWidth, card, index) => {
if (totalWidth + card.width + index * LEGEND_GAP > containerWidth) {
lastVisibleIndex = index;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@ import {
} from '@shopify/polaris-viz-core';
import {Fragment} from 'react';

import {useResizeObserver} from '../../hooks';
import type {LineChartProps} from '../LineChart';
import {LineChart} from '../LineChart';

import {RelatedAreas, MissingDataArea, CustomLegend} from './components';
import styles from './LineChartRelational.scss';

const SMALL_SCREEN_WIDTH = 400;

export type LineChartRelationalProps = Omit<
LineChartProps,
Expand All @@ -35,13 +31,12 @@ export function LineChartRelational(props: LineChartRelationalProps) {
tooltipOptions,
xAxisOptions,
yAxisOptions,
renderHiddenLegendLabel,
} = {
...DEFAULT_CHART_PROPS,
...props,
};

const {setRef, entry} = useResizeObserver();

const dataWithHiddenRelational = data.map((series) => {
return {
...series,
Expand All @@ -53,57 +48,56 @@ export function LineChartRelational(props: LineChartRelationalProps) {
});

const relatedAreasKey = buildRelatedAreasKey(dataWithHiddenRelational);
const hideLegends = entry?.contentRect.width
? entry.contentRect.width < SMALL_SCREEN_WIDTH
: false;

return (
<div ref={setRef} className={styles.Container}>
<LineChart
annotations={annotations}
data={dataWithHiddenRelational}
emptyStateText={emptyStateText}
errorText={errorText}
id={id}
isAnimated={isAnimated}
renderLegendContent={(
{getColorVisionStyles, getColorVisionEventAttrs},
activeIndex,
legendItemDimensions,
) => {
<LineChart
annotations={annotations}
data={dataWithHiddenRelational}
emptyStateText={emptyStateText}
errorText={errorText}
id={id}
isAnimated={isAnimated}
renderLegendContent={(
{getColorVisionStyles, getColorVisionEventAttrs},
activeIndex,
) => {
return (
<CustomLegend
getColorVisionStyles={getColorVisionStyles}
getColorVisionEventAttrs={getColorVisionEventAttrs}
data={data}
theme={theme ?? DEFAULT_THEME_NAME}
seriesNameFormatter={seriesNameFormatter}
activeIndex={activeIndex ?? 0}
renderHiddenLegendLabel={renderHiddenLegendLabel}
/>
);
}}
seriesNameFormatter={seriesNameFormatter}
showLegend={showLegend}
skipLinkText={skipLinkText}
slots={{
chart: (props) => {
return (
<CustomLegend
getColorVisionStyles={getColorVisionStyles}
getColorVisionEventAttrs={getColorVisionEventAttrs}
data={data}
theme={theme ?? DEFAULT_THEME_NAME}
seriesNameFormatter={seriesNameFormatter}
hideLegends={hideLegends}
activeIndex={activeIndex ?? 0}
legendItemDimensions={legendItemDimensions ?? {current: []}}
/>
<Fragment>
<MissingDataArea {...props} data={data} />
<RelatedAreas
data={data}
// remount the area otherwise it can't animate
// between areas that are differently sized
key={relatedAreasKey}
{...props}
/>
</Fragment>
);
}}
seriesNameFormatter={seriesNameFormatter}
showLegend={showLegend}
skipLinkText={skipLinkText}
slots={{
chart: (props) => {
return (
<Fragment>
<MissingDataArea {...props} data={data} />
<RelatedAreas data={data} key={relatedAreasKey} {...props} />
</Fragment>
);
},
}}
state={state}
theme={theme}
tooltipOptions={tooltipOptions}
xAxisOptions={xAxisOptions}
yAxisOptions={yAxisOptions}
/>
</div>
},
}}
state={state}
theme={theme}
tooltipOptions={tooltipOptions}
xAxisOptions={xAxisOptions}
yAxisOptions={yAxisOptions}
/>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.Container {
display: flex;
flex-wrap: wrap;
gap: 12px;
flex-wrap: nowrap;
gap: 10px;
list-style: none;
margin: 0;
padding: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,43 @@ import {
COLOR_VISION_SINGLE_ITEM,
useChartContext,
} from '@shopify/polaris-viz-core';
import {useCallback, useState} from 'react';
import {useCallback, useRef, useState} from 'react';

import {useOverflowLegend} from '../../../../components/LegendContainer/hooks/useOverflowLegend';
import {HiddenLegendTooltip} from '../../../../components/LegendContainer/components/HiddenLegendTooltip';
import type {ColorVisionInteractionMethods} from '../../../../types';
import type {
ColorVisionInteractionMethods,
RenderHiddenLegendLabel,
} from '../../../../types';
import type {LegendItemDimension} from '../../../../components/Legend';
import {LegendItem} from '../../../../components/Legend';

import {deduplicateByRelatedIndex} from './utilities/deduplicateByRelatedIndex';
import styles from './CustomLegend.scss';

export interface Props extends ColorVisionInteractionMethods {
data: DataSeries[];
seriesNameFormatter: LabelFormatter;
theme: string;
hideLegends?: boolean;
activeIndex: number;
legendItemDimensions: React.RefObject<LegendItemDimension[]>;
renderHiddenLegendLabel?: RenderHiddenLegendLabel;
}

const HORIZONTAL_MARGIN = 16;
const LEFT_MARGIN = 12;
export function CustomLegend({
data,
getColorVisionEventAttrs,
getColorVisionStyles,
seriesNameFormatter,
theme,
activeIndex,
legendItemDimensions,
renderHiddenLegendLabel = (count) => `+${count} more`,
}: Props) {
const {containerBounds} = useChartContext();
const deduplicatedData = deduplicateByRelatedIndex(data);
const [activatorWidth, setActivatorWidth] = useState(0);
const legendItemDimensions = useRef([{width: 0, height: 0}]);

const overflowLegendProps = {
direction: 'horizontal' as const,
Expand All @@ -43,8 +49,8 @@ export function CustomLegend({
legendItemDimensions,
width: containerBounds.width,
activatorWidth,
leftMargin: 16,
horizontalMargin: 16,
leftMargin: LEFT_MARGIN,
horizontalMargin: HORIZONTAL_MARGIN,
};

const {displayedData, hiddenData} = useOverflowLegend(overflowLegendProps);
Expand All @@ -57,29 +63,28 @@ export function CustomLegend({
(series) => series?.metadata?.relatedIndex != null,
);

const percentileIndex = lineSeries.length + 1;

const hasHiddenItems = hiddenData.length > 0;

const onDimensionChange = useCallback(
(index, dimensions: LegendItemDimension) => {
if (legendItemDimensions?.current) {
legendItemDimensions.current[index] = dimensions;
}
},
[legendItemDimensions],
const percentileIndex = data.findIndex(
(series) => series.metadata?.relatedIndex != null,
);

const hasHiddenData = displayedData.length < deduplicatedData.length;
const visibleSeries = hasHiddenData ? displayedData : lineSeries;

const formattedHiddenData = hiddenData.map((series) => ({
color: series.color!,
name: seriesNameFormatter(series?.metadata?.legendLabel ?? series.name),
shape: series.styleOverride?.tooltip?.shape ?? 'Line',
lineStyle: series.metadata?.lineStyle,
}));

const onDimensionChange = useCallback(
(index, dimensions: LegendItemDimension) => {
if (legendItemDimensions?.current) {
legendItemDimensions.current[index] = dimensions;
}
},
[legendItemDimensions],
);

return (
<ul className={styles.Container}>
{visibleSeries.map((series) => {
Expand Down Expand Up @@ -137,28 +142,17 @@ export function CustomLegend({
</li>
)}

{hasHiddenItems && (
{hasHiddenData && (
<HiddenLegendTooltip
activeIndex={activeIndex}
colorVisionType={COLOR_VISION_SINGLE_ITEM}
data={formattedHiddenData}
theme={theme}
label={`+${hiddenData.length} more`}
label={renderHiddenLegendLabel(hiddenData.length)}
lastVisibleIndex={deduplicatedData.length - hiddenData.length}
setActivatorWidth={setActivatorWidth}
/>
)}
</ul>
);
}

const deduplicateByRelatedIndex = (data: any[]) => {
const existingRelatedIndex = new Set();
return data.filter((item) => {
const relatedIndex = item.metadata?.relatedIndex;
if (!relatedIndex) return true;
if (existingRelatedIndex.has(relatedIndex)) return false;
existingRelatedIndex.add(relatedIndex);
return true;
});
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
/* eslint-disable @shopify/strict-component-boundaries */
import {mount} from '@shopify/react-testing';
import {DEFAULT_THEME_NAME} from '@shopify/polaris-viz-core';
import {useChartContext} from '@shopify/polaris-viz-core/src/hooks/useChartContext';

import type {Props} from '../CustomLegend';
import {CustomLegend} from '../CustomLegend';
import {LegendItem} from '../../../../Legend';
import {HiddenLegendTooltip} from '../../../../LegendContainer/components/HiddenLegendTooltip';

jest.mock('@shopify/polaris-viz-core/src/hooks/useChartContext');

describe('<CustomLegend />', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('seriesNameFormatter', () => {
it('renders formatted series name', () => {
const component = mount(
Expand All @@ -29,6 +38,29 @@ describe('<CustomLegend />', () => {
});
});
});

describe('HiddenLegendTooltip', () => {
it('shows HiddenLegendTooltip when container does not fit all legend items', () => {
(useChartContext as jest.Mock).mockImplementation(() => ({
containerBounds: {width: 20, height: 250, x: 0, y: 0},
}));

const component = mount(<CustomLegend {...MOCK_PROPS} />);

expect(component).toContainReactComponent(HiddenLegendTooltip);
expect(component).toContainReactText('+1 more');
});

it('does not show HiddenLegendTooltip when container fits all legend items', () => {
(useChartContext as jest.Mock).mockImplementation(() => ({
containerBounds: {width: 1200, height: 250, x: 0, y: 0},
}));

const component = mount(<CustomLegend {...MOCK_PROPS} />);

expect(component).not.toContainReactComponent(HiddenLegendTooltip);
});
});
});

const MOCK_PROPS: Props = {
Expand All @@ -48,10 +80,6 @@ const MOCK_PROPS: Props = {
legendLabel: '75th - 25th percentile',
},
},
{
name: 'Median',
data: [{value: 238, key: '2020-03-01T12:00:00'}],
},
{
name: '25th percentile',
data: [{value: 88, key: '2020-03-01T12:00:00'}],
Expand All @@ -66,5 +94,20 @@ const MOCK_PROPS: Props = {
getColorVisionEventAttrs: jest.fn(),
getColorVisionStyles: jest.fn(),
activeIndex: 0,
legendItemDimensions: {current: []},
legendItemDimensions: {
current: [
{
width: 71,
height: 24,
},
{
width: 66,
height: 24,
},
{
width: 100,
height: 24,
},
],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const deduplicateByRelatedIndex = (data: any[]) => {
const existingRelatedIndex = new Set();
return data.filter((item) => {
const relatedIndex = item.metadata?.relatedIndex;
if (!relatedIndex) return true;
if (existingRelatedIndex.has(relatedIndex)) return false;
existingRelatedIndex.add(relatedIndex);
return true;
});
};

0 comments on commit 69065a7

Please sign in to comment.