diff --git a/api/pin.js b/api/pin.js
index 7fad6c0..65d09f0 100644
--- a/api/pin.js
+++ b/api/pin.js
@@ -53,7 +53,7 @@ module.exports = async (req, res) => {
and if both are zero we are not showing the stats
so we can just make the cache longer, since there is no need to frequent updates
*/
- const stars = repoData.stargazers.totalCount;
+ const stars = repoData.starCount;
const forks = repoData.forkCount;
const isBothOver1K = stars > 1000 && forks > 1000;
const isBothUnder1 = stars < 1 && forks < 1;
diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js
index b1112d6..4573fcf 100644
--- a/src/cards/repo-card.js
+++ b/src/cards/repo-card.js
@@ -1,4 +1,3 @@
-const toEmoji = require("emoji-name-map");
const {
kFormatter,
encodeHTML,
@@ -6,21 +5,75 @@ const {
flexLayout,
wrapTextMultiline,
measureText,
+ parseEmojis,
} = require("../common/utils");
const I18n = require("../common/I18n");
const Card = require("../common/Card");
const icons = require("../common/icons");
const { repoCardLocales } = require("../translations");
+/**
+ * @param {string} label
+ * @param {string} textColor
+ * @returns {string}
+ */
+const getBadgeSVG = (label, textColor) => `
+
+
+
+ ${label}
+
+
+`;
+
+/**
+ * @param {string} langName
+ * @param {string} langColor
+ * @returns {string}
+ */
+const createLanguageNode = (langName, langColor) => {
+ return `
+
+
+ ${langName}
+
+ `;
+};
+
+const ICON_SIZE = 16;
+const iconWithLabel = (icon, label, testid) => {
+ if (label <= 0) return "";
+ const iconSvg = `
+
+ `;
+ const text = `${label}`;
+ return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
+};
+
const renderRepoCard = (repo, options = {}) => {
const {
name,
nameWithOwner,
description,
primaryLanguage,
- stargazers,
isArchived,
isTemplate,
+ starCount,
forkCount,
} = repo;
const {
@@ -36,22 +89,17 @@ const renderRepoCard = (repo, options = {}) => {
locale,
} = options;
+ const lineHeight = 10;
const header = show_owner ? nameWithOwner : name;
const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
- const shiftText = langName.length > 15 ? 0 : 30;
-
- let desc = description || "No description provided";
-
- // parse emojis to unicode
- desc = desc.replace(/:\w+:/gm, (emoji) => {
- return toEmoji.get(emoji) || "";
- });
-
+ const desc = parseEmojis(description || "No description provided");
const multiLineDescription = wrapTextMultiline(desc);
const descriptionLines = multiLineDescription.length;
- const lineHeight = 10;
+ const descriptionSvg = multiLineDescription
+ .map((line) => `${encodeHTML(line)}`)
+ .join("");
const height =
(descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
@@ -72,56 +120,21 @@ const renderRepoCard = (repo, options = {}) => {
theme,
});
- const totalStars = kFormatter(stargazers.totalCount);
- const totalForks = kFormatter(forkCount);
-
- const getBadgeSVG = (label) => `
-
-
-
- ${label}
-
-
- `;
-
const svgLanguage = primaryLanguage
- ? `
-
-
- ${langName}
-
- `
+ ? createLanguageNode(langName, langColor)
: "";
- const iconSize = 16;
- const iconWithLabel = (icon, label, testid) => {
- const iconSvg = `
-
- `;
- const text = `${label}`;
- return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
- };
-
- const svgStars =
- stargazers.totalCount > 0 &&
- iconWithLabel(icons.star, totalStars, "stargazers");
- const svgForks =
- forkCount > 0 && iconWithLabel(icons.fork, totalForks, "forkcount");
+ const totalStars = kFormatter(starCount);
+ const totalForks = kFormatter(forkCount);
+ const svgStars = iconWithLabel(icons.star, totalStars, "stargazers");
+ const svgForks = iconWithLabel(icons.fork, totalForks, "forkcount");
const starAndForkCount = flexLayout({
items: [svgLanguage, svgStars, svgForks],
sizes: [
measureText(langName, 12),
- iconSize + measureText(`${totalStars}`, 12),
- iconSize + measureText(`${totalForks}`, 12),
+ ICON_SIZE + measureText(`${totalStars}`, 12),
+ ICON_SIZE + measureText(`${totalForks}`, 12),
],
gap: 25,
}).join("");
@@ -155,16 +168,14 @@ const renderRepoCard = (repo, options = {}) => {
return card.render(`
${
isTemplate
- ? getBadgeSVG(i18n.t("repocard.template"))
+ ? getBadgeSVG(i18n.t("repocard.template"), textColor)
: isArchived
- ? getBadgeSVG(i18n.t("repocard.archived"))
+ ? getBadgeSVG(i18n.t("repocard.archived"), textColor)
: ""
}
- ${multiLineDescription
- .map((line) => `${encodeHTML(line)}`)
- .join("")}
+ ${descriptionSvg}
diff --git a/src/common/utils.js b/src/common/utils.js
index f14e8cc..7834dba 100644
--- a/src/common/utils.js
+++ b/src/common/utils.js
@@ -1,6 +1,7 @@
const axios = require("axios");
const wrap = require("word-wrap");
const themes = require("../../themes");
+const toEmoji = require("emoji-name-map");
const renderError = (message, secondaryMessage = "") => {
return `
@@ -88,10 +89,11 @@ function request(data, headers) {
}
/**
- *
- * @param {string[]} items
- * @param {Number} gap
- * @param {"column" | "row"} direction
+ * @param {object} props
+ * @param {string[]} props.items
+ * @param {number} props.gap
+ * @param {number[]} props.sizes
+ * @param {"column" | "row"} props.direction
*
* @returns {string[]}
*
@@ -257,6 +259,18 @@ function chunkArray(arr, perChunk) {
}, []);
}
+/**
+ *
+ * @param {string} str
+ * @returns {string}
+ */
+function parseEmojis(str) {
+ if (!str) throw new Error("[parseEmoji]: str argument not provided");
+ return str.replace(/:\w+:/gm, (emoji) => {
+ return toEmoji.get(emoji) || "";
+ });
+}
+
module.exports = {
renderError,
kFormatter,
@@ -276,4 +290,5 @@ module.exports = {
CustomError,
lowercaseTrim,
chunkArray,
+ parseEmojis,
};
diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js
index 7e44108..07e9a4c 100644
--- a/src/fetchers/repo-fetcher.js
+++ b/src/fetchers/repo-fetcher.js
@@ -63,7 +63,10 @@ async function fetchRepo(username, reponame) {
if (!data.user.repository || data.user.repository.isPrivate) {
throw new Error("User Repository Not found");
}
- return data.user.repository;
+ return {
+ ...data.user.repository,
+ starCount: data.user.repository.stargazers.totalCount,
+ };
}
if (isOrg) {
@@ -73,7 +76,10 @@ async function fetchRepo(username, reponame) {
) {
throw new Error("Organization Repository Not found");
}
- return data.organization.repository;
+ return {
+ ...data.organization.repository,
+ starCount: data.organization.repository.stargazers.totalCount,
+ };
}
}
diff --git a/tests/fetchRepo.test.js b/tests/fetchRepo.test.js
index 891cb4a..277d627 100644
--- a/tests/fetchRepo.test.js
+++ b/tests/fetchRepo.test.js
@@ -19,14 +19,14 @@ const data_repo = {
const data_user = {
data: {
- user: { repository: data_repo },
+ user: { repository: data_repo.repository },
organization: null,
},
};
const data_org = {
data: {
user: null,
- organization: { repository: data_repo },
+ organization: { repository: data_repo.repository },
},
};
@@ -41,14 +41,21 @@ describe("Test fetchRepo", () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_user);
let repo = await fetchRepo("anuraghazra", "convoychat");
- expect(repo).toStrictEqual(data_repo);
+
+ expect(repo).toStrictEqual({
+ ...data_repo.repository,
+ starCount: data_repo.repository.stargazers.totalCount,
+ });
});
it("should fetch correct org repo", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_org);
let repo = await fetchRepo("anuraghazra", "convoychat");
- expect(repo).toStrictEqual(data_repo);
+ expect(repo).toStrictEqual({
+ ...data_repo.repository,
+ starCount: data_repo.repository.stargazers.totalCount,
+ });
});
it("should throw error if user is found but repo is null", async () => {
diff --git a/tests/pin.test.js b/tests/pin.test.js
index 8abad44..29c19c6 100644
--- a/tests/pin.test.js
+++ b/tests/pin.test.js
@@ -9,7 +9,9 @@ const data_repo = {
repository: {
username: "anuraghazra",
name: "convoychat",
- stargazers: { totalCount: 38000 },
+ stargazers: {
+ totalCount: 38000,
+ },
description: "Help us take over the world! React + TS + GraphQL Chat App",
primaryLanguage: {
color: "#2b7489",
@@ -51,7 +53,12 @@ describe("Test /api/pin", () => {
await pin(req, res);
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toBeCalledWith(renderRepoCard(data_repo.repository));
+ expect(res.send).toBeCalledWith(
+ renderRepoCard({
+ ...data_repo.repository,
+ starCount: data_repo.repository.stargazers.totalCount,
+ }),
+ );
});
it("should get the query options", async () => {
@@ -76,7 +83,13 @@ describe("Test /api/pin", () => {
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
expect(res.send).toBeCalledWith(
- renderRepoCard(data_repo.repository, { ...req.query }),
+ renderRepoCard(
+ {
+ ...data_repo.repository,
+ starCount: data_repo.repository.stargazers.totalCount,
+ },
+ { ...req.query },
+ ),
);
});
diff --git a/tests/renderRepoCard.test.js b/tests/renderRepoCard.test.js
index a6d2498..4b7060a 100644
--- a/tests/renderRepoCard.test.js
+++ b/tests/renderRepoCard.test.js
@@ -9,13 +9,13 @@ const data_repo = {
repository: {
nameWithOwner: "anuraghazra/convoychat",
name: "convoychat",
- stargazers: { totalCount: 38000 },
description: "Help us take over the world! React + TS + GraphQL Chat App",
primaryLanguage: {
color: "#2b7489",
id: "MDg6TGFuZ3VhZ2UyODc=",
name: "TypeScript",
},
+ starCount: 38000,
forkCount: 100,
},
};
@@ -231,7 +231,7 @@ describe("Test renderRepoCard", () => {
it("should not render star count or fork count if either of the are zero", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
- stargazers: { totalCount: 0 },
+ starCount: 0,
});
expect(queryByTestId(document.body, "stargazers")).toBeNull();
@@ -239,7 +239,7 @@ describe("Test renderRepoCard", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
- stargazers: { totalCount: 1 },
+ starCount: 1,
forkCount: 0,
});
@@ -248,7 +248,7 @@ describe("Test renderRepoCard", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
- stargazers: { totalCount: 0 },
+ starCount: 0,
forkCount: 0,
});
@@ -311,4 +311,15 @@ describe("Test renderRepoCard", () => {
document.body.innerHTML = renderRepoCard(data_repo.repository, {});
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
});
+
+ it("should fallback to default description", () => {
+ document.body.innerHTML = renderRepoCard({
+ ...data_repo.repository,
+ description: undefined,
+ isArchived: true,
+ });
+ expect(document.getElementsByClassName("description")[0]).toHaveTextContent(
+ "No description provided",
+ );
+ });
});