diff --git a/apps/web/__e2e__/colors.issues.test.js b/apps/web/__e2e__/colors.issues.test.js index e9f3f0ec1..cd85b1cd6 100644 --- a/apps/web/__e2e__/colors.issues.test.js +++ b/apps/web/__e2e__/colors.issues.test.js @@ -25,13 +25,12 @@ test("delete the last note of a color", async ({ page }) => { const noteSelector = await createNoteAndCheckPresence(); await useContextMenu(noteSelector, async () => { - await clickMenuItem("colors-Red"); + await clickMenuItem("colors"); + await clickMenuItem("red"); }); - expect(await page.isVisible(new Menu("navitem").item("red").build())).toBe( - true - ); - await page.waitForTimeout(500); + const navItem = new Menu("navitem").item("red").build(); + await page.waitForSelector(navItem); await useContextMenu(noteSelector, async () => { await clickMenuItem("movetotrash"); diff --git a/apps/web/__e2e__/editor.test.js b/apps/web/__e2e__/editor.test.js index f63e32ba9..4417f2315 100644 --- a/apps/web/__e2e__/editor.test.js +++ b/apps/web/__e2e__/editor.test.js @@ -207,7 +207,7 @@ test("select all & backspace should clear all content in editor", async () => { await expect(getEditorContent()).resolves.toBe(""); }); -test.only("last line doesn't get saved if it's font is different", async () => { +test("last line doesn't get saved if it's font is different", async () => { const selector = await createNoteAndCheckPresence(); await page.keyboard.press("Enter"); diff --git a/apps/web/__e2e__/notebooks.test.js b/apps/web/__e2e__/notebooks.test.js index 67bcdd888..d0ed3fcc8 100644 --- a/apps/web/__e2e__/notebooks.test.js +++ b/apps/web/__e2e__/notebooks.test.js @@ -10,7 +10,11 @@ const { } = require("./utils/actions"); const List = require("./utils/listitemidbuilder"); const Menu = require("./utils/menuitemidbuilder"); -const { checkNotePresence, isPresent } = require("./utils/conditions"); +const { + checkNotePresence, + isPresent, + checkMenuItemText, +} = require("./utils/conditions"); /** * @type {Page} @@ -203,7 +207,7 @@ test("permanently delete a notebook", async () => { ).resolves.toBeFalsy(); }); -test("pin a notebook", async () => { +test("pin a notebook", async ({ page }) => { const notebookSelector = await createNotebookAndCheckPresence(); await useContextMenu(notebookSelector, () => clickMenuItem("pin")); @@ -211,10 +215,9 @@ test("pin a notebook", async () => { // wait for the menu to properly close await page.waitForTimeout(500); - await useContextMenu(notebookSelector, () => - expect( - isPresent(Menu.new("menuitem").item("unpin").build()) - ).resolves.toBeTruthy() + await useContextMenu( + notebookSelector, + async () => await checkMenuItemText("pin", "Unpin") ); // wait for the menu to properly close diff --git a/apps/web/__e2e__/notes.test.js b/apps/web/__e2e__/notes.test.js index c64e13ac1..3655d76b5 100644 --- a/apps/web/__e2e__/notes.test.js +++ b/apps/web/__e2e__/notes.test.js @@ -30,6 +30,7 @@ const { isAbsent, checkNotePresence, createNoteAndCheckPresence, + checkMenuItemText, } = require("./utils/conditions"); const List = require("./utils/listitemidbuilder"); const Menu = require("./utils/menuitemidbuilder"); @@ -66,10 +67,10 @@ async function deleteNoteAndCheckAbsence(viewId = "home") { return trashItemSelector; } -async function lockUnlockNote(noteSelector, type) { +async function lockUnlockNote(noteSelector) { await openContextMenu(noteSelector); - await clickMenuItem(type); + await clickMenuItem("lock"); await page.fill(getTestId("dialog-password"), PASSWORD); @@ -93,9 +94,7 @@ async function openLockedNote(noteSelector) { async function checkNotePinned(noteSelector, pause) { await openContextMenu(noteSelector); - const unpinSelector = Menu.new("menuitem").item("unpin").build(); - - await expect(isPresent(unpinSelector)).resolves.toBeTruthy(); + await checkMenuItemText("pin", "Unpin"); await closeContextMenu(noteSelector); @@ -114,11 +113,11 @@ async function checkNoteLocked(noteSelector) { isAbsent(List.new("note").grouped().atIndex(0).body().build()) ).resolves.toBeTruthy(); - await useContextMenu(noteSelector, () => - expect( - isPresent(Menu.new("menuitem").item("unlock").build()) - ).resolves.toBeTruthy() - ); + await openContextMenu(noteSelector); + + await checkMenuItemText("lock", "Unlock"); + + await closeContextMenu(noteSelector); } async function checkNoteColored(noteSelector) { @@ -126,8 +125,10 @@ async function checkNoteColored(noteSelector) { await openContextMenu(noteSelector); + await page.click(Menu.new("menuitem").item("colors").build()); + await expect( - isPresent(Menu.new("menuitem").colorCheck("Red").build()) + isPresent(Menu.new("menuitem").item("red").checked().build()) ).resolves.toBeTruthy(); await closeContextMenu(noteSelector); @@ -188,10 +189,7 @@ async function exportNote(format) { Date.prototype.toLocaleString = () => "xxx"; }); - const output = await downloadFile( - getTestId(`export-dialog-${format}`), - "utf-8" - ); + const output = await downloadFile(getTestId(`menuitem-${format}`), "utf-8"); expect(output).toMatchSnapshot(`export-${format}.txt`); } @@ -230,7 +228,7 @@ test.describe("run tests independently", () => { await openContextMenu(noteSelector); - await clickMenuItem("addtonotebook(s)"); + await clickMenuItem("addtonotebook"); await addNoteToNotebook(); }); @@ -242,11 +240,13 @@ test.describe("run tests independently", () => { await clickMenuItem("favorite"); }); - await useContextMenu(noteSelector, async () => { - await expect( - isPresent(Menu.new("menuitem").item("unfavorite").build()) - ).resolves.toBeTruthy(); - }); + await useContextMenu( + noteSelector, + async () => { + await checkMenuItemText("favorite", "Unfavorite"); + }, + true + ); await navigateTo("favorites"); @@ -265,14 +265,16 @@ test.describe("run tests independently", () => { await page.waitForTimeout(500); await useContextMenu(noteSelector, async () => { - await clickMenuItem("unfavorite"); + await clickMenuItem("favorite"); }); - await useContextMenu(noteSelector, async () => { - await expect( - isPresent(Menu.new("menuitem").item("favorite").build()) - ).resolves.toBeTruthy(); - }); + await useContextMenu( + noteSelector, + async () => { + await checkMenuItemText("favorite", "Favorite"); + }, + true + ); }); test("favorite a note from properties", async () => { @@ -288,11 +290,13 @@ test.describe("run tests independently", () => { noteSelector = await checkNotePresence("notes"); - await useContextMenu(noteSelector, async () => { - await expect( - isPresent(Menu.new("menuitem").item("unfavorite").build()) - ).resolves.toBeTruthy(); - }); + await useContextMenu( + noteSelector, + async () => { + await checkMenuItemText("favorite", "Unfavorite"); + }, + true + ); await navigateTo("notes"); }); @@ -302,7 +306,8 @@ test.describe("run tests independently", () => { await openContextMenu(noteSelector); - await page.click(Menu.new("menuitem").color("Red").build()); + await page.click(Menu.new("menuitem").item("colors").build()); + await page.click(Menu.new("menuitem").item("red").build()); await page.click(getTestId("properties")); @@ -330,12 +335,12 @@ test.describe("run tests independently", () => { await page.waitForTimeout(500); - await useContextMenu(noteSelector, () => clickMenuItem("unpin")); + await useContextMenu(noteSelector, () => clickMenuItem("pin")); - await useContextMenu(noteSelector, () => - expect( - isPresent(Menu.new("menuitem").item("pin").build()) - ).resolves.toBeTruthy() + await useContextMenu( + noteSelector, + async () => await checkMenuItemText("pin", "Pin"), + true ); }); @@ -369,7 +374,7 @@ test.describe("run tests independently", () => { test("lock a note", async () => { const noteSelector = await createNoteAndCheckPresence(); - await lockUnlockNote(noteSelector, "lock"); + await lockUnlockNote(noteSelector); await checkNoteLocked(noteSelector); }); @@ -393,9 +398,7 @@ test.describe("run tests independently", () => { await openContextMenu(noteSelector); - await expect( - isPresent(Menu.new("menuitem").item("lock").build()) - ).resolves.toBeTruthy(); + await checkMenuItemText("lock", "Lock"); await closeContextMenu(noteSelector); }); diff --git a/apps/web/__e2e__/tags.test.js b/apps/web/__e2e__/tags.test.js index 4470df5d8..c61ba4ec2 100644 --- a/apps/web/__e2e__/tags.test.js +++ b/apps/web/__e2e__/tags.test.js @@ -67,7 +67,7 @@ async function createTagAndCheckPresence(title) { async function renameTag(tagSelector, newTitle, expected) { await useContextMenu(tagSelector.build(), async () => { - await clickMenuItem("renametag"); + await clickMenuItem("rename"); }); await page.fill(getTestId("item-dialog-title"), newTitle); @@ -167,7 +167,7 @@ test("create a shortcut of a tag", async ({ page }) => { const tagSelector = await createTagAndCheckPresence("helloworld"); await useContextMenu(tagSelector.build(), async () => { - await clickMenuItem("createshortcut"); + await clickMenuItem("shortcut"); }); expect( @@ -183,7 +183,7 @@ test("delete a shortcut of a tag", async ({ page }) => { const tagSelector = await createTagAndCheckPresence("helloworld"); await useContextMenu(tagSelector.build(), async () => { - await clickMenuItem("createshortcut"); + await clickMenuItem("shortcut"); }); expect( @@ -208,7 +208,7 @@ test("delete the last note of a tag that is also a shortcut", async ({ const tagSelector = await createTagAndCheckPresence("helloworld"); await useContextMenu(tagSelector.build(), async () => { - await clickMenuItem("createshortcut"); + await clickMenuItem("shortcut"); }); await page.click(tagSelector.build()); diff --git a/apps/web/__e2e__/utils/actions.js b/apps/web/__e2e__/utils/actions.js index 1bf9bb15e..f2c9509f1 100644 --- a/apps/web/__e2e__/utils/actions.js +++ b/apps/web/__e2e__/utils/actions.js @@ -18,15 +18,15 @@ async function openContextMenu(selector) { } async function closeContextMenu() { - await page.click("body", { button: "left" }); + await page.keyboard.press("Escape"); } -async function useContextMenu(selector, action) { +async function useContextMenu(selector, action, close = false) { await openContextMenu(selector); await action(); - await closeContextMenu(); + if (close) await closeContextMenu(); } async function confirmDialog() { diff --git a/apps/web/__e2e__/utils/conditions.js b/apps/web/__e2e__/utils/conditions.js index e60fed2ca..edfe4f09c 100644 --- a/apps/web/__e2e__/utils/conditions.js +++ b/apps/web/__e2e__/utils/conditions.js @@ -3,6 +3,7 @@ const { expect } = require("@playwright/test"); const { getTestId, NOTE, createNote } = require("."); const List = require("./listitemidbuilder"); +const Menu = require("./menuitemidbuilder"); async function isPresent(selector) { try { @@ -59,10 +60,17 @@ async function createNoteAndCheckPresence( return noteSelector; } +async function checkMenuItemText(itemId, expectedText) { + await expect( + page.textContent(Menu.new("menuitem").item(itemId).build()) + ).resolves.toBe(expectedText); +} + module.exports = { isPresent, isAbsent, isToastPresent, checkNotePresence, createNoteAndCheckPresence, + checkMenuItemText, }; diff --git a/apps/web/__e2e__/utils/index.js b/apps/web/__e2e__/utils/index.js index 3d1e2cc52..a11fe9e8d 100644 --- a/apps/web/__e2e__/utils/index.js +++ b/apps/web/__e2e__/utils/index.js @@ -71,7 +71,7 @@ async function downloadFile(downloadActionSelector, encoding) { return new Promise(async (resolve) => { page.on("download", async (download) => { const path = await download.path(); - resolve(fs.readFileSync(path, { encoding }).toString()); + resolve(fs.readFileSync(path, { encoding })); }); await page.waitForSelector(downloadActionSelector); diff --git a/apps/web/__e2e__/utils/menuitemidbuilder.js b/apps/web/__e2e__/utils/menuitemidbuilder.js index 3eb18a5f6..cfffdfa3e 100644 --- a/apps/web/__e2e__/utils/menuitemidbuilder.js +++ b/apps/web/__e2e__/utils/menuitemidbuilder.js @@ -6,6 +6,7 @@ class MenuItemIDBuilder { } constructor(type) { this.type = type; + this.suffix = ""; } item(itemId) { @@ -13,18 +14,15 @@ class MenuItemIDBuilder { return this; } - colorCheck(color) { - this.itemId = "colors-" + color + "-check"; - return this; - } - - color(color) { - this.itemId = "colors-" + color; + checked() { + this.suffix = "checked"; return this; } build() { - return getTestId(`${this.type}-${this.itemId}`); + return getTestId( + `${this.type}-${this.itemId}${this.suffix ? `-${this.suffix}` : ""}` + ); } } module.exports = MenuItemIDBuilder; diff --git a/apps/web/playwright.config.js b/apps/web/playwright.config.js index ef431785f..ef94b343f 100644 --- a/apps/web/playwright.config.js +++ b/apps/web/playwright.config.js @@ -42,9 +42,9 @@ module.exports = { timeout: 30000, workers: IS_CI ? 3 : 4, reporter: "list", - retries: IS_CI ? 3 : 1, + retries: IS_CI ? 3 : 0, use: { - headless: false, + headless: true, acceptDownloads: true, // Artifacts diff --git a/apps/web/src/common/multi-select.ts b/apps/web/src/common/multi-select.ts index cf3f05499..3eeac4655 100644 --- a/apps/web/src/common/multi-select.ts +++ b/apps/web/src/common/multi-select.ts @@ -1,20 +1,15 @@ -import { removeStatus, updateStatus } from "../hooks/use-status"; -import { - showMultiDeleteConfirmation, - showMultiPermanentDeleteConfirmation, -} from "./dialog-controller"; -import { store as editorStore } from "../stores/editor-store"; -import { store as appStore } from "../stores/app-store"; +import { showMultiDeleteConfirmation } from "./dialog-controller"; import { store as noteStore } from "../stores/note-store"; import { store as notebookStore } from "../stores/notebook-store"; import { db } from "./db"; -import { hashNavigate } from "../navigation"; import { showToast } from "../utils/toast"; import Vault from "./vault"; import { showItemDeletedToast } from "./toasts"; import { TaskManager } from "./task-manager"; async function moveNotesToTrash(notes: any[]) { + console.log(notes); + const item = notes[0]; const isMultiselect = notes.length > 1; if (isMultiselect) { diff --git a/apps/web/src/components/list-item/index.js b/apps/web/src/components/list-item/index.js index 491036a4b..fec0f597c 100644 --- a/apps/web/src/components/list-item/index.js +++ b/apps/web/src/components/list-item/index.js @@ -68,12 +68,12 @@ function ListItem(props) { let title = props.item.title; let selectedItems = selectionStore.get().selectedItems.slice(); - if (isSelected || isFocused) { + if (isSelected) { title = `${selectedItems.length} items selected`; items = items.filter((item) => item.multiSelect); } else if (Config.get("debugMode", false)) { items.push(...debugMenuItems(props.item.type)); - } else { + } else if (selectedItems.indexOf(props.item) === -1) { selectedItems.push(props.item); } diff --git a/apps/web/src/components/menu/index.js b/apps/web/src/components/menu/index.js index d26150369..062efe55a 100644 --- a/apps/web/src/components/menu/index.js +++ b/apps/web/src/components/menu/index.js @@ -131,7 +131,10 @@ function Menu({ items, data, title, closeMenu }) { key={item.key} index={index} item={item} - onClick={(e) => onAction(e, item)} + onClick={(e) => { + if (item.items?.length) setIsSubmenuOpen(true); + else onAction(e, item); + }} isFocused={focusIndex === index} onHover={() => { setFocusIndex(index); diff --git a/apps/web/src/components/menu/menu-item.js b/apps/web/src/components/menu/menu-item.js index d1572dc40..76b1b5a25 100644 --- a/apps/web/src/components/menu/menu-item.js +++ b/apps/web/src/components/menu/menu-item.js @@ -35,7 +35,7 @@ function MenuItem({ item, isFocused, onHover, onClick }) {