/*
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 .
*/
import { Locator, Page } from "@playwright/test";
import { downloadAndReadFile, getTestId } from "../utils";
import { ContextMenuModel } from "./context-menu.model";
import { ToggleModel } from "./toggle.model";
import { Color, Notebook } from "./types";
import {
confirmDialog,
fillColorDialog,
fillNotebookDialog,
fillPasswordDialog,
iterateList
} from "./utils";
import { SessionHistoryItemModel } from "./session-history-item-model";
import dayjs from "dayjs";
abstract class BaseProperties {
protected readonly page: Page;
private readonly pinToggle: ToggleModel;
private readonly favoriteToggle: ToggleModel;
private readonly lockToggle: ToggleModel;
private readonly archiveToggle: ToggleModel;
constructor(
page: Page,
protected readonly noteLocator: Locator,
private readonly itemPrefix: string
) {
this.page = page;
this.pinToggle = new ToggleModel(page, `${itemPrefix}-pin`);
this.lockToggle = new ToggleModel(page, `${itemPrefix}-lock`);
this.favoriteToggle = new ToggleModel(page, `${itemPrefix}-favorite`);
this.archiveToggle = new ToggleModel(page, `${itemPrefix}-archive`);
}
async isPinned() {
await this.open();
const state = await this.pinToggle.isToggled();
await this.close();
return state;
}
async pin() {
await this.open();
await this.pinToggle.on();
await this.close();
}
async unpin() {
await this.open();
await this.pinToggle.off();
await this.close();
}
async lock(password: string) {
await this.open();
await this.lockToggle.on(false);
await fillPasswordDialog(this.page, password);
await this.noteLocator
.locator(getTestId("locked"))
.waitFor({ state: "visible" });
}
async unlock(password: string) {
await this.open();
await this.lockToggle.off(false);
await fillPasswordDialog(this.page, password);
await this.noteLocator
.locator(getTestId("locked"))
.waitFor({ state: "hidden" });
}
async isLocked() {
return (
(await this.noteLocator.locator(getTestId("locked")).isVisible()) &&
(await this.noteLocator.locator(getTestId(`description`)).isHidden()) &&
// (await (async () => {
// await this.noteLocator.click();
// return await this.page
// .locator(getTestId("unlock-note-title"))
// .isVisible();
// })()) &&
(await (async () => {
await this.open();
const state = await this.lockToggle.isToggled();
await this.close();
return state;
})())
);
}
async isFavorited() {
await this.open();
const state = await this.favoriteToggle.isToggled();
await this.close();
return state;
}
async favorite() {
await this.open();
await this.favoriteToggle.on();
await this.close();
}
async unfavorite() {
await this.open();
await this.favoriteToggle.off();
await this.close();
}
async isArchived() {
await this.open();
const state = await this.archiveToggle.isToggled();
await this.close();
return state;
}
async archive() {
await this.open();
await this.archiveToggle.on();
await this.close();
}
async unarchive() {
await this.open();
await this.archiveToggle.off();
await this.close();
}
abstract isColored(color: string): Promise;
abstract color(color: string): Promise;
abstract open(): Promise;
abstract close(): Promise;
}
export class NotePropertiesModel extends BaseProperties {
private readonly propertiesButton: Locator;
private readonly generalSection: Locator;
private readonly readonlyToggle: ToggleModel;
private readonly sessionItems: Locator;
constructor(page: Page, noteLocator: Locator) {
super(page, noteLocator, "properties");
this.propertiesButton = page.locator(getTestId("Properties"));
this.generalSection = page.locator(getTestId("general-section"));
this.readonlyToggle = new ToggleModel(page, `properties-readonly`);
this.sessionItems = page.locator(getTestId("session-item"));
}
async isColored(color: string): Promise {
await this.open();
const state = await new ToggleModel(
this.page,
`properties-${color}`
).isToggled();
await this.close();
return state;
}
async color(color: string) {
await this.open();
await new ToggleModel(this.page, `properties-${color}`).on();
await this.close();
}
async isReadonly() {
await this.open();
const state = await this.readonlyToggle.isToggled();
await this.close();
return state;
}
async readonly() {
await this.open();
await this.readonlyToggle.on();
await this.close();
}
async editable() {
await this.open();
await this.readonlyToggle.off();
await this.close();
}
async open() {
await this.propertiesButton.click();
await this.generalSection.waitFor();
}
async close() {
await this.propertiesButton.click();
}
async getSessionHistory() {
await this.open();
await this.sessionItems.first().waitFor();
const history: SessionHistoryItemModel[] = [];
for await (const item of iterateList(this.sessionItems)) {
history.push(new SessionHistoryItemModel(this, item));
}
await this.close();
return history;
}
async getDateCreated() {
await this.open();
const dateCreated = await this.generalSection
.locator(getTestId("date-created"))
.textContent();
await this.close();
return dateCreated;
}
async editDateCreated(newDateCreated: number) {
await this.open();
const editIcon = this.page.locator(getTestId("edit-date-created"));
await editIcon.click();
const editDateCreatedDialog = this.page.locator(
getTestId("edit-note-creation-date-dialog")
);
await editDateCreatedDialog.waitFor({ state: "visible" });
const dateInput = editDateCreatedDialog.locator(
getTestId("date-created-input")
);
const timeInput = editDateCreatedDialog.locator(
getTestId("time-created-input")
);
const date = new Date(newDateCreated);
await dateInput.fill(dayjs(date).format("DD-MM-YYYY"));
await timeInput.fill(dayjs(date).format("hh:mm A"));
await confirmDialog(editDateCreatedDialog);
await this.close();
}
}
export class NoteContextMenuModel extends BaseProperties {
private readonly menu: ContextMenuModel;
constructor(page: Page, noteLocator: Locator) {
super(page, noteLocator, "menu-button");
this.menu = new ContextMenuModel(page);
}
async openInNewTab() {
await this.open();
await this.menu.clickOnItem("openinnewtab");
}
async isColored(color: string): Promise {
await this.open();
await this.menu.clickOnItem("colors");
const state = await new ToggleModel(
this.page,
`menu-button-${color}`
).isToggled();
await this.close();
return state;
}
async color(color: string) {
await this.open();
await this.menu.clickOnItem("colors");
await new ToggleModel(this.page, `menu-button-${color}`).on();
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([
this.menu.clickOnItem("movetotrash"),
this.noteLocator.waitFor({ state: "detached" })
]);
}
async export(format: "html" | "md" | "txt") {
await this.open();
await this.menu.clickOnItem("export");
const content = (await downloadAndReadFile(
this.noteLocator.page(),
() => this.menu.getItem(format).click(),
"utf-8"
)) as string;
if (format === "html") {
return content
.replace(/(name="created-at" content=")(.+?)"/, '$1xxx"')
.replace(/(name="updated-at" content=")(.+?)"/, '$1xxx"')
.replace(/(data-block-id=")(.+?)"/gm, '$1xxx"');
}
return content;
}
async addToNotebook(notebook: Notebook) {
async function addSubNotebooks(
page: Page,
dialog: Locator,
item: Locator,
notebook: Notebook
) {
if (notebook.subNotebooks) {
const addSubNotebookButton = item.locator(
getTestId("add-sub-notebook")
);
for (const subNotebook of notebook.subNotebooks) {
await addSubNotebookButton.click();
await fillNotebookDialog(page, subNotebook);
const subNotebookItem = dialog.locator(getTestId("notebook"), {
hasText: subNotebook.title
});
await subNotebookItem.waitFor();
await page.keyboard.down("ControlOrMeta");
await subNotebookItem.click();
await page.keyboard.up("ControlOrMeta");
await addSubNotebooks(page, dialog, subNotebookItem, subNotebook);
}
}
}
await this.open();
await this.menu.clickOnItem("notebooks");
await this.menu.clickOnItem("link-notebooks");
const dialog = this.page.locator(getTestId("move-note-dialog"));
await dialog.locator(getTestId("add-new-notebook")).click();
await fillNotebookDialog(this.page, notebook);
const notebookItem = dialog.locator(getTestId("notebook"), {
hasText: notebook.title
});
await notebookItem.waitFor({ state: "visible" });
await this.page.keyboard.down("ControlOrMeta");
await notebookItem.click();
await this.page.keyboard.up("ControlOrMeta");
await addSubNotebooks(this.page, dialog, notebookItem, notebook);
await confirmDialog(dialog);
}
async open() {
await this.menu.open(this.noteLocator);
}
async close() {
await this.menu.close();
}
title() {
return this.menu.title();
}
}