Files
stats-action/src/index.test.ts

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 });
});
});