Skip to content

Commit eab5873

Browse files
authored
Add new sv reward weight form (#1733)
* Add new sv reward weight form [ci] and some stuff for #1695 Signed-off-by: fayi-da <fayimora.femibalogun@digitalasset.com> * Extra test 😉 [ci] Signed-off-by: fayi-da <fayimora.femibalogun@digitalasset.com> --------- Signed-off-by: fayi-da <fayimora.femibalogun@digitalasset.com>
1 parent f3b71d7 commit eab5873

File tree

16 files changed

+932
-255
lines changed

16 files changed

+932
-255
lines changed

apps/sv/frontend/src/__tests__/governance/create-proposal.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { ThemeProvider } from '@emotion/react';
88
import { theme } from '../../../../../common/frontend/lib/theme';
99
import { CreateProposal } from '../../routes/createProposal';
1010
import userEvent from '@testing-library/user-event';
11+
import { Wrapper } from '../helpers';
12+
import { createProposalActions } from '../../utils/governance';
1113

1214
const TestWrapper: React.FC<React.PropsWithChildren> = ({ children }) => {
1315
return (
@@ -45,6 +47,34 @@ describe('Create Proposal', () => {
4547
});
4648
});
4749

50+
test('Update Reward Weight Form is rendered after action selection', async () => {
51+
const user = userEvent.setup();
52+
53+
render(
54+
<Wrapper>
55+
<CreateProposal />
56+
</Wrapper>
57+
);
58+
59+
const actionDropdown = screen.getByTestId('select-action');
60+
61+
const selectInput = actionDropdown.querySelector('[role="combobox"]') as HTMLElement;
62+
await user.click(selectInput);
63+
64+
await waitFor(async () => {
65+
const actionToSelect = screen.getByText('Update SV Reward Weight');
66+
expect(actionToSelect).toBeDefined();
67+
await user.click(actionToSelect);
68+
});
69+
70+
const nextButton = screen.getByText('Next');
71+
await user.click(nextButton);
72+
73+
const actionInput = screen.getByTestId('update-sv-reward-weight-action');
74+
const action = createProposalActions.find(a => a.value === 'SRARC_UpdateSvRewardWeight');
75+
expect(actionInput.getAttribute('value')).toBe(action!.value);
76+
});
77+
4878
test('Display cancel and next buttons', () => {
4979
render(
5080
<MemoryRouter>
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
5+
import { describe, expect, test } from 'vitest';
6+
import { UpdateSvRewardWeightForm } from '../../../components/forms/UpdateSvRewardWeightForm';
7+
import userEvent from '@testing-library/user-event';
8+
import { SvConfigProvider } from '../../../utils';
9+
import App from '../../../App';
10+
import { svPartyId } from '../../mocks/constants';
11+
import { Wrapper } from '../../helpers';
12+
import { dateTimeFormatISO } from '@lfdecentralizedtrust/splice-common-frontend-utils';
13+
import dayjs from 'dayjs';
14+
15+
describe('SV user can', () => {
16+
test('login and see the SV party ID', async () => {
17+
const user = userEvent.setup();
18+
render(
19+
<SvConfigProvider>
20+
<App />
21+
</SvConfigProvider>
22+
);
23+
24+
expect(await screen.findByText('Log In')).toBeDefined();
25+
26+
const input = screen.getByRole('textbox');
27+
await user.type(input, 'sv1');
28+
29+
const button = screen.getByRole('button', { name: 'Log In' });
30+
user.click(button);
31+
32+
expect(await screen.findAllByDisplayValue(svPartyId)).toBeDefined();
33+
});
34+
});
35+
36+
describe('Update SV Reward Weight Form', () => {
37+
test('should render all Update SV Reward Weight Form components', () => {
38+
render(
39+
<Wrapper>
40+
<UpdateSvRewardWeightForm onSubmit={() => Promise.resolve()} />
41+
</Wrapper>
42+
);
43+
44+
expect(screen.getByTestId('update-sv-reward-weight-form')).toBeDefined();
45+
expect(screen.getByText('Action')).toBeDefined();
46+
47+
const actionInput = screen.getByTestId('update-sv-reward-weight-action');
48+
expect(actionInput).toBeDefined();
49+
expect(actionInput.getAttribute('value')).toBe('SRARC_UpdateSvRewardWeight');
50+
51+
const summaryInput = screen.getByTestId('update-sv-reward-weight-summary');
52+
expect(summaryInput).toBeDefined();
53+
expect(summaryInput.getAttribute('value')).toBeNull();
54+
55+
const urlInput = screen.getByTestId('update-sv-reward-weight-url');
56+
expect(urlInput).toBeDefined();
57+
expect(urlInput.getAttribute('value')).toBe('');
58+
59+
const memberInput = screen.getByTestId('update-sv-reward-weight-member-dropdown');
60+
expect(memberInput).toBeDefined();
61+
expect(memberInput.getAttribute('value')).toBe('');
62+
63+
const weightInput = screen.getByTestId('update-sv-reward-weight-weight');
64+
expect(weightInput).toBeDefined();
65+
expect(weightInput.getAttribute('value')).toBe('');
66+
});
67+
68+
test('should render errors when submit button is clicked on new form', async () => {
69+
const user = userEvent.setup();
70+
71+
render(
72+
<Wrapper>
73+
<UpdateSvRewardWeightForm onSubmit={() => Promise.resolve()} />
74+
</Wrapper>
75+
);
76+
77+
const actionInput = screen.getByTestId('update-sv-reward-weight-action');
78+
const submitButton = screen.getByTestId('submit-button');
79+
expect(submitButton).toBeDefined();
80+
81+
await user.click(submitButton);
82+
expect(submitButton.getAttribute('disabled')).toBeDefined();
83+
expect(async () => await user.click(submitButton)).rejects.toThrowError(
84+
/Unable to perform pointer interaction/
85+
);
86+
87+
screen.getByText('Summary is required');
88+
screen.getByText('Invalid URL');
89+
screen.getByText('Weight is required');
90+
screen.getByText('SV is required');
91+
92+
// completing the form should reenable the submit button
93+
const summaryInput = screen.getByTestId('update-sv-reward-weight-summary');
94+
expect(summaryInput).toBeDefined();
95+
user.type(summaryInput, 'Summary of the proposal');
96+
97+
const urlInput = screen.getByTestId('update-sv-reward-weight-url');
98+
expect(urlInput).toBeDefined();
99+
user.type(urlInput, 'https://example.com');
100+
101+
const memberDropdown = screen.getByTestId('update-sv-reward-weight-member-dropdown');
102+
expect(memberDropdown).toBeDefined();
103+
104+
const selectInput = screen.getByRole('combobox');
105+
fireEvent.mouseDown(selectInput);
106+
107+
await waitFor(async () => {
108+
const memberToSelect = screen.getByText('Digital-Asset-Eng-2');
109+
expect(memberToSelect).toBeDefined();
110+
await user.click(memberToSelect);
111+
});
112+
113+
const weightInput = screen.getByTestId('update-sv-reward-weight-weight');
114+
expect(weightInput).toBeDefined();
115+
await user.type(weightInput, '1000');
116+
117+
await user.click(actionInput); // using this to trigger the onBlur event which triggers the validation
118+
119+
expect(submitButton.getAttribute('disabled')).toBe('');
120+
});
121+
122+
test('expiry date must be in the future', async () => {
123+
const user = userEvent.setup();
124+
render(
125+
<Wrapper>
126+
<UpdateSvRewardWeightForm onSubmit={() => Promise.resolve()} />
127+
</Wrapper>
128+
);
129+
130+
const expiryDateInput = screen.getByTestId('update-sv-reward-weight-expiry-date-field');
131+
expect(expiryDateInput).toBeDefined();
132+
133+
const thePast = dayjs().subtract(1, 'day').format(dateTimeFormatISO);
134+
const theFuture = dayjs().add(1, 'day').format(dateTimeFormatISO);
135+
136+
await user.clear(expiryDateInput);
137+
await user.type(expiryDateInput, thePast);
138+
139+
expect(screen.getByText('Expiration must be in the future')).toBeDefined();
140+
141+
await user.clear(expiryDateInput);
142+
await user.type(expiryDateInput, theFuture);
143+
144+
expect(screen.queryByText('Expiration must be in the future')).toBeNull();
145+
});
146+
147+
test('effective date must be after expiry date', async () => {
148+
const user = userEvent.setup();
149+
150+
render(
151+
<Wrapper>
152+
<UpdateSvRewardWeightForm onSubmit={() => Promise.resolve()} />
153+
</Wrapper>
154+
);
155+
156+
const expiryDateInput = screen.getByTestId('update-sv-reward-weight-expiry-date-field');
157+
const effectiveDateInput = screen.getByTestId('update-sv-reward-weight-effective-date-field');
158+
159+
const expiryDate = dayjs().add(1, 'week');
160+
const effectiveDate = expiryDate.subtract(1, 'day');
161+
162+
await user.clear(expiryDateInput);
163+
await user.type(expiryDateInput, expiryDate.format(dateTimeFormatISO));
164+
165+
await user.clear(effectiveDateInput);
166+
await user.type(effectiveDateInput, effectiveDate.format(dateTimeFormatISO));
167+
168+
waitFor(() => {
169+
expect(screen.getByText('Effective Date must be after expiration date')).toBeDefined();
170+
});
171+
172+
const validEffectiveDate = expiryDate.add(1, 'day').format(dateTimeFormatISO);
173+
174+
await user.clear(effectiveDateInput);
175+
await user.type(effectiveDateInput, validEffectiveDate);
176+
177+
expect(screen.queryByText('Effective Date must be after expiration date')).toBeNull();
178+
});
179+
180+
test('sv dropdown contains all svs', async () => {
181+
render(
182+
<Wrapper>
183+
<UpdateSvRewardWeightForm onSubmit={() => Promise.resolve()} />
184+
</Wrapper>
185+
);
186+
187+
const memberDropdown = screen.getByTestId('update-sv-reward-weight-member-dropdown');
188+
expect(memberDropdown).toBeDefined();
189+
190+
const selectInput = screen.getByRole('combobox');
191+
fireEvent.mouseDown(selectInput);
192+
193+
const svOptions = await screen.findAllByRole('option');
194+
expect(svOptions.length).toBeGreaterThan(1);
195+
expect(svOptions.map(option => option.textContent)).toEqual(
196+
expect.arrayContaining(['Digital-Asset-2', 'Digital-Asset-Eng-2'])
197+
);
198+
});
199+
200+
test('Weight must be a valid number', async () => {
201+
const user = userEvent.setup();
202+
render(
203+
<Wrapper>
204+
<UpdateSvRewardWeightForm onSubmit={() => Promise.resolve()} />
205+
</Wrapper>
206+
);
207+
208+
const weightInput = screen.getByTestId('update-sv-reward-weight-weight');
209+
expect(weightInput).toBeDefined();
210+
await user.type(weightInput, '123abc');
211+
212+
expect(screen.getByText('Weight must be a valid number')).toBeDefined();
213+
214+
await user.clear(weightInput);
215+
await user.type(weightInput, '1001');
216+
await user.click(screen.getByTestId('update-sv-reward-weight-action'));
217+
218+
expect(screen.queryByText('Weight must be a valid number')).toBeNull();
219+
});
220+
});

apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,15 @@ import {
88
} from '../../components/governance/ProposalDetailsContent';
99
import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules';
1010
import { ContractId } from '@daml/types';
11-
import { MemoryRouter, useNavigate } from 'react-router-dom';
12-
import { ThemeProvider } from '@mui/material';
1311
import { ProposalDetails, ProposalVote, ProposalVotingInformation } from '../../utils/types';
1412
import userEvent from '@testing-library/user-event';
15-
import { SvConfigProvider, useSvConfig } from '../../utils';
16-
import {
17-
AuthProvider,
18-
SvClientProvider,
19-
theme,
20-
UserProvider,
21-
} from '@lfdecentralizedtrust/splice-common-frontend';
22-
import { SvAdminClientProvider } from '../../contexts/SvAdminServiceContext';
23-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
24-
import { replaceEqualDeep } from '@lfdecentralizedtrust/splice-common-frontend-utils';
13+
import { SvConfigProvider } from '../../utils';
2514
import { server, svUrl } from '../setup/setup';
2615
import { rest } from 'msw';
2716
import { ProposalVoteForm } from '../../components/governance/ProposalVoteForm';
2817
import App from '../../App';
2918
import { svPartyId } from '../mocks/constants';
19+
import { Wrapper } from '../helpers';
3020

3121
const voteRequest = {
3222
contractId: 'abc123' as ContractId<VoteRequest>,
@@ -110,44 +100,6 @@ const voteResult = {
110100
],
111101
} as ProposalDetailsContentProps;
112102

113-
const queryClient = new QueryClient({
114-
defaultOptions: {
115-
queries: {
116-
refetchInterval: 500,
117-
structuralSharing: replaceEqualDeep,
118-
},
119-
},
120-
});
121-
122-
const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
123-
return (
124-
<MemoryRouter>
125-
<SvConfigProvider>
126-
<WrapperProviders children={children} />
127-
</SvConfigProvider>
128-
</MemoryRouter>
129-
);
130-
};
131-
132-
const WrapperProviders: React.FC<{ children: React.ReactNode }> = ({ children }) => {
133-
const config = useSvConfig();
134-
const navigate = useNavigate();
135-
136-
return (
137-
<ThemeProvider theme={theme}>
138-
<AuthProvider authConf={config.auth} redirect={(path: string) => navigate(path)}>
139-
<QueryClientProvider client={queryClient}>
140-
<UserProvider authConf={config.auth} testAuthConf={config.testAuth}>
141-
<SvClientProvider url={config.services.sv.url}>
142-
<SvAdminClientProvider url={config.services.sv.url}>{children}</SvAdminClientProvider>
143-
</SvClientProvider>
144-
</UserProvider>
145-
</QueryClientProvider>
146-
</AuthProvider>
147-
</ThemeProvider>
148-
);
149-
};
150-
151103
describe('SV user can', () => {
152104
test('login and see the SV party ID', async () => {
153105
const user = userEvent.setup();

apps/sv/frontend/src/__tests__/helpers.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)