diff --git a/api/top-langs.js b/api/top-langs.js index 5899d9d..6c6634c 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -1,11 +1,17 @@ require("dotenv").config(); -const { renderError, clampValue, CONSTANTS } = require("../src/utils"); +const { + renderError, + clampValue, + parseBoolean, + CONSTANTS, +} = require("../src/utils"); const fetchTopLanguages = require("../src/fetchTopLanguages"); const renderTopLanguages = require("../src/renderTopLanguages"); module.exports = async (req, res) => { const { username, + hide_title, card_width, title_color, text_color, @@ -34,6 +40,7 @@ module.exports = async (req, res) => { res.send( renderTopLanguages(topLangs, { theme, + hide_title: parseBoolean(hide_title), card_width: parseInt(card_width, 10), title_color, text_color, diff --git a/src/renderTopLanguages.js b/src/renderTopLanguages.js index 608afbe..a8f5eb7 100644 --- a/src/renderTopLanguages.js +++ b/src/renderTopLanguages.js @@ -7,17 +7,31 @@ const createProgressNode = ({ width, color, name, progress }) => { const progressPercentage = clampValue(progress, 2, 100); return ` - ${name} + ${name} ${progress}% - + + `; }; const renderTopLanguages = (topLangs, options = {}) => { - const { title_color, text_color, bg_color, theme, card_width } = options; + const { + hide_title, + card_width, + title_color, + text_color, + bg_color, + theme, + } = options; const langs = Object.values(topLangs); @@ -34,8 +48,11 @@ const renderTopLanguages = (topLangs, options = {}) => { }); const width = isNaN(card_width) ? 300 : card_width; - const height = 45 + (langs.length + 1) * 40; + let height = 45 + (langs.length + 1) * 40; + if (hide_title) { + height -= 30; + } return ` - Top Languages + + ${ + hide_title + ? "" + : `Top Languages` + } - + ${FlexLayout({ items: langs.map((lang) => { return createProgressNode({ @@ -58,7 +80,7 @@ const renderTopLanguages = (topLangs, options = {}) => { }), gap: 40, direction: "column", - })} + }).join("")} `; diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js new file mode 100644 index 0000000..b934dac --- /dev/null +++ b/tests/renderTopLanguages.test.js @@ -0,0 +1,188 @@ +require("@testing-library/jest-dom"); +const cssToObject = require("css-to-object"); +const renderTopLanguages = require("../src/renderTopLanguages"); + +const { + getByTestId, + queryByTestId, + queryAllByTestId, +} = require("@testing-library/dom"); +const themes = require("../themes"); + +describe("Test renderTopLanguages", () => { + const langs = { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, + css: { + color: "#ff0", + name: "css", + size: 100, + }, + }; + + it("should render correctly", () => { + document.body.innerHTML = renderTopLanguages(langs); + + expect(queryByTestId(document.body, "header")).toHaveTextContent( + "Top 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 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 hide_title", () => { + document.body.innerHTML = renderTopLanguages(langs, { hide_title: false }); + expect(document.querySelector("svg")).toHaveAttribute("height", "205"); + expect(queryByTestId(document.body, "lang-items")).toHaveAttribute( + "y", + "55" + ); + + // Lets hide now + document.body.innerHTML = renderTopLanguages(langs, { hide_title: true }); + expect(document.querySelector("svg")).toHaveAttribute("height", "175"); + + expect(queryByTestId(document.body, "header")).not.toBeInTheDocument(); + expect(queryByTestId(document.body, "lang-items")).toHaveAttribute( + "y", + "25" + ); + }); + + 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 default colors properly", () => { + document.body.innerHTML = renderTopLanguages(langs); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.textContent); + + const headerStyles = stylesObject[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe("#2f80ed"); + expect(langNameStyles.fill).toBe("#333"); + 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[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe(`#${customColors.title_color}`); + expect(langNameStyles.fill).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[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe("#5a0"); + expect(langNameStyles.fill).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[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe(`#${themes[name].title_color}`); + expect(langNameStyles.fill).toBe(`#${themes[name].text_color}`); + expect(queryByTestId(document.body, "card-bg")).toHaveAttribute( + "fill", + `#${themes[name].bg_color}` + ); + }); + }); +}); diff --git a/tests/top-langs.test.js b/tests/top-langs.test.js new file mode 100644 index 0000000..330acfa --- /dev/null +++ b/tests/top-langs.test.js @@ -0,0 +1,142 @@ +require("@testing-library/jest-dom"); +const axios = require("axios"); +const MockAdapter = require("axios-mock-adapter"); +const topLangs = require("../api/top-langs"); +const renderTopLanguages = require("../src/renderTopLanguages"); +const { renderError } = require("../src/utils"); + +const data_langs = { + data: { + user: { + repositories: { + nodes: [ + { + languages: { + edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }], + }, + }, + { + languages: { + edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }], + }, + }, + { + languages: { + edges: [ + { size: 100, node: { color: "#0ff", name: "javascript" } }, + ], + }, + }, + { + languages: { + edges: [ + { size: 100, node: { color: "#0ff", name: "javascript" } }, + ], + }, + }, + ], + }, + }, + }, +}; + +const error = { + errors: [ + { + type: "NOT_FOUND", + path: ["user"], + locations: [], + message: "Could not fetch user", + }, + ], +}; + +const langs = { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, +}; + +const mock = new MockAdapter(axios); + +afterEach(() => { + mock.reset(); +}); + +describe("Test /api/top-langs", () => { + it("should test the request", async () => { + const req = { + query: { + username: "anuraghazra", + }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + mock.onPost("https://api.github.com/graphql").reply(200, data_langs); + + await topLangs(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.send).toBeCalledWith(renderTopLanguages(langs)); + }); + + it("should work with the query options", async () => { + const req = { + query: { + username: "anuraghazra", + hide_title: true, + card_width: 100, + title_color: "fff", + icon_color: "fff", + text_color: "fff", + bg_color: "fff", + }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + mock.onPost("https://api.github.com/graphql").reply(200, data_langs); + + await topLangs(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.send).toBeCalledWith( + renderTopLanguages(langs, { + hide_title: true, + card_width: 100, + title_color: "fff", + icon_color: "fff", + text_color: "fff", + bg_color: "fff", + }) + ); + }); + + it("should render error card on error", async () => { + const req = { + query: { + username: "anuraghazra", + }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + mock.onPost("https://api.github.com/graphql").reply(200, error); + + await topLangs(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.send).toBeCalledWith(renderError(error.errors[0].message)); + }); +});