From 889b13303b4d618d2cb664e7ecd2f54b71514cfe Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 1 Jun 2023 15:11:41 +0200 Subject: [PATCH] Extract markdown from source-browsing fixture Signed-off-by: Sebastian Martinez --- httpd-client/tests/project.test.ts | 31 +++--- tests/e2e/clipboard.spec.ts | 18 ++-- tests/e2e/hashRouter.spec.ts | 24 ++--- tests/e2e/historyRouter.spec.ts | 24 ++--- tests/e2e/project.spec.ts | 96 ++++++++++--------- tests/e2e/project/commit.spec.ts | 29 +++--- tests/e2e/project/commits.spec.ts | 32 +++---- tests/e2e/search.spec.ts | 10 +- tests/e2e/seed.spec.ts | 10 +- tests/e2e/settings.spec.ts | 4 +- tests/fixtures/repos/markdown.tar.bz2 | Bin 0 -> 4803 bytes tests/fixtures/repos/source-browsing.tar.bz2 | Bin 16625 -> 16804 bytes tests/support/fixtures.ts | 67 ++++++++----- tests/support/globalSetup.ts | 33 ++++++- tests/support/peerManager.ts | 49 +--------- tests/support/project.ts | 41 ++++++++ tests/unit/mutexExecutor.test.ts | 11 +-- tests/visual/markdown.spec.ts | 41 ++++++-- tests/visual/project.spec.ts | 15 +-- 19 files changed, 307 insertions(+), 228 deletions(-) create mode 100644 tests/fixtures/repos/markdown.tar.bz2 create mode 100644 tests/support/project.ts diff --git a/httpd-client/tests/project.test.ts b/httpd-client/tests/project.test.ts index 5b1445740..931312779 100644 --- a/httpd-client/tests/project.test.ts +++ b/httpd-client/tests/project.test.ts @@ -1,7 +1,11 @@ import { describe, test } from "vitest"; import { HttpdClient } from "../index"; -import { aliceMainHead, aliceRemote, rid } from "@tests/support/fixtures"; +import { + aliceMainHead, + aliceRemote, + sourceBrowsingRid, +} from "@tests/support/fixtures"; const api = new HttpdClient({ hostname: "127.0.0.1", @@ -19,44 +23,47 @@ describe("project", () => { }); test("#getById(id)", async () => { - await api.project.getById(rid); + await api.project.getById(sourceBrowsingRid); }); test("#getActivity(id)", async () => { - await api.project.getActivity(rid); + await api.project.getActivity(sourceBrowsingRid); }); test("#getReadme(id, sha)", async () => { - await api.project.getReadme(rid, aliceMainHead); + await api.project.getReadme(sourceBrowsingRid, aliceMainHead); }); test("#getBlob(id, sha, path)", async () => { - await api.project.getBlob(rid, aliceMainHead, "src/true.c"); + await api.project.getBlob(sourceBrowsingRid, aliceMainHead, "src/true.c"); }); test("#getTree(id, sha)", async () => { - await api.project.getTree(rid, aliceMainHead); + await api.project.getTree(sourceBrowsingRid, aliceMainHead); }); test("#getTree(id, sha, path)", async () => { - await api.project.getTree(rid, aliceMainHead, "src"); + await api.project.getTree(sourceBrowsingRid, aliceMainHead, "src"); }); test("#getAllRemotes(id)", async () => { - await api.project.getAllRemotes(rid); + await api.project.getAllRemotes(sourceBrowsingRid); }); test("#getRemoteByPeer(id, peer)", async () => { - await api.project.getRemoteByPeer(rid, aliceRemote.substring(8)); + await api.project.getRemoteByPeer( + sourceBrowsingRid, + aliceRemote.substring(8), + ); }); test("#getAllCommits(id)", async () => { - await api.project.getAllCommits(rid); + await api.project.getAllCommits(sourceBrowsingRid); }); // TODO: test since/until properly. test("#getAllCommits(id, {parent, since, until, page, perPage})", async () => { - await api.project.getAllCommits(rid, { + await api.project.getAllCommits(sourceBrowsingRid, { parent: aliceMainHead, since: 1679065819581, until: 1679065819590, @@ -66,7 +73,7 @@ describe("project", () => { }); test("#getCommitBySha(id, sha)", async () => { - await api.project.getCommitBySha(rid, aliceMainHead); + await api.project.getCommitBySha(sourceBrowsingRid, aliceMainHead); }); test.todo("#getDiff(id, revisionBase, revisionOid)"); diff --git a/tests/e2e/clipboard.spec.ts b/tests/e2e/clipboard.spec.ts index 06455c32b..eb200198b 100644 --- a/tests/e2e/clipboard.spec.ts +++ b/tests/e2e/clipboard.spec.ts @@ -2,8 +2,8 @@ import type { Page } from "@playwright/test"; import { expect, - projectFixtureUrl, - rid, + sourceBrowsingUrl, + sourceBrowsingRid, seedRemote, test, } from "@tests/support/fixtures.js"; @@ -26,7 +26,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => { } await context.grantPermissions(["clipboard-read", "clipboard-write"]); - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); // Reset system clipboard to a known state. await page.evaluate("navigator.clipboard.writeText('')"); @@ -37,19 +37,21 @@ test("copy to clipboard", async ({ page, browserName, context }) => { const clipboardContent = await page.evaluate( "navigator.clipboard.readText()", ); - expect(clipboardContent).toBe(rid); + expect(clipboardContent).toBe(sourceBrowsingRid); } // `rad clone` URL. { await page.getByText("Clone").click(); - await page.locator(`text=rad clone ${rid.substring(0, 6)}`).hover(); + await page + .locator(`text=rad clone ${sourceBrowsingRid.substring(0, 6)}`) + .hover(); await page .locator(".clone-url-wrapper > span") .first() .locator(".clipboard") .click(); - await expectClipboard(`rad clone ${rid}`, page); + await expectClipboard(`rad clone ${sourceBrowsingRid}`, page); } // `git clone` URL. @@ -57,7 +59,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => { await page.getByText("Clone").click(); await page .locator( - `text=git clone http://127.0.0.1/${rid + `text=git clone http://127.0.0.1/${sourceBrowsingRid .replace("rad:", "") .substring(0, 10)}`, ) @@ -68,7 +70,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => { .locator(".clipboard") .click(); await expectClipboard( - `git clone http://127.0.0.1/${rid.replace( + `git clone http://127.0.0.1/${sourceBrowsingRid.replace( "rad:", "", )}.git source-browsing`, diff --git a/tests/e2e/hashRouter.spec.ts b/tests/e2e/hashRouter.spec.ts index 1ac7af4ff..fb95689e0 100644 --- a/tests/e2e/hashRouter.spec.ts +++ b/tests/e2e/hashRouter.spec.ts @@ -3,7 +3,7 @@ import { aliceRemote, appConfigWithFixture, expect, - projectFixtureUrl, + sourceBrowsingUrl, test, } from "@tests/support/fixtures.js"; import { @@ -18,7 +18,7 @@ test("navigate between landing and project page", async ({ page }) => { await expect(page).toHaveURL("/#/"); await page.locator("text=source-browsing").click(); - await expect(page).toHaveURL(`/#${projectFixtureUrl}`); + await expect(page).toHaveURL(`/#${sourceBrowsingUrl}`); await expectBackAndForwardNavigationWorks("/#/", page); await expectUrlPersistsReload(page); @@ -27,9 +27,9 @@ test("navigate between landing and project page", async ({ page }) => { test("navigation between seed and project pages", async ({ page }) => { await page.goto("/#/seeds/radicle.local"); - const project = page.locator(".project"); + const project = page.locator(".project", { hasText: "source-browsing" }); await project.click(); - await expect(page).toHaveURL(`/#${projectFixtureUrl}`); + await expect(page).toHaveURL(`/#${sourceBrowsingUrl}`); await expectBackAndForwardNavigationWorks("/#/seeds/radicle.local", page); await expectUrlPersistsReload(page); @@ -42,12 +42,12 @@ test.describe("project page navigation", () => { test("navigation between commit history and single commit", async ({ page, }) => { - const projectHistoryURL = `/#${projectFixtureUrl}/history/${aliceMainHead}`; + const projectHistoryURL = `/#${sourceBrowsingUrl}/history/${aliceMainHead}`; await page.goto(projectHistoryURL); - await page.locator("text=Add Markdown cheat sheet").click(); + await page.locator("text=Add README.md").click(); await expect(page).toHaveURL( - `/#${projectFixtureUrl}/commits/f0b8db68847b01f0964380507a9db6800e5b5342`, + `/#${sourceBrowsingUrl}/commits/${aliceMainHead}`, ); await expectBackAndForwardNavigationWorks(projectHistoryURL, page); @@ -55,20 +55,20 @@ test.describe("project page navigation", () => { }); test("navigate between tree and commit history", async ({ page }) => { - const projectTreeURL = `/#${projectFixtureUrl}`; + const projectTreeURL = `/#${sourceBrowsingUrl}`; await page.goto(projectTreeURL); await expect(page).toHaveURL(projectTreeURL); - await page.locator('role=link[name="8 commits"]').click(); - await expect(page).toHaveURL(`/#${projectFixtureUrl}/history`); + await page.locator('role=link[name="6 commits"]').click(); + await expect(page).toHaveURL(`/#${sourceBrowsingUrl}/history`); await expectBackAndForwardNavigationWorks(projectTreeURL, page); await expectUrlPersistsReload(page); }); test("navigate project paths", async ({ page }) => { - const projectTreeURL = `/#${projectFixtureUrl}`; + const projectTreeURL = `/#${sourceBrowsingUrl}`; await page.goto(projectTreeURL); await expect(page).toHaveURL(projectTreeURL); @@ -88,7 +88,7 @@ test.describe("project page navigation", () => { }); test("navigate project paths with a selected peer", async ({ page }) => { - const projectTreeURL = `/#${projectFixtureUrl}/remotes/${aliceRemote.substring( + const projectTreeURL = `/#${sourceBrowsingUrl}/remotes/${aliceRemote.substring( 8, )}`; diff --git a/tests/e2e/historyRouter.spec.ts b/tests/e2e/historyRouter.spec.ts index ecc4ff37d..1871f0f0e 100644 --- a/tests/e2e/historyRouter.spec.ts +++ b/tests/e2e/historyRouter.spec.ts @@ -3,7 +3,7 @@ import { aliceRemote, appConfigWithFixture, expect, - projectFixtureUrl, + sourceBrowsingUrl, test, } from "@tests/support/fixtures.js"; import { @@ -18,7 +18,7 @@ test("navigate between landing and project page", async ({ page }) => { await expect(page).toHaveURL("/"); await page.locator("text=source-browsing").click(); - await expect(page).toHaveURL(projectFixtureUrl); + await expect(page).toHaveURL(sourceBrowsingUrl); await expectBackAndForwardNavigationWorks("/", page); await expectUrlPersistsReload(page); @@ -27,9 +27,9 @@ test("navigate between landing and project page", async ({ page }) => { test("navigation between seed and project pages", async ({ page }) => { await page.goto("/seeds/radicle.local"); - const project = page.locator(".project"); + const project = page.locator(".project", { hasText: "source-browsing" }); await project.click(); - await expect(page).toHaveURL(projectFixtureUrl); + await expect(page).toHaveURL(sourceBrowsingUrl); await expectBackAndForwardNavigationWorks("/seeds/radicle.local", page); await expectUrlPersistsReload(page); @@ -42,12 +42,12 @@ test.describe("project page navigation", () => { test("navigation between commit history and single commit", async ({ page, }) => { - const projectHistoryURL = `${projectFixtureUrl}/history/${aliceMainHead}`; + const projectHistoryURL = `${sourceBrowsingUrl}/history/${aliceMainHead}`; await page.goto(projectHistoryURL); - await page.locator("text=Add Markdown cheat sheet").click(); + await page.locator("text=Add README.md").click(); await expect(page).toHaveURL( - `${projectFixtureUrl}/commits/f0b8db68847b01f0964380507a9db6800e5b5342`, + `${sourceBrowsingUrl}/commits/${aliceMainHead}`, ); await expectBackAndForwardNavigationWorks(projectHistoryURL, page); @@ -55,14 +55,14 @@ test.describe("project page navigation", () => { }); test("navigate between tree and commit history", async ({ page }) => { - const projectTreeURL = `${projectFixtureUrl}/tree/${aliceMainHead}`; + const projectTreeURL = `${sourceBrowsingUrl}/tree/${aliceMainHead}`; await page.goto(projectTreeURL); await expect(page).toHaveURL(projectTreeURL); - await page.locator('role=link[name="8 commits"]').click(); + await page.locator('role=link[name="6 commits"]').click(); await expect(page).toHaveURL( - `${projectFixtureUrl}/history/${aliceMainHead}`, + `${sourceBrowsingUrl}/history/${aliceMainHead}`, ); await expectBackAndForwardNavigationWorks(projectTreeURL, page); @@ -70,7 +70,7 @@ test.describe("project page navigation", () => { }); test("navigate project paths", async ({ page }) => { - const projectTreeURL = `${projectFixtureUrl}/tree/${aliceMainHead}`; + const projectTreeURL = `${sourceBrowsingUrl}/tree/${aliceMainHead}`; await page.goto(projectTreeURL); await expect(page).toHaveURL(projectTreeURL); @@ -90,7 +90,7 @@ test.describe("project page navigation", () => { }); test("navigate project paths with a selected peer", async ({ page }) => { - const projectTreeURL = `${projectFixtureUrl}/remotes/${aliceRemote.substring( + const projectTreeURL = `${sourceBrowsingUrl}/remotes/${aliceRemote.substring( 8, )}`; diff --git a/tests/e2e/project.spec.ts b/tests/e2e/project.spec.ts index 0877f380e..76fb35f3e 100644 --- a/tests/e2e/project.spec.ts +++ b/tests/e2e/project.spec.ts @@ -3,10 +3,12 @@ import type { Page } from "@playwright/test"; import { aliceMainHead, aliceRemote, + bobHead, bobRemote, expect, - projectFixtureUrl, - rid, + markdownUrl, + sourceBrowsingRid, + sourceBrowsingUrl, test, } from "@tests/support/fixtures.js"; import { expectUrlPersistsReload } from "@tests/support/router"; @@ -33,12 +35,12 @@ async function expectCounts( } test("navigate to project", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); // Header. { const name = page.locator("text=source-browsing"); - const id = page.locator(`text=${rid}`); + const id = page.locator(`text=${sourceBrowsingRid}`); const description = page.locator( "text=Git repository for source browsing tests", ); @@ -53,7 +55,7 @@ test("navigate to project", async ({ page }) => { await expect(page.getByTitle("Current branch")).toContainText( `main ${aliceMainHead.substring(0, 7)}`, ); - await expectCounts({ commits: 8, contributors: 1 }, page); + await expectCounts({ commits: 6, contributors: 1 }, page); } // Navigate to the project README.md by default. @@ -70,8 +72,8 @@ test("navigate to project", async ({ page }) => { }); test("show source tree at specific revision", async ({ page }) => { - await page.goto(projectFixtureUrl); - await page.locator('role=link[name="8 commits"]').click(); + await page.goto(sourceBrowsingUrl); + await page.locator('role=link[name="6 commits"]').click(); await page .locator(".teaser", { hasText: "335dd6d" }) @@ -86,7 +88,7 @@ test("show source tree at specific revision", async ({ page }) => { }); test("source file highlighting", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); await page.getByText("src/").click(); await page.getByText("true.c").click(); @@ -98,14 +100,12 @@ test("source file highlighting", async ({ page }) => { }); test("navigate line numbers", async ({ page }) => { - await page.goto(`${projectFixtureUrl}/tree/main/markdown/cheatsheet.md`); + 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( - `${projectFixtureUrl}/tree/main/markdown/cheatsheet.md#L5`, - ); + await expect(page).toHaveURL(`${markdownUrl}/tree/main/cheatsheet.md#L5`); await expectUrlPersistsReload(page); await expect(page.locator("#L5")).toHaveClass("line highlight"); @@ -113,16 +113,11 @@ test("navigate line numbers", async ({ page }) => { 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( - `${projectFixtureUrl}/tree/main/markdown/cheatsheet.md#L30`, - ); - - await page.getByText(".hidden").click(); - await expect(page).toHaveURL(`${projectFixtureUrl}/tree/main/.hidden`); + await expect(page).toHaveURL(`${markdownUrl}/tree/main/cheatsheet.md#L30`); }); test("navigate deep file hierarchies", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); const sourceTree = page.locator(".source-tree"); @@ -165,7 +160,7 @@ test("navigate deep file hierarchies", async ({ page }) => { }); test("files with special characters in the filename", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); const sourceTree = page.locator(".source-tree"); await sourceTree.getByText("special/").click(); @@ -209,7 +204,7 @@ test("files with special characters in the filename", async ({ page }) => { }); test("binary files", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); await page.getByText("bin/").click(); await page.getByText("true").click(); @@ -218,7 +213,7 @@ test("binary files", async ({ page }) => { }); test("hidden files", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); await page.getByText(".hidden").click(); @@ -226,7 +221,7 @@ test("hidden files", async ({ page }) => { }); test("markdown files", async ({ page }) => { - await page.goto(`${projectFixtureUrl}/tree/main/markdown/cheatsheet.md`); + await page.goto(`${markdownUrl}/tree/main/cheatsheet.md`); await expect( page.locator("text=This is intended as a quick reference and showcase."), @@ -246,23 +241,27 @@ test("markdown files", async ({ page }) => { { await page.getByRole("link", { name: "YouTube Videos" }).click(); await expect(page).toHaveURL( - `${projectFixtureUrl}/tree/main/markdown/cheatsheet.md#videos`, + `${markdownUrl}/tree/main/cheatsheet.md#videos`, ); } }); test("clone modal", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); await page.getByText("Clone").click(); - await expect(page.locator(`text=rad clone ${rid}`)).toBeVisible(); await expect( - page.locator(`text=http://127.0.0.1/${rid.replace("rad:", "")}.git`), + 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(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); // Alice's peer. { @@ -284,9 +283,9 @@ test("peer and branch switching", async ({ page }) => { // Default `main` branch. { await expect(page.getByTitle("Current branch")).toContainText( - "main fcc9294", + `main ${aliceMainHead.substring(0, 7)}`, ); - await expectCounts({ commits: 8, contributors: 1 }, page); + await expectCounts({ commits: 6, contributors: 1 }, page); } // Feature branch with a slash in the name. @@ -295,9 +294,9 @@ test("peer and branch switching", async ({ page }) => { await page.locator("text=feature/branch").click(); await expect(page.getByTitle("Current branch")).toContainText( - "feature/branch d6318f7", + "feature/branch 1aded56", ); - await expectCounts({ commits: 10, contributors: 1 }, page); + await expectCounts({ commits: 9, contributors: 1 }, page); } // Branch without a history or files in it. @@ -324,7 +323,7 @@ test("peer and branch switching", async ({ page }) => { await expect(page.getByTitle("Change peer")).not.toContainText("bob"); await expect(page.getByTitle("Current branch")).toContainText( - "main fcc9294", + `main ${aliceMainHead.substring(0, 7)}`, ); await expect(page.locator("text=Git test repository")).toBeVisible(); } @@ -343,16 +342,18 @@ test("peer and branch switching", async ({ page }) => { // Default `main` branch. { await expect(page.getByTitle("Current branch")).toContainText( - "main ec5eb0b", + `main ${bobHead.substring(0, 7)}`, ); - await expectCounts({ commits: 9, contributors: 2 }, page); - await expect(page.locator("text=ec5eb0b Update readme")).toBeVisible(); + 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(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); await page.getByTitle("Change peer").click(); await page.locator(`text=${aliceRemote}`).click(); @@ -385,14 +386,14 @@ test("only one modal can be open at a time", async ({ page }) => { test.describe("browser error handling", () => { test("error appears when folder can't be loaded", async ({ page }) => { await page.route( - `**/v1/projects/${rid}/tree/${aliceMainHead}/markdown/`, + `**/v1/projects/${sourceBrowsingRid}/tree/${aliceMainHead}/src/`, route => route.fulfill({ status: 500 }), ); - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); const sourceTree = page.locator(".source-tree"); - await sourceTree.locator("text=markdown/").click(); + await sourceTree.locator("text=src/").click(); await expect( page.locator("text=Not able to expand directory"), @@ -400,32 +401,33 @@ test.describe("browser error handling", () => { }); test("error appears when file can't be loaded", async ({ page }) => { await page.route( - `**/v1/projects/${rid}/blob/${aliceMainHead}/.hidden`, + `**/v1/projects/${sourceBrowsingRid}/blob/${aliceMainHead}/.hidden`, route => route.fulfill({ status: 500 }), ); - await page.goto(projectFixtureUrl); + 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/${rid}/readme/${aliceMainHead}`, route => - route.fulfill({ status: 500 }), + await page.route( + `**/v1/projects/${sourceBrowsingRid}/readme/${aliceMainHead}`, + route => route.fulfill({ status: 500 }), ); - await page.goto(projectFixtureUrl); + 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/${rid}/blob/${aliceMainHead}/.hidden`, + `**/v1/projects/${sourceBrowsingRid}/blob/${aliceMainHead}/.hidden`, route => route.fulfill({ status: 500 }), ); - await page.goto(`${projectFixtureUrl}/tree/master/.hidden`); + await page.goto(`${sourceBrowsingUrl}/tree/master/.hidden`); await expect(page.locator("text=Not able to load file")).toBeVisible(); }); diff --git a/tests/e2e/project/commit.spec.ts b/tests/e2e/project/commit.spec.ts index 2639bbc9f..5049e367f 100644 --- a/tests/e2e/project/commit.spec.ts +++ b/tests/e2e/project/commit.spec.ts @@ -1,20 +1,21 @@ import { test, expect, - projectFixtureUrl, + sourceBrowsingUrl, bobRemote, bobHead, + aliceRemote, } from "@tests/support/fixtures.js"; -const modifiedFileFixture = `${projectFixtureUrl}/remotes/${bobRemote.substring( +const modifiedFileFixture = `${sourceBrowsingUrl}/remotes/${bobRemote.substring( 8, )}/commits/${bobHead}`; test("navigation from commit list", async ({ page }) => { - await page.goto(projectFixtureUrl); + await page.goto(sourceBrowsingUrl); await page.getByTitle("Change peer").click(); await page.locator(`text=${bobRemote}`).click(); - await page.locator('role=link[name="9 commits"]').click(); + await page.locator('role=link[name="7 commits"]').click(); await page.locator("text=Update readme").click(); await expect(page).toHaveURL(modifiedFileFixture); @@ -43,9 +44,7 @@ test("modified file", async ({ page }) => { { const header = page.locator(".commit .header"); await expect(header.locator("text=Update readme")).toBeVisible(); - await expect( - header.locator("text=ec5eb0b5efb73da17a2d25454cc47eea3967f328"), - ).toBeVisible(); + await expect(header.locator(`text=${bobHead}`)).toBeVisible(); } // Diff header. @@ -60,17 +59,21 @@ test("modified file", async ({ page }) => { test("created file", async ({ page }) => { await page.goto( - `${projectFixtureUrl}/remotes/${bobRemote}/commits/d6318f7f3d9c15b8ac6dd52267c53220d00f0982`, + `${sourceBrowsingUrl}/remotes/${aliceRemote.substring( + 8, + )}/commits/d87e27e38e244fb3346cb9e4df064c080d97647a`, ); await expect( - page.locator("text=1 file added with 9 insertions and 0 deletions"), + page.locator("text=1 file added with 1 insertion and 0 deletions"), ).toBeVisible(); - await expect(page.locator("text=subconscious.txt added")).toBeVisible(); + await expect(page.locator("text=.hidden added")).toBeVisible(); }); test("deleted file", async ({ page }) => { await page.goto( - `${projectFixtureUrl}/remotes/${bobRemote}/commits/cd13c2d9a8a930d64a82b6134b44d1b872e33662`, + `${sourceBrowsingUrl}/remotes/${aliceRemote.substring( + 8, + )}/commits/0e2db54dfd47d87202809917e2342655d9e76296`, ); await expect( page.locator("text=1 file deleted with 0 insertions and 1 deletion"), @@ -80,7 +83,7 @@ test("deleted file", async ({ page }) => { test("navigation to source tree at specific revision", async ({ page }) => { await page.goto( - `${projectFixtureUrl}/commits/0801aceeab500033f8d608778218657bd626ef73`, + `${sourceBrowsingUrl}/commits/0801aceeab500033f8d608778218657bd626ef73`, ); // Go to source tree at this revision. @@ -89,7 +92,7 @@ test("navigation to source tree at specific revision", async ({ page }) => { page.locator("text=Add a deeply nested directory tree"), ).toBeVisible(); await expect(page).toHaveURL( - `${projectFixtureUrl}/tree/0801aceeab500033f8d608778218657bd626ef73/deep/directory/hierarchy/is/entirely/possible/in/git/repositories/.gitkeep`, + `${sourceBrowsingUrl}/tree/0801aceeab500033f8d608778218657bd626ef73/deep/directory/hierarchy/is/entirely/possible/in/git/repositories/.gitkeep`, ); await expect(page.getByTitle("Current branch")).toContainText( "0801aceeab500033f8d608778218657bd626ef73", diff --git a/tests/e2e/project/commits.spec.ts b/tests/e2e/project/commits.spec.ts index cf8e919f3..3dbe40a91 100644 --- a/tests/e2e/project/commits.spec.ts +++ b/tests/e2e/project/commits.spec.ts @@ -1,15 +1,17 @@ import { test, expect, - projectFixtureUrl, + sourceBrowsingUrl, bobRemote, aliceRemote, gitOptions, + aliceMainHead, } from "@tests/support/fixtures.js"; +import { createProject } from "@tests/support/project"; test("peer and branch switching", async ({ page }) => { - await page.goto(projectFixtureUrl); - await page.locator('role=link[name="8 commits"]').click(); + await page.goto(sourceBrowsingUrl); + await page.locator('role=link[name="6 commits"]').click(); // Alice's peer. { @@ -22,13 +24,11 @@ test("peer and branch switching", async ({ page }) => { ); await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible(); - await expect(page.locator(".history .teaser")).toHaveCount(8); + await expect(page.locator(".history .teaser")).toHaveCount(6); const latestCommit = page.locator(".teaser").first(); - await expect(latestCommit).toContainText( - "Adds a new markdown file with an image with a relative path", - ); - await expect(latestCommit).toContainText("fcc9294"); + await expect(latestCommit).toContainText("Add README.md"); + await expect(latestCommit).toContainText(aliceMainHead.substring(0, 7)); const earliestCommit = page.locator(".teaser").last(); await expect(earliestCommit).toContainText( @@ -40,10 +40,10 @@ test("peer and branch switching", async ({ page }) => { await page.locator("text=feature/branch").click(); await expect(page.getByTitle("Current branch")).toContainText( - "feature/branch d6318f7", + "feature/branch 1aded56", ); await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible(); - await expect(page.locator(".history .teaser")).toHaveCount(10); + await expect(page.locator(".history .teaser")).toHaveCount(9); await page.getByTitle("Change branch").click(); await page.locator("text=orphaned-branch").click(); @@ -65,7 +65,7 @@ test("peer and branch switching", async ({ page }) => { )} `, ); - await expect(page.getByText("Tuesday, December 20, 2022")).toBeVisible(); + await expect(page.getByText("Wednesday, December 21, 2022")).toBeVisible(); await expect(page.locator(".group").first().locator(".teaser")).toHaveCount( 1, ); @@ -77,7 +77,7 @@ test("peer and branch switching", async ({ page }) => { const latestCommit = page.locator(".teaser").first(); await expect(latestCommit).toContainText("Update readme"); - await expect(latestCommit).toContainText("ec5eb0b"); + await expect(latestCommit).toContainText("28f3710"); const earliestCommit = page.locator(".teaser").last(); await expect(earliestCommit).toContainText( @@ -98,8 +98,8 @@ test("relative timestamps", async ({ page }) => { }; }); - await page.goto(projectFixtureUrl); - await page.locator('role=link[name="8 commits"]').click(); + await page.goto(sourceBrowsingUrl); + await page.locator('role=link[name="6 commits"]').click(); await page.getByTitle("Change peer").click(); await page.locator(`text=${bobRemote}`).click(); @@ -110,7 +110,7 @@ test("relative timestamps", async ({ page }) => { ); const latestCommit = page.locator(".teaser").first(); await expect(latestCommit).toContainText("Bob Belcher committed now"); - await expect(latestCommit).toContainText("ec5eb0b"); + await expect(latestCommit).toContainText("28f3710"); const earliestCommit = page.locator(".teaser").last(); await expect(earliestCommit).toContainText( "Alice Liddell committed last month", @@ -124,7 +124,7 @@ test("pushing changes while viewing history", async ({ page, peerManager }) => { }); await alice.startHttpd(8090); await alice.startNode(); - const { rid, projectFolder } = await alice.createProject("alice-project"); + const { rid, projectFolder } = await createProject(alice, "alice-project"); await page.goto(`/seeds/127.0.0.1:8090/${rid}`); await page.locator('role=link[name="1 commit"]').click(); await expect(page).toHaveURL(`/seeds/127.0.0.1:8090/${rid}/history`); diff --git a/tests/e2e/search.spec.ts b/tests/e2e/search.spec.ts index b3e05cdeb..be08fb6d5 100644 --- a/tests/e2e/search.spec.ts +++ b/tests/e2e/search.spec.ts @@ -2,19 +2,19 @@ import { searchPlaceholder } from "@app/lib/shared"; import { test, expect, - rid, - projectFixtureUrl, + sourceBrowsingRid, + sourceBrowsingUrl, } from "@tests/support/fixtures.js"; test("navigate to existing project", async ({ page }) => { await page.goto("/"); const searchInput = page.getByPlaceholder(searchPlaceholder); await searchInput.click(); - await searchInput.fill(`${rid}`); + await searchInput.fill(sourceBrowsingRid); await searchInput.press("Enter"); - await expect(page).toHaveURL(projectFixtureUrl); - await expect(searchInput).not.toHaveValue(`${rid}`); + await expect(page).toHaveURL(sourceBrowsingUrl); + await expect(searchInput).not.toHaveValue(sourceBrowsingRid); }); test("navigate to a project that does not exist", async ({ page }) => { diff --git a/tests/e2e/seed.spec.ts b/tests/e2e/seed.spec.ts index 38972fd80..bf8d0e32b 100644 --- a/tests/e2e/seed.spec.ts +++ b/tests/e2e/seed.spec.ts @@ -1,7 +1,7 @@ import { aliceMainHead, expect, - rid, + sourceBrowsingRid, seedRemote, test, } from "@tests/support/fixtures.js"; @@ -20,7 +20,7 @@ test("seed metadata", async ({ page }) => { test("seed projects", async ({ page }) => { await page.goto("/seeds/radicle.local"); - const project = page.locator(".project"); + const project = page.locator(".project", { hasText: "source-browsing" }); // Project metadata. { @@ -33,8 +33,10 @@ test("seed projects", async ({ page }) => { // Show project ID on hover. { - await expect(project.locator(`text=${rid}`)).not.toBeVisible(); + await expect( + project.locator(`text=${sourceBrowsingRid}`), + ).not.toBeVisible(); await project.hover(); - await expect(project.locator(`text=${rid}`)).toBeVisible(); + await expect(project.locator(`text=${sourceBrowsingRid}`)).toBeVisible(); } }); diff --git a/tests/e2e/settings.spec.ts b/tests/e2e/settings.spec.ts index 9764ca408..b5aeb16b2 100644 --- a/tests/e2e/settings.spec.ts +++ b/tests/e2e/settings.spec.ts @@ -1,6 +1,6 @@ -import { test, expect, projectFixtureUrl } from "@tests/support/fixtures.js"; +import { test, expect, sourceBrowsingUrl } from "@tests/support/fixtures.js"; -const sourceBrowsingFixture = `${projectFixtureUrl}/tree/main/src/true.c`; +const sourceBrowsingFixture = `${sourceBrowsingUrl}/tree/main/src/true.c`; test("default settings", async ({ page }) => { await page.goto(sourceBrowsingFixture); diff --git a/tests/fixtures/repos/markdown.tar.bz2 b/tests/fixtures/repos/markdown.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..076dad1a8fcda0de53d9b6237393faf29dc2f744 GIT binary patch literal 4803 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 dY(0)oYGz@mI>QkBbb$ZG+>uTcBsFtVbr+d5E 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 XjS>C9OwZ?Hzxcb7DZ+$@qbumBg;U-! literal 16625 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 AVgLXD diff --git a/tests/support/fixtures.ts b/tests/support/fixtures.ts index b61809d95..5af01735a 100644 --- a/tests/support/fixtures.ts +++ b/tests/support/fixtures.ts @@ -1,24 +1,25 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type * as Stream from "node:stream"; -import type { PeerManager } from "./peerManager.js"; +import type { PeerManager, RadiclePeer } from "./peerManager.js"; import * as Fs from "node:fs/promises"; import * as FsSync from "node:fs"; import * as Path from "node:path"; -import { dirname } from "path"; -import { fileURLToPath } from "url"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; import { test as base, expect } from "@playwright/test"; import * as Process from "./process.js"; import * as logLabel from "@tests/support/logLabel.js"; -import { createPeerManager } from "./peerManager.js"; +import { createPeerManager } from "@tests/support/peerManager.js"; +import { createProject } from "@tests/support/project.js"; export { expect }; const filename = fileURLToPath(import.meta.url); export const supportDir = dirname(filename); export const tmpDir = Path.resolve(supportDir, "..", "./tmp"); -const fixturesDir = Path.resolve(supportDir, "..", "./fixtures"); +export const fixturesDir = Path.resolve(supportDir, "..", "./fixtures"); export const test = base.extend<{ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type @@ -206,10 +207,13 @@ export async function startPalmHttpd() { outputLog: FsSync.createWriteStream(Path.resolve(Path.join(tmpDir, "log"))), }); const palm = await peerManager.startPeer({ name: "palm" }); - await palm.startHttpd(); + await palm.startHttpd(8080); } -export async function createSeedFixture() { +export async function createSourceBrowsingFixture( + peerManager: PeerManager, + palm: RadiclePeer, +) { const projectName = "source-browsing"; const sourceBrowsingDir = Path.join(tmpDir, "repos", projectName); await Fs.mkdir(sourceBrowsingDir, { recursive: true }); @@ -219,17 +223,7 @@ export async function createSeedFixture() { "-C", sourceBrowsingDir, ]); - // Workaround for fixing MaxListenersExceededWarning. - // Since every prefixOutput stream adds stream listeners that don't autoClose. - // TODO: We still seem to have some descriptors left open when running vitest, which we should handle. - const peerManager = await createPeerManager({ - dataDir: Path.resolve(Path.join(tmpDir, "peers")), - outputLog: FsSync.createWriteStream( - Path.join(tmpDir, "log"), - ).setMaxListeners(20), - }); - const palm = await peerManager.startPeer({ name: "palm" }); - await palm.startHttpd(); + const rid = sourceBrowsingRid; const alice = await peerManager.startPeer({ name: "alice", gitOptions: gitOptions["alice"], @@ -240,7 +234,6 @@ export async function createSeedFixture() { gitOptions: gitOptions["bob"], }); const bobProjectPath = Path.join(bob.checkoutPath, "source-browsing"); - await palm.startNode({ trackingPolicy: "track", trackingScope: "all" }); await alice.startNode(); await bob.startNode(); await alice.connect(palm); @@ -304,14 +297,42 @@ export async function createSeedFixture() { ); } -export const aliceMainHead = "fcc929424b82984b7cbff9c01d2e20d9b1249842"; +export async function createMarkdownFixture(peer: RadiclePeer) { + await Fs.mkdir(Path.join(tmpDir, "repos", "markdown")); + await Process.spawn("tar", [ + "-xf", + Path.join(fixturesDir, "repos", "markdown.tar.bz2"), + "-C", + Path.join(tmpDir, "repos", "markdown"), + ]); + const { projectFolder } = await createProject(peer, "markdown"); + await Fs.cp(Path.join(tmpDir, "repos", "markdown"), projectFolder, { + recursive: true, + }); + + await peer.git(["add", "."], { cwd: projectFolder }); + const commitMessage = `Add Markdown cheat sheet + + Borrowed from [Adam Pritchard][ap]. + No modifications were made. + + [ap]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet`; + await peer.git(["commit", "-m", commitMessage], { + cwd: projectFolder, + }); + await peer.git(["push", "rad"], { cwd: projectFolder }); +} + +export const aliceMainHead = "dd068e9aff9a569e597f6abaf84f120dd0cbbd70"; export const aliceRemote = "did:key:z6MkqGC3nWZhYieEVTVDKW5v588CiGfsDSmRVG9ZwwWTvLSK"; export const bobRemote = "did:key:z6Mkg49NtQR2LyYRDCQFK4w1VVHqhypZSSRo7HsyuN7SV7v5"; -export const bobHead = "ec5eb0b5efb73da17a2d25454cc47eea3967f328"; -export const rid = "rad:z4BwwjPCFNVP27FwVbDFgwVwkjcir"; -export const projectFixtureUrl = `/seeds/127.0.0.1/${rid}`; +export const bobHead = "28f37105bb78db48111e36281291ff253dd050e8"; +export const sourceBrowsingRid = "rad:z4BwwjPCFNVP27FwVbDFgwVwkjcir"; +export const markdownRid = "rad:z2tchH2Ti4LxRKdssPQYs6VHE5rsg"; +export const sourceBrowsingUrl = `/seeds/127.0.0.1/${sourceBrowsingRid}`; +export const markdownUrl = `/seeds/127.0.0.1/${markdownRid}`; export const seedPort = 8080; export const seedRemote = "z6MktULudTtAsAhRegYPiZ6631RV3viv12qd4GQF8z1xB22S"; export const gitOptions = { diff --git a/tests/support/globalSetup.ts b/tests/support/globalSetup.ts index 3090e881b..599d5a780 100644 --- a/tests/support/globalSetup.ts +++ b/tests/support/globalSetup.ts @@ -1,16 +1,20 @@ import type { FullConfig } from "@playwright/test"; import * as Fs from "node:fs/promises"; +import * as FsSync from "node:fs"; import * as Path from "node:path"; import * as readline from "node:readline/promises"; import { execa } from "execa"; import { - createSeedFixture, - supportDir, + createMarkdownFixture, + createSourceBrowsingFixture, + gitOptions, startPalmHttpd, + supportDir, tmpDir, -} from "./fixtures.js"; +} from "@tests/support/fixtures.js"; +import { createPeerManager } from "@tests/support/peerManager.js"; const workspacePaths = [Path.join(tmpDir, "peers"), Path.join(tmpDir, "repos")]; @@ -33,8 +37,27 @@ export default async function globalSetup(_config: FullConfig): Promise { } await removeWorkspace(); - console.log("Creating seed fixture"); - await createSeedFixture(); + // Workaround for fixing MaxListenersExceededWarning. + // Since every prefixOutput stream adds stream listeners that don't autoClose. + // TODO: We still seem to have some descriptors left open when running vitest, which we should handle. + const peerManager = await createPeerManager({ + dataDir: Path.resolve(Path.join(tmpDir, "peers")), + outputLog: FsSync.createWriteStream( + Path.join(tmpDir, "peerManager.log"), + ).setMaxListeners(20), + }); + + const palm = await peerManager.startPeer({ + name: "palm", + gitOptions: gitOptions["alice"], + }); + await palm.startHttpd(8080); + await palm.startNode({ trackingPolicy: "track", trackingScope: "all" }); + + console.log("Creating source-browsing fixture"); + await createSourceBrowsingFixture(peerManager, palm); + console.log("Creating markdown fixture"); + await createMarkdownFixture(palm); console.log("Running tests"); } else { await startPalmHttpd(); diff --git a/tests/support/peerManager.ts b/tests/support/peerManager.ts index 885e66c2d..be1b73d16 100644 --- a/tests/support/peerManager.ts +++ b/tests/support/peerManager.ts @@ -81,7 +81,7 @@ export async function createPeerManager(createParams: { outputLog = createParams.outputLog; } else { outputLogFile = await Fs.open( - Path.join(createParams.dataDir, "peer-manager.log"), + Path.join(createParams.dataDir, "peerManager.log"), "a", ); outputLog = outputLogFile.createWriteStream(); @@ -89,7 +89,6 @@ export async function createPeerManager(createParams: { const nodes: RadiclePeer[] = []; return { - // Starts a new node and registers it. async startPeer(params) { const peer = await RadiclePeer.create({ dataPath: createParams.dataDir, @@ -183,9 +182,8 @@ export class RadiclePeer { }); } - public async startHttpd(port = 8080) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.spawn("radicle-httpd", ["--listen", `0.0.0.0:${port}`]); + public async startHttpd(port: number) { + void this.spawn("radicle-httpd", ["--listen", `0.0.0.0:${port}`]); await waitOn({ resources: [`tcp:127.0.0.1:${port}`], @@ -215,7 +213,6 @@ export class RadiclePeer { args.push("--tracking-policy", params.trackingPolicy); } - // eslint-disable-next-line @typescript-eslint/no-floating-promises void this.spawn("radicle-node", args); await waitOn({ @@ -242,43 +239,6 @@ export class RadiclePeer { ); } - // Create a project using the rad CLI. - public async createProject( - name: string, - description = "", - defaultBranch = "main", - ): Promise<{ rid: string; projectFolder: string }> { - const projectFolder = Path.join(this.checkoutPath, name); - - await this.git(["init", name, "--initial-branch", defaultBranch], { - cwd: this.checkoutPath, - }); - await this.git(["commit", "--allow-empty", "--message", "initial commit"], { - cwd: projectFolder, - }); - await this.rad( - [ - "init", - "--name", - name, - "--default-branch", - defaultBranch, - "--description", - description, - "--announce", - ], - { - cwd: projectFolder, - }, - ); - - const { stdout: rid } = await this.rad(["inspect"], { - cwd: projectFolder, - }); - - return { rid, projectFolder }; - } - public async waitForRoutes(rid: string, ...nodes: string[]) { let remaining = nodes; @@ -364,8 +324,7 @@ export class RadiclePeer { }; const childProcess = Process.spawn(cmd, args, opts); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Process.prefixOutput(childProcess, this.nodeId, this.#outputLog); + void Process.prefixOutput(childProcess, this.nodeId, this.#outputLog); return childProcess; } diff --git a/tests/support/project.ts b/tests/support/project.ts new file mode 100644 index 000000000..19fed3752 --- /dev/null +++ b/tests/support/project.ts @@ -0,0 +1,41 @@ +import type { RadiclePeer } from "@tests/support/peerManager"; + +import * as Path from "node:path"; + +// Create a project using the rad CLI. +export async function createProject( + peer: RadiclePeer, + name: string, + description = "", + defaultBranch = "main", +): Promise<{ rid: string; projectFolder: string }> { + const projectFolder = Path.join(peer.checkoutPath, name); + + await peer.git(["init", name, "--initial-branch", defaultBranch], { + cwd: peer.checkoutPath, + }); + await peer.git(["commit", "--allow-empty", "--message", "initial commit"], { + cwd: projectFolder, + }); + await peer.rad( + [ + "init", + "--name", + name, + "--default-branch", + defaultBranch, + "--description", + description, + "--announce", + ], + { + cwd: projectFolder, + }, + ); + + const { stdout: rid } = await peer.rad(["inspect"], { + cwd: projectFolder, + }); + + return { rid, projectFolder }; +} diff --git a/tests/unit/mutexExecutor.test.ts b/tests/unit/mutexExecutor.test.ts index 1f13068e8..848ae7fd9 100644 --- a/tests/unit/mutexExecutor.test.ts +++ b/tests/unit/mutexExecutor.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ import * as sinon from "sinon"; import { describe, expect, test } from "vitest"; @@ -61,14 +60,14 @@ describe("executor", () => { const e = mutexExecutor.create(); const abortListener = sinon.spy(); - e.run(async abort => { + void e.run(async abort => { abort.addEventListener("abort", abortListener); await sleep(10); return "first"; }); expect(abortListener.called).toBe(false); // eslint-disable-next-line @typescript-eslint/no-empty-function - e.run(async () => {}); + void e.run(async () => {}); expect(abortListener.called).toBe(true); }); @@ -113,9 +112,9 @@ describe("worker", () => { const nextOutput = w.output.firstToPromise(); - w.submit(1); - w.submit(2); - w.submit(3); + void w.submit(1); + void w.submit(2); + void w.submit(3); expect(await nextOutput).toEqual(3); }); diff --git a/tests/visual/markdown.spec.ts b/tests/visual/markdown.spec.ts index ecd339f09..6357b85a9 100644 --- a/tests/visual/markdown.spec.ts +++ b/tests/visual/markdown.spec.ts @@ -1,8 +1,8 @@ import type { Page } from "@playwright/test"; -import { test, expect, projectFixtureUrl } from "@tests/support/fixtures.js"; +import { test, expect, markdownUrl } from "@tests/support/fixtures.js"; async function goToSection(section: string, page: Page) { - await page.goto(`${projectFixtureUrl}/tree/main/markdown/cheatsheet.md`, { + await page.goto(`${markdownUrl}/tree/main/cheatsheet.md`, { waitUntil: "networkidle", }); await page.locator(`[href="${section}"]`).click(); @@ -13,7 +13,7 @@ test.describe("markdown rendering", async () => { test.use({ viewport: { width: 1280, height: 450 } }); test("table of contents", async ({ page }) => { await page.goto( - `${projectFixtureUrl}/tree/main/markdown/cheatsheet.md#table-of-contents`, + `${markdownUrl}/tree/main/cheatsheet.md#table-of-contents`, { waitUntil: "networkidle", }, @@ -80,11 +80,31 @@ test.describe("markdown rendering", async () => { }); test.describe(async () => { - test.use({ viewport: { width: 1280, height: 590 } }); test("footnotes", async ({ page }) => { - await goToSection("#footnotes", page); - await expect(page.locator("text=Footnotes aren't part")).toBeVisible(); - await expect(page).toHaveScreenshot(); + await page.goto(`${markdownUrl}/tree/main/footnotes.md#footnotes`, { + waitUntil: "networkidle", + }); + await expect( + page.locator( + "text=This is an example footnote[0]. And some radicle[1] examples.", + ), + ).toBeVisible(); + await expect(page.locator("text=0. https://example.com ↩")).toBeVisible(); + await expect(page.locator("text=1. https://radicle.xyz ↩")).toBeVisible(); + await expect(page).toHaveScreenshot({ fullPage: true }); + + await page.locator("text=Plain").click(); + await expect( + page.locator( + "text=This is an example footnote[^0]. And some radicle[^1] examples.", + ), + ).toBeVisible(); + await expect( + page.locator("text=[^0]: https://example.com"), + ).toBeVisible(); + await expect( + page.locator("text=[^1]: https://radicle.xyz"), + ).toBeVisible(); }); }); @@ -146,3 +166,10 @@ test.describe("markdown rendering", async () => { }); }); }); + +test("relative image not able to being loaded", async ({ page }) => { + await page.goto(`${markdownUrl}/tree/main/loading-image.md`, { + waitUntil: "networkidle", + }); + await expect(page).toHaveScreenshot({ fullPage: true }); +}); diff --git a/tests/visual/project.spec.ts b/tests/visual/project.spec.ts index 94d6109e6..e16a9cf70 100644 --- a/tests/visual/project.spec.ts +++ b/tests/visual/project.spec.ts @@ -1,12 +1,12 @@ import { test, expect, - projectFixtureUrl, + sourceBrowsingUrl, aliceRemote, } from "@tests/support/fixtures.js"; test("source tree page", async ({ page }) => { - await page.goto(projectFixtureUrl, { waitUntil: "networkidle" }); + await page.goto(sourceBrowsingUrl, { waitUntil: "networkidle" }); await expect(page).toHaveScreenshot(); }); @@ -22,7 +22,7 @@ test("commits page", async ({ page }) => { }); await page.goto( - `${projectFixtureUrl}/remotes/${aliceRemote.substring(8)}/history`, + `${sourceBrowsingUrl}/remotes/${aliceRemote.substring(8)}/history`, { waitUntil: "networkidle", }, @@ -43,17 +43,10 @@ test("commit page", async ({ page }) => { }); await page.goto( - `${projectFixtureUrl}/remotes/${aliceRemote.substring( + `${sourceBrowsingUrl}/remotes/${aliceRemote.substring( 8, )}/commits/d6318f7f3d9c15b8ac6dd52267c53220d00f0982`, ); await expect(page.locator("text=subconscious.txt added")).toBeVisible(); await expect(page).toHaveScreenshot({ fullPage: true }); }); - -test("relative image not able to being loaded", async ({ page }) => { - await page.goto(`${projectFixtureUrl}/tree/main/markdown/loading-image.md`, { - waitUntil: "networkidle", - }); - await expect(page).toHaveScreenshot({ fullPage: true }); -});