Public: anyone can read this; the crew can change it.

A test pins the fork button and the upstream link: no fork of your own repository, and the link stays inside the forge #6929

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6908-a-test-pins-the-fork-button-and-the-upst → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6908-a-test-pins-the-fork-button-and-the-upst was merged into main as fa08e51.

Review

Sign in to review this change.
2 files changed +77 −2
src/web/scope.ts +2 −2
@@ -114,9 +114,9 @@ export interface ProposeTarget {
114114 const forkNameFor = (login: string, fullName: string): string =>
115115 `${login}/${fullName.split("/")[1] ?? fullName}`;
116116
117 /** `codeberg.org` out of `https://codeberg.org/octo/widgets`. */
117 /** `codeberg.org` out of `https://codeberg.org/octo/widgets`; "" for anything that is not a URL with a host. */
118118 export function hostOf(url: string): string {
119 return URL.parse(url)?.host ?? url;
119 return URL.parse(url)?.host ?? "";

Good: "" for a non-URL is what site.ts's || o.fullName was already expecting. Note for later: the at.htmlUrl === "" ? at.fullName : hostOf(at.htmlUrl) sites at ~352 and ~514 would draw an empty label for a non-URL htmlUrl; hostOf(at.htmlUrl) || at.fullName covers both cases.

120120 }
121121
122122 // ---- building one ----
test/web-scope.test.ts added +75
@@ -0,0 +1,75 @@
1 import { expect, test } from "bun:test";
2 import {
3 forkButton,
4 hostOf,
5 upstreamHref,
6 type RepoScope,
7 } from "../src/web/scope.ts";
8
9 const origin = (login: string) => ({
10 id: "o1",
11 login,
12 webUrl: "https://codeberg.org",
13 });
14
15 const scope = (over: Record<string, unknown>): RepoScope =>
16 ({
17 kind: "origin",
18 base: "/origins/o1/r/octo/widgets",
19 fullName: "octo/widgets",
20 origin: origin("me"),
21 repo: undefined,
22 upstream: undefined,
23 ...over,
24 }) as unknown as RepoScope;
25
26 test("the fork button is offered on someone else's repository", () => {
27 const out = forkButton(scope({})).toString();
28 expect(out).toContain('action="/origins/o1/r/octo/widgets/fork"');
29 expect(out).toContain("codeberg.org");
30 });
31
32 test("no fork button on a repository the account owns, in any case", () => {
33 expect(forkButton(scope({ fullName: "me/widgets" })).toString()).toBe("");
34 expect(forkButton(scope({ fullName: "ME/widgets" })).toString()).toBe("");
35 });
36
37 test("no fork button on a local scope or without an origin", () => {
38 expect(forkButton(scope({ kind: "local" })).toString()).toBe("");
39 expect(forkButton(scope({ origin: undefined })).toString()).toBe("");
40 });
41
42 test("a repository name with markup is escaped in the button", () => {
43 const out = forkButton(
44 scope({ fullName: 'octo/<img src=x onerror=1>"' }),
45 ).toString();
46 expect(out).not.toContain("<img");
47 expect(out).toContain("&lt;img");
48 expect(out).toContain("&quot;");
49 });
50
51 test("upstreamHref is a path on this forge, never an absolute URL", () => {
52 const href = upstreamHref(scope({ upstream: "up/widgets" }));
53 expect(href).toBe("/origins/o1/r/up/widgets");
54 expect(href).not.toContain("codeberg.org");
55 expect(href?.startsWith("/")).toBe(true);
56 });
57
58 test("upstreamHref reads the mirror's origin id and is undefined without upstream", () => {
59 const mirror = scope({
60 origin: undefined,
61 repo: { origin: { originId: "o9" } },
62 upstream: "up/widgets",
63 });
64 expect(upstreamHref(mirror)).toBe("/origins/o9/r/up/widgets");
65 expect(upstreamHref(scope({}))).toBeUndefined();
66 expect(
67 upstreamHref(scope({ origin: undefined, upstream: "a/b" })),
68 ).toBeUndefined();
69 });
70
71 test("hostOf gives the host, and nothing for garbage or javascript:", () => {
72 expect(hostOf("https://codeberg.org/octo/widgets")).toBe("codeberg.org");
73 expect(hostOf("not a url")).toBe("");
74 expect(hostOf("javascript:alert(1)")).toBe("");
75 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs