Merge pull request #128 from anuraghazra/top-langs
feat: added Top languages card
This commit is contained in:
commit
ad87824f3c
7 changed files with 734 additions and 39 deletions
53
api/top-langs.js
Normal file
53
api/top-langs.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
require("dotenv").config();
|
||||||
|
const {
|
||||||
|
renderError,
|
||||||
|
clampValue,
|
||||||
|
parseBoolean,
|
||||||
|
CONSTANTS,
|
||||||
|
} = require("../src/utils");
|
||||||
|
const fetchTopLanguages = require("../src/fetchTopLanguages");
|
||||||
|
const renderTopLanguages = require("../src/renderTopLanguages");
|
||||||
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
hide_langs_below,
|
||||||
|
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_langs_below: parseFloat(hide_langs_below, 10),
|
||||||
|
title_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
112
readme.md
112
readme.md
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
- [GitHub Stats Card](#github-stats-card)
|
- [GitHub Stats Card](#github-stats-card)
|
||||||
- [GitHub Extra Pins](#github-extra-pins)
|
- [GitHub Extra Pins](#github-extra-pins)
|
||||||
|
- [Top Languages Card](#top-languages-card)
|
||||||
- [Themes](#themes)
|
- [Themes](#themes)
|
||||||
- [Customization](#customization)
|
- [Customization](#customization)
|
||||||
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
|
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
|
||||||
|
@ -93,27 +94,80 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
|
||||||
|
|
||||||
Customization Options:
|
Customization Options:
|
||||||
|
|
||||||
| Option | type | description | Stats Card (default) | Repo Card (default) |
|
| Option | type | description | Stats Card (default) | Repo Card (default) | Top Lang Card (default) |
|
||||||
| ------------- | --------- | ------------------------------------ | -------------------- | ------------------- |
|
| ---------------- | --------- | ---------------------------------------------- | -------------------- | ------------------- | ----------------------- |
|
||||||
| title_color | hex color | title color | 2f80ed | 2f80ed |
|
| title_color | hex color | title color | 2f80ed | 2f80ed | 2f80ed |
|
||||||
| text_color | hex color | body color | 333 | 333 |
|
| text_color | hex color | body color | 333 | 333 | 333 |
|
||||||
| icon_color | hex color | icon color | 4c71f2 | 586069 |
|
| icon_color | hex color | icon color | 4c71f2 | 586069 | 586069 |
|
||||||
| bg_color | hex color | card bg color | FFFEFE | FFFEFE |
|
| bg_color | hex color | card bg color | FFFEFE | FFFEFE | FFFEFE |
|
||||||
| line_height | number | control the line-height between text | 30 | N/A |
|
| line_height | number | control the line-height between text | 30 | N/A | N/A |
|
||||||
| hide_rank | boolean | hides the ranking | false | N/A |
|
| hide_rank | boolean | hides the ranking | false | N/A | N/A |
|
||||||
| hide_title | boolean | hides the stats title | false | N/A |
|
| hide_title | boolean | hides the stats title | false | N/A | false |
|
||||||
| hide_border | boolean | hides the stats card border | false | N/A |
|
| 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 |
|
| show_owner | boolean | shows owner name in repo card | N/A | false | N/A |
|
||||||
| show_icons | boolean | shows icons | false | N/A |
|
| show_icons | boolean | shows icons | false | N/A | N/A |
|
||||||
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' |
|
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | 'default |
|
||||||
| cache_seconds | number | manually set custom cache control | 1800 | 1800 |
|
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | '1800' |
|
||||||
|
| hide_langs_below | number | hide langs below certain threshold (lang card) | N/A | N/A | undefined |
|
||||||
|
|
||||||
> 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
|
||||||
|
|
||||||
---
|
# GitHub Extra Pins
|
||||||
|
|
||||||
|
GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile.
|
||||||
|
|
||||||
|
Yey! You are no longer limited to 6 pinned repositories.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Copy-paste this code into your readme and change the links.
|
||||||
|
|
||||||
|
Endpoint: `api/pin?username=anuraghazra&repo=github-readme-stats`
|
||||||
|
|
||||||
|
```md
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
```
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
|
Use [show_owner](#customization) variable to include the repo's owner username
|
||||||
|
|
||||||
|
[](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
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hide languages below certain threshold
|
||||||
|
|
||||||
|
You can use `?hide_langs_below=NUMBER` parameter to hide languages below a specified threshold percentage.
|
||||||
|
|
||||||
|
```md
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Demo
|
||||||
|
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### All Demos
|
||||||
|
|
||||||
- Default
|
- Default
|
||||||
|
|
||||||

|

|
||||||
|
@ -140,32 +194,12 @@ Choose from any of the [default themes](#themes)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
- Top languages
|
||||||
|
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# GitHub Extra Pins
|
|
||||||
|
|
||||||
GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile.
|
|
||||||
|
|
||||||
Yey! You are no longer limited to 6 pinned repositories.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Copy-paste this code into your readme and change the links.
|
|
||||||
|
|
||||||
Endpoint: `api/pin?username=anuraghazra&repo=github-readme-stats`
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Demo
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
|
|
||||||
Use [show_owner](#customization) variable to include the repo's owner username
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
|
|
||||||
### Quick Tip (Align The Repo Cards)
|
### 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:
|
You usually won't be able to layout the images side by side. To do that you can use this approach:
|
||||||
|
|
84
src/fetchTopLanguages.js
Normal file
84
src/fetchTopLanguages.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
const { request } = 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) {
|
||||||
|
console.log(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;
|
96
src/renderTopLanguages.js
Normal file
96
src/renderTopLanguages.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
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 renderTopLanguages = (topLangs, options = {}) => {
|
||||||
|
const {
|
||||||
|
hide_title,
|
||||||
|
card_width,
|
||||||
|
title_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
hide_langs_below,
|
||||||
|
theme,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let langs = Object.values(topLangs);
|
||||||
|
|
||||||
|
const totalSize = langs.reduce((acc, curr) => {
|
||||||
|
return acc + curr.size;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// hide langs
|
||||||
|
langs = langs.filter((lang) => {
|
||||||
|
if (!hide_langs_below) return true;
|
||||||
|
return (lang.size / totalSize) * 100 > hide_langs_below;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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;
|
84
tests/fetchTopLanguages.test.js
Normal file
84
tests/fetchTopLanguages.test.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
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'."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
202
tests/renderTopLanguages.test.js
Normal file
202
tests/renderTopLanguages.test.js
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
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_langs_below", () => {
|
||||||
|
document.body.innerHTML = renderTopLanguages(langs, {
|
||||||
|
hide_langs_below: 34,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[0]).toBeInTheDocument(
|
||||||
|
"HTML"
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[1]).toBeInTheDocument(
|
||||||
|
"javascript"
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[2]).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}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
142
tests/top-langs.test.js
Normal file
142
tests/top-langs.test.js
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue