mirror of
https://github.com/Aviortheking/codestats-readme.git
synced 2025-04-22 10:42:08 +00:00
refactor: added reusable Card class to reduce code & test duplication (#260)
* refactor: added reusable Card class to reduce code & test duplication * fix: top-langs card width & documented card_width option
This commit is contained in:
parent
34b5dcb181
commit
3b0f1b11a0
@ -14,13 +14,14 @@ module.exports = async (req, res) => {
|
|||||||
username,
|
username,
|
||||||
hide,
|
hide,
|
||||||
hide_title,
|
hide_title,
|
||||||
|
hide_border,
|
||||||
card_width,
|
card_width,
|
||||||
title_color,
|
title_color,
|
||||||
text_color,
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
theme,
|
theme,
|
||||||
cache_seconds,
|
cache_seconds,
|
||||||
layout
|
layout,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
let topLangs;
|
let topLangs;
|
||||||
|
|
||||||
@ -42,15 +43,15 @@ module.exports = async (req, res) => {
|
|||||||
|
|
||||||
res.send(
|
res.send(
|
||||||
renderTopLanguages(topLangs, {
|
renderTopLanguages(topLangs, {
|
||||||
theme,
|
|
||||||
hide_title: parseBoolean(hide_title),
|
hide_title: parseBoolean(hide_title),
|
||||||
|
hide_border: parseBoolean(hide_border),
|
||||||
card_width: parseInt(card_width, 10),
|
card_width: parseInt(card_width, 10),
|
||||||
hide: parseArray(hide),
|
hide: parseArray(hide),
|
||||||
title_color,
|
title_color,
|
||||||
text_color,
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
theme,
|
theme,
|
||||||
layout
|
layout,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -137,6 +137,7 @@ Customization Options:
|
|||||||
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | 1800 |
|
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | 1800 |
|
||||||
| count_private | boolean | counts private contributions too if enabled | false | N/A | N/A |
|
| count_private | boolean | counts private contributions too if enabled | false | N/A | N/A |
|
||||||
| layout | string | choose a layout option | N/A | N/A | 'default' |
|
| layout | string | choose a layout option | N/A | N/A | 'default' |
|
||||||
|
| card_width | number | set the card width | N/A | N/A | 300 |
|
||||||
|
|
||||||
> Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours
|
> Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours
|
||||||
|
|
||||||
|
139
src/Card.js
Normal file
139
src/Card.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
const { FlexLayout } = require("./utils");
|
||||||
|
const { getAnimations } = require("./getStyles");
|
||||||
|
|
||||||
|
class Card {
|
||||||
|
constructor({
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
colors = {},
|
||||||
|
title = "",
|
||||||
|
titlePrefixIcon,
|
||||||
|
}) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
|
||||||
|
this.hideBorder = false;
|
||||||
|
this.hideTitle = false;
|
||||||
|
|
||||||
|
// returns theme based colors with proper overrides and defaults
|
||||||
|
this.colors = colors;
|
||||||
|
this.title = title;
|
||||||
|
this.css = "";
|
||||||
|
|
||||||
|
this.paddingX = 25;
|
||||||
|
this.paddingY = 35;
|
||||||
|
this.titlePrefixIcon = titlePrefixIcon;
|
||||||
|
this.animations = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableAnimations() {
|
||||||
|
this.animations = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCSS(value) {
|
||||||
|
this.css = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHideBorder(value) {
|
||||||
|
this.hideBorder = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHideTitle(value) {
|
||||||
|
this.hideTitle = value;
|
||||||
|
if (value) {
|
||||||
|
this.height -= 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle(text) {
|
||||||
|
this.title = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitle() {
|
||||||
|
const titleText = `
|
||||||
|
<text
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
class="header"
|
||||||
|
data-testid="header"
|
||||||
|
>${this.title}</text>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const prefixIcon = `
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
x="0"
|
||||||
|
y="-13"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
>
|
||||||
|
${this.titlePrefixIcon}
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
return `
|
||||||
|
<g
|
||||||
|
data-testid="card-title"
|
||||||
|
transform="translate(${this.paddingX}, ${this.paddingY})"
|
||||||
|
>
|
||||||
|
${FlexLayout({
|
||||||
|
items: [this.titlePrefixIcon && prefixIcon, titleText],
|
||||||
|
gap: 25,
|
||||||
|
}).join("")}
|
||||||
|
</g>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(body) {
|
||||||
|
return `
|
||||||
|
<svg
|
||||||
|
width="${this.width}"
|
||||||
|
height="${this.height}"
|
||||||
|
viewBox="0 0 ${this.width} ${this.height}"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
|
||||||
|
fill: ${this.colors.titleColor};
|
||||||
|
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
${this.css}
|
||||||
|
|
||||||
|
${
|
||||||
|
process.env.NODE_ENV === "test" || !this.animations
|
||||||
|
? ""
|
||||||
|
: getAnimations()
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
data-testid="card-bg"
|
||||||
|
x="0.5"
|
||||||
|
y="0.5"
|
||||||
|
rx="4.5"
|
||||||
|
height="99%"
|
||||||
|
stroke="#E4E2E2"
|
||||||
|
width="${this.width - 1}"
|
||||||
|
fill="${this.colors.bgColor}"
|
||||||
|
stroke-opacity="${this.hideBorder ? 0 : 1}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
${this.hideTitle ? "" : this.renderTitle()}
|
||||||
|
|
||||||
|
<g
|
||||||
|
data-testid="main-card-body"
|
||||||
|
transform="translate(0, ${
|
||||||
|
this.hideTitle ? this.paddingX : this.paddingY + 20
|
||||||
|
})"
|
||||||
|
>
|
||||||
|
${body}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Card;
|
@ -9,10 +9,23 @@ const calculateCircleProgress = (value) => {
|
|||||||
return percentage;
|
return percentage;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAnimations = ({ progress }) => {
|
const getProgressAnimation = ({ progress }) => {
|
||||||
|
return `
|
||||||
|
@keyframes rankAnimation {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: ${calculateCircleProgress(0)};
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: ${calculateCircleProgress(progress)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAnimations = () => {
|
||||||
return `
|
return `
|
||||||
/* Animations */
|
/* Animations */
|
||||||
@keyframes scaleIn {
|
@keyframes scaleInAnimation {
|
||||||
from {
|
from {
|
||||||
transform: translate(-5px, 5px) scale(0);
|
transform: translate(-5px, 5px) scale(0);
|
||||||
}
|
}
|
||||||
@ -20,7 +33,7 @@ const getAnimations = ({ progress }) => {
|
|||||||
transform: translate(-5px, 5px) scale(1);
|
transform: translate(-5px, 5px) scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes fadeIn {
|
@keyframes fadeInAnimation {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@ -28,14 +41,6 @@ const getAnimations = ({ progress }) => {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes rankAnimation {
|
|
||||||
from {
|
|
||||||
stroke-dashoffset: ${calculateCircleProgress(0)};
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
stroke-dashoffset: ${calculateCircleProgress(progress)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,20 +52,16 @@ const getStyles = ({
|
|||||||
progress,
|
progress,
|
||||||
}) => {
|
}) => {
|
||||||
return `
|
return `
|
||||||
.header {
|
|
||||||
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${titleColor};
|
|
||||||
animation: fadeIn 0.8s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
.stat {
|
.stat {
|
||||||
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
||||||
}
|
}
|
||||||
.stagger {
|
.stagger {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation: fadeIn 0.3s ease-in-out forwards;
|
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||||
}
|
}
|
||||||
.rank-text {
|
.rank-text {
|
||||||
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
|
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
|
||||||
animation: scaleIn 0.3s ease-in-out forwards;
|
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold { font-weight: 700 }
|
.bold { font-weight: 700 }
|
||||||
@ -86,9 +87,8 @@ const getStyles = ({
|
|||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
animation: rankAnimation 1s forwards ease-in-out;
|
animation: rankAnimation 1s forwards ease-in-out;
|
||||||
}
|
}
|
||||||
|
${process.env.NODE_ENV === "test" ? "" : getProgressAnimation({ progress })}
|
||||||
${process.env.NODE_ENV === "test" ? "" : getAnimations({ progress })}
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = getStyles;
|
module.exports = { getStyles, getAnimations };
|
||||||
|
@ -7,6 +7,7 @@ const {
|
|||||||
} = 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");
|
||||||
|
const Card = require("./Card");
|
||||||
|
|
||||||
const renderRepoCard = (repo, options = {}) => {
|
const renderRepoCard = (repo, options = {}) => {
|
||||||
const {
|
const {
|
||||||
@ -84,68 +85,75 @@ const renderRepoCard = (repo, options = {}) => {
|
|||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
const iconWithLabel = (icon, label, testid) => {
|
||||||
|
return `
|
||||||
|
<svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16">
|
||||||
|
${icon}
|
||||||
|
</svg>
|
||||||
|
<text data-testid="${testid}" class="gray" x="25">${label}</text>
|
||||||
|
`;
|
||||||
|
};
|
||||||
const svgStars =
|
const svgStars =
|
||||||
stargazers.totalCount > 0 &&
|
stargazers.totalCount > 0 &&
|
||||||
`
|
iconWithLabel(icons.star, totalStars, "stargazers");
|
||||||
<svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16">
|
|
||||||
${icons.star}
|
|
||||||
</svg>
|
|
||||||
<text data-testid="stargazers" class="gray" x="25">${totalStars}</text>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const svgForks =
|
const svgForks =
|
||||||
forkCount > 0 &&
|
forkCount > 0 && iconWithLabel(icons.fork, totalForks, "forkcount");
|
||||||
`
|
|
||||||
<svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16">
|
|
||||||
${icons.fork}
|
|
||||||
</svg>
|
|
||||||
<text data-testid="forkcount" class="gray" x="25">${totalForks}</text>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return `
|
const starAndForkCount = FlexLayout({
|
||||||
<svg version="1.1" width="400" height="${height}" viewBox="0 0 400 ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
items: [svgStars, svgForks],
|
||||||
<style>
|
gap: 65,
|
||||||
.header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${titleColor} }
|
}).join("");
|
||||||
.description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
|
||||||
.gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
|
||||||
.icon { fill: ${iconColor} }
|
|
||||||
.badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; }
|
|
||||||
.badge rect { opacity: 0.2 }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<rect data-testid="card-bg" x="0.5" y="0.5" width="399" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>
|
const card = new Card({
|
||||||
<svg class="icon" x="25" y="25" viewBox="0 0 16 16" version="1.1" width="16" height="16">
|
title: header,
|
||||||
${icons.contribs}
|
titlePrefixIcon: icons.contribs,
|
||||||
</svg>
|
width: 400,
|
||||||
|
height,
|
||||||
|
colors: {
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
iconColor,
|
||||||
|
bgColor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
<text x="50" y="38" class="header">${header}</text>
|
card.disableAnimations();
|
||||||
|
card.setHideBorder(false);
|
||||||
|
card.setHideTitle(false);
|
||||||
|
card.setCSS(`
|
||||||
|
.description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
||||||
|
.gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
||||||
|
.icon { fill: ${iconColor} }
|
||||||
|
.badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; }
|
||||||
|
.badge rect { opacity: 0.2 }
|
||||||
|
`);
|
||||||
|
|
||||||
${
|
return card.render(`
|
||||||
isTemplate
|
${
|
||||||
? getBadgeSVG("Template")
|
isTemplate
|
||||||
: isArchived
|
? getBadgeSVG("Template")
|
||||||
? getBadgeSVG("Archived")
|
: isArchived
|
||||||
: ""
|
? getBadgeSVG("Archived")
|
||||||
}
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
<text class="description" x="25" y="50">
|
<text class="description" x="25" y="-5">
|
||||||
${multiLineDescription
|
${multiLineDescription
|
||||||
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
|
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
|
||||||
.join("")}
|
.join("")}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<g transform="translate(0, ${height - 20})">
|
<g transform="translate(0, ${height - 75})">
|
||||||
${svgLanguage}
|
${svgLanguage}
|
||||||
|
|
||||||
<g
|
<g
|
||||||
data-testid="star-fork-group"
|
data-testid="star-fork-group"
|
||||||
transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 0)"
|
transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 0)"
|
||||||
>
|
>
|
||||||
${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")}
|
${starAndForkCount}
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</g>
|
||||||
`;
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = renderRepoCard;
|
module.exports = renderRepoCard;
|
||||||
|
@ -4,8 +4,9 @@ const {
|
|||||||
FlexLayout,
|
FlexLayout,
|
||||||
encodeHTML,
|
encodeHTML,
|
||||||
} = require("../src/utils");
|
} = require("../src/utils");
|
||||||
const getStyles = require("./getStyles");
|
const { getStyles } = require("./getStyles");
|
||||||
const icons = require("./icons");
|
const icons = require("./icons");
|
||||||
|
const Card = require("./Card");
|
||||||
|
|
||||||
const createTextNode = ({ icon, label, value, id, index, showIcons }) => {
|
const createTextNode = ({ icon, label, value, id, index, showIcons }) => {
|
||||||
const kValue = kFormatter(value);
|
const kValue = kFormatter(value);
|
||||||
@ -52,7 +53,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
theme = "default",
|
theme = "default",
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const lheight = parseInt(line_height);
|
const lheight = parseInt(line_height, 10);
|
||||||
|
|
||||||
// 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({
|
||||||
@ -116,44 +117,11 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
hide_rank ? 0 : 150
|
hide_rank ? 0 : 150
|
||||||
);
|
);
|
||||||
|
|
||||||
// the better user's score the the rank will be closer to zero so
|
|
||||||
// subtracting 100 to get the progress in 100%
|
|
||||||
const progress = 100 - rank.score;
|
|
||||||
|
|
||||||
const styles = getStyles({
|
|
||||||
titleColor,
|
|
||||||
textColor,
|
|
||||||
iconColor,
|
|
||||||
show_icons,
|
|
||||||
progress,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Conditionally rendered elements
|
// Conditionally rendered elements
|
||||||
|
|
||||||
const apostrophe = ["x", "s"].includes(name.slice(-1)) ? "" : "s";
|
|
||||||
const title = hide_title
|
|
||||||
? ""
|
|
||||||
: `<text x="25" y="35" class="header">${encodeHTML(name)}'${apostrophe} GitHub Stats</text>`;
|
|
||||||
|
|
||||||
const border = `
|
|
||||||
<rect
|
|
||||||
data-testid="card-bg"
|
|
||||||
x="0.5"
|
|
||||||
y="0.5"
|
|
||||||
width="494"
|
|
||||||
height="99%"
|
|
||||||
rx="4.5"
|
|
||||||
fill="${bgColor}"
|
|
||||||
stroke="#E4E2E2"
|
|
||||||
stroke-opacity="${hide_border ? 0 : 1}"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const rankCircle = hide_rank
|
const rankCircle = hide_rank
|
||||||
? ""
|
? ""
|
||||||
: `<g data-testid="rank-circle" transform="translate(400, ${
|
: `<g data-testid="rank-circle"
|
||||||
height / 1.85
|
transform="translate(400, ${height / 2 - 50})">
|
||||||
})">
|
|
||||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||||
<g class="rank-text">
|
<g class="rank-text">
|
||||||
@ -169,34 +137,45 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
</g>
|
</g>
|
||||||
</g>`;
|
</g>`;
|
||||||
|
|
||||||
if (hide_title) {
|
// the better user's score the the rank will be closer to zero so
|
||||||
height -= 30;
|
// subtracting 100 to get the progress in 100%
|
||||||
}
|
const progress = 100 - rank.score;
|
||||||
|
const cssStyles = getStyles({
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
iconColor,
|
||||||
|
show_icons,
|
||||||
|
progress,
|
||||||
|
});
|
||||||
|
|
||||||
return `
|
const apostrophe = ["x", "s"].includes(name.slice(-1)) ? "" : "s";
|
||||||
<svg width="495" height="${height}" viewBox="0 0 495 ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
const card = new Card({
|
||||||
<style>
|
title: `${encodeHTML(name)}'${apostrophe} GitHub Stats`,
|
||||||
${styles}
|
width: 495,
|
||||||
</style>
|
height,
|
||||||
|
colors: {
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
iconColor,
|
||||||
|
bgColor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
${border}
|
card.setHideBorder(hide_border);
|
||||||
${title}
|
card.setHideTitle(hide_title);
|
||||||
|
card.setCSS(cssStyles);
|
||||||
|
|
||||||
<g data-testid="card-body-content" transform="translate(0, ${
|
return card.render(`
|
||||||
hide_title ? -30 : 0
|
${rankCircle}
|
||||||
})">
|
|
||||||
${rankCircle}
|
|
||||||
|
|
||||||
<svg x="0" y="55">
|
<svg x="0" y="0">
|
||||||
${FlexLayout({
|
${FlexLayout({
|
||||||
items: statItems,
|
items: statItems,
|
||||||
gap: lheight,
|
gap: lheight,
|
||||||
direction: "column",
|
direction: "column",
|
||||||
}).join("")}
|
}).join("")}
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = renderStatsCard;
|
module.exports = renderStatsCard;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { getCardColors, FlexLayout, clampValue } = require("../src/utils");
|
const { getCardColors, FlexLayout, clampValue } = require("../src/utils");
|
||||||
|
const Card = require("./Card");
|
||||||
|
|
||||||
const createProgressNode = ({ width, color, name, progress }) => {
|
const createProgressNode = ({ width, color, name, progress }) => {
|
||||||
const paddingRight = 95;
|
const paddingRight = 95;
|
||||||
@ -63,6 +64,7 @@ const lowercaseTrim = (name) => name.toLowerCase().trim();
|
|||||||
const renderTopLanguages = (topLangs, options = {}) => {
|
const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
const {
|
const {
|
||||||
hide_title,
|
hide_title,
|
||||||
|
hide_border,
|
||||||
card_width,
|
card_width,
|
||||||
title_color,
|
title_color,
|
||||||
text_color,
|
text_color,
|
||||||
@ -170,29 +172,29 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hide_title) {
|
const card = new Card({
|
||||||
height -= 30;
|
title: "Most Used Languages",
|
||||||
}
|
width,
|
||||||
|
height,
|
||||||
|
colors: {
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
bgColor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return `
|
card.disableAnimations();
|
||||||
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
card.setHideBorder(hide_border);
|
||||||
<style>
|
card.setHideTitle(hide_title);
|
||||||
.header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${titleColor} }
|
card.setCSS(`
|
||||||
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
||||||
</style>
|
`);
|
||||||
<rect data-testid="card-bg" x="0.5" y="0.5" width="99.7%" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>
|
|
||||||
|
|
||||||
${
|
return card.render(`
|
||||||
hide_title
|
<svg data-testid="lang-items" x="25">
|
||||||
? ""
|
${finalLayout}
|
||||||
: `<text data-testid="header" x="25" y="35" class="header">Most Used Languages</text>`
|
|
||||||
}
|
|
||||||
|
|
||||||
<svg data-testid="lang-items" x="25" y="${hide_title ? 25 : 55}">
|
|
||||||
${finalLayout}
|
|
||||||
</svg>
|
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = renderTopLanguages;
|
module.exports = renderTopLanguages;
|
||||||
|
136
tests/card.test.js
Normal file
136
tests/card.test.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
require("@testing-library/jest-dom");
|
||||||
|
const cssToObject = require("css-to-object");
|
||||||
|
const Card = require("../src/Card");
|
||||||
|
const icons = require("../src/icons");
|
||||||
|
const { getCardColors } = require("../src/utils");
|
||||||
|
const { queryByTestId } = require("@testing-library/dom");
|
||||||
|
|
||||||
|
describe("Card", () => {
|
||||||
|
it("should hide border", () => {
|
||||||
|
const card = new Card({});
|
||||||
|
card.setHideBorder(true);
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
|
||||||
|
"stroke-opacity",
|
||||||
|
"0"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not hide border", () => {
|
||||||
|
const card = new Card({});
|
||||||
|
card.setHideBorder(false);
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
|
||||||
|
"stroke-opacity",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hide title", () => {
|
||||||
|
const card = new Card({});
|
||||||
|
card.setHideTitle(true);
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(queryByTestId(document.body, "card-title")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not hide title", () => {
|
||||||
|
const card = new Card({});
|
||||||
|
card.setHideTitle(false);
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(queryByTestId(document.body, "card-title")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("title should have prefix icon", () => {
|
||||||
|
const card = new Card({ title: "ok", titlePrefixIcon: icons.contribs });
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(document.getElementsByClassName("icon")[0]).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("title should not have prefix icon", () => {
|
||||||
|
const card = new Card({ title: "ok" });
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(document.getElementsByClassName("icon")[0]).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have proper height, width", () => {
|
||||||
|
const card = new Card({ height: 200, width: 200, title: "ok" });
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(document.getElementsByTagName("svg")[0]).toHaveAttribute(
|
||||||
|
"height",
|
||||||
|
"200"
|
||||||
|
);
|
||||||
|
expect(document.getElementsByTagName("svg")[0]).toHaveAttribute(
|
||||||
|
"height",
|
||||||
|
"200"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have less height after title is hidden", () => {
|
||||||
|
const card = new Card({ height: 200, title: "ok" });
|
||||||
|
card.setHideTitle(true);
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(document.getElementsByTagName("svg")[0]).toHaveAttribute(
|
||||||
|
"height",
|
||||||
|
"170"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("main-card-body should have proper when title is visible", () => {
|
||||||
|
const card = new Card({ height: 200 });
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(queryByTestId(document.body, "main-card-body")).toHaveAttribute(
|
||||||
|
"transform",
|
||||||
|
"translate(0, 55)"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("main-card-body should have proper position after title is hidden", () => {
|
||||||
|
const card = new Card({ height: 200 });
|
||||||
|
card.setHideTitle(true);
|
||||||
|
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
expect(queryByTestId(document.body, "main-card-body")).toHaveAttribute(
|
||||||
|
"transform",
|
||||||
|
"translate(0, 25)"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render with correct colors", () => {
|
||||||
|
// returns theme based colors with proper overrides and defaults
|
||||||
|
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
|
||||||
|
title_color: "f00",
|
||||||
|
icon_color: "0f0",
|
||||||
|
text_color: "00f",
|
||||||
|
bg_color: "fff",
|
||||||
|
theme: "default",
|
||||||
|
});
|
||||||
|
|
||||||
|
const card = new Card({
|
||||||
|
height: 200,
|
||||||
|
colors: {
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
iconColor,
|
||||||
|
bgColor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
document.body.innerHTML = card.render(``);
|
||||||
|
|
||||||
|
const styleTag = document.querySelector("style");
|
||||||
|
const stylesObject = cssToObject(styleTag.innerHTML);
|
||||||
|
const headerClassStyles = stylesObject[".header"];
|
||||||
|
|
||||||
|
expect(headerClassStyles.fill).toBe("#f00");
|
||||||
|
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
|
||||||
|
"fill",
|
||||||
|
"#fff"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -69,20 +69,6 @@ describe("Test renderStatsCard", () => {
|
|||||||
expect(queryByTestId(document.body, "contribs")).toBeNull();
|
expect(queryByTestId(document.body, "contribs")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide_border", () => {
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, { hide_border: true });
|
|
||||||
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
|
|
||||||
"stroke-opacity",
|
|
||||||
"0"
|
|
||||||
);
|
|
||||||
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, { hide_border: false });
|
|
||||||
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
|
|
||||||
"stroke-opacity",
|
|
||||||
"1"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should hide_rank", () => {
|
it("should hide_rank", () => {
|
||||||
document.body.innerHTML = renderStatsCard(stats, { hide_rank: true });
|
document.body.innerHTML = renderStatsCard(stats, { hide_rank: true });
|
||||||
|
|
||||||
@ -202,35 +188,6 @@ describe("Test renderStatsCard", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide the title", () => {
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
|
||||||
hide_title: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(document.getElementsByClassName("header")[0]).toBeUndefined();
|
|
||||||
expect(document.getElementsByTagName("svg")[0]).toHaveAttribute(
|
|
||||||
"height",
|
|
||||||
"165"
|
|
||||||
);
|
|
||||||
expect(queryByTestId(document.body, "card-body-content")).toHaveAttribute(
|
|
||||||
"transform",
|
|
||||||
"translate(0, -30)"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not hide the title", () => {
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {});
|
|
||||||
|
|
||||||
expect(document.getElementsByClassName("header")[0]).toBeDefined();
|
|
||||||
expect(document.getElementsByTagName("svg")[0]).toHaveAttribute(
|
|
||||||
"height",
|
|
||||||
"195"
|
|
||||||
);
|
|
||||||
expect(queryByTestId(document.body, "card-body-content")).toHaveAttribute(
|
|
||||||
"transform",
|
|
||||||
"translate(0, 0)"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render icons correctly", () => {
|
it("should render icons correctly", () => {
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
document.body.innerHTML = renderStatsCard(stats, {
|
||||||
|
@ -98,25 +98,6 @@ describe("Test renderTopLanguages", () => {
|
|||||||
expect(document.querySelector("svg")).toHaveAttribute("height", "245");
|
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", () => {
|
it("should render with custom width set", () => {
|
||||||
document.body.innerHTML = renderTopLanguages(langs, {});
|
document.body.innerHTML = renderTopLanguages(langs, {});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user