Skip to content

Commit 1240e77

Browse files
Add e2e my-posts page tests for delete interactions (#1209)
* test: add e2e tests for managing delete modal interactions (#1169) - Implement tests to ensure the delete modal can be closed with both 'Cancel' and 'Close' buttons. - Add test to verify deletion of a published article through the delete modal. - Introduce utility functions `openPublishedTab` and `openDeleteModal` to streamline modal interactions in tests. * feat(utils): add createArticle function for setting up test articles * feat: enhance test setup with additional articles in setup.ts * fix: improve selectors and function names, update delete test * fix: code style issue in types/types.ts --------- Co-authored-by: Niall Maher <[email protected]>
1 parent f26aca8 commit 1240e77

File tree

5 files changed

+297
-66
lines changed

5 files changed

+297
-66
lines changed

e2e/constants/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const articleContent =
22
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae ipsum id metus vestibulum rutrum eget a diam. Integer eget vulputate risus, ac convallis nulla. Mauris sed augue nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam congue posuere tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ac augue non libero ullamcorper ornare. Ut commodo ligula vitae malesuada maximus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam sagittis justo non justo placerat, a dapibus sapien volutpat. Nullam ullamcorper sodales justo sed.";
33

4-
export const articleExcerpt = "Lorem ipsum dolor sit amet";
4+
export const articleExcerpt = "This is an excerpt for a published article.";
55

66
export const E2E_USER_ONE_EMAIL = "[email protected]";
77
export const E2E_USER_ONE_ID = "8e3179ce-f32b-4d0a-ba3b-234d66b836ad";

e2e/my-posts.spec.ts

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
1+
import type { Page } from "@playwright/test";
12
import test, { expect } from "@playwright/test";
2-
import { articleExcerpt, loggedInAsUserOne } from "./utils";
3+
import { loggedInAsUserOne, createArticle } from "./utils";
4+
import { articleExcerpt } from "./constants";
5+
6+
type TabName = "Drafts" | "Scheduled" | "Published";
7+
8+
async function openTab(page: Page, tabName: TabName) {
9+
await page.goto("http://localhost:3000/my-posts");
10+
await page.getByRole("link", { name: tabName }).click();
11+
const slug = tabName.toLowerCase();
12+
await page.waitForURL(`http://localhost:3000/my-posts?tab=${slug}`);
13+
await expect(page).toHaveURL(new RegExp(`\\/my-posts\\?tab=${slug}`));
14+
}
15+
16+
async function openDeleteModal(page: Page, title: string) {
17+
const article = page.locator(`article:has-text("${title}")`);
18+
await expect(article).toBeVisible();
19+
await article.locator("button.dropdown-button").click();
20+
await article.locator('text="Delete"').click();
21+
await expect(
22+
page.getByText("Are you sure you want to delete this article?"),
23+
).toBeVisible();
24+
}
325

426
test.describe("Unauthenticated my-posts Page", () => {
527
test("Unauthenticated users should be redirected to get-started page if they access my-posts directly", async ({
@@ -35,22 +57,76 @@ test.describe("Authenticated my-posts Page", () => {
3557
await expect(page.getByRole("link", { name: "Scheduled" })).toBeVisible();
3658
await expect(page.getByRole("link", { name: "Published" })).toBeVisible();
3759

38-
await page.getByRole("link", { name: "Drafts" }).click();
60+
await openTab(page, "Published");
3961
await expect(
40-
page.getByRole("heading", { name: "Draft Article" }),
62+
page.getByRole("heading", { name: "Published Article" }),
4163
).toBeVisible();
4264
await expect(page.getByText(articleExcerpt)).toBeVisible();
4365

44-
await page.getByRole("link", { name: "Scheduled" }).click();
66+
await openTab(page, "Scheduled");
4567
await expect(
4668
page.getByRole("heading", { name: "Scheduled Article" }),
4769
).toBeVisible();
48-
await expect(page.getByText(articleExcerpt)).toBeVisible();
70+
await expect(
71+
page.getByText("This is an excerpt for a scheduled article."),
72+
).toBeVisible();
4973

50-
await page.getByRole("link", { name: "Published" }).click();
74+
await openTab(page, "Drafts");
5175
await expect(
52-
page.getByRole("heading", { name: "Published Article" }),
76+
page.getByRole("heading", { name: "Draft Article" }),
77+
).toBeVisible();
78+
await expect(
79+
page.getByText("This is an excerpt for a draft article.", {
80+
exact: true,
81+
}),
5382
).toBeVisible();
54-
await expect(page.getByText(articleExcerpt, { exact: true })).toBeVisible();
83+
});
84+
85+
test("User should close delete modal with Cancel button", async ({
86+
page,
87+
}) => {
88+
const title = "Published Article";
89+
await page.goto("http://localhost:3000/my-posts");
90+
await openTab(page, "Published");
91+
await openDeleteModal(page, title);
92+
93+
const closeButton = page.getByRole("button", { name: "Cancel" });
94+
await closeButton.click();
95+
96+
await expect(
97+
page.locator("text=Are you sure you want to delete this article?"),
98+
).toBeHidden();
99+
});
100+
101+
test("User should close delete modal with Close button", async ({ page }) => {
102+
const title = "Published Article";
103+
await page.goto("http://localhost:3000/my-posts");
104+
await openTab(page, "Published");
105+
await openDeleteModal(page, title);
106+
107+
const closeButton = page.getByRole("button", { name: "Close" });
108+
await closeButton.click();
109+
110+
await expect(
111+
page.locator("text=Are you sure you want to delete this article?"),
112+
).toBeHidden();
113+
});
114+
115+
test("User should delete published article", async ({ page }) => {
116+
const article = {
117+
id: "test-id-for-deletion",
118+
title: "Article to be deleted",
119+
slug: "article-to-be-deleted",
120+
excerpt: "This is an excerpt for the article to be deleted.",
121+
body: "This is the body for the article to be deleted.",
122+
};
123+
await createArticle(article);
124+
await page.goto("http://localhost:3000/my-posts");
125+
await openTab(page, "Published");
126+
await expect(page.getByRole("link", { name: article.title })).toBeVisible();
127+
await openDeleteModal(page, article.title);
128+
129+
await page.getByRole("button", { name: "Delete" }).click();
130+
await expect(page.getByRole("link", { name: article.slug })).toHaveCount(0);
55131
});
56132
});

e2e/setup.ts

Lines changed: 150 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
E2E_USER_TWO_SESSION_ID,
1313
} from "./constants";
1414
import { eq } from "drizzle-orm";
15+
import type { Article } from "@/types/types";
1516

1617
export const setup = async () => {
1718
// Dynamically import nanoid
@@ -20,70 +21,164 @@ export const setup = async () => {
2021
const db = drizzle(
2122
postgres("postgresql://postgres:[email protected]:5432/postgres"),
2223
);
24+
2325
const addE2EArticleAndComment = async (
2426
authorId: string,
2527
commenterId: string,
2628
) => {
2729
const publishedPostId = nanoid(8);
2830
const scheduledPostId = nanoid(8);
2931
const draftPostId = nanoid(8);
30-
const now = new Date().toISOString();
3132

32-
const oneYearFromToday = new Date(now);
33-
oneYearFromToday.setFullYear(oneYearFromToday.getFullYear() + 1);
34-
35-
await Promise.all([
36-
db
37-
.insert(post)
38-
.values({
39-
id: publishedPostId,
40-
published: now,
41-
excerpt: articleExcerpt,
42-
updatedAt: now,
43-
slug: "e2e-test-slug-published",
44-
likes: 10,
45-
readTimeMins: 3,
46-
title: "Published Article",
47-
body: articleContent,
48-
userId: authorId,
49-
})
50-
.onConflictDoNothing()
51-
.returning(),
33+
const now = new Date().toISOString();
34+
const scheduled = new Date(
35+
new Date().setFullYear(new Date().getFullYear() + 1),
36+
).toISOString();
5237

53-
db
54-
.insert(post)
55-
.values({
56-
id: draftPostId,
57-
published: null,
58-
excerpt: articleExcerpt,
59-
updatedAt: now,
60-
slug: "e2e-test-slug-draft",
61-
likes: 10,
62-
readTimeMins: 3,
63-
title: "Draft Article",
64-
body: articleContent,
65-
userId: authorId,
66-
})
67-
.onConflictDoNothing()
68-
.returning(),
38+
const articles: Article[] = [
39+
{
40+
id: publishedPostId,
41+
title: "Published Article",
42+
slug: "e2e-test-slug-published",
43+
excerpt: articleExcerpt,
44+
body: articleContent,
45+
likes: 0,
46+
readTimeMins: 2,
47+
published: now,
48+
updatedAt: now,
49+
userId: authorId,
50+
},
51+
{
52+
id: scheduledPostId,
53+
title: "Scheduled Article",
54+
slug: "e2e-test-slug-scheduled",
55+
excerpt: "This is an excerpt for a scheduled article.",
56+
body: "This is the body for a scheduled article.",
57+
likes: 0,
58+
readTimeMins: 2,
59+
published: scheduled,
60+
updatedAt: now,
61+
userId: authorId,
62+
},
63+
{
64+
id: draftPostId,
65+
title: "Draft Article",
66+
slug: "e2e-test-slug-draft",
67+
excerpt: "This is an excerpt for a draft article.",
68+
body: "This is the body for a draft article.",
69+
likes: 0,
70+
readTimeMins: 2,
71+
published: null,
72+
updatedAt: now,
73+
userId: authorId,
74+
},
75+
{
76+
id: nanoid(8),
77+
title: "Next.js Best Practices",
78+
slug: "e2e-nextjs-best-practices",
79+
excerpt:
80+
"Optimize your Next.js applications with these best practices.",
81+
body: "This guide explores how to structure your Next.js projects effectively, utilize Server-Side Rendering (SSR) and Static Site Generation (SSG) to enhance performance, and make the most of API routes to handle server-side logic.",
82+
likes: 20,
83+
readTimeMins: 4,
84+
published: now,
85+
updatedAt: now,
86+
userId: authorId,
87+
},
88+
{
89+
id: nanoid(8),
90+
title: "Understanding HTML5 Semantics",
91+
slug: "e2e-understanding-html5-semantics",
92+
excerpt: "Master the use of semantic tags in HTML5.",
93+
body: "Semantic HTML5 elements are foundational to web accessibility and search engine optimization.",
94+
likes: 15,
95+
readTimeMins: 3,
96+
published: now,
97+
updatedAt: now,
98+
userId: authorId,
99+
},
100+
{
101+
id: nanoid(8),
102+
title: "JavaScript ES6 Features",
103+
slug: "e2e-javascript-es6-features",
104+
excerpt: "Discover the powerful features of ES6.",
105+
body: "ECMAScript 6 introduces a wealth of new features to JavaScript, revolutionizing how developers write JS.",
106+
likes: 25,
107+
readTimeMins: 5,
108+
published: null,
109+
updatedAt: now,
110+
userId: authorId,
111+
},
112+
{
113+
id: nanoid(8),
114+
title: "CSS Grid vs. Flexbox",
115+
slug: "e2e-css-grid-vs-flexbox",
116+
excerpt: "Choosing between CSS Grid and Flexbox.",
117+
body: "CSS Grid and Flexbox are powerful tools for creating responsive layouts.",
118+
likes: 18,
119+
readTimeMins: 4,
120+
published: null,
121+
updatedAt: now,
122+
userId: authorId,
123+
},
124+
{
125+
id: nanoid(8),
126+
title: "React Hooks Explained",
127+
slug: "e2e-react-hooks-explained",
128+
excerpt: "Simplify your React code with Hooks.",
129+
body: "React Hooks provide a robust solution to use state and other React features without writing a class.",
130+
likes: 22,
131+
readTimeMins: 5,
132+
published: scheduled,
133+
updatedAt: now,
134+
userId: authorId,
135+
},
136+
{
137+
id: nanoid(8),
138+
title: "Web Accessibility Fundamentals",
139+
slug: "e2e-web-accessibility-fundamentals",
140+
excerpt: "Essential guidelines for web accessibility.",
141+
body: "Creating accessible websites is a critical aspect of modern web development.",
142+
likes: 20,
143+
readTimeMins: 3,
144+
published: scheduled,
145+
updatedAt: now,
146+
userId: authorId,
147+
},
148+
];
69149

70-
db
71-
.insert(post)
72-
.values({
73-
id: scheduledPostId,
74-
published: oneYearFromToday.toISOString(),
75-
excerpt: articleExcerpt,
76-
updatedAt: now,
77-
slug: "e2e-test-slug-scheduled",
78-
likes: 10,
79-
readTimeMins: 3,
80-
title: "Scheduled Article",
81-
body: articleContent,
82-
userId: authorId,
83-
})
84-
.onConflictDoNothing()
85-
.returning(),
86-
]);
150+
await Promise.all(
151+
articles.map(
152+
({
153+
id,
154+
title,
155+
slug,
156+
excerpt,
157+
body,
158+
likes,
159+
readTimeMins,
160+
published,
161+
updatedAt,
162+
userId,
163+
}) =>
164+
db
165+
.insert(post)
166+
.values({
167+
id,
168+
title,
169+
slug,
170+
excerpt,
171+
body,
172+
likes,
173+
readTimeMins,
174+
published,
175+
updatedAt,
176+
userId,
177+
})
178+
.onConflictDoNothing()
179+
.returning(),
180+
),
181+
);
87182

88183
await db
89184
.insert(comment)
@@ -119,7 +214,7 @@ export const setup = async () => {
119214
email,
120215
image: `https://robohash.org/${encodeURIComponent(name)}?bgset=bg1`,
121216
location: "Ireland",
122-
bio: "Hi I am an robot",
217+
bio: "Hi I am a robot",
123218
websiteUrl: "codu.co",
124219
};
125220
const [createdUser] = await db.insert(user).values(userData).returning();

0 commit comments

Comments
 (0)