mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-25 16:09:42 +01:00
web: add tests for keyboard list navigation
This commit is contained in:
233
apps/web/__e2e__/keyboard-list-navigation.test.ts
Normal file
233
apps/web/__e2e__/keyboard-list-navigation.test.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2022 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { expect, Page, test } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { NoteItemModel } from "./models/note-item.model";
|
||||
|
||||
async function populateList(page: Page, count = 5) {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const notesList: NoteItemModel[] = [];
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const note = await notes.createNote({
|
||||
title: `Test note ${i}`
|
||||
// content: `Test note ${i}`.repeat(10)
|
||||
});
|
||||
if (!note) continue;
|
||||
notesList.push(note);
|
||||
}
|
||||
return { notes, app, notesList: notesList.reverse() };
|
||||
}
|
||||
|
||||
test("ctrl+a should select all notes", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
|
||||
await notes.press("Control+a");
|
||||
|
||||
for (const note of notesList) {
|
||||
expect(await note.isSelected()).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test("pressing Escape should deselect all items", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
await notes.press("Control+a");
|
||||
|
||||
await notes.press("Escape");
|
||||
|
||||
let selected = 0;
|
||||
for (const note of notesList) {
|
||||
if (await note.isSelected()) ++selected;
|
||||
}
|
||||
expect(selected).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("pressing ArrowDown should focus next note", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
|
||||
await notes.press("ArrowDown");
|
||||
|
||||
expect(await notesList[1].isFocused()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("pressing ArrowUp should focus prev note", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
await notes.press("ArrowDown");
|
||||
await notes.press("ArrowDown");
|
||||
|
||||
await notes.press("ArrowUp");
|
||||
|
||||
expect(await notesList[1].isFocused()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("pressing Space should open focused note", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
await notes.press("ArrowDown");
|
||||
await notes.press("ArrowDown");
|
||||
|
||||
await notes.press("Space");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe(await notesList[2].getTitle());
|
||||
});
|
||||
|
||||
test("pressing Enter should open focused note", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
await notes.press("ArrowDown");
|
||||
await notes.press("ArrowDown");
|
||||
|
||||
await notes.press("Enter");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe(await notesList[2].getTitle());
|
||||
});
|
||||
|
||||
test("pressing Shift+ArrowDown should select next note", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
|
||||
await notes.press("Shift+ArrowDown");
|
||||
await notes.press("Shift+ArrowDown");
|
||||
|
||||
expect(await notesList[0].isSelected()).toBeTruthy();
|
||||
expect(await notesList[1].isSelected()).toBeTruthy();
|
||||
expect(await notesList[2].isSelected()).toBeTruthy();
|
||||
expect(await notesList[3].isSelected()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("pressing Shift+ArrowUp should select previous note", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page);
|
||||
await notes.focus();
|
||||
await notes.press("ArrowDown");
|
||||
await notes.press("ArrowDown");
|
||||
|
||||
await notes.press("Shift+ArrowUp");
|
||||
await notes.press("Shift+ArrowUp");
|
||||
await notes.press("Shift+ArrowUp");
|
||||
|
||||
expect(await notesList[0].isSelected()).toBeTruthy();
|
||||
expect(await notesList[1].isSelected()).toBeTruthy();
|
||||
expect(await notesList[2].isSelected()).toBeTruthy();
|
||||
expect(await notesList[3].isSelected()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("use Shift+ArrowUp & Shift+ArrowDown for note selection", async ({
|
||||
page
|
||||
}) => {
|
||||
const { notesList, notes } = await populateList(page, 10);
|
||||
await notes.focus();
|
||||
// move focus 5 items down
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
await notes.press("ArrowDown");
|
||||
}
|
||||
|
||||
// select 5 items in the upward direction
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
await notes.press("Shift+ArrowUp");
|
||||
}
|
||||
|
||||
// ensure the first 5 items are selected
|
||||
for (let i = 0; i <= 5; ++i) {
|
||||
expect(await notesList[i].isSelected()).toBeTruthy();
|
||||
}
|
||||
|
||||
// deselect 5 items downward
|
||||
for (let i = 0; i <= 5; ++i) {
|
||||
await notes.press("Shift+ArrowDown");
|
||||
}
|
||||
|
||||
// ensure the first 4 items are deselected (5th item is the anchor)
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
expect(await notesList[i].isSelected()).toBeFalsy();
|
||||
}
|
||||
|
||||
// select 5 items downward
|
||||
for (let i = 0; i <= 5; ++i) {
|
||||
await notes.press("Shift+ArrowDown");
|
||||
}
|
||||
|
||||
// ensure the last 5 items are selected
|
||||
for (let i = 5; i < 10; ++i) {
|
||||
expect(await notesList[i].isSelected()).toBeTruthy();
|
||||
}
|
||||
|
||||
// deselect the last 5 items upward
|
||||
for (let i = 0; i <= 5; ++i) {
|
||||
await notes.press("Shift+ArrowUp");
|
||||
}
|
||||
|
||||
// ensure the last 4 items were deselected (5th is anchor)
|
||||
for (let i = 6; i < 10; ++i) {
|
||||
expect(await notesList[i].isSelected()).toBeFalsy();
|
||||
}
|
||||
expect(await notesList[5].isSelected()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("select notes using Control+Click", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page, 10);
|
||||
await notes.focus();
|
||||
|
||||
await page.keyboard.down("Control");
|
||||
for (let i = 2; i < 10; i += 2) {
|
||||
await notesList[i].click();
|
||||
}
|
||||
await page.keyboard.up("Control");
|
||||
|
||||
for (let i = 2; i < 10; i += 2) {
|
||||
expect(await notesList[i].isSelected()).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test("select notes using Shift+Click downwards", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page, 10);
|
||||
await notes.focus();
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await notesList[5].click();
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
expect(await notesList[i].isSelected()).toBeTruthy();
|
||||
}
|
||||
expect(await notesList[5].isFocused()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("select notes using Shift+Click upwards", async ({ page }) => {
|
||||
const { notesList, notes } = await populateList(page, 10);
|
||||
await notes.focus();
|
||||
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
await notes.press("ArrowDown");
|
||||
}
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await notesList[0].click();
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
expect(await notesList[i].isSelected()).toBeTruthy();
|
||||
}
|
||||
expect(await notesList[0].isFocused()).toBeTruthy();
|
||||
});
|
||||
@@ -31,6 +31,19 @@ export class BaseItemModel {
|
||||
this.descriptionText = this.locator.locator(getTestId(`description`));
|
||||
}
|
||||
|
||||
async isSelected() {
|
||||
return (await this.locator.getAttribute("class"))?.includes("selected");
|
||||
}
|
||||
|
||||
async isFocused() {
|
||||
return await this.locator.evaluate((el) => el === document.activeElement);
|
||||
}
|
||||
|
||||
async click() {
|
||||
await this.locator.scrollIntoViewIfNeeded();
|
||||
await this.locator.click();
|
||||
}
|
||||
|
||||
async getId() {
|
||||
return await this.locator.getAttribute("id");
|
||||
}
|
||||
|
||||
@@ -72,4 +72,26 @@ export class BaseViewModel {
|
||||
await this.list.waitFor();
|
||||
}
|
||||
}
|
||||
|
||||
async focus() {
|
||||
const items = this.list.locator(
|
||||
`${getTestId(`virtuoso-item-list`)} >> ${getTestId("list-item")}`
|
||||
);
|
||||
await items.nth(0).click();
|
||||
await items.nth(0).click();
|
||||
}
|
||||
|
||||
// async selectAll() {
|
||||
// await this.press("Control+a");
|
||||
// }
|
||||
|
||||
// async selectNext() {
|
||||
// await this.press("ArrowDown");
|
||||
// }
|
||||
|
||||
async press(key: string) {
|
||||
const itemList = this.list.locator(getTestId(`virtuoso-item-list`));
|
||||
await itemList.press(key);
|
||||
await this.page.waitForTimeout(300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class NoteItemModel extends BaseItemModel {
|
||||
}
|
||||
|
||||
async openNote() {
|
||||
await this.locator.click();
|
||||
await this.click();
|
||||
const title = await this.getTitle();
|
||||
const description = await this.getDescription();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user