diff --git a/CHANGELOG-Nns-Dapp-unreleased.md b/CHANGELOG-Nns-Dapp-unreleased.md index 8cd5abad371..7d4fe8dbdd3 100644 --- a/CHANGELOG-Nns-Dapp-unreleased.md +++ b/CHANGELOG-Nns-Dapp-unreleased.md @@ -36,6 +36,7 @@ proposal is successful, the changes it released will be moved from this file to * Fixed a bug where a performance counter in `init` is wiped during state initialization. * Bug with parsing nervous system parameters from aborted SNSes. +* Bug where neurons are displayed as eligible to vote, even though they have already voted. #### Security diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f722ab74b6d..2b7f2c51123 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -249,10 +249,9 @@ } }, "node_modules/@dfinity/ckbtc": { - "version": "3.1.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-3.1.0-next-2024-09-30.tgz", - "integrity": "sha512-FlVouuKVSaiWWwfhB5CrhZInzZWKfTSEyKjiQ7DvYvemS0nhh67KeqPz4a+WVuh89iqNQAqbSHeYxvF6y1aYIw==", - "license": "Apache-2.0", + "version": "3.1.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-3.1.0-next-2024-10-02.tgz", + "integrity": "sha512-4Ixs29AWMy1HgQz11z1rSZyoUu3EO48+QQcfHSGIsHL09saoWiqaQi0Lyac8yd0mXzawyFZEmGW2XTKqFFFkGA==", "dependencies": { "@noble/hashes": "^1.3.2", "base58-js": "^1.0.5", @@ -266,10 +265,9 @@ } }, "node_modules/@dfinity/cmc": { - "version": "3.2.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.2.1-next-2024-09-30.tgz", - "integrity": "sha512-Ht88gCwQ/uiy2l6vMSU6ZBu5GniiWaUSNRxMqVqln1guAa4DvZBcVfx3pdM7QbFI7RmNBr5VIpuMY2AWa8ExYg==", - "license": "Apache-2.0", + "version": "3.2.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.2.1-next-2024-10-02.tgz", + "integrity": "sha512-Mwg7JPSi+vlii2+rj783rnQhSslh2+4T9/8tK5+ZQ6TVSiA6PXU8g3dvNM7EJ9G0VVO4uhv7qkYrxjXomjtQAw==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -294,10 +292,9 @@ } }, "node_modules/@dfinity/ic-management": { - "version": "5.2.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.2.1-next-2024-09-30.tgz", - "integrity": "sha512-mxLgdCoXoyd19+eiuriSPMdAu9d995xg7NuDd4GnXr/XDbAellCcGLA7tn8UumMptzYCGDdJq6NfnnXoldcvXg==", - "license": "Apache-2.0", + "version": "5.2.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.2.1-next-2024-10-02.tgz", + "integrity": "sha512-Q7C2rupyZx8JBciOLU7eq+GWs9AQwSrVBa16QstKq5S/oG84bwjif+3DaN9PgQhHnm6CasWZcShlC79a6SXxVw==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -321,10 +318,9 @@ } }, "node_modules/@dfinity/ledger-icp": { - "version": "2.6.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.6.0-next-2024-09-30.tgz", - "integrity": "sha512-la+XmcMYeJBQ8E+dsTShMhLA4BXoMDGM5pzXI7Yd6j6d7wL4ZJvkjw8UHAJaNaByuLeAusCgYG5PwgREb6rJZQ==", - "license": "Apache-2.0", + "version": "2.6.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.6.0-next-2024-10-02.tgz", + "integrity": "sha512-bmFhbwQ9OPWbUQuX+BwJP2ma6MGhhnIthsSNZu0YxxMXEgqLzjj83kBZPoji8TUg6soxaX3d6LfmD8Szv+X1dg==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -333,10 +329,9 @@ } }, "node_modules/@dfinity/ledger-icrc": { - "version": "2.6.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.6.0-next-2024-09-30.tgz", - "integrity": "sha512-KDPHVUujUUOS2WaAJXrja1vgX4iPOZ8cxSO/Q0zdrYrZo7Q0tSMoxC+t2mywxVuF2whhJUgQLIEzC8TJMbkiFQ==", - "license": "Apache-2.0", + "version": "2.6.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.6.0-next-2024-10-02.tgz", + "integrity": "sha512-J2GVKJLlFzUdQoX0M008V73g6ZSmsUC+ogiqgilU+KCP1i9vY/7GVuMhBa2g7k8wrwSxn/v4H70WC5KiJzfMMA==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -345,10 +340,9 @@ } }, "node_modules/@dfinity/nns": { - "version": "7.0.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-7.0.0-next-2024-09-30.tgz", - "integrity": "sha512-biigcsSW9bXNt0zleNsARnKFozxAYkUC4FrfgtG0LzXd7t+Lh3owh8MK4nVaSLkoEiSvE+d1obhrDrAW27PYmQ==", - "license": "Apache-2.0", + "version": "7.0.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-7.0.0-next-2024-10-02.tgz", + "integrity": "sha512-yHoRcppJ0Ktm+SIueG4w27MtKuYMKVGUqdWVcOtoSgZKOfHAjJAS0lUko916YwFvz5b7twh9OMBh6bDYtyHBJw==", "dependencies": { "@noble/hashes": "^1.3.2", "randombytes": "^2.1.0" @@ -370,10 +364,9 @@ } }, "node_modules/@dfinity/sns": { - "version": "3.2.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.2.1-next-2024-09-30.tgz", - "integrity": "sha512-fhNhk6WaES10y4oiXU3VQP+jq5fOnMpSLerGSpWNYDABTS7LT4qs5vNa/Z33mAbfyK/euuJO50sUYCw2Y6NIJg==", - "license": "Apache-2.0", + "version": "3.2.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.2.1-next-2024-10-02.tgz", + "integrity": "sha512-I/+b4Cz1GBn4c06NKVw3uywN5EAsN4178Y11H9XPJOM1nRtc03roewkAt0kfpHXyaO5hbOKWq3UYJAdUJFU9JA==", "dependencies": { "@noble/hashes": "^1.3.2" }, @@ -386,10 +379,9 @@ } }, "node_modules/@dfinity/utils": { - "version": "2.5.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.5.1-next-2024-09-30.tgz", - "integrity": "sha512-fV9N5DCS6jAtUAOCHNRvMqzjhs6Uq3IEIjZ/c2Nrj+4UPxB9vczthniMaghINJZq4xv5LQwpKYN3C8ZpwLz8QQ==", - "license": "Apache-2.0", + "version": "2.5.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.5.1-next-2024-10-02.tgz", + "integrity": "sha512-Q/2Gt0Z6DuwybmfCewszb0cQvcvGts7cV88kOcE41bIPyfLTvzK/B/TnrBW2KS2jHv8MZluBrPZJi73zVhHTbQ==", "peerDependencies": { "@dfinity/agent": "*", "@dfinity/candid": "*", @@ -2241,7 +2233,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-1.0.5.tgz", "integrity": "sha512-LkkAPP8Zu+c0SVNRTRVDyMfKVORThX+rCViget00xdgLRrKkClCTz1T7cIrpr69ShwV5XJuuoZvMvJ43yURwkA==", - "license": "MIT", "engines": { "node": ">= 8" } @@ -2276,8 +2267,7 @@ "node_modules/bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", - "license": "MIT" + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, "node_modules/bignumber.js": { "version": "9.1.1", @@ -5567,7 +5557,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -7221,9 +7210,9 @@ "requires": {} }, "@dfinity/ckbtc": { - "version": "3.1.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-3.1.0-next-2024-09-30.tgz", - "integrity": "sha512-FlVouuKVSaiWWwfhB5CrhZInzZWKfTSEyKjiQ7DvYvemS0nhh67KeqPz4a+WVuh89iqNQAqbSHeYxvF6y1aYIw==", + "version": "3.1.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ckbtc/-/ckbtc-3.1.0-next-2024-10-02.tgz", + "integrity": "sha512-4Ixs29AWMy1HgQz11z1rSZyoUu3EO48+QQcfHSGIsHL09saoWiqaQi0Lyac8yd0mXzawyFZEmGW2XTKqFFFkGA==", "requires": { "@noble/hashes": "^1.3.2", "base58-js": "^1.0.5", @@ -7231,9 +7220,9 @@ } }, "@dfinity/cmc": { - "version": "3.2.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.2.1-next-2024-09-30.tgz", - "integrity": "sha512-Ht88gCwQ/uiy2l6vMSU6ZBu5GniiWaUSNRxMqVqln1guAa4DvZBcVfx3pdM7QbFI7RmNBr5VIpuMY2AWa8ExYg==", + "version": "3.2.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/cmc/-/cmc-3.2.1-next-2024-10-02.tgz", + "integrity": "sha512-Mwg7JPSi+vlii2+rj783rnQhSslh2+4T9/8tK5+ZQ6TVSiA6PXU8g3dvNM7EJ9G0VVO4uhv7qkYrxjXomjtQAw==", "requires": {} }, "@dfinity/gix-components": { @@ -7248,9 +7237,9 @@ } }, "@dfinity/ic-management": { - "version": "5.2.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.2.1-next-2024-09-30.tgz", - "integrity": "sha512-mxLgdCoXoyd19+eiuriSPMdAu9d995xg7NuDd4GnXr/XDbAellCcGLA7tn8UumMptzYCGDdJq6NfnnXoldcvXg==", + "version": "5.2.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ic-management/-/ic-management-5.2.1-next-2024-10-02.tgz", + "integrity": "sha512-Q7C2rupyZx8JBciOLU7eq+GWs9AQwSrVBa16QstKq5S/oG84bwjif+3DaN9PgQhHnm6CasWZcShlC79a6SXxVw==", "requires": {} }, "@dfinity/identity": { @@ -7264,21 +7253,21 @@ } }, "@dfinity/ledger-icp": { - "version": "2.6.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.6.0-next-2024-09-30.tgz", - "integrity": "sha512-la+XmcMYeJBQ8E+dsTShMhLA4BXoMDGM5pzXI7Yd6j6d7wL4ZJvkjw8UHAJaNaByuLeAusCgYG5PwgREb6rJZQ==", + "version": "2.6.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icp/-/ledger-icp-2.6.0-next-2024-10-02.tgz", + "integrity": "sha512-bmFhbwQ9OPWbUQuX+BwJP2ma6MGhhnIthsSNZu0YxxMXEgqLzjj83kBZPoji8TUg6soxaX3d6LfmD8Szv+X1dg==", "requires": {} }, "@dfinity/ledger-icrc": { - "version": "2.6.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.6.0-next-2024-09-30.tgz", - "integrity": "sha512-KDPHVUujUUOS2WaAJXrja1vgX4iPOZ8cxSO/Q0zdrYrZo7Q0tSMoxC+t2mywxVuF2whhJUgQLIEzC8TJMbkiFQ==", + "version": "2.6.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/ledger-icrc/-/ledger-icrc-2.6.0-next-2024-10-02.tgz", + "integrity": "sha512-J2GVKJLlFzUdQoX0M008V73g6ZSmsUC+ogiqgilU+KCP1i9vY/7GVuMhBa2g7k8wrwSxn/v4H70WC5KiJzfMMA==", "requires": {} }, "@dfinity/nns": { - "version": "7.0.0-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-7.0.0-next-2024-09-30.tgz", - "integrity": "sha512-biigcsSW9bXNt0zleNsARnKFozxAYkUC4FrfgtG0LzXd7t+Lh3owh8MK4nVaSLkoEiSvE+d1obhrDrAW27PYmQ==", + "version": "7.0.0-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/nns/-/nns-7.0.0-next-2024-10-02.tgz", + "integrity": "sha512-yHoRcppJ0Ktm+SIueG4w27MtKuYMKVGUqdWVcOtoSgZKOfHAjJAS0lUko916YwFvz5b7twh9OMBh6bDYtyHBJw==", "requires": { "@noble/hashes": "^1.3.2", "randombytes": "^2.1.0" @@ -7293,17 +7282,17 @@ } }, "@dfinity/sns": { - "version": "3.2.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.2.1-next-2024-09-30.tgz", - "integrity": "sha512-fhNhk6WaES10y4oiXU3VQP+jq5fOnMpSLerGSpWNYDABTS7LT4qs5vNa/Z33mAbfyK/euuJO50sUYCw2Y6NIJg==", + "version": "3.2.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/sns/-/sns-3.2.1-next-2024-10-02.tgz", + "integrity": "sha512-I/+b4Cz1GBn4c06NKVw3uywN5EAsN4178Y11H9XPJOM1nRtc03roewkAt0kfpHXyaO5hbOKWq3UYJAdUJFU9JA==", "requires": { "@noble/hashes": "^1.3.2" } }, "@dfinity/utils": { - "version": "2.5.1-next-2024-09-30", - "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.5.1-next-2024-09-30.tgz", - "integrity": "sha512-fV9N5DCS6jAtUAOCHNRvMqzjhs6Uq3IEIjZ/c2Nrj+4UPxB9vczthniMaghINJZq4xv5LQwpKYN3C8ZpwLz8QQ==", + "version": "2.5.1-next-2024-10-02", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-2.5.1-next-2024-10-02.tgz", + "integrity": "sha512-Q/2Gt0Z6DuwybmfCewszb0cQvcvGts7cV88kOcE41bIPyfLTvzK/B/TnrBW2KS2jHv8MZluBrPZJi73zVhHTbQ==", "requires": {} }, "@esbuild/aix-ppc64": { diff --git a/frontend/src/lib/components/proposal-detail/VotingCard/NnsVotingCard.svelte b/frontend/src/lib/components/proposal-detail/VotingCard/NnsVotingCard.svelte index a99a93ed2db..63953109db6 100644 --- a/frontend/src/lib/components/proposal-detail/VotingCard/NnsVotingCard.svelte +++ b/frontend/src/lib/components/proposal-detail/VotingCard/NnsVotingCard.svelte @@ -29,17 +29,22 @@ type Vote, votableNeurons as getVotableNeurons, } from "@dfinity/nns"; + import type { NeuronInfo } from "@dfinity/nns"; import { getContext } from "svelte"; export let proposalInfo: ProposalInfo; - const votableNeurons = () => + const votableNeurons = ({ + neurons, + proposal, + }: { + neurons: NeuronInfo[]; + proposal: ProposalInfo; + }) => getVotableNeurons({ - neurons: $definedNeuronsStore, - proposal: proposalInfo, - }).map((neuron) => - nnsNeuronToVotingNeuron({ neuron, proposal: proposalInfo }) - ); + neurons, + proposal, + }).map((neuron) => nnsNeuronToVotingNeuron({ neuron, proposal })); let visible = false; /** Signals that the initial checkbox preselection was done. To avoid removing of user selection after second queryAndUpdate callback. */ @@ -53,17 +58,36 @@ $: $definedNeuronsStore, (visible = isProposalDeadlineInTheFuture(proposalInfo)); - const updateVotingNeuronSelectedStore = () => { + const updateVotingNeuronSelectedStore = ({ + neurons, + proposal, + }: { + neurons: NeuronInfo[]; + proposal: ProposalInfo; + }) => { if (!initialSelectionDone) { initialSelectionDone = true; - votingNeuronSelectStore.set(votableNeurons()); + votingNeuronSelectStore.set( + votableNeurons({ + neurons, + proposal, + }) + ); } else { // preserve user selection after neurons update (e.g. queryAndUpdate second callback) - votingNeuronSelectStore.updateNeurons(votableNeurons()); + votingNeuronSelectStore.updateNeurons( + votableNeurons({ + neurons, + proposal, + }) + ); } }; - $: $definedNeuronsStore, updateVotingNeuronSelectedStore(); + $: updateVotingNeuronSelectedStore({ + neurons: $definedNeuronsStore, + proposal: proposalInfo, + }); const { store } = getContext( SELECTED_PROPOSAL_CONTEXT_KEY diff --git a/frontend/src/tests/lib/components/proposal-detail/VotingCard/NnsVotingCard.spec.ts b/frontend/src/tests/lib/components/proposal-detail/VotingCard/NnsVotingCard.spec.ts index f57e8298beb..7f73b3eaf0c 100644 --- a/frontend/src/tests/lib/components/proposal-detail/VotingCard/NnsVotingCard.spec.ts +++ b/frontend/src/tests/lib/components/proposal-detail/VotingCard/NnsVotingCard.spec.ts @@ -32,7 +32,10 @@ describe("VotingCard", () => { const neuronIds = [111, 222].map(BigInt); const proposalInfo: ProposalInfo = { ...mockProposalInfo, - ballots: neuronIds.map((neuronId) => ({ neuronId }) as Ballot), + ballots: neuronIds.map( + (neuronId) => + ({ neuronId, vote: Vote.Unspecified, votingPower: 1n }) as Ballot + ), proposalTimestampSeconds: 2_000n, status: ProposalStatus.Open, }; diff --git a/frontend/src/tests/lib/pages/NnsProposalDetail.spec.ts b/frontend/src/tests/lib/pages/NnsProposalDetail.spec.ts index 07fa507d20d..0343f1538d3 100644 --- a/frontend/src/tests/lib/pages/NnsProposalDetail.spec.ts +++ b/frontend/src/tests/lib/pages/NnsProposalDetail.spec.ts @@ -1,33 +1,75 @@ import { resetNeuronsApiService } from "$lib/api-services/governance.api-service"; import * as governanceApi from "$lib/api/governance.api"; import * as proposalsApi from "$lib/api/proposals.api"; +import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; +import { AppPath } from "$lib/constants/routes.constants"; import NnsProposalDetail from "$lib/pages/NnsProposalDetail.svelte"; import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store"; +import { page } from "$mocks/$app/stores"; import { mockIdentity, resetIdentity, setNoIdentity, } from "$tests/mocks/auth.store.mock"; +import { mockNeuron } from "$tests/mocks/neurons.mock"; import { mockProposalInfo } from "$tests/mocks/proposal.mock"; import { NnsProposalPo } from "$tests/page-objects/NnsProposal.page-object"; import { JestPageObjectElement } from "$tests/page-objects/jest.page-object"; import { runResolvedPromises } from "$tests/utils/timers.test-utils"; import { toastsStore } from "@dfinity/gix-components"; +import { ProposalRewardStatus, Vote, type NeuronInfo } from "@dfinity/nns"; import { render } from "@testing-library/svelte"; import { get } from "svelte/store"; vi.mock("$lib/api/governance.api"); describe("NnsProposalDetail", () => { + const neuronId1 = 0n; + const neuronId2 = 1n; + const testNeurons = [ + { + ...mockNeuron, + neuronId: neuronId1, + }, + { + ...mockNeuron, + neuronId: neuronId2, + }, + ] as NeuronInfo[]; + const testProposal = { + ...mockProposalInfo, + rewardStatus: ProposalRewardStatus.AcceptVotes, + ballots: [ + { + neuronId: BigInt(0), + vote: Vote.Unspecified, + votingPower: BigInt(1), + }, + { + neuronId: BigInt(1), + vote: Vote.Unspecified, + votingPower: BigInt(1), + }, + ], + }; + beforeEach(() => { resetIdentity(); vi.restoreAllMocks(); resetNeuronsApiService(); toastsStore.reset(); - vi.spyOn(governanceApi, "queryNeurons").mockResolvedValue([]); + vi.spyOn(governanceApi, "queryNeurons").mockResolvedValue(testNeurons); actionableProposalsSegmentStore.set("all"); - vi.spyOn(proposalsApi, "queryProposal").mockResolvedValue(mockProposalInfo); + vi.spyOn(proposalsApi, "queryProposal").mockResolvedValue(testProposal); + vi.spyOn(proposalsApi, "queryProposals").mockResolvedValue([testProposal]); // actionable proposals update + page.mock({ + routeId: AppPath.Proposal, + data: { + universe: OWN_CANISTER_ID_TEXT, + proposal: `${testProposal.id}`, + }, + }); }); const props = { @@ -92,6 +134,71 @@ describe("NnsProposalDetail", () => { }); expect(governanceApi.queryNeurons).toHaveBeenCalledTimes(2); }); + + it("should update votable neurons after voting", async () => { + let beforeVoting = true; + const spyOnQueryProposal = vi + .spyOn(proposalsApi, "queryProposal") + .mockImplementation(() => + Promise.resolve( + beforeVoting + ? testProposal + : { + ...testProposal, + ballots: [ + { + neuronId: neuronId1, + vote: Vote.Yes, + votingPower: BigInt(1), + }, + { + neuronId: neuronId2, + vote: Vote.Yes, + votingPower: BigInt(1), + }, + ], + } + ) + ); + + const po = renderComponent(); + const votingCardPo = po.getVotingCardPo(); + await runResolvedPromises(); + + expect(await votingCardPo.isPresent()).toBe(true); + expect( + await po.getVotingCardPo().getVotingNeuronSelectListPo().isPresent() + ).toBe(true); + expect(await votingCardPo.getVoteYesButtonPo().isDisabled()).toBe(false); + expect(await votingCardPo.getVoteNoButtonPo().isDisabled()).toBe(false); + + const votingNeuronListItemPos = await po + .getVotingCardPo() + .getVotingNeuronSelectListPo() + .getVotingNeuronListItemPos(); + expect(votingNeuronListItemPos.length).toBe(testNeurons.length); + expect(await votingNeuronListItemPos[0].getNeuronId()).toBe( + `${neuronId1}` + ); + expect(await votingNeuronListItemPos[1].getNeuronId()).toBe( + `${neuronId2}` + ); + expect(spyOnQueryProposal).toBeCalledTimes(2); + + beforeVoting = false; + await votingCardPo.voteYes(); + await runResolvedPromises(); + + expect(spyOnQueryProposal).toBeCalledTimes(3); + expect( + ( + await po + .getVotingCardPo() + .getVotingNeuronSelectListPo() + .getVotingNeuronListItemPos() + ).length + ).toBe(0); + }); }); describe("logged out user", () => { diff --git a/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts b/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts index bc3af0b3a42..f7789386bc6 100644 --- a/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts +++ b/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts @@ -67,16 +67,37 @@ describe("actionable-proposals.services", () => { }; const votableProposal: ProposalInfo = { ...mockProposalInfo, + ballots: [ + { + neuronId, + vote: Vote.Unspecified, + votingPower: 1n, + }, + ], id: 0n, }; const votedProposal: ProposalInfo = { ...mockProposalInfo, + ballots: [ + { + neuronId, + vote: Vote.Yes, + votingPower: 1n, + }, + ], id: votedProposalId, }; const fiveHundredsProposal = Array.from(Array(500)) .map((_, index) => ({ ...mockProposalInfo, id: BigInt(index), + ballots: [ + { + neuronId, + vote: Vote.Unspecified, + votingPower: 1n, + }, + ], })) .reverse(); let spyQueryProposals; @@ -227,14 +248,35 @@ describe("actionable-proposals.services", () => { const proposal0 = { ...mockProposalInfo, id: 0n, + ballots: [ + { + neuronId, + vote: Vote.Unspecified, + votingPower: 1n, + }, + ], } as ProposalInfo; const proposal1 = { ...mockProposalInfo, id: 1n, + ballots: [ + { + neuronId, + vote: Vote.Unspecified, + votingPower: 1n, + }, + ], } as ProposalInfo; const proposal2 = { ...mockProposalInfo, id: 2n, + ballots: [ + { + neuronId, + vote: Vote.Unspecified, + votingPower: 1n, + }, + ], } as ProposalInfo; it("should query list proposals also with ManageNeurons payload", async () => { diff --git a/frontend/src/tests/lib/services/vote-registration.services.spec.ts b/frontend/src/tests/lib/services/vote-registration.services.spec.ts index 13941b77158..c4ad8a5aa4f 100644 --- a/frontend/src/tests/lib/services/vote-registration.services.spec.ts +++ b/frontend/src/tests/lib/services/vote-registration.services.spec.ts @@ -59,6 +59,11 @@ describe("vote-registration-services", () => { const votableProposal: ProposalInfo = { ...mockProposalInfo, id: 0n, + ballots: [ + { neuronId: 0n, vote: Vote.Unspecified, votingPower: 1n }, + { neuronId: 1n, vote: Vote.Unspecified, votingPower: 1n }, + { neuronId: 2n, vote: Vote.Unspecified, votingPower: 1n }, + ], }; let resolveSpyQueryProposals; const spyQueryProposals = vi diff --git a/frontend/src/tests/page-objects/VotingCard.page-object.ts b/frontend/src/tests/page-objects/VotingCard.page-object.ts index 38b25ca29ef..18db44b8b37 100644 --- a/frontend/src/tests/page-objects/VotingCard.page-object.ts +++ b/frontend/src/tests/page-objects/VotingCard.page-object.ts @@ -3,6 +3,7 @@ import { StakeNeuronToVotePo } from "$tests/page-objects/StakeNeuronToVote.page- import { VotingConfirmationToolbarPo } from "$tests/page-objects/VotingConfirmationToolbar.page-object"; import { BasePageObject } from "$tests/page-objects/base.page-object"; import type { PageObjectElement } from "$tests/types/page-object.types"; +import { VotingNeuronSelectListPo } from "./VotingNeuronSelectList.page-object"; export class VotingCardPo extends BasePageObject { private static readonly TID = "voting-card-component"; @@ -35,6 +36,10 @@ export class VotingCardPo extends BasePageObject { return this.root.byTestId("votable-neurons"); } + getVotingNeuronSelectListPo(): VotingNeuronSelectListPo { + return VotingNeuronSelectListPo.under(this.root); + } + getVotedNeurons(): PageObjectElement { return this.root.byTestId("voted-neurons"); }