web: fix all tests

This commit is contained in:
Abdullah Atta
2025-02-13 16:01:26 +05:00
parent e186615163
commit 0a362e9ce7
37 changed files with 252 additions and 281 deletions

View File

@@ -40,10 +40,12 @@ test("remove color", async ({ page }) => {
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
await app.navigation.waitForItem("red");
const colorItem = await app.navigation.findItem("red");
await colorItem?.removeColor();
await expect(colorItem!.locator).toBeHidden();
expect(await app.navigation.findItem("red")).toBeUndefined();
expect(await note?.contextMenu.isColored("red")).toBe(false);
});
@@ -54,6 +56,7 @@ test("rename color", async ({ page }) => {
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
await app.navigation.waitForItem("red");
const colorItem = await app.navigation.findItem("red");
await colorItem?.renameColor("priority-33");

View File

@@ -20,31 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { expect, test } from "@playwright/test";
import { AppModel } from "./models/app.model";
import { NotesViewModel } from "./models/notes-view.model";
import { getTestId } from "./utils";
for (const item of [
{ id: "notebooks", title: "Notebooks" },
{ id: "tags", title: "Tags" }
]) {
test(`drag & hover over ${item.id} should navigate inside`, async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote({
title: `Test note`
});
const navigationItem = await app.navigation.findItem(item.title);
await note?.locator.hover();
await page.mouse.down();
await navigationItem?.locator.hover();
await navigationItem?.locator.hover();
await page.waitForTimeout(1000);
await expect(app.routeHeader).toHaveText(item.title);
});
test.skip(`drag & hover over ${item.id} should navigate inside`, () => {});
}
test(`drag & drop note over Favorites should make the note favorite`, async ({
@@ -85,24 +66,20 @@ test(`drag & drop note over a notebook should get assigned to the notebook`, asy
}) => {
const app = new AppModel(page);
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook({ title: "Test notebook" });
const notes = await app.goToNotes();
const note = await notes.createNote({
title: `Test note`
});
const navigationItem = await app.navigation.findItem("Notebooks");
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook({ title: "Test notebook" });
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()) || {};
const notebookNotes = await notebook?.openNotebook();
expect(await notebookNotes?.findNote({ title: "Test note" })).toBeDefined();
});
@@ -111,19 +88,15 @@ test(`drag & drop note over a tag should get assigned to the tag`, async ({
}) => {
const app = new AppModel(page);
await app.goto();
const tags = await app.goToTags();
const tag = await tags.createItem({ title: "Tag" });
const notes = await app.goToNotes();
const note = await notes.createNote({
title: `Test note`
});
const navigationItem = await app.navigation.findItem("Tags");
const tags = await app.goToTags();
const tag = await tags.createItem({ title: "Tag" });
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();
@@ -185,25 +158,20 @@ test(`drag & drop note over a nested notebook should get assigned to the noteboo
}) => {
const app = new AppModel(page);
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook({
title: "Test notebook",
subNotebooks: [{ title: "Nested notebook" }]
});
const nestedNotebook = await (
await notebook?.openNotebook()
)?.subNotebooks.createNotebook({ title: "Nested notebook" });
const notes = await app.goToNotes();
const note = await notes.createNote({
title: `Test note`
});
const navigationItem = await app.navigation.findItem("Notebooks");
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook({
title: "Test notebook"
});
const nestedNotebook = await notebook?.createSubnotebook({
title: "Nested notebook"
});
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();
@@ -212,46 +180,3 @@ test(`drag & drop note over a nested notebook should get assigned to the noteboo
const notebookNotes = new NotesViewModel(page, "notebook", "notes");
expect(await notebookNotes?.findNote({ title: "Test note" })).toBeDefined();
});
test(`drag & hover over a nested notebook should navigate inside`, async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook({
title: "Test notebook",
subNotebooks: [{ title: "Nested notebook" }]
});
const nestedNotebook = await (
await notebook?.openNotebook()
)?.subNotebooks.createNotebook({ title: "Nested notebook" });
const notes = await app.goToNotes();
const note = await notes.createNote({
title: `Test note`
});
const navigationItem = await app.navigation.findItem("Notebooks");
await navigationItem?.click();
await navigationItem?.click();
await app.goToNotes();
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");
expect(
await page
.locator(getTestId("notebook-header"))
.locator(getTestId("notebook-title"))
.textContent()
).toBe("Nested notebook");
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -30,6 +30,7 @@ import { SearchViewModel } from "./search-view-model";
import { SettingsViewModel } from "./settings-view.model";
import { ToastsModel } from "./toasts.model";
import { TrashViewModel } from "./trash-view.model";
import { ContextMenuModel } from "./context-menu.model";
export class AppModel {
readonly page: Page;
@@ -38,6 +39,7 @@ export class AppModel {
readonly auth: AuthModel;
readonly checkout: CheckoutModel;
readonly routeHeader: Locator;
private readonly profileDropdown: ContextMenuModel;
constructor(page: Page) {
this.page = page;
@@ -46,56 +48,72 @@ export class AppModel {
this.auth = new AuthModel(page);
this.checkout = new CheckoutModel(page);
this.routeHeader = this.page.locator(getTestId("routeHeader"));
this.profileDropdown = new ContextMenuModel(this.page);
}
async goto(isLoggedIn = false) {
await this.page.goto("/");
await this.routeHeader.waitFor({ state: "visible" });
if (!isLoggedIn) await this.navigation.waitForItem("Login");
}
goBack() {
const goBackButton = this.page.locator(getTestId("route-go-back"));
return goBackButton.click();
if (!isLoggedIn)
await this.page
.locator(getTestId("logged-in"))
.waitFor({ state: "hidden" });
}
async goToNotes() {
await this.page.locator(getTestId("tab-home")).click();
await this.navigateTo("Notes");
return new NotesViewModel(this.page, "home", "home");
}
async goToNotebooks() {
await this.navigateTo("Notebooks");
return new NotebooksViewModel(this.page);
await this.page.locator(getTestId("tab-notebooks")).click();
const model = new NotebooksViewModel(this.page);
await model.waitForList();
return model;
}
async goToFavorites() {
await this.page.locator(getTestId("tab-home")).click();
await this.navigateTo("Favorites");
return new NotesViewModel(this.page, "notes", "favorites");
}
async goToReminders() {
await this.page.locator(getTestId("tab-home")).click();
await this.navigateTo("Reminders");
return new RemindersViewModel(this.page);
}
async goToTags() {
await this.navigateTo("Tags");
return new ItemsViewModel(this.page);
await this.page.locator(getTestId("tab-tags")).click();
const model = new ItemsViewModel(this.page);
await model.waitForList();
return model;
}
async goToHome() {
await this.page.locator(getTestId("tab-home")).click();
}
async goToColor(color: string) {
await this.page.locator(getTestId("tab-home")).click();
await this.navigateTo(color);
return new NotesViewModel(this.page, "notes", "notes");
}
async goToTrash() {
await this.page.locator(getTestId("tab-home")).click();
await this.navigateTo("Trash");
return new TrashViewModel(this.page);
}
async goToSettings() {
await this.navigateTo("Settings");
await this.profileDropdown.open(
this.page.locator(getTestId("profile-dropdown")),
"left"
);
await this.profileDropdown.clickOnItem("settings");
return new SettingsViewModel(this.page);
}
@@ -111,7 +129,7 @@ export class AppModel {
async getRouteHeader() {
if (!(await this.routeHeader.isVisible())) return;
return await this.routeHeader.innerText();
return await this.routeHeader.getAttribute("data-header");
}
async isSynced() {

View File

@@ -51,7 +51,11 @@ export class BaseViewModel {
.locator(getTestId("group-header"));
for await (const item of iterateList(locator)) {
if ((await item.locator(getTestId("title")).textContent()) === groupName)
if (
(
await item.locator(getTestId("title")).textContent()
)?.toLowerCase() === groupName.toLowerCase()
)
return item;
}
return undefined;
@@ -95,7 +99,9 @@ export class BaseViewModel {
// }
async press(key: string) {
const itemList = this.list.locator(getTestId(`virtuoso-item-list`, "data-testid"));
const itemList = this.list.locator(
getTestId(`virtuoso-item-list`, "data-testid")
);
await itemList.press(key);
await this.page.waitForTimeout(300);
}

View File

@@ -93,7 +93,6 @@ export class EditorModel {
}
async waitForUnloading() {
await this.page.waitForURL(/#\/notes\/?.+\/create/gm);
await this.searchButton.isDisabled();
await this.page
.locator(".active")
@@ -105,7 +104,6 @@ export class EditorModel {
}
async waitForSaving() {
await this.page.waitForURL(/#\/notes\/?.+\/edit/gm);
await this.page.locator(".active").locator(getTestId("tags")).waitFor();
await this.searchButton.waitFor();
await this.wordCountText.waitFor();

View File

@@ -28,25 +28,22 @@ export class ItemsViewModel extends BaseViewModel {
private readonly createButton: Locator;
constructor(page: Page) {
super(page, "tags", "tags");
this.createButton = page.locator(getTestId(`tags-action-button`));
this.createButton = page.locator(getTestId(`create-tag-button`));
}
async createItem(item: Item) {
const titleToCompare = `#${item.title}`;
await this.createButton.first().click();
await fillItemDialog(this.page, item);
await this.waitForItem(titleToCompare);
await this.waitForItem(item.title);
return await this.findItem(item);
}
async findItem(item: Item) {
const titleToCompare = `#${item.title}`;
for await (const _item of this.iterateItems()) {
const itemModel = new ItemModel(_item, "tag");
const title = await itemModel.getTitle();
if (title === titleToCompare) return itemModel;
if (title === item.title) return itemModel;
}
return undefined;
}

View File

@@ -134,14 +134,14 @@ abstract class BaseProperties {
export class NotePropertiesModel extends BaseProperties {
private readonly propertiesButton: Locator;
private readonly propertiesCloseButton: 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.propertiesCloseButton = page.locator(getTestId("properties-close"));
this.generalSection = page.locator(getTestId("general-section"));
this.readonlyToggle = new ToggleModel(page, `properties-readonly`);
this.sessionItems = page.locator(getTestId("session-item"));
}
@@ -183,12 +183,11 @@ export class NotePropertiesModel extends BaseProperties {
async open() {
await this.propertiesButton.click();
await this.propertiesCloseButton.waitFor();
await this.page.waitForTimeout(1000);
await this.generalSection.waitFor();
}
async close() {
await this.propertiesCloseButton.click();
await this.propertiesButton.click();
}
async getSessionHistory() {

View File

@@ -25,21 +25,21 @@ import { Notebook } from "./types";
import { confirmDialog, fillNotebookDialog } from "./utils";
import { NotesViewModel } from "./notes-view.model";
import { getTestId } from "../utils";
import { SubnotebooksViewModel } from "./subnotebooks-view.model";
import { NotebooksViewModel } from "./notebooks-view.model";
export class NotebookItemModel extends BaseItemModel {
private readonly contextMenu: ContextMenuModel;
constructor(locator: Locator) {
constructor(
locator: Locator,
private readonly notebooks: NotebooksViewModel
) {
super(locator);
this.contextMenu = new ContextMenuModel(this.page);
}
async openNotebook() {
await this.locator.click();
return {
subNotebooks: new SubnotebooksViewModel(this.page),
notes: new NotesViewModel(this.page, "notebook", "notes")
};
return new NotesViewModel(this.page, "notebook", "notes");
}
async editNotebook(notebook: Notebook) {
@@ -49,6 +49,16 @@ export class NotebookItemModel extends BaseItemModel {
await fillNotebookDialog(this.page, notebook);
}
async createSubnotebook(notebook: Notebook) {
await this.contextMenu.open(this.locator);
await this.contextMenu.clickOnItem("add");
await fillNotebookDialog(this.page, notebook);
await this.notebooks.waitForItem(notebook.title);
return await this.notebooks.findNotebook(notebook);
}
async moveToTrash(deleteContainedNotes = false) {
await this.contextMenu.open(this.locator);
await this.contextMenu.clickOnItem("movetotrash");

View File

@@ -30,7 +30,7 @@ export class NotebooksViewModel extends BaseViewModel {
constructor(page: Page) {
super(page, "notebooks", "notebooks");
this.createButton = page
.locator(getTestId("notebooks-action-button"))
.locator(getTestId("create-notebook-button"))
.first();
}
@@ -45,7 +45,7 @@ export class NotebooksViewModel extends BaseViewModel {
async findNotebook(notebook: Partial<Notebook>) {
for await (const item of this.iterateItems()) {
const notebookModel = new NotebookItemModel(item);
const notebookModel = new NotebookItemModel(item, this);
if ((await notebookModel.getTitle()) === notebook.title)
return notebookModel;
}

View File

@@ -38,10 +38,7 @@ export class NotesViewModel extends BaseViewModel {
listType: string
) {
super(page, pageId, listType);
this.createButton = page.locator(
// TODO:
getTestId(`notes-action-button`)
);
this.createButton = page.locator(getTestId(`create-new-note`));
this.editor = new EditorModel(page);
}

View File

@@ -30,7 +30,7 @@ export class RemindersViewModel extends BaseViewModel {
constructor(page: Page) {
super(page, "reminders", "reminders");
this.createButton = page
.locator(getTestId("reminders-action-button"))
.locator(getTestId("create-reminder-button"))
.first();
}

View File

@@ -56,8 +56,8 @@ export class SettingsViewModel {
await confirmDialog(this.page.locator(getTestId("confirm-dialog")));
await this.page
.locator(getTestId("not-logged-in"))
.waitFor({ state: "visible" });
.locator(getTestId("logged-in"))
.waitFor({ state: "hidden" });
}
async getRecoveryKey(password: string) {

View File

@@ -27,11 +27,9 @@ function createRoute(key: string, header: string) {
const routes = [
createRoute("notes", "Notes"),
createRoute("notebooks", "Notebooks"),
createRoute("favorites", "Favorites"),
createRoute("monographs", "Monographs"),
createRoute("reminders", "Reminders"),
createRoute("tags", "Tags"),
createRoute("trash", "Trash")
];

View File

@@ -43,7 +43,8 @@ test("create a note inside a notebook", async ({ page }) => {
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook(NOTEBOOK);
const { notes } = (await notebook?.openNotebook()) || {};
const notes = await notebook?.openNotebook();
await notes?.waitForList();
const note = await notes?.createNote(NOTE);
@@ -55,11 +56,10 @@ test("create a note inside a subnotebook", async ({ page }) => {
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook(NOTEBOOK);
const { subNotebooks } = (await notebook?.openNotebook()) || {};
const subNotebook = await subNotebooks?.createNotebook({
const subNotebook = await notebook?.createSubnotebook({
title: "Subnotebook 1"
});
const { notes } = (await subNotebook?.openNotebook()) || {};
const notes = await subNotebook?.openNotebook();
const note = await notes?.createNote(NOTE);
@@ -80,7 +80,6 @@ test("edit a notebook", async ({ page }) => {
const editedNotebook = await notebooks.findNotebook(item);
expect(editedNotebook).toBeDefined();
expect(await editedNotebook?.getDescription()).toBe(item.description);
});
test("delete a notebook", async ({ page }) => {
@@ -129,29 +128,6 @@ test("permanently delete a notebook", async ({ page }) => {
await expect(trashItem.locator).toBeHidden();
});
test("pin a notebook", async ({ page }) => {
const app = new AppModel(page);
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook(NOTEBOOK);
await notebook?.pin();
expect(await notebook?.isPinned()).toBe(true);
});
test("unpin a notebook", async ({ page }) => {
const app = new AppModel(page);
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook(NOTEBOOK);
await notebook?.pin();
await notebook?.unpin();
expect(await notebook?.isPinned()).toBe(false);
});
test("create shortcut of a notebook", async ({ page }) => {
const app = new AppModel(page);
await app.goto();
@@ -161,8 +137,8 @@ test("create shortcut of a notebook", async ({ page }) => {
await notebook?.createShortcut();
expect(await notebook?.isShortcut()).toBe(true);
const allShortcuts = await app.navigation.getShortcuts();
expect(allShortcuts.includes(NOTEBOOK.title)).toBeTruthy();
await app.goToHome();
expect(await app.navigation.findItem(NOTEBOOK.title)).toBeDefined();
});
test("remove shortcut of a notebook", async ({ page }) => {
@@ -184,14 +160,13 @@ test("delete all notes within a notebook", async ({ page }) => {
await app.goto();
const notebooks = await app.goToNotebooks();
const notebook = await notebooks.createNotebook(NOTEBOOK);
let { notes } = (await notebook?.openNotebook()) || {};
let notes = await notebook?.openNotebook();
for (let i = 0; i < 2; ++i) {
await notes?.createNote({
title: `Note ${i}`,
content: NOTE.content
});
}
await app.goBack();
await notebook?.moveToTrash(true);
@@ -254,20 +229,17 @@ test(`sort notebooks`, async ({ page }, info) => {
await notebooks.createNotebook(NOTEBOOK);
}
for (const groupBy of groupByOptions) {
for (const sortBy of sortByOptions) {
for (const orderBy of orderByOptions) {
await test.step(`group by ${groupBy}, sort by ${sortBy}, order by ${orderBy}`, async () => {
const sortResult = await notebooks?.sort({
groupBy,
orderBy,
sortBy
});
if (!sortResult) return;
await expect(notebooks.items).toHaveCount(titles.length);
for (const sortBy of sortByOptions) {
for (const orderBy of orderByOptions) {
await test.step(`sort by ${sortBy}, order by ${orderBy}`, async () => {
const sortResult = await notebooks?.sort({
orderBy,
sortBy
});
}
if (!sortResult) return;
await expect(notebooks.items).toHaveCount(titles.length);
});
}
}
});

View File

@@ -99,8 +99,8 @@ test("create shortcut of a tag", async ({ page }) => {
await tag?.createShortcut();
expect(await tag?.isShortcut()).toBe(true);
const allShortcuts = await app.navigation.getShortcuts();
expect(allShortcuts.includes("hello-world")).toBeTruthy();
await app.goToHome();
expect(await app.navigation.findItem("hello-world")).toBeDefined();
});
test("remove shortcut of a tag", async ({ page }) => {
@@ -204,20 +204,17 @@ test(`sort tags`, async ({ page }, info) => {
if (!tag) continue;
}
for (const groupBy of groupByOptions) {
for (const sortBy of sortByOptions) {
for (const orderBy of orderByOptions) {
await test.step(`group by ${groupBy}, sort by ${sortBy}, order by ${orderBy}`, async () => {
const sortResult = await tags?.sort({
groupBy,
orderBy,
sortBy
});
if (!sortResult) return;
await expect(tags.items).toHaveCount(titles.length);
for (const sortBy of sortByOptions) {
for (const orderBy of orderByOptions) {
await test.step(`sort by ${sortBy}, order by ${orderBy}`, async () => {
const sortResult = await tags?.sort({
orderBy,
sortBy
});
}
if (!sortResult) return;
await expect(tags.items).toHaveCount(titles.length);
});
}
}
});

View File

@@ -26,8 +26,10 @@ import {
Lock,
NewTab,
Note,
NoteAdd,
NoteRemove,
Pin,
Plus,
Properties,
Publish,
Published,
@@ -78,6 +80,7 @@ import { strings } from "@notesnook/intl";
import { getWindowControls } from "../title-bar";
import useTablet from "../../hooks/use-tablet";
import { isMac } from "../../utils/platform";
import { CREATE_BUTTON_MAP } from "../../common";
export function EditorActionBar() {
const { isMaximized, isFullscreen, hasNativeWindowControls } =
@@ -265,6 +268,14 @@ const TabStrip = React.memo(function TabStrip() {
}}
onDoubleClick={(e) => e.stopPropagation()}
>
<Button
variant="secondary"
{...CREATE_BUTTON_MAP.notes}
data-test-id={`create-new-note`}
sx={{ p: 1, bg: "transparent" }}
>
<NoteAdd size={16} />
</Button>
<Button
disabled={!canGoBack}
onClick={() => useEditorStore.getState().goBack()}

View File

@@ -119,7 +119,8 @@ const deferredSave = debounceWithId(saveContent, 100);
export default function TabsView() {
const tabs = useEditorStore((store) => store.tabs);
const documentPreview = useEditorStore((store) => store.documentPreview);
const activeTab = useEditorStore((store) => store.getActiveTab());
const activeTabId = useEditorStore((store) => store.activeTabId);
const activeSession = useEditorStore((store) => store.getActiveSession());
const arePropertiesVisible = useEditorStore(
(store) => store.arePropertiesVisible
);
@@ -128,6 +129,17 @@ export default function TabsView() {
return (
<>
<Flex
className="editor-action-bar"
sx={{
zIndex: 2,
height: TITLE_BAR_HEIGHT,
bg: "background-secondary"
// borderBottom: "1px solid var(--border)"
}}
>
<EditorActionBar />
</Flex>
<ScopedThemeProvider
scope="editor"
ref={dropRef}
@@ -140,17 +152,6 @@ export default function TabsView() {
position: "relative"
}}
>
<Flex
className="editor-action-bar"
sx={{
zIndex: 2,
height: TITLE_BAR_HEIGHT,
bg: "background-secondary"
// borderBottom: "1px solid var(--border)"
}}
>
<EditorActionBar />
</Flex>
<SplitPane direction="vertical" autoSaveId={"editor-panels"}>
<Pane id="editor-panel" className="editor-pane">
{tabs.map((tab) => {
@@ -159,7 +160,7 @@ export default function TabsView() {
.getSession(tab.sessionId);
if (!session) return null;
return (
<Freeze key={session.id} freeze={tab.id !== activeTab?.id}>
<Freeze key={session.id} freeze={tab.id !== activeTabId}>
{session.type === "locked" ? (
<UnlockNoteView session={session} />
) : session.type === "conflicted" ||
@@ -210,15 +211,15 @@ export default function TabsView() {
</Pane>
) : null}
{isTOCVisible && activeTab ? (
{isTOCVisible && activeSession ? (
<Pane id="table-of-contents-pane" initialSize={300} minSize={300}>
<TableOfContents sessionId={activeTab.sessionId} />
<TableOfContents sessionId={activeSession.id} />
</Pane>
) : null}
</SplitPane>
<DropZone overlayRef={overlayRef} />
{arePropertiesVisible && activeTab && (
<Properties sessionId={activeTab.sessionId} />
{arePropertiesVisible && activeSession && (
<Properties sessionId={activeSession.id} />
)}
</ScopedThemeProvider>
</>

View File

@@ -25,6 +25,7 @@ import { PasswordVisible, PasswordInvisible, Icon } from "../icons";
import { useStore as useThemeStore } from "../../stores/theme-store";
type Action = {
id?: string;
testId?: string;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
@@ -160,6 +161,7 @@ function Field(props: FieldProps) {
>
{leftActions.map((action) => (
<Button
id={action.id}
key={action.testId}
type="button"
variant={"secondary"}
@@ -202,6 +204,7 @@ function Field(props: FieldProps) {
>
{rightActions.map((action) => (
<Button
id={action.id}
key={action.testId}
type="button"
variant={"secondary"}

View File

@@ -226,7 +226,9 @@ import {
mdiHistory,
mdiArrowCollapseLeft,
mdiArrowCollapseRight,
mdiHamburger
mdiHamburger,
mdiNotePlus,
mdiNoteEditOutline
} from "@mdi/js";
import { useTheme } from "@emotion/react";
import { Theme } from "@notesnook/theme";
@@ -321,6 +323,7 @@ export function createIcon(path: string, rotate = false) {
}
export const Plus = createIcon(mdiPlus);
export const NoteAdd = createIcon(mdiNoteEditOutline);
export const Note = createIcon(mdiNoteOutline);
export const NoteRemove = createIcon(mdiNoteRemoveOutline);
export const Notes = createIcon(mdiNoteMultipleOutline);

View File

@@ -63,6 +63,7 @@ export const CustomScrollbarsVirtualList = forwardRef<
});
type ListContainerProps = {
type: GroupingKey;
group?: GroupingKey;
items: VirtualizedGrouping<Item>;
compact?: boolean;
@@ -77,7 +78,8 @@ type ListContainerProps = {
};
} & SxProp;
function ListContainer(props: ListContainerProps) {
const { group, items, context, refresh, header, button, compact, sx } = props;
const { type, group, items, context, refresh, header, button, compact, sx } =
props;
const [focusedGroupIndex, setFocusedGroupIndex] = useState(-1);
@@ -201,7 +203,7 @@ function ListContainer(props: ListContainerProps) {
<Flex
ref={listContainerRef}
variant="columnFill"
data-test-id={`${group}-list`}
data-test-id={`${type}-list`}
>
<Virtuoso
ref={listRef}

View File

@@ -149,18 +149,18 @@ const tabs = [
actions: []
},
{
id: "notebook",
id: "notebooks",
icon: NotebookIcon,
title: strings.routes.Notebooks(),
actions: [
{
id: "add",
id: "create-notebook-button",
title: CREATE_BUTTON_MAP.notebooks.title,
icon: Plus,
onClick: CREATE_BUTTON_MAP.notebooks.onClick
},
{
id: "sort-by",
id: "notebooks-sort-button",
title: strings.sortBy(),
icon: SortBy,
onClick: () =>
@@ -169,18 +169,18 @@ const tabs = [
]
},
{
id: "tag",
id: "tags",
icon: TagIcon,
title: strings.routes.Tags(),
actions: [
{
id: "add",
id: "create-tag-button",
title: CREATE_BUTTON_MAP.tags.title,
icon: Plus,
onClick: CREATE_BUTTON_MAP.tags.onClick
},
{
id: "sort-by",
id: "tags-sort-button",
title: strings.sortBy(),
icon: SortBy,
onClick: () =>
@@ -318,12 +318,12 @@ function NavigationMenu({ onExpand }: { onExpand?: () => void }) {
{tabs.map((tab) => (
<TabItem
key={tab.id}
id={tab.id}
data-test-id={`tab-${tab.id}`}
title={tab.title}
icon={tab.icon}
selected={currentTab.id === tab.id}
onClick={() => {
setExpanded(true);
if (isNavPaneCollapsed) setExpanded(true);
setCurrentTab(tab);
}}
/>
@@ -338,6 +338,7 @@ function NavigationMenu({ onExpand }: { onExpand?: () => void }) {
sx={{ p: 1, bg: "transparent" }}
onClick={action.onClick}
title={action.title}
data-test-id={action.id}
>
<action.icon size={13} color="icon" />
</Button>
@@ -360,10 +361,10 @@ function NavigationMenu({ onExpand }: { onExpand?: () => void }) {
Menu.openMenu(await getSidebarItemsAsMenuItems());
}}
>
<Freeze freeze={isCollapsed || currentTab.id !== "notebook"}>
<Freeze freeze={isCollapsed || currentTab.id !== "notebooks"}>
<Notebooks />
</Freeze>
<Freeze freeze={isCollapsed || currentTab.id !== "tag"}>
<Freeze freeze={isCollapsed || currentTab.id !== "tags"}>
<Tags />
</Freeze>
<Freeze freeze={currentTab.id !== "home" && !isCollapsed}>
@@ -720,6 +721,7 @@ function NavigationDropdown() {
);
}}
variant="columnCenter"
data-test-id="profile-dropdown"
sx={{
bg: "background-secondary",
size: 30,

View File

@@ -88,10 +88,10 @@ function NavigationItem(
AppEventManager.publish(AppEvents.toggleSideMenu, false);
if (onClick) onClick();
}}
data-test-id={`navigation-item`}
title={title}
>
<Button
data-test-id={`navigation-item`}
sx={{
p: 0,
flex: 1,

View File

@@ -205,15 +205,6 @@ const notebookMenuItems: (
);
}
},
{
type: "button",
key: "pin",
icon: Pin.path,
title: strings.pin(),
isChecked: notebook.pinned,
onClick: () => store.pin(!notebook.pinned, ...ids),
multiSelect: true
},
{
type: "button",
key: "shortcut",

View File

@@ -169,7 +169,10 @@ function EditorProperties(props: EditorPropertiesProps) {
/>
<Text variant="subtitle">{strings.properties()}</Text>
</Flex> */}
<Flex sx={{ flexDirection: "column", gap: 1 }}>
<Flex
data-test-id="general-section"
sx={{ flexDirection: "column", gap: 1 }}
>
<Section title="Properties">
<Flex sx={{ flexDirection: "column", gap: 1, px: 2, pt: 1 }}>
{session.type === "deleted" ? null : (

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { PropsWithChildren, useRef } from "react";
import { Box, Button, Flex, Text } from "@theme-ui/components";
import { ArrowLeft, Menu, Search, Plus, Close } from "../icons";
import { ArrowLeft, Menu, Search, Plus, Close, AddReminder } from "../icons";
import { useStore } from "../../stores/app-store";
import { useStore as useSearchStore } from "../../stores/search-store";
import useMobile from "../../hooks/use-mobile";
@@ -29,6 +29,7 @@ import { strings } from "@notesnook/intl";
import { TITLE_BAR_HEIGHT } from "../title-bar";
import { AppEventManager, AppEvents } from "../../common/app-events";
import { RouteResult } from "../../navigation/types";
import { CREATE_BUTTON_MAP } from "../../common";
export type RouteContainerButtons = {
search?: {
@@ -76,6 +77,10 @@ function Header(props: RouteContainerProps) {
p: 1
}}
className="route-container-header search-container"
data-test-id="routeHeader"
data-header={
titlePromise.status === "fulfilled" ? titlePromise.value || type : type
}
>
<Field
inputRef={inputRef}
@@ -102,10 +107,10 @@ function Header(props: RouteContainerProps) {
"::placeholder": {
textAlign: "center"
},
"& + .rightActions": {
"& + .rightActions #search-action-button": {
opacity: 0
},
"&:focus + .rightActions": {
"&:focus + .rightActions #search-action-button": {
opacity: 1
}
}
@@ -128,18 +133,30 @@ function Header(props: RouteContainerProps) {
});
else useSearchStore.setState({ isSearching: true, searchType: type });
}}
action={{
icon: Close,
testId: "search-button",
onClick: () => {
if (inputRef.current) inputRef.current.value = "";
useSearchStore.setState({
isSearching: false,
query: undefined,
searchType: undefined
});
}
}}
rightActions={[
{
icon: Close,
id: "search-action-button",
testId: "search-button",
onClick: () => {
if (inputRef.current) inputRef.current.value = "";
useSearchStore.setState({
isSearching: false,
query: undefined,
searchType: undefined
});
}
},
...(type === "reminders"
? [
{
icon: AddReminder,
testId: "create-reminder-button",
...CREATE_BUTTON_MAP.reminders
}
]
: [])
]}
/>
</Box>
);
@@ -235,14 +252,14 @@ function Header(props: RouteContainerProps) {
// </Button>
// )}
// {titlePromise.status === "fulfilled" && titlePromise.value && (
// <Text
// className="routeHeader"
// variant="heading"
// data-test-id="routeHeader"
// color="heading"
// >
// {titlePromise.value}
// </Text>
// <Text
// className="routeHeader"
// variant="heading"
// data-test-id="routeHeader"
// color="heading"
// >
// {titlePromise.value}
// </Text>
// )}
// </Flex>
// <Flex sx={{ flexShrink: 0, gap: 2 }}>
@@ -264,22 +281,22 @@ function Header(props: RouteContainerProps) {
// </Button>
// )}
// {!isMobile && buttons?.create && (
// <Button
// {...buttons.create}
// data-test-id={`${type}-action-button`}
// sx={{ p: 0 }}
// >
// <Plus
// color="accentForeground"
// size={18}
// sx={{
// height: 24,
// width: 24,
// bg: "accent",
// borderRadius: 100
// }}
// />
// </Button>
// <Button
// {...buttons.create}
// data-test-id={`${type}-action-button`}
// sx={{ p: 0 }}
// >
// <Plus
// color="accentForeground"
// size={18}
// sx={{
// height: 24,
// width: 24,
// bg: "accent",
// borderRadius: 100
// }}
// />
// </Button>
// )}
// </Flex>
// </Flex>

View File

@@ -80,6 +80,7 @@ function StatusBar() {
size={7}
color={"var(--icon-success)"}
sx={{ p: "small" }}
data-test-id="logged-in"
/>
) : (
<Button
@@ -92,7 +93,11 @@ function StatusBar() {
height: "100%"
}}
>
<Circle size={7} color={"var(--icon-error)"} />
<Circle
size={7}
color={"var(--icon-error)"}
data-test-id="logged-in"
/>
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
{strings.emailNotConfirmed()}
</Text>

View File

@@ -44,7 +44,7 @@ if (theme) {
if (stylesheet) stylesheet.innerHTML = css;
} else stylesheet?.remove();
const locale = !import.meta.env.DEV
const locale = import.meta.env.DEV
? import("@notesnook/intl/locales/$pseudo-LOCALE.json")
: import("@notesnook/intl/locales/$en.json");
locale.then(({ default: locale }) => {

View File

@@ -63,6 +63,7 @@ function Home() {
if (!notes) return <ListLoader />;
return (
<ListContainer
type="home"
group="home"
compact={isCompact}
refresh={refresh}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Notebook as NotebookType, VirtualizedGrouping } from "@notesnook/core";
import { Box, Flex, Input } from "@theme-ui/components";
import { Box, Flex, Input, Text } from "@theme-ui/components";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { db } from "../common/db";
import { store, useStore } from "../stores/notebook-store";
@@ -32,6 +32,7 @@ import {
} from "../components/virtualized-tree";
import { ListLoader } from "../components/loaders/list-loader";
import { debounce } from "@notesnook/common";
import { strings } from "@notesnook/intl";
export function Notebooks() {
const roots = useStore((store) => store.notebooks);
@@ -60,22 +61,29 @@ export function Notebooks() {
if (!notebooks)
return (
<Flex sx={{ flexDirection: "column" }}>
<Flex id="notebooks" sx={{ flexDirection: "column" }}>
<ListLoader />
</Flex>
);
if (notebooks.length === 0) {
return (
<Flex variant="columnCenterFill" data-test-id="list-placeholder">
<Placeholder context="notebooks" />
<Flex id="notebooks" sx={{ mx: 1, flex: 1 }}>
<Text
variant="body"
sx={{ color: "paragraph-secondary" }}
data-test-id="list-placeholder"
>
{strings.notebooksEmpty()}
</Text>
</Flex>
);
}
return (
<>
<Box sx={{ mx: 1, flex: 1 }}>
<Box id="notebooks" sx={{ mx: 1, flex: 1 }}>
<VirtualizedTree
testId="notebooks-list"
rootId={"root"}
itemHeight={26}
treeRef={treeRef}

View File

@@ -50,6 +50,7 @@ function Notes(props: NotesProps) {
if (!context || !contextNotes) return <ListLoader />;
return (
<ListContainer
type={type}
group={type}
refresh={refreshContext}
compact={isCompact}

View File

@@ -38,6 +38,7 @@ function Reminders() {
return (
<>
<ListContainer
type="reminders"
group="reminders"
refresh={refresh}
items={filteredItems || reminders}

View File

@@ -39,8 +39,9 @@ function Tags() {
if (!items) return <ListLoader />;
return (
<Flex variant="columnFill">
<Flex variant="columnFill" id="tags">
<ListContainer
type="tags"
sx={{ mx: 1 }}
refresh={refresh}
items={items}

View File

@@ -40,6 +40,7 @@ function Trash() {
if (!items) return <ListLoader />;
return (
<ListContainer
type="trash"
group="trash"
refresh={refresh}
placeholder={<Placeholder context="trash" />}

View File

@@ -153,7 +153,7 @@ export default defineConfig({
mode: "production",
workbox: { mode: "production" },
injectManifest: {
globPatterns: ["**/*.{js,css,html,wasm}", "**/open-sans-*.woff2"],
globPatterns: ["**/*.{js,css,html,wasm}", "**/Inter-*.woff2"],
globIgnores: [
"**/node_modules/**/*",
"**/code-lang-*.js",