Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 245 additions & 0 deletions app/[...githubPath]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { redirect } from "next/navigation";
import GitHubPathPage from "./page";

vi.mock("next/navigation", () => ({
redirect: vi.fn(),
}));

vi.mock("@/utils/github", () => ({
parseGitHubURL: vi.fn(),
}));

import { parseGitHubURL } from "@/utils/github";

const mockedRedirect = vi.mocked(redirect);
const mockedParseGitHubURL = vi.mocked(parseGitHubURL);

describe("GitHubPathPage", () => {
beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
vi.resetAllMocks();
});

describe("when GitHub path is valid", () => {
it("redirects to correct org/repo route for simple repository", async () => {
mockedParseGitHubURL.mockReturnValue("facebook/react");

const params = Promise.resolve({ githubPath: ["facebook", "react"] });

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/facebook/react"
);
expect(mockedRedirect).toHaveBeenCalledWith("/facebook/react");
});

it("redirects for repository with special characters", async () => {
mockedParseGitHubURL.mockReturnValue("vercel/next.js");

const params = Promise.resolve({ githubPath: ["vercel", "next.js"] });

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/vercel/next.js"
);
expect(mockedRedirect).toHaveBeenCalledWith("/vercel/next.js");
});

it("redirects for repository with hyphens", async () => {
mockedParseGitHubURL.mockReturnValue("facebook/react-native");

const params = Promise.resolve({
githubPath: ["facebook", "react-native"],
});

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/facebook/react-native"
);
expect(mockedRedirect).toHaveBeenCalledWith("/facebook/react-native");
});

it("handles complex paths with additional segments", async () => {
mockedParseGitHubURL.mockReturnValue("microsoft/typescript");

const params = Promise.resolve({
githubPath: ["microsoft", "typescript", "tree", "main", "src"],
});

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/microsoft/typescript/tree/main/src"
);
expect(mockedRedirect).toHaveBeenCalledWith("/microsoft/typescript");
});

it("handles single segment paths", async () => {
mockedParseGitHubURL.mockReturnValue("nodejs/node");

const params = Promise.resolve({ githubPath: ["nodejs", "node"] });

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/nodejs/node"
);
expect(mockedRedirect).toHaveBeenCalledWith("/nodejs/node");
});
});

describe("when GitHub path is invalid", () => {
it("returns error message when parseGitHubURL returns null", async () => {
mockedParseGitHubURL.mockReturnValue(null);

const params = Promise.resolve({ githubPath: ["invalid"] });

const result = await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/invalid"
);
expect(mockedRedirect).not.toHaveBeenCalled();
// Check that it returns JSX with the error message
expect(result).toBeDefined();
expect(result.type).toBe("div");
expect(result.props.children).toBe(
"Invalid GitHub URL. Please enter a valid GitHub repository."
);
});

it("returns error message for empty path", async () => {
mockedParseGitHubURL.mockReturnValue(null);

const params = Promise.resolve({ githubPath: [] });

const result = await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith("https://github.com/");
expect(mockedRedirect).not.toHaveBeenCalled();
// Check that it returns JSX with the error message
expect(result).toBeDefined();
expect(result.type).toBe("div");
expect(result.props.children).toBe(
"Invalid GitHub URL. Please enter a valid GitHub repository."
);
});

it("returns error message for malformed repository names", async () => {
mockedParseGitHubURL.mockReturnValue(null);

const params = Promise.resolve({
githubPath: ["not-a-valid-repo-format"],
});

const result = await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/not-a-valid-repo-format"
);
expect(mockedRedirect).not.toHaveBeenCalled();
// Check that it returns JSX with the error message
expect(result).toBeDefined();
expect(result.type).toBe("div");
expect(result.props.children).toBe(
"Invalid GitHub URL. Please enter a valid GitHub repository."
);
});
});

describe("edge cases", () => {
it("handles URLs with encoded characters", async () => {
mockedParseGitHubURL.mockReturnValue("user/repo-with-special-chars");

const params = Promise.resolve({
githubPath: ["user", "repo%20with%20spaces"],
});

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/user/repo%20with%20spaces"
);
expect(mockedRedirect).toHaveBeenCalledWith(
"/user/repo-with-special-chars"
);
});

it("handles very long paths", async () => {
mockedParseGitHubURL.mockReturnValue("org/repo");

const longPath = [
"org",
"repo",
"tree",
"main",
"src",
"components",
"ui",
"button",
];
const params = Promise.resolve({ githubPath: longPath });

await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
`https://github.com/${longPath.join("/")}`
);
expect(mockedRedirect).toHaveBeenCalledWith("/org/repo");
});

it("handles path with only organization name", async () => {
mockedParseGitHubURL.mockReturnValue(null);

const params = Promise.resolve({ githubPath: ["facebook"] });

const result = await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(
"https://github.com/facebook"
);
expect(mockedRedirect).not.toHaveBeenCalled();
// Check that it returns JSX with the error message
expect(result).toBeDefined();
expect(result.type).toBe("div");
expect(result.props.children).toBe(
"Invalid GitHub URL. Please enter a valid GitHub repository."
);
});
});

describe("parseGitHubURL integration", () => {
it("properly constructs GitHub URLs for parsing", async () => {
const testCases = [
{
input: ["microsoft", "typescript"],
expected: "https://github.com/microsoft/typescript",
},
{
input: ["vercel", "next.js", "blob", "main", "README.md"],
expected: "https://github.com/vercel/next.js/blob/main/README.md",
},
{
input: ["facebook", "react", "issues"],
expected: "https://github.com/facebook/react/issues",
},
];

for (const testCase of testCases) {
mockedParseGitHubURL.mockReturnValue("owner/repo");

const params = Promise.resolve({ githubPath: testCase.input });
await GitHubPathPage({ params });

expect(mockedParseGitHubURL).toHaveBeenCalledWith(testCase.expected);
}
});
});
});
1 change: 1 addition & 0 deletions app/[...githubPath]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import { redirect } from "next/navigation";
import { parseGitHubURL } from "@/utils/github";

Expand Down
97 changes: 97 additions & 0 deletions app/[org]/[repo]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { describe, expect, it } from "vitest";
import { generateMetadata } from "./page";

describe("RepoPage", () => {
describe("generateMetadata", () => {
it("generates correct metadata for repository", async () => {
const params = Promise.resolve({ org: "facebook", repo: "react" });

const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title: "facebook/react Related Repos - GitRelate(d)",
});
});

it("generates metadata for different repository names", async () => {
const testCases = [
{
org: "microsoft",
repo: "typescript",
expected: "microsoft/typescript Related Repos - GitRelate(d)",
},
{
org: "vercel",
repo: "next.js",
expected: "vercel/next.js Related Repos - GitRelate(d)",
},
{
org: "nodejs",
repo: "node",
expected: "nodejs/node Related Repos - GitRelate(d)",
},
{
org: "facebook",
repo: "react-native",
expected: "facebook/react-native Related Repos - GitRelate(d)",
},
];

for (const { org, repo, expected } of testCases) {
const params = Promise.resolve({ org, repo });
const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title: expected,
});
}
});

it("handles special characters in repository names", async () => {
const params = Promise.resolve({
org: "user",
repo: "repo-with-special.chars_123",
});

const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title: "user/repo-with-special.chars_123 Related Repos - GitRelate(d)",
});
});

it("handles repository names with dots", async () => {
const params = Promise.resolve({ org: "vercel", repo: "next.js" });

const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title: "vercel/next.js Related Repos - GitRelate(d)",
});
});

it("handles repository names with underscores and hyphens", async () => {
const params = Promise.resolve({ org: "some-org", repo: "my_repo" });

const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title: "some-org/my_repo Related Repos - GitRelate(d)",
});
});

it("handles long repository names", async () => {
const params = Promise.resolve({
org: "organization-with-long-name",
repo: "repository-with-very-long-name-containing-multiple-words",
});

const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title:
"organization-with-long-name/repository-with-very-long-name-containing-multiple-words Related Repos - GitRelate(d)",
});
});
});
});
Loading