diff --git a/tests/e2e/fixtures/resume.ts b/tests/e2e/fixtures/resume.ts index 2fb498b04..1393dd943 100644 --- a/tests/e2e/fixtures/resume.ts +++ b/tests/e2e/fixtures/resume.ts @@ -64,10 +64,27 @@ export async function openSidebarSection(page: Page, title: string) { await expect(row).toHaveAttribute("aria-expanded", "true"); } -/** Opens the Download dialog with every format from the ▾ next to Download PDF. */ +/** Opens Share & export on its Download tab, from the ▾ next to Download PDF. */ export async function openDownloadDialog(page: Page) { await page.getByRole("button", { name: "More download formats", exact: true }).click(); - await expect(page.getByRole("dialog", { name: "Download" })).toBeVisible(); + const sheet = page.getByRole("dialog", { name: "Share & export" }); + await expect(sheet.getByRole("tab", { name: "Download", exact: true })).toHaveAttribute("aria-selected", "true"); + return sheet; +} + +/** Turns the public link on in Share → Link and returns the public address. */ +export async function makeResumePublic(page: Page) { + await openSidebarSection(page, "Sharing"); + const sheet = page.getByRole("dialog", { name: "Share & export" }); + await sheet.getByRole("switch", { name: "Public link" }).click(); + return getPublicUrl(page); +} + +/** The public address, from Share → Link's "Open public page" (it fills in once the session has loaded). */ +export async function getPublicUrl(page: Page) { + const link = page.getByRole("dialog", { name: "Share & export" }).getByRole("link", { name: "Open public page" }); + await expect(link).toHaveAttribute("href", /\/e2e_/); + return (await link.getAttribute("href")) as string; } export async function openResumeCardMenu(page: Page, resumeName: string, { reload = true } = {}) { diff --git a/tests/e2e/specs/hyphenation.spec.ts b/tests/e2e/specs/hyphenation.spec.ts index cf85f45ed..610764666 100644 --- a/tests/e2e/specs/hyphenation.spec.ts +++ b/tests/e2e/specs/hyphenation.spec.ts @@ -61,12 +61,9 @@ async function seedHyphenationResume(page: Page) { } async function downloadPdfText(page: Page, testInfo: TestInfo, name: string) { - await openDownloadDialog(page); + const sheet = await openDownloadDialog(page); const pending = page.waitForEvent("download"); - await page - .getByRole("dialog", { name: "Download" }) - .getByRole("button", { name: "Download PDF", exact: true }) - .click(); + await sheet.getByRole("button", { name: "Download PDF", exact: true }).click(); const download = await pending; const path = testInfo.outputPath(`${name}.pdf`); await download.saveAs(path); diff --git a/tests/e2e/specs/json-export-import.spec.ts b/tests/e2e/specs/json-export-import.spec.ts index 36db467ad..f514869b4 100644 --- a/tests/e2e/specs/json-export-import.spec.ts +++ b/tests/e2e/specs/json-export-import.spec.ts @@ -5,9 +5,10 @@ import { expect, test } from "../fixtures/test"; test("exports and imports a resume JSON backup", async ({ authPage: page }, testInfo) => { await createSampleResumeFromDashboard(page, testInfo); - await openDownloadDialog(page); + const sheet = await openDownloadDialog(page); + await sheet.getByRole("radio", { name: /^JSON/ }).click(); const downloadPromise = page.waitForEvent("download"); - await page.getByRole("button", { name: "Download JSON" }).click(); + await sheet.getByRole("button", { name: "Download JSON" }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toMatch(/\.json$/); diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts index 18525f08a..d4787b5f8 100644 --- a/tests/e2e/specs/offline-fonts.spec.ts +++ b/tests/e2e/specs/offline-fonts.spec.ts @@ -360,12 +360,9 @@ test.describe("offline font diagnostic", () => { let rasterEvidenceStatus = "not-attempted"; try { await page.goto(fixture.builderURL); - await openDownloadDialog(page); + const sheet = await openDownloadDialog(page); const downloadPromise = page.waitForEvent("download", { timeout: 20_000 }); - await page - .getByRole("dialog", { name: "Download" }) - .getByRole("button", { name: "Download PDF", exact: true }) - .click(); + await sheet.getByRole("button", { name: "Download PDF", exact: true }).click(); const download = await downloadPromise; downloadStatus = "received"; const path = testInfo.outputPath("offline-font-browser-download.pdf"); diff --git a/tests/e2e/specs/preview-export-geometry.spec.ts b/tests/e2e/specs/preview-export-geometry.spec.ts index 74e820594..7f043c5a0 100644 --- a/tests/e2e/specs/preview-export-geometry.spec.ts +++ b/tests/e2e/specs/preview-export-geometry.spec.ts @@ -4,7 +4,6 @@ import { createRequire } from "node:module"; import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs"; import { Pool } from "pg"; import { defaultResumeData } from "@reactive-resume/schema/resume/default"; -import { generateFilename } from "@reactive-resume/utils/file"; import { createSampleResumeFromDashboard, openDownloadDialog } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; @@ -544,8 +543,7 @@ async function runGeometryMatrix(page: Page, testInfo: TestInfo, scenarios: Geom dedicatedDatabaseUrl(); await page.setViewportSize({ width: 1920, height: 1000 }); await installPreviewCapture(page); - const resumeName = await createSampleResumeFromDashboard(page, testInfo); - const expectedExportFilename = generateFilename(resumeName, "pdf"); + await createSampleResumeFromDashboard(page, testInfo); const resumeId = new URL(page.url()).pathname.match(/^\/builder\/([^/]+)/)?.[1]; if (!resumeId) throw new Error("Missing synthetic geometry resume id."); @@ -582,17 +580,11 @@ async function runGeometryMatrix(page: Page, testInfo: TestInfo, scenarios: Geom persistedFixture.revision, ); const pending = page.waitForEvent("download"); - await openDownloadDialog(page); - const downloadDialog = page.getByRole("dialog", { name: "Download" }); - await expect(downloadDialog.getByRole("tab", { name: "Resume", exact: true })).toHaveAttribute( - "aria-selected", - "true", - ); - await downloadDialog.getByRole("button", { name: "Download PDF", exact: true }).click(); + const downloadSheet = await openDownloadDialog(page); + await downloadSheet.getByRole("button", { name: "Download PDF", exact: true }).click(); const download = await pending; - expect(download.suggestedFilename(), `${scenarioName(scenario)} zoom ${zoom} export target`).toBe( - expectedExportFilename, - ); + // The default file name is "First-Last-Resume"; this test only checks it's the PDF. + expect(download.suggestedFilename(), `${scenarioName(scenario)} zoom ${zoom} export target`).toMatch(/\.pdf$/); const downloadPath = testInfo.outputPath(`${scenarioName(scenario)}.zoom-${zoom}.pdf`); await download.saveAs(downloadPath); const downloadBytes = new Uint8Array(await readFile(downloadPath)); diff --git a/tests/e2e/specs/public-download-preference.spec.ts b/tests/e2e/specs/public-download-preference.spec.ts index 89085a4bc..1d35e240c 100644 --- a/tests/e2e/specs/public-download-preference.spec.ts +++ b/tests/e2e/specs/public-download-preference.spec.ts @@ -1,20 +1,22 @@ -import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume"; +import { + createSampleResumeFromDashboard, + getPublicUrl, + makeResumePublic, + openSidebarSection, +} from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; test("persists public download-button visibility", async ({ browser, authPage: page }, testInfo) => { await createSampleResumeFromDashboard(page, testInfo); - await openSidebarSection(page, "Sharing"); - await page.getByRole("switch", { name: /Allow Public Access/ }).click(); - const downloadPreference = page.getByRole("switch", { name: "Show Download Buttons" }); + await makeResumePublic(page); + const downloadPreference = page.getByRole("switch", { name: "Visitors can download the PDF" }); await expect(downloadPreference).toBeChecked(); await downloadPreference.click(); await expect(downloadPreference).not.toBeChecked(); await page.reload(); await openSidebarSection(page, "Sharing"); await expect(downloadPreference).not.toBeChecked(); - // The address fills in once the session has loaded after the reload. - await expect(page.locator("#sharing-url")).toHaveValue(/\/e2e_/); - const publicUrl = await page.locator("#sharing-url").inputValue(); + const publicUrl = await getPublicUrl(page); const anonymous = await browser.newPage(); try { diff --git a/tests/e2e/specs/public-sharing.spec.ts b/tests/e2e/specs/public-sharing.spec.ts index 6ce3983fa..18d4bb0d5 100644 --- a/tests/e2e/specs/public-sharing.spec.ts +++ b/tests/e2e/specs/public-sharing.spec.ts @@ -1,4 +1,4 @@ -import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume"; +import { createSampleResumeFromDashboard, makeResumePublic } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; test("counts a visitor's PDF download without counting the preview", async ({ browser, authPage: page }, testInfo) => { @@ -11,13 +11,7 @@ test("counts a visitor's PDF download without counting the preview", async ({ br expect(response.ok()).toBe(true); return response.json(); }; - await openSidebarSection(page, "Sharing"); - - await page.getByRole("switch", { name: /Allow Public Access/ }).click(); - const sharingUrl = page.locator("#sharing-url"); - await expect(sharingUrl).toHaveValue(/\/e2e_/); - const publicUrl = await sharingUrl.inputValue(); - expect(publicUrl).toMatch(/\/e2e_/); + const publicUrl = await makeResumePublic(page); const anonymous = await browser.newPage(); try { diff --git a/tests/e2e/specs/section-recovery.spec.ts b/tests/e2e/specs/section-recovery.spec.ts index 45d9554f8..73f0debdb 100644 --- a/tests/e2e/specs/section-recovery.spec.ts +++ b/tests/e2e/specs/section-recovery.spec.ts @@ -100,12 +100,9 @@ async function showSection(page: Page, title: string) { } async function downloadPdfText(page: Page, testInfo: TestInfo, name: string) { - await openDownloadDialog(page); + const sheet = await openDownloadDialog(page); const pending = page.waitForEvent("download"); - await page - .getByRole("dialog", { name: "Download" }) - .getByRole("button", { name: "Download PDF", exact: true }) - .click(); + await sheet.getByRole("button", { name: "Download PDF", exact: true }).click(); const download = await pending; const path = testInfo.outputPath(`${name}.pdf`); await download.saveAs(path); diff --git a/tests/e2e/specs/share-history.spec.ts b/tests/e2e/specs/share-history.spec.ts new file mode 100644 index 000000000..2453438d5 --- /dev/null +++ b/tests/e2e/specs/share-history.spec.ts @@ -0,0 +1,47 @@ +import { createSampleResumeFromDashboard, getPublicUrl, makeResumePublic } from "../fixtures/resume"; +import { expect, test } from "../fixtures/test"; + +test("renames the public address and keeps the old one redirecting", async ({ browser, authPage: page }, testInfo) => { + await createSampleResumeFromDashboard(page, testInfo); + const oldUrl = await makeResumePublic(page); + const sheet = page.getByRole("dialog", { name: "Share & export" }); + const address = sheet.getByRole("textbox", { name: "Address" }); + + await address.fill("not_valid!"); + await expect(sheet.getByText("Use lowercase letters, numbers and single dashes.")).toBeVisible(); + + await address.fill("renamed-address"); + await expect(sheet.getByText(/^Live at .*\/renamed-address$/)).toBeVisible(); + expect(await getPublicUrl(page)).toMatch(/\/renamed-address$/); + + const visitor = await browser.newPage(); + try { + await visitor.goto(oldUrl); + await visitor.waitForURL(/\/renamed-address$/); + await expect(visitor.getByRole("heading", { level: 1 })).toBeVisible(); + } finally { + await visitor.close(); + } +}); + +test("names a version, previews an older one read-only and restores it", async ({ authPage: page }, testInfo) => { + await createSampleResumeFromDashboard(page, testInfo); + await page.getByRole("button", { name: "History", exact: true }).click(); + const sheet = page.getByRole("dialog", { name: "Share & export" }); + const versions = sheet.getByRole("list", { name: "Versions" }); + // A new document's history starts with where it came from. + await expect(versions.getByRole("button", { name: /^Created/ })).toBeVisible(); + + await sheet.getByRole("textbox", { name: "Name this version" }).fill("Sent to Lumen"); + await sheet.getByRole("button", { name: "Save", exact: true }).click(); + await expect(versions.getByRole("button", { name: /^Sent to Lumen/ })).toBeVisible(); + + await versions.getByRole("button", { name: /^Created/ }).click(); + // The sheet announces it; the page (outside the modal sheet) shows the same banner above the version. + await expect(sheet.getByRole("status").filter({ hasText: /Viewing .* · Created · read-only/ })).toBeVisible(); + await expect(page.getByText(/^Viewing .* · Created · read-only$/).first()).toBeVisible(); + + await sheet.getByRole("button", { name: "Restore this version" }).click(); + await expect(versions.getByRole("button", { name: /^Before restore/ })).toBeVisible(); + await expect(versions.getByRole("button", { name: /^Now/ })).toHaveAttribute("aria-pressed", "true"); +}); diff --git a/tests/e2e/specs/sharing-password.spec.ts b/tests/e2e/specs/sharing-password.spec.ts index a464af78d..c0665fbc8 100644 --- a/tests/e2e/specs/sharing-password.spec.ts +++ b/tests/e2e/specs/sharing-password.spec.ts @@ -1,17 +1,13 @@ -import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume"; +import { createSampleResumeFromDashboard, makeResumePublic } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; test("password-protects a public resume and unlocks it as a visitor", async ({ browser, authPage: page }, testInfo) => { await createSampleResumeFromDashboard(page, testInfo); - await openSidebarSection(page, "Sharing"); - - await page.getByRole("switch", { name: /Allow Public Access/ }).click(); - const sharingUrl = page.locator("#sharing-url"); - await expect(sharingUrl).toHaveValue(/\/e2e_/); - const publicUrl = await sharingUrl.inputValue(); + const publicUrl = await makeResumePublic(page); const password = "e2e-secret-42"; - await page.getByRole("button", { name: "Set Password" }).click(); + const requirePassword = page.getByRole("switch", { name: "Require a password" }); + await requirePassword.click(); const dialog = page.getByRole("dialog", { name: "Protect your resume with a password" }); await dialog.getByLabel("Password", { exact: true }).fill(password); await dialog.getByLabel("Confirm Password", { exact: true }).fill("different-password"); @@ -19,7 +15,7 @@ test("password-protects a public resume and unlocks it as a visitor", async ({ b await expect(dialog.getByRole("alert")).toHaveText("Passwords do not match."); await dialog.getByLabel("Confirm Password", { exact: true }).fill(password); await dialog.getByRole("button", { name: "Set Password" }).click(); - await expect(page.getByRole("button", { name: "Remove Password" })).toBeVisible(); + await expect(requirePassword).toBeChecked(); const anonymous = await browser.newPage(); try {