/*
Copyright (C) 2021 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import React from "react";
import { ThemePalette } from "@src/components/Theme";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import TestUtils from "@tests/TestUtils";
import DashboardBarChart from "./";
const DATA: DashboardBarChart["props"]["data"] = [
{
label: "label 1",
values: [10, 15],
data: "data 1",
},
{
label: "label 2",
values: [20, 25],
data: "data 2",
},
];
describe("DashboardBarChart", () => {
it("renders all data correctly", () => {
render();
// Y ticks
const yTickEl = TestUtils.selectAll("DashboardBarChart__YTick");
expect(yTickEl.length).toBe(3);
expect(yTickEl[0].textContent).toBe("0");
expect(yTickEl[1].textContent).toBe("20");
expect(yTickEl[2].textContent).toBe("40");
// Bars
const barsEl = TestUtils.selectAll("DashboardBarChart__Bar-");
expect(barsEl.length).toBe(DATA.length);
expect(barsEl[0].textContent).toBe("label 1");
expect(barsEl[1].textContent).toBe("label 2");
});
it.each`
barIndex | stackedBarIndex | expectedHeight | expectedColor
${0} | ${0} | ${(DATA[0].values[1] / 45) * 100} | ${ThemePalette.alert}
${0} | ${1} | ${(DATA[0].values[0] / 45) * 100} | ${ThemePalette.primary}
${1} | ${0} | ${(DATA[1].values[1] / 45) * 100} | ${ThemePalette.alert}
${1} | ${1} | ${(DATA[1].values[0] / 45) * 100} | ${ThemePalette.primary}
`(
"renders bar index $barIndex, stacked bar index $stackedBarIndex with height $expectedHeight and color $expectedColor",
({ barIndex, stackedBarIndex, expectedHeight, expectedColor }) => {
render(
,
);
const stackedBarEl = TestUtils.selectAll(
"DashboardBarChart__StackedBar-",
TestUtils.selectAll("DashboardBarChart__Bar-")[barIndex],
)[stackedBarIndex];
const style = window.getComputedStyle(stackedBarEl);
expect(parseFloat(style.height)).toBeCloseTo(expectedHeight);
expect(TestUtils.rgbToHex(style.background)).toBe(expectedColor);
},
);
it.each`
barIndex | stackedBarIndex | expectedData
${0} | ${0} | ${DATA[0]}
${0} | ${1} | ${DATA[0]}
${1} | ${0} | ${DATA[1]}
${1} | ${1} | ${DATA[1]}
`(
"fires mouse position with correct data on bar mouse enter, bar index $barIndex, stacked bar index $stackedBarIndex",
({ barIndex, stackedBarIndex, expectedData }) => {
const onBarMouseEnter = jest.fn();
render(
,
);
userEvent.hover(
TestUtils.selectAll(
"DashboardBarChart__StackedBar-",
TestUtils.selectAll("DashboardBarChart__Bar-")[barIndex],
)[stackedBarIndex],
);
expect(onBarMouseEnter).toHaveBeenCalledWith(
{ x: 48, y: 65 },
expectedData,
);
},
);
it("does not render bars with height of 0%", () => {
const ZERO_DATA = [
{
label: "label 1",
values: [0, 0],
},
{
label: "label 2",
values: [20, 25],
},
];
render();
const firstStackedBars = TestUtils.selectAll(
"DashboardBarChart__StackedBar-",
TestUtils.selectAll("DashboardBarChart__Bar-")[0],
);
const secondStackedBars = TestUtils.selectAll(
"DashboardBarChart__StackedBar-",
TestUtils.selectAll("DashboardBarChart__Bar-")[1],
);
expect(firstStackedBars.length).toBe(0);
expect(secondStackedBars.length).toBe(ZERO_DATA[1].values.length);
});
it("renders half the bars if available width is less than 30 times the number of items", () => {
const originalInnerWidth = window.innerWidth;
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 29 * DATA.length,
});
render();
const bars = TestUtils.selectAll("DashboardBarChart__Bar-");
expect(bars.length).toBe(DATA.length / 2);
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: originalInnerWidth,
});
});
it("fires the onBarMouseLeave callback on bar mouse leave", () => {
const onBarMouseLeave = jest.fn();
render(
,
);
const bar = TestUtils.selectAll("DashboardBarChart__StackedBar-")[0];
userEvent.unhover(bar);
expect(onBarMouseLeave).toHaveBeenCalled();
});
it("calculates the correct position for bars", () => {
const onBarMouseEnter = jest.fn();
render(
,
);
const firstBar = TestUtils.selectAll("DashboardBarChart__StackedBar-")[0];
userEvent.hover(firstBar);
expect(onBarMouseEnter).toHaveBeenCalledWith({ x: 48, y: 65 }, DATA[0]);
});
it("recalculates ticks when new data is received", () => {
const { rerender } = render(
,
);
const bars = TestUtils.selectAll("DashboardBarChart__Bar-");
expect(bars.length).toBe(DATA.length);
expect(bars[0].textContent).toBe("label 1");
expect(bars[1].textContent).toBe("label 2");
const NEW_DATA = [
{
label: "label 3",
values: [10, 30],
data: "data 3",
},
{
label: "label 4",
values: [5, 20],
data: "data 4",
},
];
// Mocking the offset width is necessary due to how the rendered
// output behaves within the @testing-library/react environment
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
configurable: true,
value: 500,
});
rerender();
const newBars = TestUtils.selectAll("DashboardBarChart__Bar-");
expect(newBars.length).toBe(NEW_DATA.length);
expect(newBars[0].textContent).toBe("label 3");
expect(newBars[1].textContent).toBe("label 4");
});
it("does not fire any function when onBarMouseEnter is not provided", () => {
render();
const firstStackedBar = TestUtils.selectAll(
"DashboardBarChart__StackedBar-",
)[0];
const spy = jest.spyOn(console, "error").mockImplementation(() => {});
// Hover over the stacked bar
userEvent.hover(firstStackedBar);
// Assert that there were no console errors
expect(spy).not.toHaveBeenCalled();
spy.mockRestore();
});
});