diff --git a/package.json b/package.json index b644e323..0adc0106 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ }, "dependencies": { "dotenv": "^8.2.0", - "emoji-name-map": "^1.2.8" + "emoji-name-map": "^1.2.8", + "word-wrap": "^1.2.3" }, "husky": { "hooks": { diff --git a/src/renderRepoCard.js b/src/renderRepoCard.js index a0cf6e2e..8f9f5b6a 100644 --- a/src/renderRepoCard.js +++ b/src/renderRepoCard.js @@ -3,6 +3,7 @@ const { encodeHTML, getCardColors, FlexLayout, + wrapTextMultiline, } = require("../src/utils"); const icons = require("./icons"); const toEmoji = require("emoji-name-map"); @@ -31,7 +32,6 @@ const renderRepoCard = (repo, options = {}) => { const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified"; const langColor = (primaryLanguage && primaryLanguage.color) || "#333"; - const height = 120; const shiftText = langName.length > 15 ? 0 : 30; let desc = description || "No description provided"; @@ -41,9 +41,12 @@ const renderRepoCard = (repo, options = {}) => { return toEmoji.get(emoji) || ""; }); - if (desc.length > 55) { - desc = `${desc.slice(0, 55)}..`; - } + const multiLineDescription = wrapTextMultiline(desc); + const descriptionLines = multiLineDescription.length; + const lineHeight = 10; + + const height = + (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight; // returns theme based colors with proper overrides and defaults const { titleColor, textColor, iconColor, bgColor } = getCardColors({ @@ -60,11 +63,11 @@ const renderRepoCard = (repo, options = {}) => { const getBadgeSVG = (label) => ` - ${label} @@ -74,7 +77,7 @@ const renderRepoCard = (repo, options = {}) => { const svgLanguage = primaryLanguage ? ` - + ${langName} @@ -124,15 +127,23 @@ const renderRepoCard = (repo, options = {}) => { ? getBadgeSVG("Archived") : "" } - - ${encodeHTML(desc)} - - ${svgLanguage} - - - ${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")} - + + ${multiLineDescription + .map((line) => `${encodeHTML(line)}`) + .join("")} + + + + ${svgLanguage} + + + ${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")} + + `; }; diff --git a/src/utils.js b/src/utils.js index 31689cc6..fe101c80 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,5 @@ const axios = require("axios"); +const wrap = require("word-wrap"); const themes = require("../themes"); const renderError = (message, secondaryMessage = "") => { @@ -127,10 +128,27 @@ function getCardColors({ return { titleColor, iconColor, textColor, bgColor }; } -const fn = () => {}; +function wrapTextMultiline(text, width = 60, maxLines = 3) { + const wrapped = wrap(encodeHTML(text), { width }) + .split("\n") // Split wrapped lines to get an array of lines + .map((line) => line.trim()); // Remove leading and trailing whitespace of each line + + const lines = wrapped.slice(0, maxLines); // Only consider maxLines lines + + // Add "..." to the last line if the text exceeds maxLines + if (wrapped.length > maxLines) { + lines[maxLines - 1] += "..."; + } + + // Remove empty lines if text fits in less than maxLines lines + const multiLineText = lines.filter(Boolean); + return multiLineText; +} + +const noop = () => {}; // return console instance based on the environment const logger = - process.env.NODE_ENV !== "test" ? console : { log: fn, error: fn }; + process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop }; const CONSTANTS = { THIRTY_MINUTES: 1800, @@ -150,6 +168,7 @@ module.exports = { FlexLayout, getCardColors, clampValue, + wrapTextMultiline, logger, CONSTANTS, }; diff --git a/tests/renderRepoCard.test.js b/tests/renderRepoCard.test.js index f2b2d9f8..6b741aa7 100644 --- a/tests/renderRepoCard.test.js +++ b/tests/renderRepoCard.test.js @@ -29,7 +29,7 @@ describe("Test renderRepoCard", () => { expect(header).toHaveTextContent("convoychat"); expect(header).not.toHaveTextContent("anuraghazra"); expect(document.getElementsByClassName("description")[0]).toHaveTextContent( - "Help us take over the world! React + TS + GraphQL Chat .." + "Help us take over the world! React + TS + GraphQL Chat App" ); expect(queryByTestId(document.body, "stargazers")).toHaveTextContent("38k"); expect(queryByTestId(document.body, "forkcount")).toHaveTextContent("100"); @@ -55,12 +55,16 @@ describe("Test renderRepoCard", () => { document.body.innerHTML = renderRepoCard({ ...data_repo.repository, description: - "Very long long long long long long long long text it should trim it", + "The quick brown fox jumps over the lazy dog is an English-language pangram—a sentence that contains all of the letters of the English alphabet", }); - expect(document.getElementsByClassName("description")[0]).toHaveTextContent( - "Very long long long long long long long long text it sh.." - ); + expect( + document.getElementsByClassName("description")[0].children[0].textContent + ).toBe("The quick brown fox jumps over the lazy dog is an"); + + expect( + document.getElementsByClassName("description")[0].children[1].textContent + ).toBe("English-language pangram—a sentence that contains all"); // Should not trim document.body.innerHTML = renderRepoCard({ @@ -95,9 +99,9 @@ describe("Test renderRepoCard", () => { }); expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument(); - expect(document.getElementsByTagName("g")[1]).toHaveAttribute( + expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute( "transform", - "translate(155, 100)" + "translate(155, 0)" ); // Small lang @@ -109,9 +113,9 @@ describe("Test renderRepoCard", () => { }, }); - expect(document.getElementsByTagName("g")[1]).toHaveAttribute( + expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute( "transform", - "translate(125, 100)" + "translate(125, 0)" ); }); diff --git a/tests/utils.test.js b/tests/utils.test.js index 5a6445de..ccd1864c 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -5,6 +5,7 @@ const { renderError, FlexLayout, getCardColors, + wrapTextMultiline, } = require("../src/utils"); const { queryByTestId } = require("@testing-library/dom"); @@ -108,3 +109,28 @@ describe("Test utils.js", () => { }); }); }); + +describe("wrapTextMultiline", () => { + it("should not wrap small texts", () => { + { + let multiLineText = wrapTextMultiline("Small text should not wrap"); + expect(multiLineText).toEqual(["Small text should not wrap"]); + } + }); + it("should wrap large texts", () => { + let multiLineText = wrapTextMultiline( + "Hello world long long long text", + 20, + 3 + ); + expect(multiLineText).toEqual(["Hello world long", "long long text"]); + }); + it("should wrap large texts and limit max lines", () => { + let multiLineText = wrapTextMultiline( + "Hello world long long long text", + 10, + 2 + ); + expect(multiLineText).toEqual(["Hello", "world long..."]); + }); +});