Show branch names decoded in the branch selector

Closes c639643a8e
This commit is contained in:
Rūdolfs Ošiņš
2026-08-25 15:38:10 +02:00
parent 8cfcf11a88
commit f3138ae30a
7 changed files with 124 additions and 17 deletions
+53
View File
@@ -438,6 +438,59 @@ test("peer and branch switching", async ({ page }) => {
}
});
// Links to a canonical branch encode its name so a slash cannot be mistaken
// for a path separator, which leaves the encoded form in the route after an
// in-app navigation. The label has to show the branch name, not the encoding.
// Only the canonical list encodes: the peer list covered above passes branch
// names through untouched, so the branch has to be canonical to reproduce it.
test("canonical branch with a slash in the name", async ({ page, peer }) => {
const { rid, repoFolder } = await createRepo(peer, {
name: "slash-branch",
});
// A branch is only resolved by delegate quorum when a canonical-ref rule
// matches it. Without this the branch is listed under the peer instead,
// where names are never encoded.
await peer.rad(
[
"id",
"update",
"--title",
"Add canonical ref rule",
"--description",
"",
"--payload",
"xyz.radicle.crefs",
"rules",
JSON.stringify({
"refs/heads/feature/*": {
allow: [`did:key:${peer.nodeId}`],
threshold: 1,
},
}),
"--no-confirm",
],
{ cwd: repoFolder },
);
await peer.git(["checkout", "-b", "feature/branch"], { cwd: repoFolder });
await peer.git(
["commit", "--allow-empty", "--message", "branch off the default"],
{ cwd: repoFolder },
);
await peer.git(["push", "rad", "feature/branch"], { cwd: repoFolder });
await page.goto(peer.ridUrl(rid));
const trigger = page.locator('[title="Change branch"]:visible').first();
await trigger.click();
await page.getByRole("button", { name: /feature\/branch Canonical/ }).click();
await expect(trigger).toHaveText(/feature\/branch/);
await expect(trigger).not.toHaveText(/%2F/);
// The URL keeps the encoded form; only what the user reads is decoded.
await expect(page).toHaveURL(/tree\/feature%2Fbranch/);
});
test("only one modal can be open at a time", async ({ page }) => {
await page.goto(sourceBrowsingUrl);
+23
View File
@@ -352,3 +352,26 @@ describe("repoSegment", () => {
expect(utils.repoSegment(repo)).toEqual(rid);
});
});
describe("safeDecodeURIComponent", () => {
test("decodes an encoded branch name", () => {
expect(utils.safeDecodeURIComponent("releaseprep%2Fv27.1")).toEqual(
"releaseprep/v27.1",
);
});
test("leaves an already decoded name untouched", () => {
expect(utils.safeDecodeURIComponent("releaseprep/v27.1")).toEqual(
"releaseprep/v27.1",
);
});
// Branch names, tags and repo aliases may all contain a bare `%`, which is
// not a well-formed encoding and would otherwise throw.
test("returns an undecodable segment unchanged", () => {
expect(utils.safeDecodeURIComponent("100%")).toEqual("100%");
expect(utils.safeDecodeURIComponent("wip/50%-done")).toEqual(
"wip/50%-done",
);
});
});