mirror of
https://github.com/LukeHagar/stats-action.git
synced 2025-12-06 04:21:26 +00:00
337 lines
11 KiB
TypeScript
337 lines
11 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
formatBytes,
|
|
formatNumber,
|
|
aggregateLanguages,
|
|
calculateContributionStats,
|
|
calculateComputedStats,
|
|
} from "./index";
|
|
import type { ContributionsCollection, Language, ContributionStats } from "./Types";
|
|
|
|
describe("formatBytes", () => {
|
|
test("formats bytes correctly", () => {
|
|
expect(formatBytes(0)).toBe("0 B");
|
|
expect(formatBytes(512)).toBe("512 B");
|
|
expect(formatBytes(1023)).toBe("1023 B");
|
|
});
|
|
|
|
test("formats kilobytes correctly", () => {
|
|
expect(formatBytes(1024)).toBe("1.0 KB");
|
|
expect(formatBytes(1536)).toBe("1.5 KB");
|
|
expect(formatBytes(10240)).toBe("10.0 KB");
|
|
});
|
|
|
|
test("formats megabytes correctly", () => {
|
|
expect(formatBytes(1024 * 1024)).toBe("1.0 MB");
|
|
expect(formatBytes(1.5 * 1024 * 1024)).toBe("1.5 MB");
|
|
expect(formatBytes(100 * 1024 * 1024)).toBe("100.0 MB");
|
|
});
|
|
|
|
test("formats gigabytes correctly", () => {
|
|
expect(formatBytes(1024 * 1024 * 1024)).toBe("1.0 GB");
|
|
expect(formatBytes(2.5 * 1024 * 1024 * 1024)).toBe("2.5 GB");
|
|
});
|
|
});
|
|
|
|
describe("formatNumber", () => {
|
|
test("formats small numbers as-is", () => {
|
|
expect(formatNumber(0)).toBe("0");
|
|
expect(formatNumber(1)).toBe("1");
|
|
expect(formatNumber(999)).toBe("999");
|
|
});
|
|
|
|
test("formats thousands with K suffix", () => {
|
|
expect(formatNumber(1000)).toBe("1.0K");
|
|
expect(formatNumber(1500)).toBe("1.5K");
|
|
expect(formatNumber(10000)).toBe("10.0K");
|
|
expect(formatNumber(999999)).toBe("1000.0K");
|
|
});
|
|
|
|
test("formats millions with M suffix", () => {
|
|
expect(formatNumber(1000000)).toBe("1.0M");
|
|
expect(formatNumber(1500000)).toBe("1.5M");
|
|
expect(formatNumber(10000000)).toBe("10.0M");
|
|
});
|
|
});
|
|
|
|
describe("aggregateLanguages", () => {
|
|
test("aggregates languages from multiple repos", () => {
|
|
const repos = [
|
|
{
|
|
languages: {
|
|
edges: [
|
|
{ size: 1000, node: { name: "TypeScript", color: "#3178c6" } },
|
|
{ size: 500, node: { name: "JavaScript", color: "#f1e05a" } },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
languages: {
|
|
edges: [
|
|
{ size: 2000, node: { name: "TypeScript", color: "#3178c6" } },
|
|
{ size: 300, node: { name: "Python", color: "#3572A5" } },
|
|
],
|
|
},
|
|
},
|
|
];
|
|
|
|
const result = aggregateLanguages(repos);
|
|
|
|
expect(result.codeByteTotal).toBe(3800);
|
|
expect(result.languages).toHaveLength(3);
|
|
|
|
// Should be sorted by value descending
|
|
expect(result.languages[0].languageName).toBe("TypeScript");
|
|
expect(result.languages[0].value).toBe(3000);
|
|
expect(result.languages[0].percentage).toBeCloseTo(78.95, 1);
|
|
|
|
expect(result.languages[1].languageName).toBe("JavaScript");
|
|
expect(result.languages[1].value).toBe(500);
|
|
|
|
expect(result.languages[2].languageName).toBe("Python");
|
|
expect(result.languages[2].value).toBe(300);
|
|
});
|
|
|
|
test("handles empty repos", () => {
|
|
const repos: Array<{ languages: { edges: Array<{ size: number; node: { name: string; color: string } }> } }> = [];
|
|
const result = aggregateLanguages(repos);
|
|
|
|
expect(result.codeByteTotal).toBe(0);
|
|
expect(result.languages).toHaveLength(0);
|
|
});
|
|
|
|
test("handles repos with no languages", () => {
|
|
const repos = [{ languages: { edges: [] } }];
|
|
const result = aggregateLanguages(repos);
|
|
|
|
expect(result.codeByteTotal).toBe(0);
|
|
expect(result.languages).toHaveLength(0);
|
|
});
|
|
|
|
test("preserves language colors", () => {
|
|
const repos = [
|
|
{
|
|
languages: {
|
|
edges: [{ size: 1000, node: { name: "Rust", color: "#dea584" } }],
|
|
},
|
|
},
|
|
];
|
|
|
|
const result = aggregateLanguages(repos);
|
|
expect(result.languages[0].color).toBe("#dea584");
|
|
});
|
|
});
|
|
|
|
describe("calculateContributionStats", () => {
|
|
const createContributionsCollection = (
|
|
days: Array<{ date: string; contributionCount: number }>
|
|
): ContributionsCollection => ({
|
|
totalCommitContributions: 0,
|
|
restrictedContributionsCount: 0,
|
|
totalIssueContributions: 0,
|
|
totalRepositoryContributions: 0,
|
|
totalPullRequestContributions: 0,
|
|
totalPullRequestReviewContributions: 0,
|
|
contributionCalendar: {
|
|
totalContributions: days.reduce((sum, d) => sum + d.contributionCount, 0),
|
|
weeks: [{ contributionDays: days }],
|
|
},
|
|
});
|
|
|
|
test("calculates longest streak correctly", () => {
|
|
const collection = createContributionsCollection([
|
|
{ date: "2024-01-01", contributionCount: 1 },
|
|
{ date: "2024-01-02", contributionCount: 2 },
|
|
{ date: "2024-01-03", contributionCount: 0 },
|
|
{ date: "2024-01-04", contributionCount: 1 },
|
|
{ date: "2024-01-05", contributionCount: 1 },
|
|
{ date: "2024-01-06", contributionCount: 1 },
|
|
]);
|
|
|
|
const stats = calculateContributionStats(collection);
|
|
expect(stats.longestStreak).toBe(3);
|
|
});
|
|
|
|
test("calculates averages correctly", () => {
|
|
const collection = createContributionsCollection([
|
|
{ date: "2024-01-01", contributionCount: 10 },
|
|
{ date: "2024-01-02", contributionCount: 20 },
|
|
{ date: "2024-01-03", contributionCount: 30 },
|
|
{ date: "2024-01-04", contributionCount: 40 },
|
|
]);
|
|
|
|
const stats = calculateContributionStats(collection);
|
|
expect(stats.averagePerDay).toBe(25);
|
|
expect(stats.averagePerWeek).toBe(175);
|
|
});
|
|
|
|
test("calculates monthly breakdown correctly", () => {
|
|
const collection = createContributionsCollection([
|
|
{ date: "2024-01-15", contributionCount: 5 },
|
|
{ date: "2024-01-20", contributionCount: 10 },
|
|
{ date: "2024-02-10", contributionCount: 15 },
|
|
]);
|
|
|
|
const stats = calculateContributionStats(collection);
|
|
expect(stats.monthlyBreakdown).toHaveLength(2);
|
|
expect(stats.monthlyBreakdown[0]).toEqual({ month: "2024-01", contributions: 15 });
|
|
expect(stats.monthlyBreakdown[1]).toEqual({ month: "2024-02", contributions: 15 });
|
|
});
|
|
|
|
test("identifies most active day of week", () => {
|
|
// Create contributions heavily weighted toward Monday
|
|
const collection = createContributionsCollection([
|
|
{ date: "2024-01-01", contributionCount: 100 }, // Monday
|
|
{ date: "2024-01-02", contributionCount: 1 }, // Tuesday
|
|
{ date: "2024-01-08", contributionCount: 100 }, // Monday
|
|
{ date: "2024-01-09", contributionCount: 1 }, // Tuesday
|
|
]);
|
|
|
|
const stats = calculateContributionStats(collection);
|
|
expect(stats.mostActiveDay).toBe("Monday");
|
|
});
|
|
|
|
test("handles empty contribution data", () => {
|
|
const collection = createContributionsCollection([]);
|
|
const stats = calculateContributionStats(collection);
|
|
|
|
expect(stats.longestStreak).toBe(0);
|
|
expect(stats.currentStreak).toBe(0);
|
|
expect(stats.averagePerDay).toBe(0);
|
|
expect(stats.monthlyBreakdown).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("calculateComputedStats", () => {
|
|
const currentYear = new Date().getFullYear();
|
|
const lastYear = currentYear - 1;
|
|
|
|
const createRepoInfo = (overrides: Partial<{
|
|
stars: number;
|
|
forks: number;
|
|
isArchived: boolean;
|
|
isFork: boolean;
|
|
isPrivate: boolean;
|
|
topics: string[];
|
|
updatedAt: string;
|
|
createdAt: string;
|
|
languages: { edges: Array<{ size: number; node: { name: string; color: string } }> };
|
|
}> = {}) => ({
|
|
stars: 0,
|
|
forks: 0,
|
|
isArchived: false,
|
|
isFork: false,
|
|
isPrivate: false,
|
|
topics: [],
|
|
updatedAt: `${currentYear}-06-01T00:00:00Z`,
|
|
createdAt: `${lastYear}-01-01T00:00:00Z`,
|
|
languages: { edges: [] },
|
|
...overrides,
|
|
});
|
|
|
|
const createContributionStats = (monthlyBreakdown: Array<{ month: string; contributions: number }>): ContributionStats => ({
|
|
longestStreak: 0,
|
|
currentStreak: 0,
|
|
mostActiveDay: "Monday",
|
|
averagePerDay: 0,
|
|
averagePerWeek: 0,
|
|
averagePerMonth: 0,
|
|
monthlyBreakdown,
|
|
});
|
|
|
|
test("calculates repo statistics correctly", () => {
|
|
const repos = [
|
|
createRepoInfo({ stars: 10, isPrivate: false }),
|
|
createRepoInfo({ stars: 5, isPrivate: true }),
|
|
createRepoInfo({ isArchived: true }),
|
|
createRepoInfo({ isFork: true }),
|
|
createRepoInfo({ updatedAt: `${currentYear}-01-15T00:00:00Z` }),
|
|
createRepoInfo({ createdAt: `${currentYear}-03-01T00:00:00Z`, updatedAt: `${currentYear}-03-01T00:00:00Z` }),
|
|
];
|
|
|
|
const stats = calculateComputedStats(repos, [], createContributionStats([]));
|
|
|
|
expect(stats.totalRepos).toBe(6);
|
|
expect(stats.publicRepos).toBe(5);
|
|
expect(stats.privateRepos).toBe(1);
|
|
expect(stats.archivedRepos).toBe(1);
|
|
expect(stats.forkedRepos).toBe(1);
|
|
expect(stats.originalRepos).toBe(5);
|
|
expect(stats.reposWithStars).toBe(2);
|
|
expect(stats.reposCreatedThisYear).toBe(1);
|
|
expect(stats.averageStarsPerRepo).toBe(2.5); // 15 stars / 6 repos
|
|
});
|
|
|
|
test("calculates language statistics correctly", () => {
|
|
const topLanguages: Language[] = [
|
|
{ languageName: "TypeScript", color: "#3178c6", value: 1000, percentage: 50 },
|
|
{ languageName: "JavaScript", color: "#f1e05a", value: 500, percentage: 25 },
|
|
{ languageName: "Python", color: "#3572A5", value: 500, percentage: 25 },
|
|
];
|
|
|
|
const stats = calculateComputedStats([], topLanguages, createContributionStats([]));
|
|
|
|
expect(stats.languageCount).toBe(3);
|
|
expect(stats.primaryLanguage).toBe("TypeScript");
|
|
});
|
|
|
|
test("calculates year over year growth correctly", () => {
|
|
const contributionStats = createContributionStats([
|
|
{ month: `${lastYear}-01`, contributions: 50 },
|
|
{ month: `${lastYear}-02`, contributions: 50 },
|
|
{ month: `${currentYear}-01`, contributions: 75 },
|
|
{ month: `${currentYear}-02`, contributions: 75 },
|
|
]);
|
|
|
|
const stats = calculateComputedStats([], [], contributionStats);
|
|
|
|
expect(stats.contributionsLastYear).toBe(100);
|
|
expect(stats.contributionsThisYear).toBe(150);
|
|
expect(stats.yearOverYearGrowth).toBe(50); // 50% growth
|
|
});
|
|
|
|
test("identifies most productive month", () => {
|
|
const contributionStats = createContributionStats([
|
|
{ month: `${currentYear}-01`, contributions: 50 },
|
|
{ month: `${currentYear}-02`, contributions: 150 },
|
|
{ month: `${currentYear}-03`, contributions: 75 },
|
|
]);
|
|
|
|
const stats = calculateComputedStats([], [], contributionStats);
|
|
|
|
expect(stats.mostProductiveMonth).toEqual({
|
|
month: `${currentYear}-02`,
|
|
contributions: 150,
|
|
});
|
|
});
|
|
|
|
test("handles empty data gracefully", () => {
|
|
const stats = calculateComputedStats([], [], createContributionStats([]));
|
|
|
|
expect(stats.totalRepos).toBe(0);
|
|
expect(stats.languageCount).toBe(0);
|
|
expect(stats.primaryLanguage).toBe(null);
|
|
expect(stats.yearOverYearGrowth).toBe(null);
|
|
expect(stats.mostProductiveMonth).toBe(null);
|
|
expect(stats.totalTopics).toBe(0);
|
|
expect(stats.topTopics).toHaveLength(0);
|
|
});
|
|
|
|
test("aggregates topics correctly", () => {
|
|
const repos = [
|
|
createRepoInfo({ topics: ["typescript", "github-action", "automation"] }),
|
|
createRepoInfo({ topics: ["typescript", "cli"] }),
|
|
createRepoInfo({ topics: ["python", "automation"] }),
|
|
];
|
|
|
|
const stats = calculateComputedStats(repos, [], createContributionStats([]));
|
|
|
|
expect(stats.totalTopics).toBe(5);
|
|
expect(stats.allTopics).toEqual(["automation", "cli", "github-action", "python", "typescript"]);
|
|
|
|
// Top topics should be sorted by count
|
|
expect(stats.topTopics[0]).toEqual({ name: "typescript", count: 2 });
|
|
expect(stats.topTopics[1]).toEqual({ name: "automation", count: 2 });
|
|
});
|
|
});
|