Skip to content

Commit

Permalink
Merge remote-tracking branch 'chalabi/main' into more-more-more-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fmorency committed Sep 4, 2024
2 parents 19a8476 + 15eb0c2 commit 3709436
Show file tree
Hide file tree
Showing 25 changed files with 899 additions and 849 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.validate": ["typescript", "typescriptreact"]
"eslint.validate": ["typescript", "typescriptreact"],
"typescript.tsdk": "node_modules/typescript/lib"
}
15 changes: 15 additions & 0 deletions components/admins/components/validatorList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ export default function ValidatorList({
const [modalId, setModalId] = useState<string | null>(null);
const [warningVisible, setWarningVisible] = useState(false);
const [validatorToRemove, setValidatorToRemove] = useState<ExtendedValidatorSDKType | null>(null);
const totalvp = Array.isArray(activeValidators)
? activeValidators.reduce(
(acc, validator) => acc + BigInt(validator?.consensus_power ?? 0),
BigInt(0)
)
: BigInt(0);

const validatorVPArray = Array.isArray(activeValidators)
? activeValidators.map(validator => ({
moniker: validator.description.moniker,
vp: BigInt(validator?.consensus_power ?? 0),
}))
: [];

useEffect(() => {
if (modalId) {
Expand Down Expand Up @@ -174,6 +187,8 @@ export default function ValidatorList({
validator={selectedValidator}
modalId={modalId || ''}
admin={admin}
totalvp={totalvp.toString()}
validatorVPArray={validatorVPArray}
/>
<WarningModal
admin={admin}
Expand Down
42 changes: 30 additions & 12 deletions components/admins/modals/__tests__/updateAdminModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, test, afterEach, expect } from 'bun:test';
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 { UpdateAdminModal } from '@/components/admins/modals/updateAdminModal';
import matchers from '@testing-library/jest-dom/matchers';
import { renderWithChainProvider } from '@/tests/render';
Expand Down Expand Up @@ -39,29 +39,47 @@ describe('UpdateAdminModal Component', () => {
).toBeInTheDocument();
});

test('updates input field correctly', () => {
test('updates input field correctly', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('manifest123...');
fireEvent.change(input, { target: { value: 'manifest1newadminaddress' } });
expect(input).toHaveValue('manifest1newadminaddress');
const input = screen.getByLabelText('Admin Address');
fireEvent.change(input, { target: { value: validAddress } });
await waitFor(() => {
expect(input).toHaveValue(validAddress);
});
});

test('disables update button when input is invalid', () => {
test('disables update button when input is invalid', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('manifest123...');
const input = screen.getByLabelText('Admin Address');
const updateButton = screen.getByText('Update');
expect(updateButton).toBeDisabled();
fireEvent.change(input, { target: { value: 'invalidaddress' } });
expect(updateButton).toBeDisabled();
await waitFor(() => {
expect(updateButton).toBeDisabled();
});
});

test('enables update button when input is valid', () => {
test('enables update button when input is valid', async () => {
renderWithProps();
const updateButton = screen.getByText('Update');
expect(updateButton).toBeDisabled();
const input = screen.getByPlaceholderText('manifest123...');
const input = screen.getByLabelText('Admin Address');
fireEvent.change(input, { target: { value: validAddress } });
expect(updateButton).toBeEnabled();
await waitFor(() => {
expect(updateButton).toBeEnabled();
});
});

test('accepts valid manifest1 address', async () => {
renderWithProps();
const input = screen.getByLabelText('Admin Address');
const longValidAddress = 'manifest1' + 'a'.repeat(38);
fireEvent.change(input, { target: { value: longValidAddress } });
await waitFor(() => {
expect(
screen.queryByText('Please enter a valid manifest1 address (at least 38 characters long)')
).not.toBeInTheDocument();
});
});

// // TODO: Why is this test failing?
Expand Down
71 changes: 54 additions & 17 deletions components/admins/modals/__tests__/validatorModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, test, afterEach, expect } from 'bun:test';
import React from 'react';
import { screen, fireEvent, cleanup, within } from '@testing-library/react';
import { screen, fireEvent, cleanup, within, waitFor } from '@testing-library/react';
import { ValidatorDetailsModal } from '@/components/admins/modals/validatorModal';
import matchers from '@testing-library/jest-dom/matchers';
import { mockActiveValidators } from '@/tests/mock';
Expand All @@ -11,10 +11,19 @@ expect.extend(matchers);
const validator = mockActiveValidators[0];
const modalId = 'test-modal';
const admin = 'manifest1adminaddress';
const totalvp = '10000';
const validatorVPArray = [{ vp: BigInt(1000), moniker: 'Validator One' }];

function renderWithProps(props = {}) {
return renderWithChainProvider(
<ValidatorDetailsModal validator={validator} modalId={modalId} admin={admin} {...props} />
<ValidatorDetailsModal
validator={validator}
modalId={modalId}
admin={admin}
totalvp={totalvp}
validatorVPArray={validatorVPArray}
{...props}
/>
);
}

Expand All @@ -30,27 +39,55 @@ describe('ValidatorDetailsModal Component', () => {
expect(within(detailsContainer).getByText('details1')).toBeInTheDocument();
});

test('updates input field correctly', () => {
test('updates input field correctly', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('1000');
fireEvent.change(input, { target: { value: 2000 } });
expect(input).toHaveValue(2000);
fireEvent.change(input, { target: { value: '2000' } });
await waitFor(() => {
expect(input).toHaveValue(2000);
});
});

test('enables update button when input is valid', () => {
test('enables update button when input is valid', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('1000');
fireEvent.change(input, { target: { value: 2000 } });
const updateButton = screen.getByText('update');
expect(updateButton).toBeEnabled();
fireEvent.change(input, { target: { value: '2000' } });
await waitFor(() => {
const updateButton = screen.getByText('Update');
expect(updateButton).not.toBeDisabled();
});
});

// // TODO: Why is this test failing?
// // https://github.com/capricorn86/haVyppy-dom/issues/1184
// test('closes modal when close button is clicked', async () => {
// renderWithProps();
// const closeButton = screen.getByText('✕');
// fireEvent.click(closeButton);
// await waitFor(() => expect(screen.queryByText('Validator Details')).not.toBeInTheDocument());
// });
test('disables update button when input is invalid', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('1000');
fireEvent.change(input, { target: { value: '-1' } });
fireEvent.blur(input);
await waitFor(() => {
const updateButton = screen.getByText('Update');
expect(updateButton).toBeDisabled();
});
});

test('shows error message for invalid power input', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('1000');
fireEvent.change(input, { target: { value: '-1' } });
fireEvent.blur(input);
await waitFor(() => {
const errorMessage = screen.getByText(/power must be a non-negative number/i);
expect(errorMessage).toBeInTheDocument();
});
});

test('shows warning message for unsafe power update', async () => {
renderWithProps();
const input = screen.getByPlaceholderText('1000');
fireEvent.change(input, { target: { value: '9000' } });
fireEvent.blur(input);
await waitFor(() => {
const warningMessage = screen.getByText(/Warning: This power update may be unsafe/i);
expect(warningMessage).toBeInTheDocument();
});
});
});
111 changes: 59 additions & 52 deletions components/admins/modals/updateAdminModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { useFeeEstimation, useTx } from '@/hooks';
import { cosmos, strangelove_ventures } from '@chalabi/manifestjs';
import { Any } from '@chalabi/manifestjs/dist/codegen/google/protobuf/any';
import { MsgUpdateParams } from '@chalabi/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx';
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { PiWarning } from 'react-icons/pi';
import { Formik, Form } from 'formik';
import Yup from '@/utils/yupExtensions';
import { TextInput } from '@/components/react/inputs';

interface UpdateModalProps {
modalId: string;
Expand All @@ -13,32 +16,30 @@ interface UpdateModalProps {
allowExit: boolean | undefined;
}

const UpdateAdminSchema = Yup.object().shape({
newAdmin: Yup.string().required('Admin address is required').manifestAddress(),
});

export function UpdateAdminModal({
modalId,
admin,
userAddress,
allowExit,
}: Readonly<UpdateModalProps>) {
const [newAdmin, setNewAdmin] = useState('');
const [isValidAddress, setIsValidAddress] = useState(false);

const { estimateFee } = useFeeEstimation(chainName);
const { tx } = useTx(chainName);
const { updateParams } = strangelove_ventures.poa.v1.MessageComposer.withTypeUrl;
const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl;

useEffect(() => {
const isValid = /^manifest1[a-zA-Z0-9]{38}$/.test(newAdmin);
setIsValidAddress(isValid);
}, [newAdmin]);

const handleUpdate = async () => {
const handleUpdate = async (values: { newAdmin: string }) => {
if (!isValidAddress) return;

const msgUpdateAdmin = updateParams({
sender: admin ?? '',
params: {
admins: [newAdmin],
admins: [values.newAdmin],
allowValidatorSelfExit: allowExit ?? false,
},
});
Expand All @@ -54,7 +55,7 @@ export function UpdateAdminModal({
metadata: '',
proposers: [userAddress ?? ''],
title: `Update PoA Admin`,
summary: `This proposal will update the administrator of the PoA module to ${newAdmin}`,
summary: `This proposal will update the administrator of the PoA module to ${values.newAdmin}`,
exec: 0,
});

Expand All @@ -67,49 +68,55 @@ export function UpdateAdminModal({

return (
<dialog id={modalId} className="modal">
<form method="dialog" className="modal-box">
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
<h3 className="font-bold text-lg">Update Admin</h3>
<div className="divider divider-horizon -mt-0 -mb-0"></div>
<div className="py-4 flex flex-col gap-4">
<div className="p-4 border-l-[6px] border-base-300">
<div className="flex flex-row gap-2 items-center mb-2">
<PiWarning className="text-yellow-200" />
<span className="text-sm text-yellow-200">Warning</span>
<Formik
initialValues={{ newAdmin: '' }}
validationSchema={UpdateAdminSchema}
onSubmit={handleUpdate}
validateOnChange={true}
>
{({ isValid, dirty, values, setFieldValue }) => (
<Form method="dialog" className="modal-box">
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
<h3 className="font-bold text-lg">Update Admin</h3>
<div className="divider divider-horizon -mt-0 -mb-0"></div>
<div className="py-4 flex flex-col gap-4">
<div className="p-4 border-l-[6px] border-base-300">
<div className="flex flex-row gap-2 items-center mb-2">
<PiWarning className="text-yellow-200" />
<span className="text-sm text-yellow-200">Warning</span>
</div>
<p className="text-md font-thin">
Currently, the admin is set to a group policy address. While the admin can be any
manifest1 address, it is recommended to set the new admin to another group policy
address.
</p>
</div>
<div className="flex flex-col gap-2 mt-3">
<TextInput
label="Admin Address"
name="newAdmin"
placeholder="manifest123..."
value={values.newAdmin}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setFieldValue('newAdmin', newValue);
setIsValidAddress(/^manifest1[a-zA-Z0-9]{38}$/.test(newValue));
}}
/>
</div>
</div>
<p className="text-md font-thin">
Currently, the admin is set to a group policy address. While the admin can be any
manifest1 address, it is recommended to set the new admin to another group policy
address.
</p>
</div>
<div className="flex flex-col gap-2 mt-3">
<label className="text-md font-thin">Admin Address</label>
<input
type="text"
placeholder="manifest123..."
className={`input input-bordered input-md w-full ${
newAdmin && !isValidAddress ? 'input-error' : ''
}`}
value={newAdmin}
onChange={e => setNewAdmin(e.target.value)}
/>
{newAdmin && !isValidAddress && (
<p className="text-error text-sm">Please enter a valid manifest1 address</p>
)}
</div>
</div>
<div className="modal-action">
<button
type="button"
className="btn w-full btn-primary"
onClick={handleUpdate}
disabled={!isValidAddress || !newAdmin}
>
Update
</button>
</div>
</form>
<div className="modal-action">
<button
type="submit"
className="btn w-full btn-primary"
disabled={!isValid || !dirty}
>
Update
</button>
</div>
</Form>
)}
</Formik>
<form method="dialog" className="modal-backdrop">
<button>close</button>
</form>
Expand Down
Loading

0 comments on commit 3709436

Please sign in to comment.