Skip to content

Commit

Permalink
feat(portfolio): integrate total assets card into the Portfolio page (#…
Browse files Browse the repository at this point in the history
…6180)

# Motivation

We want to display the total value of user assets on the Portfolio page.
The value will be shown in USD and ICP, along with a tooltip to
calculate the portfolio's value in ICP.

# Changes

- Adds `TotalAssetsCard` to the Portfolio page

# Tests

- Adds unit tests to the Portfolio page to ensure the component renders
correctly.
- Adds unit tests to the Portfolio route to verify that all components
render as expected.

# Todos

- [ ] Add entry to changelog (if necessary).
Not necessary
  • Loading branch information
yhabib authored Jan 18, 2025
1 parent 67f0c0b commit 82cfb96
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 70 deletions.
7 changes: 3 additions & 4 deletions frontend/src/lib/pages/Portfolio.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import NoHeldTokensCard from "$lib/components/portfolio/NoHeldTokensCard.svelte";
import NoStakedTokensCard from "$lib/components/portfolio/NoStakedTokensCard.svelte";
import StakedTokensCard from "$lib/components/portfolio/StakedTokensCard.svelte";
import UsdValueBanner from "$lib/components/ui/UsdValueBanner.svelte";
import TotalAssetsCard from "$lib/components/portfolio/TotalAssetsCard.svelte";
import { authSignedInStore } from "$lib/derived/auth.derived";
import type { TableProject } from "$lib/types/staking";
import type { UserToken, UserTokenData } from "$lib/types/tokens-page";
Expand Down Expand Up @@ -77,7 +77,7 @@
{#if !$authSignedInStore}
<LoginCard />
{/if}
<UsdValueBanner
<TotalAssetsCard
usdAmount={totalUsdAmount}
hasUnpricedTokens={hasUnpricedTokensOrStake}
/>
Expand Down Expand Up @@ -141,8 +141,7 @@
@include media.min-width(large) {
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: min-content;
align-items: stretch;
grid-auto-rows: minmax(345px, min-content);
}
}
}
Expand Down
104 changes: 47 additions & 57 deletions frontend/src/tests/lib/pages/Portfolio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ describe("Portfolio page", () => {

expect(await po.getNoHeldTokensCard().isPresent()).toBe(true);
expect(await po.getHeldTokensCardPo().isPresent()).toBe(false);
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe("$0.00");
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$0.00"
);
});

it("should not display the card when the tokens accounts balance is not zero", async () => {
Expand All @@ -199,7 +201,9 @@ describe("Portfolio page", () => {

expect(await po.getNoHeldTokensCard().isPresent()).toBe(false);
expect(await po.getHeldTokensCardPo().isPresent()).toBe(true);
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe("$2.00");
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$2.00"
);
});
});

Expand Down Expand Up @@ -354,7 +358,9 @@ describe("Portfolio page", () => {
const po = renderPage();

expect(await po.getNoStakedTokensCarPo().isPresent()).toBe(true);
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe("$0.00");
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$0.00"
);
});

it("should not display the card when the neurons accounts balance is not zero", async () => {
Expand All @@ -365,7 +371,9 @@ describe("Portfolio page", () => {
const po = renderPage({ tableProjects: [tableProject] });

expect(await po.getNoStakedTokensCarPo().isPresent()).toBe(false);
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe("$2.00");
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$2.00"
);
});

it("should display a primary action when the neurons accounts balance is zero and the tokens balance is not zero", async () => {
Expand All @@ -377,7 +385,9 @@ describe("Portfolio page", () => {

expect(await po.getNoStakedTokensCarPo().isPresent()).toBe(true);
expect(await po.getNoStakedTokensCarPo().hasPrimaryAction()).toBe(true);
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe("$2.00");
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$2.00"
);
});

it("should not display a primary action when the neurons accounts balance is zero and the tokens balance is also zero", async () => {
Expand All @@ -387,86 +397,66 @@ describe("Portfolio page", () => {
expect(await po.getNoStakedTokensCarPo().hasPrimaryAction()).toBe(
false
);
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe("$0.00");
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$0.00"
);
});
});

describe("UsdValueBanner", () => {
const token1 = createUserToken({
universeId: principal(1),
balanceInUsd: 5,
});
const token2 = createUserToken({
universeId: principal(2),
balanceInUsd: 7,
});
const token3 = createUserToken({
universeId: principal(3),
balanceInUsd: undefined,
});

const tableProject1: TableProject = {
...mockTableProject,
stakeInUsd: 2,
domKey: "/project/1",
};
const tableProject2: TableProject = {
...mockTableProject,
stakeInUsd: 10.5,
domKey: "/project/2",
};
const tableProject3: TableProject = {
...mockTableProject,
stakeInUsd: undefined,
domKey: "/project/3",
};

describe("TotalAssetsCard", () => {
it("should display total assets", async () => {
const po = renderPage({
userTokens: [token1, token2],
tableProjects: [tableProject1, tableProject2],
tableProjects: [icpProject, tableProject1],
});

// There are two tokens with a balance of 5$ and 7$, and two projects with a staked balance of 2$ and 10.5$ -> 24.5$
expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe(
"$24.50"
// There are two tokens with a balance of 100$ and 200$, and two projects with a staked balance of 100$ and 200$ -> 600$
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$600.00"
);
// 1ICP == 10$
expect(await po.getUsdValueBannerPo().getSecondaryAmount()).toBe(
"2.45 ICP"
expect(await po.getTotalAssetsCardPo().getSecondaryAmount()).toBe(
"60.00 ICP"
);
expect(
await po.getUsdValueBannerPo().getTotalsTooltipIconPo().isPresent()
await po.getTotalAssetsCardPo().getTotalsTooltipIconPo().isPresent()
).toBe(false);
});

it("should ignore tokens with unknown balance in USD and display tooltip", async () => {
const po = renderPage({ userTokens: [token1, token2, token3] });
it("should ignore held tokens with unknown balance in USD and display tooltip", async () => {
const tokenNoBalance = createUserToken({
balanceInUsd: undefined,
});
const po = renderPage({ userTokens: [token1, token2, tokenNoBalance] });

expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe(
"$12.00"
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$300.00"
);
expect(await po.getUsdValueBannerPo().getSecondaryAmount()).toBe(
"1.20 ICP"
expect(await po.getTotalAssetsCardPo().getSecondaryAmount()).toBe(
"30.00 ICP"
);
expect(
await po.getUsdValueBannerPo().getTotalsTooltipIconPo().isPresent()
await po.getTotalAssetsCardPo().getTotalsTooltipIconPo().isPresent()
).toBe(true);
});

it("should ignore neurons with unknown balance in USD and display tooltip", async () => {
it("should ignore staked tokens with unknown balance in USD and display tooltip", async () => {
const projectNoBalance: TableProject = {
...mockTableProject,
stakeInUsd: undefined,
};
const po = renderPage({
tableProjects: [tableProject1, tableProject2, tableProject3],
tableProjects: [tableProject1, tableProject2, projectNoBalance],
});

expect(await po.getUsdValueBannerPo().getPrimaryAmount()).toBe(
"$12.50"
expect(await po.getTotalAssetsCardPo().getPrimaryAmount()).toBe(
"$500.00"
);
expect(await po.getUsdValueBannerPo().getSecondaryAmount()).toBe(
"1.25 ICP"
expect(await po.getTotalAssetsCardPo().getSecondaryAmount()).toBe(
"50.00 ICP"
);
expect(
await po.getUsdValueBannerPo().getTotalsTooltipIconPo().isPresent()
await po.getTotalAssetsCardPo().getTotalsTooltipIconPo().isPresent()
).toBe(true);
});
});
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/tests/page-objects/PortfolioPage.page-object.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HeldTokensCardPo } from "$tests/page-objects/HeldTokensCard.page-object";
import { NoStakedTokensCardPo } from "$tests/page-objects/NoStakedTokensCard.page-object";
import { UsdValueBannerPo } from "$tests/page-objects/UsdValueBanner.page-object";
import { TotalAssetsCardPo } from "$tests/page-objects/TotalAssetsCard.page-object";
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
import { StakedTokensCardPo } from "./StakedTokensCard.page-object";
Expand All @@ -24,8 +24,8 @@ export class PortfolioPagePo extends BasePageObject {
return NoStakedTokensCardPo.under(this.root);
}

getUsdValueBannerPo(): UsdValueBannerPo {
return UsdValueBannerPo.under(this.root);
getTotalAssetsCardPo(): TotalAssetsCardPo {
return TotalAssetsCardPo.under(this.root);
}

getHeldTokensCardPo(): HeldTokensCardPo {
Expand Down
72 changes: 66 additions & 6 deletions frontend/src/tests/routes/app/portfolio/page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ describe("Portfolio route", () => {
const nativeBalances =
await tokensCardPo.getHeldTokensBalanceInNativeCurrency();

expect(await portfolioPagePo.getUsdValueBannerPo().getPrimaryAmount()).toBe(
"$-/-"
);
expect(
await portfolioPagePo.getUsdValueBannerPo().getSecondaryAmount()
await portfolioPagePo.getTotalAssetsCardPo().getPrimaryAmount()
).toBe("$-/-");
expect(
await portfolioPagePo.getTotalAssetsCardPo().getSecondaryAmount()
).toBe("-/- ICP");

expect(titles.length).toBe(4);
Expand Down Expand Up @@ -354,12 +354,72 @@ describe("Portfolio route", () => {
// --------------------
// Total: $203’231.00
expect(
await portfolioPagePo.getUsdValueBannerPo().getPrimaryAmount()
await portfolioPagePo.getTotalAssetsCardPo().getPrimaryAmount()
).toBe("$203’231.00");
// $1 -> 0.1ICP
expect(
await portfolioPagePo.getUsdValueBannerPo().getSecondaryAmount()
await portfolioPagePo.getTotalAssetsCardPo().getSecondaryAmount()
).toBe("20’323.10 ICP");

const heldTokensCardPo = portfolioPagePo.getHeldTokensCardPo();
const heldTokensTitles = await heldTokensCardPo.getHeldTokensTitles();
const heldTokensBalanceInUsdBalance =
await heldTokensCardPo.getHeldTokensBalanceInUsd();
const heldTokensBalanceInNativeBalance =
await heldTokensCardPo.getHeldTokensBalanceInNativeCurrency();

expect(heldTokensTitles.length).toBe(4);
expect(heldTokensTitles).toEqual([
"Internet Computer",
"ckBTC",
"ckTESTBTC",
"ckETH",
]);

expect(heldTokensBalanceInUsdBalance.length).toBe(4);
expect(heldTokensBalanceInUsdBalance).toEqual([
"$1’000.00",
"$100’000.00",
"$100’000.00",
"$1’000.00",
]);

expect(heldTokensBalanceInNativeBalance.length).toBe(4);
expect(heldTokensBalanceInNativeBalance).toEqual([
"100.00 ICP",
"1.00 ckBTC",
"1.00 ckTESTBTC",
"0.10 ckETH",
]);

expect(await heldTokensCardPo.getInfoRow().isVisible()).toBe(false);

const stakedTokensCardPo = portfolioPagePo.getStakedTokensCardPo();
const stakedTokensTitles =
await stakedTokensCardPo.getStakedTokensTitle();
const stakedTokensMaturities =
await stakedTokensCardPo.getStakedTokensMaturity();
const stakedTokensStakeInUsd =
await stakedTokensCardPo.getStakedTokensStakeInUsd();
const stakedTokensStakeInNativeCurrency =
await stakedTokensCardPo.getStakedTokensStakeInNativeCurrency();

expect(stakedTokensTitles.length).toBe(2);
expect(stakedTokensTitles).toEqual(["Internet Computer", "Tetris"]);

expect(stakedTokensMaturities.length).toBe(2);
expect(stakedTokensMaturities).toEqual(["0", "2.00"]);

expect(stakedTokensStakeInUsd.length).toBe(2);
expect(stakedTokensStakeInUsd).toEqual(["$10.00", "$200.00"]);

expect(stakedTokensStakeInNativeCurrency.length).toBe(2);
expect(stakedTokensStakeInNativeCurrency).toEqual([
"1.00 ICP",
"20.00 TST",
]);

expect(await stakedTokensCardPo.getInfoRow().isPresent()).toBe(true);
});
});
});

0 comments on commit 82cfb96

Please sign in to comment.