From 0b5bcb843396e41ead74db1fe24980267be15abb Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Thu, 9 Jan 2025 12:07:01 +0100 Subject: [PATCH] Split actionable-proposals.derived.ts --- .../proposal-detail/NnsProposal.svelte | 6 +- .../proposals/ActionableSnses.svelte | 2 +- .../derived/actionable-proposals.derived.ts | 54 +------ .../derived/actionable-universes.derived.ts | 58 ++++++++ .../src/lib/pages/SnsProposalDetail.svelte | 6 +- .../actionable-proposals.derived.spec.ts | 126 +--------------- .../actionable-universes.derived.spec.ts | 135 ++++++++++++++++++ 7 files changed, 201 insertions(+), 186 deletions(-) create mode 100644 frontend/src/lib/derived/actionable-universes.derived.ts create mode 100644 frontend/src/tests/lib/derived/actionable-universes.derived.spec.ts diff --git a/frontend/src/lib/components/proposal-detail/NnsProposal.svelte b/frontend/src/lib/components/proposal-detail/NnsProposal.svelte index f8eb2cd2169..ec73eb6f0e1 100644 --- a/frontend/src/lib/components/proposal-detail/NnsProposal.svelte +++ b/frontend/src/lib/components/proposal-detail/NnsProposal.svelte @@ -3,10 +3,8 @@ import SkeletonDetails from "$lib/components/ui/SkeletonDetails.svelte"; import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; import { AppPath } from "$lib/constants/routes.constants"; - import { - actionableProposalsActiveStore, - actionableProposalsNavigationIdsStore, - } from "$lib/derived/actionable-proposals.derived"; + import { actionableProposalsActiveStore } from "$lib/derived/actionable-proposals.derived"; + import { actionableProposalsNavigationIdsStore } from "$lib/derived/actionable-universes.derived"; import { pageStore } from "$lib/derived/page.derived"; import { filteredProposals } from "$lib/derived/proposals.derived"; import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived"; diff --git a/frontend/src/lib/components/proposals/ActionableSnses.svelte b/frontend/src/lib/components/proposals/ActionableSnses.svelte index 0b0fbada1c8..139941db45b 100644 --- a/frontend/src/lib/components/proposals/ActionableSnses.svelte +++ b/frontend/src/lib/components/proposals/ActionableSnses.svelte @@ -4,7 +4,7 @@ import { actionableSnsProposalsByUniverseStore, type ActionableSnsProposalsByUniverseData, - } from "$lib/derived/actionable-proposals.derived"; + } from "$lib/derived/actionable-universes.derived"; let actionableUniverses: ActionableSnsProposalsByUniverseData[] = []; $: actionableUniverses = $actionableSnsProposalsByUniverseStore.filter( diff --git a/frontend/src/lib/derived/actionable-proposals.derived.ts b/frontend/src/lib/derived/actionable-proposals.derived.ts index 017746e2f59..ed3a70de959 100644 --- a/frontend/src/lib/derived/actionable-proposals.derived.ts +++ b/frontend/src/lib/derived/actionable-proposals.derived.ts @@ -2,7 +2,6 @@ import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; import { AppPath } from "$lib/constants/routes.constants"; import { authSignedInStore } from "$lib/derived/auth.derived"; import { pageStore } from "$lib/derived/page.derived"; -import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived"; import { snsProjectsCommittedStore } from "$lib/derived/sns/sns-projects.derived"; import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store"; import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store"; @@ -10,12 +9,9 @@ import { actionableSnsProposalsStore, failedActionableSnsesStore, } from "$lib/stores/actionable-sns-proposals.store"; -import type { ProposalsNavigationId } from "$lib/types/proposals"; -import type { Universe } from "$lib/types/universe"; import { isSelectedPath } from "$lib/utils/navigation.utils"; import { mapEntries } from "$lib/utils/utils"; -import type { SnsProposalData } from "@dfinity/sns"; -import { fromDefinedNullable, isNullish, nonNullish } from "@dfinity/utils"; +import { isNullish, nonNullish } from "@dfinity/utils"; import { derived, type Readable } from "svelte/store"; export interface ActionableProposalCountData { @@ -65,28 +61,6 @@ export const actionableProposalTotalCountStore: Readable = derived( Object.values(map).reduce((acc: number, count) => acc + (count ?? 0), 0) ); -export interface ActionableSnsProposalsByUniverseData { - universe: Universe; - proposals: SnsProposalData[]; -} - -/** A store that contains sns universes with actionable support and their actionable proposals - * in the same order as they are displayed in the UI. */ -export const actionableSnsProposalsByUniverseStore: Readable< - Array -> = derived( - [selectableUniversesStore, actionableSnsProposalsStore], - ([universes, actionableSnsProposals]) => - universes - .filter(({ canisterId }) => - nonNullish(actionableSnsProposals[canisterId]) - ) - .map((universe) => ({ - universe, - proposals: actionableSnsProposals[universe.canisterId].proposals, - })) -); - /** A store that returns true when all ‘Actionable Proposals’ have been loaded. */ export const actionableProposalsLoadedStore: Readable = derived( @@ -103,29 +77,3 @@ export const actionableProposalsLoadedStore: Readable = derived( committedSnsProjects.length === Object.keys(snsProposals).length + failedSnses.length ); - -// Generate list of ProposalsNavigationId using universes to provide correct order -// of proposals in the UI. -export const actionableProposalsNavigationIdsStore: Readable< - Array -> = derived( - [ - selectableUniversesStore, - actionableNnsProposalsStore, - actionableSnsProposalsStore, - ], - ([universes, nnsProposals, actionableSnsProposals]) => - universes - .map(({ canisterId }) => - canisterId === OWN_CANISTER_ID_TEXT - ? (nnsProposals.proposals ?? []).map(({ id }) => ({ - universe: OWN_CANISTER_ID_TEXT, - proposalId: id as bigint, - })) - : (actionableSnsProposals[canisterId]?.proposals ?? []).map((aa) => ({ - universe: canisterId, - proposalId: fromDefinedNullable(aa.id).id, - })) - ) - .flatMap((ids) => (nonNullish(ids) ? ids : [])) -); diff --git a/frontend/src/lib/derived/actionable-universes.derived.ts b/frontend/src/lib/derived/actionable-universes.derived.ts new file mode 100644 index 00000000000..a514c0446b5 --- /dev/null +++ b/frontend/src/lib/derived/actionable-universes.derived.ts @@ -0,0 +1,58 @@ +import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; +import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store"; +import { actionableSnsProposalsStore } from "$lib/stores/actionable-sns-proposals.store"; +import type { ProposalsNavigationId } from "$lib/types/proposals"; +import type { Universe } from "$lib/types/universe"; +import type { SnsProposalData } from "@dfinity/sns"; +import { fromDefinedNullable, nonNullish } from "@dfinity/utils"; +import { derived, type Readable } from "svelte/store"; + +import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived"; + +export interface ActionableSnsProposalsByUniverseData { + universe: Universe; + proposals: SnsProposalData[]; +} + +/** A store that contains sns universes with actionable support and their actionable proposals + * in the same order as they are displayed in the UI. */ +export const actionableSnsProposalsByUniverseStore: Readable< + Array +> = derived( + [selectableUniversesStore, actionableSnsProposalsStore], + ([universes, actionableSnsProposals]) => + universes + .filter(({ canisterId }) => + nonNullish(actionableSnsProposals[canisterId]) + ) + .map((universe) => ({ + universe, + proposals: actionableSnsProposals[universe.canisterId].proposals, + })) +); + +// Generate list of ProposalsNavigationId using universes to provide correct order +// of proposals in the UI. +export const actionableProposalsNavigationIdsStore: Readable< + Array +> = derived( + [ + selectableUniversesStore, + actionableNnsProposalsStore, + actionableSnsProposalsStore, + ], + ([universes, nnsProposals, actionableSnsProposals]) => + universes + .map(({ canisterId }) => + canisterId === OWN_CANISTER_ID_TEXT + ? (nnsProposals.proposals ?? []).map(({ id }) => ({ + universe: OWN_CANISTER_ID_TEXT, + proposalId: id as bigint, + })) + : (actionableSnsProposals[canisterId]?.proposals ?? []).map((aa) => ({ + universe: canisterId, + proposalId: fromDefinedNullable(aa.id).id, + })) + ) + .flatMap((ids) => (nonNullish(ids) ? ids : [])) +); diff --git a/frontend/src/lib/pages/SnsProposalDetail.svelte b/frontend/src/lib/pages/SnsProposalDetail.svelte index 29c920439d3..56a3a43a59e 100644 --- a/frontend/src/lib/pages/SnsProposalDetail.svelte +++ b/frontend/src/lib/pages/SnsProposalDetail.svelte @@ -7,10 +7,8 @@ import SnsProposalSystemInfoSection from "$lib/components/sns-proposals/SnsProposalSystemInfoSection.svelte"; import SnsProposalVotingSection from "$lib/components/sns-proposals/SnsProposalVotingSection.svelte"; import SkeletonDetails from "$lib/components/ui/SkeletonDetails.svelte"; - import { - actionableProposalsActiveStore, - actionableProposalsNavigationIdsStore, - } from "$lib/derived/actionable-proposals.derived"; + import { actionableProposalsActiveStore } from "$lib/derived/actionable-proposals.derived"; + import { actionableProposalsNavigationIdsStore } from "$lib/derived/actionable-universes.derived"; import { authSignedInStore } from "$lib/derived/auth.derived"; import { pageStore } from "$lib/derived/page.derived"; import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived"; diff --git a/frontend/src/tests/lib/derived/actionable-proposals.derived.spec.ts b/frontend/src/tests/lib/derived/actionable-proposals.derived.spec.ts index d1d8f475874..fd8e733308b 100644 --- a/frontend/src/tests/lib/derived/actionable-proposals.derived.spec.ts +++ b/frontend/src/tests/lib/derived/actionable-proposals.derived.spec.ts @@ -6,8 +6,6 @@ import { actionableProposalTotalCountStore, actionableProposalsActiveStore, actionableProposalsLoadedStore, - actionableProposalsNavigationIdsStore, - actionableSnsProposalsByUniverseStore, } from "$lib/derived/actionable-proposals.derived"; import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store"; import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store"; @@ -19,29 +17,13 @@ import { page } from "$mocks/$app/stores"; import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock"; import { mockProposalInfo } from "$tests/mocks/proposal.mock"; import { principal } from "$tests/mocks/sns-projects.mock"; -import { - createSnsProposal, - mockSnsProposal, -} from "$tests/mocks/sns-proposals.mock"; +import { mockSnsProposal } from "$tests/mocks/sns-proposals.mock"; import { resetSnsProjects, setSnsProjects } from "$tests/utils/sns.test-utils"; -import { runResolvedPromises } from "$tests/utils/timers.test-utils"; import type { ProposalInfo } from "@dfinity/nns"; -import { Principal } from "@dfinity/principal"; -import { - SnsProposalDecisionStatus, - SnsProposalRewardStatus, - SnsSwapLifecycle, - type SnsProposalData, -} from "@dfinity/sns"; +import { SnsSwapLifecycle } from "@dfinity/sns"; import { get } from "svelte/store"; describe("actionable proposals derived stores", () => { - const createProposal = (proposalId: bigint): SnsProposalData => - createSnsProposal({ - status: SnsProposalDecisionStatus.PROPOSAL_DECISION_STATUS_OPEN, - rewardStatus: SnsProposalRewardStatus.PROPOSAL_REWARD_STATUS_ACCEPT_VOTES, - proposalId, - }); const principal0 = principal(0); const principal1 = principal(1); const principal2 = principal(2); @@ -183,46 +165,6 @@ describe("actionable proposals derived stores", () => { }); }); - describe("actionableSnsProposalsByUniverseStore", () => { - const proposals0 = [createProposal(0n)]; - const proposals1 = [createProposal(1n)]; - - it("should return snses with proposals", async () => { - expect(get(actionableSnsProposalsByUniverseStore)).toEqual([]); - - setSnsProjects([ - { - lifecycle: SnsSwapLifecycle.Committed, - rootCanisterId: principal0, - }, - { - lifecycle: SnsSwapLifecycle.Committed, - rootCanisterId: principal1, - }, - ]); - - expect(get(actionableSnsProposalsByUniverseStore)).toEqual([]); - - actionableSnsProposalsStore.set({ - rootCanisterId: principal0, - proposals: proposals0, - }); - actionableSnsProposalsStore.set({ - rootCanisterId: principal1, - proposals: proposals1, - }); - - expect( - get(actionableSnsProposalsByUniverseStore).map( - ({ universe: { canisterId }, proposals }) => [canisterId, proposals] - ) - ).toEqual([ - [principal0.toText(), proposals0], - [principal1.toText(), proposals1], - ]); - }); - }); - describe("actionableProposalsLoadedStore", () => { it("should return true when all actionable proposals are loaded", async () => { expect(get(actionableProposalsLoadedStore)).toEqual(false); @@ -299,68 +241,4 @@ describe("actionable proposals derived stores", () => { expect(get(actionableProposalsLoadedStore)).toEqual(true); }); }); - - describe("actionableProposalsNavigationIdsStore", () => { - it("should return navigation IDs", async () => { - expect(get(actionableProposalsNavigationIdsStore)).toEqual([]); - - setSnsProjects([ - { - lifecycle: SnsSwapLifecycle.Committed, - rootCanisterId: Principal.fromText("g3pce-2iaae"), - }, - { - lifecycle: SnsSwapLifecycle.Committed, - rootCanisterId: Principal.fromText("f7crg-kabae"), - }, - ]); - // Add Sns proposals in reverse order to test that the universe order is used. - actionableSnsProposalsStore.set({ - rootCanisterId: Principal.fromText("f7crg-kabae"), - proposals: [createProposal(1n), createProposal(0n)], - }); - actionableSnsProposalsStore.set({ - rootCanisterId: Principal.fromText("g3pce-2iaae"), - proposals: [createProposal(3n), createProposal(2n)], - }); - actionableNnsProposalsStore.setProposals([ - { - ...mockProposalInfo, - id: 2n, - }, - { - ...mockProposalInfo, - id: 1n, - }, - ]); - await runResolvedPromises(); - - expect(get(actionableProposalsNavigationIdsStore)).toEqual([ - { - universe: OWN_CANISTER_ID_TEXT, - proposalId: 2n, - }, - { - universe: OWN_CANISTER_ID_TEXT, - proposalId: 1n, - }, - { - proposalId: 3n, - universe: "g3pce-2iaae", - }, - { - proposalId: 2n, - universe: "g3pce-2iaae", - }, - { - proposalId: 1n, - universe: "f7crg-kabae", - }, - { - proposalId: 0n, - universe: "f7crg-kabae", - }, - ]); - }); - }); }); diff --git a/frontend/src/tests/lib/derived/actionable-universes.derived.spec.ts b/frontend/src/tests/lib/derived/actionable-universes.derived.spec.ts new file mode 100644 index 00000000000..a2e0cf74c0a --- /dev/null +++ b/frontend/src/tests/lib/derived/actionable-universes.derived.spec.ts @@ -0,0 +1,135 @@ +import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; +import { + actionableProposalsNavigationIdsStore, + actionableSnsProposalsByUniverseStore, +} from "$lib/derived/actionable-universes.derived"; +import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store"; +import { actionableSnsProposalsStore } from "$lib/stores/actionable-sns-proposals.store"; +import { mockProposalInfo } from "$tests/mocks/proposal.mock"; +import { principal } from "$tests/mocks/sns-projects.mock"; +import { createSnsProposal } from "$tests/mocks/sns-proposals.mock"; +import { setSnsProjects } from "$tests/utils/sns.test-utils"; +import { runResolvedPromises } from "$tests/utils/timers.test-utils"; +import { Principal } from "@dfinity/principal"; +import { + SnsProposalDecisionStatus, + SnsProposalRewardStatus, + SnsSwapLifecycle, + type SnsProposalData, +} from "@dfinity/sns"; +import { get } from "svelte/store"; + +describe("actionable universes derived stores", () => { + const createProposal = (proposalId: bigint): SnsProposalData => + createSnsProposal({ + status: SnsProposalDecisionStatus.PROPOSAL_DECISION_STATUS_OPEN, + rewardStatus: SnsProposalRewardStatus.PROPOSAL_REWARD_STATUS_ACCEPT_VOTES, + proposalId, + }); + const principal0 = principal(0); + const principal1 = principal(1); + + describe("actionableSnsProposalsByUniverseStore", () => { + const proposals0 = [createProposal(0n)]; + const proposals1 = [createProposal(1n)]; + + it("should return snses with proposals", async () => { + expect(get(actionableSnsProposalsByUniverseStore)).toEqual([]); + + setSnsProjects([ + { + lifecycle: SnsSwapLifecycle.Committed, + rootCanisterId: principal0, + }, + { + lifecycle: SnsSwapLifecycle.Committed, + rootCanisterId: principal1, + }, + ]); + + expect(get(actionableSnsProposalsByUniverseStore)).toEqual([]); + + actionableSnsProposalsStore.set({ + rootCanisterId: principal0, + proposals: proposals0, + }); + actionableSnsProposalsStore.set({ + rootCanisterId: principal1, + proposals: proposals1, + }); + + expect( + get(actionableSnsProposalsByUniverseStore).map( + ({ universe: { canisterId }, proposals }) => [canisterId, proposals] + ) + ).toEqual([ + [principal0.toText(), proposals0], + [principal1.toText(), proposals1], + ]); + }); + }); + + describe("actionableProposalsNavigationIdsStore", () => { + it("should return navigation IDs", async () => { + expect(get(actionableProposalsNavigationIdsStore)).toEqual([]); + + setSnsProjects([ + { + lifecycle: SnsSwapLifecycle.Committed, + rootCanisterId: Principal.fromText("g3pce-2iaae"), + }, + { + lifecycle: SnsSwapLifecycle.Committed, + rootCanisterId: Principal.fromText("f7crg-kabae"), + }, + ]); + // Add Sns proposals in reverse order to test that the universe order is used. + actionableSnsProposalsStore.set({ + rootCanisterId: Principal.fromText("f7crg-kabae"), + proposals: [createProposal(1n), createProposal(0n)], + }); + actionableSnsProposalsStore.set({ + rootCanisterId: Principal.fromText("g3pce-2iaae"), + proposals: [createProposal(3n), createProposal(2n)], + }); + actionableNnsProposalsStore.setProposals([ + { + ...mockProposalInfo, + id: 2n, + }, + { + ...mockProposalInfo, + id: 1n, + }, + ]); + await runResolvedPromises(); + + expect(get(actionableProposalsNavigationIdsStore)).toEqual([ + { + universe: OWN_CANISTER_ID_TEXT, + proposalId: 2n, + }, + { + universe: OWN_CANISTER_ID_TEXT, + proposalId: 1n, + }, + { + proposalId: 3n, + universe: "g3pce-2iaae", + }, + { + proposalId: 2n, + universe: "g3pce-2iaae", + }, + { + proposalId: 1n, + universe: "f7crg-kabae", + }, + { + proposalId: 0n, + universe: "f7crg-kabae", + }, + ]); + }); + }); +});