Skip to content

Commit

Permalink
NNS1-3497: sorts Launchpad projects from newest to oldest (#5951)
Browse files Browse the repository at this point in the history
# 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 <[email protected]>
Co-authored-by: David de Kloet <[email protected]>
  • Loading branch information
4 people authored Dec 9, 2024
1 parent 379af62 commit 1899ca7
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 84 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-Nns-Dapp-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/lib/components/launchpad/Projects.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -19,7 +22,7 @@
$: projects = filterProjectsStatus({
swapLifecycle: status,
projects: $snsProjectsActivePadStore,
});
}).sort(comparesByDecentralizationSaleOpenTimestampDesc);
let loading = false;
$: loading = $isLoadingSnsProjectsStore;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
193 changes: 112 additions & 81 deletions frontend/src/tests/lib/components/launchpad/Projects.spec.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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([
{
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/tests/mocks/sns-aggregator.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const aggregatorSnsMockWith = ({
tokenMetadata,
index,
nervousFunctions,
swapOpenTimestampSeconds,
swapDueTimestampSeconds,
nnsProposalId,
totalTokenSupply,
Expand All @@ -105,6 +106,7 @@ export const aggregatorSnsMockWith = ({
tokenMetadata?: Partial<IcrcTokenMetadata>;
index?: number;
nervousFunctions?: SnsNervousSystemFunction[];
swapOpenTimestampSeconds?: number;
swapDueTimestampSeconds?: number;
nnsProposalId?: number;
totalTokenSupply?: bigint;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/tests/utils/sns.test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const setSnsProjects = (
tokenMetadata?: Partial<IcrcTokenMetadata>;
nervousFunctions?: SnsNervousSystemFunction[];
swapDueTimestampSeconds?: number;
swapOpenTimestampSeconds?: number;
nnsProposalId?: number;
totalTokenSupply?: bigint;
nervousSystemParameters?: CachedNervousSystemParametersDto;
Expand All @@ -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,
Expand Down

0 comments on commit 1899ca7

Please sign in to comment.