Compare commits

..
Author SHA1 Message Date
anuraghazra a8d7e3b5f6 tests: added more tests for themes
Test / build (push) Canceled after 0s
2020-07-21 13:12:15 +05:30
anuraghazra b1a7aab7fe docs: update docs 2020-07-19 20:29:35 +05:30
anuraghazra 9b8faab350 docs: added theming docs 2020-07-19 20:25:47 +05:30
anuraghazra 1417432264 feat: added inbuilt themes 2020-07-19 20:05:57 +05:30
27 changed files with 98 additions and 929 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://www.paypal.me/anuraghazra", "https://www.buymeacoffee.com/anuraghazra"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ["https://www.paypal.me/anuraghazra"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
-3
View File
@@ -34,6 +34,3 @@ jobs:
run: |
npm install
npm run test
- name: Code Coverage
uses: codecov/codecov-action@v1
-3
View File
@@ -2,6 +2,3 @@
.env
node_modules
package-lock.json
*.lock
.vscode/
coverage
+1 -2
View File
@@ -1,3 +1,2 @@
.env
package-lock.json
coverage
package-lock.json
+1 -2
View File
@@ -2,7 +2,6 @@ require("dotenv").config();
const {
renderError,
parseBoolean,
parseArray,
clampValue,
CONSTANTS,
} = require("../src/utils");
@@ -45,7 +44,7 @@ module.exports = async (req, res) => {
res.send(
renderStatsCard(stats, {
hide: parseArray(hide),
hide: JSON.parse(hide || "[]"),
show_icons: parseBoolean(show_icons),
hide_title: parseBoolean(hide_title),
hide_border: parseBoolean(hide_border),
+2 -3
View File
@@ -4,7 +4,6 @@ const {
parseBoolean,
clampValue,
CONSTANTS,
logger,
} = require("../src/utils");
const fetchRepo = require("../src/fetchRepo");
const renderRepoCard = require("../src/renderRepoCard");
@@ -29,7 +28,7 @@ module.exports = async (req, res) => {
try {
repoData = await fetchRepo(username, repo);
} catch (err) {
logger.error(err);
console.log(err);
return res.send(renderError(err.message));
}
@@ -53,7 +52,7 @@ module.exports = async (req, res) => {
}
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
res.send(
renderRepoCard(repoData, {
title_color,
-54
View File
@@ -1,54 +0,0 @@
require("dotenv").config();
const {
renderError,
clampValue,
parseBoolean,
parseArray,
CONSTANTS,
} = require("../src/utils");
const fetchTopLanguages = require("../src/fetchTopLanguages");
const renderTopLanguages = require("../src/renderTopLanguages");
module.exports = async (req, res) => {
const {
username,
hide,
hide_title,
card_width,
title_color,
text_color,
bg_color,
theme,
cache_seconds,
} = req.query;
let topLangs;
res.setHeader("Content-Type", "image/svg+xml");
try {
topLangs = await fetchTopLanguages(username);
} catch (err) {
return res.send(renderError(err.message));
}
const cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.THIRTY_MINUTES, 10),
CONSTANTS.THIRTY_MINUTES,
CONSTANTS.ONE_DAY
);
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
res.send(
renderTopLanguages(topLangs, {
theme,
hide_title: parseBoolean(hide_title),
card_width: parseInt(card_width, 10),
hide: parseArray(hide),
title_color,
text_color,
bg_color,
theme,
})
);
};
-13
View File
@@ -1,13 +0,0 @@
codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default:
threshold: 5
patch: false
+1 -7
View File
@@ -4,7 +4,7 @@
"description": "Dynamically generate stats for your github readmes",
"main": "index.js",
"scripts": {
"test": "jest --coverage",
"test": "jest",
"test:watch": "jest --watch"
},
"author": "Anurag Hazra",
@@ -15,15 +15,9 @@
"axios": "^0.19.2",
"axios-mock-adapter": "^1.18.1",
"css-to-object": "^1.1.0",
"husky": "^4.2.5",
"jest": "^26.1.0"
},
"dependencies": {
"dotenv": "^8.2.0"
},
"husky": {
"hooks": {
"pre-commit": "npm test"
}
}
}
+50 -87
View File
@@ -8,9 +8,6 @@
<a href="https://github.com/anuraghazra/github-readme-stats/actions">
<img alt="Tests Passing" src="https://github.com/anuraghazra/github-readme-stats/workflows/Test/badge.svg" />
</a>
<a href="https://codecov.io/gh/anuraghazra/github-readme-stats">
<img src="https://codecov.io/gh/anuraghazra/github-readme-stats/branch/master/graph/badge.svg" />
</a>
<a href="https://github.com/anuraghazra/github-readme-stats/issues">
<img alt="Issues" src="https://img.shields.io/github/issues/anuraghazra/github-readme-stats?color=0088ff" />
</a>
@@ -38,7 +35,6 @@
- [GitHub Stats Card](#github-stats-card)
- [GitHub Extra Pins](#github-extra-pins)
- [Top Languages Card](#top-languages-card)
- [Themes](#themes)
- [Customization](#customization)
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
@@ -57,12 +53,12 @@ _Note: Ranks are calculated based on user's stats, see [src/calculateRank.js](./
### Hiding individual stats
To hide any specific stats, you can pass a query parameter `?hide=` with comma seperated values.
To hide any specific stats, you can pass a query parameter `?hide=` with an array of items you wanna hide.
> Options: `&hide=stars,commits,prs,issues,contribs`
> Options: `&hide=["stars","commits","prs","issues","contribs"]`
```md
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=contribs,prs])
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=["contribs","prs"])
```
### Showing icons
@@ -97,24 +93,55 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
Customization Options:
| Option | type | description | Stats Card (default) | Repo Card (default) | Top Lang Card (default) |
| ------------- | --------- | ------------------------------------ | -------------------- | ------------------- | ----------------------- |
| title_color | hex color | title color | 2f80ed | 2f80ed | 2f80ed |
| text_color | hex color | body color | 333 | 333 | 333 |
| icon_color | hex color | icon color | 4c71f2 | 586069 | 586069 |
| bg_color | hex color | card bg color | FFFEFE | FFFEFE | FFFEFE |
| line_height | number | control the line-height between text | 30 | N/A | N/A |
| hide | CSV | hides the items specified | undefined | N/A | undefined |
| hide_rank | boolean | hides the ranking | false | N/A | N/A |
| hide_title | boolean | hides the stats title | false | N/A | false |
| hide_border | boolean | hides the stats card border | false | N/A | N/A |
| show_owner | boolean | shows owner name in repo card | N/A | false | N/A |
| show_icons | boolean | shows icons | false | N/A | N/A |
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | 'default |
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | '1800' |
| Option | type | description | Stats Card (default) | Repo Card (default) |
| ------------- | --------- | ------------------------------------ | -------------------- | ------------------- |
| title_color | hex color | title color | 2f80ed | 2f80ed |
| text_color | hex color | body color | 333 | 333 |
| icon_color | hex color | icon color | 4c71f2 | 586069 |
| bg_color | hex color | card bg color | FFFEFE | FFFEFE |
| line_height | number | control the line-height between text | 30 | N/A |
| hide_rank | boolean | hides the ranking | false | N/A |
| hide_title | boolean | hides the stats title | false | N/A |
| hide_border | boolean | hides the stats card border | false | N/A |
| show_owner | boolean | shows owner name in repo card | N/A | false |
| show_icons | boolean | shows icons | false | N/A |
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' |
| cache_seconds | number | manually set custom cache control | 1800 | 1800 |
> 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
---
### Demo
- Default
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra)
- Hiding specific stats
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=["contribs","issues"])
- Showing icons
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=["issues"]&show_icons=true)
- Themes
Choose from any of the [default themes](#themes)
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical)
- Customizing stats card
![Anurag's github stats](https://github-readme-stats.vercel.app/api/?username=anuraghazra&show_icons=true&title_color=fff&icon_color=79ff97&text_color=9f9f9f&bg_color=151515)
- Customizing repo card
![Customized Card](https://github-readme-stats.vercel.app/api/pin?username=anuraghazra&repo=github-readme-stats&title_color=fff&icon_color=f9f9f9&text_color=9f9f9f&bg_color=151515)
---
# GitHub Extra Pins
GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile.
@@ -139,70 +166,6 @@ Use [show_owner](#customization) variable to include the repo's owner username
[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&show_owner=true)](https://github.com/anuraghazra/github-readme-stats)
# Top Languages Card
Top languages card shows github user's top langauges which has been mostly used.
_NOTE: Top languages does not indicate my skill level or something like that, it's a github metric of which languages i have the most code on github, it's a new feature of github-readme-stats_
### Usage
Copy-paste this code into your readme and change the links.
Endpoint: `api/top-langs?username=anuraghazra`
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
```
### Hide individual languages
You can use `?hide=language1,language2` parameter to hide individual languages.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide=javascript,html])](https://github.com/anuraghazra/github-readme-stats)
```
### Demo
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
---
### All Demos
- Default
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra)
- Hiding specific stats
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=contribs,issues])
- Showing icons
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=issues&show_icons=true)
- Themes
Choose from any of the [default themes](#themes)
![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical)
- Customizing stats card
![Anurag's github stats](https://github-readme-stats.vercel.app/api/?username=anuraghazra&show_icons=true&title_color=fff&icon_color=79ff97&text_color=9f9f9f&bg_color=151515)
- Customizing repo card
![Customized Card](https://github-readme-stats.vercel.app/api/pin?username=anuraghazra&repo=github-readme-stats&title_color=fff&icon_color=f9f9f9&text_color=9f9f9f&bg_color=151515)
- Top languages
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
---
### Quick Tip (Align The Repo Cards)
You usually won't be able to layout the images side by side. To do that you can use this approach:
@@ -240,7 +203,7 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we
1. Select `Import Git Repository`
![](https://files.catbox.moe/pqub9q.png)
1. Select root and keep everything as is, just add your environment variable named PAT_1 (as shown), which will contain a personal access token (PAT), which you can easily create [here](https://github.com/settings/tokens/new) (leave everything as is, just name it something, it can be anything you want)
![](https://files.catbox.moe/0ez4g7.png)
![](https://files.catbox.moe/caem5b.png)
1. Click deploy, and you're good to go. See your domains to use the API!
</details>
-1
View File
@@ -10,7 +10,6 @@ const fetcher = (variables, token) => {
nameWithOwner
isPrivate
isArchived
isTemplate
stargazers {
totalCount
}
+2 -2
View File
@@ -1,4 +1,4 @@
const { request, logger } = require("./utils");
const { request } = require("./utils");
const retryer = require("./retryer");
const calculateRank = require("./calculateRank");
require("dotenv").config();
@@ -61,7 +61,7 @@ async function fetchStats(username) {
let res = await retryer(fetcher, { login: username });
if (res.data.errors) {
logger.error(res.data.errors);
console.log(res.data.errors);
throw Error(res.data.errors[0].message || "Could not fetch user");
}
-84
View File
@@ -1,84 +0,0 @@
const { request, logger } = require("./utils");
const retryer = require("./retryer");
require("dotenv").config();
const fetcher = (variables, token) => {
return request(
{
query: `
query userInfo($login: String!) {
user(login: $login) {
repositories(isFork: false, first: 100) {
nodes {
languages(first: 1) {
edges {
size
node {
color
name
}
}
}
}
}
}
}
`,
variables,
},
{
Authorization: `bearer ${token}`,
}
);
};
async function fetchTopLanguages(username) {
if (!username) throw Error("Invalid username");
let res = await retryer(fetcher, { login: username });
if (res.data.errors) {
logger.error(res.data.errors);
throw Error(res.data.errors[0].message || "Could not fetch user");
}
let repoNodes = res.data.data.user.repositories.nodes;
// TODO: perf improvement
repoNodes = repoNodes
.filter((node) => {
return node.languages.edges.length > 0;
})
.sort((a, b) => {
return b.languages.edges[0].size - a.languages.edges[0].size;
})
.map((node) => {
return node.languages.edges[0];
})
.reduce((acc, prev) => {
let langSize = prev.size;
if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) {
langSize = prev.size + acc[prev.node.name].size;
}
return {
...acc,
[prev.node.name]: {
name: prev.node.name,
color: prev.node.color,
size: langSize,
},
};
}, {});
const topLangs = Object.keys(repoNodes)
.slice(0, 5)
.reduce((result, key) => {
result[key] = repoNodes[key];
return result;
}, {});
return topLangs;
}
module.exports = fetchTopLanguages;
+18 -35
View File
@@ -14,7 +14,6 @@ const renderRepoCard = (repo, options = {}) => {
primaryLanguage,
stargazers,
isArchived,
isTemplate,
forkCount,
} = repo;
const {
@@ -27,8 +26,8 @@ const renderRepoCard = (repo, options = {}) => {
} = options;
const header = show_owner ? nameWithOwner : name;
const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
const langName = primaryLanguage ? primaryLanguage.name : "Unspecified";
const langColor = primaryLanguage ? primaryLanguage.color : "#333";
const height = 120;
const shiftText = langName.length > 15 ? 0 : 30;
@@ -50,30 +49,22 @@ const renderRepoCard = (repo, options = {}) => {
const totalStars = kFormatter(stargazers.totalCount);
const totalForks = kFormatter(forkCount);
const getBadgeSVG = (label) => `
<g data-testid="badge" class="badge" transform="translate(320, 38)">
<rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
<text
x="23" y="-5"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
fill="${textColor}"
>
${label}
</text>
</g>
`;
const svgLanguage = primaryLanguage
const archiveBadge = isArchived
? `
<g data-testid="primary-lang" transform="translate(30, 100)">
<circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
<text data-testid="lang-name" class="gray" x="15">${langName}</text>
<g data-testid="archive-badge" class="archive-badge" transform="translate(320, 38)">
<rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
<text fill="${textColor}">Archived</text>
</g>
`
: "";
const svgLanguage = `
<g transform="translate(30, 100)">
<circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
<text data-testid="lang" class="gray" x="15">${langName}</text>
</g>
`;
const svgStars =
stargazers.totalCount > 0 &&
`
@@ -99,30 +90,22 @@ const renderRepoCard = (repo, options = {}) => {
.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 }
.archive-badge { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; }
.archive-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"/>
<svg class="icon" x="25" y="25" viewBox="0 0 16 16" version="1.1" width="16" height="16">
${icons.contribs}
</svg>
<text x="50" y="38" class="header">${header}</text>
${archiveBadge}
${
isTemplate
? getBadgeSVG("Template")
: isArchived
? getBadgeSVG("Archived")
: ""
}
<text x="50" y="38" class="header">${header}</text>
<text class="description" x="25" y="70">${encodeHTML(desc)}</text>
${svgLanguage}
<g transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 100)">
<g transform="translate(${155 - shiftText}, 100)">
${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")}
</g>
-108
View File
@@ -1,108 +0,0 @@
const { getCardColors, FlexLayout, clampValue } = require("../src/utils");
const createProgressNode = ({ width, color, name, progress }) => {
const paddingRight = 95;
const progressTextX = width - paddingRight + 10;
const progressWidth = width - paddingRight;
const progressPercentage = clampValue(progress, 2, 100);
return `
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
<svg width="${progressWidth}">
<rect rx="5" ry="5" x="0" y="25" width="${progressWidth}" height="8" fill="#ddd"></rect>
<rect
height="8"
fill="${color}"
rx="5" ry="5" x="0" y="25"
data-testid="lang-progress"
width="${progressPercentage}%"
>
</rect>
</svg>
`;
};
const lowercaseTrim = (name) => name.toLowerCase().trim();
const renderTopLanguages = (topLangs, options = {}) => {
const {
hide_title,
card_width,
title_color,
text_color,
bg_color,
hide,
theme,
} = options;
let langs = Object.values(topLangs);
let langsToHide = {};
// populate langsToHide map for quick lookup
// while filtering out
if (hide) {
hide.forEach((langName) => {
langsToHide[lowercaseTrim(langName)] = true;
});
}
// filter out langauges to be hidden
langs = langs
.sort((a, b) => b.size - a.size)
.filter((lang) => {
return !langsToHide[lowercaseTrim(lang.name)];
});
const totalSize = langs.reduce((acc, curr) => {
return acc + curr.size;
}, 0);
// returns theme based colors with proper overrides and defaults
const { titleColor, textColor, bgColor } = getCardColors({
title_color,
text_color,
bg_color,
theme,
});
const width = isNaN(card_width) ? 300 : card_width;
let height = 45 + (langs.length + 1) * 40;
if (hide_title) {
height -= 30;
}
return `
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${titleColor} }
.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"/>
${
hide_title
? ""
: `<text data-testid="header" x="25" y="35" class="header">Top Languages</text>`
}
<svg data-testid="lang-items" x="25" y="${hide_title ? 25 : 55}">
${FlexLayout({
items: langs.map((lang) => {
return createProgressNode({
width: width,
name: lang.name,
color: lang.color || "#858585",
progress: ((lang.size / totalSize) * 100).toFixed(2),
});
}),
gap: 40,
direction: "column",
}).join("")}
</svg>
</svg>
`;
};
module.exports = renderTopLanguages;
+3 -5
View File
@@ -1,11 +1,9 @@
const { logger } = require("./utils");
const retryer = async (fetcher, variables, retries = 0) => {
if (retries > 7) {
throw new Error("Maximum retries exceeded");
}
try {
logger.log(`Trying PAT_${retries + 1}`);
console.log(`Trying PAT_${retries + 1}`);
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
let response = await fetcher(
@@ -20,7 +18,7 @@ const retryer = async (fetcher, variables, retries = 0) => {
// if rate limit is hit increase the RETRIES and recursively call the retryer
// with username, and current RETRIES
if (isRateExceeded) {
logger.log(`PAT_${retries + 1} Failed`);
console.log(`PAT_${retries + 1} Failed`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
@@ -34,7 +32,7 @@ const retryer = async (fetcher, variables, retries = 0) => {
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
if (isBadCredential) {
logger.log(`PAT_${retries + 1} Failed`);
console.log(`PAT_${retries + 1} Failed`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
-12
View File
@@ -44,11 +44,6 @@ function parseBoolean(value) {
}
}
function parseArray(str) {
if (!str) return [];
return str.split(",");
}
function clampValue(number, min, max) {
return Math.max(min, Math.min(number, max));
}
@@ -121,11 +116,6 @@ function getCardColors({
return { titleColor, iconColor, textColor, bgColor };
}
const fn = () => {};
// return console instance based on the environment
const logger =
process.env.NODE_ENV !== "test" ? console : { log: fn, error: fn };
const CONSTANTS = {
THIRTY_MINUTES: 1800,
TWO_HOURS: 7200,
@@ -138,12 +128,10 @@ module.exports = {
encodeHTML,
isValidHexColor,
request,
parseArray,
parseBoolean,
fallbackColor,
FlexLayout,
getCardColors,
clampValue,
logger,
CONSTANTS,
};
+1 -1
View File
@@ -98,7 +98,7 @@ describe("Test /api/", () => {
const { req, res } = faker(
{
username: "anuraghazra",
hide: "issues,prs,contribs",
hide: `["issues","prs","contribs"]`,
show_icons: true,
hide_border: true,
line_height: 100,
-84
View File
@@ -1,84 +0,0 @@
require("@testing-library/jest-dom");
const axios = require("axios");
const MockAdapter = require("axios-mock-adapter");
const fetchTopLanguages = require("../src/fetchTopLanguages");
const mock = new MockAdapter(axios);
afterEach(() => {
mock.reset();
});
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 resolve to a User with the login of 'noname'.",
},
],
};
describe("FetchTopLanguages", () => {
it("should fetch correct language data", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
let repo = await fetchTopLanguages("anuraghazra");
expect(repo).toStrictEqual({
HTML: {
color: "#0f0",
name: "HTML",
size: 200,
},
javascript: {
color: "#0ff",
name: "javascript",
size: 200,
},
});
});
it("should throw error", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, error);
await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow(
"Could not resolve to a User with the login of 'noname'."
);
});
});
-1
View File
@@ -17,7 +17,6 @@ const data_repo = {
name: "TypeScript",
},
forkCount: 100,
isTemplate: false
},
};
+14 -50
View File
@@ -33,7 +33,7 @@ describe("Test renderRepoCard", () => {
);
expect(queryByTestId(document.body, "stargazers")).toHaveTextContent("38k");
expect(queryByTestId(document.body, "forkcount")).toHaveTextContent("100");
expect(queryByTestId(document.body, "lang-name")).toHaveTextContent(
expect(queryByTestId(document.body, "lang")).toHaveTextContent(
"TypeScript"
);
expect(queryByTestId(document.body, "lang-color")).toHaveAttribute(
@@ -82,7 +82,6 @@ describe("Test renderRepoCard", () => {
},
});
expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument();
expect(document.getElementsByTagName("g")[1]).toHaveAttribute(
"transform",
"translate(155, 100)"
@@ -103,30 +102,6 @@ describe("Test renderRepoCard", () => {
);
});
it("should hide language if primaryLanguage is null & fallback to correct values", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
primaryLanguage: null,
});
expect(queryByTestId(document.body, "primary-lang")).toBeNull();
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
primaryLanguage: { color: null, name: null },
});
expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument();
expect(queryByTestId(document.body, "lang-color")).toHaveAttribute(
"fill",
"#333"
);
expect(queryByTestId(document.body, "lang-name")).toHaveTextContent(
"Unspecified"
);
});
it("should render default colors properly", () => {
document.body.innerHTML = renderRepoCard(data_repo.repository);
@@ -242,6 +217,17 @@ describe("Test renderRepoCard", () => {
);
});
it("should render archive badge if repo is archived", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
isArchived: true,
});
expect(queryByTestId(document.body, "archive-badge")).toHaveTextContent(
"Archived"
);
});
it("should not render star count or fork count if either of the are zero", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
@@ -249,7 +235,7 @@ describe("Test renderRepoCard", () => {
});
expect(queryByTestId(document.body, "stargazers")).toBeNull();
expect(queryByTestId(document.body, "forkcount")).toBeInTheDocument();
expect(queryByTestId(document.body, "forkcount")).toBeDefined();
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
@@ -257,7 +243,7 @@ describe("Test renderRepoCard", () => {
forkCount: 0,
});
expect(queryByTestId(document.body, "stargazers")).toBeInTheDocument();
expect(queryByTestId(document.body, "stargazers")).toBeDefined();
expect(queryByTestId(document.body, "forkcount")).toBeNull();
document.body.innerHTML = renderRepoCard({
@@ -269,26 +255,4 @@ describe("Test renderRepoCard", () => {
expect(queryByTestId(document.body, "stargazers")).toBeNull();
expect(queryByTestId(document.body, "forkcount")).toBeNull();
});
it("should render badges", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
isArchived: true,
});
expect(queryByTestId(document.body, "badge")).toHaveTextContent("Archived");
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
isTemplate: true,
});
expect(queryByTestId(document.body, "badge")).toHaveTextContent("Template");
});
it("should not render template", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
});
expect(queryByTestId(document.body, "badge")).toBeNull();
});
});
+1 -1
View File
@@ -55,7 +55,7 @@ describe("Test renderStatsCard", () => {
it("should hide individual stats", () => {
document.body.innerHTML = renderStatsCard(stats, {
hide: ["issues", "prs", "contribs"],
hide: "['issues', 'prs', 'contribs']",
});
expect(
-210
View File
@@ -1,210 +0,0 @@
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 hide languages when hide is passed", () => {
document.body.innerHTML = renderTopLanguages(langs, {
hide: ["HTML"],
});
expect(queryAllByTestId(document.body, "lang-name")[0]).toBeInTheDocument(
"javascript"
);
expect(queryAllByTestId(document.body, "lang-name")[1]).toBeInTheDocument(
"css"
);
expect(queryAllByTestId(document.body, "lang-name")[2]).not.toBeDefined();
// multiple languages passed
document.body.innerHTML = renderTopLanguages(langs, {
hide: ["HTML","css"],
});
expect(queryAllByTestId(document.body, "lang-name")[0]).toBeInTheDocument(
"javascript"
);
expect(queryAllByTestId(document.body, "lang-name")[1]).not.toBeDefined();
});
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}`
);
});
});
});
+1 -2
View File
@@ -1,9 +1,8 @@
require("@testing-library/jest-dom");
const retryer = require("../src/retryer");
const { logger } = require("../src/utils");
const fetcher = jest.fn((variables, token) => {
logger.log(variables, token);
console.log(variables, token);
return new Promise((res, rej) => res({ data: "ok" }));
});
-142
View File
@@ -1,142 +0,0 @@
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));
});
});
+2 -8
View File
@@ -17,8 +17,7 @@ Use `?theme=THEME_NAME` parameter like so :-
| `default` ![default][default] | `radical` ![radical][radical] | `merko` ![merko][merko] |
| `gruvbox` ![gruvbox][gruvbox] | `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] |
| `cobalt` ![cobalt][cobalt] | `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] |
| `dracula` ![dracula][dracula] | `dark` ![dark][dark] | `prussian` ![prussian][prussian] |
| [Add your theme][add-theme] |||
| `dracula` ![dracula][dracula] | `dark` ![dark][dark] | [Add your theme][add-theme] |
## Repo Card
@@ -29,9 +28,7 @@ Use `?theme=THEME_NAME` parameter like so :-
| `default_repocard` ![default_repocard][default_repocard] | `radical` ![radical][radical_repocard] | `merko` ![merko][merko_repocard] |
| `gruvbox` ![gruvbox][gruvbox_repocard] | `tokyonight` ![tokyonight][tokyonight_repocard] | `onedark` ![onedark][onedark_repocard] |
| `cobalt` ![cobalt][cobalt_repocard] | `synthwave` ![synthwave][synthwave_repocard] | `highcontrast` ![highcontrast][highcontrast_repocard] |
| `dracula` ![dracula][dracula_repocard] | `dark` ![dark_repocard][dark_repocard] | `prussian` ![prussian_repocard][prussian_repocard] |
| [Add your theme][add-theme] |||
| `dracula` ![dracula][dracula_repocard] | `dark` ![dark_repocard][dark_repocard] | [Add your theme][add-theme] |
<!-- Repo Card Theme previews -->
@@ -47,7 +44,6 @@ Use `?theme=THEME_NAME` parameter like so :-
[synthwave_repocard]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=synthwave
[onedark_repocard]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=onedark
[highcontrast_repocard]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=highcontrast
[prussian_repocard]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=prussian
<!-- Stats Theme previews -->
@@ -62,7 +58,5 @@ Use `?theme=THEME_NAME` parameter like so :-
[synthwave]: https://github-readme-stats.vercel.app/api?username=anuraghazra&theme=synthwave&hide=["contribs","prs"]&cache_seconds=86400
[highcontrast]: https://github-readme-stats.vercel.app/api?username=anuraghazra&theme=highcontrast&hide=["contribs","prs"]&cache_seconds=86400
[dracula]: https://github-readme-stats.vercel.app/api?username=anuraghazra&theme=dracula&hide=["contribs","prs"]&cache_seconds=86400
[prussian]: https://github-readme-stats.vercel.app/api?username=anuraghazra&theme=prussian&hide=["contribs","prs"]&cache_seconds=86400
[add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js
-8
View File
@@ -1,8 +0,0 @@
{
"redirects": [
{
"source": "/",
"destination": "https://github.com/anuraghazra/github-readme-stats"
}
]
}