Skip to content

Commit 99dcb7a

Browse files
SV UI Look&Feel: Voting (#3445)
Signed-off-by: Paweł Perek <pawel.perek@digitalasset.com>
1 parent 6b82685 commit 99dcb7a

24 files changed

+457
-291
lines changed

apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -454,18 +454,20 @@ class SvFrontendIntegrationTest
454454
)(
455455
"sv2 can see the vote request and cast a vote",
456456
_ => {
457-
inside(find(id("your-vote-reason-input"))) { case Some(element) =>
457+
eventuallySucceeds() {
458+
find(testId("your-vote-reason-input")) should not be empty
459+
}
460+
461+
inside(find(testId("your-vote-reason-input"))) { case Some(element) =>
458462
element.underlying.sendKeys("A sample reason")
459463
}
460464

461-
inside(find(id("your-vote-url-input"))) { case Some(element) =>
465+
inside(find(testId("your-vote-url-input"))) { case Some(element) =>
462466
element.underlying.sendKeys("https://my-splice-vote-url.com")
463467
}
464468

465469
click on testId("your-vote-accept")
466470

467-
eventuallyClickOn(id("submit-vote-button"))
468-
469471
clue("wait for the vote submission success message") {
470472
eventuallySucceeds() {
471473
inside(find(testId("vote-submission-success"))) { case Some(element) =>
@@ -516,7 +518,7 @@ class SvFrontendIntegrationTest
516518
)(implicit
517519
env: SpliceTestConsoleEnvironment
518520
): String = clue(s"Creating proposal: $action") {
519-
val requestReasonUrl = "https://new-proposal-url.com/"
521+
val requestReasonUrl = "new-proposal-url.com/"
520522
val requestReasonBody = "This is a summary of the proposal"
521523

522524
val proposalContractId = withFrontEnd("sv1") { implicit webDriver =>

apps/sv/frontend/src/__tests__/governance/forms/create-unallocated-unclaimed-activity-form.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('Create Unallocated Unclaimed Activity Record Form', () => {
6565

6666
const urlInput = screen.getByTestId('create-unallocated-unclaimed-activity-record-url');
6767
expect(urlInput).toBeInTheDocument();
68-
expect(urlInput.getAttribute('value')).toBe('');
68+
expect(urlInput.getAttribute('value')).toBe('https://');
6969

7070
const beneficiaryInput = screen.getByTestId(
7171
'create-unallocated-unclaimed-activity-record-beneficiary'

apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('Grant Featured App Form', () => {
6161

6262
const urlInput = screen.getByTestId('grant-featured-app-url');
6363
expect(urlInput).toBeDefined();
64-
expect(urlInput.getAttribute('value')).toBe('');
64+
expect(urlInput.getAttribute('value')).toBe('https://');
6565

6666
const idInput = screen.getByTestId('grant-featured-app-idValue');
6767
expect(idInput).toBeDefined();
@@ -231,7 +231,7 @@ describe('Revoke Featured App Form', () => {
231231

232232
const urlInput = screen.getByTestId('revoke-featured-app-url');
233233
expect(urlInput).toBeDefined();
234-
expect(urlInput.getAttribute('value')).toBe('');
234+
expect(urlInput.getAttribute('value')).toBe('https://');
235235

236236
const idInput = screen.getByTestId('revoke-featured-app-idValue');
237237
expect(idInput).toBeDefined();

apps/sv/frontend/src/__tests__/governance/forms/offboard-sv-form.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('Offboard SV Form', () => {
6161

6262
const urlInput = screen.getByTestId('offboard-sv-url');
6363
expect(urlInput).toBeDefined();
64-
expect(urlInput.getAttribute('value')).toBe('');
64+
expect(urlInput.getAttribute('value')).toBe('https://');
6565

6666
const memberInput = screen.getByTestId('offboard-sv-member-dropdown');
6767
expect(memberInput).toBeDefined();

apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('Set Amulet Config Rules Form', () => {
6161

6262
const urlInput = screen.getByTestId('set-amulet-config-rules-url');
6363
expect(urlInput).toBeDefined();
64-
expect(urlInput.getAttribute('value')).toBe('');
64+
expect(urlInput.getAttribute('value')).toBe('https://');
6565

6666
// Amulet Rules has a lot of fields to process so this can get flakey if not given enough time
6767
waitFor(

apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('Set DSO Config Rules Form', () => {
5959

6060
const urlInput = screen.getByTestId('set-dso-config-rules-url');
6161
expect(urlInput).toBeDefined();
62-
expect(urlInput.getAttribute('value')).toBe('');
62+
expect(urlInput.getAttribute('value')).toBe('https://');
6363

6464
const configLabels = screen.getAllByTestId(/config-label-/);
6565
expect(configLabels.length).toBeGreaterThan(15);

apps/sv/frontend/src/__tests__/governance/forms/update-sv-reward-weight-form-test.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('Update SV Reward Weight Form', () => {
6161

6262
const urlInput = screen.getByTestId('update-sv-reward-weight-url');
6363
expect(urlInput).toBeDefined();
64-
expect(urlInput.getAttribute('value')).toBe('');
64+
expect(urlInput.getAttribute('value')).toBe('https://');
6565

6666
const memberInput = screen.getByTestId('update-sv-reward-weight-member-dropdown');
6767
expect(memberInput).toBeDefined();

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

Lines changed: 54 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,30 @@ describe('SV user can', () => {
130130

131131
describe('Proposal Details Content', () => {
132132
test('should render proposal details page', async () => {
133+
const votesWithNoCurrentSvVote = [
134+
{
135+
sv: 'sv1',
136+
isYou: true,
137+
vote: 'no-vote' as const,
138+
},
139+
{
140+
sv: 'sv3',
141+
vote: 'rejected' as const,
142+
reason: {
143+
url: 'https://example.com',
144+
body: 'Reason',
145+
},
146+
},
147+
];
148+
133149
render(
134150
<Wrapper>
135151
<ProposalDetailsContent
136152
currentSvPartyId={voteRequest.votingInformation.requester}
137153
contractId={voteRequest.contractId}
138154
proposalDetails={voteRequest.proposalDetails}
139155
votingInformation={voteRequest.votingInformation}
140-
votes={voteRequest.votes}
156+
votes={votesWithNoCurrentSvVote}
141157
/>
142158
</Wrapper>
143159
);
@@ -160,7 +176,7 @@ describe('Proposal Details Content', () => {
160176
const summary = screen.getByTestId('proposal-details-summary-value');
161177
expect(summary.textContent).toMatch(/Summary of the proposal/);
162178

163-
const url = screen.getByTestId('proposal-details-url-value');
179+
const url = screen.getByTestId('proposal-details-url');
164180
expect(url.textContent).toMatch(/https:\/\/example.com/);
165181

166182
const votingInformationSection = screen.getByTestId('proposal-details-voting-information');
@@ -263,7 +279,7 @@ describe('Proposal Details Content', () => {
263279
expect(unfeaturedAppSection).toBeInTheDocument();
264280

265281
const rightContractId = screen.getByTestId('proposal-details-unfeature-app-label');
266-
expect(rightContractId.textContent).toMatch(/Contract ID/);
282+
expect(rightContractId.textContent).toMatch(/Proposal ID/);
267283

268284
const rightContractIdValue = screen.getByTestId('proposal-details-unfeature-app-value');
269285
expect(rightContractIdValue.textContent).toMatch(/rightContractId/);
@@ -330,10 +346,8 @@ describe('Proposal Details Content', () => {
330346
const action = screen.getByTestId('proposal-details-action-value');
331347
expect(action.textContent).toMatch(/Create Unclaimed Activity Record/);
332348

333-
const beneficiary = screen
334-
.getByTestId('proposal-details-beneficiary-input')
335-
.getAttribute('value');
336-
expect(beneficiary).toMatch(/sv1/);
349+
const beneficiary = screen.getByTestId('proposal-details-beneficiary');
350+
expect(beneficiary.textContent).toMatch(/sv1/);
337351

338352
const amount = screen.getByTestId('proposal-details-amount-value');
339353
expect(amount.textContent).toMatch(/10/);
@@ -760,14 +774,18 @@ describe('Proposal Details > Votes & Voting', () => {
760774
});
761775

762776
test('should render voting form for vote request when voting has not closed', () => {
777+
const votesWithNoCurrentSvVote: ProposalVote[] = votesData.map(v =>
778+
v.sv === 'sv1' ? { sv: v.sv, isYou: v.isYou, vote: 'no-vote' as const } : v
779+
);
780+
763781
render(
764782
<Wrapper>
765783
<ProposalDetailsContent
766784
currentSvPartyId={voteRequest.votingInformation.requester}
767785
contractId={voteRequest.contractId}
768786
proposalDetails={voteRequest.proposalDetails}
769787
votingInformation={voteRequest.votingInformation}
770-
votes={votesData}
788+
votes={votesWithNoCurrentSvVote}
771789
/>
772790
</Wrapper>
773791
);
@@ -804,7 +822,7 @@ describe('Proposal Details > Votes & Voting', () => {
804822
expect(screen.queryByTestId('your-vote-form')).not.toBeInTheDocument();
805823
});
806824

807-
test('submit button says Submit if sv has not voted', async () => {
825+
test('renders accept and reject buttons with correct labels', async () => {
808826
const votes: ProposalVote[] = [
809827
{
810828
sv: 'sv1',
@@ -823,39 +841,13 @@ describe('Proposal Details > Votes & Voting', () => {
823841
);
824842

825843
const votingForm = screen.getByTestId('your-vote-form');
826-
const submitButton = within(votingForm).getByTestId('submit-vote-button');
844+
const acceptButton = within(votingForm).getByTestId('your-vote-accept');
845+
const rejectButton = within(votingForm).getByTestId('your-vote-reject');
827846

828-
expect(submitButton).toBeInTheDocument();
829-
expect(submitButton.textContent).toMatch(/Submit/);
830-
});
831-
832-
test('submit button says Update if sv has already voted', async () => {
833-
const votes: ProposalVote[] = [
834-
{
835-
sv: 'sv1',
836-
vote: 'accepted',
837-
reason: {
838-
url: 'https://sv1.example.com',
839-
body: 'SV1 Reason',
840-
},
841-
},
842-
];
843-
844-
render(
845-
<Wrapper>
846-
<ProposalVoteForm
847-
voteRequestContractId={voteRequest.contractId}
848-
currentSvPartyId={'sv1'}
849-
votes={votes}
850-
/>
851-
</Wrapper>
852-
);
853-
854-
const votingForm = screen.getByTestId('your-vote-form');
855-
const submitButton = within(votingForm).getByTestId('submit-vote-button');
856-
857-
expect(submitButton).toBeInTheDocument();
858-
expect(submitButton.textContent).toMatch(/Update/);
847+
expect(acceptButton).toBeInTheDocument();
848+
expect(acceptButton.textContent).toMatch(/Accept/);
849+
expect(rejectButton).toBeInTheDocument();
850+
expect(rejectButton.textContent).toMatch(/Reject/);
859851
});
860852

861853
test('render success message after api returns success', async () => {
@@ -901,20 +893,16 @@ describe('Proposal Details > Votes & Voting', () => {
901893
const reasonInput = within(votingForm).getByTestId('your-vote-reason-input');
902894
expect(reasonInput).toBeInTheDocument();
903895

904-
const acceptRadio = within(votingForm).getByTestId('your-vote-accept');
905-
expect(acceptRadio).toBeInTheDocument();
906-
907-
await user.click(acceptRadio);
908-
909-
const submitButton = within(votingForm).getByTestId('submit-vote-button');
910-
expect(submitButton).toBeInTheDocument();
896+
const acceptButton = within(votingForm).getByTestId('your-vote-accept');
897+
expect(acceptButton).toBeInTheDocument();
911898

899+
// Clicking the Accept button both selects the vote and submits
912900
// It's usually a good idea to await this click action. However this happens to be one where we shouldn't
913901
// This is because awaiting the button click makes it very difficult for the test runner to see the loading state
914-
user.click(submitButton);
902+
user.click(acceptButton);
915903

916904
await waitFor(async () => {
917-
expect(submitButton.getAttribute('disabled')).toBeDefined();
905+
expect(acceptButton.getAttribute('disabled')).toBeDefined();
918906
});
919907

920908
const submissionMessage = await screen.findByTestId('submission-message');
@@ -968,20 +956,16 @@ describe('Proposal Details > Votes & Voting', () => {
968956
const reasonInput = within(votingForm).getByTestId('your-vote-reason-input');
969957
expect(reasonInput).toBeInTheDocument();
970958

971-
const acceptRadio = within(votingForm).getByTestId('your-vote-accept');
972-
expect(acceptRadio).toBeInTheDocument();
973-
974-
await user.click(acceptRadio);
975-
976-
const submitButton = within(votingForm).getByTestId('submit-vote-button');
977-
expect(submitButton).toBeInTheDocument();
959+
const acceptButton = within(votingForm).getByTestId('your-vote-accept');
960+
expect(acceptButton).toBeInTheDocument();
978961

962+
// Clicking the Accept button both selects the vote and submits
979963
// It's usually a good idea to await this click action. However this happens to be one where we shouldn't
980964
// This is because awaiting the button click makes it very difficult for the test runner to see the loading state
981-
user.click(submitButton);
965+
user.click(acceptButton);
982966

983967
await waitFor(async () => {
984-
expect(submitButton.getAttribute('disabled')).toBeDefined();
968+
expect(acceptButton.getAttribute('disabled')).toBeDefined();
985969
});
986970

987971
const submissionMessage = await screen.findByTestId('submission-message');
@@ -1028,17 +1012,17 @@ describe('Proposal Details > Votes & Voting', () => {
10281012

10291013
await user.type(urlInput, 'invalid_url');
10301014

1031-
const acceptRadio = within(votingForm).getByTestId('your-vote-accept');
1032-
user.click(acceptRadio);
1015+
const acceptButton = within(votingForm).getByTestId('your-vote-accept');
1016+
const rejectButton = within(votingForm).getByTestId('your-vote-reject');
10331017

1034-
const submitButton = screen.getByTestId('submit-vote-button');
1035-
expect(submitButton.getAttribute('disabled')).toBeDefined();
1018+
expect(acceptButton).toBeDisabled();
1019+
expect(rejectButton).toBeDisabled();
10361020

10371021
const urlHelperText = within(votingForm).getByTestId('your-vote-url-helper-text');
10381022
expect(urlHelperText.textContent).toMatch(/Invalid URL/);
10391023
});
10401024

1041-
test('prevent submission if vote has not been chosen', async () => {
1025+
test('renders accept and reject buttons', async () => {
10421026
const votes: ProposalVote[] = [
10431027
{
10441028
sv: 'sv1',
@@ -1054,8 +1038,6 @@ describe('Proposal Details > Votes & Voting', () => {
10541038
},
10551039
];
10561040

1057-
const user = userEvent.setup();
1058-
10591041
render(
10601042
<Wrapper>
10611043
<ProposalVoteForm
@@ -1069,12 +1051,12 @@ describe('Proposal Details > Votes & Voting', () => {
10691051
const votingForm = screen.getByTestId('your-vote-form');
10701052
expect(votingForm).toBeInTheDocument();
10711053

1072-
const submitButton = screen.getByTestId('submit-vote-button');
1073-
expect(submitButton.getAttribute('disabled')?.valueOf()).toBe('');
1074-
1075-
const rejectRadio = within(votingForm).getByTestId('your-vote-reject');
1076-
await user.click(rejectRadio);
1054+
const acceptButton = within(votingForm).getByTestId('your-vote-accept');
1055+
const rejectButton = within(votingForm).getByTestId('your-vote-reject');
10771056

1078-
expect(submitButton.getAttribute('disabled')?.valueOf()).toBe(undefined);
1057+
expect(acceptButton).toBeInTheDocument();
1058+
expect(rejectButton).toBeInTheDocument();
1059+
expect(acceptButton).not.toBeDisabled();
1060+
expect(rejectButton).not.toBeDisabled();
10791061
});
10801062
});

apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ export type CopyableIdentifierSize = 'small' | 'large';
77

88
interface CopyableIdentifierProps {
99
value: string;
10+
copyValue?: string;
1011
badge?: string;
1112
size: CopyableIdentifierSize;
1213
'data-testid': string;
1314
}
1415

1516
const CopyableIdentifier: React.FC<CopyableIdentifierProps> = ({
1617
value,
18+
copyValue,
1719
badge,
1820
size,
1921
'data-testid': testId,
@@ -32,7 +34,7 @@ const CopyableIdentifier: React.FC<CopyableIdentifierProps> = ({
3234
<IconButton
3335
color="secondary"
3436
data-testid={`${testId}-copy-button`}
35-
onClick={() => navigator.clipboard.writeText(value)}
37+
onClick={() => navigator.clipboard.writeText(copyValue ?? value)}
3638
>
3739
<ContentCopy sx={{ fontSize: size === 'small' ? '14px' : '18px' }} />
3840
</IconButton>

0 commit comments

Comments
 (0)