diff --git a/tests/e2e/bookmarks.$bookmarkId._index.test.ts b/tests/e2e/bookmarks.$bookmarkId._index.test.ts new file mode 100644 index 0000000..d8c3e6f --- /dev/null +++ b/tests/e2e/bookmarks.$bookmarkId._index.test.ts @@ -0,0 +1,85 @@ +import { expect, login, logout, test } from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/bookmarks/bid2"); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Remix \|/); + }); + + test("User can go to a bookmark's tag detail page", async ({ page }) => { + await page.getByRole("link", { name: "tag1", exact: true }).click(); + + await expect(page).toHaveURL("/tags/tid0"); + }); + + test("User can NOT add a bookmark", async ({ page }) => { + await page.getByRole("link", { name: "Add bookmark", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/bookmarks/new"); + }); + + test("User can NOT (un)favorite a bookmark", async ({ page }) => { + await page.getByRole("link", { name: "Unfavorite", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/bookmarks/bid2"); + }); + + test("User can NOT edit a bookmark", async ({ page }) => { + await page + .getByRole("link", { name: "Edit bookmark", exact: true }) + .click(); + + await expect(page).toHaveURL("/login?redirectTo=/bookmarks/bid2/edit"); + }); + + test("User can NOT delete a bookmark", async ({ page }) => { + await page + .getByRole("link", { name: "Delete bookmark", exact: true }) + .click(); + + await expect(page).toHaveURL("/login?redirectTo=/bookmarks/bid2"); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + const { redirectTo } = await login({ + page, + to: "/bookmarks/bid2", + }); + await page.waitForURL(redirectTo); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can add a bookmark", async ({ page }) => { + await page.getByRole("link", { name: "Add bookmark", exact: true }).click(); + + await page.waitForURL("/bookmarks/new"); + }); + + test("User can (un)favorite a bookmark", async ({ page }) => { + await expect( + page.getByRole("button", { name: "Unfavorite", exact: true }), + ).toBeVisible(); + }); + + test("User can edit a bookmark", async ({ page }) => { + await page + .getByRole("link", { name: "Edit bookmark", exact: true }) + .click(); + + await page.waitForURL("/bookmarks/bid2/edit"); + }); + + test("User can delete a bookmark", async ({ page }) => { + await expect( + page.getByRole("button", { name: "Delete bookmark", exact: true }), + ).toBeVisible(); + }); +}); diff --git a/tests/e2e/bookmarks.$bookmarkId.edit.test.ts b/tests/e2e/bookmarks.$bookmarkId.edit.test.ts new file mode 100644 index 0000000..7f79ffc --- /dev/null +++ b/tests/e2e/bookmarks.$bookmarkId.edit.test.ts @@ -0,0 +1,113 @@ +import { + encodeUrlRedirectTo, + expect, + login, + logout, + test, +} from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/bookmarks/bid2/edit"); + }); + + test("User can NOT view the page", async ({ page }) => { + await expect(page).toHaveURL( + encodeUrlRedirectTo({ + page, + url: "/login?redirectTo=/bookmarks/bid2/edit", + }), + ); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + // Login from a different page so we have a history to redirect back to. + const { redirectTo } = await login({ page }); + await page.waitForURL(redirectTo); + await page.goto("/bookmarks/bid2/edit"); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Editing Bookmark… \|/); + }); + + test("User can cancel viewing the form/page", async ({ page }) => { + await page.getByRole("button", { name: "Cancel" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("User can toggle bookmark tags", async ({ page }) => { + await page + .getByRole("button", { name: "Remove tag1", exact: true }) + .press("Enter"); + await page + .getByRole("button", { name: "Add tag1", exact: true }) + .press("Enter"); + }); + + test("User can NOT update bookmark without valid URL", async ({ page }) => { + await page.getByLabel("URL").fill(""); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("URL is required")).toBeVisible(); + + await page.getByLabel("URL").fill("x"); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("URL is invalid")).toBeVisible(); + + await page.getByLabel("URL").fill("http://remix.run"); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("URL is insecure, use https")).toBeVisible(); + + await page.getByLabel("URL").fill("https://x.x"); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("URL is too short")).toBeVisible(); + + const urlOfLength2001 = "https://".concat("x".repeat(1991)).concat(".x"); + await page.getByLabel("URL").fill(urlOfLength2001); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("URL is too long")).toBeVisible(); + + await page.getByLabel("URL").fill("https://conform.guide"); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("URL must be unique")).toBeVisible(); + }); + + test("User can NOT update bookmark without valid Title", async ({ page }) => { + await page.getByLabel("Title").fill("x"); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("Title is too short")).toBeVisible(); + + await page.getByLabel("Title").fill("x".repeat(46)); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("Title is too long")).toBeVisible(); + }); + + test("User can NOT update bookmark without valid Content", async ({ + page, + }) => { + await page.getByLabel("Content").fill("x"); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("Content is too short")).toBeVisible(); + + await page.getByLabel("Content").fill("x".repeat(256)); + await page.getByRole("button", { name: "Update bookmark" }).press("Enter"); + + await expect(page.getByText("Content is too long")).toBeVisible(); + }); +}); diff --git a/tests/e2e/bookmarks._index.test.ts b/tests/e2e/bookmarks._index.test.ts new file mode 100644 index 0000000..933bd3b --- /dev/null +++ b/tests/e2e/bookmarks._index.test.ts @@ -0,0 +1,176 @@ +import { expect, login, logout, test } from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/bookmarks"); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Bookmarks \|/); + }); + + test("User can view bookmarks", async ({ page }) => { + await expect( + page.getByText("TypeScript https://www.typescriptlang.org Unfavorite"), + ).toBeVisible(); + await expect(page.getByText("Zod https://zod.dev Favorite")).toBeVisible(); + await expect( + page.getByText("Conform https://conform.guide Unfavorite"), + ).toBeVisible(); + await expect( + page.getByText("Prisma https://www.prisma.io Favorite"), + ).toBeVisible(); + await expect( + page.getByText("Remix https://remix.run Unfavorite"), + ).toBeVisible(); + await expect( + page.getByText("Tailwind CSS https://tailwindcss.com Favorite"), + ).toBeVisible(); + }); + + test("User can search bookmarks by keyword", async ({ page }) => { + await page.getByPlaceholder("Search for…").fill("mix"); + await page.getByPlaceholder("Search for…").press("Enter"); + + await expect(page).toHaveURL("/bookmarks?searchKey=url&searchValue=mix"); + await expect(page.getByRole("group").getByText("URL")).toBeChecked(); + await expect(page.getByPlaceholder("Search for…")).toHaveValue("mix"); + await expect( + page.getByText("TypeScript https://www.typescriptlang.org Unfavorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Zod https://zod.dev Favorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Conform https://conform.guide Unfavorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Prisma https://www.prisma.io Favorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Remix https://remix.run Unfavorite"), + ).toBeVisible(); + await expect( + page.getByText("Tailwind CSS https://tailwindcss.com Favorite"), + ).not.toBeVisible(); + }); + + test("User can search bookmarks by keyword and column name", async ({ + page, + }) => { + await page.getByRole("group").getByText("Content").click(); + await page.getByPlaceholder("Search for…").fill("mix"); + await page.getByRole("button", { name: "Submit" }).press("Enter"); + + await expect(page).toHaveURL( + "/bookmarks?searchKey=content&searchValue=mix", + ); + await expect(page.getByRole("group").getByText("Content")).toBeChecked(); + await expect(page.getByPlaceholder("Search for…")).toHaveValue("mix"); + await expect( + page.getByText("TypeScript https://www.typescriptlang.org Unfavorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Zod https://zod.dev Favorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Conform https://conform.guide Unfavorite"), + ).toBeVisible(); + await expect( + page.getByText("Prisma https://www.prisma.io Favorite"), + ).not.toBeVisible(); + await expect( + page.getByText("Remix https://remix.run Unfavorite"), + ).toBeVisible(); + await expect( + page.getByText("Tailwind CSS https://tailwindcss.com Favorite"), + ).not.toBeVisible(); + }); + + test("User can reset search form", async ({ page }) => { + await page.goto("/bookmarks?searchKey=tags&searchValue=badvalue"); + + await expect(page.getByRole("group").getByText("Tags")).toBeChecked(); + await expect(page.getByPlaceholder("Search for…")).toHaveValue("badvalue"); + + await page.getByRole("link", { name: "Reset" }).click(); + + await expect(page).toHaveURL("/bookmarks"); + await expect(page.getByRole("group").getByText("URL")).toBeChecked(); + await expect(page.getByPlaceholder("Search for…")).toHaveValue(""); + }); + + test("User can go to a bookmark's detail page", async ({ page }) => { + await page.getByRole("link", { name: "Remix", exact: true }).click(); + + await expect(page).toHaveTitle(/^Remix \|/); + await expect(page).toHaveURL(/\/bookmarks\/[a-zA-Z0-9]+$/); + }); + + test("User can NOT view bookmarks if bookmarks data is missing", async ({ + page, + }) => { + await page.getByRole("group").getByText("Tags").click(); + await page.getByPlaceholder("Search for…").fill("badvalue"); + await page.getByRole("button", { name: "Submit" }).press("Enter"); + + await expect(page).toHaveURL( + "/bookmarks?searchKey=tags&searchValue=badvalue", + ); + await expect( + page.getByRole("heading", { name: "No Bookmarks Found" }), + ).toBeVisible(); + }); + + test("User can NOT search bookmarks if keyword is invalid", async ({ + page, + }) => { + await page.getByPlaceholder("Search for…").fill("x"); + await page.getByPlaceholder("Search for…").press("Enter"); + + await expect(page.getByText("Search term is too short")).toBeVisible(); + + await page.getByPlaceholder("Search for…").fill("x".repeat(46)); + await page.getByPlaceholder("Search for…").press("Enter"); + + await expect(page.getByText("Search term is too long")).toBeVisible(); + }); + + test("User can NOT add a bookmark", async ({ page }) => { + await page.getByRole("link", { name: "Add bookmark", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/bookmarks/new"); + }); + + test("User can NOT (un)favorite a bookmark", async ({ page }) => { + await page + .getByRole("link", { name: "Unfavorite", exact: true }) + .first() + .click(); + + await expect(page).toHaveURL("/login?redirectTo=/bookmarks"); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + const { redirectTo } = await login({ page, to: "/bookmarks" }); + await page.waitForURL(redirectTo); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can add a bookmark", async ({ page }) => { + await page.getByRole("link", { name: "Add bookmark", exact: true }).click(); + + await page.waitForURL("/bookmarks/new"); + }); + + test("User can (un)favorite a bookmark", async ({ page }) => { + await expect( + page.getByRole("button", { name: "Unfavorite", exact: true }).first(), + ).toBeVisible(); + }); +}); diff --git a/tests/e2e/bookmarks.new.test.ts b/tests/e2e/bookmarks.new.test.ts new file mode 100644 index 0000000..b1b0551 --- /dev/null +++ b/tests/e2e/bookmarks.new.test.ts @@ -0,0 +1,110 @@ +import { + encodeUrlRedirectTo, + expect, + login, + logout, + test, +} from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/bookmarks/new"); + }); + + test("User can NOT view the page", async ({ page }) => { + await expect(page).toHaveURL( + encodeUrlRedirectTo({ page, url: "/login?redirectTo=/bookmarks/new" }), + ); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + // Login from a different page so we have a history to redirect back to. + const { redirectTo } = await login({ page }); + await page.waitForURL(redirectTo); + await page.goto("/bookmarks/new"); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^New Bookmark \|/); + }); + + test("User can cancel viewing the form/page", async ({ page }) => { + await page.getByRole("button", { name: "Cancel" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("User can toggle bookmark tags", async ({ page }) => { + await page + .getByRole("button", { name: "Add tag1", exact: true }) + .press("Enter"); + await page + .getByRole("button", { name: "Remove tag1", exact: true }) + .press("Enter"); + }); + + test("User can NOT update bookmark without valid URL", async ({ page }) => { + await page.getByLabel("URL").fill(""); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("URL is required")).toBeVisible(); + + await page.getByLabel("URL").fill("x"); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("URL is invalid")).toBeVisible(); + + await page.getByLabel("URL").fill("http://remix.run"); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("URL is insecure, use https")).toBeVisible(); + + await page.getByLabel("URL").fill("https://x.x"); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("URL is too short")).toBeVisible(); + + const urlOfLength2001 = "https://".concat("x".repeat(1991)).concat(".x"); + await page.getByLabel("URL").fill(urlOfLength2001); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("URL is too long")).toBeVisible(); + + await page.getByLabel("URL").fill("https://conform.guide"); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("URL must be unique")).toBeVisible(); + }); + + test("User can NOT update bookmark without valid Title", async ({ page }) => { + await page.getByLabel("Title").fill("x"); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("Title is too short")).toBeVisible(); + + await page.getByLabel("Title").fill("x".repeat(46)); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("Title is too long")).toBeVisible(); + }); + + test("User can NOT update bookmark without valid Content", async ({ + page, + }) => { + await page.getByLabel("Content").fill("x"); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("Content is too short")).toBeVisible(); + + await page.getByLabel("Content").fill("x".repeat(256)); + await page.getByRole("button", { name: "Add bookmark" }).press("Enter"); + + await expect(page.getByText("Content is too long")).toBeVisible(); + }); +}); diff --git a/tests/e2e/tags.$tagId._index.test.ts b/tests/e2e/tags.$tagId._index.test.ts new file mode 100644 index 0000000..05cee3f --- /dev/null +++ b/tests/e2e/tags.$tagId._index.test.ts @@ -0,0 +1,91 @@ +import { expect, login, logout, test } from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/tags/tid0"); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^tag1 \|/); + }); + + test("User can search bookmarks by tag", async ({ page }) => { + await page.getByText("Bookmarks(6)").click(); + + await expect(page).toHaveURL("/bookmarks?searchValue=tag1&searchKey=tags"); + }); + + test("User can NOT add a tag", async ({ page }) => { + await page.getByRole("link", { name: "Add tag", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/tags/new"); + }); + + test("User can NOT edit a tag", async ({ page }) => { + await page.getByRole("link", { name: "Edit tag", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/tags/tid0/edit"); + }); + + test("User can NOT split a tag", async ({ page }) => { + await page.getByRole("link", { name: "Split tag", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/tags/tid0/split"); + }); + + test("User can NOT merge a tag", async ({ page }) => { + await page.getByRole("link", { name: "Merge tag", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/tags/tid0/merge"); + }); + + test("User can NOT delete a tag", async ({ page }) => { + await page.getByRole("link", { name: "Delete tag", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/tags/tid0"); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + const { redirectTo } = await login({ + page, + to: "/tags/tid0", + }); + await page.waitForURL(redirectTo); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can add a tag", async ({ page }) => { + await page.getByRole("link", { name: "Add tag", exact: true }).click(); + + await page.waitForURL("/tags/new"); + }); + + test("User can edit a tag", async ({ page }) => { + await page.getByRole("link", { name: "Edit tag", exact: true }).click(); + + await expect(page).toHaveURL("/tags/tid0/edit"); + }); + + test("User can split a tag", async ({ page }) => { + await page.getByRole("link", { name: "Split tag", exact: true }).click(); + + await expect(page).toHaveURL("/tags/tid0/split"); + }); + + test("User can merge a tag", async ({ page }) => { + await page.getByRole("link", { name: "Merge tag", exact: true }).click(); + + await expect(page).toHaveURL("/tags/tid0/merge"); + }); + + test("User can delete a tag", async ({ page }) => { + await expect( + page.getByRole("button", { name: "Delete tag", exact: true }), + ).toBeVisible(); + }); +}); diff --git a/tests/e2e/tags.$tagId.edit.test.ts b/tests/e2e/tags.$tagId.edit.test.ts new file mode 100644 index 0000000..65e21ef --- /dev/null +++ b/tests/e2e/tags.$tagId.edit.test.ts @@ -0,0 +1,82 @@ +import { + encodeUrlRedirectTo, + expect, + login, + logout, + test, +} from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/tags/tid0/edit"); + }); + + test("User can NOT view the page", async ({ page }) => { + await expect(page).toHaveURL( + encodeUrlRedirectTo({ + page, + url: "/login?redirectTo=/tags/tid0/edit", + }), + ); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + // Login from a different page so we have a history to redirect back to. + const { redirectTo } = await login({ page }); + await page.waitForURL(redirectTo); + await page.goto("/tags/tid0/edit"); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Editing Tag… \|/); + }); + + test("User can cancel viewing the form/page", async ({ page }) => { + await page.getByRole("button", { name: "Cancel" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("User can NOT update tag without valid Name", async ({ page }) => { + await page.getByLabel("Name").fill(""); + await page.getByRole("button", { name: "Update tag" }).press("Enter"); + + await expect(page.getByText("Name is required")).toBeVisible(); + + await page.getByLabel("Name").fill("x"); + await page.getByRole("button", { name: "Update tag" }).press("Enter"); + + await expect(page.getByText("Name is too short")).toBeVisible(); + + await page.getByLabel("Name").fill("x".repeat(46)); + await page.getByRole("button", { name: "Update tag" }).press("Enter"); + + await expect(page.getByText("Name is too long")).toBeVisible(); + + await page.getByLabel("Name").fill("x!"); + await page.getByRole("button", { name: "Update tag" }).press("Enter"); + + await expect( + page.getByText( + "Name can only include letters, numbers, hyphens, and periods", + ), + ).toBeVisible(); + + await page.getByLabel("Name").fill("tag2"); + await page.getByRole("button", { name: "Update tag" }).press("Enter"); + + await expect(page.getByText("Name must be unique")).toBeVisible(); + }); + + test("User can delete a tag", async ({ page }) => { + await expect( + page.getByRole("button", { name: "Delete tag", exact: true }), + ).toBeVisible(); + }); +}); diff --git a/tests/e2e/tags.$tagId.merge.test.ts b/tests/e2e/tags.$tagId.merge.test.ts new file mode 100644 index 0000000..513338e --- /dev/null +++ b/tests/e2e/tags.$tagId.merge.test.ts @@ -0,0 +1,53 @@ +import { + encodeUrlRedirectTo, + expect, + login, + logout, + test, +} from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/tags/tid0/merge"); + }); + + test("User can NOT view the page", async ({ page }) => { + await expect(page).toHaveURL( + encodeUrlRedirectTo({ + page, + url: "/login?redirectTo=/tags/tid0/merge", + }), + ); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + // Login from a different page so we have a history to redirect back to. + const { redirectTo } = await login({ page }); + await page.waitForURL(redirectTo); + await page.goto("/tags/tid0/merge"); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Merging Tag… \|/); + }); + + test("User can cancel viewing the form/page", async ({ page }) => { + await page.getByRole("button", { name: "Cancel" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("User can NOT merge tag without valid Target", async ({ page }) => { + await page.getByLabel("Target").click(); + await page.locator("body").press("Escape"); + await page.getByRole("button", { name: "Merge tag" }).press("Enter"); + + await expect(page.getByText("Name is required")).toBeVisible(); + }); +}); diff --git a/tests/e2e/tags.$tagId.split.test.ts b/tests/e2e/tags.$tagId.split.test.ts new file mode 100644 index 0000000..1c0cc5a --- /dev/null +++ b/tests/e2e/tags.$tagId.split.test.ts @@ -0,0 +1,76 @@ +import { + encodeUrlRedirectTo, + expect, + login, + logout, + test, +} from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/tags/tid0/split"); + }); + + test("User can NOT view the page", async ({ page }) => { + await expect(page).toHaveURL( + encodeUrlRedirectTo({ + page, + url: "/login?redirectTo=/tags/tid0/split", + }), + ); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + // Login from a different page so we have a history to redirect back to. + const { redirectTo } = await login({ page }); + await page.waitForURL(redirectTo); + await page.goto("/tags/tid0/split"); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Splitting Tag… \|/); + }); + + test("User can cancel viewing the form/page", async ({ page }) => { + await page.getByRole("button", { name: "Cancel" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("User can NOT split tag without valid Target(s)", async ({ page }) => { + await page.getByLabel("Target").fill(""); + await page.getByRole("button", { name: "Split tag" }).press("Enter"); + + await expect(page.getByText("Name is required")).toBeVisible(); + + await page.getByLabel("Target").fill("x"); + await page.getByRole("button", { name: "Split tag" }).press("Enter"); + + await expect(page.getByText("Name is too short")).toBeVisible(); + + await page.getByLabel("Target").fill("x".repeat(46)); + await page.getByRole("button", { name: "Split tag" }).press("Enter"); + + await expect(page.getByText("Name is too long")).toBeVisible(); + + await page.getByLabel("Target").fill("x!"); + await page.getByRole("button", { name: "Split tag" }).press("Enter"); + + await expect( + page.getByText( + "Name can only include letters, numbers, hyphens, and periods", + ), + ).toBeVisible(); + + await page.getByLabel("Target").fill("t1,tag2,t3"); + await page.getByRole("button", { name: "Split tag" }).press("Enter"); + + await expect(page.getByText("Name must be unique")).toBeVisible(); + }); +}); diff --git a/tests/e2e/tags._index.test.ts b/tests/e2e/tags._index.test.ts new file mode 100644 index 0000000..6f9c026 --- /dev/null +++ b/tests/e2e/tags._index.test.ts @@ -0,0 +1,65 @@ +import { expect, login, logout, test } from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/tags"); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^Tags \|/); + }); + + test("User can view tags", async ({ page }) => { + const tagNameRegex = /^[a-zA-Z0-9-.\s]+\s\(\s\d+\s\)/; + const tags = await page.getByRole("link", { name: tagNameRegex }).all(); + + expect(tags.length).toBe(11); + }); + + test("User can view tags sorted by Name", async ({ page }) => { + await expect( + page.getByText( + "taaaaaaaaaaaaag that is exactly 45 characters (0)tag1 (6)tag10 (0)tag2 (5)tag3 (4)tag4 (3)tag5 (2)tag6 (1)tag7 (0)tag8 (0)tag9 (0)", + ), + ).toBeVisible(); + }); + + test("User can view tags sorted by Relations", async ({ page }) => { + await page.getByRole("button", { name: "Relations" }).click(); + + await expect( + page.getByText( + "tag1 (6)tag2 (5)tag3 (4)tag4 (3)tag5 (2)tag6 (1)taaaaaaaaaaaaag that is exactly 45 characters (0)tag10 (0)tag7 (0)tag8 (0)tag9 (0)", + ), + ).toBeVisible(); + }); + + test("User can go to a tag's detail page", async ({ page }) => { + await page.getByRole("link", { name: "tag1 ( 6 )", exact: true }).click(); + + await expect(page).toHaveURL("/tags/tid0"); + }); + + test("User can NOT add a tag", async ({ page }) => { + await page.getByRole("link", { name: "Add tag", exact: true }).click(); + + await expect(page).toHaveURL("/login?redirectTo=/tags/new"); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + const { redirectTo } = await login({ page, to: "/tags" }); + await page.waitForURL(redirectTo); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can add a tag", async ({ page }) => { + await page.getByRole("link", { name: "Add tag", exact: true }).click(); + + await page.waitForURL("/tags/new"); + }); +}); diff --git a/tests/e2e/tags.new.test.ts b/tests/e2e/tags.new.test.ts new file mode 100644 index 0000000..b1ecf2a --- /dev/null +++ b/tests/e2e/tags.new.test.ts @@ -0,0 +1,73 @@ +import { + encodeUrlRedirectTo, + expect, + login, + logout, + test, +} from "../utils/playwright-test-utils"; + +test.describe("Unauthenticated", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/tags/new"); + }); + + test("User can NOT view the page", async ({ page }) => { + await expect(page).toHaveURL( + encodeUrlRedirectTo({ page, url: "/login?redirectTo=/tags/new" }), + ); + }); +}); + +test.describe("Authenticated", () => { + test.beforeEach(async ({ page }) => { + // Login from a different page so we have a history to redirect back to. + const { redirectTo } = await login({ page }); + await page.waitForURL(redirectTo); + await page.goto("/tags/new"); + }); + + test.afterEach(async ({ page }) => { + await logout({ page }); + }); + + test("User can view the page title", async ({ page }) => { + await expect(page).toHaveTitle(/^New Tag \|/); + }); + + test("User can cancel viewing the form/page", async ({ page }) => { + await page.getByRole("button", { name: "Cancel" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("User can NOT add tag(s) without valid Name(s)", async ({ page }) => { + await page.getByLabel("Name").fill(""); + await page.getByRole("button", { name: "Add tag" }).press("Enter"); + + await expect(page.getByText("Name is required")).toBeVisible(); + + await page.getByLabel("Name").fill("x"); + await page.getByRole("button", { name: "Add tag" }).press("Enter"); + + await expect(page.getByText("Name is too short")).toBeVisible(); + + await page.getByLabel("Name").fill("x".repeat(46)); + await page.getByRole("button", { name: "Add tag" }).press("Enter"); + + await expect(page.getByText("Name is too long")).toBeVisible(); + + await page.getByLabel("Name").fill("x!"); + await page.getByRole("button", { name: "Add tag" }).press("Enter"); + + await expect( + page.getByText( + "Name can only include letters, numbers, hyphens, and periods", + ), + ).toBeVisible(); + + await page.getByLabel("Name").fill("t1,tag2,t3"); + await page.getByRole("button", { name: "Add tag" }).press("Enter"); + + await expect(page.getByText("Name must be unique")).toBeVisible(); + }); +});