Skip to content

Commit

Permalink
test: group forms
Browse files Browse the repository at this point in the history
  • Loading branch information
fmorency committed Aug 28, 2024
1 parent 15aeb2c commit 674467b
Show file tree
Hide file tree
Showing 11 changed files with 391 additions and 14 deletions.
4 changes: 2 additions & 2 deletions components/groups/forms/groups/ConfirmationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ export default function ConfirmationModal({
nextStep,
prevStep,
formData,
}: {
}: Readonly<{
nextStep: () => void;
prevStep: () => void;
formData: FormData;
}) {
}>) {
const { address } = useChain("manifest");
const { createGroupWithPolicy } = cosmos.group.v1.MessageComposer.withTypeUrl;
const [isSigning, setIsSigning] = useState(false);
Expand Down
4 changes: 2 additions & 2 deletions components/groups/forms/groups/GroupDetailsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export default function GroupDetails({
formData,
dispatch,
address,
}: {
}: Readonly<{
nextStep: () => void;
formData: FormData;
dispatch: React.Dispatch<Action>;
address: string;
}) {
}>) {
const updateField = (field: keyof FormData, value: any) => {
dispatch({ type: "UPDATE_FIELD", field, value });
};
Expand Down
4 changes: 2 additions & 2 deletions components/groups/forms/groups/GroupPolicyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export default function GroupPolicyForm({
prevStep,
formData,
dispatch,
}: {
}: Readonly<{
formData: FormData;
dispatch: React.Dispatch<Action>;
nextStep: () => void;
prevStep: () => void;
}) {
}>) {
const [votingUnit, setVotingUnit] = useState("days");
const [votingAmount, setVotingAmount] = useState(1);

Expand Down
8 changes: 6 additions & 2 deletions components/groups/forms/groups/MemberInfoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export default function MemberInfoForm({
nextStep,
prevStep,
address,
}: {
}: Readonly<{
formData: FormData;
dispatch: (action: Action) => void;
nextStep: () => void;
prevStep: () => void;
address: string;
}) {
}>) {
const [numberOfMembers, setNumberOfMembers] = useState(
formData.members.length
);
Expand Down Expand Up @@ -93,6 +93,7 @@ export default function MemberInfoForm({
-
</button>
<input
aria-label={'member-count'}
className="input input-bordered mx-2 text-center input-sm w-[40px]"
value={numberOfMembers}
onChange={handleNumberChange}
Expand Down Expand Up @@ -125,6 +126,7 @@ export default function MemberInfoForm({
<div className="flex flex-row items-center justify-between">
<input
type="text"
aria-label={`address-${index}`}
id={`address-${index}`}
value={member.address}
onChange={(e) =>
Expand Down Expand Up @@ -157,6 +159,7 @@ export default function MemberInfoForm({

<input
type="text"
aria-label={`name-${index}`}
id={`name-${index}`}
value={member.name}
onChange={(e) =>
Expand All @@ -176,6 +179,7 @@ export default function MemberInfoForm({

<input
type="text"
aria-label={`weight-${index}`}
id={`weight-${index}`}
value={member.weight}
onChange={(e) =>
Expand Down
18 changes: 13 additions & 5 deletions components/groups/forms/groups/Success.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import Link from "next/link";
export default function Success({
formData,
prevStep,
}: {
}: Readonly<{
formData: FormData;
prevStep: () => void;
}) {
}>) {
const renderAuthors = () => {
if (formData.authors.startsWith("manifest")) {
return <TruncatedAddressWithCopy address={formData.authors} slice={14} />;
Expand Down Expand Up @@ -42,12 +42,16 @@ export default function Success({
You may now interact with your group by adding members, submitting or
voting on proposals, and changing group parameters.
</p>
<p className="text-md text-gray-300 mb-6 text-pretty">
{/*
TODO: Verify the render is correct.
I changed the <p> to a <div> here because <div> (in TruncatedAddressWithCopy) cannot be a descendant of <p>
*/}
<div className="text-md text-gray-300 mb-6 text-pretty">
Remember to fund your group by sending tokens to the policy address{" "}
<span>
<TruncatedAddressWithCopy address="address" slice={24} />
</span>
</p>
</div>
<div className="border-t border-gray-700 pt-4">
<h2 className="text-2xl font-semibold mb-4">Group Details</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
Expand All @@ -57,7 +61,11 @@ export default function Success({
</div>
<div>
<h3 className="text-md font-light text-gray-400">AUTHORS</h3>
<p className="text-lg font-medium">{renderAuthors()}</p>
{/*
TODO: Verify the render is correct.
I changed the <p> to a <div> here because <div> (in TruncatedAddressWithCopy) cannot be a descendant of <p>
*/}
<div className="text-lg font-medium">{renderAuthors()}</div>
</div>
<div className="col-span-1 md:col-span-2">
<h3 className="text-md font-light text-gray-400">SUMMARY</h3>
Expand Down
71 changes: 71 additions & 0 deletions components/groups/forms/groups/__tests__/ConfirmationForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, test, afterEach, expect, jest, mock } from 'bun:test';
import React from 'react';
import { screen, fireEvent, cleanup } from '@testing-library/react';
import ConfirmationModal from '@/components/groups/forms/groups/ConfirmationForm';
import matchers from '@testing-library/jest-dom/matchers';
import { FormData } from '@/helpers/formReducer';
import { renderWithChainProvider } from '@/tests/render';

expect.extend(matchers);

const mockFormData: FormData = {
title: 'Test Group',
authors: 'manifest1author',
summary: 'This is a test group',
description: 'Detailed description of the test group',
forumLink: 'http://forumlink.com',
votingThreshold: '50%',
votingPeriod: { seconds: BigInt(3600), nanos: 0 },
members: [
{ address: 'manifest1member1', name: 'Member 1', weight: '1' },
{ address: 'manifest1member2', name: 'Member 2', weight: '2' },
],
};

const mockProps = {
nextStep: jest.fn(),
prevStep: jest.fn(),
formData: mockFormData,
};

describe('ConfirmationModal Component', () => {
afterEach(cleanup);

test('renders component with correct details', () => {
renderWithChainProvider(<ConfirmationModal {...mockProps} />);
expect(screen.getByText('Confirmation')).toBeInTheDocument();
expect(screen.getByText('GROUP DETAILS')).toBeInTheDocument();
expect(screen.getByText('GROUP TITLE')).toBeInTheDocument();
expect(screen.getByText('AUTHORS')).toBeInTheDocument();
expect(screen.getByText('SUMMARY')).toBeInTheDocument();
expect(screen.getByText('DESCRIPTION')).toBeInTheDocument();
expect(screen.getByText('THRESHOLD')).toBeInTheDocument();
expect(screen.getByText('VOTING PERIOD')).toBeInTheDocument();
expect(screen.getByText('MEMBERS')).toBeInTheDocument();
});

test('calls prevStep when "Prev: Member Info" button is clicked', () => {
renderWithChainProvider(<ConfirmationModal {...mockProps} />);
const prevButton = screen.getByText('Prev: Member Info');
fireEvent.click(prevButton);
expect(mockProps.prevStep).toHaveBeenCalled();
});


test('disables "Sign Transaction" button when isSigning is true', () => {
renderWithChainProvider(<ConfirmationModal {...mockProps} />);
const signButton = screen.getByText('Sign Transaction');
fireEvent.click(signButton);
expect(signButton).toBeDisabled();
});

test('disables "Sign Transaction" button when address is not provided', () => {
mock.module('@/hooks/useChain', () => ({
useChain: () => ({ address: '' }),
}));
renderWithChainProvider(<ConfirmationModal {...mockProps} />);
const signButton = screen.getByText('Sign Transaction');
fireEvent.click(signButton);
expect(signButton).toBeDisabled();
});
});
74 changes: 74 additions & 0 deletions components/groups/forms/groups/__tests__/GroupDetailsForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {afterEach, describe, expect, jest, test} from 'bun:test';
import React from 'react';
import {cleanup, fireEvent, screen} from '@testing-library/react';
import GroupDetails from '@/components/groups/forms/groups/GroupDetailsForm';
import matchers from '@testing-library/jest-dom/matchers';
import {renderWithChainProvider} from '@/tests/render';
import {mockGroupFormData} from "@/tests/mock";

expect.extend(matchers);

const mockProps = {
nextStep: jest.fn(),
formData: mockGroupFormData,
dispatch: jest.fn(),
address: 'cosmos1address',
};

describe('GroupDetails Component', () => {
afterEach(cleanup);

test('renders component with correct details', () => {
renderWithChainProvider(<GroupDetails {...mockProps} />);
expect(screen.getByText('Group details')).toBeInTheDocument();
expect(screen.getByText('Group Title')).toBeInTheDocument();
expect(screen.getByText('Authors')).toBeInTheDocument();
expect(screen.getByText('Summary')).toBeInTheDocument();
expect(screen.getByText('Description')).toBeInTheDocument();
expect(screen.getByText('Forum Link')).toBeInTheDocument();
});

// TODO: Make this test pass. Why is the input not being updated?
// test('updates form fields correctly', async () => {
// renderWithChainProvider(<GroupDetails {...mockProps} />);
// const titleInput = screen.getByPlaceholderText('Title');
// fireEvent.change(titleInput, { target: { value: 'New Group Title' } });
// await waitFor(() => expect(titleInput).toHaveValue('New Group Title'));
//
// const authorsInput = screen.getByPlaceholderText('List of authors or address');
// fireEvent.change(authorsInput, { target: { value: 'New Author' } });
// expect(authorsInput).toHaveValue('New Author');
//
// const summaryInput = screen.getByPlaceholderText('Short Bio');
// fireEvent.change(summaryInput, { target: { value: 'New Summary' } });
// expect(summaryInput).toHaveValue('New Summary');
//
// const descriptionInput = screen.getByPlaceholderText('Long Bio');
// fireEvent.change(descriptionInput, { target: { value: 'New Description' } });
// expect(descriptionInput).toHaveValue('New Description');
//
// const forumLinkInput = screen.getByPlaceholderText('Link to forum');
// fireEvent.change(forumLinkInput, { target: { value: 'http://newforumlink.com' } });
// expect(forumLinkInput).toHaveValue('http://newforumlink.com');
// });
//
test('next button is disabled when form is invalid', () => {
const invalidFormData = { ...mockGroupFormData, title: '' };
renderWithChainProvider(<GroupDetails {...mockProps} formData={invalidFormData} />);
const nextButton = screen.getByText('Next: Group Policy');
expect(nextButton).toBeDisabled();
});

test('next button is enabled when form is valid', () => {
renderWithChainProvider(<GroupDetails {...mockProps} />);
const nextButton = screen.getByText('Next: Group Policy');
expect(nextButton).toBeEnabled();
});

test('calls nextStep when next button is clicked', () => {
renderWithChainProvider(<GroupDetails {...mockProps} />);
const nextButton = screen.getByText('Next: Group Policy');
fireEvent.click(nextButton);
expect(mockProps.nextStep).toHaveBeenCalled();
});
});
66 changes: 66 additions & 0 deletions components/groups/forms/groups/__tests__/GroupPolicyForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, test, afterEach, expect, jest } from 'bun:test';
import React from 'react';
import { screen, fireEvent, cleanup } from '@testing-library/react';
import GroupPolicyForm from '@/components/groups/forms/groups/GroupPolicyForm';
import matchers from '@testing-library/jest-dom/matchers';
import { renderWithChainProvider } from '@/tests/render';
import {mockGroupFormData} from "@/tests/mock";

expect.extend(matchers);

const mockProps = {
nextStep: jest.fn(),
prevStep: jest.fn(),
formData: mockGroupFormData,
dispatch: jest.fn(),
};

describe('GroupPolicyForm Component', () => {
afterEach(cleanup);

test('renders component with correct details', () => {
renderWithChainProvider(<GroupPolicyForm {...mockProps} />);
expect(screen.getByText('Group Policy')).toBeInTheDocument();
expect(screen.getByText('Voting Period')).toBeInTheDocument();
expect(screen.getByText('Voting Threshold')).toBeInTheDocument();
});

test('updates form fields correctly', () => {
renderWithChainProvider(<GroupPolicyForm {...mockProps} />);
const votingAmountInput = screen.getByPlaceholderText('Enter duration');
fireEvent.change(votingAmountInput, { target: { value: '2' } });
expect(votingAmountInput).toHaveValue(2);

// TODO: The input is not being updated, why?
// const votingThresholdInput = screen.getByPlaceholderText('e.g. (1)');
// fireEvent.change(votingThresholdInput, { target: { value: '3' } });
// expect(votingThresholdInput).toHaveValue('3');
});

test('next button is disabled when form is invalid', () => {
const invalidFormData = { ...mockGroupFormData, votingThreshold: '' };
renderWithChainProvider(<GroupPolicyForm {...mockProps} formData={invalidFormData} />);
const nextButton = screen.getByText('Next: Member Info');
expect(nextButton).toBeDisabled();
});

test('next button is enabled when form is valid', () => {
renderWithChainProvider(<GroupPolicyForm {...mockProps} />);
const nextButton = screen.getByText('Next: Member Info');
expect(nextButton).toBeEnabled();
});

test('calls nextStep when next button is clicked', () => {
renderWithChainProvider(<GroupPolicyForm {...mockProps} />);
const nextButton = screen.getByText('Next: Member Info');
fireEvent.click(nextButton);
expect(mockProps.nextStep).toHaveBeenCalled();
});

test('calls prevStep when prev button is clicked', () => {
renderWithChainProvider(<GroupPolicyForm {...mockProps} />);
const prevButton = screen.getByText('Prev: Group Details');
fireEvent.click(prevButton);
expect(mockProps.prevStep).toHaveBeenCalled();
});
});
Loading

0 comments on commit 674467b

Please sign in to comment.