Passed
Pull Request — master (#353)
by
unknown
02:09 queued 21s
created

odosInLocalStorage   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 7
c 0
b 0
f 0
rs 10
cc 1
1
import { test, expect, type Page } from "@playwright/test";
2
3
test.beforeEach(async ({ page }) => {
4
  await page.goto("https://demo.playwright.dev/todomvc");
5
});
6
7
const TODO_ITEMS = ["buy some cheese", "feed the cat", "book a doctors appointment"];
8
9
test.describe("New Todo", () => {
10
  test("should allow me to add todo items", async ({ page }) => {
11
    // create a new todo locator
12
    const newTodo = page.getByPlaceholder("What needs to be done?");
13
14
    // Create 1st todo.
15
    await newTodo.fill(TODO_ITEMS[0]);
16
    await newTodo.press("Enter");
17
18
    // Make sure the list only has one todo item.
19
    await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]);
20
21
    // Create 2nd todo.
22
    await newTodo.fill(TODO_ITEMS[1]);
23
    await newTodo.press("Enter");
24
25
    // Make sure the list now has two todo items.
26
    await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
27
28
    await checkNumberOfTodosInLocalStorage(page, 2);
29
  });
30
31
  test("should clear text input field when an item is added", async ({ page }) => {
32
    // create a new todo locator
33
    const newTodo = page.getByPlaceholder("What needs to be done?");
34
35
    // Create one todo item.
36
    await newTodo.fill(TODO_ITEMS[0]);
37
    await newTodo.press("Enter");
38
39
    // Check that input is empty.
40
    await expect(newTodo).toBeEmpty();
41
    await checkNumberOfTodosInLocalStorage(page, 1);
42
  });
43
44
  test("should append new items to the bottom of the list", async ({ page }) => {
45
    // Create 3 items.
46
    await createDefaultTodos(page);
47
48
    // create a todo count locator
49
    const todoCount = page.getByTestId("todo-count");
50
51
    // Check test using different methods.
52
    await expect(page.getByText("3 items left")).toBeVisible();
53
    await expect(todoCount).toHaveText("3 items left");
54
    await expect(todoCount).toContainText("3");
55
    await expect(todoCount).toHaveText(/3/);
56
57
    // Check all items in one call.
58
    await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS);
59
    await checkNumberOfTodosInLocalStorage(page, 3);
60
  });
61
});
62
63
test.describe("Mark all as completed", () => {
64
  test.beforeEach(async ({ page }) => {
65
    await createDefaultTodos(page);
66
    await checkNumberOfTodosInLocalStorage(page, 3);
67
  });
68
69
  test.afterEach(async ({ page }) => {
70
    await checkNumberOfTodosInLocalStorage(page, 3);
71
  });
72
73
  test("should allow me to mark all items as completed", async ({ page }) => {
74
    // Complete all todos.
75
    await page.getByLabel("Mark all as complete").check();
76
77
    // Ensure all todos have 'completed' class.
78
    await expect(page.getByTestId("todo-item")).toHaveClass([
79
      "completed",
80
      "completed",
81
      "completed",
82
    ]);
83
    await checkNumberOfCompletedTodosInLocalStorage(page, 3);
84
  });
85
86
  test("should allow me to clear the complete state of all items", async ({ page }) => {
87
    const toggleAll = page.getByLabel("Mark all as complete");
88
    // Check and then immediately uncheck.
89
    await toggleAll.check();
90
    await toggleAll.uncheck();
91
92
    // Should be no completed classes.
93
    await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]);
94
  });
95
96
  test("complete all checkbox should update state when items are completed / cleared", async ({
97
    page,
98
  }) => {
99
    const toggleAll = page.getByLabel("Mark all as complete");
100
    await toggleAll.check();
101
    await expect(toggleAll).toBeChecked();
102
    await checkNumberOfCompletedTodosInLocalStorage(page, 3);
103
104
    // Uncheck first todo.
105
    const firstTodo = page.getByTestId("todo-item").nth(0);
106
    await firstTodo.getByRole("checkbox").uncheck();
107
108
    // Reuse toggleAll locator and make sure its not checked.
109
    await expect(toggleAll).not.toBeChecked();
110
111
    await firstTodo.getByRole("checkbox").check();
112
    await checkNumberOfCompletedTodosInLocalStorage(page, 3);
113
114
    // Assert the toggle all is checked again.
115
    await expect(toggleAll).toBeChecked();
116
  });
117
});
118
119
test.describe("Item", () => {
120
  test("should allow me to mark items as complete", async ({ page }) => {
121
    // create a new todo locator
122
    const newTodo = page.getByPlaceholder("What needs to be done?");
123
124
    // Create two items.
125
    for (const item of TODO_ITEMS.slice(0, 2)) {
126
      await newTodo.fill(item);
127
      await newTodo.press("Enter");
128
    }
129
130
    // Check first item.
131
    const firstTodo = page.getByTestId("todo-item").nth(0);
132
    await firstTodo.getByRole("checkbox").check();
133
    await expect(firstTodo).toHaveClass("completed");
134
135
    // Check second item.
136
    const secondTodo = page.getByTestId("todo-item").nth(1);
137
    await expect(secondTodo).not.toHaveClass("completed");
138
    await secondTodo.getByRole("checkbox").check();
139
140
    // Assert completed class.
141
    await expect(firstTodo).toHaveClass("completed");
142
    await expect(secondTodo).toHaveClass("completed");
143
  });
144
145
  test("should allow me to un-mark items as complete", async ({ page }) => {
146
    // create a new todo locator
147
    const newTodo = page.getByPlaceholder("What needs to be done?");
148
149
    // Create two items.
150
    for (const item of TODO_ITEMS.slice(0, 2)) {
151
      await newTodo.fill(item);
152
      await newTodo.press("Enter");
153
    }
154
155
    const firstTodo = page.getByTestId("todo-item").nth(0);
156
    const secondTodo = page.getByTestId("todo-item").nth(1);
157
    const firstTodoCheckbox = firstTodo.getByRole("checkbox");
158
159
    await firstTodoCheckbox.check();
160
    await expect(firstTodo).toHaveClass("completed");
161
    await expect(secondTodo).not.toHaveClass("completed");
162
    await checkNumberOfCompletedTodosInLocalStorage(page, 1);
163
164
    await firstTodoCheckbox.uncheck();
165
    await expect(firstTodo).not.toHaveClass("completed");
166
    await expect(secondTodo).not.toHaveClass("completed");
167
    await checkNumberOfCompletedTodosInLocalStorage(page, 0);
168
  });
169
170
  test("should allow me to edit an item", async ({ page }) => {
171
    await createDefaultTodos(page);
172
173
    const todoItems = page.getByTestId("todo-item");
174
    const secondTodo = todoItems.nth(1);
175
    await secondTodo.dblclick();
176
    await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue(TODO_ITEMS[1]);
177
    await secondTodo.getByRole("textbox", { name: "Edit" }).fill("buy some sausages");
178
    await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter");
179
180
    // Explicitly assert the new text value.
181
    await expect(todoItems).toHaveText([TODO_ITEMS[0], "buy some sausages", TODO_ITEMS[2]]);
182
    await checkTodosInLocalStorage(page, "buy some sausages");
183
  });
184
});
185
186
test.describe("Editing", () => {
187
  test.beforeEach(async ({ page }) => {
188
    await createDefaultTodos(page);
189
    await checkNumberOfTodosInLocalStorage(page, 3);
190
  });
191
192
  test("should hide other controls when editing", async ({ page }) => {
193
    const todoItem = page.getByTestId("todo-item").nth(1);
194
    await todoItem.dblclick();
195
    await expect(todoItem.getByRole("checkbox")).not.toBeVisible();
196
    await expect(
197
      todoItem.locator("label", {
198
        hasText: TODO_ITEMS[1],
199
      })
200
    ).not.toBeVisible();
201
    await checkNumberOfTodosInLocalStorage(page, 3);
202
  });
203
204
  test("should save edits on blur", async ({ page }) => {
205
    const todoItems = page.getByTestId("todo-item");
206
    await todoItems.nth(1).dblclick();
207
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("buy some sausages");
208
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).dispatchEvent("blur");
209
210
    await expect(todoItems).toHaveText([TODO_ITEMS[0], "buy some sausages", TODO_ITEMS[2]]);
211
    await checkTodosInLocalStorage(page, "buy some sausages");
212
  });
213
214
  test("should trim entered text", async ({ page }) => {
215
    const todoItems = page.getByTestId("todo-item");
216
    await todoItems.nth(1).dblclick();
217
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("    buy some sausages    ");
218
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).press("Enter");
219
220
    await expect(todoItems).toHaveText([TODO_ITEMS[0], "buy some sausages", TODO_ITEMS[2]]);
221
    await checkTodosInLocalStorage(page, "buy some sausages");
222
  });
223
224
  test("should remove the item if an empty text string was entered", async ({ page }) => {
225
    const todoItems = page.getByTestId("todo-item");
226
    await todoItems.nth(1).dblclick();
227
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("");
228
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).press("Enter");
229
230
    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
231
  });
232
233
  test("should cancel edits on escape", async ({ page }) => {
234
    const todoItems = page.getByTestId("todo-item");
235
    await todoItems.nth(1).dblclick();
236
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("buy some sausages");
237
    await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).press("Escape");
238
    await expect(todoItems).toHaveText(TODO_ITEMS);
239
  });
240
});
241
242
test.describe("Counter", () => {
243
  test("should display the current number of todo items", async ({ page }) => {
244
    // create a new todo locator
245
    const newTodo = page.getByPlaceholder("What needs to be done?");
246
247
    // create a todo count locator
248
    const todoCount = page.getByTestId("todo-count");
249
250
    await newTodo.fill(TODO_ITEMS[0]);
251
    await newTodo.press("Enter");
252
253
    await expect(todoCount).toContainText("1");
254
255
    await newTodo.fill(TODO_ITEMS[1]);
256
    await newTodo.press("Enter");
257
    await expect(todoCount).toContainText("2");
258
259
    await checkNumberOfTodosInLocalStorage(page, 2);
260
  });
261
});
262
263
test.describe("Clear completed button", () => {
264
  test.beforeEach(async ({ page }) => {
265
    await createDefaultTodos(page);
266
  });
267
268
  test("should display the correct text", async ({ page }) => {
269
    await page.locator(".todo-list li .toggle").first().check();
270
    await expect(page.getByRole("button", { name: "Clear completed" })).toBeVisible();
271
  });
272
273
  test("should remove completed items when clicked", async ({ page }) => {
274
    const todoItems = page.getByTestId("todo-item");
275
    await todoItems.nth(1).getByRole("checkbox").check();
276
    await page.getByRole("button", { name: "Clear completed" }).click();
277
    await expect(todoItems).toHaveCount(2);
278
    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
279
  });
280
281
  test("should be hidden when there are no items that are completed", async ({ page }) => {
282
    await page.locator(".todo-list li .toggle").first().check();
283
    await page.getByRole("button", { name: "Clear completed" }).click();
284
    await expect(page.getByRole("button", { name: "Clear completed" })).toBeHidden();
285
  });
286
});
287
288
test.describe("Persistence", () => {
289
  test("should persist its data", async ({ page }) => {
290
    // create a new todo locator
291
    const newTodo = page.getByPlaceholder("What needs to be done?");
292
293
    for (const item of TODO_ITEMS.slice(0, 2)) {
294
      await newTodo.fill(item);
295
      await newTodo.press("Enter");
296
    }
297
298
    const todoItems = page.getByTestId("todo-item");
299
    const firstTodoCheck = todoItems.nth(0).getByRole("checkbox");
300
    await firstTodoCheck.check();
301
    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
302
    await expect(firstTodoCheck).toBeChecked();
303
    await expect(todoItems).toHaveClass(["completed", ""]);
304
305
    // Ensure there is 1 completed item.
306
    await checkNumberOfCompletedTodosInLocalStorage(page, 1);
307
308
    // Now reload.
309
    await page.reload();
310
    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
311
    await expect(firstTodoCheck).toBeChecked();
312
    await expect(todoItems).toHaveClass(["completed", ""]);
313
  });
314
});
315
316
test.describe("Routing", () => {
317
  test.beforeEach(async ({ page }) => {
318
    await createDefaultTodos(page);
319
    // make sure the app had a chance to save updated todos in storage
320
    // before navigating to a new view, otherwise the items can get lost :(
321
    // in some frameworks like Durandal
322
    await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
323
  });
324
325
  test("should allow me to display active items", async ({ page }) => {
326
    const todoItem = page.getByTestId("todo-item");
327
    await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
328
329
    await checkNumberOfCompletedTodosInLocalStorage(page, 1);
330
    await page.getByRole("link", { name: "Active" }).click();
331
    await expect(todoItem).toHaveCount(2);
332
    await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
333
  });
334
335
  test("should respect the back button", async ({ page }) => {
336
    const todoItem = page.getByTestId("todo-item");
337
    await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
338
339
    await checkNumberOfCompletedTodosInLocalStorage(page, 1);
340
341
    await test.step("Showing all items", async () => {
342
      await page.getByRole("link", { name: "All" }).click();
343
      await expect(todoItem).toHaveCount(3);
344
    });
345
346
    await test.step("Showing active items", async () => {
347
      await page.getByRole("link", { name: "Active" }).click();
348
    });
349
350
    await test.step("Showing completed items", async () => {
351
      await page.getByRole("link", { name: "Completed" }).click();
352
    });
353
354
    await expect(todoItem).toHaveCount(1);
355
    await page.goBack();
356
    await expect(todoItem).toHaveCount(2);
357
    await page.goBack();
358
    await expect(todoItem).toHaveCount(3);
359
  });
360
361
  test("should allow me to display completed items", async ({ page }) => {
362
    await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
363
    await checkNumberOfCompletedTodosInLocalStorage(page, 1);
364
    await page.getByRole("link", { name: "Completed" }).click();
365
    await expect(page.getByTestId("todo-item")).toHaveCount(1);
366
  });
367
368
  test("should allow me to display all items", async ({ page }) => {
369
    await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
370
    await checkNumberOfCompletedTodosInLocalStorage(page, 1);
371
    await page.getByRole("link", { name: "Active" }).click();
372
    await page.getByRole("link", { name: "Completed" }).click();
373
    await page.getByRole("link", { name: "All" }).click();
374
    await expect(page.getByTestId("todo-item")).toHaveCount(3);
375
  });
376
377
  test("should highlight the currently applied filter", async ({ page }) => {
378
    await expect(page.getByRole("link", { name: "All" })).toHaveClass("selected");
379
380
    //create locators for active and completed links
381
    const activeLink = page.getByRole("link", { name: "Active" });
382
    const completedLink = page.getByRole("link", { name: "Completed" });
383
    await activeLink.click();
384
385
    // Page change - active items.
386
    await expect(activeLink).toHaveClass("selected");
387
    await completedLink.click();
388
389
    // Page change - completed items.
390
    await expect(completedLink).toHaveClass("selected");
391
  });
392
});
393
394
async function createDefaultTodos(page: Page) {
395
  // create a new todo locator
396
  const newTodo = page.getByPlaceholder("What needs to be done?");
397
398
  for (const item of TODO_ITEMS) {
399
    await newTodo.fill(item);
400
    await newTodo.press("Enter");
401
  }
402
}
403
404
async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
405
  return await page.waitForFunction((e) => {
406
    return JSON.parse(localStorage["react-todos"]).length === e;
407
  }, expected);
408
}
409
410
async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {
411
  return await page.waitForFunction((e) => {
412
    return (
413
      JSON.parse(localStorage["react-todos"]).filter((todo: any) => todo.completed).length === e
414
    );
415
  }, expected);
416
}
417
418
async function checkTodosInLocalStorage(page: Page, title: string) {
419
  return await page.waitForFunction((t) => {
420
    return JSON.parse(localStorage["react-todos"])
421
      .map((todo: any) => todo.title)
422
      .includes(t);
423
  }, title);
424
}
425