Skip to content

Commit

Permalink
Add a bunch of test cases to navigate fn (many failing, mostly punyco…
Browse files Browse the repository at this point in the history
…de related)
  • Loading branch information
tom-sherman committed Nov 8, 2024
1 parent 4ee97d6 commit 9bffe65
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 3 deletions.
127 changes: 127 additions & 0 deletions packages/atproto-browser/lib/navigation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { test, vi, expect, describe, afterAll } from "vitest";
import { navigateAtUri } from "./navigation";

vi.mock("server-only", () => {
return {
// mock server-only module
};
});

class RedirectError extends Error {
constructor(public readonly location: string) {
super(`Redirecting to ${location}`);
}
}

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

const PATH_SUFFIXES = [
"",
"/collection",
"/collection/rkey",
"/collection/rkey",
];

const makeValidCases = (authority: string) =>
PATH_SUFFIXES.flatMap((suffix) => {
const result = `/at/${authority}${suffix}`;
return [
[`${authority}${suffix}`, result],
[`at://${authority}${suffix}`, result],
];
});

const VALID_CASES = [
...makeValidCases("valid-handle.com"),
...makeValidCases("did:plc:hello"),
...makeValidCases("did:web:hello"),

// punycode
...PATH_SUFFIXES.flatMap((suffix) => {
const result = `/at/xn--maana-pta.com${suffix}`;
return [
[`mañana.com${suffix}`, result],
[`at://mañana.com${suffix}`, result],
];
}),

["@valid-handle.com", "/at/valid-handle.com"],
];

describe("navigates valid input", () => {
test.each(VALID_CASES)("%s -> %s", async (input, expectedRedirect) => {
await expect(navigateAtUri(input)).rejects.toThrowError(
new RedirectError(expectedRedirect),
);
});
});

describe("strips whitespace and zero-width characters from valid input", () => {
test.each(VALID_CASES.map((c) => [...c]))(
"%s -> %s",
async (input, expectedRedirect) => {
await expect(
navigateAtUri(` ${input}\u200B\u200D\uFEFF \u202C`),
).rejects.toThrowError(new RedirectError(expectedRedirect));
},
);
});

describe("shows error on invalid input", () => {
test.each([
["@", "Invalid URI: @"],
["@invalid", "Invalid URI: @invalid"],
// ["invalid", "Invalid URI: invalid"],
])('"%s" -> "%s"', async (input, expectedError) => {
expect((await navigateAtUri(input)).error).toMatch(expectedError);
});
});

const originalFetch = global.fetch;
const mockFetch = vi.fn();
global.fetch = mockFetch;
afterAll(() => {
global.fetch = originalFetch;
});

describe("valid http input with link", () => {
// Include only cases with the protocol prefix
test.each(VALID_CASES.filter((c) => c[0]!.startsWith("at://")))(
'valid http input with "%s" -> "%s"',
async (link, expectedUri) => {
mockFetch.mockResolvedValueOnce(
new Response(/*html*/ `
<html>
<head>
<link rel="alternate" href="${link}" />
</head>
<body></body>
</html>
`),
);

await expect(navigateAtUri("http://example.com")).rejects.toThrowError(
new RedirectError(expectedUri),
);
},
);
});

test("valid http input without included at uri", async () => {
mockFetch.mockResolvedValueOnce(
new Response(/*html*/ `
<html>
<head></head>
<body></body>
</html>
`),
);

expect(await navigateAtUri("http://example.com")).toEqual({
error: "No AT URI found in http://example.com",
});
});
4 changes: 3 additions & 1 deletion packages/atproto-browser/lib/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { domainToASCII } from "url";

export async function navigateAtUri(input: string) {
// Remove all zero-width characters and weird control codes from the input
const sanitizedInput = input.replace(/[\u200B-\u200D\uFEFF\u202C]/g, "");
const sanitizedInput = input
.replace(/[\u200B-\u200D\uFEFF\u202C]/g, "")
.trim();

// Try punycode encoding the input as a domain name and parse it as a handle
const handle = parseHandle(domainToASCII(sanitizedInput) || sanitizedInput);
Expand Down
7 changes: 5 additions & 2 deletions packages/atproto-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"test": "vitest"
},
"dependencies": {
"@atproto/did": "^0.1.1",
Expand Down Expand Up @@ -36,6 +37,8 @@
"eslint": "^8",
"eslint-config-next": "15.0.0-rc.0",
"tsx": "^4.16.5",
"typescript": "^5"
"typescript": "^5",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.0.4"
}
}
9 changes: 9 additions & 0 deletions packages/atproto-browser/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "vitest/config";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [tsconfigPaths()],
test: {
environment: "node",
},
});
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9bffe65

Please sign in to comment.