Commit 45941f6c authored by anuraghazra's avatar anuraghazra
Browse files

test: added test & refactored files

parent 223bd659
Loading
Loading
Loading
Loading
+4 −136
Original line number Diff line number Diff line
const axios = require("axios");
const { renderError, kFormatter } = require("../utils");
require("dotenv").config();

async function fetchStats(username) {
  if (!username) throw Error("Invalid username");

  const res = await axios({
    url: "https://api.github.com/graphql",
    method: "post",
    headers: {
      Authorization: `bearer ${process.env.GITHUB_TOKEN}`,
    },
    data: {
      query: `
        query userInfo($login: String!) {
          user(login: $login) {
            name
            repositoriesContributedTo(first: 100, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
              totalCount
            }
            contributionsCollection {
              totalCommitContributions
            }
            pullRequests(first: 100) {
              totalCount
            }
            issues(first: 100) {
              totalCount
            }
            repositories(first: 100) {
              nodes {
                stargazers {
                  totalCount
                }
              }
            }
          }
        }
      `,
      variables: {
        login: username,
      },
    },
  });

  const stats = {
    name: "",
    totalPRs: 0,
    totalCommits: 0,
    totalIssues: 0,
    totalStars: 0,
    contributedTo: 0,
  };

  if (res.data.errors) {
    console.log(res.data.errors);
    throw Error("Could not fetch user");
  }

  const user = res.data.data.user;

  stats.name = user.name;
  stats.totalIssues = user.issues.totalCount;
  stats.totalCommits = user.contributionsCollection.totalCommitContributions;
  stats.totalPRs = user.pullRequests.totalCount;
  stats.contributedTo = user.repositoriesContributedTo.totalCount;

  stats.totalStars = user.repositories.nodes.reduce((prev, curr) => {
    return prev + curr.stargazers.totalCount;
  }, 0);

  return stats;
}

const createTextNode = (icon, label, value, lheight) => {
  const classname = icon === "" && "star-icon";
  return `
    <tspan x="25" dy="${lheight}" class="stat bold">
    <tspan class="icon ${classname}" fill="#4C71F2">${icon}</tspan> ${label}:</tspan>
    <tspan x="155" dy="0" class="stat">${kFormatter(value)}</tspan>
  `;
};

const renderSVG = (stats, options) => {
  const {
    name,
    totalStars,
    totalCommits,
    totalIssues,
    totalPRs,
    contributedTo,
  } = stats;
  const { hide, show_icons, hide_border, line_height } = options || {};

  const lheight = line_height || 25;

  const STAT_MAP = {
    stars: createTextNode("", "Total Stars", totalStars, lheight),
    commits: createTextNode("🕗", "Total Commits", totalCommits, lheight),
    prs: createTextNode("🔀", "Total PRs", totalPRs, lheight),
    issues: createTextNode("", "Total Issues", totalIssues, lheight),
    contribs: createTextNode("📕", "Contributed to", contributedTo, lheight),
  };

  const statItems = Object.keys(STAT_MAP)
    .filter((key) => !hide.includes(key))
    .map((key) => STAT_MAP[key]);

  const height = 45 + (statItems.length + 1) * lheight;

  return `
    <svg width="495" height="${height}" viewBox="0 0 495 ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
      <style>
      .header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
      .stat { font: 600 14px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333 }
      .star-icon { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; }
      .bold { font-weight: 700 }
      .icon {
        display: none;
        ${!!show_icons && "display: block"}
      }
      </style>
      ${
        !hide_border &&
        `<rect x="0.5" y="0.5" width="494" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>`
      }
     
      <text x="25" y="35" class="header">${name}'s GitHub Stats</text>
      <text y="45">
        ${statItems}
      </text>
    </svg>
  `;
};
const { renderError } = require("../src/utils");
const fetchStats = require("../src/fetchStats");
const renderStatsCard = require("../src/renderStatsCard");

module.exports = async (req, res) => {
  const { username, hide, hide_border, show_icons, line_height } = req.query;
@@ -146,7 +14,7 @@ module.exports = async (req, res) => {
  }

  res.send(
    renderSVG(stats, {
    renderStatsCard(stats, {
      hide: JSON.parse(hide || "[]"),
      show_icons,
      hide_border,
+3 −115
Original line number Diff line number Diff line
const axios = require("axios");
const { renderError, kFormatter, encodeHTML } = require("../utils");
const { renderError } = require("../src/utils");
const fetchRepo = require("../src/fetchRepo");
const renderRepoCard = require("../src/renderRepoCard");
require("dotenv").config();

async function fetchRepo(username, reponame) {
  const res = await axios({
    url: "https://api.github.com/graphql",
    method: "post",
    headers: {
      Authorization: `bearer ${process.env.GITHUB_TOKEN}`,
    },
    data: {
      query: `
        fragment RepoInfo on Repository {
          name
          stargazers {
            totalCount
          }
          description
          primaryLanguage {
            color
            id
            name
          }
          forkCount
        }
        query getRepo($login: String!, $repo: String!) {
          user(login: $login) {
            repository(name: $repo) {
              ...RepoInfo
            }
          }
          organization(login: $login) {
            repository(name: $repo) {
              ...RepoInfo
            }
          }
        }
      `,
      variables: {
        login: username,
        repo: reponame,
      },
    },
  });

  const data = res.data.data;

  if (!data.user && !data.organization) {
    throw new Error("Not found");
  }

  if (data.organization === null && data.user) {
    if (!data.user.repository) {
      throw new Error("User Repository Not found");
    }
    return data.user.repository;
  }

  if (data.user === null && data.organization) {
    if (!data.organization.repository) {
      throw new Error("Organization Repository Not found");
    }
    return data.organization.repository;
  }
}

const renderRepoCard = (repo) => {
  const { name, description, primaryLanguage, stargazers, forkCount } = repo;
  const height = 120;
  const shiftText = primaryLanguage.name.length > 15 ? 0 : 30;

  let desc = description || "No description provided";
  if (desc.length > 55) {
    desc = `${description.slice(0, 55)}..`;
  }

  return `
    <svg width="400" height="${height}" viewBox="0 0 400 ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
      <style>
      .header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
      .stat { font: 600 14px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333 }
      .star-icon { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; }
      .bold { font-weight: 700 }
      .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: #586069 }
      .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #586069 }
      </style>
      <rect x="0.5" y="0.5" width="399" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
      <svg x="25" y="25" viewBox="0 0 16 16" version="1.1" width="16" height="16" fill="#586069">
        <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"></path>
      </svg>

      <text x="50" y="38" class="header">${name}</text>
      <text class="description" x="25" y="70">${encodeHTML(desc)}</text>
      
      <g transform="translate(30, 100)">
        <circle cx="0" cy="-5" r="6" fill="${primaryLanguage.color}" />
        <text class="gray" x="15">${primaryLanguage.name}</text>
      </g>
      
      <g transform="translate(${155 - shiftText}, 100)">
        <svg y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16" fill="#586069">
          <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"></path>
        </svg>
        <text class="gray" x="25">${kFormatter(stargazers.totalCount)}</text>
      </g>

      <g transform="translate(${220 - shiftText}, 100)">
        <svg y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16" fill="#586069">
          <path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path>
        </svg>
        <text class="gray" x="25">${kFormatter(forkCount)}</text>
      </g>
    </svg>
  `;
};

module.exports = async (req, res) => {
  const { username, repo } = req.query;

jest.config.js

0 → 100644
+3 −0
Original line number Diff line number Diff line
module.exports = {
  clearMocks: true,
};
+5025 −17

File changed.

Preview size limit exceeded, changes collapsed.

+8 −3
Original line number Diff line number Diff line
{
  "name": "github-readme-stats",
  "version": "1.0.0",
  "description": "",
  "description": "Dynamically generate stats for your github readmes",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    "test": "jest",
    "test:watch": "jest --watch"
  },
  "author": "Anurag Hazra",
  "license": "MIT",
  "devDependencies": {
    "axios": "^0.19.2"
    "@testing-library/dom": "^7.20.0",
    "@testing-library/jest-dom": "^5.11.0",
    "axios": "^0.19.2",
    "axios-mock-adapter": "^1.18.1",
    "jest": "^26.1.0"
  },
  "dependencies": {
    "dotenv": "^8.2.0"
Loading