Metric.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React from "react";
  14. import { render, waitFor } from "@testing-library/react";
  15. import "@testing-library/jest-dom";
  16. import { ThemeProvider } from "@mui/material/styles";
  17. import { createTheme } from "@mui/material/styles";
  18. import Metric from "./Metric";
  19. const template = {
  20. layout: {
  21. colorscale: {
  22. diverging: [
  23. [0, "#8e0152"],
  24. [0.1, "#c51b7d"],
  25. [0.2, "#de77ae"],
  26. [0.3, "#f1b6da"],
  27. [0.4, "#fde0ef"],
  28. [0.5, "#f7f7f7"],
  29. [0.6, "#e6f5d0"],
  30. [0.7, "#b8e186"],
  31. [0.8, "#7fbc41"],
  32. [0.9, "#4d9221"],
  33. [1, "#276419"],
  34. ],
  35. sequential: [
  36. [0, "#0d0887"],
  37. [0.1111111111111111, "#46039f"],
  38. [0.2222222222222222, "#7201a8"],
  39. [0.3333333333333333, "#9c179e"],
  40. [0.4444444444444444, "#bd3786"],
  41. [0.5555555555555556, "#d8576b"],
  42. [0.6666666666666666, "#ed7953"],
  43. [0.7777777777777778, "#fb9f3a"],
  44. [0.8888888888888888, "#fdca26"],
  45. [1, "#f0f921"],
  46. ],
  47. sequentialminus: [
  48. [0, "#0d0887"],
  49. [0.1111111111111111, "#46039f"],
  50. [0.2222222222222222, "#7201a8"],
  51. [0.3333333333333333, "#9c179e"],
  52. [0.4444444444444444, "#bd3786"],
  53. [0.5555555555555556, "#d8576b"],
  54. [0.6666666666666666, "#ed7953"],
  55. [0.7777777777777778, "#fb9f3a"],
  56. [0.8888888888888888, "#fdca26"],
  57. [1, "#f0f921"],
  58. ],
  59. },
  60. paper_bgcolor: "rgb(255,0,0)",
  61. },
  62. };
  63. const colorMap = {
  64. 20: "red",
  65. 40: null,
  66. 60: "blue",
  67. 80: null,
  68. };
  69. const darkTemplate = {
  70. layout: {
  71. colorscale: {
  72. diverging: [
  73. [0, "#8e0152"],
  74. [0.1, "#c51b7d"],
  75. [0.2, "#de77ae"],
  76. [0.3, "#f1b6da"],
  77. [0.4, "#fde0ef"],
  78. [0.5, "#f7f7f7"],
  79. [0.6, "#e6f5d0"],
  80. [0.7, "#b8e186"],
  81. [0.8, "#7fbc41"],
  82. [0.9, "#4d9221"],
  83. [1, "#276419"],
  84. ],
  85. sequential: [
  86. [0, "#0d0887"],
  87. [0.1111111111111111, "#46039f"],
  88. [0.2222222222222222, "#7201a8"],
  89. [0.3333333333333333, "#9c179e"],
  90. [0.4444444444444444, "#bd3786"],
  91. [0.5555555555555556, "#d8576b"],
  92. [0.6666666666666666, "#ed7953"],
  93. [0.7777777777777778, "#fb9f3a"],
  94. [0.8888888888888888, "#fdca26"],
  95. [1, "#f0f921"],
  96. ],
  97. sequentialminus: [
  98. [0, "#0d0887"],
  99. [0.1111111111111111, "#46039f"],
  100. [0.2222222222222222, "#7201a8"],
  101. [0.3333333333333333, "#9c179e"],
  102. [0.4444444444444444, "#bd3786"],
  103. [0.5555555555555556, "#d8576b"],
  104. [0.6666666666666666, "#ed7953"],
  105. [0.7777777777777778, "#fb9f3a"],
  106. [0.8888888888888888, "#fdca26"],
  107. [1, "#f0f921"],
  108. ],
  109. },
  110. paper_bgcolor: "rgb(31,47,68)",
  111. },
  112. };
  113. const lightTemplate = {
  114. layout: {
  115. colorscale: {
  116. diverging: [
  117. [0, "#053061"],
  118. [0.1, "#2166ac"],
  119. [0.2, "#4393c3"],
  120. [0.3, "#92c5de"],
  121. [0.4, "#d1e5f0"],
  122. [0.5, "#f7f7f7"],
  123. [0.6, "#fddbc7"],
  124. [0.7, "#f4a582"],
  125. [0.8, "#d6604d"],
  126. [0.9, "#b2182b"],
  127. [1, "#67001f"],
  128. ],
  129. sequential: [
  130. [0, "#f7fcf5"],
  131. [0.1111111111111111, "#e5f5e0"],
  132. [0.2222222222222222, "#c7e9c0"],
  133. [0.3333333333333333, "#a1d99b"],
  134. [0.4444444444444444, "#74c476"],
  135. [0.5555555555555556, "#41ab5d"],
  136. [0.6666666666666666, "#238b45"],
  137. [0.7777777777777778, "#006d2c"],
  138. [0.8888888888888888, "#00441b"],
  139. [1, "#000000"],
  140. ],
  141. sequentialminus: [
  142. [0, "#f7fcf5"],
  143. [0.1111111111111111, "#e5f5e0"],
  144. [0.2222222222222222, "#c7e9c0"],
  145. [0.3333333333333333, "#a1d99b"],
  146. [0.4444444444444444, "#74c476"],
  147. [0.5555555555555556, "#41ab5d"],
  148. [0.6666666666666666, "#238b45"],
  149. [0.7777777777777778, "#006d2c"],
  150. [0.8888888888888888, "#00441b"],
  151. [1, "#000000"],
  152. ],
  153. },
  154. paper_bgcolor: "rgb(255,255,255)",
  155. },
  156. };
  157. describe("Metric Component", () => {
  158. it("renders", async () => {
  159. const { container } = render(<Metric className="test" />);
  160. const elt = container.querySelector(".test");
  161. expect(elt?.tagName).toBe("DIV");
  162. });
  163. it("displays the right info for class", async () => {
  164. const { container } = render(<Metric className="test" />);
  165. const elt = container.querySelector(".test");
  166. expect(elt).toHaveClass("test");
  167. });
  168. it("sets the title when provided", async () => {
  169. const title = "Test Title";
  170. const { container } = render(<Metric title={title} />);
  171. await waitFor(() => {
  172. const titleElement = container.querySelector(".gtitle");
  173. if (!titleElement) {
  174. throw new Error("Title element not found");
  175. }
  176. expect(titleElement.textContent).toContain(title);
  177. });
  178. });
  179. it("logs an error when template prop is a malformed JSON string", () => {
  180. const consoleSpy = jest.spyOn(console, "info");
  181. const malformedJson = "{ key: 'value'";
  182. render(<Metric template={malformedJson} />);
  183. expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
  184. consoleSpy.mockRestore();
  185. });
  186. it("logs an error when colorMap prop is a malformed JSON string", () => {
  187. const consoleSpy = jest.spyOn(console, "info");
  188. const malformedJson = "{ key: 'value'"; // missing closing brace
  189. render(<Metric colorMap={malformedJson} />);
  190. expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error parsing color_map value (metric)"));
  191. consoleSpy.mockRestore();
  192. });
  193. it("sets the template when provided", async () => {
  194. const { container } = render(<Metric template={JSON.stringify(template)} />);
  195. await waitFor(() => {
  196. const elt = container.querySelector(".main-svg");
  197. expect(elt).toHaveStyle({
  198. backgroundColor: "rgb(255, 0, 0)",
  199. });
  200. });
  201. });
  202. it("processes colorMap prop correctly", async () => {
  203. const { container } = render(<Metric colorMap={JSON.stringify(colorMap)} />);
  204. await waitFor(() => {
  205. const elements = container.querySelectorAll(".bg-arc");
  206. const redElt = Array.from(elements[1].children);
  207. const redEltHasRedFill = redElt.some((elt) => (elt as HTMLElement).style.fill === "rgb(255, 0, 0)");
  208. expect(redEltHasRedFill).toBeTruthy();
  209. const blueElt = Array.from(elements[2].children);
  210. const blueEltHasBlueFill = blueElt.some((elt) => (elt as HTMLElement).style.fill === "rgb(0, 0, 255)");
  211. expect(blueEltHasBlueFill).toBeTruthy();
  212. });
  213. });
  214. it("processes delta prop correctly when delta is defined", async () => {
  215. const { container } = render(<Metric delta={10} />);
  216. await waitFor(() => {
  217. const elt = container.querySelector(".delta");
  218. if (elt) {
  219. expect(elt.textContent).toContain("10");
  220. } else {
  221. throw new Error("Element with class .delta not found");
  222. }
  223. });
  224. });
  225. it("applies style correctly when deltaColor is set", async () => {
  226. const { container } = render(<Metric delta={10} deltaColor="#FF4136" />);
  227. await waitFor(() => {
  228. const elt = container.querySelector(".delta");
  229. expect(elt).toHaveStyle({
  230. fill: "rgb(255, 65, 54)",
  231. });
  232. });
  233. });
  234. it("applies style correctly when deltaColor is set invert", async () => {
  235. const { container } = render(<Metric delta={10} deltaColor="invert" />);
  236. await waitFor(() => {
  237. const elt = container.querySelector(".delta");
  238. expect(elt).toHaveStyle({
  239. fill: "rgb(255, 65, 54)",
  240. });
  241. });
  242. });
  243. it("processes type and threshold props correctly when type is linear", async () => {
  244. const { container } = render(<Metric type="linear" threshold={50} />);
  245. await waitFor(() => {
  246. const elt = container.querySelector(".bullet");
  247. expect(elt).toBeInTheDocument();
  248. });
  249. });
  250. it("processes type and threshold props correctly when type is not linear", async () => {
  251. const { container } = render(<Metric type="angular" threshold={50} />);
  252. await waitFor(() => {
  253. const elt = container.querySelector(".angular");
  254. expect(elt).toBeInTheDocument();
  255. });
  256. });
  257. it("applies style correctly when height is undefined", async () => {
  258. const { container } = render(<Metric />);
  259. await waitFor(() => {
  260. const elt = container.querySelector(".js-plotly-plot");
  261. expect(elt).toHaveStyle({
  262. width: "100%",
  263. position: "relative",
  264. display: "inline-block",
  265. });
  266. });
  267. });
  268. it("processes type prop correctly when type is none (string)", async () => {
  269. const { container } = render(<Metric type="none" />);
  270. await waitFor(() => {
  271. const angularElm = container.querySelector(".angular");
  272. const angularAxis = container.querySelector(".angularaxis");
  273. expect(angularElm).not.toBeInTheDocument();
  274. expect(angularAxis).not.toBeInTheDocument();
  275. });
  276. });
  277. it("processes type prop correctly when type is None", async () => {
  278. const { container } = render(<Metric type="None" />);
  279. await waitFor(() => {
  280. const angularElm = container.querySelector(".angular");
  281. const angularAxis = container.querySelector(".angularaxis");
  282. expect(angularElm).not.toBeInTheDocument();
  283. expect(angularAxis).not.toBeInTheDocument();
  284. });
  285. });
  286. it("processes template_Dark_ prop correctly when theme is dark", async () => {
  287. const darkTheme = createTheme({
  288. palette: {
  289. mode: "dark",
  290. },
  291. });
  292. const { container } = render(
  293. <ThemeProvider theme={darkTheme}>
  294. <Metric template_Dark_={JSON.stringify(darkTemplate)} />
  295. </ThemeProvider>
  296. );
  297. await waitFor(() => {
  298. const elt = container.querySelector(".main-svg");
  299. expect(elt).toHaveStyle({
  300. backgroundColor: "rgb(31, 47, 68)",
  301. });
  302. });
  303. });
  304. it("processes template_Light_ prop correctly when theme is not dark", async () => {
  305. const lightTheme = createTheme({
  306. palette: {
  307. mode: "light",
  308. },
  309. });
  310. const { container } = render(
  311. <ThemeProvider theme={lightTheme}>
  312. <Metric template_Light_={JSON.stringify(lightTemplate)} />
  313. </ThemeProvider>
  314. );
  315. await waitFor(() => {
  316. const elt = container.querySelector(".main-svg");
  317. expect(elt).toHaveStyle({
  318. backgroundColor: "rgb(255, 255, 255)",
  319. });
  320. });
  321. });
  322. it.skip("logs an error when template_Dark_ prop is not a valid JSON string", () => {
  323. const consoleSpy = jest.spyOn(console, "info");
  324. render(<Metric template_Dark_="not a valid JSON string"/>);
  325. expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
  326. consoleSpy.mockRestore();
  327. }); // TODO: Not working at the moment, need to fix
  328. it("logs an error when template_Light_ prop is not a valid JSON string", () => {
  329. const consoleSpy = jest.spyOn(console, "info");
  330. render(<Metric template_Light_="not a valid JSON string" />);
  331. expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
  332. consoleSpy.mockRestore();
  333. });
  334. });