diff --git a/client/.env.development b/client/.env.development index 1e635d1f675..e8a77025464 100644 --- a/client/.env.development +++ b/client/.env.development @@ -7,3 +7,4 @@ PARSEC_APP_BMS_USE_MOCK="false" PARSEC_APP_BMS_MOCKED_FUNCTIONS="" PARSEC_APP_BMS_FAIL_FUNCTIONS="" PARSEC_APP_CREATE_DEFAULT_WORKSPACES="true" +PARSEC_APP_CLEAR_CACHE="true" diff --git a/client/README.md b/client/README.md index 4cbdff01069..1fc077e5502 100644 --- a/client/README.md +++ b/client/README.md @@ -126,3 +126,4 @@ ionic cap add ios | `PARSEC_APP_BMS_MOCKED_FUNCTIONS ` | `function1;function2;...` | Comma-separated list of functions from the BMS API to mock | Only for development purposes! | | `PARSEC_APP_BMS_FAIL_FUNCTIONS ` | `function1;function2;...` | Comma-separated list of functions from the BMS API that should fail if mocked | Only for development purposes! | | `PARSEC_APP_CREATE_DEFAULT_WORKSPACES` | `boolean` | Create default workspaces when initializing the app | Only for development purposes! | +| `PARSEC_APP_CLEAR_CACHE` | `boolean` | Clear the cache | Only for development purposes! | diff --git a/client/src/services/storageManager.ts b/client/src/services/storageManager.ts index ba03ae8a392..1dbcaf96029 100644 --- a/client/src/services/storageManager.ts +++ b/client/src/services/storageManager.ts @@ -191,6 +191,11 @@ export class StorageManager { async retrieveBmsAccess(): Promise { return await this.internalStore.get(StorageManager.STORED_BMS_ACCESS_KEY); } + + async clearAll(): Promise { + window.electronAPI.log('warn', 'Clearing all cache'); + await this.internalStore.clear(); + } } class StorageManagerInstance { diff --git a/client/src/views/layouts/DevLayout.vue b/client/src/views/layouts/DevLayout.vue index d954f850d91..a96247028ca 100644 --- a/client/src/views/layouts/DevLayout.vue +++ b/client/src/views/layouts/DevLayout.vue @@ -11,10 +11,22 @@ import { inject, onMounted, ref } from 'vue'; import { IonPage, IonRouterOutlet } from '@ionic/vue'; import { InjectionProvider, InjectionProviderKey } from '@/services/injectionProvider'; import { EventDistributor } from '@/services/eventDistributor'; -import { AccessStrategy, createWorkspace, getLoggedInDevices, listAvailableDevices, login } from '@/parsec'; +import { + AccessStrategy, + closeFile, + createWorkspace, + getLoggedInDevices, + listAvailableDevices, + login, + openFile, + startWorkspace, + writeFile, +} from '@/parsec'; import { getConnectionHandle, navigateTo, Routes } from '@/router'; +import { StorageManagerKey, StorageManager } from '@/services/storageManager'; const injectionProvider: InjectionProvider = inject(InjectionProviderKey)!; +const storageManager: StorageManager = inject(StorageManagerKey)!; const initialized = ref(false); const DEV_DEFAULT_HANDLE = 1; @@ -64,12 +76,48 @@ onMounted(async () => { window.electronAPI.log('info', `Logged in as ${device.humanHandle.label}`); } + if (import.meta.env.PARSEC_APP_CLEAR_CACHE === 'true') { + await storageManager.clearAll(); + } if (import.meta.env.PARSEC_APP_CREATE_DEFAULT_WORKSPACES === 'true') { - await createWorkspace('The Copper Coronet'); - await createWorkspace('Trademeet'); - await createWorkspace("Watcher's Keep"); + await populate(); } initialized.value = true; }); + +async function populate(): Promise { + // Avoid importing files if unnecessary + const mockFiles = (await import('@/parsec/mock_files')).MockFiles; + + window.electronAPI.log('debug', 'Creating mock workspaces and files'); + for (const workspaceName of ['The Copper Coronet', 'Trademeet', "Watcher's Keep"]) { + const wkResult = await createWorkspace(workspaceName); + if (!wkResult.ok) { + window.electronAPI.log('error', `Could not create dev workspace ${workspaceName}`); + continue; + } + const startWkResult = await startWorkspace(wkResult.value); + if (!startWkResult.ok) { + window.electronAPI.log('error', `Could not start dev workspace ${workspaceName}`); + continue; + } + for (const fileType in mockFiles) { + console.log(workspaceName, fileType); + const fileName = `document_${fileType}.${fileType.toLocaleLowerCase()}`; + const openResult = await openFile(startWkResult.value, `/${fileName}`, { write: true, truncate: true, create: true }); + + if (!openResult.ok) { + window.electronAPI.log('error', `Could not open file ${fileName}`); + continue; + } + const writeResult = await writeFile(startWkResult.value, openResult.value, 0, mockFiles[fileType as keyof typeof mockFiles]); + if (!writeResult.ok) { + window.electronAPI.log('error', `Failed to write file ${fileName}`); + continue; + } + await closeFile(startWkResult.value, openResult.value); + } + } +} diff --git a/client/tests/e2e/data/imports/audio.mp3 b/client/tests/e2e/data/imports/audio.mp3 new file mode 100644 index 00000000000..fa7474c3dca Binary files /dev/null and b/client/tests/e2e/data/imports/audio.mp3 differ diff --git a/client/tests/e2e/data/imports/code.py b/client/tests/e2e/data/imports/code.py new file mode 100644 index 00000000000..56f479500c5 --- /dev/null +++ b/client/tests/e2e/data/imports/code.py @@ -0,0 +1,31 @@ +# Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import argparse +import sys +import pathlib + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "-i", "--input", type=pathlib.Path, required=True, help="File to convert to Uint8Array" + ) + parser.add_argument("--name", type=str, required=True, help="Name of the variable") + parser.add_argument("-o", "--output", default=sys.stdout, help="Where to put the result") + args = parser.parse_args() + content = args.input.read_bytes() + array = ",".join([str(c) for c in content]) + result = f""" +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +/* +Generated automatically with {sys.argv[0]} +*/ + +export const {args.name} = new Uint8Array([{array}]); +""" + + if args.output is sys.stdout: + print(result) + else: + with open(args.output, "w+") as fd: + fd.write(result) diff --git a/client/tests/e2e/data/imports/document.docx b/client/tests/e2e/data/imports/document.docx new file mode 100644 index 00000000000..07ceaea2078 Binary files /dev/null and b/client/tests/e2e/data/imports/document.docx differ diff --git a/client/tests/e2e/data/imports/image.png b/client/tests/e2e/data/imports/image.png new file mode 100644 index 00000000000..0062f4d81d5 Binary files /dev/null and b/client/tests/e2e/data/imports/image.png differ diff --git a/client/tests/e2e/data/imports/pdfDocument.pdf b/client/tests/e2e/data/imports/pdfDocument.pdf new file mode 100644 index 00000000000..89e3cf12e11 Binary files /dev/null and b/client/tests/e2e/data/imports/pdfDocument.pdf differ diff --git a/client/tests/e2e/data/imports/spreadsheet.xlsx b/client/tests/e2e/data/imports/spreadsheet.xlsx new file mode 100644 index 00000000000..8e351794c47 Binary files /dev/null and b/client/tests/e2e/data/imports/spreadsheet.xlsx differ diff --git a/client/tests/e2e/data/imports/text.txt b/client/tests/e2e/data/imports/text.txt new file mode 100644 index 00000000000..59115050716 --- /dev/null +++ b/client/tests/e2e/data/imports/text.txt @@ -0,0 +1 @@ +A simple text file diff --git a/client/tests/e2e/data/imports/video.mp4 b/client/tests/e2e/data/imports/video.mp4 new file mode 100644 index 00000000000..f938acf784e Binary files /dev/null and b/client/tests/e2e/data/imports/video.mp4 differ diff --git a/client/tests/e2e/helpers/fixtures.ts b/client/tests/e2e/helpers/fixtures.ts index ba5aa98148f..6dd006bd72c 100644 --- a/client/tests/e2e/helpers/fixtures.ts +++ b/client/tests/e2e/helpers/fixtures.ts @@ -109,7 +109,18 @@ export const msTest = base.extend<{ await expect(workspaces.locator('.folder-container').locator('.no-files')).toBeVisible(); // Also create a folder here when available const dropZone = workspaces.locator('.folder-container').locator('.drop-zone').nth(0); - await dragAndDropFile(workspaces, dropZone, [path.join(testInfo.config.rootDir, 'data', 'imports', 'hell_yeah.png')]); + await dragAndDropFile(workspaces, dropZone, [ + path.join(testInfo.config.rootDir, 'data', 'imports', 'image.png'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'document.docx'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'pdfDocument.pdf'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'video.mp4'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'audio.mp3'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'spreadsheet.xlsx'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'text.txt'), + path.join(testInfo.config.rootDir, 'data', 'imports', 'code.py'), + ]); + // Hide the import menu + await workspaces.locator('.upload-menu').locator('.menu-header-icons').locator('ion-icon').nth(1).click(); use(workspaces); }, diff --git a/client/tests/e2e/specs/user_greet.spec.ts b/client/tests/e2e/specs/user_greet.spec.ts index 20e5dd5ca77..1a9aaf9237e 100644 --- a/client/tests/e2e/specs/user_greet.spec.ts +++ b/client/tests/e2e/specs/user_greet.spec.ts @@ -50,7 +50,7 @@ msTest('Greet user process', async ({ usersPage, secondTab }) => { await expect(greetModal).toHaveWizardStepper(['Host code', 'Guest code', 'Contact details'], 0); await expect(greetTitle).toHaveText('Share your code'); await expect(greetSubtitle).toHaveText('Give the code below to the guest.'); - const greetCode = await greetContent.locator('.host-code').locator('.code').textContent() ?? ''; + const greetCode = (await greetContent.locator('.host-code').locator('.code').textContent()) ?? ''; expect(greetCode).toMatch(/^[A-Z0-9]{4}$/); // Check the enter code page from the joiner and select the code @@ -62,7 +62,7 @@ msTest('Greet user process', async ({ usersPage, secondTab }) => { // Check the provide code page from the joiner and retrieve the code await expect(joinTitle).toHaveText('Share your code'); await expect(joinModal).toHaveWizardStepper(['Host code', 'Guest code', 'Contact details', 'Authentication'], 1); - const joinCode = await joinContent.locator('.guest-code').locator('.code').textContent() ?? ''; + const joinCode = (await joinContent.locator('.guest-code').locator('.code').textContent()) ?? ''; expect(joinCode).toMatch(/^[A-Z0-9]{4}$/); // Check the enter code page from the greeter and select the code @@ -82,9 +82,7 @@ msTest('Greet user process', async ({ usersPage, secondTab }) => { await expect(joinModal).toHaveWizardStepper(['Host code', 'Guest code', 'Contact details', 'Authentication'], 2); await expect(joinNextButton).toHaveDisabledAttribute(); await fillIonInput(joinModal.locator('#get-user-info').locator('ion-input').nth(0), 'Gordon Freeman'); - await expect(joinModal.locator('#get-user-info').locator('ion-input').nth(1).locator('input')).toHaveValue( - 'gordon.freeman@blackmesa.nm', - ); + await expect(joinModal.locator('#get-user-info').locator('ion-input').nth(1).locator('input')).toHaveValue('gordon.freeman@blackmesa.nm'); await joinNextButton.click(); await expect(joinNextButton).toBeHidden(); await expect(joinModal.locator('.spinner-container')).toBeVisible(); @@ -166,6 +164,7 @@ msTest('Greet user process', async ({ usersPage, secondTab }) => { // const choices = userGreetModal.locator('.modal-content').locator('.code:visible'); // await choices.nth(0).click(); +// eslint-disable-next-line max-len // await expect(userGreetModal.page()).toShowToast('You did not select the correct code. Please restart the onboarding process.', 'Error'); // await expect(title).toHaveText('Onboard a new user'); // });