From 33b8190a2d2f569b5e7144acb86ee9b0abdad544 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sat, 13 Jan 2024 12:45:58 +0000 Subject: [PATCH] new home --- android/app/capacitor.build.gradle | 2 + android/capacitor.settings.gradle | 6 + e2e/encrypt.spec.ts | 43 +- e2e/fedimint.spec.ts | 71 +- e2e/load.spec.ts | 19 +- e2e/restore.spec.ts | 60 +- e2e/roundtrip.spec.ts | 16 +- e2e/routes.spec.ts | 57 +- e2e/utils.ts | 27 + index.html | 11 +- ios/App/Podfile | 2 + ios/App/Podfile.lock | 20 +- package.json | 50 +- pnpm-lock.yaml | 10326 ++++-------------- postcss.config.cjs | 7 + public/i18n/en.json | 14 +- public/mutiny-pixel-m-white.png | Bin 0 -> 267 bytes public/mutiny-pixel-m.png | Bin 0 -> 281 bytes src/App.tsx | 36 - src/assets/generic-avatar.jpg | Bin 0 -> 2376 bytes src/assets/icons/airplane.svg | 3 - src/assets/icons/back.svg | 3 - src/assets/icons/big-receive.svg | 3 - src/assets/icons/black-close.svg | 3 - src/assets/icons/bolt-black.svg | 3 - src/assets/icons/bolt.svg | 3 - src/assets/icons/chain-black.svg | 4 - src/assets/icons/chain.svg | 4 - src/assets/icons/check.svg | 3 - src/assets/icons/close.svg | 3 - src/assets/icons/coin.svg | 18 - src/assets/icons/community.svg | 4 - src/assets/icons/copy-black.svg | 3 - src/assets/icons/copy.svg | 3 - src/assets/icons/currency-swap.svg | 3 - src/assets/icons/down.svg | 5 - src/assets/icons/download-channel.svg | 3 - src/assets/icons/eye.svg | 3 - src/assets/icons/feedback.svg | 3 - src/assets/icons/forward.svg | 5 - src/assets/icons/gift.svg | 3 - src/assets/icons/green-check.svg | 3 - src/assets/icons/help.svg | 5 - src/assets/icons/info.svg | 3 - src/assets/icons/m.svg | 3 - src/assets/icons/paste.svg | 5 - src/assets/icons/pencil.svg | 3 - src/assets/icons/private-eye.svg | 3 - src/assets/icons/receive.svg | 3 - src/assets/icons/red-close.svg | 3 - src/assets/icons/refresh.svg | 3 - src/assets/icons/right-arrow.svg | 3 - src/assets/icons/rs.svg | 16 - src/assets/icons/save.svg | 3 - src/assets/icons/scan.svg | 3 - src/assets/icons/send.svg | 3 - src/assets/icons/settings.svg | 3 - src/assets/icons/share-black.svg | 3 - src/assets/icons/share.svg | 3 - src/assets/icons/shuffle-black.svg | 3 - src/assets/icons/shuffle.svg | 3 - src/assets/icons/side-to-side.svg | 5 - src/assets/icons/tinyArrow.svg | 3 - src/assets/icons/up-down.svg | 3 - src/assets/icons/upload-channel.svg | 3 - src/assets/icons/upload.svg | 3 - src/assets/icons/user-clock.svg | 10 - src/assets/icons/user.svg | 3 - src/assets/svg/Back.tsx | 18 - src/assets/svg/Paste.tsx | 24 - src/assets/svg/Scan.tsx | 18 - src/components/Activity.tsx | 278 +- src/components/ActivityDetailsModal.tsx | 74 +- src/components/ActivityItem.tsx | 172 - src/components/Amount.tsx | 10 +- src/components/BalanceBox.tsx | 91 +- src/components/BigMoney.tsx | 12 +- src/components/ContactButton.tsx | 28 + src/components/ContactViewer.tsx | 32 +- src/components/DeleteEverything.tsx | 3 + src/components/EditProfileForm.tsx | 88 + src/components/ErrorDisplay.tsx | 58 +- src/components/Fab.tsx | 171 + src/components/Fee.tsx | 2 +- src/components/GenericItem.tsx | 193 + src/components/GiftLink.tsx | 4 +- src/components/HomeBalance.tsx | 54 + src/components/HomeSubnav.tsx | 128 + src/components/IOSbanner.tsx | 4 +- src/components/InfoBox.tsx | 5 +- src/components/IntegratedQR.tsx | 93 +- src/components/LabelCircle.tsx | 59 +- src/components/LoadingIndicator.tsx | 12 +- src/components/MoreInfoModal.tsx | 11 +- src/components/MutinyPlusCta.tsx | 4 +- src/components/NavBar.tsx | 54 +- src/components/NostrActivity.tsx | 213 +- src/components/OnboardWarning.tsx | 37 - src/components/PendingNwc.tsx | 175 +- src/components/Reader.tsx | 2 +- src/components/ReceiveWarnings.tsx | 4 +- src/components/Reload.tsx | 7 +- src/components/SeedWords.tsx | 8 +- src/components/SetupErrorDisplay.tsx | 286 +- src/components/ShareCard.tsx | 12 +- src/components/SharpButton.tsx | 4 +- src/components/SimpleInput.tsx | 2 +- src/components/SocialActionRow.tsx | 52 + src/components/Toaster.tsx | 8 +- src/components/index.ts | 8 +- src/components/layout/BackButton.tsx | 20 - src/components/layout/BackLink.tsx | 24 +- src/components/layout/BackPop.tsx | 43 +- src/components/layout/Button.tsx | 17 +- src/components/layout/LoadingSpinner.tsx | 9 +- src/components/layout/Misc.tsx | 97 +- src/components/layout/index.ts | 1 - src/components/successfail/SuccessModal.tsx | 1 - src/index.tsx | 18 +- src/logic/mutinyWalletSetup.ts | 30 +- src/logic/waila.ts | 6 +- src/root.css | 51 +- src/router.tsx | 158 +- src/routes/Activity.tsx | 196 - src/routes/Chat.tsx | 605 + src/routes/EditProfile.tsx | 86 + src/routes/Feedback.tsx | 79 +- src/routes/Gift.tsx | 229 +- src/routes/ImportProfile.tsx | 85 + src/routes/Main.tsx | 179 +- src/routes/NewProfile.tsx | 71 + src/routes/Profile.tsx | 94 + src/routes/Receive.tsx | 6 +- src/routes/Request.tsx | 140 + src/routes/Scanner.tsx | 4 +- src/routes/Search.tsx | 222 +- src/routes/Send.tsx | 76 +- src/routes/Setup.tsx | 79 + src/routes/[...404].tsx | 20 +- src/routes/index.ts | 8 +- src/routes/settings/Admin.tsx | 38 +- src/routes/settings/Backup.tsx | 62 +- src/routes/settings/Channels.tsx | 24 +- src/routes/settings/Connections.tsx | 47 +- src/routes/settings/Currency.tsx | 28 +- src/routes/settings/EmergencyKit.tsx | 37 +- src/routes/settings/Encrypt.tsx | 186 +- src/routes/settings/Gift.tsx | 217 +- src/routes/settings/Language.tsx | 28 +- src/routes/settings/ManageFederations.tsx | 105 +- src/routes/settings/Plus.tsx | 114 +- src/routes/settings/Restore.tsx | 35 +- src/routes/settings/Root.tsx | 217 +- src/routes/settings/Servers.tsx | 17 +- src/routes/settings/SyncNostrContacts.tsx | 110 +- src/state/megaStore.tsx | 135 +- src/styles/dialogs.ts | 5 +- src/utils/blobToBase64.ts | 20 + src/utils/fetchZaps.ts | 92 +- src/utils/index.ts | 1 + src/utils/nostr.ts | 9 + src/utils/prettyPrintTime.ts | 27 + tailwind.config.cjs | 25 +- tsconfig.json | 4 +- vite.config.ts | 13 +- 165 files changed, 6736 insertions(+), 10991 deletions(-) create mode 100644 e2e/utils.ts create mode 100644 postcss.config.cjs create mode 100644 public/mutiny-pixel-m-white.png create mode 100644 public/mutiny-pixel-m.png delete mode 100644 src/App.tsx create mode 100644 src/assets/generic-avatar.jpg delete mode 100644 src/assets/icons/airplane.svg delete mode 100644 src/assets/icons/back.svg delete mode 100644 src/assets/icons/big-receive.svg delete mode 100644 src/assets/icons/black-close.svg delete mode 100644 src/assets/icons/bolt-black.svg delete mode 100644 src/assets/icons/bolt.svg delete mode 100644 src/assets/icons/chain-black.svg delete mode 100644 src/assets/icons/chain.svg delete mode 100644 src/assets/icons/check.svg delete mode 100644 src/assets/icons/close.svg delete mode 100644 src/assets/icons/coin.svg delete mode 100644 src/assets/icons/community.svg delete mode 100644 src/assets/icons/copy-black.svg delete mode 100644 src/assets/icons/copy.svg delete mode 100644 src/assets/icons/currency-swap.svg delete mode 100644 src/assets/icons/down.svg delete mode 100644 src/assets/icons/download-channel.svg delete mode 100644 src/assets/icons/eye.svg delete mode 100644 src/assets/icons/feedback.svg delete mode 100644 src/assets/icons/forward.svg delete mode 100644 src/assets/icons/gift.svg delete mode 100644 src/assets/icons/green-check.svg delete mode 100644 src/assets/icons/help.svg delete mode 100644 src/assets/icons/info.svg delete mode 100644 src/assets/icons/m.svg delete mode 100644 src/assets/icons/paste.svg delete mode 100644 src/assets/icons/pencil.svg delete mode 100644 src/assets/icons/private-eye.svg delete mode 100644 src/assets/icons/receive.svg delete mode 100644 src/assets/icons/red-close.svg delete mode 100644 src/assets/icons/refresh.svg delete mode 100644 src/assets/icons/right-arrow.svg delete mode 100644 src/assets/icons/rs.svg delete mode 100644 src/assets/icons/save.svg delete mode 100644 src/assets/icons/scan.svg delete mode 100644 src/assets/icons/send.svg delete mode 100644 src/assets/icons/settings.svg delete mode 100644 src/assets/icons/share-black.svg delete mode 100644 src/assets/icons/share.svg delete mode 100644 src/assets/icons/shuffle-black.svg delete mode 100644 src/assets/icons/shuffle.svg delete mode 100644 src/assets/icons/side-to-side.svg delete mode 100644 src/assets/icons/tinyArrow.svg delete mode 100644 src/assets/icons/up-down.svg delete mode 100644 src/assets/icons/upload-channel.svg delete mode 100644 src/assets/icons/upload.svg delete mode 100644 src/assets/icons/user-clock.svg delete mode 100644 src/assets/icons/user.svg delete mode 100644 src/assets/svg/Back.tsx delete mode 100644 src/assets/svg/Paste.tsx delete mode 100644 src/assets/svg/Scan.tsx delete mode 100644 src/components/ActivityItem.tsx create mode 100644 src/components/ContactButton.tsx create mode 100644 src/components/EditProfileForm.tsx create mode 100644 src/components/Fab.tsx create mode 100644 src/components/GenericItem.tsx create mode 100644 src/components/HomeBalance.tsx create mode 100644 src/components/HomeSubnav.tsx delete mode 100644 src/components/OnboardWarning.tsx create mode 100644 src/components/SocialActionRow.tsx delete mode 100644 src/components/layout/BackButton.tsx delete mode 100644 src/routes/Activity.tsx create mode 100644 src/routes/Chat.tsx create mode 100644 src/routes/EditProfile.tsx create mode 100644 src/routes/ImportProfile.tsx create mode 100644 src/routes/NewProfile.tsx create mode 100644 src/routes/Profile.tsx create mode 100644 src/routes/Request.tsx create mode 100644 src/routes/Setup.tsx create mode 100644 src/utils/blobToBase64.ts diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index c909025a..5b5832d4 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -12,12 +12,14 @@ dependencies { implementation project(':capacitor-mlkit-barcode-scanning') implementation project(':capacitor-app') implementation project(':capacitor-app-launcher') + implementation project(':capacitor-camera') implementation project(':capacitor-clipboard') implementation project(':capacitor-filesystem') implementation project(':capacitor-haptics') implementation project(':capacitor-share') implementation project(':capacitor-status-bar') implementation project(':capacitor-toast') + implementation project(':capacitor-secure-storage-plugin') } diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 8a2fdb13..ba78cf2e 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -11,6 +11,9 @@ project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacito include ':capacitor-app-launcher' project(':capacitor-app-launcher').projectDir = new File('../node_modules/.pnpm/@capacitor+app-launcher@5.0.6_@capacitor+core@5.5.1/node_modules/@capacitor/app-launcher/android') +include ':capacitor-camera' +project(':capacitor-camera').projectDir = new File('../node_modules/.pnpm/@capacitor+camera@5.0.9_@capacitor+core@5.5.1/node_modules/@capacitor/camera/android') + include ':capacitor-clipboard' project(':capacitor-clipboard').projectDir = new File('../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.1/node_modules/@capacitor/clipboard/android') @@ -28,3 +31,6 @@ project(':capacitor-status-bar').projectDir = new File('../node_modules/.pnpm/@c include ':capacitor-toast' project(':capacitor-toast').projectDir = new File('../node_modules/.pnpm/@capacitor+toast@5.0.6_@capacitor+core@5.5.1/node_modules/@capacitor/toast/android') + +include ':capacitor-secure-storage-plugin' +project(':capacitor-secure-storage-plugin').projectDir = new File('../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.1/node_modules/capacitor-secure-storage-plugin/android') diff --git a/e2e/encrypt.spec.ts b/e2e/encrypt.spec.ts index f0e897aa..81f6f61c 100644 --- a/e2e/encrypt.spec.ts +++ b/e2e/encrypt.spec.ts @@ -1,28 +1,14 @@ import { expect, test } from "@playwright/test"; +import { loadHome, visitSettings } from "./utils"; + test.beforeEach(async ({ page }) => { await page.goto("http://localhost:3420/"); }); test("test local encrypt", async ({ page }) => { - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Mutiny Wallet/); - - // Wait for an element matching the selector to appear in DOM. - await page.waitForSelector("text=0 SATS"); - - console.log("Page loaded."); - - // Wait for a while just to make sure we can load everything - await page.waitForTimeout(1000); - - // Navigate to settings - const settingsLink = await page.getByRole("link", { name: "Settings" }); - - settingsLink.click(); - - // Wait for settings to load - await page.waitForSelector("text=Settings"); + await loadHome(page); + await visitSettings(page); // Click the "Backup" link await page.click("text=Backup"); @@ -48,6 +34,13 @@ test("test local encrypt", async ({ page }) => { // Click the "I wrote down the words" button await wroteDownButton.click(); + // Make sure the balance box ready light is on + await page.locator("title=READY"); + + // Go back to settings / change password + await visitSettings(page); + await page.click("text=Change Password"); + // The header should now say "Encrypt your seed words" await expect(page.locator("h1")).toContainText(["Encrypt your seed words"]); @@ -56,7 +49,7 @@ test("test local encrypt", async ({ page }) => { const passwordInput = await page.locator(`input[name='password']`); // 2. Type the password into the input field - await passwordInput.type("test"); + await passwordInput.fill("test"); // 3. Find the input field with the name "confirmPassword" const confirmPasswordInput = await page.locator( @@ -64,15 +57,21 @@ test("test local encrypt", async ({ page }) => { ); // 4. Type the password into the input field - await confirmPasswordInput.type("test"); + await confirmPasswordInput.fill("test"); // The "Encrypt" button should not be disabled const encryptButton = await page.locator("button", { hasText: "Encrypt" }); await expect(encryptButton).not.toBeDisabled(); + // wait 5 seconds for no reason (SADLY THIS IS IMPORTANT FOR THE TEST TO PASS) + await page.waitForTimeout(5000); + // Click the "Encrypt" button await encryptButton.click(); + // wait for a while just to see what happens + // await page.waitForTimeout(10000); + // Wait for a modal with the text "Enter your password" await page.waitForSelector("text=Enter your password"); @@ -80,11 +79,11 @@ test("test local encrypt", async ({ page }) => { const passwordInput2 = await page.locator(`input[name='password']`); // Type the password into the input field - await passwordInput2.type("test"); + await passwordInput2.fill("test"); // Click the "Decrypt Wallet" button await page.click("text=Decrypt Wallet"); // Wait for an element matching the selector to appear in DOM. - await page.waitForSelector("text=0 SATS"); + await page.locator(`text=0 sats`).first(); }); diff --git a/e2e/fedimint.spec.ts b/e2e/fedimint.spec.ts index 6ba9d244..933326ff 100644 --- a/e2e/fedimint.spec.ts +++ b/e2e/fedimint.spec.ts @@ -1,5 +1,7 @@ import { expect, test } from "@playwright/test"; +import { loadHome, visitSettings } from "./utils"; + const SIGNET_INVITE_CODE = "fed11qgqzc2nhwden5te0vejkg6tdd9h8gepwvejkg6tdd9h8garhduhx6at5d9h8jmn9wshxxmmd9uqqzgxg6s3evnr6m9zdxr6hxkdkukexpcs3mn7mj3g5pc5dfh63l4tj6g9zk4er"; @@ -8,24 +10,8 @@ test.beforeEach(async ({ page }) => { }); test("fedmint join, receive, send", async ({ page }) => { - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Mutiny Wallet/); - - // Wait for an element matching the selector to appear in DOM. - await page.waitForSelector("text=0 SATS"); - - console.log("Page loaded."); - - // Wait for a while just to make sure we can load everything - await page.waitForTimeout(1000); - - // Navigate to settings - const settingsLink = await page.getByRole("link", { name: "Settings" }); - - settingsLink.click(); - - // Wait for settings to load - await page.waitForSelector("text=Settings"); + await loadHome(page); + await visitSettings(page); // Click "Manage Federations" link await page.click("text=Manage Federations"); @@ -45,11 +31,20 @@ test("fedmint join, receive, send", async ({ page }) => { await page.goBack(); await page.goBack(); - // Make sure there's a fedimint icon - await expect(page.getByRole("img", { name: "community" })).toBeVisible(); + // Click the top left button (it's the profile button), a child of header + // TODO: better ARIA stuff + await page.locator(`header button`).first().click(); - // Click the receive button - await page.click("text=Receive"); + // Make sure there's text that says "fedimint" + await page.locator("text=fedimint").first(); + + // Navigate back home + await page.goBack(); + + // Click the fab button + await page.locator("#fab").click(); + // Click the receive button in the fab + await page.locator("text=Receive").last().click(); // Expect the url to conain receive await expect(page).toHaveURL(/.*receive/); @@ -57,9 +52,6 @@ test("fedmint join, receive, send", async ({ page }) => { // At least one h1 should show "0 sats" await expect(page.locator("h1")).toContainText(["0 SATS"]); - // At least one h2 should show "0 USD" - await expect(page.locator("h2")).toContainText(["$0 USD"]); - // Type 100 into the input await page.locator("#sats-input").pressSequentially("100"); @@ -72,11 +64,7 @@ test("fedmint join, receive, send", async ({ page }) => { }); await expect(continueButton).not.toBeDisabled(); - // Wait one second - // TODO: figure out how to not get an error without waiting - await page.waitForTimeout(1000); - - continueButton.click(); + await continueButton.click(); await expect( page.getByText("Keep Mutiny open to complete the payment.") @@ -109,21 +97,17 @@ test("fedmint join, receive, send", async ({ page }) => { ); // Wait for an h1 to appear in the dom that says "Payment Received" - await page.waitForSelector("text=Payment Received", { timeout: 30000 }); + await page.waitForSelector("text=Payment Received"); // Click the "Nice" button await page.click("text=Nice"); - // Make sure we have 100 sats in the fedimint balance - await expect( - page - .locator("div") - .filter({ hasText: /^100 eSATS$/ }) - .nth(1) - ).toBeVisible(); + // Make sure we have 100 sats in the top balance + await page.waitForSelector("text=100 SATS"); // Now we send - await page.click("text=Send"); + await page.locator("#fab").click(); + await page.locator("text=Send").last().click(); // type refund@lnurl-staging.mutinywallet.com const sendInput = await page.locator("input"); @@ -131,9 +115,8 @@ test("fedmint join, receive, send", async ({ page }) => { await page.click("text=Continue"); - // Wait two seconds (the destination doesn't show up immediately) - // TODO: figure out how to not get an error without waiting - await page.waitForTimeout(2000); + // Wait for the destination to show up + await page.waitForSelector("text=LIGHTNING"); // Type 90 into the input await page.locator("#sats-input").fill("90"); @@ -147,8 +130,8 @@ test("fedmint join, receive, send", async ({ page }) => { }); await expect(confirmButton).not.toBeDisabled(); - confirmButton.click(); + await confirmButton.click(); // Wait for an h1 to appear in the dom that says "Payment Sent" - await page.waitForSelector("text=Payment Sent", { timeout: 30000 }); + await page.waitForSelector("text=Payment Sent"); }); diff --git a/e2e/load.spec.ts b/e2e/load.spec.ts index d41af63c..4774a688 100644 --- a/e2e/load.spec.ts +++ b/e2e/load.spec.ts @@ -1,22 +1,11 @@ -import { expect, test } from "@playwright/test"; +import { test } from "@playwright/test"; + +import { loadHome } from "./utils"; test.beforeEach(async ({ page }) => { await page.goto("http://localhost:3420/"); }); test("initial load", async ({ page }) => { - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Mutiny Wallet/); - - await expect(page.locator("header")).toContainText(["Activity"], { - timeout: 30000 - }); - - // Wait up to 30 seconds for an image element matching the selector to be visible - await page.waitForSelector("img[alt='lightning']", { timeout: 30000 }); - - // Wait for an element matching the selector to appear in DOM. - await page.waitForSelector("text=0 SATS"); - - console.log("Page loaded."); + await loadHome(page); }); diff --git a/e2e/restore.spec.ts b/e2e/restore.spec.ts index 651e6f2e..d054d046 100644 --- a/e2e/restore.spec.ts +++ b/e2e/restore.spec.ts @@ -1,49 +1,37 @@ import { expect, test } from "@playwright/test"; +import { visitSettings } from "./utils"; + test.beforeEach(async ({ page }) => { await page.goto("http://localhost:3420/"); }); test("restore from seed @slow", async ({ page }) => { - // should have 100k sats on-chain - const TEST_SEED_WORDS = - "rival hood review write spoon tide orange ill opera enrich clip acoustic"; - - // Expect a title "to contain" a substring. + // Start on the home page await expect(page).toHaveTitle(/Mutiny Wallet/); + await page.waitForSelector("text=Welcome to the Mutiny!"); - // Wait for an element matching the selector to appear in DOM. - await page.waitForSelector("text=0 SATS"); - - console.log("Page loaded."); - - // Wait for a while just to make sure we can load everything - await page.waitForTimeout(1000); - - // Navigate to settings - const settingsLink = await page.getByRole("link", { name: "Settings" }); - - settingsLink.click(); + console.log("Waiting for new wallet to be created..."); - // Wait for settings to load - await page.waitForSelector("text=Settings"); + await page.locator(`button:has-text('Import Existing')`).click(); - // Click the "Restore" link - page.click("text=Restore"); + // should have 100k sats on-chain + const TEST_SEED_WORDS = + "rival hood review write spoon tide orange ill opera enrich clip acoustic"; // There should be some warning text: "This will replace your existing wallet" await expect(page.locator("p")).toContainText([ "This will replace your existing wallet" ]); - let seedWords = TEST_SEED_WORDS.split(" "); + const seedWords = TEST_SEED_WORDS.split(" "); // Find the input field with the name "words.0" for (let i = 0; i < 12; i++) { const wordInput = await page.locator(`input[name='words.${i}']`); // Type the seed words into the input field - await wordInput.type(seedWords[i]); + await wordInput.fill(seedWords[i]); } // There should be a button with the text "Restore" and it should not be disabled @@ -54,33 +42,29 @@ test("restore from seed @slow", async ({ page }) => { // A modal should pop up, click the "Confirm" button const confirmButton = await page.locator("button", { hasText: "Confirm" }); - confirmButton.click(); - - // Wait for the wallet to load - await page.waitForSelector("img[alt='lightning']"); + await confirmButton.click(); // Eventually we should have a balance of 100k sats - await page.waitForSelector("text=100,000 SATS"); + await page.locator("text=100,000 SATS"); // Now we should clean up after ourselves and delete the wallet - settingsLink.click(); - - // Wait for settings to load - await page.waitForSelector("text=Settings"); + await visitSettings(page); // Click the "Restore" link - page.click("text=Admin Page"); + await page.click("text=Admin Page"); // Clicke the Delete Everything button - page.click("text=Delete Everything"); + await page.click("text=Delete Everything"); // A modal should pop up, click the "Confirm" button const confirmDeleteButton = await page.locator("button", { hasText: "Confirm" }); - confirmDeleteButton.click(); - // Wait for the wallet to load - // Wait for the wallet to load - await page.waitForSelector("img[alt='lightning']"); + // wait 5 seconds for no reason + await page.waitForTimeout(5000); + + await confirmDeleteButton.click(); + + await page.locator("text=Welcome to the Mutiny!"); }); diff --git a/e2e/roundtrip.spec.ts b/e2e/roundtrip.spec.ts index 0acefd5b..2450d334 100644 --- a/e2e/roundtrip.spec.ts +++ b/e2e/roundtrip.spec.ts @@ -1,21 +1,26 @@ import { expect, test } from "@playwright/test"; +import { loadHome } from "./utils"; + test.beforeEach(async ({ page }) => { await page.goto("http://localhost:3420/"); }); test("rountrip receive and send", async ({ page }) => { - // Click the receive button - await page.click("text=Receive"); + await loadHome(page); + + await page.locator("#fab").click(); + await page.locator("text=Receive").last().click(); - // Expect the url to conain receive + // Expect the url to contain receive await expect(page).toHaveURL(/.*receive/); // At least one h1 should show "0 sats" await expect(page.locator("h1")).toContainText(["0 SATS"]); // At least one h2 should show "0 USD" - await expect(page.locator("h2")).toContainText(["$0 USD"]); + // await expect(page.locator("h2")).toContainText(["$0 USD"]); + await page.waitForSelector("text=$0 USD"); // Type 100000 into the input await page.locator("#sats-input").pressSequentially("100000"); @@ -72,7 +77,8 @@ test("rountrip receive and send", async ({ page }) => { await page.click("text=Nice"); // Now we send - await page.click("text=Send"); + await page.locator("#fab").click(); + await page.locator("text=Send").click(); // In the textarea with the placeholder "bitcoin:..." type refund@lnurl-staging.mutinywallet.com const sendInput = await page.locator("input"); diff --git a/e2e/routes.spec.ts b/e2e/routes.spec.ts index 97234753..a99293b3 100644 --- a/e2e/routes.spec.ts +++ b/e2e/routes.spec.ts @@ -1,12 +1,14 @@ import { expect, Page, test } from "@playwright/test"; +import { loadHome, visitSettings } from "./utils"; + const routes = [ "/", - "/activity", "/feedback", "/gift", "/receive", "/scanner", + "/search", "/send", "/swap", "/settings" @@ -57,25 +59,13 @@ test.beforeEach(async ({ page }) => { }); test("visit each route", async ({ page }) => { - // Start on the home page - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Mutiny Wallet/); - - // Wait for an element matching the selector to appear in DOM. - await page.waitForSelector("text=0 SATS"); - - console.log("Page loaded."); - - // Wait for a while just to make sure we can load everything - await page.waitForTimeout(1000); + await loadHome(page); checklist.set("/", true); - await checkRoute(page, "/activity", "Activity", checklist); - await page.goBack(); + await visitSettings(page); - // Navigate to settings - await checkRoute(page, "/settings", "Settings", checklist); + checklist.set("/settings", true); // Mutiny+ await checkRoute(page, "/settings/plus", "Mutiny+", checklist); @@ -146,22 +136,43 @@ test("visit each route", async ({ page }) => { await checkRoute(page, "/settings/admin", "Secret Debug Tools", checklist); await page.goBack(); - // Go back home - await page.goBack(); - // Feedback await checkRoute(page, "/feedback", "Give us feedback!", checklist); await page.goBack(); - // Receive is covered in another test - checklist.set("/receive", true); + // Go back home + await page.goBack(); + + // Try the fab button + await page.locator("#fab").click(); + await page.locator("text=Send").click(); + await expect(page.locator("input").first()).toBeFocused(); // Send is covered in another test checklist.set("/send", true); + await page.goBack(); + + // Try the fab button again + await page.locator("#fab").click(); + // (There are actually two buttons with the "Receive text on first run) + await page.locator("text=Receive").last().click(); + + await expect(page.locator("h1").first()).toHaveText("Receive Bitcoin"); + + // Actual receive is covered in another test + checklist.set("/receive", true); + + await page.goBack(); + + // Try the fab button again + await page.locator("#fab").click(); + await page.locator("text=Scan").click(); + // Scanner - await page.locator(`a[href='/scanner']`).first().click(); - await expect(page.locator("button").first()).toHaveText("Paste Something"); + await expect( + page.locator("button:has-text('Paste Something')") + ).toBeVisible(); checklist.set("/scanner", true); // Now we have to check routes that aren't linked to directly for whatever reason diff --git a/e2e/utils.ts b/e2e/utils.ts new file mode 100644 index 00000000..d4cafd33 --- /dev/null +++ b/e2e/utils.ts @@ -0,0 +1,27 @@ +import { expect, Page } from "@playwright/test"; + +export async function loadHome(page: Page) { + // Start on the home page + await expect(page).toHaveTitle(/Mutiny Wallet/); + await page.waitForSelector("text=Welcome to the Mutiny!"); + + console.log("Waiting for new wallet to be created..."); + + await page.locator(`button:has-text('New Wallet')`).click(); + + await page.locator("text=Create your profile").first(); + + await page.locator("button:has-text('Skip for now')").click(); + + // Should have a balance up top now + await page.locator(`text=0 sats`).first(); + // Status light should be ready + await page.locator(`title="READY"`).first(); +} + +export async function visitSettings(page: Page) { + // Find an image with an alt text of "mutiny" and click it + // TODO: probably should have better ARIA stuff for this + await page.locator("img[alt='mutiny']").first().click(); + await expect(page.locator("h1").first()).toHaveText("Settings"); +} diff --git a/index.html b/index.html index 3c0d91be..5be48c9f 100644 --- a/index.html +++ b/index.html @@ -47,6 +47,9 @@ #no-script { margin: 1rem; } + body { + background-color: hsla(0, 0%, 5%, 1); + } @@ -63,8 +66,10 @@ Please update or enable WebAssembly to run this app.

- If you're running iOS in lockdown mode you'll need to add an - exception for Mutiny Wallet. + If you're running iOS in lockdown mode you'll need to + add an exception for Mutiny Wallet.

-
+