Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c504c9338e | ||
|
|
aa2e8ff3c5 |
@@ -1,10 +0,0 @@
|
||||
import os
|
||||
|
||||
file = open('./vercel.json', 'r')
|
||||
str = file.read()
|
||||
file = open('./vercel.json', 'w')
|
||||
|
||||
str = str.replace('"maxDuration": 10', '"maxDuration": 30')
|
||||
|
||||
file.write(str)
|
||||
file.close()
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Deployment Prep
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
config:
|
||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Deployment Prep
|
||||
run: python ./.github/workflows/deploy-prep.py
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
branch: vercel
|
||||
create_branch: true
|
||||
push_options: "--force"
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
jobs:
|
||||
e2eTests:
|
||||
if:
|
||||
github.repository == 'anuraghazra/github-readme-stats' &&
|
||||
github.event_name == 'deployment_status' &&
|
||||
github.event.deployment_status.state == 'success'
|
||||
name: Perform 2e2 tests
|
||||
|
||||
@@ -8,7 +8,6 @@ on:
|
||||
|
||||
jobs:
|
||||
closeEmptyIssuesAndTemplates:
|
||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
||||
name: Close empty issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
|
||||
jobs:
|
||||
closeOldThemePrs:
|
||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
||||
name: Close stale 'invalid' theme PRs
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
|
||||
jobs:
|
||||
showAndLabelTopIssues:
|
||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
||||
name: Update top issues Dashboard.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
parseBoolean,
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { HttpException } from "../src/common/exceptions.js";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
|
||||
@@ -56,7 +57,7 @@ export default async (req, res) => {
|
||||
);
|
||||
|
||||
const cacheSeconds = clampValue(
|
||||
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
|
||||
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10),
|
||||
CONSTANTS.FOUR_HOURS,
|
||||
CONSTANTS.ONE_DAY,
|
||||
);
|
||||
@@ -65,7 +66,8 @@ export default async (req, res) => {
|
||||
"Cache-Control",
|
||||
`max-age=${
|
||||
cacheSeconds / 2
|
||||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY},
|
||||
stale-if-error=${CONSTANTS.ONE_HOUR}`,
|
||||
);
|
||||
|
||||
return res.send(
|
||||
@@ -93,7 +95,27 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
||||
// Throw error if REST and GraphQL API calls fail. This way we can return a cached
|
||||
if (err instanceof HttpException) {
|
||||
// throw err;
|
||||
// throw new Error(err.message);
|
||||
// return res.status(404).;
|
||||
// return {statusCode: 404, body: err.message}
|
||||
return res
|
||||
.status(500)
|
||||
.send(
|
||||
renderError(err.errors[0].message, err.errors[0].secondaryMessage),
|
||||
);
|
||||
}
|
||||
|
||||
// Cache the error response less frequently.
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}
|
||||
stale-if-error=${CONSTANTS.ONE_HOUR}`,
|
||||
);
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export default async (req, res) => {
|
||||
const repoData = await fetchRepo(username, repo);
|
||||
|
||||
let cacheSeconds = clampValue(
|
||||
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
|
||||
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10),
|
||||
CONSTANTS.FOUR_HOURS,
|
||||
CONSTANTS.ONE_DAY,
|
||||
);
|
||||
@@ -80,7 +80,12 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Cache the error response less frequently.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* @file Contains a simple cloud function that can be used to check which PATs are no
|
||||
* longer working. It returns a list of valid PATs, expired PATs and PATs with errors.
|
||||
*
|
||||
* @description This function is currently rate limited to 1 request per 10 minutes.
|
||||
*/
|
||||
|
||||
import { logger, request, dateDiff } from "../../src/common/utils.js";
|
||||
export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 10 minutes
|
||||
|
||||
/**
|
||||
* Simple uptime check fetcher for the PATs.
|
||||
*
|
||||
* @param {import('axios').AxiosRequestHeaders} variables
|
||||
* @param {string} token
|
||||
*/
|
||||
const uptimeFetcher = (variables, token) => {
|
||||
return request(
|
||||
{
|
||||
query: `
|
||||
query {
|
||||
rateLimit {
|
||||
remaining
|
||||
resetAt
|
||||
},
|
||||
}`,
|
||||
variables,
|
||||
},
|
||||
{
|
||||
Authorization: `bearer ${token}`,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const getAllPATs = () => {
|
||||
return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether any of the PATs is expired.
|
||||
*/
|
||||
const getPATInfo = async (fetcher, variables) => {
|
||||
const details = {};
|
||||
const PATs = getAllPATs();
|
||||
|
||||
for (const pat of PATs) {
|
||||
try {
|
||||
const response = await fetcher(variables, process.env[pat]);
|
||||
const errors = response.data.errors;
|
||||
const hasErrors = Boolean(errors);
|
||||
const errorType = errors?.[0]?.type;
|
||||
const isRateLimited =
|
||||
(hasErrors && errorType === "RATE_LIMITED") ||
|
||||
response.data.data?.rateLimit?.remaining === 0;
|
||||
|
||||
// Store PATs with errors.
|
||||
if (hasErrors && errorType !== "RATE_LIMITED") {
|
||||
details[pat] = {
|
||||
status: "error",
|
||||
error: {
|
||||
type: errors[0].type,
|
||||
message: errors[0].message,
|
||||
},
|
||||
};
|
||||
continue;
|
||||
} else if (isRateLimited) {
|
||||
const date1 = new Date();
|
||||
const date2 = new Date(response.data?.data?.rateLimit?.resetAt);
|
||||
details[pat] = {
|
||||
status: "exhausted",
|
||||
remaining: 0,
|
||||
resetIn: dateDiff(date2, date1) + " minutes",
|
||||
};
|
||||
} else {
|
||||
details[pat] = {
|
||||
status: "valid",
|
||||
remaining: response.data.data.rateLimit.remaining,
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
// Store the PAT if it is expired.
|
||||
const errorMessage = err.response?.data?.message?.toLowerCase();
|
||||
if (errorMessage === "bad credentials") {
|
||||
details[pat] = {
|
||||
status: "expired",
|
||||
};
|
||||
} else if (errorMessage === "sorry. your account was suspended.") {
|
||||
details[pat] = {
|
||||
status: "suspended",
|
||||
};
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const filterPATsByStatus = (status) => {
|
||||
return Object.keys(details).filter((pat) => details[pat].status === status);
|
||||
};
|
||||
|
||||
const sortedDetails = Object.keys(details)
|
||||
.sort()
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = details[key];
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
validPATs: filterPATsByStatus("valid"),
|
||||
expiredPATs: filterPATsByStatus("expired"),
|
||||
exhaustedPATs: filterPATsByStatus("exhausted"),
|
||||
suspendedPATs: filterPATsByStatus("suspended"),
|
||||
errorPATs: filterPATsByStatus("error"),
|
||||
details: sortedDetails,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Cloud function that returns information about the used PATs.
|
||||
*/
|
||||
export default async (_, res) => {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
try {
|
||||
// Add header to prevent abuse.
|
||||
const PATsInfo = await getPATInfo(uptimeFetcher, {});
|
||||
if (PATsInfo) {
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
|
||||
);
|
||||
}
|
||||
res.send(JSON.stringify(PATsInfo, null, 2));
|
||||
} catch (err) {
|
||||
// Throw error if something went wrong.
|
||||
logger.error(err);
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
res.send("Something went wrong: " + err.message);
|
||||
}
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* @file Contains a simple cloud function that can be used to check if the PATs are still
|
||||
* functional.
|
||||
*
|
||||
* @description This function is currently rate limited to 1 request per 10 minutes.
|
||||
*/
|
||||
|
||||
import retryer from "../../src/common/retryer.js";
|
||||
import { logger, request } from "../../src/common/utils.js";
|
||||
|
||||
export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes
|
||||
|
||||
/**
|
||||
* Simple uptime check fetcher for the PATs.
|
||||
*
|
||||
* @param {import('axios').AxiosRequestHeaders} variables
|
||||
* @param {string} token
|
||||
*/
|
||||
const uptimeFetcher = (variables, token) => {
|
||||
return request(
|
||||
{
|
||||
query: `
|
||||
query {
|
||||
rateLimit {
|
||||
remaining
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables,
|
||||
},
|
||||
{
|
||||
Authorization: `bearer ${token}`,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates Json response that can be used for shields.io dynamic card generation.
|
||||
*
|
||||
* @param {*} up Whether the PATs are up or not.
|
||||
* @returns Dynamic shields.io JSON response object.
|
||||
*
|
||||
* @see https://shields.io/endpoint.
|
||||
*/
|
||||
const shieldsUptimeBadge = (up) => {
|
||||
const schemaVersion = 1;
|
||||
const isError = true;
|
||||
const label = "Public Instance";
|
||||
const message = up ? "up" : "down";
|
||||
const color = up ? "brightgreen" : "red";
|
||||
return {
|
||||
schemaVersion,
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
isError,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Cloud function that returns whether the PATs are still functional.
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
let { type } = req.query;
|
||||
type = type ? type.toLowerCase() : "boolean";
|
||||
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
try {
|
||||
let PATsValid = true;
|
||||
try {
|
||||
await retryer(uptimeFetcher, {});
|
||||
} catch (err) {
|
||||
PATsValid = false;
|
||||
}
|
||||
|
||||
if (PATsValid) {
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
|
||||
);
|
||||
} else {
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "shields":
|
||||
res.send(shieldsUptimeBadge(PATsValid));
|
||||
break;
|
||||
case "json":
|
||||
res.send({ up: PATsValid });
|
||||
break;
|
||||
default:
|
||||
res.send(PATsValid);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// Return fail boolean if something went wrong.
|
||||
logger.error(err);
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
res.send("Something went wrong: " + err.message);
|
||||
}
|
||||
};
|
||||
@@ -48,7 +48,7 @@ export default async (req, res) => {
|
||||
);
|
||||
|
||||
const cacheSeconds = clampValue(
|
||||
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
|
||||
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10),
|
||||
CONSTANTS.FOUR_HOURS,
|
||||
CONSTANTS.ONE_DAY,
|
||||
);
|
||||
@@ -80,7 +80,12 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Cache the error response less frequently.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,15 +43,11 @@ export default async (req, res) => {
|
||||
const stats = await fetchWakatimeStats({ username, api_domain, range });
|
||||
|
||||
let cacheSeconds = clampValue(
|
||||
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
|
||||
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10),
|
||||
CONSTANTS.FOUR_HOURS,
|
||||
CONSTANTS.ONE_DAY,
|
||||
);
|
||||
|
||||
if (!cache_seconds) {
|
||||
cacheSeconds = CONSTANTS.FOUR_HOURS;
|
||||
}
|
||||
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=${
|
||||
@@ -80,7 +76,12 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Cache the error response less frequently.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@uppercod/css-to-object": "^1.1.1",
|
||||
"axios-mock-adapter": "^1.21.2",
|
||||
"axios-mock-adapter": "^1.18.1",
|
||||
"color-contrast-checker": "^2.1.0",
|
||||
"hjson": "^3.2.2",
|
||||
"husky": "^8.0.0",
|
||||
|
||||
@@ -39,18 +39,17 @@
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@uppercod/css-to-object": "^1.1.1",
|
||||
"axios-mock-adapter": "^1.21.2",
|
||||
"axios-mock-adapter": "^1.18.1",
|
||||
"color-contrast-checker": "^2.1.0",
|
||||
"hjson": "^3.2.2",
|
||||
"husky": "^8.0.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"jest-svg-snapshot": "^0.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"parse-diff": "^0.7.0",
|
||||
"prettier": "^2.8.3"
|
||||
"prettier": "^2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0",
|
||||
|
||||
@@ -12,11 +12,6 @@ import {
|
||||
import { getStyles } from "../getStyles.js";
|
||||
import { statCardLocales } from "../translations.js";
|
||||
|
||||
const CARD_MIN_WIDTH = 287;
|
||||
const CARD_DEFAULT_WIDTH = 287;
|
||||
const RANK_CARD_MIN_WIDTH = 420;
|
||||
const RANK_CARD_DEFAULT_WIDTH = 450;
|
||||
|
||||
/**
|
||||
* Create a stats card text item.
|
||||
*
|
||||
@@ -223,17 +218,11 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
||||
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
|
||||
Numbers are picked by looking at existing dimensions on production.
|
||||
*/
|
||||
const iconWidth = show_icons ? 16 + /* padding */ 1 : 0;
|
||||
const minCardWidth =
|
||||
(hide_rank
|
||||
? clampValue(
|
||||
50 /* padding */ + calculateTextWidth() * 2,
|
||||
CARD_MIN_WIDTH,
|
||||
Infinity,
|
||||
)
|
||||
: RANK_CARD_MIN_WIDTH) + iconWidth;
|
||||
const defaultCardWidth =
|
||||
(hide_rank ? CARD_DEFAULT_WIDTH : RANK_CARD_DEFAULT_WIDTH) + iconWidth;
|
||||
const iconWidth = show_icons ? 16 : 0;
|
||||
const minCardWidth = hide_rank
|
||||
? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity)
|
||||
: 340 + iconWidth;
|
||||
const defaultCardWidth = hide_rank ? 270 : 495;
|
||||
let width = isNaN(card_width) ? defaultCardWidth : card_width;
|
||||
if (width < minCardWidth) {
|
||||
width = minCardWidth;
|
||||
@@ -262,21 +251,18 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
||||
|
||||
/**
|
||||
* Calculates the right rank circle translation values such that the rank circle
|
||||
* keeps respecting the following padding:
|
||||
* keeps respecting the padding.
|
||||
*
|
||||
* width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used.
|
||||
* width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged
|
||||
* equally from a certain minimum at RANK_CARD_MIN_WIDTH.
|
||||
* width > 450: The default left padding of 50 px will be used.
|
||||
* width < 450: The left and right padding will shrink equally.
|
||||
*
|
||||
* @returns {number} - Rank circle translation value.
|
||||
*/
|
||||
const calculateRankXTranslation = () => {
|
||||
const minXTranslation = RANK_CARD_MIN_WIDTH + iconWidth - 70;
|
||||
if (width > RANK_CARD_DEFAULT_WIDTH) {
|
||||
const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2;
|
||||
return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH;
|
||||
if (width < 450) {
|
||||
return width - 95 + (45 * (450 - 340)) / 110;
|
||||
} else {
|
||||
return minXTranslation + (width - minCardWidth) / 2;
|
||||
return width - 95;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -177,11 +177,7 @@ class Card {
|
||||
}
|
||||
${this.css}
|
||||
|
||||
${
|
||||
process.env.NODE_ENV === "test" || this.animations === false
|
||||
? ""
|
||||
: getAnimations()
|
||||
}
|
||||
${process.env.NODE_ENV === "test" ? "" : getAnimations()}
|
||||
${
|
||||
this.animations === false
|
||||
? `* { animation-duration: 0s !important; animation-delay: 0s !important; }`
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file GRS Exception/Errors.
|
||||
*/
|
||||
|
||||
const SECONDARY_ERROR_MESSAGES = {
|
||||
MAX_RETRY:
|
||||
"Please add an env variable called PAT_1 with your github token in vercel",
|
||||
USER_NOT_FOUND: "Make sure the provided username is not an organization",
|
||||
GRAPHQL_ERROR: "Please try again later",
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom error class to handle custom GRS errors.
|
||||
*
|
||||
* @extends Error
|
||||
*/
|
||||
class CustomError extends Error {
|
||||
/**
|
||||
* @param {string} message Error message.
|
||||
* @param {string} type Error type.
|
||||
*/
|
||||
constructor(message, type) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
|
||||
}
|
||||
|
||||
static MAX_RETRY = "MAX_RETRY";
|
||||
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
||||
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing query parameter class.
|
||||
*
|
||||
* @extends Error
|
||||
*/
|
||||
class MissingParamError extends Error {
|
||||
/**
|
||||
* @param {string[]} missedParams
|
||||
* @param {string?=} secondaryMessage
|
||||
*/
|
||||
constructor(missedParams, secondaryMessage) {
|
||||
const msg = `Missing params ${missedParams
|
||||
.map((p) => `"${p}"`)
|
||||
.join(", ")} make sure you pass the parameters in URL`;
|
||||
super(msg);
|
||||
this.missedParams = missedParams;
|
||||
this.secondaryMessage = secondaryMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpException class.
|
||||
*
|
||||
* @extends Error
|
||||
*/
|
||||
class HttpException extends Error {
|
||||
/**
|
||||
* Create a HttpException.
|
||||
*
|
||||
* @param {number} statusCode - The status code.
|
||||
* @param {string} message - The error message.
|
||||
* @param {string[]} errors - The errors.
|
||||
*/
|
||||
constructor(
|
||||
statusCode,
|
||||
message = "Exception occurred during the processing of HTTP requests.",
|
||||
errors = [],
|
||||
) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.errors = errors;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
SECONDARY_ERROR_MESSAGES,
|
||||
CustomError,
|
||||
MissingParamError,
|
||||
HttpException,
|
||||
};
|
||||
@@ -21,10 +21,9 @@ export {
|
||||
wrapTextMultiline,
|
||||
logger,
|
||||
CONSTANTS,
|
||||
CustomError,
|
||||
MissingParamError,
|
||||
measureText,
|
||||
lowercaseTrim,
|
||||
chunkArray,
|
||||
parseEmojis,
|
||||
} from "./utils.js";
|
||||
export { CustomError, MissingParamError, HttpException } from "./exceptions.js";
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { CustomError, logger } from "./utils.js";
|
||||
import { logger } from "./utils.js";
|
||||
import {
|
||||
CustomError,
|
||||
HttpException,
|
||||
} from "./exceptions.js";
|
||||
|
||||
// Script variables.
|
||||
const PATs = Object.keys(process.env).filter((key) =>
|
||||
@@ -16,8 +20,13 @@ const RETRIES = PATs ? PATs : 7;
|
||||
* @returns Promise<retryer>
|
||||
*/
|
||||
const retryer = async (fetcher, variables, retries = 0) => {
|
||||
if (retries > RETRIES) {
|
||||
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
|
||||
// if (retries > RETRIES) {
|
||||
if (true) { // FIXME: Test out error
|
||||
throw new HttpException(
|
||||
statusCode=500,
|
||||
message=`Max GraphQL retries exceeded. Please add an env variable called PAT_1 with your github token in vercel.`,
|
||||
errors=[new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY)]
|
||||
);
|
||||
}
|
||||
try {
|
||||
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
|
||||
@@ -60,5 +69,5 @@ const retryer = async (fetcher, variables, retries = 0) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { retryer };
|
||||
export { retryer, RETRIES };
|
||||
export default retryer;
|
||||
|
||||
@@ -22,8 +22,7 @@ const renderError = (message, secondaryMessage = "") => {
|
||||
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 }
|
||||
.gray { fill: #858585 }
|
||||
</style>
|
||||
<rect x="0.5" y="0.5" width="${
|
||||
ERROR_CARD_LENGTH - 1
|
||||
<rect x="0.5" y="0.5" width="${ERROR_CARD_LENGTH - 1
|
||||
}" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
||||
<text x="25" y="45" class="text">Something went wrong! file an issue at https://tiny.one/readme-stats</text>
|
||||
<text data-testid="message" x="25" y="55" class="text small">
|
||||
@@ -288,62 +287,28 @@ const wrapTextMultiline = (text, width = 59, maxLines = 3) => {
|
||||
return multiLineText;
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
const noop = () => { };
|
||||
// return console instance based on the environment
|
||||
const logger =
|
||||
process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop };
|
||||
|
||||
// Cache settings.
|
||||
const CARD_CACHE_SECONDS = 14400;
|
||||
const ERROR_CACHE_SECONDS = 600;
|
||||
|
||||
const CONSTANTS = {
|
||||
ONE_MINUTE: 60,
|
||||
FIVE_MINUTES: 300,
|
||||
TEN_MINUTES: 600,
|
||||
FIFTEEN_MINUTES: 900,
|
||||
THIRTY_MINUTES: 1800,
|
||||
TWO_HOURS: 7200,
|
||||
FOUR_HOURS: 14400,
|
||||
ONE_DAY: 86400,
|
||||
CARD_CACHE_SECONDS: CARD_CACHE_SECONDS,
|
||||
ERROR_CACHE_SECONDS: ERROR_CACHE_SECONDS,
|
||||
};
|
||||
|
||||
const SECONDARY_ERROR_MESSAGES = {
|
||||
MAX_RETRY:
|
||||
"Please add an env variable called PAT_1 with your github token in vercel",
|
||||
USER_NOT_FOUND: "Make sure the provided username is not an organization",
|
||||
GRAPHQL_ERROR: "Please try again later",
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom error class to handle custom GRS errors.
|
||||
*/
|
||||
class CustomError extends Error {
|
||||
/**
|
||||
* @param {string} message Error message.
|
||||
* @param {string} type Error type.
|
||||
*/
|
||||
constructor(message, type) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
|
||||
}
|
||||
|
||||
static MAX_RETRY = "MAX_RETRY";
|
||||
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
||||
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing query parameter class.
|
||||
*/
|
||||
class MissingParamError extends Error {
|
||||
/**
|
||||
* @param {string[]} missedParams
|
||||
* @param {string?=} secondaryMessage
|
||||
*/
|
||||
constructor(missedParams, secondaryMessage) {
|
||||
const msg = `Missing params ${missedParams
|
||||
.map((p) => `"${p}"`)
|
||||
.join(", ")} make sure you pass the parameters in URL`;
|
||||
super(msg);
|
||||
this.missedParams = missedParams;
|
||||
this.secondaryMessage = secondaryMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve text length.
|
||||
*
|
||||
@@ -424,19 +389,6 @@ const parseEmojis = (str) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get diff in minutes
|
||||
* @param {Date} d1
|
||||
* @param {Date} d2
|
||||
* @returns {number}
|
||||
*/
|
||||
const dateDiff = (d1, d2) => {
|
||||
const date1 = new Date(d1);
|
||||
const date2 = new Date(d2);
|
||||
const diff = date1.getTime() - date2.getTime();
|
||||
return Math.round(diff / (1000 * 60));
|
||||
};
|
||||
|
||||
export {
|
||||
ERROR_CARD_LENGTH,
|
||||
renderError,
|
||||
@@ -454,11 +406,8 @@ export {
|
||||
wrapTextMultiline,
|
||||
logger,
|
||||
CONSTANTS,
|
||||
CustomError,
|
||||
MissingParamError,
|
||||
measureText,
|
||||
lowercaseTrim,
|
||||
chunkArray,
|
||||
parseEmojis,
|
||||
dateDiff,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-check
|
||||
import { retryer } from "../common/retryer.js";
|
||||
import { MissingParamError, request } from "../common/utils.js";
|
||||
import { request } from "../common/utils.js";
|
||||
import { MissingParamError } from "../common/exceptions.js";
|
||||
|
||||
/**
|
||||
* Repo data fetcher.
|
||||
|
||||
@@ -5,12 +5,11 @@ import githubUsernameRegex from "github-username-regex";
|
||||
import { calculateRank } from "../calculateRank.js";
|
||||
import { retryer } from "../common/retryer.js";
|
||||
import {
|
||||
CustomError,
|
||||
logger,
|
||||
MissingParamError,
|
||||
request,
|
||||
wrapTextMultiline,
|
||||
} from "../common/utils.js";
|
||||
import { CustomError, MissingParamError } from "../common/exceptions.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// @ts-check
|
||||
import { retryer } from "../common/retryer.js";
|
||||
import {
|
||||
CustomError,
|
||||
logger,
|
||||
MissingParamError,
|
||||
request,
|
||||
wrapTextMultiline,
|
||||
} from "../common/utils.js";
|
||||
import { CustomError, MissingParamError } from "../common/exceptions.js";
|
||||
|
||||
/**
|
||||
* Top languages fetcher object.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { MissingParamError } from "../common/utils.js";
|
||||
import { MissingParamError } from "../common/exceptions.js";
|
||||
|
||||
/**
|
||||
* WakaTime data fetcher.
|
||||
|
||||
@@ -43,11 +43,6 @@ const getProgressAnimation = ({ progress }) => {
|
||||
const getAnimations = () => {
|
||||
return `
|
||||
/* Animations */
|
||||
.stagger {
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
@keyframes scaleInAnimation {
|
||||
from {
|
||||
transform: translate(-5px, 5px) scale(0);
|
||||
@@ -94,7 +89,10 @@ const getStyles = ({
|
||||
/* Selector detects Firefox */
|
||||
.stat { font-size:12px; }
|
||||
}
|
||||
|
||||
.stagger {
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
.rank-text {
|
||||
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
|
||||
@@ -34,7 +34,10 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
|
||||
/* Selector detects Firefox */
|
||||
.stat { font-size:12px; }
|
||||
}
|
||||
|
||||
.stagger {
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
.rank-text {
|
||||
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
@@ -189,7 +192,10 @@ exports[`Test Render Wakatime Card should render correctly with compact layout w
|
||||
/* Selector detects Firefox */
|
||||
.stat { font-size:12px; }
|
||||
}
|
||||
|
||||
.stagger {
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
.rank-text {
|
||||
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
|
||||
@@ -171,13 +171,18 @@ describe("Test /api/", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not store cache when error", async () => {
|
||||
it("should set shorter cache when error", async () => {
|
||||
const { req, res } = faker({}, error);
|
||||
await api(req, res);
|
||||
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
["Content-Type", "image/svg+xml"],
|
||||
["Cache-Control", `no-cache, no-store, must-revalidate`],
|
||||
[
|
||||
"Cache-Control",
|
||||
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* @file Tests for the status/pat-info cloud function.
|
||||
*/
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { jest } from "@jest/globals";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import patInfo, { RATE_LIMIT_SECONDS } from "../api/status/pat-info.js";
|
||||
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
const successData = {
|
||||
data: {
|
||||
rateLimit: {
|
||||
remaining: 4986,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const faker = (query) => {
|
||||
const req = {
|
||||
query: { ...query },
|
||||
};
|
||||
const res = {
|
||||
setHeader: jest.fn(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
return { req, res };
|
||||
};
|
||||
|
||||
const rate_limit_error = {
|
||||
errors: [
|
||||
{
|
||||
type: "RATE_LIMITED",
|
||||
message: "API rate limit exceeded for user ID.",
|
||||
},
|
||||
],
|
||||
data: {
|
||||
rateLimit: {
|
||||
resetAt: Date.now(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const other_error = {
|
||||
errors: [
|
||||
{
|
||||
type: "SOME_ERROR",
|
||||
message: "This is a error",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const bad_credentials_error = {
|
||||
message: "Bad credentials",
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
describe("Test /api/status/pat-info", () => {
|
||||
beforeAll(() => {
|
||||
// reset patenv first so that dotenv doesn't populate them with local envs
|
||||
process.env = {};
|
||||
process.env.PAT_1 = "testPAT1";
|
||||
process.env.PAT_2 = "testPAT2";
|
||||
process.env.PAT_3 = "testPAT3";
|
||||
process.env.PAT_4 = "testPAT4";
|
||||
});
|
||||
|
||||
it("should return only 'validPATs' if all PATs are valid", async () => {
|
||||
mock
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, rate_limit_error)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.reply(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await patInfo(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(
|
||||
JSON.stringify(
|
||||
{
|
||||
validPATs: ["PAT_2", "PAT_3", "PAT_4"],
|
||||
expiredPATs: [],
|
||||
exhaustedPATs: ["PAT_1"],
|
||||
suspendedPATs: [],
|
||||
errorPATs: [],
|
||||
details: {
|
||||
PAT_1: {
|
||||
status: "exhausted",
|
||||
remaining: 0,
|
||||
resetIn: "0 minutes",
|
||||
},
|
||||
PAT_2: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
PAT_3: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
PAT_4: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("should return `errorPATs` if a PAT causes an error to be thrown", async () => {
|
||||
mock
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, other_error)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.reply(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await patInfo(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(
|
||||
JSON.stringify(
|
||||
{
|
||||
validPATs: ["PAT_2", "PAT_3", "PAT_4"],
|
||||
expiredPATs: [],
|
||||
exhaustedPATs: [],
|
||||
suspendedPATs: [],
|
||||
errorPATs: ["PAT_1"],
|
||||
details: {
|
||||
PAT_1: {
|
||||
status: "error",
|
||||
error: {
|
||||
type: "SOME_ERROR",
|
||||
message: "This is a error",
|
||||
},
|
||||
},
|
||||
PAT_2: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
PAT_3: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
PAT_4: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("should return `expiredPaths` if a PAT returns a 'Bad credentials' error", async () => {
|
||||
mock
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(404, bad_credentials_error)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.reply(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await patInfo(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(
|
||||
JSON.stringify(
|
||||
{
|
||||
validPATs: ["PAT_2", "PAT_3", "PAT_4"],
|
||||
expiredPATs: ["PAT_1"],
|
||||
exhaustedPATs: [],
|
||||
suspendedPATs: [],
|
||||
errorPATs: [],
|
||||
details: {
|
||||
PAT_1: {
|
||||
status: "expired",
|
||||
},
|
||||
PAT_2: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
PAT_3: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
PAT_4: {
|
||||
status: "valid",
|
||||
remaining: 4986,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error if something goes wrong", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").networkError();
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await patInfo(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith("Something went wrong: Network Error");
|
||||
});
|
||||
|
||||
it("should have proper cache when no error is thrown", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").reply(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await patInfo(req, res);
|
||||
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
["Content-Type", "application/json"],
|
||||
["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`],
|
||||
]);
|
||||
});
|
||||
|
||||
it("should have proper cache when error is thrown", async () => {
|
||||
mock.reset();
|
||||
mock.onPost("https://api.github.com/graphql").networkError();
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await patInfo(req, res);
|
||||
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
["Content-Type", "application/json"],
|
||||
["Cache-Control", "no-store"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -78,17 +78,16 @@ describe("Test renderStatsCard", () => {
|
||||
|
||||
it("should render with custom width set", () => {
|
||||
document.body.innerHTML = renderStatsCard(stats);
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "450");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "495");
|
||||
|
||||
document.body.innerHTML = renderStatsCard(stats, { card_width: 500 });
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "500");
|
||||
document.body.innerHTML = renderStatsCard(stats, { card_width: 400 });
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "400");
|
||||
});
|
||||
|
||||
it("should render with custom width set and limit minimum width", () => {
|
||||
document.body.innerHTML = renderStatsCard(stats, { card_width: 1 });
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "420");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
|
||||
|
||||
// Test default minimum card width without rank circle.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
card_width: 1,
|
||||
hide_rank: true,
|
||||
@@ -98,7 +97,6 @@ describe("Test renderStatsCard", () => {
|
||||
"305.81250000000006",
|
||||
);
|
||||
|
||||
// Test minimum card width with rank and icons.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
card_width: 1,
|
||||
hide_rank: true,
|
||||
@@ -106,24 +104,22 @@ describe("Test renderStatsCard", () => {
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute(
|
||||
"width",
|
||||
"322.81250000000006",
|
||||
"305.81250000000006",
|
||||
);
|
||||
|
||||
// Test minimum card width with icons but without rank.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
card_width: 1,
|
||||
hide_rank: false,
|
||||
show_icons: true,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "437");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "356");
|
||||
|
||||
// Test minimum card width without icons or rank.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
card_width: 1,
|
||||
hide_rank: false,
|
||||
show_icons: false,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "420");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
|
||||
});
|
||||
|
||||
it("should render default colors properly", () => {
|
||||
@@ -316,7 +312,7 @@ describe("Test renderStatsCard", () => {
|
||||
|
||||
expect(
|
||||
document.body.getElementsByTagName("svg")[0].getAttribute("width"),
|
||||
).toBe("287");
|
||||
).toBe("270");
|
||||
});
|
||||
|
||||
it("should render translations", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { jest } from "@jest/globals";
|
||||
import "@testing-library/jest-dom";
|
||||
import { retryer } from "../src/common/retryer.js";
|
||||
import { retryer, RETRIES } from "../src/common/retryer.js";
|
||||
import { logger } from "../src/common/utils.js";
|
||||
|
||||
const fetcher = jest.fn((variables, token) => {
|
||||
@@ -45,7 +45,7 @@ describe("Test Retryer", () => {
|
||||
try {
|
||||
res = await retryer(fetcherFail, {});
|
||||
} catch (err) {
|
||||
expect(fetcherFail).toBeCalledTimes(8);
|
||||
expect(fetcherFail).toBeCalledTimes(RETRIES + 1);
|
||||
expect(err.message).toBe("Maximum retries exceeded");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* @file Tests for the status/up cloud function.
|
||||
*/
|
||||
import { jest } from "@jest/globals";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import up, { RATE_LIMIT_SECONDS } from "../api/status/up.js";
|
||||
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
const successData = {
|
||||
rateLimit: {
|
||||
remaining: 4986,
|
||||
},
|
||||
};
|
||||
|
||||
const faker = (query) => {
|
||||
const req = {
|
||||
query: { ...query },
|
||||
};
|
||||
const res = {
|
||||
setHeader: jest.fn(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
return { req, res };
|
||||
};
|
||||
|
||||
const rate_limit_error = {
|
||||
errors: [
|
||||
{
|
||||
type: "RATE_LIMITED",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const bad_credentials_error = {
|
||||
message: "Bad credentials",
|
||||
};
|
||||
|
||||
const shields_up = {
|
||||
schemaVersion: 1,
|
||||
label: "Public Instance",
|
||||
isError: true,
|
||||
message: "up",
|
||||
color: "brightgreen",
|
||||
};
|
||||
const shields_down = {
|
||||
schemaVersion: 1,
|
||||
label: "Public Instance",
|
||||
isError: true,
|
||||
message: "down",
|
||||
color: "red",
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
describe("Test /api/status/up", () => {
|
||||
it("should return `true` if request was successful", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(true);
|
||||
});
|
||||
|
||||
it("should return `false` if all PATs are rate limited", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(false);
|
||||
});
|
||||
|
||||
it("should return JSON `true` if request was successful and type='json'", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
||||
|
||||
const { req, res } = faker({ type: "json" }, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith({ up: true });
|
||||
});
|
||||
|
||||
it("should return JSON `false` if all PATs are rate limited and type='json'", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);
|
||||
|
||||
const { req, res } = faker({ type: "json" }, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith({ up: false });
|
||||
});
|
||||
|
||||
it("should return UP shields.io config if request was successful and type='shields'", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
||||
|
||||
const { req, res } = faker({ type: "shields" }, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(shields_up);
|
||||
});
|
||||
|
||||
it("should return DOWN shields.io config if all PATs are rate limited and type='shields'", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);
|
||||
|
||||
const { req, res } = faker({ type: "shields" }, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(shields_down);
|
||||
});
|
||||
|
||||
it("should return `true` if the first PAT is rate limited but the second PATs works", async () => {
|
||||
mock
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, rate_limit_error)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(true);
|
||||
});
|
||||
|
||||
it("should return `true` if the first PAT has 'Bad credentials' but the second PAT works", async () => {
|
||||
mock
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(404, bad_credentials_error)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(true);
|
||||
});
|
||||
|
||||
it("should return `false` if all pats have 'Bad credentials'", async () => {
|
||||
mock
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.reply(404, bad_credentials_error);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(false);
|
||||
});
|
||||
|
||||
it("should throw an error if the request fails", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").networkError();
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
||||
expect(res.send).toBeCalledWith(false);
|
||||
});
|
||||
|
||||
it("should have proper cache when no error is thrown", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
["Content-Type", "application/json"],
|
||||
["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`],
|
||||
]);
|
||||
});
|
||||
|
||||
it("should have proper cache when error is thrown", async () => {
|
||||
mock.onPost("https://api.github.com/graphql").networkError();
|
||||
|
||||
const { req, res } = faker({}, {});
|
||||
await up(req, res);
|
||||
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
["Content-Type", "application/json"],
|
||||
["Cache-Control", "no-store"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,177 +0,0 @@
|
||||
<svg
|
||||
width="800"
|
||||
height="195"
|
||||
viewBox="0 0 800 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="799"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(715, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,249 +0,0 @@
|
||||
<svg
|
||||
width="467"
|
||||
height="195"
|
||||
viewBox="0 0 467 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #abd200;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #68b587;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #68b587;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #b7d364;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #abd200;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #abd200;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#0044ff"
|
||||
width="466"
|
||||
fill="#0a0f0b"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(390.5, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">
|
||||
Contributed to (last year):
|
||||
</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -1,179 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="195"
|
||||
viewBox="0 0 450 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">
|
||||
Hello world, this is a very very very very very long title, Rank: A+
|
||||
</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Hello world, this is a very very very very very long title
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,164 +0,0 @@
|
||||
<svg
|
||||
width="524.375"
|
||||
height="195"
|
||||
viewBox="0 0 524.375 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">
|
||||
Hello world, this is a very very very very very long title, Rank: A+
|
||||
</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="523.375"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Hello world, this is a very very very very very long title
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,164 +0,0 @@
|
||||
<svg
|
||||
width="524.375"
|
||||
height="195"
|
||||
viewBox="0 0 524.375 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">
|
||||
Hello world, this is a very very very very very long title, Rank: A+
|
||||
</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="523.375"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Hello world, this is a very very very very very long title
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,152 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="150"
|
||||
viewBox="0 0 450 150"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total PRs: 1, Total Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 25)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,177 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="195"
|
||||
viewBox="0 0 450 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="0"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,162 +0,0 @@
|
||||
<svg
|
||||
width="370.1875000000001"
|
||||
height="195"
|
||||
viewBox="0 0 370.1875000000001 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="369.1875000000001"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,169 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="165"
|
||||
viewBox="0 0 450 165"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 25)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,249 +0,0 @@
|
||||
<svg
|
||||
width="467"
|
||||
height="645"
|
||||
viewBox="0 0 467 645"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="466"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(390.5, 272.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 200)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 300)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 400)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<svg
|
||||
data-testid="icon"
|
||||
class="icon"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<text class="stat bold" x="25" y="12.5">
|
||||
Contributed to (last year):
|
||||
</text>
|
||||
<text class="stat bold" x="219.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -1,185 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="195"
|
||||
viewBox="0 0 450 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">
|
||||
Статистика
|
||||
GitHub
|
||||
пользователя
|
||||
Cateline Mnemosyne, Rank: A+
|
||||
</title>
|
||||
<desc id="descId">
|
||||
Всего звезд: 1, Всего коммитов in 2023 : 1, Всего pull request`ов: 1, Всего
|
||||
issue: 1, Внёс вклад в (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Статистика
|
||||
GitHub
|
||||
пользователя
|
||||
Cateline Mnemosyne
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Всего звезд:</text>
|
||||
<text class="stat bold" x="249.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Всего коммитов (2023):</text>
|
||||
<text class="stat bold" x="249.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Всего pull request`ов:</text>
|
||||
<text class="stat bold" x="249.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Всего issue:</text>
|
||||
<text class="stat bold" x="249.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Внёс вклад в (last year):</text>
|
||||
<text class="stat bold" x="249.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -1,177 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="195"
|
||||
viewBox="0 0 450 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #fe428e;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #a9fef7;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #a9fef7;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #f8d847;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #fe428e;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #fe428e;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#141321"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,177 +0,0 @@
|
||||
<svg
|
||||
width="450"
|
||||
height="195"
|
||||
viewBox="0 0 450 195"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="descId"
|
||||
>
|
||||
<title id="titleId">Cateline Mnemosyne's GitHub Stats, Rank: A+</title>
|
||||
<desc id="descId">
|
||||
Total Stars Earned: 1, Total Commits in 2023 : 1, Total PRs: 1, Total
|
||||
Issues: 1, Contributed to (last year): 1
|
||||
</desc>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #2f80ed;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header {
|
||||
font-size: 15.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat {
|
||||
font: 600 14px "Segoe UI", Ubuntu, "Helvetica Neue", Sans-Serif;
|
||||
fill: #434d58;
|
||||
}
|
||||
@supports (-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.stat {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font: 800 24px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold {
|
||||
font-weight: 400;
|
||||
}
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="449"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">
|
||||
Cateline Mnemosyne's GitHub Stats
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
<g data-testid="rank-circle" transform="translate(365, 47.5)">
|
||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||
<g class="rank-text">
|
||||
<text
|
||||
x="-5"
|
||||
y="3"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
>
|
||||
A+
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<svg x="0" y="0">
|
||||
<g transform="translate(0, 0)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 450ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Stars Earned:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="stars">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 25)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 600ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Commits (2023):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="commits">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 50)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 750ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total PRs:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="prs">1</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 75)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 900ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Total Issues:</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="issues">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(0, 100)">
|
||||
<g
|
||||
class="stagger"
|
||||
style="animation-delay: 1050ms"
|
||||
transform="translate(25, 0)"
|
||||
>
|
||||
<text class="stat bold" y="12.5">Contributed to (last year):</text>
|
||||
<text class="stat bold" x="199.01" y="12.5" data-testid="contribs">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,152 +0,0 @@
|
||||
import "jest-svg-snapshot";
|
||||
import "@testing-library/jest-dom";
|
||||
import renderStatsCard from "../../src/cards/stats-card";
|
||||
import prettier from "prettier";
|
||||
|
||||
const STATS_DATA = {
|
||||
name: "Cateline Mnemosyne",
|
||||
totalPRs: 1,
|
||||
totalCommits: 7,
|
||||
totalIssues: 1,
|
||||
totalStars: 1,
|
||||
contributedTo: 1,
|
||||
rank: {
|
||||
level: "A+",
|
||||
score: 50.893750297869225,
|
||||
},
|
||||
};
|
||||
|
||||
const format = (svg) => prettier.format(svg, { parser: "html" });
|
||||
|
||||
describe("statsCard", () => {
|
||||
it("should match default stat card", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, { disable_animations: true }),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option hide", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
hide: ["commits", "stars"],
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option hide_border", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
hide_border: true,
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option hide_title", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
hide_title: true,
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option hide_rank", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
hide_rank: true,
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option theme", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
theme: "radical",
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option card_width", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
card_width: 800,
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option custom_title", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
custom_title:
|
||||
"Hello world, this is a very very very very very long title",
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
it("option custom_title hideRank", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
hide_rank: true,
|
||||
custom_title:
|
||||
"Hello world, this is a very very very very very long title",
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
|
||||
it("option custom_title hideRank card_width", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
card_width: 300,
|
||||
hide_rank: true,
|
||||
custom_title:
|
||||
"Hello world, this is a very very very very very long title",
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
|
||||
it("option line_height show_icons", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
line_height: 100,
|
||||
show_icons: true,
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
|
||||
it("option locale", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
locale: "ru",
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
|
||||
it("option colors", () => {
|
||||
const svg = format(
|
||||
renderStatsCard(STATS_DATA, {
|
||||
disable_animations: true,
|
||||
border_color: "0044ff",
|
||||
title_color: "abd200",
|
||||
icon_color: "b7d364",
|
||||
text_color: "68b587",
|
||||
bg_color: "0a0f0b",
|
||||
show_icons: true,
|
||||
}),
|
||||
);
|
||||
expect(svg).toMatchSVGSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
"functions": {
|
||||
"api/*.js": {
|
||||
"memory": 128,
|
||||
"maxDuration": 10
|
||||
"maxDuration": 30
|
||||
}
|
||||
},
|
||||
"redirects": [
|
||||
|
||||