import { queryAllByTestId, queryByTestId } from "@testing-library/dom";
import { cssToObject } from "@uppercod/css-to-object";
import {
  getLongestLang,
  degreesToRadians,
  radiansToDegrees,
  polarToCartesian,
  cartesianToPolar,
  getCircleLength,
  calculateCompactLayoutHeight,
  calculateNormalLayoutHeight,
  calculateDonutLayoutHeight,
  calculateDonutVerticalLayoutHeight,
  calculatePieLayoutHeight,
  donutCenterTranslation,
  trimTopLanguages,
  renderTopLanguages,
  MIN_CARD_WIDTH,
  getDefaultLanguagesCountByLayout,
} from "../src/cards/top-languages.js";
import { expect, it, describe } from "@jest/globals";

// adds special assertions like toHaveTextContent
import "@testing-library/jest-dom";

import { themes } from "../themes/index.js";

const langs = {
  HTML: {
    color: "#0f0",
    name: "HTML",
    size: 200,
  },
  javascript: {
    color: "#0ff",
    name: "javascript",
    size: 200,
  },
  css: {
    color: "#ff0",
    name: "css",
    size: 100,
  },
};

/**
 * Retrieve number array from SVG path definition string.
 *
 * @param {string} d SVG path definition string.
 * @returns {number[]} Resulting numbers array.
 */
const getNumbersFromSvgPathDefinitionAttribute = (d) => {
  return d
    .split(" ")
    .filter((x) => !isNaN(x))
    .map((x) => parseFloat(x));
};

/**
 * Retrieve the language percentage from the donut chart SVG.
 *
 * @param {string} d The SVG path element.
 * @param {number} centerX The center X coordinate of the donut chart.
 * @param {number} centerY The center Y coordinate of the donut chart.
 * @returns {number} The percentage of the language.
 */
const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => {
  const dTmp = getNumbersFromSvgPathDefinitionAttribute(d);
  const endAngle =
    cartesianToPolar(centerX, centerY, dTmp[0], dTmp[1]).angleInDegrees + 90;
  let startAngle =
    cartesianToPolar(centerX, centerY, dTmp[7], dTmp[8]).angleInDegrees + 90;
  if (startAngle > endAngle) {
    startAngle -= 360;
  }
  return (endAngle - startAngle) / 3.6;
};

/**
 * Calculate language percentage for donut vertical chart SVG.
 *
 * @param {number} partLength Length of current chart part..
 * @param {number} totalCircleLength Total length of circle.
 * @returns {number} Chart part percentage.
 */
const langPercentFromDonutVerticalLayoutSvg = (
  partLength,
  totalCircleLength,
) => {
  return (partLength / totalCircleLength) * 100;
};

/**
 * Retrieve the language percentage from the pie chart SVG.
 *
 * @param {string} d The SVG path element.
 * @param {number} centerX The center X coordinate of the pie chart.
 * @param {number} centerY The center Y coordinate of the pie chart.
 * @returns {number} The percentage of the language.
 */
const langPercentFromPieLayoutSvg = (d, centerX, centerY) => {
  const dTmp = getNumbersFromSvgPathDefinitionAttribute(d);
  const startAngle = cartesianToPolar(
    centerX,
    centerY,
    dTmp[2],
    dTmp[3],
  ).angleInDegrees;
  let endAngle = cartesianToPolar(
    centerX,
    centerY,
    dTmp[9],
    dTmp[10],
  ).angleInDegrees;
  return ((endAngle - startAngle) / 360) * 100;
};

describe("Test renderTopLanguages helper functions", () => {
  it("getLongestLang", () => {
    const langArray = Object.values(langs);
    expect(getLongestLang(langArray)).toBe(langs.javascript);
  });

  it("degreesToRadians", () => {
    expect(degreesToRadians(0)).toBe(0);
    expect(degreesToRadians(90)).toBe(Math.PI / 2);
    expect(degreesToRadians(180)).toBe(Math.PI);
    expect(degreesToRadians(270)).toBe((3 * Math.PI) / 2);
    expect(degreesToRadians(360)).toBe(2 * Math.PI);
  });

  it("radiansToDegrees", () => {
    expect(radiansToDegrees(0)).toBe(0);
    expect(radiansToDegrees(Math.PI / 2)).toBe(90);
    expect(radiansToDegrees(Math.PI)).toBe(180);
    expect(radiansToDegrees((3 * Math.PI) / 2)).toBe(270);
    expect(radiansToDegrees(2 * Math.PI)).toBe(360);
  });

  it("polarToCartesian", () => {
    expect(polarToCartesian(100, 100, 60, 0)).toStrictEqual({ x: 160, y: 100 });
    expect(polarToCartesian(100, 100, 60, 45)).toStrictEqual({
      x: 142.42640687119285,
      y: 142.42640687119285,
    });
    expect(polarToCartesian(100, 100, 60, 90)).toStrictEqual({
      x: 100,
      y: 160,
    });
    expect(polarToCartesian(100, 100, 60, 135)).toStrictEqual({
      x: 57.573593128807154,
      y: 142.42640687119285,
    });
    expect(polarToCartesian(100, 100, 60, 180)).toStrictEqual({
      x: 40,
      y: 100.00000000000001,
    });
    expect(polarToCartesian(100, 100, 60, 225)).toStrictEqual({
      x: 57.57359312880714,
      y: 57.573593128807154,
    });
    expect(polarToCartesian(100, 100, 60, 270)).toStrictEqual({
      x: 99.99999999999999,
      y: 40,
    });
    expect(polarToCartesian(100, 100, 60, 315)).toStrictEqual({
      x: 142.42640687119285,
      y: 57.57359312880714,
    });
    expect(polarToCartesian(100, 100, 60, 360)).toStrictEqual({
      x: 160,
      y: 99.99999999999999,
    });
  });

  it("cartesianToPolar", () => {
    expect(cartesianToPolar(100, 100, 160, 100)).toStrictEqual({
      radius: 60,
      angleInDegrees: 0,
    });
    expect(
      cartesianToPolar(100, 100, 142.42640687119285, 142.42640687119285),
    ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 45 });
    expect(cartesianToPolar(100, 100, 100, 160)).toStrictEqual({
      radius: 60,
      angleInDegrees: 90,
    });
    expect(
      cartesianToPolar(100, 100, 57.573593128807154, 142.42640687119285),
    ).toStrictEqual({ radius: 60, angleInDegrees: 135 });
    expect(cartesianToPolar(100, 100, 40, 100.00000000000001)).toStrictEqual({
      radius: 60,
      angleInDegrees: 180,
    });
    expect(
      cartesianToPolar(100, 100, 57.57359312880714, 57.573593128807154),
    ).toStrictEqual({ radius: 60, angleInDegrees: 225 });
    expect(cartesianToPolar(100, 100, 99.99999999999999, 40)).toStrictEqual({
      radius: 60,
      angleInDegrees: 270,
    });
    expect(
      cartesianToPolar(100, 100, 142.42640687119285, 57.57359312880714),
    ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 315 });
    expect(cartesianToPolar(100, 100, 160, 99.99999999999999)).toStrictEqual({
      radius: 60,
      angleInDegrees: 360,
    });
  });

  it("calculateCompactLayoutHeight", () => {
    expect(calculateCompactLayoutHeight(0)).toBe(90);
    expect(calculateCompactLayoutHeight(1)).toBe(115);
    expect(calculateCompactLayoutHeight(2)).toBe(115);
    expect(calculateCompactLayoutHeight(3)).toBe(140);
    expect(calculateCompactLayoutHeight(4)).toBe(140);
    expect(calculateCompactLayoutHeight(5)).toBe(165);
    expect(calculateCompactLayoutHeight(6)).toBe(165);
    expect(calculateCompactLayoutHeight(7)).toBe(190);
    expect(calculateCompactLayoutHeight(8)).toBe(190);
    expect(calculateCompactLayoutHeight(9)).toBe(215);
    expect(calculateCompactLayoutHeight(10)).toBe(215);
  });

  it("calculateNormalLayoutHeight", () => {
    expect(calculateNormalLayoutHeight(0)).toBe(85);
    expect(calculateNormalLayoutHeight(1)).toBe(125);
    expect(calculateNormalLayoutHeight(2)).toBe(165);
    expect(calculateNormalLayoutHeight(3)).toBe(205);
    expect(calculateNormalLayoutHeight(4)).toBe(245);
    expect(calculateNormalLayoutHeight(5)).toBe(285);
    expect(calculateNormalLayoutHeight(6)).toBe(325);
    expect(calculateNormalLayoutHeight(7)).toBe(365);
    expect(calculateNormalLayoutHeight(8)).toBe(405);
    expect(calculateNormalLayoutHeight(9)).toBe(445);
    expect(calculateNormalLayoutHeight(10)).toBe(485);
  });

  it("calculateDonutLayoutHeight", () => {
    expect(calculateDonutLayoutHeight(0)).toBe(215);
    expect(calculateDonutLayoutHeight(1)).toBe(215);
    expect(calculateDonutLayoutHeight(2)).toBe(215);
    expect(calculateDonutLayoutHeight(3)).toBe(215);
    expect(calculateDonutLayoutHeight(4)).toBe(215);
    expect(calculateDonutLayoutHeight(5)).toBe(215);
    expect(calculateDonutLayoutHeight(6)).toBe(247);
    expect(calculateDonutLayoutHeight(7)).toBe(279);
    expect(calculateDonutLayoutHeight(8)).toBe(311);
    expect(calculateDonutLayoutHeight(9)).toBe(343);
    expect(calculateDonutLayoutHeight(10)).toBe(375);
  });

  it("calculateDonutVerticalLayoutHeight", () => {
    expect(calculateDonutVerticalLayoutHeight(0)).toBe(300);
    expect(calculateDonutVerticalLayoutHeight(1)).toBe(325);
    expect(calculateDonutVerticalLayoutHeight(2)).toBe(325);
    expect(calculateDonutVerticalLayoutHeight(3)).toBe(350);
    expect(calculateDonutVerticalLayoutHeight(4)).toBe(350);
    expect(calculateDonutVerticalLayoutHeight(5)).toBe(375);
    expect(calculateDonutVerticalLayoutHeight(6)).toBe(375);
    expect(calculateDonutVerticalLayoutHeight(7)).toBe(400);
    expect(calculateDonutVerticalLayoutHeight(8)).toBe(400);
    expect(calculateDonutVerticalLayoutHeight(9)).toBe(425);
    expect(calculateDonutVerticalLayoutHeight(10)).toBe(425);
  });

  it("calculatePieLayoutHeight", () => {
    expect(calculatePieLayoutHeight(0)).toBe(300);
    expect(calculatePieLayoutHeight(1)).toBe(325);
    expect(calculatePieLayoutHeight(2)).toBe(325);
    expect(calculatePieLayoutHeight(3)).toBe(350);
    expect(calculatePieLayoutHeight(4)).toBe(350);
    expect(calculatePieLayoutHeight(5)).toBe(375);
    expect(calculatePieLayoutHeight(6)).toBe(375);
    expect(calculatePieLayoutHeight(7)).toBe(400);
    expect(calculatePieLayoutHeight(8)).toBe(400);
    expect(calculatePieLayoutHeight(9)).toBe(425);
    expect(calculatePieLayoutHeight(10)).toBe(425);
  });

  it("donutCenterTranslation", () => {
    expect(donutCenterTranslation(0)).toBe(-45);
    expect(donutCenterTranslation(1)).toBe(-45);
    expect(donutCenterTranslation(2)).toBe(-45);
    expect(donutCenterTranslation(3)).toBe(-45);
    expect(donutCenterTranslation(4)).toBe(-45);
    expect(donutCenterTranslation(5)).toBe(-45);
    expect(donutCenterTranslation(6)).toBe(-29);
    expect(donutCenterTranslation(7)).toBe(-13);
    expect(donutCenterTranslation(8)).toBe(3);
    expect(donutCenterTranslation(9)).toBe(19);
    expect(donutCenterTranslation(10)).toBe(35);
  });

  it("getCircleLength", () => {
    expect(getCircleLength(20)).toBeCloseTo(125.663);
    expect(getCircleLength(30)).toBeCloseTo(188.495);
    expect(getCircleLength(40)).toBeCloseTo(251.327);
    expect(getCircleLength(50)).toBeCloseTo(314.159);
    expect(getCircleLength(60)).toBeCloseTo(376.991);
    expect(getCircleLength(70)).toBeCloseTo(439.822);
    expect(getCircleLength(80)).toBeCloseTo(502.654);
    expect(getCircleLength(90)).toBeCloseTo(565.486);
    expect(getCircleLength(100)).toBeCloseTo(628.318);
  });

  it("trimTopLanguages", () => {
    expect(trimTopLanguages([])).toStrictEqual({
      langs: [],
      totalLanguageSize: 0,
    });
    expect(trimTopLanguages([langs.javascript])).toStrictEqual({
      langs: [langs.javascript],
      totalLanguageSize: 200,
    });
    expect(trimTopLanguages([langs.javascript, langs.HTML], 5)).toStrictEqual({
      langs: [langs.javascript, langs.HTML],
      totalLanguageSize: 400,
    });
    expect(trimTopLanguages(langs, 5)).toStrictEqual({
      langs: Object.values(langs),
      totalLanguageSize: 500,
    });
    expect(trimTopLanguages(langs, 2)).toStrictEqual({
      langs: Object.values(langs).slice(0, 2),
      totalLanguageSize: 400,
    });
    expect(trimTopLanguages(langs, 5, ["javascript"])).toStrictEqual({
      langs: [langs.HTML, langs.css],
      totalLanguageSize: 300,
    });
  });

  it("getDefaultLanguagesCountByLayout", () => {
    expect(
      getDefaultLanguagesCountByLayout({ layout: "normal" }),
    ).toStrictEqual(5);
    expect(getDefaultLanguagesCountByLayout({})).toStrictEqual(5);
    expect(
      getDefaultLanguagesCountByLayout({ layout: "compact" }),
    ).toStrictEqual(6);
    expect(
      getDefaultLanguagesCountByLayout({ hide_progress: true }),
    ).toStrictEqual(6);
    expect(getDefaultLanguagesCountByLayout({ layout: "donut" })).toStrictEqual(
      5,
    );
    expect(
      getDefaultLanguagesCountByLayout({ layout: "donut-vertical" }),
    ).toStrictEqual(6);
    expect(getDefaultLanguagesCountByLayout({ layout: "pie" })).toStrictEqual(
      6,
    );
  });
});

describe("Test renderTopLanguages", () => {
  it("should render correctly", () => {
    document.body.innerHTML = renderTopLanguages(langs);

    expect(queryByTestId(document.body, "header")).toHaveTextContent(
      "Most Used Languages",
    );

    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML",
    );
    expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
      "javascript",
    );
    expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
      "css",
    );
    expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute(
      "width",
      "40%",
    );
    expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute(
      "width",
      "40%",
    );
    expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute(
      "width",
      "20%",
    );
  });

  it("should hide languages when hide is passed", () => {
    document.body.innerHTML = renderTopLanguages(langs, {
      hide: ["HTML"],
    });
    expect(queryAllByTestId(document.body, "lang-name")[0]).toBeInTheDocument(
      "javascript",
    );
    expect(queryAllByTestId(document.body, "lang-name")[1]).toBeInTheDocument(
      "css",
    );
    expect(queryAllByTestId(document.body, "lang-name")[2]).not.toBeDefined();

    // multiple languages passed
    document.body.innerHTML = renderTopLanguages(langs, {
      hide: ["HTML", "css"],
    });
    expect(queryAllByTestId(document.body, "lang-name")[0]).toBeInTheDocument(
      "javascript",
    );
    expect(queryAllByTestId(document.body, "lang-name")[1]).not.toBeDefined();
  });

  it("should resize the height correctly depending on langs", () => {
    document.body.innerHTML = renderTopLanguages(langs, {});
    expect(document.querySelector("svg")).toHaveAttribute("height", "205");

    document.body.innerHTML = renderTopLanguages(
      {
        ...langs,
        python: {
          color: "#ff0",
          name: "python",
          size: 100,
        },
      },
      {},
    );
    expect(document.querySelector("svg")).toHaveAttribute("height", "245");
  });

  it("should render with custom width set", () => {
    document.body.innerHTML = renderTopLanguages(langs, {});

    expect(document.querySelector("svg")).toHaveAttribute("width", "300");

    document.body.innerHTML = renderTopLanguages(langs, { card_width: 400 });
    expect(document.querySelector("svg")).toHaveAttribute("width", "400");
  });

  it("should render with min width", () => {
    document.body.innerHTML = renderTopLanguages(langs, { card_width: 190 });

    expect(document.querySelector("svg")).toHaveAttribute(
      "width",
      MIN_CARD_WIDTH.toString(),
    );

    document.body.innerHTML = renderTopLanguages(langs, { card_width: 100 });
    expect(document.querySelector("svg")).toHaveAttribute(
      "width",
      MIN_CARD_WIDTH.toString(),
    );
  });

  it("should render default colors properly", () => {
    document.body.innerHTML = renderTopLanguages(langs);

    const styleTag = document.querySelector("style");
    const stylesObject = cssToObject(styleTag.textContent);

    const headerStyles = stylesObject[":host"][".header "];
    const langNameStyles = stylesObject[":host"][".lang-name "];

    expect(headerStyles.fill.trim()).toBe("#2f80ed");
    expect(langNameStyles.fill.trim()).toBe("#434d58");
    expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
      "fill",
      "#fffefe",
    );
  });

  it("should render custom colors properly", () => {
    const customColors = {
      title_color: "5a0",
      icon_color: "1b998b",
      text_color: "9991",
      bg_color: "252525",
    };

    document.body.innerHTML = renderTopLanguages(langs, { ...customColors });

    const styleTag = document.querySelector("style");
    const stylesObject = cssToObject(styleTag.innerHTML);

    const headerStyles = stylesObject[":host"][".header "];
    const langNameStyles = stylesObject[":host"][".lang-name "];

    expect(headerStyles.fill.trim()).toBe(`#${customColors.title_color}`);
    expect(langNameStyles.fill.trim()).toBe(`#${customColors.text_color}`);
    expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
      "fill",
      "#252525",
    );
  });

  it("should render custom colors with themes", () => {
    document.body.innerHTML = renderTopLanguages(langs, {
      title_color: "5a0",
      theme: "radical",
    });

    const styleTag = document.querySelector("style");
    const stylesObject = cssToObject(styleTag.innerHTML);

    const headerStyles = stylesObject[":host"][".header "];
    const langNameStyles = stylesObject[":host"][".lang-name "];

    expect(headerStyles.fill.trim()).toBe("#5a0");
    expect(langNameStyles.fill.trim()).toBe(`#${themes.radical.text_color}`);
    expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
      "fill",
      `#${themes.radical.bg_color}`,
    );
  });

  it("should render with all the themes", () => {
    Object.keys(themes).forEach((name) => {
      document.body.innerHTML = renderTopLanguages(langs, {
        theme: name,
      });

      const styleTag = document.querySelector("style");
      const stylesObject = cssToObject(styleTag.innerHTML);

      const headerStyles = stylesObject[":host"][".header "];
      const langNameStyles = stylesObject[":host"][".lang-name "];

      expect(headerStyles.fill.trim()).toBe(`#${themes[name].title_color}`);
      expect(langNameStyles.fill.trim()).toBe(`#${themes[name].text_color}`);
      const backgroundElement = queryByTestId(document.body, "card-bg");
      const backgroundElementFill = backgroundElement.getAttribute("fill");
      expect([`#${themes[name].bg_color}`, "url(#gradient)"]).toContain(
        backgroundElementFill,
      );
    });
  });

  it("should render with layout compact", () => {
    document.body.innerHTML = renderTopLanguages(langs, { layout: "compact" });

    expect(queryByTestId(document.body, "header")).toHaveTextContent(
      "Most Used Languages",
    );

    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute(
      "width",
      "100",
    );

    expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
      "javascript 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute(
      "width",
      "100",
    );

    expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
      "css 20.00%",
    );
    expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute(
      "width",
      "50",
    );
  });

  it("should render with layout donut", () => {
    document.body.innerHTML = renderTopLanguages(langs, { layout: "donut" });

    expect(queryByTestId(document.body, "header")).toHaveTextContent(
      "Most Used Languages",
    );

    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
      "size",
      "40",
    );
    const d = getNumbersFromSvgPathDefinitionAttribute(
      queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"),
    );
    const center = { x: d[7], y: d[7] };
    const HTMLLangPercent = langPercentFromDonutLayoutSvg(
      queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"),
      center.x,
      center.y,
    );
    expect(HTMLLangPercent).toBeCloseTo(40);

    expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
      "javascript 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute(
      "size",
      "40",
    );
    const javascriptLangPercent = langPercentFromDonutLayoutSvg(
      queryAllByTestId(document.body, "lang-donut")[1].getAttribute("d"),
      center.x,
      center.y,
    );
    expect(javascriptLangPercent).toBeCloseTo(40);

    expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
      "css 20.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute(
      "size",
      "20",
    );
    const cssLangPercent = langPercentFromDonutLayoutSvg(
      queryAllByTestId(document.body, "lang-donut")[2].getAttribute("d"),
      center.x,
      center.y,
    );
    expect(cssLangPercent).toBeCloseTo(20);

    expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);

    // Should render full donut (circle) if one language is 100%.
    document.body.innerHTML = renderTopLanguages(
      { HTML: langs.HTML },
      { layout: "donut" },
    );
    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 100.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
      "size",
      "100",
    );
    expect(queryAllByTestId(document.body, "lang-donut")).toHaveLength(1);
    expect(queryAllByTestId(document.body, "lang-donut")[0].tagName).toBe(
      "circle",
    );
  });

  it("should render with layout donut vertical", () => {
    document.body.innerHTML = renderTopLanguages(langs, {
      layout: "donut-vertical",
    });

    expect(queryByTestId(document.body, "header")).toHaveTextContent(
      "Most Used Languages",
    );

    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
      "size",
      "40",
    );

    const totalCircleLength = queryAllByTestId(
      document.body,
      "lang-donut",
    )[0].getAttribute("stroke-dasharray");

    const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg(
      queryAllByTestId(document.body, "lang-donut")[1].getAttribute(
        "stroke-dashoffset",
      ) -
        queryAllByTestId(document.body, "lang-donut")[0].getAttribute(
          "stroke-dashoffset",
        ),
      totalCircleLength,
    );
    expect(HTMLLangPercent).toBeCloseTo(40);

    expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
      "javascript 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute(
      "size",
      "40",
    );
    const javascriptLangPercent = langPercentFromDonutVerticalLayoutSvg(
      queryAllByTestId(document.body, "lang-donut")[2].getAttribute(
        "stroke-dashoffset",
      ) -
        queryAllByTestId(document.body, "lang-donut")[1].getAttribute(
          "stroke-dashoffset",
        ),
      totalCircleLength,
    );
    expect(javascriptLangPercent).toBeCloseTo(40);

    expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
      "css 20.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute(
      "size",
      "20",
    );
    const cssLangPercent = langPercentFromDonutVerticalLayoutSvg(
      totalCircleLength -
        queryAllByTestId(document.body, "lang-donut")[2].getAttribute(
          "stroke-dashoffset",
        ),
      totalCircleLength,
    );
    expect(cssLangPercent).toBeCloseTo(20);

    expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);
  });

  it("should render with layout donut vertical full donut circle of one language is 100%", () => {
    document.body.innerHTML = renderTopLanguages(
      { HTML: langs.HTML },
      { layout: "donut-vertical" },
    );
    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 100.00%",
    );
    expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
      "size",
      "100",
    );
    const totalCircleLength = queryAllByTestId(
      document.body,
      "lang-donut",
    )[0].getAttribute("stroke-dasharray");

    const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg(
      totalCircleLength -
        queryAllByTestId(document.body, "lang-donut")[0].getAttribute(
          "stroke-dashoffset",
        ),
      totalCircleLength,
    );
    expect(HTMLLangPercent).toBeCloseTo(100);
  });

  it("should render with layout pie", () => {
    document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" });

    expect(queryByTestId(document.body, "header")).toHaveTextContent(
      "Most Used Languages",
    );

    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute(
      "size",
      "40",
    );

    const d = getNumbersFromSvgPathDefinitionAttribute(
      queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"),
    );
    const center = { x: d[0], y: d[1] };
    const HTMLLangPercent = langPercentFromPieLayoutSvg(
      queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"),
      center.x,
      center.y,
    );
    expect(HTMLLangPercent).toBeCloseTo(40);

    expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
      "javascript 40.00%",
    );
    expect(queryAllByTestId(document.body, "lang-pie")[1]).toHaveAttribute(
      "size",
      "40",
    );
    const javascriptLangPercent = langPercentFromPieLayoutSvg(
      queryAllByTestId(document.body, "lang-pie")[1].getAttribute("d"),
      center.x,
      center.y,
    );
    expect(javascriptLangPercent).toBeCloseTo(40);

    expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
      "css 20.00%",
    );
    expect(queryAllByTestId(document.body, "lang-pie")[2]).toHaveAttribute(
      "size",
      "20",
    );
    const cssLangPercent = langPercentFromPieLayoutSvg(
      queryAllByTestId(document.body, "lang-pie")[2].getAttribute("d"),
      center.x,
      center.y,
    );
    expect(cssLangPercent).toBeCloseTo(20);

    expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);

    // Should render full pie (circle) if one language is 100%.
    document.body.innerHTML = renderTopLanguages(
      { HTML: langs.HTML },
      { layout: "pie" },
    );
    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
      "HTML 100.00%",
    );
    expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute(
      "size",
      "100",
    );
    expect(queryAllByTestId(document.body, "lang-pie")).toHaveLength(1);
    expect(queryAllByTestId(document.body, "lang-pie")[0].tagName).toBe(
      "circle",
    );
  });

  it("should render a translated title", () => {
    document.body.innerHTML = renderTopLanguages(langs, { locale: "cn" });
    expect(document.getElementsByClassName("header")[0].textContent).toBe(
      "最常用的语言",
    );
  });

  it("should render without rounding", () => {
    document.body.innerHTML = renderTopLanguages(langs, { border_radius: "0" });
    expect(document.querySelector("rect")).toHaveAttribute("rx", "0");
    document.body.innerHTML = renderTopLanguages(langs, {});
    expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
  });

  it("should render langs with specified langs_count", () => {
    const options = {
      langs_count: 1,
    };
    document.body.innerHTML = renderTopLanguages(langs, { ...options });
    expect(queryAllByTestId(document.body, "lang-name").length).toBe(
      options.langs_count,
    );
  });

  it("should render langs with specified langs_count even when hide is set", () => {
    const options = {
      hide: ["HTML"],
      langs_count: 2,
    };
    document.body.innerHTML = renderTopLanguages(langs, { ...options });
    expect(queryAllByTestId(document.body, "lang-name").length).toBe(
      options.langs_count,
    );
  });

  it('should show "No languages data." message instead of empty card when nothing to show', () => {
    document.body.innerHTML = renderTopLanguages({});
    expect(document.querySelector(".stat").textContent).toBe(
      "No languages data.",
    );
  });
});
