Compare commits

..
Author SHA1 Message Date
Rick Staa 147a5d9e2a refactor: update folder structure 2023-01-16 12:19:47 +01:00
rickstaa 2c970b29c1 docs: rename readme.md to README.md
This commit makes the README.md stand out more from the other files.
2023-01-16 12:18:03 +01:00
rickstaa 5c25f9c429 refactor: improve folder structure 2023-01-16 12:18:03 +01:00
16 changed files with 229 additions and 315 deletions
+5 -6
View File
@@ -93,7 +93,7 @@ Visit <https://indiafightscorona.giveindia.org> and make a small donation to hel
- [Language Card Exclusive Options](#language-card-exclusive-options) - [Language Card Exclusive Options](#language-card-exclusive-options)
- [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options) - [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options)
- [Deploy Yourself](#deploy-on-your-own-vercel-instance) - [Deploy Yourself](#deploy-on-your-own-vercel-instance)
- [Keep your fork up to date](#keep-your-fork-up-to-date) - [Keep your fork up to date](#keep-your-fork-up-to-date)
# GitHub Stats Card # GitHub Stats Card
@@ -259,12 +259,12 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_. Default: `fffefe` - `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_. Default: `fffefe`
- `hide_border` - Hides the card's border _(boolean)_. Default: `false` - `hide_border` - Hides the card's border _(boolean)_. Default: `false`
- `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme. - `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme.
- `cache_seconds` - set the cache header manually _(min: 14400, max: 86400)_. Default: `14400 seconds (4 hours)`. - `cache_seconds` - set the cache header manually _(min: 7200, max: 86400)_. Default: `14400 seconds (4 hours)`.
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`. - `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`.
- `border_radius` - Corner rounding on the card. Default: `4.5`. - `border_radius` - Corner rounding on the card. Default: `4.5`.
> **Warning** > **Warning**
> We use caching to decrease the load on our servers (see <https://github.com/anuraghazra/github-readme-stats/issues/1471#issuecomment-1271551425>). Our cards have a default cache of 4 hours (14400 seconds). Also, note that the cache is clamped to a minimum of 4 hours and a maximum of 24 hours. > We use caching to decrease the load on our servers (see https://github.com/anuraghazra/github-readme-stats/issues/1471#issuecomment-1271551425). Our cards have a default cache of 4 hours (14400 seconds). Also, note that the cache is clamped to a minimum of 4 hours and a maximum of 24 hours.
##### Gradient in bg_color ##### Gradient in bg_color
@@ -304,7 +304,6 @@ You can provide multiple comma-separated values in the bg_color option to render
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`. - `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`.
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
- `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`. - `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`.
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
> **Warning** > **Warning**
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) > Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
@@ -354,7 +353,7 @@ Use [show_owner](#customization) variable to include the repo's owner username
The top languages card shows a GitHub user's most frequently used top language. The top languages card shows a GitHub user's most frequently used top language.
> **Note** > **Note**
> Top Languages does not indicate my skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats. > Top Languages does not indicate my skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats._
### Usage ### Usage
@@ -498,7 +497,7 @@ By default, GitHub does not lay out the cards side by side. To do that, you can
## Deploy on your own Vercel instance ## Deploy on your own Vercel instance
#### :film_projector: [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107) #### [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
> **Warning** > **Warning**
> If you are on the [hobby (i.e. free)](https://vercel.com/pricing) Vercel plan, please make sure you change the `maxDuration` parameter in the [vercel.json](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json) file from `30` to `10` (see [#1416](https://github.com/anuraghazra/github-readme-stats/issues/1416#issuecomment-950275476) for more information). > If you are on the [hobby (i.e. free)](https://vercel.com/pricing) Vercel plan, please make sure you change the `maxDuration` parameter in the [vercel.json](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json) file from `30` to `10` (see [#1416](https://github.com/anuraghazra/github-readme-stats/issues/1416#issuecomment-950275476) for more information).
-2
View File
@@ -29,7 +29,6 @@ export default async (req, res) => {
locale, locale,
border_radius, border_radius,
border_color, border_color,
disable_animations,
} = req.query; } = req.query;
res.setHeader("Content-Type", "image/svg+xml"); res.setHeader("Content-Type", "image/svg+xml");
@@ -76,7 +75,6 @@ export default async (req, res) => {
border_radius, border_radius,
border_color, border_color,
locale: locale ? locale.toLowerCase() : null, locale: locale ? locale.toLowerCase() : null,
disable_animations: parseBoolean(disable_animations),
}), }),
); );
} catch (err) { } catch (err) {
+1 -1
View File
@@ -9,6 +9,6 @@ git config --global user.name "GitHub Readme Stats Bot"
git branch -d $BRANCH_NAME || true git branch -d $BRANCH_NAME || true
git checkout -b $BRANCH_NAME git checkout -b $BRANCH_NAME
git add --all git add --all
git commit --no-verify --message "docs(theme): Auto update theme readme" git commit --message "docs(theme): Auto update theme readme" || exit 0
git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git
git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME
+1 -1
View File
@@ -9,7 +9,7 @@ import {
kFormatter, kFormatter,
measureText, measureText,
} from "../common/utils.js"; } from "../common/utils.js";
import { getStyles } from "../getStyles.js"; import { getStyles } from "../common/getStyles.js";
import { statCardLocales } from "../translations.js"; import { statCardLocales } from "../translations.js";
/** /**
+21 -61
View File
@@ -39,53 +39,46 @@ const getLongestLang = (arr) =>
* Creates a node to display usage of a programming language in percentage * Creates a node to display usage of a programming language in percentage
* using text and a horizontal progress bar. * using text and a horizontal progress bar.
* *
* @param {object} props Function properties. * @param {object[]} props Function properties.
* @param {number} props.width The card width * @param {number} props.width The card width
* @param {string} props.name Name of the programming language. * @param {string} props.name Name of the programming language.
* @param {string} props.color Color of the programming language. * @param {string} props.color Color of the programming language.
* @param {string} props.progress Usage of the programming language in percentage. * @param {string} props.progress Usage of the programming language in percentage.
* @param {number} props.index Index of the programming language.
* @returns {string} Programming language SVG node. * @returns {string} Programming language SVG node.
*/ */
const createProgressTextNode = ({ width, color, name, progress, index }) => { const createProgressTextNode = ({ width, color, name, progress }) => {
const staggerDelay = (index + 3) * 150;
const paddingRight = 95; const paddingRight = 95;
const progressTextX = width - paddingRight + 10; const progressTextX = width - paddingRight + 10;
const progressWidth = width - paddingRight; const progressWidth = width - paddingRight;
return ` return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms"> <text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text> <text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text> ${createProgressNode({
${createProgressNode({ x: 0,
x: 0, y: 25,
y: 25, color,
color, width: progressWidth,
width: progressWidth, progress,
progress, progressBarBackgroundColor: "#ddd",
progressBarBackgroundColor: "#ddd", })}
delay: staggerDelay + 300,
})}
</g>
`; `;
}; };
/** /**
* Creates a text only node to display usage of a programming language in percentage. * Creates a text only node to display usage of a programming language in percentage.
* *
* @param {object} props Function properties. * @param {object[]} props Function properties.
* @param {Lang} props.lang Programming language object. * @param {Lang} props.lang Programming language object.
* @param {number} props.totalSize Total size of all languages. * @param {number} props.totalSize Total size of all languages.
* @param {number} props.index Index of the programming language.
* @returns {string} Compact layout programming language SVG node. * @returns {string} Compact layout programming language SVG node.
*/ */
const createCompactLangNode = ({ lang, totalSize, index }) => { const createCompactLangNode = ({ lang, totalSize }) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2); const percentage = ((lang.size / totalSize) * 100).toFixed(2);
const staggerDelay = (index + 3) * 150;
const color = lang.color || "#858585"; const color = lang.color || "#858585";
return ` return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms"> <g>
<circle cx="5" cy="6" r="5" fill="${color}" /> <circle cx="5" cy="6" r="5" fill="${color}" />
<text data-testid="lang-name" x="15" y="10" class='lang-name'> <text data-testid="lang-name" x="15" y="10" class='lang-name'>
${lang.name} ${percentage}% ${lang.name} ${percentage}%
@@ -111,6 +104,7 @@ const createLanguageTextNode = ({ langs, totalSize }) => {
createCompactLangNode({ createCompactLangNode({
lang, lang,
totalSize, totalSize,
// @ts-ignore
index, index,
}), }),
); );
@@ -140,13 +134,12 @@ const createLanguageTextNode = ({ langs, totalSize }) => {
*/ */
const renderNormalLayout = (langs, width, totalLanguageSize) => { const renderNormalLayout = (langs, width, totalLanguageSize) => {
return flexLayout({ return flexLayout({
items: langs.map((lang, index) => { items: langs.map((lang) => {
return createProgressTextNode({ return createProgressTextNode({
width, width: width,
name: lang.name, name: lang.name,
color: lang.color || DEFAULT_LANG_COLOR, color: lang.color || DEFAULT_LANG_COLOR,
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2), progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
index,
}); });
}), }),
gap: 40, gap: 40,
@@ -194,7 +187,7 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => {
return ` return `
<mask id="rect-mask"> <mask id="rect-mask">
<rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5"/> <rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5" />
</mask> </mask>
${compactProgressBar} ${compactProgressBar}
@@ -283,7 +276,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
langs_count = DEFAULT_LANGS_COUNT, langs_count = DEFAULT_LANGS_COUNT,
border_radius, border_radius,
border_color, border_color,
disable_animations,
} = options; } = options;
const i18n = new I18n({ const i18n = new I18n({
@@ -332,43 +324,11 @@ const renderTopLanguages = (topLangs, options = {}) => {
colors, colors,
}); });
if (disable_animations) card.disableAnimations(); card.disableAnimations();
card.setHideBorder(hide_border); card.setHideBorder(hide_border);
card.setHideTitle(hide_title); card.setHideTitle(hide_title);
card.setCSS( card.setCSS(
` `.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }`,
@keyframes slideInAnimation {
from {
width: 0;
}
to {
width: calc(100%-100px);
}
}
@keyframes growWidthAnimation {
from {
width: 0;
}
to {
width: 100%;
}
}
.lang-name {
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
fill: ${colors.textColor};
}
.stagger {
opacity: 0;
animation: fadeInAnimation 0.3s ease-in-out forwards;
}
#rect-mask rect{
animation: slideInAnimation 1s ease-in-out forwards;
}
.lang-progress{
animation: growWidthAnimation 0.6s ease-in-out forwards;
}
`,
); );
return card.render(` return card.render(`
-1
View File
@@ -37,7 +37,6 @@ export type TopLangOptions = CommonOptions & {
layout: "compact" | "normal"; layout: "compact" | "normal";
custom_title: string; custom_title: string;
langs_count: number; langs_count: number;
disable_animations: boolean;
}; };
type WakaTimeOptions = CommonOptions & { type WakaTimeOptions = CommonOptions & {
+1 -1
View File
@@ -8,7 +8,7 @@ import {
getCardColors, getCardColors,
lowercaseTrim, lowercaseTrim,
} from "../common/utils.js"; } from "../common/utils.js";
import { getStyles } from "../getStyles.js"; import { getStyles } from "../common/getStyles.js";
import { wakatimeCardLocales } from "../translations.js"; import { wakatimeCardLocales } from "../translations.js";
/** Import language colors. /** Import language colors.
+1 -1
View File
@@ -1,4 +1,4 @@
import { getAnimations } from "../getStyles.js"; import { getAnimations } from "./getStyles.js";
import { encodeHTML, flexLayout } from "./utils.js"; import { encodeHTML, flexLayout } from "./utils.js";
class Card { class Card {
+8 -11
View File
@@ -10,7 +10,6 @@ import { clampValue } from "./utils.js";
* @param {string} createProgressNodeParams.color Progress color. * @param {string} createProgressNodeParams.color Progress color.
* @param {string} createProgressNodeParams.progress Progress value. * @param {string} createProgressNodeParams.progress Progress value.
* @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color. * @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color.
* @param {number} createProgressNodeParams.delay Delay before animation starts.
* @returns {string} Progress node. * @returns {string} Progress node.
*/ */
const createProgressNode = ({ const createProgressNode = ({
@@ -20,22 +19,20 @@ const createProgressNode = ({
color, color,
progress, progress,
progressBarBackgroundColor, progressBarBackgroundColor,
delay,
}) => { }) => {
const progressPercentage = clampValue(progress, 2, 100); const progressPercentage = clampValue(progress, 2, 100);
return ` return `
<svg width="${width}" x="${x}" y="${y}"> <svg width="${width}" x="${x}" y="${y}">
<rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect> <rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
<svg data-testid="lang-progress" width="${progressPercentage}%"> <rect
<rect height="8"
height="8" fill="${color}"
fill="${color}" rx="5" ry="5" x="0" y="0"
rx="5" ry="5" x="0" y="0" data-testid="lang-progress"
class="lang-progress" width="${progressPercentage}%"
style="animation-delay: ${delay}ms;" >
/> </rect>
</svg>
</svg> </svg>
`; `;
}; };
+105 -104
View File
@@ -1,8 +1,7 @@
// @ts-check // @ts-check
import axios from "axios"; import axios from "axios";
import * as dotenv from "dotenv";
import githubUsernameRegex from "github-username-regex"; import githubUsernameRegex from "github-username-regex";
import { calculateRank } from "../calculateRank.js"; import { calculateRank } from "../common/calculateRank.js";
import { retryer } from "../common/retryer.js"; import { retryer } from "../common/retryer.js";
import { import {
CustomError, CustomError,
@@ -12,74 +11,46 @@ import {
wrapTextMultiline, wrapTextMultiline,
} from "../common/utils.js"; } from "../common/utils.js";
dotenv.config();
// GraphQL queries.
const GRAPHQL_REPOS_FIELD = `
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
totalCount
nodes {
name
stargazers {
totalCount
}
}
pageInfo {
hasNextPage
endCursor
}
}
`;
const GRAPHQL_REPOS_QUERY = `
query userInfo($login: String!, $after: String) {
user(login: $login) {
${GRAPHQL_REPOS_FIELD}
}
}
`;
const GRAPHQL_STATS_QUERY = `
query userInfo($login: String!, $after: String) {
user(login: $login) {
name
login
contributionsCollection {
totalCommitContributions
restrictedContributionsCount
}
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
totalCount
}
pullRequests(first: 1) {
totalCount
}
openIssues: issues(states: OPEN) {
totalCount
}
closedIssues: issues(states: CLOSED) {
totalCount
}
followers {
totalCount
}
${GRAPHQL_REPOS_FIELD}
}
}
`;
/** /**
* Stats fetcher object. * Stats fetcher object.
* *
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
* @param {string} token GitHub token. * @param {string} token GitHub token.
* @returns {Promise<import('../common/types').Fetcher>} Stats fetcher response. * @returns {Promise<import('../common/types').StatsFetcherResponse>} Stats fetcher response.
*/ */
const fetcher = (variables, token) => { const fetcher = (variables, token) => {
const query = !variables.after ? GRAPHQL_STATS_QUERY : GRAPHQL_REPOS_QUERY;
return request( return request(
{ {
query, query: `
query userInfo($login: String!) {
user(login: $login) {
name
login
contributionsCollection {
totalCommitContributions
restrictedContributionsCount
}
repositoriesContributedTo(contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
totalCount
}
pullRequests {
totalCount
}
openIssues: issues(states: OPEN) {
totalCount
}
closedIssues: issues(states: CLOSED) {
totalCount
}
followers {
totalCount
}
repositories(ownerAffiliations: OWNER) {
totalCount
}
}
}
`,
variables, variables,
}, },
{ {
@@ -89,42 +60,39 @@ const fetcher = (variables, token) => {
}; };
/** /**
* Fetch stats information for a given username. * Fetch first 100 repositories for a given username.
* *
* @param {string} username Github username. * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
* @returns {Promise<import('../common/types').StatsFetcher>} GraphQL Stats object. * @param {string} token GitHub token.
* * @returns {Promise<import('../common/types').StatsFetcherResponse>} Repositories fetcher response.
* @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true.
*/ */
const statsFetcher = async (username) => { const repositoriesFetcher = (variables, token) => {
let stats; return request(
let hasNextPage = true; {
let endCursor = null; query: `
while (hasNextPage) { query userInfo($login: String!, $after: String) {
const variables = { login: username, first: 100, after: endCursor }; user(login: $login) {
let res = await retryer(fetcher, variables); repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
if (res.data.errors) return res; nodes {
name
// Store stats data. stargazers {
const repoNodes = res.data.data.user.repositories.nodes; totalCount
if (!stats) { }
stats = res; }
} else { pageInfo {
stats.data.data.user.repositories.nodes.push(...repoNodes); hasNextPage
} endCursor
}
// Disable multi page fetching on public Vercel instance due to rate limits. }
const repoNodesWithStars = repoNodes.filter( }
(node) => node.stargazers.totalCount !== 0, }
); `,
hasNextPage = variables,
process.env.FETCH_MULTI_PAGE_STARS === "true" && },
repoNodes.length === repoNodesWithStars.length && {
res.data.data.user.repositories.pageInfo.hasNextPage; Authorization: `bearer ${token}`,
endCursor = res.data.data.user.repositories.pageInfo.endCursor; },
} );
return stats;
}; };
/** /**
@@ -169,6 +137,46 @@ const totalCommitsFetcher = async (username) => {
return 0; return 0;
}; };
/**
* Fetch all the stars for all the repositories of a given username.
*
* @param {string} username GitHub username.
* @param {array} repoToHide Repositories to hide.
* @returns {Promise<number>} Total stars.
*/
const totalStarsFetcher = async (username, repoToHide) => {
let nodes = [];
let hasNextPage = true;
let endCursor = null;
while (hasNextPage) {
const variables = { login: username, first: 100, after: endCursor };
let res = await retryer(repositoriesFetcher, variables);
if (res.data.errors) {
logger.error(res.data.errors);
throw new CustomError(
res.data.errors[0].message || "Could not fetch user",
CustomError.USER_NOT_FOUND,
);
}
const allNodes = res.data.data.user.repositories.nodes;
const nodesWithStars = allNodes.filter(
(node) => node.stargazers.totalCount !== 0,
);
nodes.push(...nodesWithStars);
// hasNextPage =
// allNodes.length === nodesWithStars.length &&
// res.data.data.user.repositories.pageInfo.hasNextPage;
hasNextPage = false; // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
}
return nodes
.filter((data) => !repoToHide[data.name])
.reduce((prev, curr) => prev + curr.stargazers.totalCount, 0);
};
/** /**
* Fetch stats for a given username. * Fetch stats for a given username.
* *
@@ -195,7 +203,7 @@ const fetchStats = async (
rank: { level: "C", score: 0 }, rank: { level: "C", score: 0 },
}; };
let res = await statsFetcher(username); let res = await retryer(fetcher, { login: username });
// Catch GraphQL errors. // Catch GraphQL errors.
if (res.data.errors) { if (res.data.errors) {
@@ -251,15 +259,8 @@ const fetchStats = async (
stats.contributedTo = user.repositoriesContributedTo.totalCount; stats.contributedTo = user.repositoriesContributedTo.totalCount;
// Retrieve stars while filtering out repositories to be hidden // Retrieve stars while filtering out repositories to be hidden
stats.totalStars = user.repositories.nodes stats.totalStars = await totalStarsFetcher(username, repoToHide);
.filter((data) => {
return !repoToHide[data.name];
})
.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
// @ts-ignore // TODO: Fix this.
stats.rank = calculateRank({ stats.rank = calculateRank({
totalCommits: stats.totalCommits, totalCommits: stats.totalCommits,
totalRepos: user.repositories.totalCount, totalRepos: user.repositories.totalCount,
+25 -12
View File
@@ -2,7 +2,7 @@ import { jest } from "@jest/globals";
import axios from "axios"; import axios from "axios";
import MockAdapter from "axios-mock-adapter"; import MockAdapter from "axios-mock-adapter";
import api from "../api/index.js"; import api from "../api/index.js";
import { calculateRank } from "../src/calculateRank.js"; import { calculateRank } from "../src/common/calculateRank.js";
import { renderStatsCard } from "../src/cards/stats-card.js"; import { renderStatsCard } from "../src/cards/stats-card.js";
import { CONSTANTS, renderError } from "../src/common/utils.js"; import { CONSTANTS, renderError } from "../src/common/utils.js";
@@ -25,7 +25,7 @@ stats.rank = calculateRank({
issues: stats.totalIssues, issues: stats.totalIssues,
}); });
const data_stats = { const data = {
data: { data: {
user: { user: {
name: stats.name, name: stats.name,
@@ -40,6 +40,15 @@ const data_stats = {
followers: { totalCount: 0 }, followers: { totalCount: 0 },
repositories: { repositories: {
totalCount: 1, totalCount: 1,
},
},
},
};
const repositoriesData = {
data: {
user: {
repositories: {
nodes: [{ stargazers: { totalCount: 100 } }], nodes: [{ stargazers: { totalCount: 100 } }],
pageInfo: { pageInfo: {
hasNextPage: false, hasNextPage: false,
@@ -74,7 +83,11 @@ const faker = (query, data) => {
setHeader: jest.fn(), setHeader: jest.fn(),
send: jest.fn(), send: jest.fn(),
}; };
mock.onPost("https://api.github.com/graphql").replyOnce(200, data); mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, repositoriesData);
return { req, res }; return { req, res };
}; };
@@ -85,7 +98,7 @@ afterEach(() => {
describe("Test /api/", () => { describe("Test /api/", () => {
it("should test the request", async () => { it("should test the request", async () => {
const { req, res } = faker({}, data_stats); const { req, res } = faker({}, data);
await api(req, res); await api(req, res);
@@ -120,7 +133,7 @@ describe("Test /api/", () => {
text_color: "fff", text_color: "fff",
bg_color: "fff", bg_color: "fff",
}, },
data_stats, data,
); );
await api(req, res); await api(req, res);
@@ -141,7 +154,7 @@ describe("Test /api/", () => {
}); });
it("should have proper cache", async () => { it("should have proper cache", async () => {
const { req, res } = faker({}, data_stats); const { req, res } = faker({}, data);
await api(req, res); await api(req, res);
@@ -157,7 +170,7 @@ describe("Test /api/", () => {
}); });
it("should set proper cache", async () => { it("should set proper cache", async () => {
const { req, res } = faker({ cache_seconds: 15000 }, data_stats); const { req, res } = faker({ cache_seconds: 15000 }, data);
await api(req, res); await api(req, res);
expect(res.setHeader.mock.calls).toEqual([ expect(res.setHeader.mock.calls).toEqual([
@@ -183,7 +196,7 @@ describe("Test /api/", () => {
it("should set proper cache with clamped values", async () => { it("should set proper cache with clamped values", async () => {
{ {
let { req, res } = faker({ cache_seconds: 200000 }, data_stats); let { req, res } = faker({ cache_seconds: 200000 }, data);
await api(req, res); await api(req, res);
expect(res.setHeader.mock.calls).toEqual([ expect(res.setHeader.mock.calls).toEqual([
@@ -199,7 +212,7 @@ describe("Test /api/", () => {
// note i'm using block scoped vars // note i'm using block scoped vars
{ {
let { req, res } = faker({ cache_seconds: 0 }, data_stats); let { req, res } = faker({ cache_seconds: 0 }, data);
await api(req, res); await api(req, res);
expect(res.setHeader.mock.calls).toEqual([ expect(res.setHeader.mock.calls).toEqual([
@@ -214,7 +227,7 @@ describe("Test /api/", () => {
} }
{ {
let { req, res } = faker({ cache_seconds: -10000 }, data_stats); let { req, res } = faker({ cache_seconds: -10000 }, data);
await api(req, res); await api(req, res);
expect(res.setHeader.mock.calls).toEqual([ expect(res.setHeader.mock.calls).toEqual([
@@ -235,7 +248,7 @@ describe("Test /api/", () => {
username: "anuraghazra", username: "anuraghazra",
count_private: true, count_private: true,
}, },
data_stats, data,
); );
await api(req, res); await api(req, res);
@@ -275,7 +288,7 @@ describe("Test /api/", () => {
text_color: "fff", text_color: "fff",
bg_color: "fff", bg_color: "fff",
}, },
data_stats, data,
); );
await api(req, res); await api(req, res);
+1 -1
View File
@@ -1,5 +1,5 @@
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { calculateRank } from "../src/calculateRank.js"; import { calculateRank } from "../src/common/calculateRank.js";
describe("Test calculateRank", () => { describe("Test calculateRank", () => {
it("should calculate rank correctly", () => { it("should calculate rank correctly", () => {
+25 -20
View File
@@ -11,22 +11,27 @@ import { renderStatsCard } from "../../src/cards/stats-card.js";
import { renderTopLanguages } from "../../src/cards/top-languages-card.js"; import { renderTopLanguages } from "../../src/cards/top-languages-card.js";
import { renderWakatimeCard } from "../../src/cards/wakatime-card.js"; import { renderWakatimeCard } from "../../src/cards/wakatime-card.js";
const REPO = "curly-fiesta"; const REPO = "dummy-cra";
const USER = "catelinemnemosyne"; const USER = "grsdummy";
const STATS_DATA = { const STATS_DATA = {
name: "Cateline Mnemosyne", name: "grsdummy",
totalPRs: 1, totalPRs: 2,
totalCommits: 7, totalCommits: 2,
totalIssues: 1, totalIssues: 1,
totalStars: 1, totalStars: 1,
contributedTo: 1, contributedTo: 2,
rank: { rank: {
level: "A+", level: "A+",
score: 50.893750297869225, score: 50.900829325065935,
}, },
}; };
const LANGS_DATA = { const LANGS_DATA = {
TypeScript: {
color: "#3178c6",
name: "TypeScript",
size: 2049,
},
HTML: { HTML: {
color: "#e34c26", color: "#e34c26",
name: "HTML", name: "HTML",
@@ -37,19 +42,19 @@ const LANGS_DATA = {
name: "CSS", name: "CSS",
size: 930, size: 930,
}, },
JavaScript: { Python: {
color: "#f1e05a", color: "#3572A5",
name: "JavaScript", name: "Python",
size: 1912, size: 671,
}, },
}; };
const WAKATIME_DATA = { const WAKATIME_DATA = {
human_readable_range: "last week", human_readable_range: "last week",
is_already_updating: false, is_already_updating: false,
is_coding_activity_visible: true, is_coding_activity_visible: false,
is_including_today: false, is_including_today: false,
is_other_usage_visible: true, is_other_usage_visible: false,
is_stuck: false, is_stuck: false,
is_up_to_date: false, is_up_to_date: false,
is_up_to_date_pending_future: false, is_up_to_date_pending_future: false,
@@ -57,24 +62,24 @@ const WAKATIME_DATA = {
range: "last_7_days", range: "last_7_days",
status: "pending_update", status: "pending_update",
timeout: 15, timeout: 15,
username: USER, username: "grsdummy",
writes_only: false, writes_only: false,
}; };
const REPOSITORY_DATA = { const REPOSITORY_DATA = {
name: REPO, name: "dummy-cra",
nameWithOwner: `${USER}/cra-test`, nameWithOwner: "grsdummy/dummy-cra",
isPrivate: false, isPrivate: false,
isArchived: false, isArchived: false,
isTemplate: false, isTemplate: false,
stargazers: { stargazers: {
totalCount: 1, totalCount: 1,
}, },
description: "Simple cra test repo.", description: "Dummy create react app.",
primaryLanguage: { primaryLanguage: {
color: "#f1e05a", color: "#3178c6",
id: "MDg6TGFuZ3VhZ2UxNDA=", id: "MDg6TGFuZ3VhZ2UyODc=",
name: "JavaScript", name: "TypeScript",
}, },
forkCount: 0, forkCount: 0,
starCount: 1, starCount: 1,
+35 -93
View File
@@ -1,11 +1,10 @@
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import axios from "axios"; import axios from "axios";
import MockAdapter from "axios-mock-adapter"; import MockAdapter from "axios-mock-adapter";
import { calculateRank } from "../src/calculateRank.js"; import { calculateRank } from "../src/common/calculateRank.js";
import { fetchStats } from "../src/fetchers/stats-fetcher.js"; import { fetchStats } from "../src/fetchers/stats-fetcher.js";
// Test parameters. const data = {
const data_stats = {
data: { data: {
user: { user: {
name: "Anurag Hazra", name: "Anurag Hazra",
@@ -20,6 +19,15 @@ const data_stats = {
followers: { totalCount: 100 }, followers: { totalCount: 100 },
repositories: { repositories: {
totalCount: 5, totalCount: 5,
},
},
},
};
const firstRepositoriesData = {
data: {
user: {
repositories: {
nodes: [ nodes: [
{ name: "test-repo-1", stargazers: { totalCount: 100 } }, { name: "test-repo-1", stargazers: { totalCount: 100 } },
{ name: "test-repo-2", stargazers: { totalCount: 100 } }, { name: "test-repo-2", stargazers: { totalCount: 100 } },
@@ -34,7 +42,7 @@ const data_stats = {
}, },
}; };
const data_repo = { const secondRepositoriesData = {
data: { data: {
user: { user: {
repositories: { repositories: {
@@ -51,7 +59,7 @@ const data_repo = {
}, },
}; };
const data_repo_zero_stars = { const repositoriesWithZeroStarsData = {
data: { data: {
user: { user: {
repositories: { repositories: {
@@ -85,12 +93,13 @@ const error = {
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
beforeEach(() => { beforeEach(() => {
process.env.FETCH_MULTI_PAGE_STARS = "false"; // Set to `false` to fetch only one page of stars.
mock mock
.onPost("https://api.github.com/graphql") .onPost("https://api.github.com/graphql")
.replyOnce(200, data_stats) .replyOnce(200, data)
.onPost("https://api.github.com/graphql") .onPost("https://api.github.com/graphql")
.replyOnce(200, data_repo); .replyOnce(200, firstRepositoriesData);
// .onPost("https://api.github.com/graphql") // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
// .replyOnce(200, secondRepositoriesData); // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
}); });
afterEach(() => { afterEach(() => {
@@ -105,7 +114,8 @@ describe("Test fetchStats", () => {
totalRepos: 5, totalRepos: 5,
followers: 100, followers: 100,
contributions: 61, contributions: 61,
stargazers: 300, // stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
prs: 300, prs: 300,
issues: 200, issues: 200,
}); });
@@ -116,7 +126,8 @@ describe("Test fetchStats", () => {
totalCommits: 100, totalCommits: 100,
totalIssues: 200, totalIssues: 200,
totalPRs: 300, totalPRs: 300,
totalStars: 300, // totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
rank, rank,
}); });
}); });
@@ -125,9 +136,9 @@ describe("Test fetchStats", () => {
mock.reset(); mock.reset();
mock mock
.onPost("https://api.github.com/graphql") .onPost("https://api.github.com/graphql")
.replyOnce(200, data_stats) .replyOnce(200, data)
.onPost("https://api.github.com/graphql") .onPost("https://api.github.com/graphql")
.replyOnce(200, data_repo_zero_stars); .replyOnce(200, repositoriesWithZeroStarsData);
let stats = await fetchStats("anuraghazra"); let stats = await fetchStats("anuraghazra");
const rank = calculateRank({ const rank = calculateRank({
@@ -167,7 +178,8 @@ describe("Test fetchStats", () => {
totalRepos: 5, totalRepos: 5,
followers: 100, followers: 100,
contributions: 61, contributions: 61,
stargazers: 300, // stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
prs: 300, prs: 300,
issues: 200, issues: 200,
}); });
@@ -178,7 +190,8 @@ describe("Test fetchStats", () => {
totalCommits: 150, totalCommits: 150,
totalIssues: 200, totalIssues: 200,
totalPRs: 300, totalPRs: 300,
totalStars: 300, // totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
rank, rank,
}); });
}); });
@@ -194,7 +207,8 @@ describe("Test fetchStats", () => {
totalRepos: 5, totalRepos: 5,
followers: 100, followers: 100,
contributions: 61, contributions: 61,
stargazers: 300, // stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
prs: 300, prs: 300,
issues: 200, issues: 200,
}); });
@@ -205,7 +219,8 @@ describe("Test fetchStats", () => {
totalCommits: 1050, totalCommits: 1050,
totalIssues: 200, totalIssues: 200,
totalPRs: 300, totalPRs: 300,
totalStars: 300, // totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
rank, rank,
}); });
}); });
@@ -221,7 +236,8 @@ describe("Test fetchStats", () => {
totalRepos: 5, totalRepos: 5,
followers: 100, followers: 100,
contributions: 61, contributions: 61,
stargazers: 200, // stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
stargazers: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
prs: 300, prs: 300,
issues: 200, issues: 200,
}); });
@@ -232,82 +248,8 @@ describe("Test fetchStats", () => {
totalCommits: 1050, totalCommits: 1050,
totalIssues: 200, totalIssues: 200,
totalPRs: 300, totalPRs: 300,
totalStars: 200, // totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
rank, totalStars: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
});
});
it("should fetch two pages of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `true`", async () => {
process.env.FETCH_MULTI_PAGE_STARS = true;
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 400,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 100,
totalIssues: 200,
totalPRs: 300,
totalStars: 400,
rank,
});
});
it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `false`", async () => {
process.env.FETCH_MULTI_PAGE_STARS = "false";
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 100,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank,
});
});
it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is not set", async () => {
process.env.FETCH_MULTI_PAGE_STARS = undefined;
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 100,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank, rank,
}); });
}); });