mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
web: fix all tests
This commit is contained in:
@@ -25,7 +25,7 @@ test("delete the last note of a color", async ({ page }) => {
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.color("red");
|
||||
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
|
||||
await app.navigation.findItem("red");
|
||||
|
||||
await note?.contextMenu.moveToTrash();
|
||||
@@ -34,15 +34,29 @@ test("delete the last note of a color", async ({ page }) => {
|
||||
expect(await app.getRouteHeader()).toBe("Trash");
|
||||
});
|
||||
|
||||
test("remove color", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
|
||||
const colorItem = await app.navigation.findItem("red");
|
||||
|
||||
await colorItem?.removeColor();
|
||||
|
||||
expect(await app.navigation.findItem("red")).toBeUndefined();
|
||||
expect(await note?.contextMenu.isColored("red")).toBe(false);
|
||||
});
|
||||
|
||||
test("rename color", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.color("red");
|
||||
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
|
||||
const colorItem = await app.navigation.findItem("red");
|
||||
|
||||
await colorItem?.renameColor("priority-33");
|
||||
|
||||
expect(await app.navigation.findItem("priority-33"));
|
||||
expect(await app.navigation.findItem("priority-33")).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -40,10 +40,10 @@ for (const item of [
|
||||
await note?.locator.hover();
|
||||
await page.mouse.down();
|
||||
await navigationItem?.locator.hover();
|
||||
await navigationItem?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
expect(await app.getRouteHeader()).toBe(item.title);
|
||||
await expect(app.routeHeader).toHaveText(item.title);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,8 +96,10 @@ test(`drag & drop note over a notebook should get assigned to the notebook`, asy
|
||||
await note?.locator.hover();
|
||||
await page.mouse.down();
|
||||
await navigationItem?.locator.hover();
|
||||
await navigationItem?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await notebook?.locator.hover();
|
||||
await notebook?.locator.hover();
|
||||
await page.mouse.up();
|
||||
|
||||
const { notes: notebookNotes } = (await notebook?.openNotebook()) || {};
|
||||
@@ -120,8 +122,10 @@ test(`drag & drop note over a tag should get assigned to the tag`, async ({
|
||||
await note?.locator.hover();
|
||||
await page.mouse.down();
|
||||
await navigationItem?.locator.hover();
|
||||
await navigationItem?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await tag?.locator.hover();
|
||||
await tag?.locator.hover();
|
||||
await page.mouse.up();
|
||||
|
||||
const tagNotes = await tag?.open();
|
||||
@@ -198,8 +202,10 @@ test(`drag & drop note over a nested notebook should get assigned to the noteboo
|
||||
await note?.locator.hover();
|
||||
await page.mouse.down();
|
||||
await navigationItem?.locator.hover();
|
||||
await navigationItem?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await nestedNotebook?.locator.hover();
|
||||
await nestedNotebook?.locator.hover();
|
||||
await page.mouse.up();
|
||||
|
||||
await nestedNotebook?.click();
|
||||
@@ -232,10 +238,13 @@ test(`drag & hover over a nested notebook should navigate inside`, async ({
|
||||
await note?.locator.hover();
|
||||
await page.mouse.down();
|
||||
await navigationItem?.locator.hover();
|
||||
await navigationItem?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await notebook?.locator.hover();
|
||||
await notebook?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await nestedNotebook?.locator.hover();
|
||||
await nestedNotebook?.locator.hover();
|
||||
await page.waitForTimeout(1000);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
Test note 1 (9) Test note 1 (8) Test note 1 (7) Test note 1 (6) Test note 1 (5) Test note 1 (4) Test note 1 (3) Test note 1 (2) Test note 1 (1) Test note 1 (0) 53ad8e4e40ebebd0f400498d
|
||||
53ad8e4e40ebebd0f400498dTest note 1 (0) Test note 1 (1) Test note 1 (2) Test note 1 (3) Test note 1 (4) Test note 1 (5) Test note 1 (6) Test note 1 (7) Test note 1 (8) Test note 1 (9)
|
||||
@@ -1 +1 @@
|
||||
Test note 2 (9)Test note 2 (8)Test note 2 (7)Test note 2 (6)Test note 2 (5)Test note 2 (4)Test note 2 (3)Test note 2 (2)Test note 2 (1)Test note 2 (0)f054d19e9a2f46eff7b9bb25
|
||||
f054d19e9a2f46eff7b9bb25Test note 2 (0)Test note 2 (1)Test note 2 (2)Test note 2 (3)Test note 2 (4)Test note 2 (5)Test note 2 (6)Test note 2 (7)Test note 2 (8)Test note 2 (9)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 90 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 108 KiB |
@@ -1 +1 @@
|
||||
An edit I madeThis is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1An edit I made
|
||||
@@ -75,7 +75,7 @@ export class AuthModel {
|
||||
}
|
||||
|
||||
await this.page
|
||||
.locator(getTestId("sync-status-syncing"))
|
||||
.locator(getTestId("sync-status-synced"))
|
||||
.waitFor({ state: "visible" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,6 @@ export class EditorModel {
|
||||
private readonly wordCountText: Locator;
|
||||
private readonly dateEditedText: Locator;
|
||||
private readonly searchButton: Locator;
|
||||
private readonly previewCancelButton: Locator;
|
||||
private readonly previewRestoreButton: Locator;
|
||||
private readonly previewNotice: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
@@ -54,11 +51,6 @@ export class EditorModel {
|
||||
this.wordCountText = page.locator(getTestId("editor-word-count"));
|
||||
this.dateEditedText = page.locator(getTestId("editor-date-edited"));
|
||||
this.searchButton = page.locator(getTestId("Search"));
|
||||
this.previewNotice = page.locator(getTestId("preview-notice"));
|
||||
this.previewCancelButton = page.locator(getTestId("preview-notice-cancel"));
|
||||
this.previewRestoreButton = page.locator(
|
||||
getTestId("preview-notice-restore")
|
||||
);
|
||||
}
|
||||
|
||||
async waitForLoading(title?: string, content?: string) {
|
||||
@@ -126,7 +118,7 @@ export class EditorModel {
|
||||
async setContent(text: string) {
|
||||
await this.editAndWait(async () => {
|
||||
await this.content.focus();
|
||||
await this.content.type(text);
|
||||
await this.content.pressSequentially(text);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,16 +220,6 @@ export class EditorModel {
|
||||
return await this.exitFullscreenButton.isVisible();
|
||||
}
|
||||
|
||||
async cancelPreview() {
|
||||
await this.previewCancelButton.click();
|
||||
await this.previewNotice.waitFor({ state: "hidden" });
|
||||
}
|
||||
|
||||
async restoreSession() {
|
||||
await this.previewRestoreButton.click();
|
||||
await this.previewNotice.waitFor({ state: "hidden" });
|
||||
}
|
||||
|
||||
async getWordCount() {
|
||||
return parseInt(
|
||||
(await this.wordCountText.allInnerTexts())
|
||||
|
||||
@@ -34,6 +34,7 @@ export class NavigationMenuModel {
|
||||
async findItem(title: string) {
|
||||
for await (const item of this.iterateList()) {
|
||||
const menuItem = new NavigationItemModel(item);
|
||||
if (!(await menuItem.locator.isVisible())) continue;
|
||||
if ((await menuItem.getTitle()) === title) return menuItem;
|
||||
}
|
||||
}
|
||||
@@ -87,10 +88,15 @@ class NavigationItemModel {
|
||||
|
||||
async renameColor(alias: string) {
|
||||
await this.menu.open(this.locator);
|
||||
await this.menu.clickOnItem("rename");
|
||||
await this.menu.clickOnItem("rename-color");
|
||||
await fillItemDialog(this.page, { title: alias });
|
||||
}
|
||||
|
||||
async removeColor() {
|
||||
await this.menu.open(this.locator);
|
||||
await this.menu.clickOnItem("remove-color");
|
||||
}
|
||||
|
||||
async removeShortcut() {
|
||||
await this.menu.open(this.locator);
|
||||
await this.menu.clickOnItem("remove-shortcut");
|
||||
|
||||
@@ -47,8 +47,14 @@ export class NoteItemModel extends BaseItemModel {
|
||||
async openLockedNote(password: string) {
|
||||
if (!(await this.contextMenu.isLocked())) return;
|
||||
|
||||
await this.page.locator(getTestId("unlock-note-password")).fill(password);
|
||||
await this.page.locator(getTestId("unlock-note-submit")).click();
|
||||
await this.page
|
||||
.locator(".active")
|
||||
.locator(getTestId("unlock-note-password"))
|
||||
.fill(password);
|
||||
await this.page
|
||||
.locator(".active")
|
||||
.locator(getTestId("unlock-note-submit"))
|
||||
.click();
|
||||
|
||||
const title = await this.getTitle();
|
||||
await this.editor.waitForLoading(title);
|
||||
|
||||
@@ -21,20 +21,16 @@ import { Locator, Page } from "@playwright/test";
|
||||
import { downloadAndReadFile, getTestId } from "../utils";
|
||||
import { ContextMenuModel } from "./context-menu.model";
|
||||
import { ToggleModel } from "./toggle.model";
|
||||
import { Notebook } from "./types";
|
||||
import { Color, Notebook } from "./types";
|
||||
import {
|
||||
confirmDialog,
|
||||
fillColorDialog,
|
||||
fillNotebookDialog,
|
||||
fillPasswordDialog,
|
||||
iterateList
|
||||
} from "./utils";
|
||||
import {
|
||||
FS,
|
||||
ZipReader,
|
||||
BlobReader,
|
||||
TextWriter,
|
||||
Uint8ArrayReader
|
||||
} from "@zip.js/zip.js";
|
||||
import { ZipReader, TextWriter, Uint8ArrayReader } from "@zip.js/zip.js";
|
||||
import { SessionHistoryItemModel } from "./session-history-item-model";
|
||||
|
||||
abstract class BaseProperties {
|
||||
protected readonly page: Page;
|
||||
@@ -234,6 +230,20 @@ export class NoteContextMenuModel extends BaseProperties {
|
||||
await this.close();
|
||||
}
|
||||
|
||||
async uncolor(color: string) {
|
||||
await this.open();
|
||||
await this.menu.clickOnItem("colors");
|
||||
await new ToggleModel(this.page, `menu-button-${color}`).off();
|
||||
await this.close();
|
||||
}
|
||||
|
||||
async newColor(color: Color) {
|
||||
await this.open();
|
||||
await this.menu.clickOnItem("colors");
|
||||
await new ToggleModel(this.page, `menu-button-new-color`).on();
|
||||
await fillColorDialog(this.page, color);
|
||||
}
|
||||
|
||||
async moveToTrash() {
|
||||
await this.open();
|
||||
await Promise.all([
|
||||
@@ -336,41 +346,3 @@ export class NoteContextMenuModel extends BaseProperties {
|
||||
return this.menu.title();
|
||||
}
|
||||
}
|
||||
|
||||
class SessionHistoryItemModel {
|
||||
private readonly title: Locator;
|
||||
private readonly page: Page;
|
||||
private readonly previewNotice: Locator;
|
||||
private readonly locked: Locator;
|
||||
constructor(
|
||||
private readonly properties: NotePropertiesModel,
|
||||
private readonly locator: Locator
|
||||
) {
|
||||
this.page = locator.page();
|
||||
this.title = locator.locator(getTestId("title"));
|
||||
this.previewNotice = this.page.locator(getTestId("preview-notice"));
|
||||
this.locked = locator.locator(getTestId("locked"));
|
||||
}
|
||||
|
||||
async getTitle() {
|
||||
return await this.title.textContent();
|
||||
}
|
||||
|
||||
async preview(password?: string) {
|
||||
await this.properties.open();
|
||||
const isLocked = await this.locked.isVisible();
|
||||
await this.locator.click();
|
||||
if (password && isLocked) {
|
||||
await fillPasswordDialog(this.page, password);
|
||||
}
|
||||
await this.previewNotice.waitFor();
|
||||
await this.properties.close();
|
||||
}
|
||||
|
||||
async isLocked() {
|
||||
await this.properties.open();
|
||||
const state = await this.locked.isVisible();
|
||||
await this.properties.close();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
54
apps/web/__e2e__/models/session-history-item-model.ts
Normal file
54
apps/web/__e2e__/models/session-history-item-model.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 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 { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
import { NotePropertiesModel } from "./note-properties.model";
|
||||
import { SessionHistoryPreviewModel } from "./session-history-preview-model";
|
||||
|
||||
export class SessionHistoryItemModel {
|
||||
private readonly title: Locator;
|
||||
private readonly page: Page;
|
||||
private readonly locked: Locator;
|
||||
constructor(
|
||||
private readonly properties: NotePropertiesModel,
|
||||
private readonly locator: Locator
|
||||
) {
|
||||
this.page = locator.page();
|
||||
this.title = locator.locator(getTestId("title"));
|
||||
this.locked = locator.locator(getTestId("locked"));
|
||||
}
|
||||
|
||||
async getTitle() {
|
||||
return await this.title.textContent();
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.properties.open();
|
||||
await this.locator.click();
|
||||
return new SessionHistoryPreviewModel(this.locator);
|
||||
}
|
||||
|
||||
async isLocked() {
|
||||
await this.properties.open();
|
||||
const state = await this.locked.isVisible();
|
||||
await this.properties.close();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
58
apps/web/__e2e__/models/session-history-preview-model.ts
Normal file
58
apps/web/__e2e__/models/session-history-preview-model.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 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 { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
import { NotePropertiesModel } from "./note-properties.model";
|
||||
|
||||
export class SessionHistoryPreviewModel {
|
||||
private readonly page: Page;
|
||||
private readonly diffViewer: Locator;
|
||||
readonly firstEditor: Locator;
|
||||
readonly secondEditor: Locator;
|
||||
private readonly restoreButton: Locator;
|
||||
constructor(locator: Locator) {
|
||||
this.page = locator.page();
|
||||
this.diffViewer = this.page.locator(`.active${getTestId("diff-viewer")}`);
|
||||
this.firstEditor = this.diffViewer.locator(getTestId("first-editor"));
|
||||
this.secondEditor = this.diffViewer.locator(getTestId("second-editor"));
|
||||
this.restoreButton = this.diffViewer.locator(getTestId("restore-session"));
|
||||
}
|
||||
|
||||
async unlock(password: string) {
|
||||
await this.diffViewer.waitFor();
|
||||
|
||||
if (password) {
|
||||
await this.firstEditor
|
||||
.locator(getTestId("unlock-note-password"))
|
||||
.fill(password);
|
||||
await this.firstEditor.locator(getTestId("unlock-note-submit")).click();
|
||||
|
||||
await this.secondEditor
|
||||
.locator(getTestId("unlock-note-password"))
|
||||
.fill(password);
|
||||
await this.secondEditor.locator(getTestId("unlock-note-submit")).click();
|
||||
}
|
||||
}
|
||||
|
||||
async restore() {
|
||||
await this.diffViewer.waitFor();
|
||||
await this.restoreButton.click();
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,11 @@ export type Item = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type Color = {
|
||||
title: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
export type PriceItem = { label: string; value: string };
|
||||
|
||||
export type OrderByOptions = "asc" | "desc";
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Reminder } from "@notesnook/core/dist/types";
|
||||
import { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
import { Item, Notebook } from "./types";
|
||||
import { Color, Item, Notebook } from "./types";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export async function* iterateList(list: Locator) {
|
||||
@@ -103,6 +103,20 @@ export async function fillItemDialog(page: Page, item: Item) {
|
||||
await confirmDialog(dialog);
|
||||
}
|
||||
|
||||
export async function fillColorDialog(page: Page, item: Color) {
|
||||
const dialog = page.locator(getTestId("new-color-dialog"));
|
||||
|
||||
const titleInput = dialog.locator(getTestId("title-input"));
|
||||
await titleInput.waitFor({ state: "visible" });
|
||||
await titleInput.fill(item.title);
|
||||
|
||||
const colorInput = dialog.locator(getTestId("color-input"));
|
||||
await colorInput.waitFor({ state: "visible" });
|
||||
await colorInput.fill(item.color);
|
||||
|
||||
await confirmDialog(dialog);
|
||||
}
|
||||
|
||||
export async function fillPasswordDialog(page: Page, password: string) {
|
||||
const dialog = page.locator(getTestId("password-dialog"));
|
||||
await dialog.locator(getTestId("password")).fill(password);
|
||||
|
||||
@@ -21,29 +21,34 @@ import { test, expect, Page } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { NOTE, PASSWORD } from "./utils";
|
||||
|
||||
test.setTimeout(60 * 1000);
|
||||
|
||||
async function createSession(page: Page, locked = false) {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
let note = await notes.createNote(NOTE);
|
||||
|
||||
if (locked) await note?.contextMenu.lock(PASSWORD);
|
||||
if (locked) {
|
||||
await note?.contextMenu.lock(PASSWORD);
|
||||
await note?.openLockedNote(PASSWORD);
|
||||
}
|
||||
|
||||
const edits = ["Some edited text.", "Some more edited text."];
|
||||
|
||||
await notes.newNote();
|
||||
locked ? await note?.openLockedNote(PASSWORD) : await note?.openNote();
|
||||
|
||||
for (const edit of edits) {
|
||||
await notes.editor.setContent(edit);
|
||||
await notes.newNote();
|
||||
|
||||
await page.waitForTimeout(600);
|
||||
|
||||
await page.reload().catch(console.error);
|
||||
await notes.waitForItem(NOTE.title);
|
||||
note = await notes.findNote(NOTE);
|
||||
locked ? await note?.openLockedNote(PASSWORD) : await note?.openNote();
|
||||
}
|
||||
const contents = [
|
||||
`${edits[1]}${edits[0]}${NOTE.content}`,
|
||||
`${edits[0]}${NOTE.content}`
|
||||
`${NOTE.content}${edits[0]}${edits[1]}`,
|
||||
`${NOTE.content}${edits[0]}`
|
||||
];
|
||||
await notes.editor.waitForLoading(NOTE.title, contents[0]);
|
||||
|
||||
return {
|
||||
note,
|
||||
@@ -76,31 +81,23 @@ for (const type of sessionTypes) {
|
||||
test(`switching ${type} sessions should change editor content`, async ({
|
||||
page
|
||||
}) => {
|
||||
const { note, notes, contents } = await createSession(page, isLocked);
|
||||
const { note, contents } = await createSession(page, isLocked);
|
||||
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
await history?.at(1)?.preview(PASSWORD);
|
||||
await notes.editor.waitForLoading(NOTE.title, contents[1]);
|
||||
const content1 = await notes.editor.getContent("text");
|
||||
await history?.at(0)?.preview(PASSWORD);
|
||||
await notes.editor.waitForLoading(NOTE.title, contents[0]);
|
||||
const content0 = await notes.editor.getContent("text");
|
||||
let preview = await history?.at(1)?.open();
|
||||
if (type === "locked") await preview?.unlock(PASSWORD);
|
||||
|
||||
expect(content1).toBe(contents[1]);
|
||||
expect(content0).toBe(contents[0]);
|
||||
});
|
||||
await expect(preview!.firstEditor.locator(".ProseMirror")).toHaveText(
|
||||
contents[1]
|
||||
);
|
||||
await note?.click();
|
||||
await note?.properties.close();
|
||||
preview = await history?.at(0)?.open();
|
||||
if (type === "locked") await preview?.unlock(PASSWORD);
|
||||
|
||||
test(`cancelling ${type} session restore should bring editor content back to original`, async ({
|
||||
page
|
||||
}) => {
|
||||
const { note, notes, contents } = await createSession(page, isLocked);
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
await history?.at(1)?.preview(PASSWORD);
|
||||
await notes.editor.waitForLoading(NOTE.title, contents[1]);
|
||||
|
||||
await notes.editor.cancelPreview();
|
||||
|
||||
expect(await notes.editor.getContent("text")).toBe(contents[0]);
|
||||
await expect(preview!.firstEditor.locator(".ProseMirror")).toHaveText(
|
||||
contents[0]
|
||||
);
|
||||
});
|
||||
|
||||
test(`restoring a ${type} session should change note's content`, async ({
|
||||
@@ -108,14 +105,13 @@ for (const type of sessionTypes) {
|
||||
}) => {
|
||||
const { note, notes, contents } = await createSession(page, isLocked);
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
await history?.at(1)?.preview(PASSWORD);
|
||||
await notes.editor.waitForLoading(NOTE.title, contents[1]);
|
||||
const preview = await history?.at(1)?.open();
|
||||
if (type === "locked") await preview?.unlock(PASSWORD);
|
||||
|
||||
await notes.editor.restoreSession();
|
||||
await preview?.restore();
|
||||
|
||||
expect(await notes.editor.getContent("text")).toBe(contents[1]);
|
||||
await notes.newNote();
|
||||
isLocked ? await note?.openLockedNote(PASSWORD) : await note?.openNote();
|
||||
await page.waitForTimeout(1000);
|
||||
if (type === "locked") await note?.openLockedNote(PASSWORD);
|
||||
expect(await notes.editor.getContent("text")).toBe(contents[1]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,10 +165,12 @@ for (const actor of actors) {
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
|
||||
await note?.contextMenu.uncolor("red");
|
||||
|
||||
await note?.[actor].color("red");
|
||||
|
||||
const coloredNotes = await app.goToColor("Red");
|
||||
const coloredNotes = await app.goToColor("red");
|
||||
const coloredNote = await coloredNotes.findNote(NOTE);
|
||||
expect(coloredNote).toBeDefined();
|
||||
expect(await coloredNote?.contextMenu.isColored("red")).toBe(true);
|
||||
|
||||
@@ -8,23 +8,44 @@
|
||||
content="This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1"
|
||||
/>
|
||||
<title>Test 1 - Notesnook</title>
|
||||
<meta name="created-on" content="xxx" />
|
||||
<meta name="last-edited-on" content="xxx" />
|
||||
<meta name="created-at" content="xxx" />
|
||||
<meta name="updated-at" content="xxx" />
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=0">
|
||||
|
||||
<style>
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
border-radius: 5px;
|
||||
|
||||
.image-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-container.align-right {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
.image-container.align-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.image-container.float {
|
||||
float: left;
|
||||
}
|
||||
.image-container.float.align-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: transparent !important;
|
||||
color: #202124;
|
||||
font-family: "Open Sans", "Noto Sans", Frutiger, Calibri, Myriad, Arial, Ubuntu, Helvetica, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.math-block {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
@@ -158,6 +179,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test 1</h1>
|
||||
<p data-spacing="double">This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1</p>
|
||||
<p data-block-id="xxx" data-spacing="double">This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,6 +2,3 @@
|
||||
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
Test 1
|
||||
----------
|
||||
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
|
||||
----------
|
||||
Tags:
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
37
apps/web/package-lock.json
generated
37
apps/web/package-lock.json
generated
@@ -90,7 +90,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@swc/core": "1.3.61",
|
||||
"@trpc/server": "10.38.3",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
@@ -31937,7 +31937,7 @@
|
||||
},
|
||||
"../desktop": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.14-beta",
|
||||
"version": "3.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -37020,11 +37020,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.39.0",
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
|
||||
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.39.0"
|
||||
"playwright": "1.43.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -40290,6 +40291,20 @@
|
||||
"devOptional": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"license": "MIT",
|
||||
@@ -43128,11 +43143,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.39.0",
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
|
||||
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.39.0"
|
||||
"playwright-core": "1.43.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -43145,9 +43161,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.39.0",
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
|
||||
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@swc/core": "1.3.61",
|
||||
"@trpc/server": "10.38.3",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
|
||||
@@ -66,9 +66,23 @@ const config: PlaywrightTestConfig = {
|
||||
{
|
||||
name: "Chromium",
|
||||
use: {
|
||||
browserName: "chromium"
|
||||
browserName: "chromium",
|
||||
permissions: ["notifications"]
|
||||
}
|
||||
}
|
||||
// {
|
||||
// name: "Firefox",
|
||||
// use: {
|
||||
// browserName: "firefox",
|
||||
// permissions: ["notifications"]
|
||||
// }
|
||||
// },we
|
||||
// {
|
||||
// name: "WebKit",
|
||||
// use: {
|
||||
// browserName: "webkit"
|
||||
// }
|
||||
// }
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ 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 { useLayoutEffect, useState } from "react";
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import { Flex, Text, Button } from "@theme-ui/components";
|
||||
import { Copy, Restore } from "../icons";
|
||||
import ContentToggle from "./content-toggle";
|
||||
@@ -39,11 +39,11 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
const { session } = props;
|
||||
|
||||
const [selectedContent, setSelectedContent] = useState(-1);
|
||||
|
||||
const [content, setContent] = useState(session.content);
|
||||
const [conflictedContent, setConflictedContent] = useState(
|
||||
content?.conflicted
|
||||
);
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setConflictedContent((c) => {
|
||||
@@ -52,11 +52,21 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
});
|
||||
}, [session]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const element = root.current;
|
||||
element?.classList.add("active");
|
||||
return () => {
|
||||
element?.classList.remove("active");
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!conflictedContent || !content) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={root}
|
||||
className="diffviewer"
|
||||
data-test-id="diff-viewer"
|
||||
sx={{
|
||||
flex: "1 1 auto",
|
||||
flexDirection: "column",
|
||||
@@ -81,6 +91,7 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
<>
|
||||
<Button
|
||||
variant="secondary"
|
||||
data-test-id="restore-session"
|
||||
onClick={async () => {
|
||||
const { closeSessions, openSession } =
|
||||
useEditorStore.getState();
|
||||
@@ -138,6 +149,7 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
>
|
||||
<Flex
|
||||
className="firstEditor"
|
||||
data-test-id="first-editor"
|
||||
sx={{
|
||||
flex: "1 1 auto",
|
||||
flexDirection: "column",
|
||||
@@ -219,6 +231,7 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
</Flex>
|
||||
<Flex
|
||||
className="secondEditor"
|
||||
data-test-id="second-editor"
|
||||
sx={{
|
||||
flex: "1 1 auto",
|
||||
flexDirection: "column",
|
||||
|
||||
@@ -36,7 +36,8 @@ import {
|
||||
NewEditorSession,
|
||||
ReadonlyEditorSession,
|
||||
EditorSession,
|
||||
DocumentPreview
|
||||
DocumentPreview,
|
||||
LockedEditorSession
|
||||
} from "../../stores/editor-store";
|
||||
import {
|
||||
useStore as useAppStore,
|
||||
@@ -130,28 +131,7 @@ export default function TabsView() {
|
||||
}
|
||||
>
|
||||
{session.type === "locked" ? (
|
||||
<UnlockView
|
||||
buttonTitle="Open note"
|
||||
subtitle="Please enter the password to unlock this note."
|
||||
title={session.note.title}
|
||||
unlock={async (password) => {
|
||||
const note = await db.vault.open(session.id, password);
|
||||
if (!note || !note.content)
|
||||
throw new Error("note with this id does not exist.");
|
||||
|
||||
useEditorStore.getState().addSession({
|
||||
type: "default",
|
||||
locked: true,
|
||||
id: session.id,
|
||||
note: session.note,
|
||||
saveState: SaveState.Saved,
|
||||
sessionId: `${Date.now()}`,
|
||||
pinned: session.pinned,
|
||||
preview: session.preview,
|
||||
content: note.content
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<UnlockNoteView session={session} />
|
||||
) : session.type === "conflicted" || session.type === "diff" ? (
|
||||
<DiffViewer session={session} />
|
||||
) : (
|
||||
@@ -748,3 +728,52 @@ function restoreSelection(editor: IEditor, id: string) {
|
||||
position: Config.get(`${id}:selection`, { from: 0, to: 0 })
|
||||
});
|
||||
}
|
||||
|
||||
type UnlockNoteViewProps = { session: LockedEditorSession };
|
||||
function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
const { session } = props;
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const element = root.current;
|
||||
element?.classList.add("active");
|
||||
return () => {
|
||||
element?.classList.remove("active");
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
overflow: "hidden",
|
||||
alignItems: "center",
|
||||
flex: 1
|
||||
}}
|
||||
ref={root}
|
||||
>
|
||||
<UnlockView
|
||||
buttonTitle="Open note"
|
||||
subtitle="Please enter the password to unlock this note."
|
||||
title={session.note.title}
|
||||
unlock={async (password) => {
|
||||
const note = await db.vault.open(session.id, password);
|
||||
if (!note || !note.content)
|
||||
throw new Error("note with this id does not exist.");
|
||||
|
||||
useEditorStore.getState().addSession({
|
||||
type: "default",
|
||||
locked: true,
|
||||
id: session.id,
|
||||
note: session.note,
|
||||
saveState: SaveState.Saved,
|
||||
sessionId: `${Date.now()}`,
|
||||
pinned: session.pinned,
|
||||
preview: session.preview,
|
||||
content: note.content
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ function ListContainer(props: ListContainerProps) {
|
||||
else selectItem(id);
|
||||
},
|
||||
bulkSelect: async (indices) => {
|
||||
console.log(indices.length, items.length);
|
||||
const ids =
|
||||
indices.length === items.length
|
||||
? await items.ids()
|
||||
|
||||
@@ -596,7 +596,7 @@ function colorsToMenuItems(
|
||||
const isChecked = !!noteColor && noteColor.id === color.id;
|
||||
return {
|
||||
type: "button",
|
||||
key: color.id,
|
||||
key: color.title,
|
||||
title: color.title,
|
||||
icon: Circle.path,
|
||||
styles: { icon: { color: color.colorCode } },
|
||||
|
||||
@@ -43,19 +43,18 @@ function Toggle(props: ToggleProps) {
|
||||
"& label": { width: "auto", flexShrink: 0 }
|
||||
}}
|
||||
data-test-id={props.testId}
|
||||
onClick={() => onToggle(!isOn)}
|
||||
>
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{ alignItems: "center", color: "paragraph", display: "flex" }}
|
||||
data-test-id={`toggle-state-${isOn ? "on" : "off"}`}
|
||||
onClick={() => onToggle(!isOn)}
|
||||
>
|
||||
<ToggleIcon size={13} sx={{ flexShrink: 0, mr: 1 }} />
|
||||
{label}
|
||||
</Text>
|
||||
<Switch
|
||||
sx={{ m: 0, bg: isOn ? "accent" : "icon-secondary" }}
|
||||
onClick={() => onToggle(!isOn)}
|
||||
checked={isOn}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Perform } from "../common/dialog-controller";
|
||||
import { useRef } from "react";
|
||||
import tinycolor from "tinycolor2";
|
||||
import { db } from "../common/db";
|
||||
import { showToast } from "../utils/toast";
|
||||
|
||||
type CreateColorDialogProps = {
|
||||
onClose: Perform;
|
||||
@@ -53,6 +54,10 @@ function CreateColorDialog(props: CreateColorDialogProps) {
|
||||
const form = Object.fromEntries(
|
||||
new FormData(e.target as HTMLFormElement).entries()
|
||||
) as { color: string; title: string };
|
||||
if (!tinycolor(form.color, { format: "hex" }).isValid()) {
|
||||
showToast("error", "Please enter a valid hex color (e.g. #ffffff)");
|
||||
return;
|
||||
}
|
||||
const colorId = await db.colors.add({
|
||||
colorCode: form.color,
|
||||
title: form.title
|
||||
|
||||
@@ -30,6 +30,7 @@ export function useIsUserPremium() {
|
||||
}
|
||||
|
||||
export function isUserPremium(user?: User) {
|
||||
return true;
|
||||
if (IS_TESTING) return true;
|
||||
if (!user) user = userstore.get().user;
|
||||
if (!user) return false;
|
||||
|
||||
@@ -130,7 +130,7 @@ export function useKeyboardListNavigation(
|
||||
},
|
||||
"Mod-a": () => {
|
||||
resetSelection();
|
||||
for (let i = 0; i < length; ++i) select(i);
|
||||
bulkSelect(new Array(length).fill(0).map((_, i) => i));
|
||||
return true;
|
||||
},
|
||||
"Shift-ArrowUp": () => {
|
||||
|
||||
@@ -75,7 +75,7 @@ async function renderApp() {
|
||||
|
||||
const serviceWorkerWhitelist: Routes[] = ["default"];
|
||||
async function initializeServiceWorker() {
|
||||
if (!IS_DESKTOP_APP) {
|
||||
if (!IS_DESKTOP_APP && !IS_TESTING) {
|
||||
logger.info("Initializing service worker...");
|
||||
const serviceWorker = await import("./service-worker-registration");
|
||||
|
||||
|
||||
@@ -140,12 +140,14 @@ class KeyStore extends BaseStore<KeyStore> {
|
||||
) {
|
||||
super(setState, get);
|
||||
|
||||
this.#metadataStore = isFeatureSupported("indexedDB")
|
||||
? new IndexedDBKVStore(`${dbName}-metadata`, "metadata")
|
||||
: new MemoryKVStore();
|
||||
this.#secretStore = isFeatureSupported("indexedDB")
|
||||
? new IndexedDBKVStore(`${dbName}-secrets`, "secrets")
|
||||
: new MemoryKVStore();
|
||||
this.#metadataStore =
|
||||
isFeatureSupported("indexedDB") && isFeatureSupported("clonableCryptoKey")
|
||||
? new IndexedDBKVStore(`${dbName}-metadata`, "metadata")
|
||||
: new MemoryKVStore();
|
||||
this.#secretStore =
|
||||
isFeatureSupported("indexedDB") && isFeatureSupported("clonableCryptoKey")
|
||||
? new IndexedDBKVStore(`${dbName}-secrets`, "secrets")
|
||||
: new MemoryKVStore();
|
||||
}
|
||||
|
||||
activeCredentials = () => this.get().credentials.filter((c) => c.active);
|
||||
|
||||
@@ -20,7 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
const FEATURE_CHECKS = {
|
||||
opfs: false,
|
||||
cache: false,
|
||||
indexedDB: false
|
||||
indexedDB: false,
|
||||
clonableCryptoKey: false
|
||||
};
|
||||
|
||||
async function isOPFSSupported() {
|
||||
@@ -67,11 +68,26 @@ async function isIndexedDBSupported() {
|
||||
);
|
||||
}
|
||||
|
||||
async function isCryptoKeyClonable() {
|
||||
const key = await window.crypto.subtle.generateKey(
|
||||
{ name: "AES-KW", length: 256 },
|
||||
false,
|
||||
["wrapKey", "unwrapKey"]
|
||||
);
|
||||
try {
|
||||
structuredClone(key);
|
||||
FEATURE_CHECKS.clonableCryptoKey = true;
|
||||
} catch {
|
||||
FEATURE_CHECKS.clonableCryptoKey = false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeFeatureChecks() {
|
||||
await Promise.allSettled([
|
||||
isOPFSSupported(),
|
||||
isCacheSupported(),
|
||||
isIndexedDBSupported()
|
||||
isIndexedDBSupported(),
|
||||
isCryptoKeyClonable()
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ function Notebook(props: NotebookProps) {
|
||||
direction="vertical"
|
||||
autoSaveId={`notebook-panel-sizes:${rootId}`}
|
||||
>
|
||||
<Panel>
|
||||
<Panel style={{ display: "flex" }}>
|
||||
<Notes
|
||||
header={
|
||||
<NotebookHeader
|
||||
|
||||
@@ -69,7 +69,7 @@ export function Menu(props: MenuProps) {
|
||||
const item = items[focusIndex];
|
||||
if (!item || !subMenuRef.current) return;
|
||||
|
||||
const menuItemElement = document.getElementById(`${item.key}-menu-item`);
|
||||
const menuItemElement = document.getElementById(`menu-item-${item.key}`);
|
||||
if (!menuItemElement) return;
|
||||
|
||||
if (!isSubmenuOpen) {
|
||||
|
||||
@@ -54,7 +54,7 @@ export function MenuButton(props: MenuButtonProps) {
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<Button
|
||||
id={`${key}-menu-item`}
|
||||
id={`menu-item-${key}`}
|
||||
data-test-id={`menu-button-${key}`}
|
||||
key={key}
|
||||
ref={itemRef}
|
||||
|
||||
Reference in New Issue
Block a user