feat: multiline text in repo description (#206)

* Enable multi line repo card descriptions

* Adjust unit tests to multi line descriptions

* refactor: refactored code & fixed tests

* style: minor height adjustments

* chore: codecov stuck

Co-authored-by: anuraghazra <hazru.anurag@gmail.com>
This commit is contained in:
Leo Hanisch 2020-07-27 10:39:35 +02:00 committed by GitHub
parent 68198d8b16
commit 11757dba42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 29 deletions

View File

@ -21,7 +21,8 @@
}, },
"dependencies": { "dependencies": {
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"emoji-name-map": "^1.2.8" "emoji-name-map": "^1.2.8",
"word-wrap": "^1.2.3"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@ -3,6 +3,7 @@ const {
encodeHTML, encodeHTML,
getCardColors, getCardColors,
FlexLayout, FlexLayout,
wrapTextMultiline,
} = require("../src/utils"); } = require("../src/utils");
const icons = require("./icons"); const icons = require("./icons");
const toEmoji = require("emoji-name-map"); const toEmoji = require("emoji-name-map");
@ -31,7 +32,6 @@ const renderRepoCard = (repo, options = {}) => {
const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified"; const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
const langColor = (primaryLanguage && primaryLanguage.color) || "#333"; const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
const height = 120;
const shiftText = langName.length > 15 ? 0 : 30; const shiftText = langName.length > 15 ? 0 : 30;
let desc = description || "No description provided"; let desc = description || "No description provided";
@ -41,9 +41,12 @@ const renderRepoCard = (repo, options = {}) => {
return toEmoji.get(emoji) || ""; return toEmoji.get(emoji) || "";
}); });
if (desc.length > 55) { const multiLineDescription = wrapTextMultiline(desc);
desc = `${desc.slice(0, 55)}..`; const descriptionLines = multiLineDescription.length;
} const lineHeight = 10;
const height =
(descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
// returns theme based colors with proper overrides and defaults // returns theme based colors with proper overrides and defaults
const { titleColor, textColor, iconColor, bgColor } = getCardColors({ const { titleColor, textColor, iconColor, bgColor } = getCardColors({
@ -74,7 +77,7 @@ const renderRepoCard = (repo, options = {}) => {
const svgLanguage = primaryLanguage const svgLanguage = primaryLanguage
? ` ? `
<g data-testid="primary-lang" transform="translate(30, 100)"> <g data-testid="primary-lang" transform="translate(30, 0)">
<circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" /> <circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
<text data-testid="lang-name" class="gray" x="15">${langName}</text> <text data-testid="lang-name" class="gray" x="15">${langName}</text>
</g> </g>
@ -125,14 +128,22 @@ const renderRepoCard = (repo, options = {}) => {
: "" : ""
} }
<text class="description" x="25" y="70">${encodeHTML(desc)}</text> <text class="description" x="25" y="50">
${multiLineDescription
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
.join("")}
</text>
<g transform="translate(0, ${height - 20})">
${svgLanguage} ${svgLanguage}
<g transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 100)"> <g
data-testid="star-fork-group"
transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 0)"
>
${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")} ${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")}
</g> </g>
</g>
</svg> </svg>
`; `;
}; };

View File

@ -1,4 +1,5 @@
const axios = require("axios"); const axios = require("axios");
const wrap = require("word-wrap");
const themes = require("../themes"); const themes = require("../themes");
const renderError = (message, secondaryMessage = "") => { const renderError = (message, secondaryMessage = "") => {
@ -127,10 +128,27 @@ function getCardColors({
return { titleColor, iconColor, textColor, bgColor }; 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 // return console instance based on the environment
const logger = const logger =
process.env.NODE_ENV !== "test" ? console : { log: fn, error: fn }; process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop };
const CONSTANTS = { const CONSTANTS = {
THIRTY_MINUTES: 1800, THIRTY_MINUTES: 1800,
@ -150,6 +168,7 @@ module.exports = {
FlexLayout, FlexLayout,
getCardColors, getCardColors,
clampValue, clampValue,
wrapTextMultiline,
logger, logger,
CONSTANTS, CONSTANTS,
}; };

View File

@ -29,7 +29,7 @@ describe("Test renderRepoCard", () => {
expect(header).toHaveTextContent("convoychat"); expect(header).toHaveTextContent("convoychat");
expect(header).not.toHaveTextContent("anuraghazra"); expect(header).not.toHaveTextContent("anuraghazra");
expect(document.getElementsByClassName("description")[0]).toHaveTextContent( 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, "stargazers")).toHaveTextContent("38k");
expect(queryByTestId(document.body, "forkcount")).toHaveTextContent("100"); expect(queryByTestId(document.body, "forkcount")).toHaveTextContent("100");
@ -55,12 +55,16 @@ describe("Test renderRepoCard", () => {
document.body.innerHTML = renderRepoCard({ document.body.innerHTML = renderRepoCard({
...data_repo.repository, ...data_repo.repository,
description: 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( expect(
"Very long long long long long long long long text it sh.." 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 // Should not trim
document.body.innerHTML = renderRepoCard({ document.body.innerHTML = renderRepoCard({
@ -95,9 +99,9 @@ describe("Test renderRepoCard", () => {
}); });
expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument(); expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument();
expect(document.getElementsByTagName("g")[1]).toHaveAttribute( expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute(
"transform", "transform",
"translate(155, 100)" "translate(155, 0)"
); );
// Small lang // Small lang
@ -109,9 +113,9 @@ describe("Test renderRepoCard", () => {
}, },
}); });
expect(document.getElementsByTagName("g")[1]).toHaveAttribute( expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute(
"transform", "transform",
"translate(125, 100)" "translate(125, 0)"
); );
}); });

View File

@ -5,6 +5,7 @@ const {
renderError, renderError,
FlexLayout, FlexLayout,
getCardColors, getCardColors,
wrapTextMultiline,
} = require("../src/utils"); } = require("../src/utils");
const { queryByTestId } = require("@testing-library/dom"); 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..."]);
});
});