Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Improving first render cycle of cartesian chart",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
//enableComputationOptimization is used for optimized code to group data points by x value
//from O(n^2) to O(n) using a map.
private _enableComputationOptimization: boolean;
private _firstRenderOptimization: boolean;

public constructor(props: IAreaChartProps) {
super(props);
Expand Down Expand Up @@ -144,6 +145,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
this._rectId = getId('rectangle');
this._tooltipId = getId('AreaChartTooltipID');
this._enableComputationOptimization = true;
this._firstRenderOptimization = true;
}

public componentDidUpdate() {
Expand Down Expand Up @@ -203,6 +205,7 @@ export class AreaChartBase extends React.Component<IAreaChartProps, IAreaChartSt
getmargins={this._getMargins}
customizedCallout={this._getCustomizedCallout()}
onChartMouseLeave={this._handleChartMouseLeave}
enableFirstRenderOptimization={this.props.enablePerfOptimization && this._firstRenderOptimization}
/* eslint-disable react/jsx-no-bind */
// eslint-disable-next-line react/no-children-prop
children={(props: IChildProps) => {
Expand Down
125 changes: 91 additions & 34 deletions packages/react-charting/src/components/AreaChart/AreaChart.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
jest.mock('react-dom');
import * as React from 'react';
import { resetIds } from '../../Utilities';
import * as renderer from 'react-test-renderer';
import { mount, ReactWrapper } from 'enzyme';
import { IAreaChartProps, AreaChart } from './index';
import { IAreaChartState, AreaChartBase } from './AreaChart.base';
Expand All @@ -10,9 +9,15 @@ import toJson from 'enzyme-to-json';

// Wrapper of the AreaChart to be tested.
let wrapper: ReactWrapper<IAreaChartProps, IAreaChartState, AreaChartBase> | undefined;
const originalRAF = window.requestAnimationFrame;

function sharedBeforeEach() {
resetIds();
jest.useFakeTimers();
Object.defineProperty(window, 'requestAnimationFrame', {
writable: true,
value: (callback: FrameRequestCallback) => callback(0),
});
}

function sharedAfterEach() {
Expand All @@ -27,6 +32,8 @@ function sharedAfterEach() {
if ((global.setTimeout as any).mock) {
jest.useRealTimers();
}
jest.useRealTimers();
window.requestAnimationFrame = originalRAF;
}

const points: ILineChartPoints[] = [
Expand Down Expand Up @@ -69,68 +76,118 @@ const emptyChartPoints = {
};

describe('AreaChart snapShot testing', () => {
it('renders Areachart correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
beforeEach(() => {
resetIds();
});
afterEach(() => {
if (wrapper) {
wrapper.unmount();
wrapper = undefined;
}

it('renders hideLegend hhh correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} hideLegend={true} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
// Do this after unmounting the wrapper to make sure if any timers cleaned up on unmount are
// cleaned up in fake timers world
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((global.setTimeout as any).mock) {
jest.useRealTimers();
}
});

it('renders hideTooltip correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} hideTooltip={true} />);
const tree = component.toJSON();
it('renders Areachart correctly', async () => {
wrapper = mount(<AreaChart data={chartPoints} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('renders enabledLegendsWrapLines correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} enabledLegendsWrapLines={true} />);
const tree = component.toJSON();
it('renders hideLegend hhh correctly', async () => {
wrapper = mount(<AreaChart data={chartPoints} hideLegend={true} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('renders showXAxisLablesTooltip correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} showXAxisLablesTooltip={true} />);
const tree = component.toJSON();
it('renders hideTooltip correctly', async () => {
wrapper = mount(<AreaChart data={chartPoints} hideTooltip={true} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('renders wrapXAxisLables correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} wrapXAxisLables={true} />);
const tree = component.toJSON();
it('renders enabledLegendsWrapLines correctly', async () => {
wrapper = mount(<AreaChart data={chartPoints} enabledLegendsWrapLines={true} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('renders yAxisTickFormat correctly', () => {
const component = renderer.create(<AreaChart data={chartPoints} yAxisTickFormat={'/%d'} />);
const tree = component.toJSON();
it('renders yAxisTickFormat correctly', async () => {
wrapper = mount(<AreaChart data={chartPoints} yAxisTickFormat={'/%d'} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('renders Areachart with single point correctly', () => {
const component = renderer.create(<AreaChart data={singleChartPoint} />);
const tree = component.toJSON();
it('renders Areachart with single point correctly', async () => {
wrapper = mount(<AreaChart data={singleChartPoint} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('Should render with default colors when line color is not provided', () => {
it('Should render with default colors when line color is not provided', async () => {
const lineColor = points[0].color;
delete points[0].color;

const component = renderer.create(<AreaChart data={chartPoints} />);
const tree = component.toJSON();
wrapper = mount(<AreaChart data={chartPoints} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();

points[0].color = lineColor;
});

it('Should not render circles when optimizeLargeData is true', () => {
const component = renderer.create(<AreaChart data={chartPoints} optimizeLargeData />);
const tree = component.toJSON();
it('Should not render circles when optimizeLargeData is true', async () => {
wrapper = mount(<AreaChart data={chartPoints} optimizeLargeData />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});

it('renders showXAxisLablesTooltip correctly', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any

wrapper = mount(<AreaChart data={chartPoints} showXAxisLablesTooltip={true} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
if (wrapper) {
const tree = toJson(wrapper, { mode: 'deep' });
expect(tree).toMatchSnapshot();
}
});

it('renders wrapXAxisLables correctly', async () => {
const mockGetComputedTextLength = jest.fn().mockReturnValue(100);

// Replace the original method with the mock implementation
Object.defineProperty(
Object.getPrototypeOf(document.createElementNS('http://www.w3.org/2000/svg', 'tspan')),
'getComputedTextLength',
{
value: mockGetComputedTextLength,
},
);
wrapper = mount(<AreaChart data={chartPoints} wrapXAxisLables={true} />);
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
const tree = toJson(wrapper!, { mode: 'deep' });
expect(tree).toMatchSnapshot();
});
});
Expand Down
Loading