From 088b13af480fe5ee8196aafff94b18cf20555db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C5=ABdolfs=20O=C5=A1i=C5=86=C5=A1?= Date: Wed, 30 Sep 2026 17:28:22 +0200 Subject: [PATCH] Test the read views and persisted UI state end to end Cover the repo home, file tree, commits, a contributor's branch, patch filters and the identity page, and check that the sidebar, pins, hints, diff style and revision sort survive a reload. --- tests/e2e/persistence.spec.ts | 72 +++++++++++++++++++ tests/e2e/repo/source.spec.ts | 129 ++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 tests/e2e/persistence.spec.ts create mode 100644 tests/e2e/repo/source.spec.ts diff --git a/tests/e2e/persistence.spec.ts b/tests/e2e/persistence.spec.ts new file mode 100644 index 000000000..c1fa0b558 --- /dev/null +++ b/tests/e2e/persistence.spec.ts @@ -0,0 +1,72 @@ +import { cobRid, expect, reload, test } from "@tests/support/fixtures.js"; + +test("a collapsed sidebar stays collapsed", async ({ page }) => { + await page.goto("/inbox"); + await page.getByRole("button", { name: "Collapse sidebar" }).click(); + await expect( + page.getByRole("button", { name: "Expand sidebar" }), + ).toBeVisible(); + + await reload(page); + await expect( + page.getByRole("button", { name: "Expand sidebar" }), + ).toBeVisible(); +}); + +test("a pinned repo stays pinned and first", async ({ page }) => { + await page.goto("/inbox"); + const markdown = page.getByRole("link", { name: /markdown/ }); + await markdown.hover(); + await markdown.getByTitle("Pin repository").click(); + await expect(markdown.getByTitle("Unpin repository")).toBeAttached(); + + await reload(page); + await expect(markdown.getByTitle("Unpin repository")).toBeAttached(); + const repos = page.getByRole("link", { name: /Repository Avatar/ }); + await expect(repos.first()).toContainText("markdown"); +}); + +test("a dismissed hint stays dismissed", async ({ page }) => { + await page.goto(`/repos/${cobRid}/issues?status=all`); + await page.getByText("This title has **markdown**").click(); + const hint = page.getByText("Markdown is supported"); + await expect(hint).toBeVisible(); + + await page.getByTitle("Don't show again").click(); + await expect(hint).toBeHidden(); + await reload(page); + await expect(page.getByPlaceholder("Leave a comment")).toBeVisible(); + await expect(hint).toBeHidden(); +}); + +test("the split diff style is remembered", async ({ page }) => { + await page.goto(`/repos/${cobRid}/patches`); + await page.getByRole("button", { name: "Settings" }).click(); + await page.getByTitle("Split", { exact: true }).click(); + await page.keyboard.press("Escape"); + + await reload(page); + await page.getByText("Taking another stab at the README").click(); + await page.getByRole("button", { name: "icon-diff Changes" }).click(); + + await expect(page.locator("[data-deletions]").first()).toBeAttached(); + await expect(page.locator("[data-unified]")).toHaveCount(0); +}); + +test("the revision sort order is remembered", async ({ page }) => { + await page.goto(`/repos/${cobRid}/patches`); + await page.getByText("Taking another stab at the README").click(); + await page.getByRole("button", { name: "icon-diff Changes" }).click(); + + const picker = page.getByRole("button", { name: /Revision \d+ of 2/ }); + const sort = page.getByTitle(/^(Newest|Oldest) first$/); + await picker.click(); + const before = await sort.getAttribute("title"); + await sort.click(); + const after = before === "Newest first" ? "Oldest first" : "Newest first"; + await expect(page.getByTitle(after, { exact: true })).toBeVisible(); + + await reload(page); + await picker.click(); + await expect(page.getByTitle(after, { exact: true })).toBeVisible(); +}); diff --git a/tests/e2e/repo/source.spec.ts b/tests/e2e/repo/source.spec.ts new file mode 100644 index 000000000..9ada52c09 --- /dev/null +++ b/tests/e2e/repo/source.spec.ts @@ -0,0 +1,129 @@ +import type { RadiclePeer } from "@tests/support/peerManager.js"; + +import * as Fs from "node:fs/promises"; +import * as Path from "node:path"; + +import { clone, createCollaborators } from "@tests/support/collaboration.js"; +import { cobRid, expect, test, useBackend } from "@tests/support/fixtures.js"; +import { createRepo } from "@tests/support/repo.js"; + +async function createDocsRepo(peer: RadiclePeer) { + const { rid, repoFolder } = await createRepo(peer, { name: "docs" }); + await Fs.writeFile( + Path.join(repoFolder, "README.md"), + "# Docs\n\nWelcome to the docs.\n", + ); + await Fs.mkdir(Path.join(repoFolder, "guides")); + await Fs.writeFile( + Path.join(repoFolder, "guides", "setup.md"), + "Install it first.\n", + ); + await peer.git(["add", "."], { cwd: repoFolder }); + await peer.git(["commit", "-m", "Write the docs"], { cwd: repoFolder }); + await peer.git(["push", "rad", "main"], { cwd: repoFolder }); + return { rid, repoFolder }; +} + +test("the repo home shows the README and browses the tree", async ({ + page, + peer, +}) => { + const { rid } = await createDocsRepo(peer); + await page.goto(`/repos/${rid}/home`); + await expect(page.getByText("Welcome to the docs.")).toBeVisible(); + + await page.getByText("guides", { exact: true }).click(); + await page.getByText("setup.md", { exact: true }).click(); + await expect(page.getByText("Install it first.")).toBeVisible(); +}); + +test("commits are listed and open with their changes", async ({ + page, + peer, +}) => { + const { rid } = await createDocsRepo(peer); + await page.goto(`/repos/${rid}/commits`); + + await page + .getByRole("button", { name: "commit-teaser" }) + .filter({ hasText: "Write the docs" }) + .click(); + await expect(page).toHaveURL(/\/commits\/[0-9a-f]{40}/); + await expect(page.getByText("Write the docs").first()).toBeVisible(); + await expect(page.getByText("guides/setup.md").first()).toBeVisible(); +}); + +test("a contributor's branch can be browsed", async ({ page, peerManager }) => { + const { bob, eve } = await createCollaborators(peerManager); + const { rid } = await createDocsRepo(bob); + await clone(eve, rid); + const eveCopy = Path.join(eve.checkoutPath, "docs"); + await eve.git(["switch", "-c", "friendlier"], { cwd: eveCopy }); + await Fs.writeFile( + Path.join(eveCopy, "README.md"), + "# Docs\n\nHello, and welcome.\n", + ); + await eve.git(["commit", "-am", "Greet readers"], { cwd: eveCopy }); + await eve.git(["push", "rad", "friendlier"], { cwd: eveCopy }); + await bob.rad(["sync", rid, "--fetch"]); + await bob.startHttpd(); + await useBackend(page, bob); + + await page.goto(`/repos/${rid}/home`); + await expect(page.getByText("Welcome to the docs.")).toBeVisible(); + + await page.getByTitle("Change branch or tag").click(); + await page.getByPlaceholder("Filter branches").fill("friendlier"); + await page + .getByRole("link", { name: /friendlier/ }) + .first() + .click(); + + await expect(page).toHaveURL(new RegExp(`/remotes/${eve.nodeId}/friendlier`)); + await expect(page.getByText("Hello, and welcome.")).toBeVisible(); +}); + +test("patches are filtered by state", async ({ page }) => { + await page.goto(`/repos/${cobRid}/patches`); + await expect( + page.getByText("Taking another stab at the README"), + ).toBeVisible(); + + await page.getByRole("link", { name: /Merged/ }).click(); + await expect(page.getByText("Let's add a README")).toBeVisible(); + await expect( + page.getByText("Taking another stab at the README"), + ).toBeHidden(); + + await page.getByRole("link", { name: /Archived/ }).click(); + await expect( + page.getByText("This patch is going to be archived"), + ).toBeVisible(); + await expect(page.getByText("Let's add a README")).toBeHidden(); +}); + +test("the identity page lists accepted revisions", async ({ page, peer }) => { + const { rid, repoFolder } = await createDocsRepo(peer); + await peer.rad( + [ + "id", + "update", + "--title", + "Describe the docs", + "--description", + "So people know what this is.", + "--payload", + "xyz.radicle.project", + "description", + '"Docs for everyone"', + ], + { cwd: repoFolder }, + ); + + await page.goto(`/repos/${rid}/identity`); + await expect( + page.getByRole("button", { + name: /^icon-checkmark .*Describe the docs Current/, + }), + ).toBeVisible(); +});