From 2062b7e42d2455ea3678075d2530dd448ea7b6a2 Mon Sep 17 00:00:00 2001
From: Megha <100185149+Megha-Dev-19@users.noreply.github.com>
Date: Tue, 7 Jan 2025 12:48:25 +0530
Subject: [PATCH] Added low and insufficient balance UI (#200)
Resovles #180
The tests passes locally
[video.webm](https://github.com/user-attachments/assets/40b185bc-403c-4cb0-8fd8-c6455e6a50d4)
[video.webm](https://github.com/user-attachments/assets/c9d6fa00-4587-4afe-9d09-5d5e75099f77)
[video.webm](https://github.com/user-attachments/assets/417c9ed6-5220-4d64-8483-ee3b3b35cc6b)
[video.webm](https://github.com/user-attachments/assets/1c2a0de4-55bf-43aa-94f8-ef7fba383aa8)
[video.webm](https://github.com/user-attachments/assets/cf62a42f-386f-4b8e-9842-f8120339359a)
[video.webm](https://github.com/user-attachments/assets/2a82a1c9-9946-41bf-a363-51651391d5e8)
---
instances/treasury-devdao.near/widget/app.jsx | 7 +-
.../widget/components/BalanceBanner.jsx | 88 +++++++++++++++++++
.../components/InsufficientBannerModal.jsx | 74 ++++++++++++++++
.../widget/components/VoteActions.jsx | 85 ++++++++++++------
.../widget/components/templates/AppLayout.jsx | 11 ++-
.../widget/lib/common.jsx | 6 +-
.../treasury-devdao.near/widget/lib/modal.jsx | 2 +-
.../widget/pages/payments/index.jsx | 19 ++--
.../widget/pages/settings/MembersPage.jsx | 59 +++++++++----
.../widget/pages/settings/Theme.jsx | 22 +++--
.../widget/pages/settings/Thresholds.jsx | 31 ++++---
.../pages/settings/VotingDurationPage.jsx | 32 ++++---
.../pages/stake-delegation/CreateButton.jsx | 24 +++--
.../treasury-infinex.near/widget/app.jsx | 7 +-
.../treasury-templar.near/widget/app.jsx | 7 +-
.../widget/app.jsx | 7 +-
.../treasury-testing.near/widget/app.jsx | 7 +-
.../payments/create-payment-request.spec.js | 70 ++++++++++++++-
.../tests/payments/vote-on-request.spec.js | 43 ++++++++-
.../settings/create-member-request.spec.js | 39 ++++++--
.../settings/create-threshold-request.spec.js | 66 +++++++++++---
playwright-tests/tests/settings/theme.spec.js | 47 ++++++++--
.../tests/settings/voting-duration.spec.js | 38 +++++++-
.../stake-delegation/stake-delegation.spec.js | 81 ++++++++++-------
playwright-tests/util/lib.js | 2 +
playwright-tests/util/rpcmock.js | 21 +++++
26 files changed, 732 insertions(+), 163 deletions(-)
create mode 100644 instances/treasury-devdao.near/widget/components/BalanceBanner.jsx
create mode 100644 instances/treasury-devdao.near/widget/components/InsufficientBannerModal.jsx
diff --git a/instances/treasury-devdao.near/widget/app.jsx b/instances/treasury-devdao.near/widget/app.jsx
index edcfc545..d2fcc990 100644
--- a/instances/treasury-devdao.near/widget/app.jsx
+++ b/instances/treasury-devdao.near/widget/app.jsx
@@ -85,7 +85,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-devdao.near/widget/components/BalanceBanner.jsx b/instances/treasury-devdao.near/widget/components/BalanceBanner.jsx
new file mode 100644
index 00000000..0a9b2c35
--- /dev/null
+++ b/instances/treasury-devdao.near/widget/components/BalanceBanner.jsx
@@ -0,0 +1,88 @@
+const { getNearBalances } = VM.require(
+ "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
+);
+
+const Container = styled.div`
+ .low-balance-warning {
+ background: rgba(255, 158, 0, 0.1);
+ color: var(--other-warning);
+ padding-inline: 0.8rem;
+ padding-block: 0.5rem;
+ font-weight: 500;
+ font-size: 13px;
+
+ h5 {
+ color: var(--other-warning) !important;
+ }
+ }
+
+ .insufficient-balance-warning {
+ background-color: rgba(217, 92, 74, 0.08);
+ color: var(--other-red);
+ padding-inline: 0.8rem;
+ padding-block: 0.5rem;
+ font-weight: 500;
+ font-size: 13px;
+
+ h5 {
+ color: var(--other-red) !important;
+ }
+ }
+`;
+
+function formatNearAmount(amount) {
+ return parseFloat(
+ Big(amount ?? "0")
+ .div(Big(10).pow(24))
+ .toFixed(2)
+ );
+}
+
+function BalanceBanner({ accountId, treasuryDaoID }) {
+ if (!treasuryDaoID || typeof getNearBalances !== "function" || !accountId) {
+ return <>>;
+ }
+
+ const nearBalances = getNearBalances(accountId);
+
+ const LOW_BALANCE_LIMIT = 0.7; // 0.7N
+ const INSUFFICIENT_BALANCE_LIMIT = 0.1; // 0.1N
+
+ const profile = Social.getr(`${accountId}/profile`);
+ const name = profile.name ?? accountId;
+
+ return (
+
+ {!nearBalances ||
+ !nearBalances.availableParsed ||
+ nearBalances.availableParsed === "0.00" ||
+ parseFloat(nearBalances.availableParsed) > LOW_BALANCE_LIMIT ? (
+ <>>
+ ) : parseFloat(nearBalances.availableParsed) <
+ INSUFFICIENT_BALANCE_LIMIT ? (
+
+
+
+
Insufficient Funds
+ Hey {name}, you don't have enough NEAR to complete actions on your
+ treasury. You need at least {INSUFFICIENT_BALANCE_LIMIT}N. Please
+ add more funds to your account and try again
+
+
+ ) : (
+
+
+
+
Low Balance
+ Hey {name}, your NEAR balance is {nearBalances.availableParsed}N,
+ which is getting low. The minimum balance required is{" "}
+ {INSUFFICIENT_BALANCE_LIMIT}N. Please add more NEAR to your account
+ soon to avoid any issues completing actions on your treasury.
+
+
+ )}
+
+ );
+}
+
+return { BalanceBanner };
diff --git a/instances/treasury-devdao.near/widget/components/InsufficientBannerModal.jsx b/instances/treasury-devdao.near/widget/components/InsufficientBannerModal.jsx
new file mode 100644
index 00000000..a604b2b8
--- /dev/null
+++ b/instances/treasury-devdao.near/widget/components/InsufficientBannerModal.jsx
@@ -0,0 +1,74 @@
+const { getNearBalances } = VM.require(
+ "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.common"
+);
+
+const { Modal, ModalContent, ModalHeader, ModalFooter } = VM.require(
+ "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.modal"
+);
+
+function formatNearAmount(amount) {
+ return parseFloat(
+ Big(amount ?? "0")
+ .div(Big(10).pow(24))
+ .toFixed(2)
+ );
+}
+
+const { ActionButton, checkForDeposit, treasuryDaoID, callbackAction } = props;
+
+const nearBalances = getNearBalances(context.accountId);
+const profile = Social.getr(`${context.accountId}/profile`);
+const name = profile.name ?? context.accountId;
+const daoPolicy = useCache(
+ () => Near.asyncView(treasuryDaoID, "get_policy"),
+ "get_policy",
+ { subscribe: false }
+);
+
+const [showModal, setShowModal] = useState(false);
+const ADDITIONAL_AMOUNT = checkForDeposit
+ ? formatNearAmount(daoPolicy?.proposal_bond)
+ : 0;
+
+const INSUFFICIENT_BALANCE_LIMIT = ADDITIONAL_AMOUNT + 0.1; // 0.1N
+
+function checkBalance() {
+ if (parseFloat(nearBalances?.availableParsed) < INSUFFICIENT_BALANCE_LIMIT) {
+ setShowModal(true);
+ } else {
+ callbackAction();
+ }
+}
+
+const WarningModal = () => (
+
+
+
+
+
+ Insufficient Funds
+
+
setShowModal(false)}
+ >
+
+
+
+ Hey {name}, you don't have enough NEAR to complete actions on your
+ treasury. You need at least {INSUFFICIENT_BALANCE_LIMIT}N{" "}
+ {checkForDeposit &&
+ ", which includes the proposal bond needed to create a proposal"}
+ . Please add more funds to your account and try again.
+
+
+);
+
+return (
+ <>
+
+ {showModal && }
+ >
+);
diff --git a/instances/treasury-devdao.near/widget/components/VoteActions.jsx b/instances/treasury-devdao.near/widget/components/VoteActions.jsx
index 1f35290c..e0afd73a 100644
--- a/instances/treasury-devdao.near/widget/components/VoteActions.jsx
+++ b/instances/treasury-devdao.near/widget/components/VoteActions.jsx
@@ -229,13 +229,24 @@ return (
hasVotingPermission && (
{
+ ActionButton: () => (
+
+ ),
+ checkForDeposit: false,
+ treasuryDaoID,
+ callbackAction: () => {
if (isInsufficientBalance) {
setShowWarning(true);
} else {
@@ -243,23 +254,30 @@ return (
setConfirmModal(true);
}
},
- loading: isTxnCreated && vote === actions.APPROVE,
- disabled: isTxnCreated,
}}
/>
{
+ ActionButton: () => (
+
+ ),
+ checkForDeposit: false,
+ treasuryDaoID,
+ callbackAction: () => {
setVote(actions.REJECT);
setConfirmModal(true);
},
- loading: isTxnCreated && vote === actions.REJECT,
- disabled: isTxnCreated,
}}
/>
@@ -267,20 +285,29 @@ return (
)}
{/* currently showing delete btn only for proposal creator */}
{hasDeletePermission && proposalCreator === accountId && (
- {
- setVote(actions.REMOVE);
- setConfirmModal(true);
+ (
+
+
+
+ ),
+ checkForDeposit: false,
+ treasuryDaoID,
+ callbackAction: () => {
+ setVote(actions.REMOVE);
+ setConfirmModal(true);
+ },
}}
- data-testid="delete-btn"
- disabled={isTxnCreated}
- >
-
-
+ />
)}
)}
diff --git a/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx b/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx
index b82075fc..53df5cd8 100644
--- a/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx
+++ b/instances/treasury-devdao.near/widget/components/templates/AppLayout.jsx
@@ -1,3 +1,7 @@
+const { BalanceBanner } = VM.require(
+ `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.BalanceBanner`
+) || { BalanceBanner: () => <>> };
+
const AppHeader = ({ page, instance }) => (
(
/>
);
-function AppLayout({ page, instance, children, treasuryDaoID }) {
+function AppLayout({ page, instance, children, treasuryDaoID, accountId }) {
const config = treasuryDaoID
? useCache(
() => Near.asyncView(treasuryDaoID, "get_config"),
@@ -379,6 +383,10 @@ function AppLayout({ page, instance, children, treasuryDaoID }) {
.warning-icon {
color: var(--other-warning) !important;
}
+
+ .error-icon {
+ color: var(--other-red) !important;
+ }
`;
return !config ? (
@@ -387,6 +395,7 @@ function AppLayout({ page, instance, children, treasuryDaoID }) {
+
{children}
diff --git a/instances/treasury-devdao.near/widget/lib/common.jsx b/instances/treasury-devdao.near/widget/lib/common.jsx
index d9d942ef..3c650c45 100644
--- a/instances/treasury-devdao.near/widget/lib/common.jsx
+++ b/instances/treasury-devdao.near/widget/lib/common.jsx
@@ -426,10 +426,8 @@ function formatNearAmount(amount) {
.toFixed(2);
}
-function getNearBalances(treasuryDaoID) {
- const resp = fetch(
- `https://api.fastnear.com/v1/account/${treasuryDaoID}/full`
- );
+function getNearBalances(accountId) {
+ const resp = fetch(`https://api.fastnear.com/v1/account/${accountId}/full`);
const storage = Big(resp?.body?.state?.storage_bytes ?? "0")
.mul(Big(10).pow(19))
.toFixed();
diff --git a/instances/treasury-devdao.near/widget/lib/modal.jsx b/instances/treasury-devdao.near/widget/lib/modal.jsx
index 07abd2c5..36921f0b 100644
--- a/instances/treasury-devdao.near/widget/lib/modal.jsx
+++ b/instances/treasury-devdao.near/widget/lib/modal.jsx
@@ -95,7 +95,7 @@ const NoButton = styled.button`
const ModalHeader = ({ children }) => (
- {children}
+ {children}
);
diff --git a/instances/treasury-devdao.near/widget/pages/payments/index.jsx b/instances/treasury-devdao.near/widget/pages/payments/index.jsx
index 1a26c592..e1583adb 100644
--- a/instances/treasury-devdao.near/widget/pages/payments/index.jsx
+++ b/instances/treasury-devdao.near/widget/pages/payments/index.jsx
@@ -28,12 +28,19 @@ const SidebarMenu = ({ currentTab }) => {
style={{ paddingBottom: "7px" }}
>
{hasCreatePermission && (
- setShowCreateRequest(true)}
- >
- Create Request
-
+ (
+
+ Create Request
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: () => setShowCreateRequest(true),
+ }}
+ />
)}
{
{hasCreatePermission && (
- {
- setSelectedMember(group);
- setShowEditor(true);
+ (
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: () => {
+ setSelectedMember(group);
+ setShowEditor(true);
+ },
}}
- >
- {
- setSelectedMember(group);
- setShowDeleteModal(true);
+ />
+ (
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: () => {
+ setSelectedMember(group);
+ setShowDeleteModal(true);
+ },
}}
- >
+ />
)}
@@ -310,12 +324,21 @@ return (
All Members
{hasCreatePermission && (
-
setShowEditor(true)}
- >
- New Member
-
+
(
+
+ New Member
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: () => {
+ setShowEditor(true);
+ },
+ }}
+ />
)}
{loading ? (
diff --git a/instances/treasury-devdao.near/widget/pages/settings/Theme.jsx b/instances/treasury-devdao.near/widget/pages/settings/Theme.jsx
index 3c6c1a2d..7c2472e6 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/Theme.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/Theme.jsx
@@ -444,15 +444,23 @@ return (
disabled: !hasCreatePermission,
}}
/>
-
(
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: onSubmitClick,
}}
/>
diff --git a/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx b/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx
index 1c52eafc..bcdc71df 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/Thresholds.jsx
@@ -481,17 +481,28 @@ return (
}}
/>
setConfirmModal(true),
- loading: isTxnCreated,
+ ActionButton: () => (
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: () => {
+ setConfirmModal(true);
+ },
}}
/>
diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
index 5dc25d02..fe06776d 100644
--- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
+++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx
@@ -475,18 +475,28 @@ return (
}}
/>
(
+
+ ),
+ checkForDeposit: true,
+ treasuryDaoID,
+ callbackAction: submitChangeRequest,
}}
/>
diff --git a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx
index f9822abc..9c101231 100644
--- a/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx
+++ b/instances/treasury-devdao.near/widget/pages/stake-delegation/CreateButton.jsx
@@ -52,6 +52,10 @@ function toggleWithdrawPage() {
setShowWithdrawRequest((prev) => !prev);
}
+const toggleDropdown = () => {
+ setCreateBtnOpen((prev) => !prev);
+};
+
const CreateBtn = () => {
const btnOptions = [
{
@@ -85,10 +89,6 @@ const CreateBtn = () => {
},
];
- const toggleDropdown = () => {
- setCreateBtnOpen((prev) => !prev);
- };
-
const DropdowntBtnContainer = styled.div`
font-size: 13px;
min-width: 150px;
@@ -129,7 +129,7 @@ const CreateBtn = () => {
display: block;
opacity: 1;
transform: translateY(0);
- z-index:9999;
+ z-index:1000;
}
}
@@ -199,14 +199,12 @@ const CreateBtn = () => {
Create Request
- {hasCreatePermission && }
+ {hasCreatePermission && (
+
+ )}
-
+
diff --git a/instances/treasury-templar.near/widget/app.jsx b/instances/treasury-templar.near/widget/app.jsx
index edcfc545..d2fcc990 100644
--- a/instances/treasury-templar.near/widget/app.jsx
+++ b/instances/treasury-templar.near/widget/app.jsx
@@ -85,7 +85,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-testing-infinex.near/widget/app.jsx b/instances/treasury-testing-infinex.near/widget/app.jsx
index 3aca2e8b..4859aafb 100644
--- a/instances/treasury-testing-infinex.near/widget/app.jsx
+++ b/instances/treasury-testing-infinex.near/widget/app.jsx
@@ -96,7 +96,12 @@ function Page() {
return (
-
+
diff --git a/instances/treasury-testing.near/widget/app.jsx b/instances/treasury-testing.near/widget/app.jsx
index edcfc545..d2fcc990 100644
--- a/instances/treasury-testing.near/widget/app.jsx
+++ b/instances/treasury-testing.near/widget/app.jsx
@@ -85,7 +85,12 @@ function Page() {
return (
-
+
diff --git a/playwright-tests/tests/payments/create-payment-request.spec.js b/playwright-tests/tests/payments/create-payment-request.spec.js
index 1826f614..08317a33 100644
--- a/playwright-tests/tests/payments/create-payment-request.spec.js
+++ b/playwright-tests/tests/payments/create-payment-request.spec.js
@@ -5,7 +5,7 @@ import {
getTransactionModalObject,
mockTransactionSubmitRPCResponses,
} from "../../util/transaction";
-import { updateDaoPolicyMembers } from "../../util/rpcmock";
+import { mockNearBalances, updateDaoPolicyMembers } from "../../util/rpcmock";
import { getInstanceConfig } from "../../util/config.js";
import {
CurrentTimestampInNanoseconds,
@@ -18,6 +18,7 @@ import {
focusInputClearAndBlur,
focusInputReplaceAndBlur,
} from "../../util/forms.js";
+import { InsufficientBalance } from "../../util/lib.js";
async function clickCreatePaymentRequestButton(page) {
const createPaymentRequestButton = await page.getByRole("button", {
@@ -120,7 +121,23 @@ test.afterEach(async ({ page }, testInfo) => {
await page.unrouteAll({ behavior: "ignoreErrors" });
});
-test.describe("admin connected", function () {
+test.describe("User is not logged in", function () {
+ test("should not see 'Create Request' action", async ({
+ page,
+ instanceAccount,
+ }) => {
+ await page.goto(`/${instanceAccount}/widget/app?page=payments`);
+ await expect(page.getByText("Pending Requests")).toBeVisible();
+ await expect(
+ page.getByRole("button", {
+ name: "Create Request",
+ })
+ ).toBeHidden();
+ });
+});
+
+test.describe("User is logged in", function () {
+ const signedUser = "theori.near";
test.use({
contextOptions: {
permissions: ["clipboard-read", "clipboard-write"],
@@ -128,6 +145,55 @@ test.describe("admin connected", function () {
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
+ test("low account balance should show warning modal, and allow action ", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await mockNearBalances({
+ page,
+ accountId: signedUser,
+ balance: BigInt(0.6 * 10 ** 24).toString(),
+ storage: 8,
+ });
+ await page.goto(`/${instanceAccount}/widget/app?page=payments`);
+ await expect(
+ page.getByText(
+ "Please add more NEAR to your account soon to avoid any issues completing actions on your treasury"
+ )
+ ).toBeVisible();
+ });
+
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await updateDaoPolicyMembers({ page });
+ await mockNearBalances({
+ page,
+ accountId: signedUser,
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ await page.goto(`/${instanceAccount}/widget/app?page=payments`);
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page
+ .getByRole("button", {
+ name: "Create Request",
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("different amount values should not throw any error", async ({
page,
instanceAccount,
diff --git a/playwright-tests/tests/payments/vote-on-request.spec.js b/playwright-tests/tests/payments/vote-on-request.spec.js
index 9304f174..77a649ff 100644
--- a/playwright-tests/tests/payments/vote-on-request.spec.js
+++ b/playwright-tests/tests/payments/vote-on-request.spec.js
@@ -2,13 +2,18 @@ import { expect } from "@playwright/test";
import { test } from "../../util/test.js";
import { mockTransactionSubmitRPCResponses } from "../../util/transaction";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock";
import { setDontAskAgainCacheValues } from "../../util/cache";
import { mockPikespeakFTTokensResponse } from "../../util/pikespeak.js";
import {
CurrentTimestampInNanoseconds,
TransferProposalData,
} from "../../util/inventory.js";
+import { InsufficientBalance } from "../../util/lib.js";
async function mockWithFTBalance({ page, daoAccount, isSufficient }) {
await page.route(
@@ -153,7 +158,41 @@ test.describe("don't ask again", function () {
storageState:
"playwright-tests/storage-states/wallet-connected-admin-with-accesskey.json",
});
- test("should throw insufficient balance error", async ({
+
+ test("should throw insufficient signed in account balance error", async ({
+ page,
+ instanceAccount,
+ daoAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await updateDaoPolicyMembers({ page });
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ await voteOnProposal({
+ page,
+ daoAccount,
+ instanceAccount,
+ voteStatus: "Approved",
+ vote: "Approve",
+ });
+ const approveButton = page
+ .getByRole("button", {
+ name: "Approve",
+ })
+ .first();
+ await expect(approveButton).toBeEnabled({ timeout: 30_000 });
+ await approveButton.click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+ test("should throw insufficient dao account balance error", async ({
page,
instanceAccount,
daoAccount,
diff --git a/playwright-tests/tests/settings/create-member-request.spec.js b/playwright-tests/tests/settings/create-member-request.spec.js
index 7b00f4e5..e2256b87 100644
--- a/playwright-tests/tests/settings/create-member-request.spec.js
+++ b/playwright-tests/tests/settings/create-member-request.spec.js
@@ -5,9 +5,13 @@ import {
getTransactionModalObject,
mockTransactionSubmitRPCResponses,
} from "../../util/transaction.js";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock.js";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock.js";
import { mockInventory } from "../../util/inventory.js";
-import { encodeToMarkdown } from "../../util/lib.js";
+import { InsufficientBalance, encodeToMarkdown } from "../../util/lib.js";
const lastProposalId = 3;
@@ -32,7 +36,7 @@ async function updateLastProposalId(page) {
async function navigateToMembersPage({ page, instanceAccount }) {
await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.waitForTimeout(2_000);
+ await page.waitForTimeout(5_000);
await page.getByText("Members").click();
await expect(page.getByText("All Members")).toBeVisible({ timeout: 10_000 });
}
@@ -74,18 +78,39 @@ test.describe("User is logged in", function () {
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
- test.beforeEach(async ({ page, daoAccount, instanceAccount }) => {
+ test.beforeEach(async ({ page, daoAccount, instanceAccount }, testInfo) => {
await mockInventory({ page, account: daoAccount });
await updateDaoPolicyMembers({ page });
await updateLastProposalId(page);
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ }
await navigateToMembersPage({ page, instanceAccount });
});
- test("should show members of the DAO", async ({
+ test("insufficient account balance should show warning modal, disallow action ", async ({
page,
- instanceAccount,
- daoAccount,
}) => {
+ test.setTimeout(60_000);
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page.getByRole("button", { name: " New Member" }).click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
+ test("should show members of the DAO", async ({ page }) => {
test.setTimeout(60_000);
await expect(page.getByText("Megha", { exact: true })).toBeVisible();
});
diff --git a/playwright-tests/tests/settings/create-threshold-request.spec.js b/playwright-tests/tests/settings/create-threshold-request.spec.js
index 4188d438..526d6472 100644
--- a/playwright-tests/tests/settings/create-threshold-request.spec.js
+++ b/playwright-tests/tests/settings/create-threshold-request.spec.js
@@ -4,10 +4,11 @@ import { test } from "../../util/test.js";
import { getTransactionModalObject } from "../../util/transaction.js";
import {
getMockedPolicy,
+ mockNearBalances,
mockRpcRequest,
updateDaoPolicyMembers,
} from "../../util/rpcmock.js";
-import { encodeToMarkdown } from "../../util/lib.js";
+import { InsufficientBalance, encodeToMarkdown } from "../../util/lib.js";
const lastProposalId = 3;
@@ -51,11 +52,19 @@ test.afterEach(async ({ page }, testInfo) => {
await page.unrouteAll({ behavior: "ignoreErrors" });
});
+async function navigateToThresholdPage({ page, instanceAccount }) {
+ await page.goto(`/${instanceAccount}/widget/app?page=settings`);
+ await page.waitForTimeout(5_000);
+ await page.getByText("Voting Threshold").click();
+ await expect(page.getByText("Permission Groups")).toBeVisible({
+ timeout: 10_000,
+ });
+}
+
test.describe("User is not logged in", function () {
test.beforeEach(async ({ page, instanceAccount }) => {
await updateDaoPolicyMembers({ page });
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Thresholds").click({ timeout: 20_000 });
+ await navigateToThresholdPage({ page, instanceAccount });
});
test("should show members of different roles", async ({ page }) => {
@@ -87,13 +96,13 @@ test.describe("User is not logged in", function () {
timeout: 20_000,
});
await expect(page.getByTestId("dropdown-btn")).toBeDisabled();
- await expect(page.getByText("Submit")).toBeHidden();
+ await expect(page.getByText("Submit Request")).toBeHidden();
await expect(page.getByTestId("threshold-input")).toBeDisabled();
});
});
async function fillInput(page, value) {
- const submitBtn = page.getByText("Submit");
+ const submitBtn = page.getByText("Submit Request");
const thresholdInput = page.getByTestId("threshold-input");
await thresholdInput.type(value);
expect(submitBtn).toBeVisible();
@@ -104,19 +113,46 @@ test.describe("User is logged in", function () {
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
- test.beforeEach(async ({ page, instanceAccount }) => {
+ test.beforeEach(async ({ page, instanceAccount }, testInfo) => {
await updateLastProposalId(page);
await updateDaoPolicyMembers({ page });
- await page.goto(`/${instanceAccount}/widget/app?page=settings`);
- await page.getByText("Voting Thresholds").click({ timeout: 20_000 });
- await expect(page.getByText("Submit")).toBeVisible({
- timeout: 20_000,
- });
- await page.getByTestId("dropdown-btn").click();
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ }
+ await navigateToThresholdPage({ page, instanceAccount });
+ });
+
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ }) => {
+ test.setTimeout(60_000);
+
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page.getByTestId("threshold-input").fill("1");
+ await page
+ .getByText("Submit Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
});
test("should allow only valid input for threshold", async ({ page }) => {
test.setTimeout(60_000);
+ await page.getByTestId("dropdown-btn").click();
await page.getByRole("list").getByText("Number of votes").click();
await fillInput(page, "20097292");
await fillInput(page, "10.323");
@@ -134,7 +170,8 @@ test.describe("User is logged in", function () {
page,
}) => {
test.setTimeout(150_000);
- const submitBtn = page.getByText("Submit");
+ const submitBtn = page.getByText("Submit Request");
+ await page.getByTestId("dropdown-btn").click();
await page.getByRole("list").getByText("Number of votes").click();
const thresholdInput = page.getByTestId("threshold-input");
await thresholdInput.fill("20");
@@ -173,7 +210,8 @@ test.describe("User is logged in", function () {
test("should be able to update policy by percentage", async ({ page }) => {
test.setTimeout(150_000);
- const submitBtn = page.getByText("Submit");
+ const submitBtn = page.getByText("Submit Request");
+ await page.getByTestId("dropdown-btn").click();
await page.getByRole("list").getByText("Percentage of members").click();
const thresholdInput = page.getByTestId("threshold-input");
await thresholdInput.fill("101");
diff --git a/playwright-tests/tests/settings/theme.spec.js b/playwright-tests/tests/settings/theme.spec.js
index f9ae6fcd..754a838e 100644
--- a/playwright-tests/tests/settings/theme.spec.js
+++ b/playwright-tests/tests/settings/theme.spec.js
@@ -1,9 +1,14 @@
import { expect } from "@playwright/test";
import { test } from "../../util/test.js";
import { getTransactionModalObject } from "../../util/transaction.js";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock.js";
+import {
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+ mockNearBalances,
+} from "../../util/rpcmock.js";
import path from "path";
import { fileURLToPath } from "url";
+import { InsufficientBalance } from "../../util/lib.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -59,7 +64,7 @@ test.describe("User is not logged in", function () {
await navigateToThemePage({ page, instanceAccount });
await expect(page.locator("input[type='color']")).toBeDisabled();
await expect(
- page.getByRole("button", { name: "Save changes" })
+ page.getByRole("button", { name: "Submit Request" })
).toBeDisabled();
});
});
@@ -69,10 +74,39 @@ test.describe("User is logged in", function () {
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
- test.beforeEach(async ({ page, instanceAccount }) => {
+ test.beforeEach(async ({ page, instanceAccount }, testInfo) => {
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ }
await navigateToThemePage({ page, instanceAccount });
});
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ }) => {
+ test.setTimeout(60_000);
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page
+ .getByText("Submit Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("should be able to upload image, should show error with incorrect width and allow correct one", async ({
page,
}) => {
@@ -91,7 +125,9 @@ test.describe("User is logged in", function () {
const logoInput = await page
.frameLocator("iframe")
.locator("input[type=file]");
- const submitBtn = await page.getByRole("button", { name: "Save changes" });
+ const submitBtn = await page.getByRole("button", {
+ name: "Submit Request",
+ });
// invalid image
await logoInput.setInputFiles(path.join(__dirname, "./assets/invalid.png"));
await expect(
@@ -128,9 +164,8 @@ test.describe("User is logged in", function () {
await page.getByRole("textbox").nth(1).fill(newColor);
await page.getByTestId("dropdown-btn").click();
await page.getByText("Light").click();
- await page.getByRole("button", { name: "Save changes" }).click();
await expect(page.getByText("Processing your request ...")).toBeVisible();
-
+ await page.getByRole("button", { name: "Submit Request" }).click();
await expect(await getTransactionModalObject(page)).toEqual({
proposal: {
description: "* Title: Update Config - Theme & logo",
diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js
index 92ff2991..a7c6f43d 100644
--- a/playwright-tests/tests/settings/voting-duration.spec.js
+++ b/playwright-tests/tests/settings/voting-duration.spec.js
@@ -2,8 +2,12 @@ import { expect } from "@playwright/test";
import { test } from "../../util/test.js";
import { getTransactionModalObject } from "../../util/transaction.js";
import { SandboxRPC } from "../../util/sandboxrpc.js";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock.js";
-import { encodeToMarkdown } from "../../util/lib.js";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock.js";
+import { InsufficientBalance, encodeToMarkdown } from "../../util/lib.js";
test.afterEach(async ({ page }, testInfo) => {
console.log(`Finished ${testInfo.title} with status ${testInfo.status}`);
@@ -42,6 +46,36 @@ test.describe("User is logged in", function () {
storageState: "playwright-tests/storage-states/wallet-connected-admin.json",
});
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ instanceAccount,
+ }) => {
+ test.setTimeout(60_000);
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ await navigateToVotingDurationPage({ page, instanceAccount });
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page.getByPlaceholder("Enter voting duration days").fill("2");
+ await page
+ .getByText("Submit Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("should set voting duration", async ({
page,
instanceAccount,
diff --git a/playwright-tests/tests/stake-delegation/stake-delegation.spec.js b/playwright-tests/tests/stake-delegation/stake-delegation.spec.js
index bd10d964..6294d24f 100644
--- a/playwright-tests/tests/stake-delegation/stake-delegation.spec.js
+++ b/playwright-tests/tests/stake-delegation/stake-delegation.spec.js
@@ -6,7 +6,11 @@ import {
mockTransactionSubmitRPCResponses,
} from "../../util/transaction";
import { utils } from "near-api-js";
-import { mockRpcRequest, updateDaoPolicyMembers } from "../../util/rpcmock.js";
+import {
+ mockNearBalances,
+ mockRpcRequest,
+ updateDaoPolicyMembers,
+} from "../../util/rpcmock.js";
import {
CurrentTimestampInNanoseconds,
OldJsonProposalData,
@@ -16,6 +20,7 @@ import {
} from "../../util/inventory.js";
import { setDontAskAgainCacheValues } from "../../util/cache.js";
import Big from "big.js";
+import { InsufficientBalance } from "../../util/lib.js";
test.afterEach(async ({ page }, testInfo) => {
console.log(`Finished ${testInfo.title} with status ${testInfo.status}`);
@@ -160,27 +165,6 @@ async function mockStakedPoolBalances({ page }) {
});
}
-async function mockNearBalances({ page, daoAccount, balance }) {
- await page.route(
- `https://api.fastnear.com/v1/account/${daoAccount}/full`,
- async (route) => {
- const json = {
- account_id: daoAccount,
- nfts: [],
- pools: [],
- state: {
- balance: balance,
- locked: "0",
- storage_bytes: 677278,
- },
- tokens: [],
- };
-
- await route.fulfill({ json });
- }
- );
-}
-
export async function mockStakedPools({
daoAccount,
page,
@@ -412,7 +396,7 @@ async function voteOnProposal({
}
test.describe("Have valid staked requests and sufficient token balance", function () {
- test.beforeEach(async ({ page, instanceAccount, daoAccount }) => {
+ test.beforeEach(async ({ page, instanceAccount, daoAccount }, testInfo) => {
const instanceConfig = await getInstanceConfig({ page, instanceAccount });
if (
!instanceConfig.navbarLinks.find(
@@ -425,17 +409,28 @@ test.describe("Have valid staked requests and sufficient token balance", functio
await mockStakeProposals({ page });
await updateDaoPolicyMembers({ page });
await mockStakedPools({ page, daoAccount });
- await mockNearBalances({
- page,
- daoAccount,
- balance: sufficientAvailableBalance,
- });
+ if (testInfo.title.includes("insufficient account balance")) {
+ await mockNearBalances({
+ page,
+ accountId: "theori.near",
+ balance: InsufficientBalance,
+ storage: 8,
+ });
+ } else {
+ await mockNearBalances({
+ page,
+ accountId: daoAccount,
+ balance: sufficientAvailableBalance,
+ storage: 2323,
+ });
+ }
await mockStakedPoolBalances({ page });
await page.goto(`/${instanceAccount}/widget/app?page=stake-delegation`);
await expect(
await page.locator("div").filter({ hasText: /^Stake Delegation$/ })
).toBeVisible();
+ await page.waitForTimeout(5_000);
});
test.describe("User not logged in", function () {
@@ -480,6 +475,28 @@ test.describe("Have valid staked requests and sufficient token balance", functio
});
});
+ test("insufficient account balance should show warning modal, disallow action ", async ({
+ page,
+ }) => {
+ test.setTimeout(60_000);
+
+ await expect(
+ page.getByText(
+ "Hey Ori, you don't have enough NEAR to complete actions on your treasury."
+ )
+ ).toBeVisible();
+ await page
+ .getByText("Create Request", {
+ exact: true,
+ })
+ .click();
+ await expect(
+ page
+ .getByText("Please add more funds to your account and try again")
+ .nth(1)
+ ).toBeVisible();
+ });
+
test("Should create stake delegation request, should throw error when invalid data is provided", async ({
page,
}) => {
@@ -842,9 +859,11 @@ test.describe("Lockup staking", function () {
await updateDaoPolicyMembers({ page });
await mockNearBalances({
page,
- daoAccount,
+ accountId: daoAccount,
balance: sufficientAvailableBalance,
+ storage: 2323,
});
+
await mockLockupNearBalances({
page,
balance: sufficientAvailableBalance,
@@ -1317,9 +1336,11 @@ test.describe("Insufficient balance ", function () {
await mockStakedPoolBalances({ page });
await mockNearBalances({
page,
- daoAccount,
+ accountId: daoAccount,
balance: inSufficientAvailableBalance,
+ storage: 2323,
});
+
await page.goto(`/${instanceAccount}/widget/app?page=stake-delegation`);
await expect(
await page.locator("div").filter({ hasText: /^Stake Delegation$/ })
diff --git a/playwright-tests/util/lib.js b/playwright-tests/util/lib.js
index 35b81046..e6f7134a 100644
--- a/playwright-tests/util/lib.js
+++ b/playwright-tests/util/lib.js
@@ -20,3 +20,5 @@ export const encodeToMarkdown = (data) => {
})
.join(" ");
};
+
+export const InsufficientBalance = BigInt(0.05 * 10 ** 24).toString();
diff --git a/playwright-tests/util/rpcmock.js b/playwright-tests/util/rpcmock.js
index 72b74c13..1ea4781a 100644
--- a/playwright-tests/util/rpcmock.js
+++ b/playwright-tests/util/rpcmock.js
@@ -190,3 +190,24 @@ export async function updateDaoPolicyMembers({ page, isMultiVote = false }) {
},
});
}
+
+export async function mockNearBalances({ page, accountId, balance, storage }) {
+ await page.route(
+ `https://api.fastnear.com/v1/account/${accountId}/full`,
+ async (route) => {
+ const json = {
+ account_id: accountId,
+ nfts: [],
+ pools: [],
+ state: {
+ balance: balance,
+ locked: "0",
+ storage: storage,
+ },
+ tokens: [],
+ };
+
+ await route.fulfill({ json });
+ }
+ );
+}