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.
-
+