mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
web: add tests for backups (#1542)
Signed-off-by: Muhammad Ali <alihamuh@gmail.com> Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
This commit is contained in:
74
apps/web/__e2e__/backups.test.ts
Normal file
74
apps/web/__e2e__/backups.test.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2022 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 { test, Browser, expect } from "@playwright/test";
|
||||||
|
import { AppModel } from "./models/app.model";
|
||||||
|
import { NOTE, USER } from "./utils";
|
||||||
|
|
||||||
|
test("create a backup", async ({ page }) => {
|
||||||
|
const app = new AppModel(page);
|
||||||
|
await app.goto();
|
||||||
|
const notes = await app.goToNotes();
|
||||||
|
await notes.createNote(NOTE);
|
||||||
|
const settings = await app.goToSettings();
|
||||||
|
const backup = await settings.createBackup();
|
||||||
|
expect(backup.length > 0).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.setTimeout(45 * 1000);
|
||||||
|
test("restore a backup", async ({ page }) => {
|
||||||
|
const app = new AppModel(page);
|
||||||
|
await app.goto();
|
||||||
|
|
||||||
|
const settings = await app.goToSettings();
|
||||||
|
await settings.restoreData("backup.nnbackup", USER.CURRENT.password);
|
||||||
|
const notes = await app.goToNotes();
|
||||||
|
expect(await notes.isEmpty()).toBeTruthy();
|
||||||
|
const notebooks = await app.goToNotebooks();
|
||||||
|
expect(await notebooks.isEmpty()).toBeTruthy();
|
||||||
|
const favotites = await app.goToFavorites();
|
||||||
|
expect(await favotites.isEmpty()).toBeTruthy();
|
||||||
|
const tags = await app.goToTags();
|
||||||
|
expect(await tags.isEmpty()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("create an encrypted backup", async ({ page }) => {
|
||||||
|
const app = new AppModel(page);
|
||||||
|
await app.auth.goto();
|
||||||
|
await app.auth.login(USER.CURRENT);
|
||||||
|
const settings = await app.goToSettings();
|
||||||
|
const backup = await settings.createBackup(USER.CURRENT.password);
|
||||||
|
|
||||||
|
expect(backup.length > 0).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore an encrypted backup", async ({ page }) => {
|
||||||
|
const app = new AppModel(page);
|
||||||
|
await app.goto();
|
||||||
|
|
||||||
|
const settings = await app.goToSettings();
|
||||||
|
await settings.restoreData("encrypted.nnbackup", USER.CURRENT.password);
|
||||||
|
const notes = await app.goToNotes();
|
||||||
|
expect(await notes.isEmpty()).toBeTruthy();
|
||||||
|
const notebooks = await app.goToNotebooks();
|
||||||
|
expect(await notebooks.isEmpty()).toBeTruthy();
|
||||||
|
const favotites = await app.goToFavorites();
|
||||||
|
expect(await favotites.isEmpty()).toBeTruthy();
|
||||||
|
const tags = await app.goToTags();
|
||||||
|
expect(await tags.isEmpty()).toBeTruthy();
|
||||||
|
});
|
||||||
1
apps/web/__e2e__/data/backup.nnbackup
Normal file
1
apps/web/__e2e__/data/backup.nnbackup
Normal file
File diff suppressed because one or more lines are too long
1
apps/web/__e2e__/data/encrypted.nnbackup
Normal file
1
apps/web/__e2e__/data/encrypted.nnbackup
Normal file
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Locator, Page } from "@playwright/test";
|
import { Locator, Page } from "@playwright/test";
|
||||||
import { getTestId } from "../utils";
|
import { downloadAndReadFile, getTestId, uploadFile } from "../utils";
|
||||||
import { confirmDialog, fillPasswordDialog, waitToHaveText } from "./utils";
|
import { confirmDialog, fillPasswordDialog, waitToHaveText } from "./utils";
|
||||||
|
|
||||||
export class SettingsViewModel {
|
export class SettingsViewModel {
|
||||||
@@ -26,6 +26,10 @@ export class SettingsViewModel {
|
|||||||
private readonly logoutButton: Locator;
|
private readonly logoutButton: Locator;
|
||||||
private readonly accountStatusContainer: Locator;
|
private readonly accountStatusContainer: Locator;
|
||||||
private readonly backupRecoveryKeyButton: Locator;
|
private readonly backupRecoveryKeyButton: Locator;
|
||||||
|
private readonly backupRestoreContainer: Locator;
|
||||||
|
private readonly backupData: Locator;
|
||||||
|
private readonly restoreBackup: Locator;
|
||||||
|
private readonly encyptBackups: Locator;
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
this.logoutButton = page.locator(getTestId("settings-logout"));
|
this.logoutButton = page.locator(getTestId("settings-logout"));
|
||||||
@@ -33,6 +37,10 @@ export class SettingsViewModel {
|
|||||||
this.backupRecoveryKeyButton = page.locator(
|
this.backupRecoveryKeyButton = page.locator(
|
||||||
getTestId("backup-recovery-key")
|
getTestId("backup-recovery-key")
|
||||||
);
|
);
|
||||||
|
this.backupRestoreContainer = page.locator(getTestId("backup-restore"));
|
||||||
|
this.backupData = page.locator(getTestId("backup-data"));
|
||||||
|
this.restoreBackup = page.locator(getTestId("restore-backup"));
|
||||||
|
this.encyptBackups = page.locator(getTestId("encrypt-backups"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
@@ -59,4 +67,19 @@ export class SettingsViewModel {
|
|||||||
async isLoggedIn() {
|
async isLoggedIn() {
|
||||||
return await this.accountStatusContainer.isVisible();
|
return await this.accountStatusContainer.isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createBackup(password?: string) {
|
||||||
|
await this.backupRestoreContainer.click();
|
||||||
|
if (password) await this.encyptBackups.click();
|
||||||
|
await this.backupData.click();
|
||||||
|
if (password) await fillPasswordDialog(this.page, password);
|
||||||
|
return await downloadAndReadFile(this.page, this.backupData, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreData(filename: string, password: string | undefined) {
|
||||||
|
await this.backupRestoreContainer.click();
|
||||||
|
await this.restoreBackup.click();
|
||||||
|
await uploadFile(this.page, this.restoreBackup, filename);
|
||||||
|
if (password) await fillPasswordDialog(this.page, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,15 @@ async function downloadAndReadFile(
|
|||||||
return fs.readFileSync(path, { encoding });
|
return fs.readFileSync(path, { encoding });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function uploadFile(page: Page, action: Locator, filename: string) {
|
||||||
|
const [fileChooser] = await Promise.all([
|
||||||
|
page.waitForEvent("filechooser"),
|
||||||
|
await action.click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
await fileChooser.setFiles(path.join(__dirname, "../data", filename));
|
||||||
|
}
|
||||||
|
|
||||||
function isTestAll() {
|
function isTestAll() {
|
||||||
return process.env.TEST_ALL === "true";
|
return process.env.TEST_ALL === "true";
|
||||||
}
|
}
|
||||||
@@ -135,6 +144,7 @@ export {
|
|||||||
createNote,
|
createNote,
|
||||||
editNote,
|
editNote,
|
||||||
downloadAndReadFile,
|
downloadAndReadFile,
|
||||||
|
uploadFile,
|
||||||
isTestAll,
|
isTestAll,
|
||||||
orderByOptions,
|
orderByOptions,
|
||||||
sortByOptions,
|
sortByOptions,
|
||||||
|
|||||||
@@ -25,7 +25,16 @@ import { Flex } from "@theme-ui/components";
|
|||||||
import Switch from "../switch";
|
import Switch from "../switch";
|
||||||
|
|
||||||
function Toggle(props) {
|
function Toggle(props) {
|
||||||
const { title, onTip, offTip, isToggled, onToggled, onlyIf, premium } = props;
|
const {
|
||||||
|
title,
|
||||||
|
onTip,
|
||||||
|
offTip,
|
||||||
|
isToggled,
|
||||||
|
onToggled,
|
||||||
|
onlyIf,
|
||||||
|
premium,
|
||||||
|
testId
|
||||||
|
} = props;
|
||||||
const onClick = useCallback(async () => {
|
const onClick = useCallback(async () => {
|
||||||
if (isUserPremium() || !premium || isToggled) onToggled();
|
if (isUserPremium() || !premium || isToggled) onToggled();
|
||||||
else {
|
else {
|
||||||
@@ -37,6 +46,7 @@ function Toggle(props) {
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
data-test-id={testId}
|
||||||
py={2}
|
py={2}
|
||||||
sx={{
|
sx={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import { appVersion } from "../utils/version";
|
|||||||
import { CHECK_IDS } from "@notesnook/core/common";
|
import { CHECK_IDS } from "@notesnook/core/common";
|
||||||
import Tip from "../components/tip";
|
import Tip from "../components/tip";
|
||||||
import Toggle from "../components/toggle";
|
import Toggle from "../components/toggle";
|
||||||
import { isDesktop, isMacStoreApp } from "../utils/platform";
|
import { isDesktop, isMacStoreApp, isTesting } from "../utils/platform";
|
||||||
import Vault from "../common/vault";
|
import Vault from "../common/vault";
|
||||||
import { isUserPremium } from "../hooks/use-is-user-premium";
|
import { isUserPremium } from "../hooks/use-is-user-premium";
|
||||||
import { Slider } from "@theme-ui/components";
|
import { Slider } from "@theme-ui/components";
|
||||||
@@ -529,6 +529,7 @@ function Settings() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Header
|
<Header
|
||||||
|
testId={"backup-restore"}
|
||||||
title="Backup & restore"
|
title="Backup & restore"
|
||||||
isOpen={groups.backup}
|
isOpen={groups.backup}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -539,6 +540,7 @@ function Settings() {
|
|||||||
{groups.backup && (
|
{groups.backup && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
data-test-id={"backup-data"}
|
||||||
variant="list"
|
variant="list"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!isUserPremium() && encryptBackups) toggleEncryptBackups();
|
if (!isUserPremium() && encryptBackups) toggleEncryptBackups();
|
||||||
@@ -571,9 +573,10 @@ function Settings() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isLoggedIn && (
|
{(isLoggedIn || isTesting()) && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
data-test-id="restore-backup"
|
||||||
variant="list"
|
variant="list"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await importBackup();
|
await importBackup();
|
||||||
@@ -587,6 +590,7 @@ function Settings() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Toggle
|
<Toggle
|
||||||
title="Encrypt backups"
|
title="Encrypt backups"
|
||||||
|
testId="encrypt-backups"
|
||||||
onTip="All backup files will be encrypted"
|
onTip="All backup files will be encrypted"
|
||||||
offTip="Backup files will not be encrypted"
|
offTip="Backup files will not be encrypted"
|
||||||
onToggled={toggleEncryptBackups}
|
onToggled={toggleEncryptBackups}
|
||||||
|
|||||||
Reference in New Issue
Block a user