mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-02 18:20:29 +08:00
434 lines
14 KiB
TypeScript
434 lines
14 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
|
|
import {
|
|
aliceMainHead,
|
|
aliceRemote,
|
|
bobHead,
|
|
bobRemote,
|
|
expect,
|
|
markdownUrl,
|
|
sourceBrowsingRid,
|
|
sourceBrowsingUrl,
|
|
test,
|
|
} from "@tests/support/fixtures.js";
|
|
import { expectUrlPersistsReload } from "@tests/support/router";
|
|
|
|
async function expectCounts(
|
|
params: { commits: number; contributors: number },
|
|
page: Page,
|
|
) {
|
|
await expect(
|
|
page.locator(
|
|
`role=link[name="${params.commits} ${
|
|
params.commits === 1 ? "commit" : "commits"
|
|
}"]`,
|
|
),
|
|
).toBeVisible();
|
|
|
|
await expect(
|
|
page.locator(
|
|
`text=${params.contributors} ${
|
|
params.contributors === 1 ? "contributor" : "contributors"
|
|
}`,
|
|
),
|
|
).toBeVisible();
|
|
}
|
|
|
|
test("navigate to project", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
// Header.
|
|
{
|
|
const name = page.locator("text=source-browsing");
|
|
const id = page.locator(`text=${sourceBrowsingRid}`);
|
|
const description = page.locator(
|
|
"text=Git repository for source browsing tests",
|
|
);
|
|
|
|
await expect(name).toBeVisible();
|
|
await expect(id).toBeVisible();
|
|
await expect(description).toBeVisible();
|
|
}
|
|
|
|
// Project menu shows default selected branch and commit and contributor counts.
|
|
{
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
`main ${aliceMainHead.substring(0, 7)}`,
|
|
);
|
|
await expectCounts({ commits: 6, contributors: 1 }, page);
|
|
}
|
|
|
|
// Navigate to the project README.md by default.
|
|
await expect(page.locator(".file-name")).toContainText("README.md");
|
|
|
|
// Show a commit teaser.
|
|
await expect(page.locator("text=dd068e9 Add README.md")).toBeVisible();
|
|
|
|
// Show rendered README.md contents.
|
|
await expect(page.locator("text=Git test repository")).toBeVisible();
|
|
|
|
// Number of nodes tracking this project.
|
|
await expect(page.locator("text=3 nodes")).toBeVisible();
|
|
});
|
|
|
|
test("show source tree at specific revision", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
await page.locator('role=link[name="6 commits"]').click();
|
|
|
|
await page
|
|
.locator(".teaser", { hasText: "335dd6d" })
|
|
.getByTitle("Browse the repository at this point in the history")
|
|
.click();
|
|
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
"335dd6dc89b535a4a31e9422c803199bb6b0a09a",
|
|
);
|
|
await expect(page.locator(".source-tree")).toHaveText("bin/ src/");
|
|
await expectCounts({ commits: 2, contributors: 1 }, page);
|
|
});
|
|
|
|
test("source file highlighting", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
await page.getByText("src/").click();
|
|
await page.getByText("true.c").click();
|
|
|
|
await expect(page.getByText("return")).toHaveCSS(
|
|
"color",
|
|
"rgb(255, 123, 114)",
|
|
);
|
|
});
|
|
|
|
test("navigate line numbers", async ({ page }) => {
|
|
await page.goto(`${markdownUrl}/tree/main/cheatsheet.md`);
|
|
await page.locator('text="Plain"').click();
|
|
|
|
await page.locator('[href="#L5"]').click();
|
|
await expect(page.locator("#L5")).toHaveClass("line highlight");
|
|
await expect(page).toHaveURL(`${markdownUrl}/tree/main/cheatsheet.md#L5`);
|
|
|
|
await expectUrlPersistsReload(page);
|
|
await expect(page.locator("#L5")).toHaveClass("line highlight");
|
|
|
|
await page.locator('[href="#L30"]').click();
|
|
await expect(page.locator("#L5")).not.toHaveClass("line highlight");
|
|
await expect(page.locator("#L30")).toHaveClass("line highlight");
|
|
await expect(page).toHaveURL(`${markdownUrl}/tree/main/cheatsheet.md#L30`);
|
|
});
|
|
|
|
test("navigate deep file hierarchies", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
const sourceTree = page.locator(".source-tree");
|
|
|
|
await sourceTree.getByText("deep/").click();
|
|
await sourceTree.getByText("directory/").click();
|
|
await sourceTree.getByText("hierarchy/").click();
|
|
await sourceTree.getByText("is/").click();
|
|
await sourceTree.getByText("entirely/").click();
|
|
await sourceTree.getByText("possible/").click();
|
|
await sourceTree.getByText("in/").nth(1).click();
|
|
await sourceTree.getByText("git/").click();
|
|
await sourceTree.getByText("repositories/").click();
|
|
await sourceTree.getByText(".gitkeep").click();
|
|
await expect(
|
|
page.locator("text=0801ace Add a deeply nested directory tree"),
|
|
).toBeVisible();
|
|
|
|
// After a page reload the tree browser is still expanded and we're still
|
|
// showing the .gitkeep file.
|
|
{
|
|
await page.reload();
|
|
|
|
const sourceTree = page.locator(".source-tree");
|
|
|
|
await expect(sourceTree.getByText("deep/")).toBeVisible();
|
|
await expect(sourceTree.getByText("directory/")).toBeVisible();
|
|
await expect(sourceTree.getByText("hierarchy/")).toBeVisible();
|
|
await expect(sourceTree.getByText("is/")).toBeVisible();
|
|
await expect(sourceTree.getByText("entirely/")).toBeVisible();
|
|
await expect(sourceTree.getByText("possible/")).toBeVisible();
|
|
await expect(sourceTree.getByText("in/").nth(1)).toBeVisible();
|
|
await expect(sourceTree.getByText("git/")).toBeVisible();
|
|
await expect(sourceTree.getByText("repositories/")).toBeVisible();
|
|
await expect(sourceTree.getByText(".gitkeep")).toBeVisible();
|
|
|
|
await expect(
|
|
page.locator("text=0801ace Add a deeply nested directory tree"),
|
|
).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test("files with special characters in the filename", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
const sourceTree = page.locator(".source-tree");
|
|
await sourceTree.getByText("special/").click();
|
|
|
|
await sourceTree.getByText("+plus+").click();
|
|
await expect(page.locator(".file-name")).toContainText("+plus");
|
|
|
|
await sourceTree.getByText("-dash-").click();
|
|
await expect(page.locator(".file-name")).toContainText("-dash-");
|
|
|
|
await sourceTree.getByText(":colon:").click();
|
|
await expect(page.locator(".file-name")).toContainText(":colon:");
|
|
|
|
await sourceTree.getByText(";semicolon;").click();
|
|
await expect(page.locator(".file-name")).toContainText(";semicolon;");
|
|
|
|
await sourceTree.getByText("@at@").click();
|
|
await expect(page.locator(".file-name")).toContainText("@at@");
|
|
|
|
await sourceTree.getByText("_underscore_").click();
|
|
await expect(page.locator(".file-name")).toContainText("_underscore_");
|
|
|
|
// TODO: fix these errors in `radicle-httpd` for the following edge cases.
|
|
//
|
|
// await sourceTree.getByText("back\\slash").click();
|
|
// await expect(page.locator(".file-name")).toContainText("back\\slash");
|
|
// await sourceTree.getByText("qs?param1=value?param2=value2#hash").click();
|
|
// await expect(page.locator(".file-name")).toContainText(
|
|
// "qs?param1=value?param2=value2#hash",
|
|
// );
|
|
|
|
await sourceTree.getByText("spaces are okay").click();
|
|
await expect(page.locator(".file-name")).toContainText("spaces are okay");
|
|
|
|
await sourceTree.getByText("~tilde~").click();
|
|
await expect(page.locator(".file-name")).toContainText("~tilde~");
|
|
|
|
await sourceTree.getByText("👹👹👹").click();
|
|
await expect(page.locator(".file-name")).toContainText("👹👹👹");
|
|
});
|
|
|
|
test("binary files", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
await page.getByText("bin/").click();
|
|
await page.getByText("true").click();
|
|
|
|
await expect(page.locator("text=Binary content")).toBeVisible();
|
|
});
|
|
|
|
test("hidden files", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
await page.getByText(".hidden").click();
|
|
|
|
await expect(page.locator("text=I'm a hidden file.")).toBeVisible();
|
|
});
|
|
|
|
test("markdown files", async ({ page }) => {
|
|
await page.goto(`${markdownUrl}/tree/main/cheatsheet.md`);
|
|
|
|
await expect(
|
|
page.locator("text=This is intended as a quick reference and showcase."),
|
|
).toBeVisible();
|
|
|
|
// Switch between raw and rendered modes.
|
|
{
|
|
const plainButton = page.locator('text="Plain"');
|
|
await plainButton.click();
|
|
await expect(page.locator("text=##### Table of Contents")).toBeVisible();
|
|
|
|
const markdownButton = page.locator('text="Markdown"');
|
|
await markdownButton.click();
|
|
}
|
|
|
|
// Internal links go to anchor.
|
|
{
|
|
await page.getByRole("link", { name: "YouTube Videos" }).click();
|
|
await expect(page).toHaveURL(
|
|
`${markdownUrl}/tree/main/cheatsheet.md#videos`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("clone modal", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
await page.getByText("Clone").click();
|
|
await expect(
|
|
page.locator(`text=rad clone ${sourceBrowsingRid}`),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.locator(
|
|
`text=http://127.0.0.1/${sourceBrowsingRid.replace("rad:", "")}.git`,
|
|
),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("peer and branch switching", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
// Alice's peer.
|
|
{
|
|
await page.getByTitle("Change peer").click();
|
|
await page.locator(`text=${aliceRemote}`).click();
|
|
await expect(page.getByTitle("Change peer")).toHaveText(
|
|
`did:key:${aliceRemote.substring(8).substring(0, 6)}…${aliceRemote.slice(
|
|
-6,
|
|
)} delegate`,
|
|
);
|
|
await expect(
|
|
page.locator(
|
|
`text=source-browsing / did:key:${aliceRemote
|
|
.substring(8)
|
|
.substring(0, 6)}…${aliceRemote.slice(-6)}`,
|
|
),
|
|
).toBeVisible();
|
|
|
|
// Default `main` branch.
|
|
{
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
`main ${aliceMainHead.substring(0, 7)}`,
|
|
);
|
|
await expectCounts({ commits: 6, contributors: 1 }, page);
|
|
}
|
|
|
|
// Feature branch with a slash in the name.
|
|
{
|
|
await page.getByTitle("Change branch").click();
|
|
await page.locator("text=feature/branch").click();
|
|
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
"feature/branch 1aded56",
|
|
);
|
|
await expectCounts({ commits: 9, contributors: 1 }, page);
|
|
}
|
|
|
|
// Branch without a history or files in it.
|
|
{
|
|
await page.getByTitle("Change branch").click();
|
|
await page.locator("text=orphaned-branch").click();
|
|
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
"orphaned-branch af3641c",
|
|
);
|
|
await expectCounts({ commits: 1, contributors: 1 }, page);
|
|
|
|
await expect(
|
|
page.locator("text=We couldn't find any files at this revision."),
|
|
).toBeVisible();
|
|
}
|
|
}
|
|
|
|
// Reset the source browser by clicking the project title.
|
|
{
|
|
await page.locator("text=source-browsing").click();
|
|
|
|
await expect(page.getByTitle("Change peer")).not.toContainText("alice");
|
|
await expect(page.getByTitle("Change peer")).not.toContainText("bob");
|
|
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
`main ${aliceMainHead.substring(0, 7)}`,
|
|
);
|
|
await expect(page.locator("text=Git test repository")).toBeVisible();
|
|
}
|
|
|
|
// Bob's peer.
|
|
{
|
|
await page.getByTitle("Change peer").click();
|
|
await page.locator(`text=${bobRemote}`).click();
|
|
await expect(page.getByTitle("Change peer")).toHaveText(
|
|
`did:key:${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
|
|
-6,
|
|
)}`,
|
|
);
|
|
await expect(page.getByTitle("Change peer")).not.toHaveText("delegate");
|
|
|
|
// Default `main` branch.
|
|
{
|
|
await expect(page.getByTitle("Current branch")).toContainText(
|
|
`main ${bobHead.substring(0, 7)}`,
|
|
);
|
|
await expectCounts({ commits: 7, contributors: 2 }, page);
|
|
await expect(
|
|
page.locator(`text=${bobHead.substring(0, 7)} Update readme`),
|
|
).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
|
|
test("only one modal can be open at a time", async ({ page }) => {
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
await page.getByTitle("Change peer").click();
|
|
await page.locator(`text=${aliceRemote}`).click();
|
|
|
|
await page.getByText("Clone").click();
|
|
await expect(page.locator("text=Code font")).not.toBeVisible();
|
|
await expect(page.locator("text=Use the Radicle CLI")).toBeVisible();
|
|
await expect(page.locator("text=bob hyyzz9")).not.toBeVisible();
|
|
await expect(page.locator("text=feature/branch")).not.toBeVisible();
|
|
|
|
await page.locator('button[name="Settings"]').click();
|
|
await expect(page.locator("text=Code font")).toBeVisible();
|
|
await expect(page.locator("text=Use the Radicle CLI")).not.toBeVisible();
|
|
await expect(page.locator("text=bob hyyzz9")).not.toBeVisible();
|
|
await expect(page.locator("text=feature/branch")).not.toBeVisible();
|
|
|
|
await page.getByTitle("Change branch").click();
|
|
await expect(page.locator("text=Code font")).not.toBeVisible();
|
|
await expect(page.locator("text=Use the Radicle CLI")).not.toBeVisible();
|
|
await expect(page.locator("text=bob hyyzz9")).not.toBeVisible();
|
|
await expect(page.locator("text=feature/branch")).toBeVisible();
|
|
|
|
await page.getByTitle("Change peer").click();
|
|
await expect(page.locator("text=Code font")).not.toBeVisible();
|
|
await expect(page.locator("text=Use the Radicle CLI")).not.toBeVisible();
|
|
await expect(page.locator(`text=${bobRemote}`)).toBeVisible();
|
|
await expect(page.locator("text=feature/branch")).not.toBeVisible();
|
|
});
|
|
|
|
test.describe("browser error handling", () => {
|
|
test("error appears when folder can't be loaded", async ({ page }) => {
|
|
await page.route(
|
|
`**/v1/projects/${sourceBrowsingRid}/tree/${aliceMainHead}/src/`,
|
|
route => route.fulfill({ status: 500 }),
|
|
);
|
|
|
|
await page.goto(sourceBrowsingUrl);
|
|
|
|
const sourceTree = page.locator(".source-tree");
|
|
await sourceTree.locator("text=src/").click();
|
|
|
|
await expect(
|
|
page.locator("text=Not able to expand directory"),
|
|
).toBeVisible();
|
|
});
|
|
test("error appears when file can't be loaded", async ({ page }) => {
|
|
await page.route(
|
|
`**/v1/projects/${sourceBrowsingRid}/blob/${aliceMainHead}/.hidden`,
|
|
route => route.fulfill({ status: 500 }),
|
|
);
|
|
|
|
await page.goto(sourceBrowsingUrl);
|
|
await page.locator("text=.hidden").click();
|
|
|
|
await expect(page.locator("text=Not able to load file")).toBeVisible();
|
|
});
|
|
test("error appears when README can't be loaded", async ({ page }) => {
|
|
await page.route(
|
|
`**/v1/projects/${sourceBrowsingRid}/readme/${aliceMainHead}`,
|
|
route => route.fulfill({ status: 500 }),
|
|
);
|
|
|
|
await page.goto(sourceBrowsingUrl);
|
|
await expect(
|
|
page.locator("text=The README could not be loaded."),
|
|
).toBeVisible();
|
|
});
|
|
test("error appears when navigating to missing file", async ({ page }) => {
|
|
await page.route(
|
|
`**/v1/projects/${sourceBrowsingRid}/blob/${aliceMainHead}/.hidden`,
|
|
route => route.fulfill({ status: 500 }),
|
|
);
|
|
|
|
await page.goto(`${sourceBrowsingUrl}/tree/master/.hidden`);
|
|
|
|
await expect(page.locator("text=Not able to load file")).toBeVisible();
|
|
});
|
|
});
|