From 8c832c78b350664617210b5a5d9ddb44cf5a9bb7 Mon Sep 17 00:00:00 2001
From: Hasan <57855533+hasan-py@users.noreply.github.com>
Date: Sat, 12 Oct 2024 14:31:15 +0600
Subject: [PATCH] Revert "First test case" (#2)
---
client/e2e/example.spec.ts | 18 +
client/e2e/fixtures/createModerator.json | 4 -
client/e2e/fixtures/moderatorExists.json | 3 -
client/e2e/fixtures/moderatorNotExists.json | 3 -
client/e2e/support/constants.ts | 9 -
client/e2e/tests/home.spec.ts | 45 --
client/e2e/tests/login.spec.ts | 140 -------
client/package.json | 4 +-
client/playwright.config.ts | 27 +-
client/src/pages/gameList/gameCard.tsx | 4 +-
client/src/pages/gameList/index.tsx | 4 +-
client/src/pages/gameReview/index.tsx | 12 +-
client/src/pages/moderator/mapView.tsx | 6 +-
client/tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++++
client/tsconfig.json | 5 +-
15 files changed, 484 insertions(+), 237 deletions(-)
create mode 100644 client/e2e/example.spec.ts
delete mode 100644 client/e2e/fixtures/createModerator.json
delete mode 100644 client/e2e/fixtures/moderatorExists.json
delete mode 100644 client/e2e/fixtures/moderatorNotExists.json
delete mode 100644 client/e2e/support/constants.ts
delete mode 100644 client/e2e/tests/home.spec.ts
delete mode 100644 client/e2e/tests/login.spec.ts
create mode 100644 client/tests-examples/demo-todo-app.spec.ts
diff --git a/client/e2e/example.spec.ts b/client/e2e/example.spec.ts
new file mode 100644
index 0000000..54a906a
--- /dev/null
+++ b/client/e2e/example.spec.ts
@@ -0,0 +1,18 @@
+import { test, expect } from '@playwright/test';
+
+test('has title', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Expect a title "to contain" a substring.
+ await expect(page).toHaveTitle(/Playwright/);
+});
+
+test('get started link', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Click the get started link.
+ await page.getByRole('link', { name: 'Get started' }).click();
+
+ // Expects page to have a heading with the name of Installation.
+ await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
+});
diff --git a/client/e2e/fixtures/createModerator.json b/client/e2e/fixtures/createModerator.json
deleted file mode 100644
index b0f6f8a..0000000
--- a/client/e2e/fixtures/createModerator.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "success": true,
- "message": "Moderator created successfully"
-}
diff --git a/client/e2e/fixtures/moderatorExists.json b/client/e2e/fixtures/moderatorExists.json
deleted file mode 100644
index ed421bd..0000000
--- a/client/e2e/fixtures/moderatorExists.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "userExists": true
-}
diff --git a/client/e2e/fixtures/moderatorNotExists.json b/client/e2e/fixtures/moderatorNotExists.json
deleted file mode 100644
index 76250b3..0000000
--- a/client/e2e/fixtures/moderatorNotExists.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "userExists": false
-}
diff --git a/client/e2e/support/constants.ts b/client/e2e/support/constants.ts
deleted file mode 100644
index 1d72ab8..0000000
--- a/client/e2e/support/constants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-const serverURL = (path: string) => {
- return "http://localhost:8000" + path;
-};
-
-export const API_ROUTES = {
- LOGIN: serverURL("/api/auth/login"),
- CHECK_MODERATOR: serverURL("/api/auth/check-moderator"),
- CREATE_MODERATOR: serverURL("/api/auth/create-moderator"),
-};
diff --git a/client/e2e/tests/home.spec.ts b/client/e2e/tests/home.spec.ts
deleted file mode 100644
index 74949fa..0000000
--- a/client/e2e/tests/home.spec.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { test, expect } from "@playwright/test";
-
-test.describe("Home Page", () => {
- test("page rendered successfully", async ({ page }) => {
- await page.goto("/");
- await expect(
- page.locator("text=Find your next captivating gaming moment")
- ).toBeVisible();
- });
-
- test("Game List: should display game cards with images, titles, and review counts", async ({
- page,
- }) => {
- await page.goto("/");
-
- // Check that the grid exists
- await expect(page.locator(".grid")).toBeVisible();
-
- const gameCards = page.locator(".grid .col-span-1");
- const cardCount = await gameCards.count(); // Get the number of game cards
-
- // Check if the count is greater than 1
- expect(cardCount).toBeGreaterThan(1);
-
- // Check the first game card
- const firstGameCard = gameCards.first();
- await expect(firstGameCard.locator("img")).toHaveAttribute(
- "src",
- /thumbnail\.jpg/
- );
- await expect(firstGameCard.locator("img")).toHaveAttribute(
- "alt",
- /Game Image/
- );
-
- // Check game card details
- await expect(firstGameCard.locator("p.text-lg")).toBeVisible();
- await expect(firstGameCard.locator("p.text-lg")).not.toBeEmpty();
-
- await expect(firstGameCard.locator("p.text-sm")).toBeVisible();
- await expect(firstGameCard.locator("p.text-sm")).not.toBeEmpty();
-
- await expect(firstGameCard.locator("p.text-xs")).toContainText("Reviews");
- });
-});
diff --git a/client/e2e/tests/login.spec.ts b/client/e2e/tests/login.spec.ts
deleted file mode 100644
index 3d84673..0000000
--- a/client/e2e/tests/login.spec.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-// import { test, expect, Page } from "@playwright/test";
-// import { API_ROUTES } from "../support/constants";
-
-// test.describe("Moderator Authentication", () => {
-// test.beforeEach(async ({ page }) => {
-// await page.goto("/login");
-// });
-
-// const interceptCheckModerator = async (page: Page, fixtureName: string) => {
-// await page.route(API_ROUTES.CHECK_MODERATOR, async (route) => {
-// const jsonResponse = await route.fetch();
-// await jsonResponse.json();
-// route.fulfill({
-// status: 200,
-// });
-// });
-// };
-
-// test("should display login or create account based on user existence", async ({
-// page,
-// }) => {
-// await interceptCheckModerator(page, "moderatorExists.json");
-
-// // Visit the login page or trigger the action that sends the request
-// await page.goto("/login"); // Ensure the page that triggers the API call is visited
-
-// // Now wait for the response
-// const response = await page.waitForResponse(API_ROUTES.CHECK_MODERATOR);
-// const data = response.json();
-// const userExists = data;
-
-// // Expectation based on the response
-// if (!!userExists) {
-// await expect(page.locator("text=Login")).toBeVisible();
-// } else {
-// await expect(page.locator("text=Create")).toBeVisible();
-// }
-// });
-
-// test("should show error on login validations", async ({ page }) => {
-// await interceptCheckModerator(page, "moderatorExists.json");
-
-// await page.click("button");
-// await expect(page.locator("text=Invalid email address")).toBeVisible();
-// await expect(
-// page.locator("text=Password must be at least 6 characters")
-// ).toBeVisible();
-// });
-
-// test("should show error on create moderator validations", async ({
-// page,
-// }) => {
-// await interceptCheckModerator(page, "moderatorNotExists.json");
-
-// await page.click("button");
-// await expect(page.locator("text=Invalid email address")).toBeVisible();
-// await expect(
-// page.locator("text=Password must be at least 6 characters")
-// ).toBeVisible();
-// await expect(page.locator("text=Name is required")).toBeVisible();
-// });
-
-// test("should toggle the password visibility", async ({ page }) => {
-// await interceptCheckModerator(page, "moderatorExists.json");
-
-// await page.fill('input[placeholder="Password"]', "password123");
-// await page.click("#toggle-password");
-// await expect(page.locator('input[placeholder="Password"]')).toHaveAttribute(
-// "type",
-// "text"
-// );
-// await expect(page.locator('input[placeholder="Password"]')).toHaveValue(
-// "password123"
-// );
-// await page.click("#toggle-password");
-// await expect(page.locator('input[placeholder="Password"]')).toHaveAttribute(
-// "type",
-// "password"
-// );
-// });
-
-// test("should create an account for new users", async ({ page }) => {
-// await interceptCheckModerator(page, "moderatorNotExists.json");
-
-// await page.route(API_ROUTES.CREATE_MODERATOR, (route) =>
-// route.fulfill({
-// status: 200,
-// body: JSON.stringify({
-// success: true,
-// message: "Moderator created! Please login!",
-// }),
-// })
-// );
-
-// const response = await page.waitForResponse(API_ROUTES.CHECK_MODERATOR);
-// const data = await response.json();
-// const userExists = data.userExists;
-
-// if (!userExists) {
-// await expect(page.locator("text=Create Account")).toBeVisible();
-// await page.fill('input[placeholder="Name"]', "John Doe");
-// await page.fill('input[placeholder="Email"]', "john@example.com");
-// await page.fill('input[placeholder="Password"]', "password123");
-// await page.click("button");
-
-// const createModeratorResponse = await page.waitForResponse(
-// API_ROUTES.CREATE_MODERATOR
-// );
-// const createModeratorData = await createModeratorResponse.json();
-
-// expect(createModeratorData.success).toBeTruthy();
-// await expect(
-// page.locator("text=Moderator created! Please login!")
-// ).toBeVisible();
-// }
-// });
-
-// test("should login an existing user", async ({ page }) => {
-// await interceptCheckModerator(page, "moderatorExists.json");
-
-// await page.route(API_ROUTES.LOGIN, (route) =>
-// route.fulfill({
-// status: 200,
-// body: JSON.stringify({ token: "mockToken" }),
-// })
-// );
-
-// await page.fill('input[placeholder="Email"]', "john@example.com");
-// await page.fill('input[placeholder="Password"]', "password123");
-// await page.click("button");
-
-// const loginResponse = await page.waitForResponse(API_ROUTES.LOGIN);
-// const loginData = await loginResponse.json();
-
-// expect(loginData.token).toBe("mockToken");
-// await expect(page.locator("text=Login successfully")).toBeVisible();
-
-// await expect(page).toHaveURL("/admin");
-// });
-// });
diff --git a/client/package.json b/client/package.json
index 3554c42..009d311 100644
--- a/client/package.json
+++ b/client/package.json
@@ -6,9 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
- "preview": "vite preview",
- "e2e": "npx playwright test",
- "e2e:ui": "npx playwright test --ui"
+ "preview": "vite preview"
},
"dependencies": {
"@chakra-ui/icons": "^2.0.18",
diff --git a/client/playwright.config.ts b/client/playwright.config.ts
index 0582a58..e3309cf 100644
--- a/client/playwright.config.ts
+++ b/client/playwright.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig, devices } from "@playwright/test";
+import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
@@ -12,7 +12,7 @@ import { defineConfig, devices } from "@playwright/test";
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
- testDir: "./e2e",
+ testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -22,25 +22,26 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
- reporter: "html",
+ reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
- baseURL: "http://127.0.0.1:5173",
+ // baseURL: 'http://127.0.0.1:3000',
+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: "on-first-retry",
+ trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
- name: "chromium",
- use: { ...devices["Desktop Chrome"] },
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
},
{
- name: "firefox",
- use: { ...devices["Desktop Firefox"] },
+ name: 'firefox',
+ use: { ...devices['Desktop Firefox'] },
},
// {
@@ -50,8 +51,8 @@ export default defineConfig({
/* Test against mobile viewports. */
{
- name: "Mobile Chrome",
- use: { ...devices["Pixel 5"] },
+ name: 'Mobile Chrome',
+ use: { ...devices['Pixel 5'] },
},
// {
// name: 'Mobile Safari',
@@ -64,8 +65,8 @@ export default defineConfig({
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
{
- name: "Google Chrome",
- use: { ...devices["Desktop Chrome"], channel: "chrome" },
+ name: 'Google Chrome',
+ use: { ...devices['Desktop Chrome'], channel: 'chrome' },
},
],
diff --git a/client/src/pages/gameList/gameCard.tsx b/client/src/pages/gameList/gameCard.tsx
index b280aee..6aaac43 100644
--- a/client/src/pages/gameList/gameCard.tsx
+++ b/client/src/pages/gameList/gameCard.tsx
@@ -27,8 +27,8 @@ export default function GameCard({ item }: { item: any }) {
/>
{item?.reviews?.length > 0 ? (
- {[...Array(Math.floor(+item?.avgRating))]?.map((_, key) => (
-
+ {[...Array(Math.floor(+item?.avgRating))]?.map((_) => (
+
))}
{+item?.avgRating?.toFixed(2)}
diff --git a/client/src/pages/gameList/index.tsx b/client/src/pages/gameList/index.tsx
index 58c1299..7fbbc3b 100644
--- a/client/src/pages/gameList/index.tsx
+++ b/client/src/pages/gameList/index.tsx
@@ -38,8 +38,8 @@ export default function Home() {
{!isLoading && data?.length > 0 ? (
- {data?.map((item: any, key: number) => (
-
+ {data?.map((item: any) => (
+
))}
diff --git a/client/src/pages/gameReview/index.tsx b/client/src/pages/gameReview/index.tsx
index 81b5cba..22cd12a 100644
--- a/client/src/pages/gameReview/index.tsx
+++ b/client/src/pages/gameReview/index.tsx
@@ -81,8 +81,8 @@ export default function Review() {
{avgRating() > 0 ? (
- {[...Array(Math.floor(+avgRating()))]?.map((_, key) => (
-
+ {[...Array(Math.floor(+avgRating()))]?.map((_) => (
+
))}
{avgRating()?.toFixed(2)}
@@ -103,8 +103,8 @@ export default function Review() {
- {data?.reviews?.map((item: any, key:number) => (
-
+ {data?.reviews?.map((item: any) => (
+
@@ -124,8 +124,8 @@ export default function Review() {
{+item?.rating > 0 ? (
- {[...Array(Math.floor(+item?.rating))]?.map((_, key) => (
-
+ {[...Array(Math.floor(+item?.rating))]?.map((_) => (
+
))}
{item?.rating}
diff --git a/client/src/pages/moderator/mapView.tsx b/client/src/pages/moderator/mapView.tsx
index 144d0e8..2d64915 100644
--- a/client/src/pages/moderator/mapView.tsx
+++ b/client/src/pages/moderator/mapView.tsx
@@ -85,9 +85,8 @@ export default function MapView(props: any) {
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
- {data?.map((item: any, key: number) => (
+ {data?.map((item: any) => (
@@ -101,9 +100,8 @@ export default function MapView(props: any) {
{item?.reviews ? (
- {[...Array(+item?.reviews?.rating)]?.map((star, key) => (
+ {[...Array(+item?.reviews?.rating)]?.map((star) => (
{
+ await page.goto('https://demo.playwright.dev/todomvc');
+});
+
+const TODO_ITEMS = [
+ 'buy some cheese',
+ 'feed the cat',
+ 'book a doctors appointment'
+] as const;
+
+test.describe('New Todo', () => {
+ test('should allow me to add todo items', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create 1st todo.
+ await newTodo.fill(TODO_ITEMS[0]);
+ await newTodo.press('Enter');
+
+ // Make sure the list only has one todo item.
+ await expect(page.getByTestId('todo-title')).toHaveText([
+ TODO_ITEMS[0]
+ ]);
+
+ // Create 2nd todo.
+ await newTodo.fill(TODO_ITEMS[1]);
+ await newTodo.press('Enter');
+
+ // Make sure the list now has two todo items.
+ await expect(page.getByTestId('todo-title')).toHaveText([
+ TODO_ITEMS[0],
+ TODO_ITEMS[1]
+ ]);
+
+ await checkNumberOfTodosInLocalStorage(page, 2);
+ });
+
+ test('should clear text input field when an item is added', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create one todo item.
+ await newTodo.fill(TODO_ITEMS[0]);
+ await newTodo.press('Enter');
+
+ // Check that input is empty.
+ await expect(newTodo).toBeEmpty();
+ await checkNumberOfTodosInLocalStorage(page, 1);
+ });
+
+ test('should append new items to the bottom of the list', async ({ page }) => {
+ // Create 3 items.
+ await createDefaultTodos(page);
+
+ // create a todo count locator
+ const todoCount = page.getByTestId('todo-count')
+
+ // Check test using different methods.
+ await expect(page.getByText('3 items left')).toBeVisible();
+ await expect(todoCount).toHaveText('3 items left');
+ await expect(todoCount).toContainText('3');
+ await expect(todoCount).toHaveText(/3/);
+
+ // Check all items in one call.
+ await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS);
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+});
+
+test.describe('Mark all as completed', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test.afterEach(async ({ page }) => {
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test('should allow me to mark all items as completed', async ({ page }) => {
+ // Complete all todos.
+ await page.getByLabel('Mark all as complete').check();
+
+ // Ensure all todos have 'completed' class.
+ await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']);
+ await checkNumberOfCompletedTodosInLocalStorage(page, 3);
+ });
+
+ test('should allow me to clear the complete state of all items', async ({ page }) => {
+ const toggleAll = page.getByLabel('Mark all as complete');
+ // Check and then immediately uncheck.
+ await toggleAll.check();
+ await toggleAll.uncheck();
+
+ // Should be no completed classes.
+ await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']);
+ });
+
+ test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => {
+ const toggleAll = page.getByLabel('Mark all as complete');
+ await toggleAll.check();
+ await expect(toggleAll).toBeChecked();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 3);
+
+ // Uncheck first todo.
+ const firstTodo = page.getByTestId('todo-item').nth(0);
+ await firstTodo.getByRole('checkbox').uncheck();
+
+ // Reuse toggleAll locator and make sure its not checked.
+ await expect(toggleAll).not.toBeChecked();
+
+ await firstTodo.getByRole('checkbox').check();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 3);
+
+ // Assert the toggle all is checked again.
+ await expect(toggleAll).toBeChecked();
+ });
+});
+
+test.describe('Item', () => {
+
+ test('should allow me to mark items as complete', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create two items.
+ for (const item of TODO_ITEMS.slice(0, 2)) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+
+ // Check first item.
+ const firstTodo = page.getByTestId('todo-item').nth(0);
+ await firstTodo.getByRole('checkbox').check();
+ await expect(firstTodo).toHaveClass('completed');
+
+ // Check second item.
+ const secondTodo = page.getByTestId('todo-item').nth(1);
+ await expect(secondTodo).not.toHaveClass('completed');
+ await secondTodo.getByRole('checkbox').check();
+
+ // Assert completed class.
+ await expect(firstTodo).toHaveClass('completed');
+ await expect(secondTodo).toHaveClass('completed');
+ });
+
+ test('should allow me to un-mark items as complete', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create two items.
+ for (const item of TODO_ITEMS.slice(0, 2)) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+
+ const firstTodo = page.getByTestId('todo-item').nth(0);
+ const secondTodo = page.getByTestId('todo-item').nth(1);
+ const firstTodoCheckbox = firstTodo.getByRole('checkbox');
+
+ await firstTodoCheckbox.check();
+ await expect(firstTodo).toHaveClass('completed');
+ await expect(secondTodo).not.toHaveClass('completed');
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+
+ await firstTodoCheckbox.uncheck();
+ await expect(firstTodo).not.toHaveClass('completed');
+ await expect(secondTodo).not.toHaveClass('completed');
+ await checkNumberOfCompletedTodosInLocalStorage(page, 0);
+ });
+
+ test('should allow me to edit an item', async ({ page }) => {
+ await createDefaultTodos(page);
+
+ const todoItems = page.getByTestId('todo-item');
+ const secondTodo = todoItems.nth(1);
+ await secondTodo.dblclick();
+ await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]);
+ await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
+ await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter');
+
+ // Explicitly assert the new text value.
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ 'buy some sausages',
+ TODO_ITEMS[2]
+ ]);
+ await checkTodosInLocalStorage(page, 'buy some sausages');
+ });
+});
+
+test.describe('Editing', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test('should hide other controls when editing', async ({ page }) => {
+ const todoItem = page.getByTestId('todo-item').nth(1);
+ await todoItem.dblclick();
+ await expect(todoItem.getByRole('checkbox')).not.toBeVisible();
+ await expect(todoItem.locator('label', {
+ hasText: TODO_ITEMS[1],
+ })).not.toBeVisible();
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test('should save edits on blur', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur');
+
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ 'buy some sausages',
+ TODO_ITEMS[2],
+ ]);
+ await checkTodosInLocalStorage(page, 'buy some sausages');
+ });
+
+ test('should trim entered text', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages ');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter');
+
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ 'buy some sausages',
+ TODO_ITEMS[2],
+ ]);
+ await checkTodosInLocalStorage(page, 'buy some sausages');
+ });
+
+ test('should remove the item if an empty text string was entered', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter');
+
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ TODO_ITEMS[2],
+ ]);
+ });
+
+ test('should cancel edits on escape', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape');
+ await expect(todoItems).toHaveText(TODO_ITEMS);
+ });
+});
+
+test.describe('Counter', () => {
+ test('should display the current number of todo items', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // create a todo count locator
+ const todoCount = page.getByTestId('todo-count')
+
+ await newTodo.fill(TODO_ITEMS[0]);
+ await newTodo.press('Enter');
+
+ await expect(todoCount).toContainText('1');
+
+ await newTodo.fill(TODO_ITEMS[1]);
+ await newTodo.press('Enter');
+ await expect(todoCount).toContainText('2');
+
+ await checkNumberOfTodosInLocalStorage(page, 2);
+ });
+});
+
+test.describe('Clear completed button', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ });
+
+ test('should display the correct text', async ({ page }) => {
+ await page.locator('.todo-list li .toggle').first().check();
+ await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible();
+ });
+
+ test('should remove completed items when clicked', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).getByRole('checkbox').check();
+ await page.getByRole('button', { name: 'Clear completed' }).click();
+ await expect(todoItems).toHaveCount(2);
+ await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
+ });
+
+ test('should be hidden when there are no items that are completed', async ({ page }) => {
+ await page.locator('.todo-list li .toggle').first().check();
+ await page.getByRole('button', { name: 'Clear completed' }).click();
+ await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden();
+ });
+});
+
+test.describe('Persistence', () => {
+ test('should persist its data', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ for (const item of TODO_ITEMS.slice(0, 2)) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+
+ const todoItems = page.getByTestId('todo-item');
+ const firstTodoCheck = todoItems.nth(0).getByRole('checkbox');
+ await firstTodoCheck.check();
+ await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
+ await expect(firstTodoCheck).toBeChecked();
+ await expect(todoItems).toHaveClass(['completed', '']);
+
+ // Ensure there is 1 completed item.
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+
+ // Now reload.
+ await page.reload();
+ await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
+ await expect(firstTodoCheck).toBeChecked();
+ await expect(todoItems).toHaveClass(['completed', '']);
+ });
+});
+
+test.describe('Routing', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ // make sure the app had a chance to save updated todos in storage
+ // before navigating to a new view, otherwise the items can get lost :(
+ // in some frameworks like Durandal
+ await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
+ });
+
+ test('should allow me to display active items', async ({ page }) => {
+ const todoItem = page.getByTestId('todo-item');
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+ await page.getByRole('link', { name: 'Active' }).click();
+ await expect(todoItem).toHaveCount(2);
+ await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
+ });
+
+ test('should respect the back button', async ({ page }) => {
+ const todoItem = page.getByTestId('todo-item');
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+
+ await test.step('Showing all items', async () => {
+ await page.getByRole('link', { name: 'All' }).click();
+ await expect(todoItem).toHaveCount(3);
+ });
+
+ await test.step('Showing active items', async () => {
+ await page.getByRole('link', { name: 'Active' }).click();
+ });
+
+ await test.step('Showing completed items', async () => {
+ await page.getByRole('link', { name: 'Completed' }).click();
+ });
+
+ await expect(todoItem).toHaveCount(1);
+ await page.goBack();
+ await expect(todoItem).toHaveCount(2);
+ await page.goBack();
+ await expect(todoItem).toHaveCount(3);
+ });
+
+ test('should allow me to display completed items', async ({ page }) => {
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+ await page.getByRole('link', { name: 'Completed' }).click();
+ await expect(page.getByTestId('todo-item')).toHaveCount(1);
+ });
+
+ test('should allow me to display all items', async ({ page }) => {
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+ await page.getByRole('link', { name: 'Active' }).click();
+ await page.getByRole('link', { name: 'Completed' }).click();
+ await page.getByRole('link', { name: 'All' }).click();
+ await expect(page.getByTestId('todo-item')).toHaveCount(3);
+ });
+
+ test('should highlight the currently applied filter', async ({ page }) => {
+ await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected');
+
+ //create locators for active and completed links
+ const activeLink = page.getByRole('link', { name: 'Active' });
+ const completedLink = page.getByRole('link', { name: 'Completed' });
+ await activeLink.click();
+
+ // Page change - active items.
+ await expect(activeLink).toHaveClass('selected');
+ await completedLink.click();
+
+ // Page change - completed items.
+ await expect(completedLink).toHaveClass('selected');
+ });
+});
+
+async function createDefaultTodos(page: Page) {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ for (const item of TODO_ITEMS) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+}
+
+async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
+ return await page.waitForFunction(e => {
+ return JSON.parse(localStorage['react-todos']).length === e;
+ }, expected);
+}
+
+async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {
+ return await page.waitForFunction(e => {
+ return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e;
+ }, expected);
+}
+
+async function checkTodosInLocalStorage(page: Page, title: string) {
+ return await page.waitForFunction(t => {
+ return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t);
+ }, title);
+}
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 04c5825..3d0a51a 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -14,9 +14,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "react-jsx",
- "types": ["node"]
+ "jsx": "react-jsx"
},
- "include": ["src", "e2e", "./**/*.ts"],
+ "include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}