mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Test the remaining write paths and permissions
Reactions, review edits, patch comments, title, description and assignees, stopping seeding, creating an identity, and that a contributor can't resolve someone else's review comment.
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
import { expect, test } from "@tests/support/fixtures.js";
|
||||
import * as Fs from "node:fs/promises";
|
||||
import * as Path from "node:path";
|
||||
|
||||
import {
|
||||
expect,
|
||||
gitOptions,
|
||||
test,
|
||||
useBackend,
|
||||
} from "@tests/support/fixtures.js";
|
||||
|
||||
test("explains why an alias is invalid", async ({ page }) => {
|
||||
await page.route(
|
||||
@@ -31,3 +39,33 @@ test("explains why an alias is invalid", async ({ page }) => {
|
||||
page.getByText("Max 32 characters, no whitespace."),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("creates an identity and opens the guide", async ({
|
||||
page,
|
||||
peerManager,
|
||||
sshAuthSock,
|
||||
stateDir,
|
||||
}) => {
|
||||
const peer = await peerManager.createPeer({
|
||||
name: "newcomer",
|
||||
gitOptions: gitOptions["alice"],
|
||||
sshAuthSock,
|
||||
});
|
||||
// Start from a node that has never had an identity.
|
||||
const home = Path.join(stateDir, "peers", "newcomer", "home");
|
||||
await Fs.rm(home, { recursive: true, force: true });
|
||||
await Fs.mkdir(home, { recursive: true });
|
||||
await peer.startHttpd();
|
||||
await useBackend(page, peer);
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByPlaceholder("Enter desired alias").fill("newcomer");
|
||||
await page
|
||||
.getByPlaceholder("Enter passphrase to protect your keys")
|
||||
.fill("secret");
|
||||
await page.getByPlaceholder("Repeat passphrase").fill("secret");
|
||||
await page.getByRole("button", { name: /Create new identity/ }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/guide/);
|
||||
await expect(page.getByText("newcomer").first()).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -153,3 +153,15 @@ test("an issue can be deleted", async ({ page, peer }) => {
|
||||
await expect(page.getByRole("link", { name: /Closed/ })).toBeVisible();
|
||||
await expect(page.getByText("The notes are too quiet")).toBeHidden();
|
||||
});
|
||||
|
||||
test("a reaction can be added to the description", async ({ page, peer }) => {
|
||||
await openIssue(page, peer);
|
||||
|
||||
await page.getByText("Nobody reads them.").hover();
|
||||
await page.getByTitle("React").first().click();
|
||||
await waitForCommand(page, "edit_issue", () =>
|
||||
page.getByRole("button", { name: "👍" }).click(),
|
||||
);
|
||||
await reload(page);
|
||||
await expect(page.getByTitle("Remove reaction")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -73,3 +73,147 @@ test("a patch can be deleted", async ({ page, peer }) => {
|
||||
await expect(page.getByRole("link", { name: /Merged/ })).toBeVisible();
|
||||
await expect(page.getByText("Shout the third line")).toBeHidden();
|
||||
});
|
||||
|
||||
// Items in the menu behind a comment's "Comment actions" button.
|
||||
async function commentAction(page: Page, body: string, action: string) {
|
||||
await page.getByText(body).hover();
|
||||
const toggle = page.getByTitle("Comment actions").last();
|
||||
await toggle.click();
|
||||
await toggle
|
||||
.locator(
|
||||
`xpath=following::*[@role="button"][normalize-space()="${action}"][1]`,
|
||||
)
|
||||
.click();
|
||||
}
|
||||
|
||||
test("the title and description can be edited", async ({ page, peer }) => {
|
||||
const { rid, patchId } = await openPatch(page, peer);
|
||||
|
||||
await page.getByRole("button", { name: "Shout the third line" }).click();
|
||||
const input = page.locator("input:focus");
|
||||
await input.fill("Shout the third line louder");
|
||||
await waitForCommand(page, "edit_patch", () => input.press("Enter"));
|
||||
const title = page.getByRole("button", {
|
||||
name: "Shout the third line louder",
|
||||
});
|
||||
await expect(title).toBeVisible();
|
||||
|
||||
await page.goto(`/repos/${rid}/patches/${patchId}?view=changes`);
|
||||
await page.getByText("Readers skim").hover();
|
||||
await page.getByTitle("Edit description").click();
|
||||
const editor = page.getByRole("textbox", { name: "textarea-comment" });
|
||||
await editor.fill("Readers skim, so make it loud.");
|
||||
await waitForCommand(page, "edit_patch", () =>
|
||||
page.getByRole("button", { name: /^Save/ }).click(),
|
||||
);
|
||||
await expect(page.getByText("Readers skim, so make it loud.")).toBeVisible();
|
||||
|
||||
await reload(page);
|
||||
await expect(title).toBeVisible();
|
||||
await expect(page.getByText("Readers skim, so make it loud.")).toBeVisible();
|
||||
});
|
||||
|
||||
test("a patch comment can be edited and deleted", async ({ page, peer }) => {
|
||||
await openPatch(page, peer);
|
||||
|
||||
await page.getByPlaceholder("Leave a comment").fill("Needs more shouting");
|
||||
await page.getByRole("button", { name: /^Comment/ }).click();
|
||||
await expect(page.getByText("Needs more shouting")).toBeVisible();
|
||||
|
||||
await commentAction(page, "Needs more shouting", "Edit");
|
||||
await page
|
||||
.getByPlaceholder("Leave a comment")
|
||||
.first()
|
||||
.fill("Needs less shouting");
|
||||
await waitForCommand(page, "edit_patch", () =>
|
||||
page.getByRole("button", { name: /^Save/ }).click(),
|
||||
);
|
||||
await reload(page);
|
||||
await expect(page.getByText("Needs less shouting")).toBeVisible();
|
||||
|
||||
await waitForCommand(page, "edit_patch", () =>
|
||||
commentAction(page, "Needs less shouting", "Delete"),
|
||||
);
|
||||
await reload(page);
|
||||
await expect(page.getByText("Needs less shouting")).toBeHidden();
|
||||
});
|
||||
|
||||
test("an assignee can be added to a patch", async ({ page, peer }) => {
|
||||
await openPatch(page, peer);
|
||||
|
||||
await page.getByRole("button", { name: "Add assignees" }).click();
|
||||
const input = page.getByPlaceholder(/^Assignee DID/);
|
||||
await input.fill(peer.nodeId);
|
||||
await waitForCommand(page, "edit_patch", () => input.press("Enter"));
|
||||
|
||||
await reload(page);
|
||||
await expect(
|
||||
page.getByRole("button", {
|
||||
name: "icon-avatar-incognito Assignees",
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("a reaction can be added to a patch comment", async ({ page, peer }) => {
|
||||
await openPatch(page, peer);
|
||||
await page.getByPlaceholder("Leave a comment").fill("Loud and clear");
|
||||
await page.getByRole("button", { name: /^Comment/ }).click();
|
||||
|
||||
await page.getByText("Loud and clear").hover();
|
||||
await page.getByTitle("React").last().click();
|
||||
await waitForCommand(page, "edit_patch", () =>
|
||||
page.getByRole("button", { name: "👍" }).click(),
|
||||
);
|
||||
await reload(page);
|
||||
await expect(page.getByTitle("Remove reaction")).toBeVisible();
|
||||
});
|
||||
|
||||
test("a published review's verdict and summary can be changed", async ({
|
||||
page,
|
||||
peer,
|
||||
}) => {
|
||||
const { rid, patchId } = await createProject(peer);
|
||||
await page.goto(`/repos/${rid}/patches/${patchId}?view=changes`);
|
||||
// Only reviews with code comments get a page of their own.
|
||||
await page
|
||||
.locator(
|
||||
'[data-unified] [data-column-number="3"]:not([data-line-type="change-deletion"])',
|
||||
)
|
||||
.hover();
|
||||
await page.locator("[data-utility-button]").click();
|
||||
await page.getByPlaceholder("Leave a comment").fill("Loud enough");
|
||||
await page
|
||||
.getByLabel("extended-textarea")
|
||||
.filter({ has: page.getByPlaceholder("Leave a comment") })
|
||||
.getByRole("button", { name: /^Start review/ })
|
||||
.click();
|
||||
await page
|
||||
.getByPlaceholder("Add an optional review summary")
|
||||
.fill("Looks fine");
|
||||
await waitForCommand(page, "create_patch_review", () =>
|
||||
page.getByRole("button", { name: "Accept revision", exact: true }).click(),
|
||||
);
|
||||
// Publishing opens the review's own page.
|
||||
|
||||
await page.getByRole("button", { name: /Accepted/ }).click();
|
||||
await waitForCommand(page, "edit_patch", () =>
|
||||
page.getByRole("button", { name: /Reject$/ }).click(),
|
||||
);
|
||||
const rejected = page.getByRole("button", { name: /Rejected/ });
|
||||
await expect(rejected).toBeVisible();
|
||||
|
||||
await page.getByText("Looks fine").hover();
|
||||
await page.getByTitle("Edit summary").click();
|
||||
await page
|
||||
.getByRole("textbox", { name: "textarea-comment" })
|
||||
.first()
|
||||
.fill("Too loud");
|
||||
await waitForCommand(page, "edit_patch", () =>
|
||||
page.getByRole("button", { name: /^Save/ }).click(),
|
||||
);
|
||||
|
||||
await reload(page);
|
||||
await expect(rejected).toBeVisible();
|
||||
await expect(page.getByText("Too loud")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
createCollaborators,
|
||||
createProject,
|
||||
} from "@tests/support/collaboration.js";
|
||||
import { expect, test, useBackend } from "@tests/support/fixtures.js";
|
||||
import {
|
||||
expect,
|
||||
test,
|
||||
useBackend,
|
||||
waitForCommand,
|
||||
} from "@tests/support/fixtures.js";
|
||||
|
||||
// Opens bob's project as eve, who cloned it but isn't a delegate.
|
||||
async function asContributor(page: Page, peerManager: PeerManager) {
|
||||
@@ -86,3 +91,42 @@ test("a contributor can't change a patch's state or delete it", async ({
|
||||
await expect(page.getByRole("button", { name: "Add labels" })).toBeDisabled();
|
||||
await expect(page.getByTitle("Delete patch from your node")).toBeHidden();
|
||||
});
|
||||
|
||||
test("a contributor can't resolve someone else's review comment", async ({
|
||||
page,
|
||||
peerManager,
|
||||
}) => {
|
||||
const { bob, eve } = await createCollaborators(peerManager);
|
||||
const { rid, patchId } = await createProject(bob);
|
||||
await clone(eve, rid);
|
||||
await bob.startHttpd();
|
||||
await useBackend(page, bob);
|
||||
|
||||
// Bob reviews his own patch with a comment on the changed line.
|
||||
await page.goto(`/repos/${rid}/patches/${patchId}?view=changes`);
|
||||
await page
|
||||
.locator(
|
||||
'[data-unified] [data-column-number="3"]:not([data-line-type="change-deletion"])',
|
||||
)
|
||||
.hover();
|
||||
await page.locator("[data-utility-button]").click();
|
||||
await page.getByPlaceholder("Leave a comment").fill("Keep it loud");
|
||||
await page
|
||||
.getByLabel("extended-textarea")
|
||||
.filter({ has: page.getByPlaceholder("Leave a comment") })
|
||||
.getByRole("button", { name: /^Start review/ })
|
||||
.click();
|
||||
await waitForCommand(page, "create_patch_review", () =>
|
||||
page.getByRole("button", { name: "Accept revision", exact: true }).click(),
|
||||
);
|
||||
await eve.rad(["sync", rid, "--fetch"]);
|
||||
|
||||
// Now as eve.
|
||||
await eve.startHttpd();
|
||||
await useBackend(page, eve);
|
||||
await page.goto(`/repos/${rid}/patches/${patchId}?view=changes`);
|
||||
const thread = page.getByRole("group").filter({ hasText: "Keep it loud" });
|
||||
await thread.hover();
|
||||
await expect(thread.getByTitle("Reply")).toBeVisible();
|
||||
await expect(thread.getByTitle("Mark as resolved")).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
import type { PeerManager } from "@tests/support/peerManager.js";
|
||||
|
||||
import * as Fs from "node:fs/promises";
|
||||
import * as Path from "node:path";
|
||||
|
||||
import { createCollaborators } from "@tests/support/collaboration.js";
|
||||
import {
|
||||
expect,
|
||||
reload,
|
||||
test,
|
||||
useBackend,
|
||||
waitForCommand,
|
||||
@@ -35,3 +42,68 @@ test("seeds a repo from the network by its bare RID", async ({
|
||||
page.getByText(/This repo is already (seeded|queued for fetching)/),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
async function stopSeeding(page: Page, name: string, deleteFiles: boolean) {
|
||||
await page.getByRole("link", { name: new RegExp(name) }).click({
|
||||
button: "right",
|
||||
});
|
||||
await page.getByRole("menuitem", { name: /Stop seeding/ }).click();
|
||||
if (deleteFiles) {
|
||||
await page.getByText("Delete the files from local storage").click();
|
||||
}
|
||||
await waitForCommand(page, deleteFiles ? "clean" : "unseed", () =>
|
||||
page.getByRole("button", { name: /Stop seeding$/ }).click(),
|
||||
);
|
||||
await expect(page.getByRole("link", { name: new RegExp(name) })).toBeHidden();
|
||||
}
|
||||
|
||||
async function seededFrom(peerManager: PeerManager, page: Page) {
|
||||
const { bob, eve } = await createCollaborators(peerManager);
|
||||
const { rid } = await createRepo(eve, { name: "garden" });
|
||||
await bob.rad(["seed", rid]);
|
||||
await bob.startHttpd();
|
||||
await useBackend(page, bob);
|
||||
await page.goto("/inbox");
|
||||
return rid;
|
||||
}
|
||||
|
||||
function storedRepo(stateDir: string, rid: string) {
|
||||
return Path.join(
|
||||
stateDir,
|
||||
"peers",
|
||||
"bob",
|
||||
"home",
|
||||
"storage",
|
||||
rid.replace(/^rad:/, ""),
|
||||
);
|
||||
}
|
||||
|
||||
async function exists(path: string) {
|
||||
return Fs.stat(path).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
}
|
||||
|
||||
test("stopping seeding keeps the repo's files", async ({
|
||||
page,
|
||||
peerManager,
|
||||
stateDir,
|
||||
}) => {
|
||||
const rid = await seededFrom(peerManager, page);
|
||||
await stopSeeding(page, "garden", false);
|
||||
await reload(page);
|
||||
await expect(page.getByRole("link", { name: /garden/ })).toBeHidden();
|
||||
expect(await exists(storedRepo(stateDir, rid))).toBe(true);
|
||||
});
|
||||
|
||||
test("stopping seeding can delete the repo's files", async ({
|
||||
page,
|
||||
peerManager,
|
||||
stateDir,
|
||||
}) => {
|
||||
const rid = await seededFrom(peerManager, page);
|
||||
expect(await exists(storedRepo(stateDir, rid))).toBe(true);
|
||||
await stopSeeding(page, "garden", true);
|
||||
expect(await exists(storedRepo(stateDir, rid))).toBe(false);
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function createProject(peer: RadiclePeer, name = "project") {
|
||||
["Shout the third line"],
|
||||
"feature/shout",
|
||||
() => Fs.writeFile(file, "one\ntwo\nTHREE\nfour\nfive\n"),
|
||||
[],
|
||||
["Shout the third line", "Readers skim, so the key line should stand out."],
|
||||
{ cwd: repoFolder },
|
||||
);
|
||||
return { rid, repoFolder, issueId, patchId };
|
||||
|
||||
Reference in New Issue
Block a user