diff --git a/change/@fluentui-react-charting-1ff12240-f722-4a36-a1d7-9aa7f5fcd495.json b/change/@fluentui-react-charting-1ff12240-f722-4a36-a1d7-9aa7f5fcd495.json new file mode 100644 index 0000000000000..7370b0c585073 --- /dev/null +++ b/change/@fluentui-react-charting-1ff12240-f722-4a36-a1d7-9aa7f5fcd495.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Added component tests for Vertical bar chart", + "packageName": "@fluentui/react-charting", + "email": "srmukher@microsoft.com", + "dependentChangeType": "none" +} diff --git a/packages/react-charting/docs/TestPlans/TestingGuide.md b/packages/react-charting/docs/TestPlans/TestingGuide.md new file mode 100644 index 0000000000000..83102d3ae5943 --- /dev/null +++ b/packages/react-charting/docs/TestPlans/TestingGuide.md @@ -0,0 +1,13 @@ +This document highlights few common testing practices for any new tests that are being added to the charting library. + +1. Any new test should always be added using React Testing Library. +2. The utility functions like `testWithoutWait`, `testWithWait` and `testScreenResolutionChanges` can be used in writing the component tests which will reduce the number of lines of code. +3. `testWithWait` is needed when we are either trying to provide any prop and update or we are trying to extract any sub-sub svg element like bars within the vertical bar chart. +4. Order of imports is important while writing tests. Following is an example: + Importing the test data before importing the render function from the '@testing-library/react' results in erroneous rendering of the chart. + For example: for Vertical bar charts, improper sequencing of the imports results in the following output: + `import { chartPoints } from '../VerticalBarChart/VerticalBarChart.test';` + `import { render, screen, queryAllByAttribute, fireEvent, act } from '@testing-library/react';` + However, reordering the import sequence results in the correct rendering as follows: + `import { render, screen, queryAllByAttribute } from '@testing-library/react';` + `import { chartPoints } from './VerticalBarChart.test';` diff --git a/packages/react-charting/docs/TestPlans/VerticalBarChart/ComponentTests.md b/packages/react-charting/docs/TestPlans/VerticalBarChart/ComponentTests.md new file mode 100644 index 0000000000000..18ae22daf4c6b --- /dev/null +++ b/packages/react-charting/docs/TestPlans/VerticalBarChart/ComponentTests.md @@ -0,0 +1,65 @@ +**Vertical Bar Chart – Component test plan** + +**Sub-components: Bar, Line, Legends, Callout, Labels** + +1. **Bar: bar data, bar color (single/multi colors), bar label (show/hide)** +1. **Line: show/hide line, highlight data point on line and show callout** +1. **Legends: show/hide legends, highlight the corresponding bar/line on legend hover** +1. **Callout: Default/custom callout** +1. **Labels: x-Axis labels default/rotated** + +| **Test steps** | **Validation** | **Tool used** | +| :-----------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | :-----------: | +| Test 1: [Snapshot testing] | | | +| - With only data prop, numerical data on x-axis. | Renders vertical bar chart correctly | Enzyme | +| - With only data prop, string data on x-axis. | Renders vertical bar chart correctly | RTL | +| - With HideLegend prop set to “true” | Should hide legends | Enzyme | +| - With HideTooltip prop set to “true” | Should hide the tooltip in chart | Enzyme | +| - With EnabledLegendsWrapLines set to “true” | Should enable the legends to wrap lines if there is not enough space to show all legends on a single line | Enzyme | +| - With ShowXAxisLablesTooltip set to “true” | Should truncate x axis labels and show tooltip on x axis labels | Enzyme | +| - With WrapXAxisLables set to “true” | Should wrap x axis label values | Enzyme | +| - With yAxisTickFormat set to “%d” |

Should render the y-axis ticks in the format specified

| Enzyme | +| - With HideLabels set to “true” | Should hide the bar labels | Enzyme | +| Test 2: Basic props testing | | | +| - HideLegend prop set to “false” | Should mount legend when hideLegend is false | Enzyme | +| - HideTooltip prop set to “false” | Should mount callout when hideTootip is false | Enzyme | +| - onRenderCalloutPerStack prop is not given | Should not render onRenderCalloutPerStack | Enzyme | +| - onRenderCalloutPerDataPoint is given | Should render onRenderCalloutPerDataPoint | Enzyme | +| - onRenderCalloutPerDataPoint is not given | Should not render onRenderCalloutPerDataPoint | Enzyme | +| Test 3: Render calling with respective to props | | | +| - No prop changes: Mount vertical bar chart and then set the same props again | Render function should have been called twice | Enzyme | +| - Prop changes: Mount vertical bar chart and then set the some other prop | Render function should have been called twice | Enzyme | +| Test 4: Mouse events | | | +| - Mouse over on a bar | Should render callout correctly on mouseover | Enzyme | +| - Mouse over on a bar with customized callout | Should render customized callout on mouseover | Enzyme | +| Test 5: Render empty chart aria label div when chart is empty | | | +| - Vertical bar chart mounted with non-empty data | No empty chart aria label div rendered | Enzyme | +| - Vertical bar chart mounted with empty data | Empty chart aria label div rendered | Enzyme | +| Test 6: Render empty chart calling with respective to props | | | +| - prop changes: Mount vertical bar chart with empty data and then set the props | Render function should have been called 3 times | Enzyme | +| Test 7: [Sub-Component]: Vertical Bar | | | +| - Specify bar width | Should render the bar with the given width | RTL | +| - Specify bar colors (multiple) | Should render the bars with the specified colors | RTL | +| - Specify to use a single color for all bars | Should render the bars with the single specified color | RTL | +| - Hide the bar labels | Should render the bars with labels hidden | RTL | +| - Provide xAxisPadding between the bars | Should render the bars with the given padding between bar's or lines in the graph | E2E | +| - Localize the numbers of the bars with a given culture | Should render the bars with the numbers localized in the given culture | E2E | +| Test 8: [Sub-Component]: Line | | | +| - Specify line data | Should render line with the data provided | RTL | +| - Hover mouse over the data points | Should highlight the data points (No callout is rendered when we hover only on the line. Callout appears on hover over the bars only.) | RTL | +| Test 9: [Sub-Component]: Legends | | | +| - Hide legends | Should not show any rendered legends | RTL | +| - Hover mouse over bar legends | Should reduce the opacity of the other bars/lines and their legends | RTL | +| - Hover mouse over line legends | Should reduce the opacity of the other bars/lines and their legends | RTL | +| Test 10: [Sub-Component]: Callout | | | +| - Hover mouse over a bar | Should call the handler on mouse over bar | RTL | +| - Hover mouse over a bar | Should show the default callout over that bar | RTL | +| - Specify custom callout and hover mouse over a bar | Should show the custom callout over that bar | RTL | +| - Specify custom callout and hover mouse over the line | Should not show the custom callout over that line as custom callout is rendered only on mouse over on the bars. | RTL | +| Test 11: [Sub-Component]: x-axis labels | | | +| - Truncate x-axis labels | Should show the x-axis labels tooltip when hovered | RTL | +| - Rotate x-axis labels | Should rotate the x-axis labels by 45 degrees | RTL | +| Test 12: [Sub-Component]: Screen resolution | | | +| - Increase the screen resolution (zoom in) | Should remain unchanged on zoom in | RTL | +| - Decrease the screen resolution (zoom out) | Should remain unchanged on zoom out | RTL | +| Test 13: Theme changed to Dark Theme | Should reflect theme change | RTL | diff --git a/packages/react-charting/package.json b/packages/react-charting/package.json index 930ff9d8c8774..0d0559a4ad977 100644 --- a/packages/react-charting/package.json +++ b/packages/react-charting/package.json @@ -65,6 +65,7 @@ }, "peerDependencies": { "@fluentui/react": "^8.110.8", + "@testing-library/react": "12.1.2", "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", "react": ">=16.8.0 <19.0.0", diff --git a/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx b/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx index bf8f32775d352..6ce8d3317cce4 100644 --- a/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx +++ b/packages/react-charting/src/components/VerticalBarChart/VerticalBarChart.base.tsx @@ -232,6 +232,8 @@ export class VerticalBarChartBase extends React.Component 0) { line.push( { + testWithoutWait( + 'Should render the vertical bar chart with numeric x-axis data', + VerticalBarChart, + { data: chartPoints }, + container => { + // Assert + expect(container).toMatchSnapshot(); + }, + ); + + testWithoutWait( + 'Should render the vertical bar chart with string x-axis data', + VerticalBarChart, + { data: simplePoints }, + container => { + // Assert + expect(container).toMatchSnapshot(); + }, + ); +}); + +describe('Vertical bar chart - Subcomponent bar', () => { + testWithWait( + 'Should render the bar with the given width', + VerticalBarChart, + { data: chartPoints, barWidth: 100 }, + container => { + // Assert + const bars = getById(container, /_VBC_bar/i); + expect(bars).toHaveLength(3); + expect(bars[0].getAttribute('width')).toEqual('100'); + expect(bars[1].getAttribute('width')).toEqual('100'); + expect(bars[2].getAttribute('width')).toEqual('100'); + }, + ); + + testWithWait( + 'Should render the bars with the specified colors', + VerticalBarChart, + { data: chartPoints }, + container => { + // colors mentioned in the data points itself + // Assert + const bars = getById(container, /_VBC_bar/i); + expect(bars[0].getAttribute('fill')).toEqual('#0078d4'); + expect(bars[1].getAttribute('fill')).toEqual('#002050'); + expect(bars[2].getAttribute('fill')).toEqual('#00188f'); + }, + ); + + testWithWait( + 'Should render the bars with the a single color', + VerticalBarChart, + { data: chartPoints, useSingleColor: true }, + container => { + // Assert + const bars = getById(container, /_VBC_bar/i); + expect(bars[0].getAttribute('fill')).toEqual('#00bcf2'); + expect(bars[1].getAttribute('fill')).toEqual('#00bcf2'); + expect(bars[2].getAttribute('fill')).toEqual('#00bcf2'); + }, + ); + + testWithWait( + 'Should render the bars with labels hidden', + VerticalBarChart, + { data: chartPoints, hideLabels: true }, + container => { + // Assert + expect(getByClass(container, /barLabel/i)).toHaveLength(0); + }, + ); +}); + +describe('Vertical bar chart - Subcomponent line', () => { + testWithoutWait('Should render line along with bars', VerticalBarChart, { data: pointsWithLine }, container => { + const line = getById(container, /_VBC_line/i); + const points = getById(container, /_VBC_point/i); + // Assert + expect(line).toHaveLength(1); + expect(points).toHaveLength(7); + }); + testWithoutWait( + 'Should highlight the data points and not render the corresponding callout', + VerticalBarChart, + { data: pointsWithLine }, + container => { + const firstPointonLine = getById(container, /_VBC_point/i)[0]; + expect(firstPointonLine).toBeDefined(); + fireEvent.mouseOver(firstPointonLine); + // Assert + expect(firstPointonLine.getAttribute('visibility')).toEqual('visibility'); + expect(getById(container, /toolTipcallout/i)).toHaveLength(0); + }, + ); +}); + +describe('Vertical bar chart - Subcomponent Legends', () => { + testWithoutWait( + 'Should not show any rendered legends when hideLegend is true', + VerticalBarChart, + { data: pointsWithLine, hideLegend: true }, + container => { + // Legends have 'rect' as a part of their classname + expect(getByClass(container, /rect/i)).toHaveLength(0); + }, + ); + testWithWait( + 'Should reduce the opacity of the other bars/lines and their legends on mouse over a line legend', + VerticalBarChart, + { data: pointsWithLine, lineLegendText: 'just line' }, + container => { + const bars = getById(container, /_VBC_bar/i); + const line = getById(container, /_VBC_line/i)[0]; + const legends = screen.getAllByText((content, element) => element!.tagName.toLowerCase() === 'button'); + expect(line).toBeDefined(); + expect(bars).toHaveLength(8); + expect(legends).toHaveLength(9); + fireEvent.mouseOver(screen.getByText('just line')); + expect(line.getAttribute('opacity')).toEqual('1'); + expect(screen.getByText('Oranges')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Dogs')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Apples')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Bananas')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Giraffes')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Cats')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Elephants')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Monkeys')).toHaveStyle('opacity: 0.67'); + expect(line).toBeDefined(); + expect(bars[0]).toBeDefined(); + expect(bars[0]).toHaveStyle('opacity: 0.1'); + expect(bars[1]).toBeDefined(); + expect(bars[1]).toHaveStyle('opacity: 0.1'); + expect(bars[2]).toBeDefined(); + expect(bars[2]).toHaveStyle('opacity: 0.1'); + expect(bars[3]).toBeDefined(); + expect(bars[3]).toHaveStyle('opacity: 0.1'); + expect(bars[4]).toBeDefined(); + expect(bars[4]).toHaveStyle('opacity: 0.1'); + expect(bars[5]).toBeDefined(); + expect(bars[5]).toHaveStyle('opacity: 0.1'); + expect(bars[6]).toBeDefined(); + expect(bars[6]).toHaveStyle('opacity: 0.1'); + expect(bars[7]).toBeDefined(); + expect(bars[7]).toHaveStyle('opacity: 0.1'); + }, + ); + testWithWait( + 'Should reduce the opacity of the other bars/lines and their legends on mouse over a bar legend', + VerticalBarChart, + { data: pointsWithLine, lineLegendText: 'just line' }, + container => { + const bars = getById(container, /_VBC_bar/i); + const line = getById(container, /_VBC_line/i); + const legends = screen.getAllByText((content, element) => element!.tagName.toLowerCase() === 'button'); + expect(line).toBeDefined(); + expect(bars).toHaveLength(8); + expect(legends).toHaveLength(9); + fireEvent.mouseOver(screen.getByText('Oranges')); + expect(screen.getByText('just line')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Dogs')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Apples')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Bananas')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Giraffes')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Cats')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Elephants')).toHaveStyle('opacity: 0.67'); + expect(screen.getByText('Monkeys')).toHaveStyle('opacity: 0.67'); + expect(line).toBeDefined(); + expect(bars[1]).toBeDefined(); + expect(bars[1]).toHaveStyle('opacity: 0.1'); + expect(bars[2]).toBeDefined(); + expect(bars[2]).toHaveStyle('opacity: 0.1'); + expect(bars[3]).toBeDefined(); + expect(bars[3]).toHaveStyle('opacity: 0.1'); + expect(bars[4]).toBeDefined(); + expect(bars[4]).toHaveStyle('opacity: 0.1'); + expect(bars[5]).toBeDefined(); + expect(bars[5]).toHaveStyle('opacity: 0.1'); + expect(bars[6]).toBeDefined(); + expect(bars[6]).toHaveStyle('opacity: 0.1'); + expect(bars[7]).toBeDefined(); + expect(bars[7]).toHaveStyle('opacity: 0.1'); + }, + ); +}); + +describe('Vertical bar chart - Subcomponent callout', () => { + test('Should call the handler on mouse over bar and on mouse leave from bar', async () => { + // Arrange + const handleMouseOver = jest.spyOn(VerticalBarChartBase.prototype as any, '_onBarHover'); + const { container } = render(); + await waitFor(() => { + const bars = getById(container, /_VBC_bar/i); + expect(bars).toHaveLength(8); + fireEvent.mouseOver(bars[0]); + // Assert + expect(handleMouseOver).toHaveBeenCalled(); + }); + }); + + testWithWait( + 'Should show the callout over the bar on mouse over', + VerticalBarChart, + { data: pointsWithLine, calloutProps: { doNotLayer: true } }, + container => { + const bars = getById(container, /_VBC_bar/i); + expect(bars).toHaveLength(8); + fireEvent.mouseOver(bars[0]); + // Assert + expect(getById(container, /toolTipcallout/i)).toBeDefined(); + }, + ); + + testWithWait( + 'Should show the callout over the line on mouse over', + VerticalBarChart, + { data: pointsWithLine, calloutProps: { doNotLayer: true } }, + container => { + const line = getById(container, /_VBC_line/i)[0]; + expect(line).toBeDefined(); + fireEvent.mouseOver(line); + // Assert + expect(getById(container, /toolTipcallout/i)).toBeDefined(); + }, + ); + + testWithWait( + 'Should show the custom callout over the bar on mouse over', + VerticalBarChart, + { + data: pointsWithLine, + calloutProps: { doNotLayer: true }, + onRenderCalloutPerDataPoint: (props: IVerticalBarChartProps) => + props ? ( +
+

Custom Callout Content

+
+ ) : null, + }, + container => { + const bars = getById(container, /_VBC_bar/i); + expect(bars).toHaveLength(8); + fireEvent.mouseOver(bars[0]); + // Assert + expect(getById(container, /toolTipcallout/i)).toBeDefined(); + expect(screen.queryByText('Custom Callout Content')).toBeDefined(); + }, + ); + + testWithWait( + 'Should not show the custom callout over the line on mouse over', + VerticalBarChart, + { + data: pointsWithLine, + calloutProps: { doNotLayer: true }, + onRenderCalloutPerDataPoint: (props: IVerticalBarChartProps) => + props ? ( +
+

Custom Callout Content

+
+ ) : null, + }, + container => { + const line = getById(container, /_VBC_line/i)[0]; + expect(line).toBeDefined(); + fireEvent.mouseOver(line); + // Assert + expect(getById(container, /toolTipcallout/i)).toBeDefined(); + expect(screen.queryByText('Custom Callout Content')).toBeNull(); + }, + ); +}); + +describe('Vertical bar chart - Subcomponent xAxis Labels', () => { + testWithWait( + 'Should show the x-axis labels tooltip when hovered', + VerticalBarChart, + { data: pointsWithLine, showXAxisLablesTooltip: true }, + container => { + const bars = getById(container, /_VBC_bar/i); + expect(bars).toHaveLength(8); + fireEvent.mouseOver(bars[0]); + // Assert + expect(getById(container, /showDots/i)).toHaveLength(5); + expect(getById(container, /showDots/i)[0]!.textContent!).toEqual('20,0...'); + }, + ); + + testWithWait( + 'Should show rotated x-axis labels', + VerticalBarChart, + { data: simplePoints, rotateXAxisLables: true }, + container => { + // Arrange + expect(getByClass(container, /tick/i)[0].getAttribute('transform')).toContain('rotate(-45)'); + }, + ); +}); + +describe('Screen resolution', () => { + testScreenResolutionChanges(() => { + const { container } = render(); + // Assert + expect(container).toMatchSnapshot(); + }); +}); + +test('Should reflect theme change', () => { + // Arrange + const { container } = render( + + + , + ); + // Assert + expect(container).toMatchSnapshot(); +}); + +describe('Vertical bar chart re-rendering', () => { test('Should re-render the vertical bar chart with data', async () => { // Arrange const { container, rerender } = render(); - const getById = queryAllByAttribute.bind(null, 'id'); // Assert expect(container).toMatchSnapshot(); expect(getById(container, /_VBC_empty/i)).toHaveLength(1); diff --git a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap index 4ef68b953b603..2632076c3df9c 100644 --- a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap +++ b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap @@ -114,6 +114,7 @@ exports[`VerticalBarChart - mouse events Should render callout correctly on mous data-is-focusable={true} fill="#0078d4" height={51} + id="_VBC_bar_14" onBlur={[Function]} onFocus={[Function]} onMouseLeave={[Function]} @@ -152,6 +153,7 @@ exports[`VerticalBarChart - mouse events Should render callout correctly on mous data-is-focusable={true} fill="#002050" height={255} + id="_VBC_bar_15" onBlur={[Function]} onFocus={[Function]} onMouseLeave={[Function]} @@ -190,6 +192,7 @@ exports[`VerticalBarChart - mouse events Should render callout correctly on mous data-is-focusable={true} fill="#00188f" height={153} + id="_VBC_bar_16" onBlur={[Function]} onFocus={[Function]} onMouseLeave={[Function]} @@ -821,6 +824,7 @@ exports[`VerticalBarChart - mouse events Should render customized callout on mou data-is-focusable={true} fill="#0078d4" height={51} + id="_VBC_bar_8" onBlur={[Function]} onFocus={[Function]} onMouseLeave={[Function]} @@ -859,6 +863,7 @@ exports[`VerticalBarChart - mouse events Should render customized callout on mou data-is-focusable={true} fill="#002050" height={255} + id="_VBC_bar_9" onBlur={[Function]} onFocus={[Function]} onMouseLeave={[Function]} @@ -897,6 +902,7 @@ exports[`VerticalBarChart - mouse events Should render customized callout on mou data-is-focusable={true} fill="#00188f" height={153} + id="_VBC_bar_10" onBlur={[Function]} onFocus={[Function]} onMouseLeave={[Function]} diff --git a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap index 6345afd77bc04..fa893cb669fbe 100644 --- a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap +++ b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap @@ -1,17 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Vertical bar chart rendering Should re-render the vertical bar chart with data 1`] = ` -
- -`; - -exports[`Vertical bar chart rendering Should re-render the vertical bar chart with data 2`] = ` +exports[`Screen resolution Should remain unchanged on zoom in 1`] = `