diff --git a/apps/web/__e2e__/app-lock.test.ts b/apps/web/__e2e__/app-lock.test.ts index d66cddc4e..e03bfcbee 100644 --- a/apps/web/__e2e__/app-lock.test.ts +++ b/apps/web/__e2e__/app-lock.test.ts @@ -44,9 +44,9 @@ test("don't show status bar lock app button to authenticated user", async ({ test("show status bar lock app button to authenticated user if app lock is enabled", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); - await app.auth.goto(); - await app.auth.login(USER.CURRENT); + await app.goto(); const settings = await app.goToSettings(); await settings.enableAppLock(USER.CURRENT.password!, APP_LOCK_PASSWORD); @@ -58,9 +58,9 @@ test("show status bar lock app button to authenticated user if app lock is enabl test("clicking on status bar lock app button should lock app", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); - await app.auth.goto(); - await app.auth.login(USER.CURRENT); + await app.goto(); const settings = await app.goToSettings(); await settings.enableAppLock(USER.CURRENT.password!, APP_LOCK_PASSWORD); @@ -75,9 +75,9 @@ test("clicking on status bar lock app button should lock app", async ({ test("disabling app lock setting should remove status bar lock app button", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); - await app.auth.goto(); - await app.auth.login(USER.CURRENT); + await app.goto(); let settings = await app.goToSettings(); await settings.enableAppLock(USER.CURRENT.password!, APP_LOCK_PASSWORD); diff --git a/apps/web/__e2e__/checkout.test.ts b/apps/web/__e2e__/checkout.test.ts index 85c65f37d..c8dadf672 100644 --- a/apps/web/__e2e__/checkout.test.ts +++ b/apps/web/__e2e__/checkout.test.ts @@ -59,7 +59,7 @@ function roundOffPrices(prices: PriceItem[]) { .join("\n"); } -test("change plans", async () => { +test.skip("change plans", async () => { await app.checkout.goto(); const plans = await app.checkout.getPlans(); @@ -75,7 +75,7 @@ test("change plans", async () => { expect(titles.join("").length).toBeGreaterThan(0); }); -test("confirm plan prices", async () => { +test.skip("confirm plan prices", async () => { await app.checkout.goto(); const plans = await app.checkout.getPlans(); @@ -94,7 +94,7 @@ test("confirm plan prices", async () => { } }); -test("changing locale should show localized prices", async () => { +test.skip("changing locale should show localized prices", async () => { await app.checkout.goto(); const plans = await app.checkout.getPlans(); @@ -114,7 +114,7 @@ test("changing locale should show localized prices", async () => { } }); -test("applying coupon should change discount & total price", async () => { +test.skip("applying coupon should change discount & total price", async () => { await app.checkout.goto(); const plans = await app.checkout.getPlans(); @@ -137,7 +137,7 @@ test("applying coupon should change discount & total price", async () => { } }); -test("apply coupon through url", async () => { +test.skip("apply coupon through url", async () => { const planPrices: Record = {}; for (const plan of ["monthly", "yearly"] as const) { await app.checkout.goto(plan, "INTRO50"); @@ -157,7 +157,7 @@ test("apply coupon through url", async () => { } }); -test("apply coupon after changing country", async () => { +test.skip("apply coupon after changing country", async () => { await app.checkout.goto(); const plans = await app.checkout.getPlans(); diff --git a/apps/web/__e2e__/colors.test.ts b/apps/web/__e2e__/colors.test.ts index 0ae1d9cc1..547d0855f 100644 --- a/apps/web/__e2e__/colors.test.ts +++ b/apps/web/__e2e__/colors.test.ts @@ -18,7 +18,7 @@ along with this program. If not, see . */ import { test, expect } from "@playwright/test"; import { AppModel } from "./models/app.model"; -import { NOTE } from "./utils"; +import { getTestId, NOTE } from "./utils"; test("delete the last note of a color", async ({ page }) => { const app = new AppModel(page); @@ -64,18 +64,36 @@ test("rename color", async ({ page }) => { expect(await app.navigation.findItem("priority-33")).toBeDefined(); }); -test("creating a color shouldn't be possible on basic plan", async ({ +test("creating more than 7 colors shouldn't be possible on free plan", async ({ page }) => { - await page.exposeBinding("isBasic", () => true); 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" }); + for (let i = 0; i < 7; ++i) { + await note?.contextMenu.newColor({ + title: `red${i}`, + color: getRandomColor() + }); + } - expect( - await app.toasts.waitForToast("Upgrade to Notesnook Pro to add colors.") - ).toBe(true); + const result = await Promise.race([ + note?.contextMenu.newColor({ + title: `color`, + color: getRandomColor() + }), + page.waitForSelector(getTestId("upgrade-dialog")).then(() => true) + ]); + expect(result).toBe(true); }); + +function getRandomColor() { + const letters = "0123456789ABCDEF"; + let color = "#"; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +} diff --git a/apps/web/__e2e__/models/auth.model.ts b/apps/web/__e2e__/models/auth.model.ts index 1d0a33ebc..9ee305ad3 100644 --- a/apps/web/__e2e__/models/auth.model.ts +++ b/apps/web/__e2e__/models/auth.model.ts @@ -74,6 +74,8 @@ export class AuthModel { await this.submitButton.click(); } + await this.page.locator(getTestId("close-plans")).click(); + await this.page .locator(getTestId("sync-status-synced")) .waitFor({ state: "visible" }); diff --git a/apps/web/__e2e__/models/settings-view.model.ts b/apps/web/__e2e__/models/settings-view.model.ts index 3ba69b6fd..f1fa8b6d5 100644 --- a/apps/web/__e2e__/models/settings-view.model.ts +++ b/apps/web/__e2e__/models/settings-view.model.ts @@ -55,6 +55,10 @@ export class SettingsViewModel { await logoutButton.click(); await confirmDialog(this.page.locator(getTestId("confirm-dialog"))); + await this.page + .locator(getTestId("progress-dialog")) + .waitFor({ state: "hidden" }); + await this.page .locator(getTestId("logged-in")) .waitFor({ state: "hidden" }); diff --git a/apps/web/__e2e__/notebooks.test.ts b/apps/web/__e2e__/notebooks.test.ts index f264247c9..6bb14ddff 100644 --- a/apps/web/__e2e__/notebooks.test.ts +++ b/apps/web/__e2e__/notebooks.test.ts @@ -21,6 +21,7 @@ import { test, expect } from "@playwright/test"; import { AppModel } from "./models/app.model"; import { Notebook } from "./models/types"; import { + getTestId, groupByOptions, NOTE, NOTEBOOK, @@ -197,22 +198,24 @@ test("delete all notes within a notebook", async ({ page }) => { // expect(await notes.isEmpty()).toBe(true); // }); -test("creating more than 20 notebooks shouldn't be possible on basic plan", async ({ +test("creating more than 50 notebooks shouldn't be possible on basic plan", async ({ page }, info) => { info.setTimeout(2 * 60 * 1000); - await page.exposeBinding("isBasic", () => true); const app = new AppModel(page); await app.goto(); const notebooks = await app.goToNotebooks(); - for (let i = 0; i < 20; ++i) { + + for (let i = 0; i < 50; ++i) { await notebooks.createNotebook({ title: `Notebook ${i}` }); } const result = await Promise.race([ notebooks.createNotebook(NOTEBOOK), - app.toasts.waitForToast("Upgrade to Notesnook Pro to add more notebooks.") + page + .waitForSelector(getTestId("upgrade-dialog"), { state: "visible" }) + .then(() => true) ]); expect(result).toBe(true); }); @@ -247,6 +250,7 @@ test(`sort notebooks`, async ({ page }, info) => { test("when default notebook is set, created note in notes context should go to default notebook", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); let notebooks = await app.goToNotebooks(); @@ -265,6 +269,7 @@ test("when default notebook is set, created note in notes context should go to d test("when default notebook is set, created note in other notebook's context should not go to default notebook", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); let notebooks = await app.goToNotebooks(); @@ -286,6 +291,7 @@ test("when default notebook is set, created note in other notebook's context sho test("when default notebook is set, created note in tags context should go to default notebook", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); let notebooks = await app.goToNotebooks(); @@ -306,6 +312,7 @@ test("when default notebook is set, created note in tags context should go to de test("when default notebook is set, created note in colors context should go to default notebook", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const coloredNote = { title: "Red note", content: NOTE.content }; const app = new AppModel(page); await app.goto(); diff --git a/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-darwin.txt b/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-darwin.txt index 24dab4114..a35ec00b8 100644 --- a/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-darwin.txt +++ b/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-darwin.txt @@ -1,3 +1,3 @@ Test 1 - This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1 \ No newline at end of file +This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1 \ No newline at end of file diff --git a/apps/web/__e2e__/reminders.test.ts b/apps/web/__e2e__/reminders.test.ts index e1c1d7284..269debcfa 100644 --- a/apps/web/__e2e__/reminders.test.ts +++ b/apps/web/__e2e__/reminders.test.ts @@ -68,6 +68,7 @@ test("adding a one-time reminder before current time should not be possible", as for (const recurringMode of ["Daily", "Weekly", "Monthly"] as const) { test(`add a recurring reminder (${recurringMode})`, async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); const reminders = await app.goToReminders(); @@ -90,6 +91,8 @@ for (const recurringMode of ["Daily", "Weekly", "Monthly"] as const) { } test(`add a recurring reminder before current time`, async ({ page }) => { + await page.exposeBinding("isPro", () => true); + const app = new AppModel(page); await app.goto(); const reminders = await app.goToReminders(); @@ -172,6 +175,8 @@ test("enable a disabled reminder", async ({ page }) => { test("editing a weekly recurring reminder should not revert it to daily", async ({ page }) => { + await page.exposeBinding("isPro", () => true); + const RECURRING_REMINDER: Partial = { ...ONE_TIME_REMINDER, recurringMode: "week", diff --git a/apps/web/__e2e__/settings.test.ts b/apps/web/__e2e__/settings.test.ts index cfbf17ca2..76c6b00fb 100644 --- a/apps/web/__e2e__/settings.test.ts +++ b/apps/web/__e2e__/settings.test.ts @@ -62,6 +62,8 @@ test("do not ask for image compression during image upload when 'Image Compressi test("do not ask for image compression during image upload when 'Image Compression' setting is 'Disable'", async ({ page }) => { + await page.exposeBinding("isPro", () => true); + const app = new AppModel(page); await app.goto(); const settings = await app.goToSettings(); diff --git a/apps/web/__e2e__/tags.test.ts b/apps/web/__e2e__/tags.test.ts index 60098d4d0..ccd48c61c 100644 --- a/apps/web/__e2e__/tags.test.ts +++ b/apps/web/__e2e__/tags.test.ts @@ -20,7 +20,7 @@ along with this program. If not, see . import { test, expect } from "@playwright/test"; import { AppModel } from "./models/app.model"; import { Item } from "./models/types"; -import { NOTE, orderByOptions, sortByOptions } from "./utils"; +import { getTestId, NOTE, orderByOptions, sortByOptions } from "./utils"; const TAG: Item = { title: "hello-world" }; const EDITED_TAG: Item = { title: "hello-world-2" }; @@ -219,20 +219,22 @@ test(`sort tags`, async ({ page }, info) => { } }); -test("creating more than 5 tags shouldn't be possible on basic plan", async ({ +test("creating more than 50 tags shouldn't be possible on free plan", async ({ page -}) => { - await page.exposeBinding("isBasic", () => true); +}, info) => { + info.setTimeout(2 * 60 * 1000); const app = new AppModel(page); await app.goto(); const tags = await app.goToTags(); - for (const tag of ["tag1", "tag2", "tag3", "tag4", "tag5"]) { - await tags.createItem({ title: tag }); + for (let i = 0; i < 50; i++) { + await tags.createItem({ title: `tag${i}` }); } const result = await Promise.race([ - tags.createItem({ title: "tag6" }), - app.toasts.waitForToast("Upgrade to Notesnook Pro to create more tags.") + tags.createItem({ title: "tag50" }), + page + .waitForSelector(getTestId("upgrade-dialog"), { state: "visible" }) + .then(() => true) ]); expect(result).toBe(true); }); @@ -240,6 +242,7 @@ test("creating more than 5 tags shouldn't be possible on basic plan", async ({ test("when default tag is set, created note in notes context should have default tag", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); let tags = await app.goToTags(); @@ -258,6 +261,7 @@ test("when default tag is set, created note in notes context should have default test("when default tag is set, created note in other tag's context should not have default tag", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); let tags = await app.goToTags(); @@ -279,6 +283,7 @@ test("when default tag is set, created note in other tag's context should not ha test("when default tag is set, created note in notebooks context should have default tag", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const app = new AppModel(page); await app.goto(); let tags = await app.goToTags(); @@ -299,6 +304,7 @@ test("when default tag is set, created note in notebooks context should have def test("when default tag is set, created note in colors context should have default tag", async ({ page }) => { + await page.exposeBinding("isPro", () => true); const coloredNote = { title: "Red note", content: NOTE.content }; const app = new AppModel(page); await app.goto(); diff --git a/apps/web/src/common/db.ts b/apps/web/src/common/db.ts index 4ea52cf77..7430df7bd 100644 --- a/apps/web/src/common/db.ts +++ b/apps/web/src/common/db.ts @@ -30,7 +30,11 @@ import { createDialect } from "./sqlite"; import { isFeatureSupported } from "../utils/feature-check"; import { generatePassword } from "../utils/password-generator"; import { deriveKey, useKeyStore } from "../interfaces/key-store"; -import { logManager } from "@notesnook/core"; +import { + logManager, + SubscriptionPlan, + SubscriptionStatus +} from "@notesnook/core"; import Config from "../utils/config"; import { FileStorage } from "../interfaces/fs"; @@ -44,24 +48,15 @@ async function initializeDatabase(persistence: DatabasePersistence) { await useKeyStore.getState().setValue("databaseKey", databaseKey); } - // db.host({ - // API_HOST: "https://api.notesnook.com", - // AUTH_HOST: "https://auth.streetwriters.co", - // SSE_HOST: "https://events.streetwriters.co", - // ISSUES_HOST: "https://issues.streetwriters.co", - // MONOGRAPH_HOST: "https://monogr.ph", - // SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co", - // ...Config.get("serverUrls", {}) - // }); - const base = `http://localhost`; db.host({ - API_HOST: `${base}:5264`, - AUTH_HOST: `${base}:8264`, - SSE_HOST: `${base}:7264`, - ISSUES_HOST: `${base}:2624`, - SUBSCRIPTIONS_HOST: `${base}:9264`, - MONOGRAPH_HOST: `${base}:6264`, - NOTESNOOK_HOST: `${base}:8788` + API_HOST: "https://api.notesnook.com", + AUTH_HOST: "https://auth.streetwriters.co", + SSE_HOST: "https://events.streetwriters.co", + ISSUES_HOST: "https://issues.streetwriters.co", + MONOGRAPH_HOST: "https://monogr.ph", + SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co", + NOTESNOOK_HOST: "https://notesnook.com", + ...Config.get("serverUrls", {}) }); const storage = new NNStorage( @@ -143,6 +138,17 @@ async function initializeDatabase(persistence: DatabasePersistence) { } performance.mark("end:initializeDatabase"); + + if (IS_TESTING && "isPro" in window) { + await db.user.setUser({ + // @ts-expect-error just for testing purposes + subscription: { + plan: SubscriptionPlan.PRO, + status: SubscriptionStatus.ACTIVE + } + }); + } + return db; } diff --git a/apps/web/src/common/index.ts b/apps/web/src/common/index.ts index 1362b3c56..8200c0261 100644 --- a/apps/web/src/common/index.ts +++ b/apps/web/src/common/index.ts @@ -378,7 +378,7 @@ async function restoreWithProgress( } export async function verifyAccount() { - if (!(await db.user?.getUser())) return true; + if (!(await db.user?.getUser())?.email) return true; return await showPasswordDialog({ title: strings.verifyItsYou(), subtitle: strings.enterAccountPasswordDesc(), diff --git a/apps/web/src/dialogs/buy-dialog/upgrade-dialog.tsx b/apps/web/src/dialogs/buy-dialog/upgrade-dialog.tsx index 873122882..66176b4a1 100644 --- a/apps/web/src/dialogs/buy-dialog/upgrade-dialog.tsx +++ b/apps/web/src/dialogs/buy-dialog/upgrade-dialog.tsx @@ -74,6 +74,7 @@ export const UpgradeDialog = DialogManager.register(function UpgradeDialog( return ( ( return ( {}} diff --git a/apps/web/src/global.d.ts b/apps/web/src/global.d.ts index d8c4a4f07..dc0624fec 100644 --- a/apps/web/src/global.d.ts +++ b/apps/web/src/global.d.ts @@ -21,6 +21,7 @@ along with this program. If not, see . import "vite/client"; import "vite-plugin-svgr/client"; import "@notesnook/desktop/dist/preload"; +import type { Database } from "@notesnook/core"; declare global { var PUBLIC_URL: string; diff --git a/apps/web/src/stores/user-store.ts b/apps/web/src/stores/user-store.ts index 8e6fcb872..a3b99091c 100644 --- a/apps/web/src/stores/user-store.ts +++ b/apps/web/src/stores/user-store.ts @@ -45,7 +45,7 @@ class UserStore extends BaseStore { }); db.user.getUser().then((user) => { - if (!user) { + if (!user?.email) { this.set({ isLoggedIn: false }); return; } diff --git a/apps/web/src/views/plans.tsx b/apps/web/src/views/plans.tsx index f6e68c647..3a07bcf7d 100644 --- a/apps/web/src/views/plans.tsx +++ b/apps/web/src/views/plans.tsx @@ -57,6 +57,7 @@ function Plans() {