DashboardBarChart.spec.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. Copyright (C) 2021 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import React from "react";
  15. import { ThemePalette } from "@src/components/Theme";
  16. import { render } from "@testing-library/react";
  17. import userEvent from "@testing-library/user-event";
  18. import TestUtils from "@tests/TestUtils";
  19. import DashboardBarChart from "./";
  20. const DATA: DashboardBarChart["props"]["data"] = [
  21. {
  22. label: "label 1",
  23. values: [10, 15],
  24. data: "data 1",
  25. },
  26. {
  27. label: "label 2",
  28. values: [20, 25],
  29. data: "data 2",
  30. },
  31. ];
  32. describe("DashboardBarChart", () => {
  33. it("renders all data correctly", () => {
  34. render(<DashboardBarChart data={DATA} yNumTicks={3} />);
  35. // Y ticks
  36. const yTickEl = TestUtils.selectAll("DashboardBarChart__YTick");
  37. expect(yTickEl.length).toBe(3);
  38. expect(yTickEl[0].textContent).toBe("0");
  39. expect(yTickEl[1].textContent).toBe("20");
  40. expect(yTickEl[2].textContent).toBe("40");
  41. // Bars
  42. const barsEl = TestUtils.selectAll("DashboardBarChart__Bar-");
  43. expect(barsEl.length).toBe(DATA.length);
  44. expect(barsEl[0].textContent).toBe("label 1");
  45. expect(barsEl[1].textContent).toBe("label 2");
  46. });
  47. it.each`
  48. barIndex | stackedBarIndex | expectedHeight | expectedColor
  49. ${0} | ${0} | ${(DATA[0].values[1] / 45) * 100} | ${ThemePalette.alert}
  50. ${0} | ${1} | ${(DATA[0].values[0] / 45) * 100} | ${ThemePalette.primary}
  51. ${1} | ${0} | ${(DATA[1].values[1] / 45) * 100} | ${ThemePalette.alert}
  52. ${1} | ${1} | ${(DATA[1].values[0] / 45) * 100} | ${ThemePalette.primary}
  53. `(
  54. "renders bar index $barIndex, stacked bar index $stackedBarIndex with height $expectedHeight and color $expectedColor",
  55. ({ barIndex, stackedBarIndex, expectedHeight, expectedColor }) => {
  56. render(
  57. <DashboardBarChart
  58. data={DATA}
  59. yNumTicks={3}
  60. colors={[ThemePalette.alert, ThemePalette.primary]}
  61. />,
  62. );
  63. const stackedBarEl = TestUtils.selectAll(
  64. "DashboardBarChart__StackedBar-",
  65. TestUtils.selectAll("DashboardBarChart__Bar-")[barIndex],
  66. )[stackedBarIndex];
  67. const style = window.getComputedStyle(stackedBarEl);
  68. expect(parseFloat(style.height)).toBeCloseTo(expectedHeight);
  69. expect(TestUtils.rgbToHex(style.background)).toBe(expectedColor);
  70. },
  71. );
  72. it.each`
  73. barIndex | stackedBarIndex | expectedData
  74. ${0} | ${0} | ${DATA[0]}
  75. ${0} | ${1} | ${DATA[0]}
  76. ${1} | ${0} | ${DATA[1]}
  77. ${1} | ${1} | ${DATA[1]}
  78. `(
  79. "fires mouse position with correct data on bar mouse enter, bar index $barIndex, stacked bar index $stackedBarIndex",
  80. ({ barIndex, stackedBarIndex, expectedData }) => {
  81. const onBarMouseEnter = jest.fn();
  82. render(
  83. <DashboardBarChart
  84. data={DATA}
  85. yNumTicks={3}
  86. onBarMouseEnter={onBarMouseEnter}
  87. />,
  88. );
  89. userEvent.hover(
  90. TestUtils.selectAll(
  91. "DashboardBarChart__StackedBar-",
  92. TestUtils.selectAll("DashboardBarChart__Bar-")[barIndex],
  93. )[stackedBarIndex],
  94. );
  95. expect(onBarMouseEnter).toHaveBeenCalledWith(
  96. { x: 48, y: 65 },
  97. expectedData,
  98. );
  99. },
  100. );
  101. it("does not render bars with height of 0%", () => {
  102. const ZERO_DATA = [
  103. {
  104. label: "label 1",
  105. values: [0, 0],
  106. },
  107. {
  108. label: "label 2",
  109. values: [20, 25],
  110. },
  111. ];
  112. render(<DashboardBarChart data={ZERO_DATA} yNumTicks={3} />);
  113. const firstStackedBars = TestUtils.selectAll(
  114. "DashboardBarChart__StackedBar-",
  115. TestUtils.selectAll("DashboardBarChart__Bar-")[0],
  116. );
  117. const secondStackedBars = TestUtils.selectAll(
  118. "DashboardBarChart__StackedBar-",
  119. TestUtils.selectAll("DashboardBarChart__Bar-")[1],
  120. );
  121. expect(firstStackedBars.length).toBe(0);
  122. expect(secondStackedBars.length).toBe(ZERO_DATA[1].values.length);
  123. });
  124. it("renders half the bars if available width is less than 30 times the number of items", () => {
  125. const originalInnerWidth = window.innerWidth;
  126. Object.defineProperty(window, "innerWidth", {
  127. writable: true,
  128. configurable: true,
  129. value: 29 * DATA.length,
  130. });
  131. render(<DashboardBarChart data={DATA} yNumTicks={3} />);
  132. const bars = TestUtils.selectAll("DashboardBarChart__Bar-");
  133. expect(bars.length).toBe(DATA.length / 2);
  134. Object.defineProperty(window, "innerWidth", {
  135. writable: true,
  136. configurable: true,
  137. value: originalInnerWidth,
  138. });
  139. });
  140. it("fires the onBarMouseLeave callback on bar mouse leave", () => {
  141. const onBarMouseLeave = jest.fn();
  142. render(
  143. <DashboardBarChart
  144. data={DATA}
  145. yNumTicks={3}
  146. onBarMouseLeave={onBarMouseLeave}
  147. />,
  148. );
  149. const bar = TestUtils.selectAll("DashboardBarChart__StackedBar-")[0];
  150. userEvent.unhover(bar);
  151. expect(onBarMouseLeave).toHaveBeenCalled();
  152. });
  153. it("calculates the correct position for bars", () => {
  154. const onBarMouseEnter = jest.fn();
  155. render(
  156. <DashboardBarChart
  157. data={DATA}
  158. yNumTicks={3}
  159. onBarMouseEnter={onBarMouseEnter}
  160. />,
  161. );
  162. const firstBar = TestUtils.selectAll("DashboardBarChart__StackedBar-")[0];
  163. userEvent.hover(firstBar);
  164. expect(onBarMouseEnter).toHaveBeenCalledWith({ x: 48, y: 65 }, DATA[0]);
  165. });
  166. it("recalculates ticks when new data is received", () => {
  167. const { rerender } = render(
  168. <DashboardBarChart data={DATA} yNumTicks={3} />,
  169. );
  170. const bars = TestUtils.selectAll("DashboardBarChart__Bar-");
  171. expect(bars.length).toBe(DATA.length);
  172. expect(bars[0].textContent).toBe("label 1");
  173. expect(bars[1].textContent).toBe("label 2");
  174. const NEW_DATA = [
  175. {
  176. label: "label 3",
  177. values: [10, 30],
  178. data: "data 3",
  179. },
  180. {
  181. label: "label 4",
  182. values: [5, 20],
  183. data: "data 4",
  184. },
  185. ];
  186. // Mocking the offset width is necessary due to how the rendered
  187. // output behaves within the @testing-library/react environment
  188. Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
  189. configurable: true,
  190. value: 500,
  191. });
  192. rerender(<DashboardBarChart data={NEW_DATA} yNumTicks={3} />);
  193. const newBars = TestUtils.selectAll("DashboardBarChart__Bar-");
  194. expect(newBars.length).toBe(NEW_DATA.length);
  195. expect(newBars[0].textContent).toBe("label 3");
  196. expect(newBars[1].textContent).toBe("label 4");
  197. });
  198. it("does not fire any function when onBarMouseEnter is not provided", () => {
  199. render(<DashboardBarChart data={DATA} yNumTicks={3} />);
  200. const firstStackedBar = TestUtils.selectAll(
  201. "DashboardBarChart__StackedBar-",
  202. )[0];
  203. const spy = jest.spyOn(console, "error").mockImplementation(() => {});
  204. // Hover over the stacked bar
  205. userEvent.hover(firstStackedBar);
  206. // Assert that there were no console errors
  207. expect(spy).not.toHaveBeenCalled();
  208. spy.mockRestore();
  209. });
  210. });