Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Added component tests for donut chart",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "none"
}
1 change: 1 addition & 0 deletions packages/react-charting/config/setup-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('@testing-library/jest-dom');
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div align="center">

# Component testing - Donut chart test plan

![Alt text](BasicDonutChart.png)

### Subcomponents: Pie and Legend

### Library used: jest and (enzyme or react testing library)

</div>

| Test case | Execution steps | Validation steps | Library used |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Test case 1: [Snapshot testing] [Component] | <ul><li>Renders donut chart with data.</li></ul> | Donut chart renders correctly | Enzyme |
| Test case 2: [Snapshot testing] [Individual Props] | Renders donut chart with: <ul><li>HideTooltip prop set to “true”.</li></ul> | Donut chart renders correctly | Enzyme |
| | <ul><li>HideLegend prop set to “true”.</li></ul> | Donut chart renders correctly | Enzyme |
| | <ul><li>EnabledLegendsWrapLines prop set to “true”.</li></ul> | Donut chart renders correctly | Enzyme |
| | <ul><li>ValueInsideDonut set to a string / number.</li></ul> | Donut chart renders correctly | Enzyme |
| | <ul><li>HideLabels prop set to "false".</li></ul> | Donut chart renders correctly | Enzyme |
| | <ul><li>HideLabels prop set to "false" and ShowLabelsInPercent prop set to "true".</li></ul> | Donut chart renders correctly | Enzyme |
| Test case 3: [Specific DOM elements] Renders individual elements on a prop change: | <ul><li>HideLegend prop is set to “false”.</li></ul> | Legend mounts correctly | Enzyme |
| | <ul><li>HideTootip prop is set to “false”.</li></ul> | Callout mounts correctly | Enzyme |
| | <ul><li>onRenderCalloutPerStack prop is not given.</li></ul> | Should not render onRenderCalloutPerStack | Enzyme |
| | <ul><li>onRenderCalloutPerDataPoint prop is given.</li></ul> | Should render onRenderCalloutPerDataPoint correctly | Enzyme |
| Test case 4: [Mouse events – Donut chart] Renders individual elements on mouse events: | <ul><li>On mouse over on the donut chart.</li></ul> | Should render callout | Enzyme |
| | <ul><li>On mouse move on Pie 1 (step 1) -> mouse leave (step 2) -> mouse move on Pie 2 (step 3).</li></ul> | Html in step 1 should not be the same as in step 3 | Enzyme |
| | <ul><li>On mouse over with onRenderCalloutPerDataPoint prop provided.</li></ul> | Should render the custom callout | Enzyme |
| | <ul><li>On mouse over, followed by mouse leave on callout.</li></ul> | On mouse over, callout should be defined, on mouse leave, callout should disappear. | Enzyme |
| Test case 5: [Mouse events – Legends] Renders individual elements on mouse events: | <ul><li>On mouse over on legends.</li></ul> | Should highlight the corresponding pie | RTL |
| | <ul><li>On mouse over on legends.</li></ul> | Should change the value inside donut with the legend value | Enzyme |
| | <ul><li>On click on Pie.</li></ul> | Should highlight the corresponding pie with aria-selected set to “true” and tabIndex set to 0. | RTL |
| | <ul><li>On mouse out after mouse over on first legend.</li></ul> | Should have opacity 0.1 for second Pie initially (during mouseOver on first legend) and opacity set to 1 for both the Pies on mouse out. | RTL |
| Test case 6: [Keyboard events – Donut chart] Renders individual elements on keyboard events: | <ul><li>On focus on a Pie.</li></ul> | Should render the corresponding callout | RTL |
| | <ul><li>On blur on a Pie.</li></ul> | Should remove focus from the corresponding Pie | RTL |
2 changes: 1 addition & 1 deletion packages/react-charting/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function getEsmOnlyPackagesToCjsMapping() {
const config = createConfig({
setupFiles: ['./config/tests.js'],
snapshotSerializers: ['@fluentui/jest-serializer-merge-styles', 'enzyme-to-json/serializer'],

setupFilesAfterEnv: ['./config/setup-env.js'],
moduleNameMapper: {
...getEsmOnlyPackagesToCjsMapping(),
},
Expand Down
1 change: 1 addition & 0 deletions packages/react-charting/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"dependencies": {
"@fluentui/react-focus": "^8.8.21",
"@fluentui/theme-samples": "^8.7.70",
"@microsoft/load-themed-styles": "^1.10.26",
"@types/d3-array": "1.2.1",
"@types/d3-axis": "1.0.10",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ function sharedAfterEach() {
const points: IChartDataPoint[] = [
{ legend: 'first', data: 20000, color: '#E5E5E5', xAxisCalloutData: '2020/04/30' },
{ legend: 'second', data: 39000, color: '#0078D4', xAxisCalloutData: '2020/04/20' },
{ legend: 'third', data: 45000, color: '#DADADA', xAxisCalloutData: '2020/04/25' },
];

const chartTitle = 'Stacked Bar chart example';

const chartPoints: IChartProps = {
export const chartPoints: IChartProps = {
chartTitle: chartTitle,
chartData: points,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { render, screen, queryAllByAttribute, fireEvent, act } from '@testing-library/react';
import { chartPoints } from './DonutChart.test';
import { DonutChart } from './index';
import * as React from 'react';
import { DarkTheme } from '@fluentui/theme-samples';
import { ThemeProvider } from '@fluentui/react';
import * as utils from '../../utilities/utilities';

test('Should hide callout on mouse leave', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} calloutProps={{ doNotLayer: true }} />);

// Act
const getById = queryAllByAttribute.bind(null, 'id');
fireEvent.mouseOver(getById(container, /Pie/i)[0]);
expect(getById(container, /callout/i)[0]).toBeDefined();
fireEvent.mouseLeave(getById(container, /Pie/i)[0]);

// Assert
expect(getById(container, /callout/i)[0]).toHaveStyle('opacity: 0');
expect(container).toMatchSnapshot();
});

test('Should show callout on focus', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} calloutProps={{ doNotLayer: true }} />);

// Act
const getById = queryAllByAttribute.bind(null, 'id');
fireEvent.focus(getById(container, /Pie/i)[0]);

// Assert
expect(getById(container, /focusRing/i)).toBeDefined();
});

test('Should remove focus on blur', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} calloutProps={{ doNotLayer: true }} />);

// Act
const getById = queryAllByAttribute.bind(null, 'id');
fireEvent.blur(getById(container, /Pie/i)[0]);

// Assert
const value = getById(container, /Pie/i)[0].getAttribute('id');
expect(value).not.toContain('focusRing');
});

test('Should highlight the corresponding Pie on mouse over on legends', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} hideLegend={false} />);

// Act
const legend = screen.queryByText('first');
expect(legend).toBeDefined();
fireEvent.mouseOver(legend!);

// Assert
const getById = queryAllByAttribute.bind(null, 'id');
expect(getById(container, /Pie.*?second/i)[0]).toHaveAttribute('opacity', '0.1');
expect(getById(container, /Pie.*?third/i)[0]).toHaveAttribute('opacity', '0.1');
});

test('Should select legend on single mouse click on legends', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} hideLegend={false} />);

// Act
const legend = screen.queryByText('first');
expect(legend).toBeDefined();
fireEvent.click(legend!);

// Assert
const getById = queryAllByAttribute.bind(null, 'id');
expect(getById(container, /Pie.*?second/i)[0]).toHaveAttribute('opacity', '0.1');
const firstLegend = screen.queryByText('first')?.closest('button');
expect(firstLegend).toHaveAttribute('aria-selected', 'true');
expect(firstLegend).toHaveAttribute('tabIndex', '0');
});

test('Should deselect legend on double mouse click on legends', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} hideLegend={false} />);

// Act
const legend = screen.queryByText('first');
expect(legend).toBeDefined();

//single click on first legend
fireEvent.click(legend!);
const getById = queryAllByAttribute.bind(null, 'id');
expect(getById(container, /Pie.*?second/i)[0]).toHaveAttribute('opacity', '0.1');
const firstLegend = screen.queryByText('first')?.closest('button');
expect(firstLegend).toHaveAttribute('aria-selected', 'true');
expect(firstLegend).toHaveAttribute('tabIndex', '0');
// double click on same first legend
fireEvent.click(legend!);

// Assert
expect(firstLegend).toHaveAttribute('aria-selected', 'false');
});

test('Should show Pies with same opacity on mouse out of legends', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} hideLegend={false} />);

// Act
const legend = screen.queryByText('first');
expect(legend).toBeDefined();
fireEvent.mouseOver(legend!);
const getById = queryAllByAttribute.bind(null, 'id');
expect(getById(container, /Pie.*?second/i)[0]).toHaveAttribute('opacity', '0.1');
fireEvent.mouseOut(legend!);

// Assert
expect(getById(container, /Pie.*?first/i)[0]).toHaveAttribute('opacity', '1');
expect(getById(container, /Pie.*?second/i)[0]).toHaveAttribute('opacity', '1');
});

test('Should display correct callout data on mouse move', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} calloutProps={{ doNotLayer: true }} />);

// Act
const getById = queryAllByAttribute.bind(null, 'id');
fireEvent.mouseOver(getById(container, /Pie/i)[0]);
expect(getById(container, /callout/i)[0]).toHaveTextContent('20,000');
fireEvent.mouseLeave(getById(container, /Pie/i)[0]);
fireEvent.mouseOver(getById(container, /Pie/i)[1]);

// Assert
expect(getById(container, /callout/i)[0]).toHaveTextContent('39,000');
});

test('Should reflect theme change', () => {
// Arrange
const { container } = render(
<ThemeProvider theme={DarkTheme}>
<DonutChart culture={window.navigator.language} data={chartPoints} innerRadius={55} />
</ThemeProvider>,
);

// Assert
expect(container).toMatchSnapshot();
});

describe('Screen resolution', () => {
const originalInnerWidth = global.innerWidth;
const originalInnerHeight = global.innerHeight;
afterEach(() => {
global.innerWidth = originalInnerWidth;
global.innerHeight = originalInnerHeight;
act(() => {
global.dispatchEvent(new Event('resize'));
});
});

test('Should remain unchanged on zoom in', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} width={300} height={300} />);

// Act
global.innerWidth = window.innerWidth / 2;
global.innerHeight = window.innerHeight / 2;
act(() => {
global.dispatchEvent(new Event('resize'));
});

// Assert
expect(container).toMatchSnapshot();
});

test('Should remain unchanged on zoom out', () => {
// Arrange
const { container } = render(<DonutChart data={chartPoints} innerRadius={55} width={300} height={300} />);

// Act
global.innerWidth = window.innerWidth * 2;
global.innerHeight = window.innerHeight * 2;
act(() => {
global.dispatchEvent(new Event('resize'));
});

// Assert
expect(container).toMatchSnapshot();
});
});

test('Should change value inside donut with the legend value on mouseOver legend ', () => {
// Mock the implementation of wrapTextInsideDonut as it internally calls a Browser Function like
// getComputedTextLength() which will otherwise lead to a crash if mounted
jest.spyOn(utils, 'wrapTextInsideDonut').mockImplementation(() => '1000');
// Arrange
const { container } = render(
<DonutChart data={chartPoints} innerRadius={55} hideLegend={false} valueInsideDonut={1000} />,
);
const getByClass = queryAllByAttribute.bind(null, 'class');

// Act
fireEvent.mouseOver(screen.getByText('first'));

// Assert
expect(getByClass(container, /insideDonutString.*?/)[0].textContent).toBe('20,000');
});
Loading