From 8006cfbc2d1db9eec45db583650df0d42a791c1f Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Wed, 15 Jan 2025 15:44:33 +0100 Subject: [PATCH 1/6] projectsCard --- .../components/portfolio/ProjectsCard.svelte | 328 ++++++++++++++++++ frontend/src/lib/i18n/en.json | 9 +- frontend/src/lib/types/i18n.d.ts | 7 + .../components/portfolio/ProjectsCard.spec.ts | 203 +++++++++++ .../page-objects/ProjectsCard.page-object.ts | 62 ++++ 5 files changed, 608 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/components/portfolio/ProjectsCard.svelte create mode 100644 frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts create mode 100644 frontend/src/tests/page-objects/ProjectsCard.page-object.ts diff --git a/frontend/src/lib/components/portfolio/ProjectsCard.svelte b/frontend/src/lib/components/portfolio/ProjectsCard.svelte new file mode 100644 index 0000000000..817603f00b --- /dev/null +++ b/frontend/src/lib/components/portfolio/ProjectsCard.svelte @@ -0,0 +1,328 @@ + + + +
+
+
+ +
+
{$i18n.portfolio.projects_card_title}
+

+ ${usdAmountFormatted} +

+
+
+ + + + + + {$i18n.portfolio.projects_card_link} + + +
+
+
+ {$i18n.portfolio.projects_card_list_first_column} + + {$i18n.portfolio.projects_card_list_second_column_mobile} + {$i18n.portfolio.projects_card_list_second_column} + {$i18n.portfolio.projects_card_list_third_column} +
+ +
+ {#each topProjects as project (project.domKey)} +
+
+
+ +
+ {project.title} +
+ +
+ {#if $authSignedInStore} + + {:else} + {PRICE_NOT_AVAILABLE_PLACEHOLDER} + {/if} +
+
+ ${formatNumber(project?.stakeInUsd ?? 0)} +
+ {#if project.stake instanceof TokenAmountV2} +
+ {formatTokenV2({ + value: project.stake, + detailed: true, + })} + {project.stake.token.symbol} +
+ {/if} +
+ {/each} + {#if showInfoRow} +
+
+ +
+ {$i18n.portfolio.projects_card_info_row} +
+
+
+ {/if} +
+
+
+
+ + diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 6b8565a7ca..d4ad630c79 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1201,6 +1201,13 @@ "tokens_card_list_second_column_mobile": "Balance", "tokens_card_list_second_column": "Value Native", "tokens_card_list_third_column": "Value $", - "tokens_card_info_row": "Store and transfer tokens securely in the NNS wallet — running 100% on the Internet Computer blockchain. Tokens allow you to participate in ICP's onchain governance systems." + "tokens_card_info_row": "Store and transfer tokens securely in the NNS wallet — running 100% on the Internet Computer blockchain. Tokens allow you to participate in ICP's onchain governance systems.", + "projects_card_title": "Total Staking Balance", + "projects_card_link": "Stake neurons", + "projects_card_list_first_column": "Top Staked Neurons", + "projects_card_list_second_column_mobile": "Balance $/Maturity", + "projects_card_list_second_column": "Maturity", + "projects_card_list_third_column": "Staked", + "projects_card_info_row": "Optimize voting power and earn rewards by increasing your staked neurons." } } diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 2994185619..fd507dedf0 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1267,6 +1267,13 @@ interface I18nPortfolio { tokens_card_list_second_column: string; tokens_card_list_third_column: string; tokens_card_info_row: string; + projects_card_title: string; + projects_card_link: string; + projects_card_list_first_column: string; + projects_card_list_second_column_mobile: string; + projects_card_list_second_column: string; + projects_card_list_third_column: string; + projects_card_info_row: string; } interface I18nNeuron_state { diff --git a/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts b/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts new file mode 100644 index 0000000000..081e9538b1 --- /dev/null +++ b/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts @@ -0,0 +1,203 @@ +import ProjectsCard from "$lib/components/portfolio/ProjectsCard.svelte"; +import type { TableProject } from "$lib/types/staking"; +import type { UserTokenData } from "$lib/types/tokens-page"; +import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock"; +import { mockCkBTCToken as CkBTCToken } from "$tests/mocks/ckbtc-accounts.mock"; +import { mockCkETHToken as CkETHToken } from "$tests/mocks/cketh-accounts.mock"; +import { + ckBTCTokenBase, + ckETHTokenBase, + createIcpUserToken, + createUserToken, +} from "$tests/mocks/tokens-page.mock"; +import { TokensCardPo } from "$tests/page-objects/TokensCard.page-object"; +import { JestPageObjectElement } from "$tests/page-objects/jest.page-object"; +import { ICPToken, TokenAmountV2 } from "@dfinity/utils"; +import { render } from "@testing-library/svelte"; + +describe("ProjectsCard", () => { + const renderComponent = (props: { + topProjects: TableProject[]; + usdAmount: number; + }) => { + const { container } = render(ProjectsCard, { + props, + }); + + return TokensCardPo.under(new JestPageObjectElement(container)); + }; + + describe("when not signed in", () => { + const mockIcpToken = createIcpUserToken(); + const mockCkBTCToken = createUserToken(ckBTCTokenBase); + const mockCkETHToken = createUserToken(ckETHTokenBase); + + const mockTokens = [ + mockIcpToken, + mockCkBTCToken, + mockCkETHToken, + ] as UserTokenData[]; + + beforeEach(() => { + setNoIdentity(); + }); + + it("should show placeholder balance", async () => { + const po = renderComponent({ + topTokens: mockTokens, + usdAmount: 0, + }); + + expect(await po.getAmount()).toBe("$-/-"); + }); + + it("should show list of tokens with name and balance", async () => { + const po = renderComponent({ + topTokens: mockTokens, + usdAmount: 0, + }); + const titles = await po.getTokensTitles(); + const balances = await po.getTokensUsdBalances(); + + expect(titles.length).toBe(3); + expect(titles).toEqual(["Internet Computer", "ckBTC", "ckETH"]); + + expect(balances.length).toBe(3); + expect(balances).toEqual(["$0.00", "$0.00", "$0.00"]); + }); + }); + + describe("when signed in", () => { + const mockIcpToken = createIcpUserToken(); + mockIcpToken.balanceInUsd = 100; + mockIcpToken.balance = TokenAmountV2.fromUlps({ + amount: 2160000000n, + token: ICPToken, + }); + + const mockCkBTCToken = createUserToken(ckBTCTokenBase); + mockCkBTCToken.balanceInUsd = 200; + mockCkBTCToken.balance = TokenAmountV2.fromUlps({ + amount: 2160000000n, + token: CkBTCToken, + }); + + const mockCkETHToken = createUserToken(ckETHTokenBase); + mockCkETHToken.balanceInUsd = 300; + mockCkETHToken.balance = TokenAmountV2.fromUlps({ + amount: 21600000000000000000n, + token: CkETHToken, + }); + + const mockTokens = [ + mockIcpToken, + mockCkBTCToken, + mockCkETHToken, + ] as UserTokenData[]; + + beforeEach(() => { + resetIdentity(); + }); + + it("should show the usd amount", async () => { + const po = renderComponent({ + topTokens: mockTokens, + usdAmount: 600, + }); + + expect(await po.getAmount()).toBe("$600.00"); + }); + + it("should show all the tokens with their balance", async () => { + const po = renderComponent({ + topTokens: mockTokens, + usdAmount: 600, + }); + const titles = await po.getTokensTitles(); + const usdBalances = await po.getTokensUsdBalances(); + const nativeBalances = await po.getTokensNativeBalances(); + + expect(titles.length).toBe(3); + expect(titles).toEqual(["Internet Computer", "ckBTC", "ckETH"]); + + expect(usdBalances.length).toBe(3); + expect(usdBalances).toEqual(["$100.00", "$200.00", "$300.00"]); + + expect(nativeBalances.length).toBe(3); + expect(nativeBalances).toEqual([ + "21.60 ICP", + "21.60 ckBTC", + "21.60 ckETH", + ]); + }); + + it("should not show info row when tokens length is 3 or more", async () => { + const po = renderComponent({ + topTokens: mockTokens.slice(0, 3), + usdAmount: 600, + }); + const titles = await po.getTokensTitles(); + const balances = await po.getTokensUsdBalances(); + const nativeBalances = await po.getTokensNativeBalances(); + + expect(titles.length).toBe(3); + expect(titles).toEqual(["Internet Computer", "ckBTC", "ckETH"]); + + expect(balances.length).toBe(3); + expect(balances).toEqual(["$100.00", "$200.00", "$300.00"]); + + expect(nativeBalances.length).toBe(3); + expect(nativeBalances).toEqual([ + "21.60 ICP", + "21.60 ckBTC", + "21.60 ckETH", + ]); + + expect(await po.getInfoRow().isPresent()).toBe(false); + }); + + it("should show info row when tokens length is 1", async () => { + const po = renderComponent({ + topTokens: mockTokens.slice(0, 1), + usdAmount: 100, + }); + + const titles = await po.getTokensTitles(); + const balances = await po.getTokensUsdBalances(); + const nativeBalances = await po.getTokensNativeBalances(); + + expect(titles.length).toBe(1); + expect(titles).toEqual(["Internet Computer"]); + + expect(balances.length).toBe(1); + expect(balances).toEqual(["$100.00"]); + + expect(nativeBalances.length).toBe(1); + expect(nativeBalances).toEqual(["21.60 ICP"]); + + expect(await po.getInfoRow().isPresent()).toBe(true); + }); + + it("should show info row when tokens length is 2", async () => { + const po = renderComponent({ + topTokens: mockTokens.slice(0, 2), + usdAmount: 300, + }); + + const titles = await po.getTokensTitles(); + const balances = await po.getTokensUsdBalances(); + const nativeBalances = await po.getTokensNativeBalances(); + + expect(titles.length).toBe(2); + expect(titles).toEqual(["Internet Computer", "ckBTC"]); + + expect(balances.length).toBe(2); + expect(balances).toEqual(["$100.00", "$200.00"]); + + expect(nativeBalances.length).toBe(2); + expect(nativeBalances).toEqual(["21.60 ICP", "21.60 ckBTC"]); + + expect(await po.getInfoRow().isPresent()).toBe(true); + }); + }); +}); diff --git a/frontend/src/tests/page-objects/ProjectsCard.page-object.ts b/frontend/src/tests/page-objects/ProjectsCard.page-object.ts new file mode 100644 index 0000000000..6253065823 --- /dev/null +++ b/frontend/src/tests/page-objects/ProjectsCard.page-object.ts @@ -0,0 +1,62 @@ +import { BasePageObject } from "$tests/page-objects/base.page-object"; +import type { PageObjectElement } from "$tests/types/page-object.types"; + +class ProjectsCardRoPo extends BasePageObject { + private static readonly TID = "card-row"; + + static async allUnder( + element: PageObjectElement + ): Promise { + const rows = await element.allByTestId(ProjectsCardRoPo.TID); + return rows.map((el) => new ProjectsCardRoPo(el)); + } + + getTokenTitle(): Promise { + return this.getText("token-title"); + } + + getTokenNativeBalance(): Promise { + return this.getText("token-native-balance"); + } + + getTokenUsdBalance(): Promise { + return this.getText("token-usd-balance"); + } +} + +export class TokensCardPo extends BasePageObject { + private static readonly TID = "tokens-card"; + + static under(element: PageObjectElement): TokensCardPo { + return new TokensCardPo(element.byTestId(TokensCardPo.TID)); + } + + async getRows(): Promise { + return ProjectsCardRoPo.allUnder(this.root); + } + + getAmount(): Promise { + return this.getText("amount"); + } + + getInfoRow(): PageObjectElement { + return this.getElement("info-row"); + } + + async getTokensTitles(): Promise { + const rowsPos = await this.getRows(); + return Promise.all(rowsPos.map((po) => po.getTokenTitle())); + } + + async getTokensUsdBalances(): Promise { + const rows = await this.getRows(); + + return Promise.all(rows.map((row) => row.getTokenUsdBalance())); + } + + async getTokensNativeBalances(): Promise { + const rows = await this.getRows(); + + return Promise.all(rows.map((row) => row.getTokenNativeBalance())); + } +} From 2f233862c80024655e1957c823f39bf64c1d136b Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Wed, 15 Jan 2025 17:05:39 +0100 Subject: [PATCH 2/6] tests --- .../components/portfolio/ProjectsCard.svelte | 53 +-- .../components/portfolio/ProjectsCard.spec.ts | 336 +++++++++++------- .../page-objects/ProjectsCard.page-object.ts | 47 ++- 3 files changed, 274 insertions(+), 162 deletions(-) diff --git a/frontend/src/lib/components/portfolio/ProjectsCard.svelte b/frontend/src/lib/components/portfolio/ProjectsCard.svelte index 817603f00b..4765d86083 100644 --- a/frontend/src/lib/components/portfolio/ProjectsCard.svelte +++ b/frontend/src/lib/components/portfolio/ProjectsCard.svelte @@ -14,6 +14,7 @@ export let topProjects: TableProject[]; export let usdAmount: number; + export let numberOfTopTokens: number; const href = AppPath.Staking; let usdAmountFormatted: string; @@ -21,9 +22,9 @@ ? formatNumber(usdAmount) : PRICE_NOT_AVAILABLE_PLACEHOLDER; - // TODO: This will also depend on the number of tokens + const numberOfTopProjects = topProjects.length; let showInfoRow: boolean; - $: showInfoRow = topProjects.length > 0 && topProjects.length <= 3; + $: showInfoRow = numberOfTopTokens - numberOfTopProjects > 0; @@ -90,7 +91,7 @@ framed /> - {project.title} + {project.title}
@@ -104,27 +105,27 @@ {/if}
${formatNumber(project?.stakeInUsd ?? 0)}
- {#if project.stake instanceof TokenAmountV2} -
- {formatTokenV2({ - value: project.stake, - detailed: true, - })} - {project.stake.token.symbol} -
- {/if} +
+ {project.stake instanceof TokenAmountV2 + ? formatTokenV2({ + value: project.stake, + detailed: true, + }) + : PRICE_NOT_AVAILABLE_PLACEHOLDER} + {project.stake.token.symbol} +
{/each} {#if showInfoRow} @@ -239,8 +240,8 @@ } .maturity, - .native-balance, - .usd-balance { + .staked-usd, + .staked-native { justify-self: end; text-align: right; } @@ -256,7 +257,11 @@ } } - .native-balance { + .staked-usd { + grid-area: usd; + } + + .staked-native { display: none; grid-area: native; font-size: 0.875rem; @@ -266,10 +271,6 @@ display: block; } } - - .usd-balance { - grid-area: usd; - } } } diff --git a/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts b/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts index 081e9538b1..35c0a67df4 100644 --- a/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts +++ b/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts @@ -1,99 +1,169 @@ import ProjectsCard from "$lib/components/portfolio/ProjectsCard.svelte"; +import { NNS_TOKEN_DATA } from "$lib/constants/tokens.constants"; import type { TableProject } from "$lib/types/staking"; -import type { UserTokenData } from "$lib/types/tokens-page"; +import { UnavailableTokenAmount } from "$lib/utils/token.utils"; import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock"; -import { mockCkBTCToken as CkBTCToken } from "$tests/mocks/ckbtc-accounts.mock"; -import { mockCkETHToken as CkETHToken } from "$tests/mocks/cketh-accounts.mock"; -import { - ckBTCTokenBase, - ckETHTokenBase, - createIcpUserToken, - createUserToken, -} from "$tests/mocks/tokens-page.mock"; -import { TokensCardPo } from "$tests/page-objects/TokensCard.page-object"; +import { mockToken } from "$tests/mocks/sns-projects.mock"; +import { mockTableProject } from "$tests/mocks/staking.mock"; +import { ProjectsCardPo } from "$tests/page-objects/ProjectsCard.page-object"; import { JestPageObjectElement } from "$tests/page-objects/jest.page-object"; import { ICPToken, TokenAmountV2 } from "@dfinity/utils"; import { render } from "@testing-library/svelte"; describe("ProjectsCard", () => { - const renderComponent = (props: { - topProjects: TableProject[]; - usdAmount: number; - }) => { + const renderComponent = ({ + topProjects = [], + usdAmount = 0, + numberOfTopTokens = 0, + }: { + topProjects?: TableProject[]; + usdAmount?: number; + numberOfTopTokens?: number; + } = {}) => { const { container } = render(ProjectsCard, { - props, + props: { + topProjects, + usdAmount, + numberOfTopTokens, + }, }); - return TokensCardPo.under(new JestPageObjectElement(container)); + return ProjectsCardPo.under(new JestPageObjectElement(container)); }; describe("when not signed in", () => { - const mockIcpToken = createIcpUserToken(); - const mockCkBTCToken = createUserToken(ckBTCTokenBase); - const mockCkETHToken = createUserToken(ckETHTokenBase); - - const mockTokens = [ - mockIcpToken, - mockCkBTCToken, - mockCkETHToken, - ] as UserTokenData[]; + const icpProject: TableProject = { + ...mockTableProject, + stakeInUsd: undefined, + domKey: "/staking/icp", + stake: new UnavailableTokenAmount(NNS_TOKEN_DATA), + }; + const tableProject1: TableProject = { + ...mockTableProject, + title: "Project 1", + stakeInUsd: undefined, + domKey: "/staking/1", + stake: new UnavailableTokenAmount(mockToken), + }; + const tableProject2: TableProject = { + ...mockTableProject, + title: "Project 2", + stakeInUsd: undefined, + domKey: "/staking/2", + stake: new UnavailableTokenAmount(mockToken), + }; + + const tableProject3: TableProject = { + ...mockTableProject, + title: "Project 3", + stakeInUsd: undefined, + domKey: "/staking/3", + stake: new UnavailableTokenAmount(mockToken), + }; + + const mockProjects: TableProject[] = [ + icpProject, + tableProject1, + tableProject2, + tableProject3, + ]; beforeEach(() => { setNoIdentity(); }); it("should show placeholder balance", async () => { - const po = renderComponent({ - topTokens: mockTokens, - usdAmount: 0, - }); + const po = renderComponent(); expect(await po.getAmount()).toBe("$-/-"); }); - it("should show list of tokens with name and balance", async () => { + it("should list of tokens with placeholders", async () => { const po = renderComponent({ - topTokens: mockTokens, - usdAmount: 0, + topProjects: mockProjects, }); - const titles = await po.getTokensTitles(); - const balances = await po.getTokensUsdBalances(); - - expect(titles.length).toBe(3); - expect(titles).toEqual(["Internet Computer", "ckBTC", "ckETH"]); + const titles = await po.getProjectsTitle(); + const maturities = await po.getProjectsMaturity(); + const stakesInUsd = await po.getProjectsStakeInUsd(); + const stakesInNativeCurrency = + await po.getProjectsStakeInNativeCurrency(); + + expect(titles.length).toBe(4); + expect(titles).toEqual([ + "Internet Computer", + "Project 1", + "Project 2", + "Project 3", + ]); - expect(balances.length).toBe(3); - expect(balances).toEqual(["$0.00", "$0.00", "$0.00"]); - }); - }); + expect(maturities.length).toBe(4); + expect(maturities).toEqual(["-/-", "-/-", "-/-", "-/-"]); - describe("when signed in", () => { - const mockIcpToken = createIcpUserToken(); - mockIcpToken.balanceInUsd = 100; - mockIcpToken.balance = TokenAmountV2.fromUlps({ - amount: 2160000000n, - token: ICPToken, - }); + expect(stakesInUsd.length).toBe(4); + expect(stakesInUsd).toEqual(["$0.00", "$0.00", "$0.00", "$0.00"]); - const mockCkBTCToken = createUserToken(ckBTCTokenBase); - mockCkBTCToken.balanceInUsd = 200; - mockCkBTCToken.balance = TokenAmountV2.fromUlps({ - amount: 2160000000n, - token: CkBTCToken, - }); + expect(stakesInNativeCurrency.length).toBe(4); + expect(stakesInNativeCurrency).toEqual([ + "-/- ICP", + "-/- TET", + "-/- TET", + "-/- TET", + ]); - const mockCkETHToken = createUserToken(ckETHTokenBase); - mockCkETHToken.balanceInUsd = 300; - mockCkETHToken.balance = TokenAmountV2.fromUlps({ - amount: 21600000000000000000n, - token: CkETHToken, + expect(await po.getInfoRow().isPresent()).toBe(false); }); + }); - const mockTokens = [ - mockIcpToken, - mockCkBTCToken, - mockCkETHToken, - ] as UserTokenData[]; + describe("when signed in", () => { + const icpProject: TableProject = { + ...mockTableProject, + stakeInUsd: 100, + domKey: "/staking/icp", + stake: TokenAmountV2.fromUlps({ + amount: 1000_000n, + token: ICPToken, + }), + availableMaturity: 1_000_000_000n, + stakedMaturity: 1_000_000_000n, + }; + const tableProject1: TableProject = { + ...mockTableProject, + title: "Project 1", + stakeInUsd: 200, + domKey: "/staking/1", + stake: TokenAmountV2.fromUlps({ + amount: 1000_000n, + token: mockToken, + }), + }; + const tableProject2: TableProject = { + ...mockTableProject, + title: "Project 2", + stakeInUsd: 300, + domKey: "/staking/2", + stake: TokenAmountV2.fromUlps({ + amount: 1000_000n, + token: mockToken, + }), + }; + + const tableProject3: TableProject = { + ...mockTableProject, + title: "Project 3", + stakeInUsd: 400, + domKey: "/staking/3", + stake: TokenAmountV2.fromUlps({ + amount: 1000_000n, + token: mockToken, + }), + }; + + const mockProjects: TableProject[] = [ + icpProject, + tableProject1, + tableProject2, + tableProject3, + ]; beforeEach(() => { resetIdentity(); @@ -101,101 +171,127 @@ describe("ProjectsCard", () => { it("should show the usd amount", async () => { const po = renderComponent({ - topTokens: mockTokens, - usdAmount: 600, + usdAmount: 5000, }); - expect(await po.getAmount()).toBe("$600.00"); + expect(await po.getAmount()).toBe("$5’000.00"); }); - it("should show all the tokens with their balance", async () => { + it("should show all the projects with their maturity, staked in usd and staked in native currency", async () => { const po = renderComponent({ - topTokens: mockTokens, - usdAmount: 600, + topProjects: mockProjects, }); - const titles = await po.getTokensTitles(); - const usdBalances = await po.getTokensUsdBalances(); - const nativeBalances = await po.getTokensNativeBalances(); - expect(titles.length).toBe(3); - expect(titles).toEqual(["Internet Computer", "ckBTC", "ckETH"]); + const titles = await po.getProjectsTitle(); + const maturities = await po.getProjectsMaturity(); + const stakesInUsd = await po.getProjectsStakeInUsd(); + const stakesInNativeCurrency = + await po.getProjectsStakeInNativeCurrency(); + + expect(titles.length).toBe(4); + expect(titles).toEqual([ + "Internet Computer", + "Project 1", + "Project 2", + "Project 3", + ]); + + expect(maturities.length).toBe(4); + expect(maturities).toEqual(["20.00", "0", "0", "0"]); - expect(usdBalances.length).toBe(3); - expect(usdBalances).toEqual(["$100.00", "$200.00", "$300.00"]); + expect(stakesInUsd.length).toBe(4); + expect(stakesInUsd).toEqual(["$100.00", "$200.00", "$300.00", "$400.00"]); - expect(nativeBalances.length).toBe(3); - expect(nativeBalances).toEqual([ - "21.60 ICP", - "21.60 ckBTC", - "21.60 ckETH", + expect(stakesInNativeCurrency.length).toBe(4); + expect(stakesInNativeCurrency).toEqual([ + "0.01 ICP", + "0.01 TET", + "0.01 TET", + "0.01 TET", ]); }); - it("should not show info row when tokens length is 3 or more", async () => { + it("should not show info row when numberOfTopTokens is the same as the number of topProjects", async () => { const po = renderComponent({ - topTokens: mockTokens.slice(0, 3), - usdAmount: 600, + topProjects: mockProjects.slice(0, 3), + numberOfTopTokens: 3, }); - const titles = await po.getTokensTitles(); - const balances = await po.getTokensUsdBalances(); - const nativeBalances = await po.getTokensNativeBalances(); + + const titles = await po.getProjectsTitle(); + const maturities = await po.getProjectsMaturity(); + const stakesInUsd = await po.getProjectsStakeInUsd(); + const stakesInNativeCurrency = + await po.getProjectsStakeInNativeCurrency(); expect(titles.length).toBe(3); - expect(titles).toEqual(["Internet Computer", "ckBTC", "ckETH"]); + expect(titles).toEqual(["Internet Computer", "Project 1", "Project 2"]); + + expect(maturities.length).toBe(3); + expect(maturities).toEqual(["20.00", "0", "0"]); - expect(balances.length).toBe(3); - expect(balances).toEqual(["$100.00", "$200.00", "$300.00"]); + expect(stakesInUsd.length).toBe(3); + expect(stakesInUsd).toEqual(["$100.00", "$200.00", "$300.00"]); - expect(nativeBalances.length).toBe(3); - expect(nativeBalances).toEqual([ - "21.60 ICP", - "21.60 ckBTC", - "21.60 ckETH", + expect(stakesInNativeCurrency.length).toBe(3); + expect(stakesInNativeCurrency).toEqual([ + "0.01 ICP", + "0.01 TET", + "0.01 TET", ]); expect(await po.getInfoRow().isPresent()).toBe(false); }); - it("should show info row when tokens length is 1", async () => { + it("should not show info row when the number of topProjects is less than numberOfTopTokens like 1", async () => { const po = renderComponent({ - topTokens: mockTokens.slice(0, 1), - usdAmount: 100, + topProjects: mockProjects.slice(0, 2), + numberOfTopTokens: 4, }); - const titles = await po.getTokensTitles(); - const balances = await po.getTokensUsdBalances(); - const nativeBalances = await po.getTokensNativeBalances(); + const titles = await po.getProjectsTitle(); + const maturities = await po.getProjectsMaturity(); + const stakesInUsd = await po.getProjectsStakeInUsd(); + const stakesInNativeCurrency = + await po.getProjectsStakeInNativeCurrency(); - expect(titles.length).toBe(1); - expect(titles).toEqual(["Internet Computer"]); + expect(titles.length).toBe(2); + expect(titles).toEqual(["Internet Computer", "Project 1"]); - expect(balances.length).toBe(1); - expect(balances).toEqual(["$100.00"]); + expect(maturities.length).toBe(2); + expect(maturities).toEqual(["20.00", "0"]); - expect(nativeBalances.length).toBe(1); - expect(nativeBalances).toEqual(["21.60 ICP"]); + expect(stakesInUsd.length).toBe(2); + expect(stakesInUsd).toEqual(["$100.00", "$200.00"]); + + expect(stakesInNativeCurrency.length).toBe(2); + expect(stakesInNativeCurrency).toEqual(["0.01 ICP", "0.01 TET"]); expect(await po.getInfoRow().isPresent()).toBe(true); }); - it("should show info row when tokens length is 2", async () => { + it("should not show info row when the number of topProjects is less than numberOfTopTokens like 2", async () => { const po = renderComponent({ - topTokens: mockTokens.slice(0, 2), - usdAmount: 300, + topProjects: mockProjects.slice(0, 1), + numberOfTopTokens: 3, }); - const titles = await po.getTokensTitles(); - const balances = await po.getTokensUsdBalances(); - const nativeBalances = await po.getTokensNativeBalances(); + const titles = await po.getProjectsTitle(); + const maturities = await po.getProjectsMaturity(); + const stakesInUsd = await po.getProjectsStakeInUsd(); + const stakesInNativeCurrency = + await po.getProjectsStakeInNativeCurrency(); - expect(titles.length).toBe(2); - expect(titles).toEqual(["Internet Computer", "ckBTC"]); + expect(titles.length).toBe(1); + expect(titles).toEqual(["Internet Computer"]); + + expect(maturities.length).toBe(1); + expect(maturities).toEqual(["20.00"]); - expect(balances.length).toBe(2); - expect(balances).toEqual(["$100.00", "$200.00"]); + expect(stakesInUsd.length).toBe(1); + expect(stakesInUsd).toEqual(["$100.00"]); - expect(nativeBalances.length).toBe(2); - expect(nativeBalances).toEqual(["21.60 ICP", "21.60 ckBTC"]); + expect(stakesInNativeCurrency.length).toBe(1); + expect(stakesInNativeCurrency).toEqual(["0.01 ICP"]); expect(await po.getInfoRow().isPresent()).toBe(true); }); diff --git a/frontend/src/tests/page-objects/ProjectsCard.page-object.ts b/frontend/src/tests/page-objects/ProjectsCard.page-object.ts index 6253065823..2b09835c4e 100644 --- a/frontend/src/tests/page-objects/ProjectsCard.page-object.ts +++ b/frontend/src/tests/page-objects/ProjectsCard.page-object.ts @@ -1,8 +1,10 @@ import { BasePageObject } from "$tests/page-objects/base.page-object"; import type { PageObjectElement } from "$tests/types/page-object.types"; +import { nonNullish } from "@dfinity/utils"; +import { MaturityWithTooltipPo } from "./MaturityWithTooltip.page-object"; class ProjectsCardRoPo extends BasePageObject { - private static readonly TID = "card-row"; + private static readonly TID = "project-card-row"; static async allUnder( element: PageObjectElement @@ -12,23 +14,31 @@ class ProjectsCardRoPo extends BasePageObject { } getTokenTitle(): Promise { - return this.getText("token-title"); + return this.getText("project-title"); } - getTokenNativeBalance(): Promise { - return this.getText("token-native-balance"); + async getProjectMaturity(): Promise { + const maturityWithTooltipPo = MaturityWithTooltipPo.under(this.root); + const totalMaturity = await maturityWithTooltipPo.getTotalMaturity(); + + if (nonNullish(totalMaturity)) return totalMaturity; + return this.getText("project-maturity"); + } + + getProjectStakeInUsd(): Promise { + return this.getText("project-staked-usd"); } - getTokenUsdBalance(): Promise { - return this.getText("token-usd-balance"); + getProjectStakeInNativeCurrency(): Promise { + return this.getText("project-staked-native"); } } -export class TokensCardPo extends BasePageObject { - private static readonly TID = "tokens-card"; +export class ProjectsCardPo extends BasePageObject { + private static readonly TID = "projects-card"; - static under(element: PageObjectElement): TokensCardPo { - return new TokensCardPo(element.byTestId(TokensCardPo.TID)); + static under(element: PageObjectElement): ProjectsCardPo { + return new ProjectsCardPo(element.byTestId(ProjectsCardPo.TID)); } async getRows(): Promise { @@ -43,20 +53,25 @@ export class TokensCardPo extends BasePageObject { return this.getElement("info-row"); } - async getTokensTitles(): Promise { + async getProjectsTitle(): Promise { const rowsPos = await this.getRows(); return Promise.all(rowsPos.map((po) => po.getTokenTitle())); } - async getTokensUsdBalances(): Promise { + async getProjectsMaturity(): Promise { const rows = await this.getRows(); - - return Promise.all(rows.map((row) => row.getTokenUsdBalance())); + return Promise.all(rows.map((row) => row.getProjectMaturity())); } - async getTokensNativeBalances(): Promise { + async getProjectsStakeInUsd(): Promise { const rows = await this.getRows(); + return Promise.all(rows.map((row) => row.getProjectStakeInUsd())); + } - return Promise.all(rows.map((row) => row.getTokenNativeBalance())); + async getProjectsStakeInNativeCurrency(): Promise { + const rows = await this.getRows(); + return Promise.all( + rows.map((row) => row.getProjectStakeInNativeCurrency()) + ); } } From 6b63255d8c346968ecf96aef4ff235859e4032f5 Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Wed, 15 Jan 2025 17:13:42 +0100 Subject: [PATCH 3/6] fix import --- frontend/src/lib/components/portfolio/ProjectsCard.svelte | 4 +++- frontend/src/tests/page-objects/ProjectsCard.page-object.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/portfolio/ProjectsCard.svelte b/frontend/src/lib/components/portfolio/ProjectsCard.svelte index 4765d86083..318f0bcea6 100644 --- a/frontend/src/lib/components/portfolio/ProjectsCard.svelte +++ b/frontend/src/lib/components/portfolio/ProjectsCard.svelte @@ -22,7 +22,9 @@ ? formatNumber(usdAmount) : PRICE_NOT_AVAILABLE_PLACEHOLDER; - const numberOfTopProjects = topProjects.length; + let numberOfTopProjects: number; + $: numberOfTopProjects = topProjects.length; + let showInfoRow: boolean; $: showInfoRow = numberOfTopTokens - numberOfTopProjects > 0; diff --git a/frontend/src/tests/page-objects/ProjectsCard.page-object.ts b/frontend/src/tests/page-objects/ProjectsCard.page-object.ts index 2b09835c4e..628ca24713 100644 --- a/frontend/src/tests/page-objects/ProjectsCard.page-object.ts +++ b/frontend/src/tests/page-objects/ProjectsCard.page-object.ts @@ -1,7 +1,7 @@ +import { MaturityWithTooltipPo } from "$tests/page-objects/MaturityWithTooltip.page-object"; import { BasePageObject } from "$tests/page-objects/base.page-object"; import type { PageObjectElement } from "$tests/types/page-object.types"; import { nonNullish } from "@dfinity/utils"; -import { MaturityWithTooltipPo } from "./MaturityWithTooltip.page-object"; class ProjectsCardRoPo extends BasePageObject { private static readonly TID = "project-card-row"; From 8712d3af14720cc81f5f35e0db8ae74524cb1797 Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Wed, 15 Jan 2025 17:14:56 +0100 Subject: [PATCH 4/6] fix import --- frontend/src/lib/components/portfolio/ProjectsCard.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/portfolio/ProjectsCard.svelte b/frontend/src/lib/components/portfolio/ProjectsCard.svelte index 318f0bcea6..5c0c027877 100644 --- a/frontend/src/lib/components/portfolio/ProjectsCard.svelte +++ b/frontend/src/lib/components/portfolio/ProjectsCard.svelte @@ -1,4 +1,5 @@ @@ -199,7 +202,6 @@ .header { display: grid; grid-template-columns: 1fr 1fr; - justify-content: space-between; font-size: 0.875rem; color: var(--text-description); From 90788cc1369fb18788170fdf7a5724c108a7ce71 Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Thu, 16 Jan 2025 11:58:49 +0100 Subject: [PATCH 6/6] rename --- .../components/portfolio/ProjectsCard.svelte | 34 +++++++++---------- .../components/portfolio/ProjectsCard.spec.ts | 34 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/frontend/src/lib/components/portfolio/ProjectsCard.svelte b/frontend/src/lib/components/portfolio/ProjectsCard.svelte index 5cdd1c77dc..28ceffbf68 100644 --- a/frontend/src/lib/components/portfolio/ProjectsCard.svelte +++ b/frontend/src/lib/components/portfolio/ProjectsCard.svelte @@ -12,9 +12,9 @@ import { IconNeuronsPage, IconRight } from "@dfinity/gix-components"; import { TokenAmountV2 } from "@dfinity/utils"; - export let topProjects: TableProject[]; + export let topStakedTokens: TableProject[]; export let usdAmount: number; - export let numberOfTopTokens: number; + export let numberOfTopHeldTokens: number; const href = AppPath.Staking; let usdAmountFormatted: string; @@ -22,14 +22,14 @@ ? formatNumber(usdAmount) : PRICE_NOT_AVAILABLE_PLACEHOLDER; - let numberOfTopProjects: number; - $: numberOfTopProjects = topProjects.length; + let numberOfTopStakedTokens: number; + $: numberOfTopStakedTokens = topStakedTokens.length; // Show an informational row when there are fewer projects than tokens. // This ensures both cards have consistent heights by filling empty space // with a message instead of leaving blank space. let showInfoRow: boolean; - $: showInfoRow = numberOfTopTokens - numberOfTopProjects > 0; + $: showInfoRow = numberOfTopHeldTokens - numberOfTopStakedTokens > 0; @@ -85,25 +85,25 @@
- {#each topProjects as project (project.domKey)} + {#each topStakedTokens as stakedToken (stakedToken.domKey)}
- {project.title} + {stakedToken.title}
{#if $authSignedInStore} {:else} {PRICE_NOT_AVAILABLE_PLACEHOLDER} @@ -113,23 +113,23 @@ class="staked-usd" data-tid="project-staked-usd" role="cell" - aria-label={`${project.title} USD: ${project?.stakeInUsd ?? 0}`} + aria-label={`${stakedToken.title} USD: ${stakedToken?.stakeInUsd ?? 0}`} > - ${formatNumber(project?.stakeInUsd ?? 0)} + ${formatNumber(stakedToken?.stakeInUsd ?? 0)}
- {project.stake instanceof TokenAmountV2 + {stakedToken.stake instanceof TokenAmountV2 ? formatTokenV2({ - value: project.stake, + value: stakedToken.stake, detailed: true, }) : PRICE_NOT_AVAILABLE_PLACEHOLDER} - {project.stake.token.symbol} + {stakedToken.stake.token.symbol}
{/each} diff --git a/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts b/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts index 35c0a67df4..ff6011c3d4 100644 --- a/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts +++ b/frontend/src/tests/lib/components/portfolio/ProjectsCard.spec.ts @@ -12,19 +12,19 @@ import { render } from "@testing-library/svelte"; describe("ProjectsCard", () => { const renderComponent = ({ - topProjects = [], + topStakedTokens = [], usdAmount = 0, - numberOfTopTokens = 0, + numberOfTopHeldTokens = 0, }: { - topProjects?: TableProject[]; + topStakedTokens?: TableProject[]; usdAmount?: number; - numberOfTopTokens?: number; + numberOfTopHeldTokens?: number; } = {}) => { const { container } = render(ProjectsCard, { props: { - topProjects, + topStakedTokens, usdAmount, - numberOfTopTokens, + numberOfTopHeldTokens, }, }); @@ -80,7 +80,7 @@ describe("ProjectsCard", () => { it("should list of tokens with placeholders", async () => { const po = renderComponent({ - topProjects: mockProjects, + topStakedTokens: mockProjects, }); const titles = await po.getProjectsTitle(); const maturities = await po.getProjectsMaturity(); @@ -179,7 +179,7 @@ describe("ProjectsCard", () => { it("should show all the projects with their maturity, staked in usd and staked in native currency", async () => { const po = renderComponent({ - topProjects: mockProjects, + topStakedTokens: mockProjects, }); const titles = await po.getProjectsTitle(); @@ -211,10 +211,10 @@ describe("ProjectsCard", () => { ]); }); - it("should not show info row when numberOfTopTokens is the same as the number of topProjects", async () => { + it("should not show info row when numberOfTopHeldTokens is the same as the number of topStakedTokens", async () => { const po = renderComponent({ - topProjects: mockProjects.slice(0, 3), - numberOfTopTokens: 3, + topStakedTokens: mockProjects.slice(0, 3), + numberOfTopHeldTokens: 3, }); const titles = await po.getProjectsTitle(); @@ -242,10 +242,10 @@ describe("ProjectsCard", () => { expect(await po.getInfoRow().isPresent()).toBe(false); }); - it("should not show info row when the number of topProjects is less than numberOfTopTokens like 1", async () => { + it("should not show info row when the number of topStakedTokens is less than numberOfTopHeldTokens like 1", async () => { const po = renderComponent({ - topProjects: mockProjects.slice(0, 2), - numberOfTopTokens: 4, + topStakedTokens: mockProjects.slice(0, 2), + numberOfTopHeldTokens: 4, }); const titles = await po.getProjectsTitle(); @@ -269,10 +269,10 @@ describe("ProjectsCard", () => { expect(await po.getInfoRow().isPresent()).toBe(true); }); - it("should not show info row when the number of topProjects is less than numberOfTopTokens like 2", async () => { + it("should not show info row when the number of topStakedTokens is less than numberOfTopHeldTokens like 2", async () => { const po = renderComponent({ - topProjects: mockProjects.slice(0, 1), - numberOfTopTokens: 3, + topStakedTokens: mockProjects.slice(0, 1), + numberOfTopHeldTokens: 3, }); const titles = await po.getProjectsTitle();