From 1899ca767db944cdd6effadc90cbfd68162898ec Mon Sep 17 00:00:00 2001 From: Yusef Habib Date: Mon, 9 Dec 2024 17:10:44 +0100 Subject: [PATCH] NNS1-3497: sorts Launchpad projects from newest to oldest (#5951) # Motivation This is a follow-up to #5918 regarding the display of SNS projects in the Launchpad section. We aim to sort them in descending order, from newest to oldest. https://github.com/user-attachments/assets/7e89bddd-64fc-44f2-badf-752d35b1a773 # Changes - Applies new sort functionality to the Launchpad page - Changes section title to `Latest Launches` # Tests - Unit tests to ensure that the `Projects` component always displays projects from newest to oldest. # Todos - [x] Add entry to changelog (if necessary). Prev: #5948 --------- Co-authored-by: pr-automation-bot-public[bot] <189003650+pr-automation-bot-public[bot]@users.noreply.github.com> Co-authored-by: gix-bot Co-authored-by: David de Kloet <122978264+dskloetd@users.noreply.github.com> --- CHANGELOG-Nns-Dapp-unreleased.md | 2 + .../lib/components/launchpad/Projects.svelte | 7 +- frontend/src/lib/i18n/en.json | 2 +- .../lib/components/launchpad/Projects.spec.ts | 193 ++++++++++-------- .../src/tests/mocks/sns-aggregator.mock.ts | 3 + frontend/src/tests/utils/sns.test-utils.ts | 2 + 6 files changed, 125 insertions(+), 84 deletions(-) diff --git a/CHANGELOG-Nns-Dapp-unreleased.md b/CHANGELOG-Nns-Dapp-unreleased.md index 2e827fdcbd1..0943af62adc 100644 --- a/CHANGELOG-Nns-Dapp-unreleased.md +++ b/CHANGELOG-Nns-Dapp-unreleased.md @@ -16,6 +16,8 @@ proposal is successful, the changes it released will be moved from this file to #### Changed +- Sorts launched projects in the Launchpad page from newest to oldest. + #### Deprecated #### Removed diff --git a/frontend/src/lib/components/launchpad/Projects.svelte b/frontend/src/lib/components/launchpad/Projects.svelte index d1284c5f255..14f5d76d134 100644 --- a/frontend/src/lib/components/launchpad/Projects.svelte +++ b/frontend/src/lib/components/launchpad/Projects.svelte @@ -7,7 +7,10 @@ } from "$lib/derived/sns/sns-projects.derived"; import { i18n } from "$lib/stores/i18n"; import { isLoadingSnsProjectsStore } from "$lib/stores/sns.store"; - import { filterProjectsStatus } from "$lib/utils/projects.utils"; + import { + comparesByDecentralizationSaleOpenTimestampDesc, + filterProjectsStatus, + } from "$lib/utils/projects.utils"; import ProjectCard from "./ProjectCard.svelte"; import { Html } from "@dfinity/gix-components"; import { SnsSwapLifecycle } from "@dfinity/sns"; @@ -19,7 +22,7 @@ $: projects = filterProjectsStatus({ swapLifecycle: status, projects: $snsProjectsActivePadStore, - }); + }).sort(comparesByDecentralizationSaleOpenTimestampDesc); let loading = false; $: loading = $isLoadingSnsProjectsStore; diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 219b539a2bd..2749fe1fe9d 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -785,7 +785,7 @@ }, "sns_launchpad": { "header": "Launchpad", - "committed_projects": "Launched", + "committed_projects": "Latest Launches", "no_committed_projects": "No launched projects so far.", "no_opening_soon_projects": "No projects opening soon.", "no_projects": "No projects for this state.", diff --git a/frontend/src/tests/lib/components/launchpad/Projects.spec.ts b/frontend/src/tests/lib/components/launchpad/Projects.spec.ts index 12ad8887b00..5fec663bcd7 100644 --- a/frontend/src/tests/lib/components/launchpad/Projects.spec.ts +++ b/frontend/src/tests/lib/components/launchpad/Projects.spec.ts @@ -1,4 +1,6 @@ import Projects from "$lib/components/launchpad/Projects.svelte"; +import { JestPageObjectElement } from "$tests/page-objects/jest.page-object"; +import { ProjectsPo } from "$tests/page-objects/Projects.page-object"; import { resetSnsProjects, setSnsProjects } from "$tests/utils/sns.test-utils"; import { SnsSwapLifecycle } from "@dfinity/sns"; import { render, waitFor } from "@testing-library/svelte"; @@ -8,106 +10,135 @@ describe("Projects", () => { resetSnsProjects(); }); - it("should render 'Open' projects", () => { - const lifecycles = [ - SnsSwapLifecycle.Open, - SnsSwapLifecycle.Open, - SnsSwapLifecycle.Committed, - SnsSwapLifecycle.Open, + const renderComponent = ({ + testId, + status, + }: { + testId: string; + status: SnsSwapLifecycle; + }) => { + const { container } = render(Projects, { testId, status }); + const po = ProjectsPo.under({ + element: new JestPageObjectElement(container), + testId, + }); + return po; + }; + + describe("should render projects sorted from newest to oldest", () => { + const mockProjects = [ + { + lifecycle: SnsSwapLifecycle.Adopted, + swapOpenTimestampSeconds: 10, + projectName: "Project Adopted 10", + }, + { + lifecycle: SnsSwapLifecycle.Adopted, + swapOpenTimestampSeconds: 1, + projectName: "Project Adopted 1", + }, + { + lifecycle: SnsSwapLifecycle.Committed, + swapOpenTimestampSeconds: 1, + projectName: "Project Committed 1", + }, + { + lifecycle: SnsSwapLifecycle.Open, + swapOpenTimestampSeconds: 1, + projectName: "Project Open 1", + }, + { + lifecycle: SnsSwapLifecycle.Open, + swapOpenTimestampSeconds: 10, + projectName: "Project Open 10", + }, + { + lifecycle: SnsSwapLifecycle.Committed, + swapOpenTimestampSeconds: 10, + projectName: "Project Committed 10", + }, + { + lifecycle: SnsSwapLifecycle.Committed, + swapOpenTimestampSeconds: 100, + projectName: "Project Committed 100", + }, ]; - setSnsProjects(lifecycles.map((lifecycle) => ({ lifecycle }))); + beforeEach(() => { + setSnsProjects(mockProjects); + }); - const { getAllByTestId } = render(Projects, { - props: { + it("should render 'Open' projects sorted from newest to oldest", async () => { + const po = renderComponent({ testId: "open-projects", status: SnsSwapLifecycle.Open, - }, - }); + }); + const projects = await po.getProjectCardPos(); - expect(getAllByTestId("project-card-component").length).toBe( - lifecycles.filter((lc) => lc === SnsSwapLifecycle.Open).length - ); - }); - - it("should render 'Adopted' projects", () => { - const lifecycles = [ - SnsSwapLifecycle.Open, - SnsSwapLifecycle.Adopted, - SnsSwapLifecycle.Committed, - SnsSwapLifecycle.Adopted, - ]; - - setSnsProjects(lifecycles.map((lifecycle) => ({ lifecycle }))); + expect(projects.length).toBe(2); + expect(await projects[0].getProjectName()).toBe("Project Open 10"); + expect(await projects[1].getProjectName()).toBe("Project Open 1"); + }); - const { getAllByTestId } = render(Projects, { - props: { + it("should render 'Adopted' projects", async () => { + const po = renderComponent({ testId: "upcoming-projects", status: SnsSwapLifecycle.Adopted, - }, - }); - - expect(getAllByTestId("project-card-component").length).toBe( - lifecycles.filter((lc) => lc === SnsSwapLifecycle.Adopted).length - ); - }); + }); + const projects = await po.getProjectCardPos(); - it("should render 'Committed' projects", () => { - const lifecycles = [ - SnsSwapLifecycle.Open, - SnsSwapLifecycle.Open, - SnsSwapLifecycle.Committed, - SnsSwapLifecycle.Open, - ]; - - setSnsProjects(lifecycles.map((lifecycle) => ({ lifecycle }))); + expect(projects.length).toBe(2); + expect(await projects[0].getProjectName()).toBe("Project Adopted 10"); + expect(await projects[1].getProjectName()).toBe("Project Adopted 1"); + }); - const { getAllByTestId } = render(Projects, { - props: { + it("should render 'Committed' projects sorted from newest to oldest", async () => { + const po = renderComponent({ testId: "committed-projects", status: SnsSwapLifecycle.Committed, - }, - }); - - expect(getAllByTestId("project-card-component").length).toBe( - lifecycles.filter((lc) => lc === SnsSwapLifecycle.Committed).length - ); - }); + }); + const projects = await po.getProjectCardPos(); - it("should render a message when no open projects available", () => { - setSnsProjects([ - { - lifecycle: SnsSwapLifecycle.Committed, - }, - ]); - - const { queryByTestId } = render(Projects, { - props: { - testId: "open-projects", - status: SnsSwapLifecycle.Open, - }, + expect(projects.length).toBe(3); + expect(await projects[0].getProjectName()).toBe("Project Committed 100"); + expect(await projects[1].getProjectName()).toBe("Project Committed 10"); + expect(await projects[2].getProjectName()).toBe("Project Committed 1"); }); - expect(queryByTestId("no-projects-message")).toBeInTheDocument(); - }); - - it("should render a message when no adopted projects available", () => { - setSnsProjects([ - { - lifecycle: SnsSwapLifecycle.Open, - }, - ]); - - const { queryByTestId } = render(Projects, { - props: { - testId: "upcoming-projects", - status: SnsSwapLifecycle.Adopted, - }, + it("should render a message when no open projects available", () => { + setSnsProjects([ + { + lifecycle: SnsSwapLifecycle.Committed, + }, + ]); + + const { queryByTestId } = render(Projects, { + props: { + testId: "open-projects", + status: SnsSwapLifecycle.Open, + }, + }); + + expect(queryByTestId("no-projects-message")).toBeInTheDocument(); }); - expect(queryByTestId("no-projects-message")).toBeInTheDocument(); + it("should render a message when no adopted projects available", () => { + setSnsProjects([ + { + lifecycle: SnsSwapLifecycle.Open, + }, + ]); + + const { queryByTestId } = render(Projects, { + props: { + testId: "upcoming-projects", + status: SnsSwapLifecycle.Adopted, + }, + }); + + expect(queryByTestId("no-projects-message")).toBeInTheDocument(); + }); }); - it("should render a message when no committed projects available", () => { setSnsProjects([ { diff --git a/frontend/src/tests/mocks/sns-aggregator.mock.ts b/frontend/src/tests/mocks/sns-aggregator.mock.ts index a51d927d8c2..5ec3f343e5e 100644 --- a/frontend/src/tests/mocks/sns-aggregator.mock.ts +++ b/frontend/src/tests/mocks/sns-aggregator.mock.ts @@ -82,6 +82,7 @@ export const aggregatorSnsMockWith = ({ tokenMetadata, index, nervousFunctions, + swapOpenTimestampSeconds, swapDueTimestampSeconds, nnsProposalId, totalTokenSupply, @@ -105,6 +106,7 @@ export const aggregatorSnsMockWith = ({ tokenMetadata?: Partial; index?: number; nervousFunctions?: SnsNervousSystemFunction[]; + swapOpenTimestampSeconds?: number; swapDueTimestampSeconds?: number; nnsProposalId?: number; totalTokenSupply?: bigint; @@ -134,6 +136,7 @@ export const aggregatorSnsMockWith = ({ swap: { ...aggregatorSnsMockDto.swap_state.swap, lifecycle, + decentralization_sale_open_timestamp_seconds: swapOpenTimestampSeconds, init: { ...aggregatorSnsMockDto.swap_state.swap.init, restricted_countries: nonNullish(restrictedCountries) diff --git a/frontend/src/tests/utils/sns.test-utils.ts b/frontend/src/tests/utils/sns.test-utils.ts index d5db422c561..2fd1d0c0706 100644 --- a/frontend/src/tests/utils/sns.test-utils.ts +++ b/frontend/src/tests/utils/sns.test-utils.ts @@ -26,6 +26,7 @@ export const setSnsProjects = ( tokenMetadata?: Partial; nervousFunctions?: SnsNervousSystemFunction[]; swapDueTimestampSeconds?: number; + swapOpenTimestampSeconds?: number; nnsProposalId?: number; totalTokenSupply?: bigint; nervousSystemParameters?: CachedNervousSystemParametersDto; @@ -51,6 +52,7 @@ export const setSnsProjects = ( tokenMetadata: params.tokenMetadata, nervousFunctions: params.nervousFunctions, swapDueTimestampSeconds: params.swapDueTimestampSeconds, + swapOpenTimestampSeconds: params.swapOpenTimestampSeconds, nnsProposalId: params.nnsProposalId, totalTokenSupply: params.totalTokenSupply, nervousSystemParameters: params.nervousSystemParameters,