diff --git a/components/factory/components/__tests__/metaBox.test.tsx b/components/factory/components/__tests__/metaBox.test.tsx index a566b8d..f0d3338 100644 --- a/components/factory/components/__tests__/metaBox.test.tsx +++ b/components/factory/components/__tests__/metaBox.test.tsx @@ -53,15 +53,6 @@ describe('MetaBox', () => { expect(screen.getByText('Select a token to view options')).toBeInTheDocument(); }); - test("renders TransferForm when active tab is 'transfer'", async () => { - renderWithProps({ denom: mockDenom }); - const transferTab = screen.getByText('Transfer'); - expect(transferTab).toBeInTheDocument(); - expect(transferTab).toBeEnabled(); - transferTab.click(); - await waitFor(() => expect(screen.getByText('Transfer TEST')).toBeInTheDocument()); - }); - test("renders BurnForm when active tab is 'burn'", async () => { renderWithProps({ denom: mockDenom }); const burnTab = screen.getByText('Burn'); diff --git a/components/factory/components/metaBox.tsx b/components/factory/components/metaBox.tsx index bc067f4..4042a12 100644 --- a/components/factory/components/metaBox.tsx +++ b/components/factory/components/metaBox.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { MetadataSDKType } from '@chalabi/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; import MintForm from '@/components/factory/forms/MintForm'; import BurnForm from '@/components/factory/forms/BurnForm'; -import TransferForm from '@/components/factory/forms/TransferForm'; + import { useGroupsByAdmin, usePoaParams } from '@/hooks'; export default function MetaBox({ @@ -16,7 +16,7 @@ export default function MetaBox({ refetch: () => void; balance: string; }>) { - const [activeTab, setActiveTab] = useState<'transfer' | 'burn' | 'mint'>('mint'); + const [activeTab, setActiveTab] = useState<'burn' | 'mint'>('mint'); const { poaParams, isPoaParamsLoading, refetchPoaParams, isPoaParamsError } = usePoaParams(); const admin = poaParams?.admins[0]; @@ -51,7 +51,7 @@ export default function MetaBox({ {`${activeTab.charAt(0).toUpperCase() + activeTab.slice(1)} ${denom.display}`}
- {[...(denom.base.includes('mfx') ? [] : ['transfer']), 'burn', 'mint'].map(tab => ( + {['burn', 'mint'].map(tab => ( @@ -78,9 +78,6 @@ export default function MetaBox({ )} {denom && ( <> - {!denom.base.includes('mfx') && activeTab === 'transfer' && ( - - )} {activeTab === 'burn' && ( unit.denom === denom.display)?.exponent || 0; const isMFX = denom.base.includes('mfx'); + const { balance: recipientBalance } = useTokenFactoryBalance(recipient ?? '', denom.base); + const balanceNumber = parseFloat( + shiftDigits(isMFX ? recipientBalance?.amount || '0' : balance, -exponent) + ); + + const BurnSchema = Yup.object().shape({ + amount: Yup.number() + .positive('Amount must be positive') + .required('Amount is required') + .max(1e12, 'Amount is too large') + .test('max-balance', 'Amount exceeds balance', function (value) { + return value <= balanceNumber; + }), + }); + const handleBurn = async () => { if (!amount || isNaN(Number(amount))) { return; @@ -187,58 +205,82 @@ export default function BurnForm({

{denom.display}

-
-
- - setAmount(e.target.value)} - /> -
-
- -
- setRecipient(e.target.value)} - /> - -
-
-
-
- - {isMFX && ( - + { + setAmount(values.amount); + handleBurn(); + }} + validateOnChange={true} + validateOnBlur={true} + validateOnMount={true} + > + {({ isValid, dirty, setFieldValue }) => ( +
+
+
+ ) => { + setAmount(e.target.value); + setFieldValue('amount', e.target.value); + }} + /> +
+
+ +
+ setRecipient(e.target.value)} + /> + +
+ {isMFX &&

{balanceNumber}

} +
+
+
+ + {isMFX && ( + + )} +
+
)} -
+ {isMFX && ( unit.denom === denom.display)?.exponent || 0; const isMFX = denom.base.includes('mfx'); + const MintSchema = Yup.object().shape({ + amount: Yup.number() + .positive('Amount must be positive') + .required('Amount is required') + .max(1e12, 'Amount is too large'), + recipient: Yup.string() + .required('Recipient address is required') + .manifestAddress('Invalid address format'), + }); + const handleMint = async () => { if (!amount || isNaN(Number(amount))) { return; @@ -173,86 +186,110 @@ export default function MintForm({ ) : ( <> - <> -
-
-

NAME

-

{denom.name}

-
-
-

YOUR BALANCE

-

{shiftDigits(balance, -exponent)}

-
-
-

EXPONENT

-

{denom?.denom_units[1]?.exponent}

-
-
-

CIRCULATING SUPPLY

-

{denom.display}

-
+
+
+

NAME

+

{denom.name}

+
+
+

YOUR BALANCE

+

{shiftDigits(balance, -exponent)}

+
+
+

EXPONENT

+

{denom?.denom_units[1]?.exponent}

+
+
+

CIRCULATING SUPPLY

+

{denom.display}

-
-
- - setAmount(e.target.value)} - /> -
-
- -
- setRecipient(e.target.value)} - /> +
+ { + setAmount(values.amount); + setRecipient(values.recipient); + handleMint(); + }} + validateOnChange={true} + validateOnBlur={true} + > + {({ isValid, dirty, setFieldValue, errors, touched }) => ( +
+
+
+ ) => { + setAmount(e.target.value); + setFieldValue('amount', e.target.value); + }} + /> +
+
+
+ ) => { + setRecipient(e.target.value); + setFieldValue('recipient', e.target.value); + }} + /> + +
+
+
+
+ {isMFX && ( + + )}
-
-
- -
- - {isMFX && ( - + )} - setIsModalOpen(false)} - payoutPairs={payoutPairs} - updatePayoutPair={updatePayoutPair} - addPayoutPair={addPayoutPair} - removePayoutPair={removePayoutPair} - handleMultiMint={handleMultiMint} - isSigning={isSigning} - /> -
+ + setIsModalOpen(false)} + payoutPairs={payoutPairs} + updatePayoutPair={updatePayoutPair} + addPayoutPair={addPayoutPair} + removePayoutPair={removePayoutPair} + handleMultiMint={handleMultiMint} + isSigning={isSigning} + /> )}
diff --git a/components/factory/forms/__tests__/BurnForm.test.tsx b/components/factory/forms/__tests__/BurnForm.test.tsx index ed13301..07fc43a 100644 --- a/components/factory/forms/__tests__/BurnForm.test.tsx +++ b/components/factory/forms/__tests__/BurnForm.test.tsx @@ -1,6 +1,6 @@ import { describe, test, afterEach, expect, jest } from 'bun:test'; import React from 'react'; -import { screen, fireEvent, cleanup } from '@testing-library/react'; +import { screen, fireEvent, cleanup, waitFor } from '@testing-library/react'; import BurnForm from '@/components/factory/forms/BurnForm'; import matchers from '@testing-library/jest-dom/matchers'; import { mockDenomMeta1, mockMfxDenom } from '@/tests/mock'; @@ -44,18 +44,40 @@ describe('BurnForm Component', () => { ).toBeInTheDocument(); }); - test('updates amount input correctly', () => { + test('updates amount input correctly', async () => { renderWithProps(); - const amountInput = screen.getByPlaceholderText('Enter amount'); + const amountInput = screen.getByLabelText('burn-amount-input'); fireEvent.change(amountInput, { target: { value: '100' } }); - expect(amountInput).toHaveValue('100'); + await waitFor(() => { + expect(amountInput).toHaveValue(100); + }); }); - test('updates recipient input correctly', () => { + test('burn button is disabled when inputs are invalid', async () => { renderWithProps(); + const burnButton = screen.getByLabelText('burn-target-input'); + expect(burnButton).toBeDisabled(); + + const amountInput = screen.getByPlaceholderText('Enter amount'); + fireEvent.change(amountInput, { target: { value: '-100' } }); + + await waitFor(() => { + expect(burnButton).toBeDisabled(); + }); + }); + + test('burn button is enabled when inputs are valid', async () => { + renderWithProps(); + const amountInput = screen.getByPlaceholderText('Enter amount'); const recipientInput = screen.getByPlaceholderText('Target address'); + const burnButton = screen.getByText('Burn'); + + fireEvent.change(amountInput, { target: { value: '100' } }); fireEvent.change(recipientInput, { target: { value: 'cosmos1recipient' } }); - expect(recipientInput).toHaveValue('cosmos1recipient'); + + await waitFor(() => { + expect(burnButton).toBeEnabled(); + }); }); // // TODO: Make this test pass diff --git a/components/factory/forms/__tests__/MintForm.test.tsx b/components/factory/forms/__tests__/MintForm.test.tsx index fe666ca..e59d663 100644 --- a/components/factory/forms/__tests__/MintForm.test.tsx +++ b/components/factory/forms/__tests__/MintForm.test.tsx @@ -1,10 +1,10 @@ import { describe, test, afterEach, expect, jest } from 'bun:test'; import React from 'react'; -import { screen, fireEvent, cleanup } from '@testing-library/react'; +import { screen, fireEvent, cleanup, waitFor } from '@testing-library/react'; import MintForm from '@/components/factory/forms/MintForm'; import matchers from '@testing-library/jest-dom/matchers'; import { renderWithChainProvider } from '@/tests/render'; -import { mockDenomMeta1 } from '@/tests/mock'; +import { mockDenomMeta1, mockMfxDenom } from '@/tests/mock'; expect.extend(matchers); @@ -17,49 +17,77 @@ const mockProps = { balance: '1000000', }; +function renderWithProps(props = {}) { + return renderWithChainProvider(); +} + describe('MintForm Component', () => { afterEach(cleanup); test('renders form with correct details', () => { - renderWithChainProvider(); + renderWithProps(); expect(screen.getByText('NAME')).toBeInTheDocument(); expect(screen.getByText('YOUR BALANCE')).toBeInTheDocument(); expect(screen.getByText('EXPONENT')).toBeInTheDocument(); expect(screen.getByText('CIRCULATING SUPPLY')).toBeInTheDocument(); }); - test('updates amount input correctly', () => { - renderWithChainProvider(); + test('updates amount input correctly', async () => { + renderWithProps(); const amountInput = screen.getByLabelText('mint-amount-input'); fireEvent.change(amountInput, { target: { value: '100' } }); - expect(amountInput).toHaveValue('100'); + await waitFor(() => { + expect(amountInput).toHaveValue(100); + }); }); - test('updates recipient input correctly', () => { - renderWithChainProvider(); - const recipientInput = screen.getByLabelText('mint-recipient-input'); + test('updates recipient input correctly', async () => { + renderWithProps(); + const recipientInput = screen.getByPlaceholderText('Recipient address'); fireEvent.change(recipientInput, { target: { value: 'cosmos1recipient' } }); - expect(recipientInput).toHaveValue('cosmos1recipient'); + await waitFor(() => { + expect(recipientInput).toHaveValue('cosmos1recipient'); + }); }); - // TODO: Button is disabled when inputs are invalid - // test('mint button is disabled when inputs are invalid', () => { - // renderWithChainProvider(); - // const mintButton = screen.getByText('Mint'); - // expect(mintButton).toBeDisabled(); - // }); - // - // TODO: Button is enabled when inputs are valid - // Fix values validation in the component, this test should not pass as-is - test('mint button is enabled when inputs are valid', () => { - renderWithChainProvider(); - fireEvent.change(screen.getByLabelText('mint-amount-input'), { - target: { value: '100' }, - }); - fireEvent.change(screen.getByLabelText('mint-recipient-input'), { - target: { value: 'cosmos1recipient' }, + test('mint button is disabled when inputs are invalid', async () => { + renderWithProps(); + const mintButton = screen.getByText('Mint'); + expect(mintButton).toBeDisabled(); + + const amountInput = screen.getByLabelText('mint-amount-input'); + fireEvent.change(amountInput, { target: { value: '-100' } }); + + await waitFor(() => { + expect(mintButton).toBeDisabled(); }); + }); + + test('mint button is enabled when inputs are valid', async () => { + renderWithProps(); + const amountInput = screen.getByLabelText('mint-amount-input'); + const recipientInput = screen.getByLabelText('mint-recipient-input'); const mintButton = screen.getByText('Mint'); - expect(mintButton).toBeEnabled(); + + fireEvent.change(amountInput, { target: { value: '1' } }); + fireEvent.change(recipientInput, { + target: { value: 'manifest1aucdev30u9505dx9t6q5fkcm70sjg4rh7rn5nf' }, + }); + + await waitFor(() => { + expect(mintButton).toBeEnabled(); + }); + }); + + test('renders multi mint button when token is mfx', () => { + renderWithProps({ denom: mockMfxDenom }); + expect(screen.getByLabelText('multi-mint-button')).toBeInTheDocument(); + }); + + test('renders not affiliated message when not admin and token is mfx', () => { + renderWithProps({ isAdmin: false, denom: mockMfxDenom }); + expect( + screen.getByText('You are not affiliated with any PoA Admin entity.') + ).toBeInTheDocument(); }); }); diff --git a/components/groups/forms/groups/MemberInfoForm.tsx b/components/groups/forms/groups/MemberInfoForm.tsx index b18b769..920683a 100644 --- a/components/groups/forms/groups/MemberInfoForm.tsx +++ b/components/groups/forms/groups/MemberInfoForm.tsx @@ -192,21 +192,18 @@ export default function MemberInfoForm({
- - - ); - }} - + + + )} +
- - ); - }} + + /> +
+ + + )}
diff --git a/components/react/inputs/BaseInput.tsx b/components/react/inputs/BaseInput.tsx index 3e61e19..0b89840 100644 --- a/components/react/inputs/BaseInput.tsx +++ b/components/react/inputs/BaseInput.tsx @@ -22,7 +22,7 @@ export const BaseInput: React.FC {meta.touched && meta.error ? (