Skip to content

Commit

Permalink
cars are aware of each other
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabib committed Jan 17, 2025
1 parent 41bfd36 commit 7df24e4
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 58 deletions.
91 changes: 60 additions & 31 deletions frontend/src/lib/components/portfolio/HeldTokensCard.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import AmountDisplay from "$lib/components/ic/AmountDisplay.svelte";
import Card from "$lib/components/portfolio/Card.svelte";
import Logo from "$lib/components/ui/Logo.svelte";
import { PRICE_NOT_AVAILABLE_PLACEHOLDER } from "$lib/constants/constants";
Expand All @@ -8,10 +7,14 @@
import { i18n } from "$lib/stores/i18n";
import type { UserTokenData } from "$lib/types/tokens-page";
import { formatNumber } from "$lib/utils/format.utils";
import { shouldShowInfoRow } from "$lib/utils/portfolio.utils";
import { formatTokenV2 } from "$lib/utils/token.utils";
import { IconAccountsPage, IconRight } from "@dfinity/gix-components";
import { TokenAmountV2 } from "@dfinity/utils";
export let topHeldTokens: UserTokenData[];
export let usdAmount: number;
export let numberOfTopStakedTokens: number;
const href = AppPath.Tokens;
Expand All @@ -20,9 +23,14 @@
? formatNumber(usdAmount)
: PRICE_NOT_AVAILABLE_PLACEHOLDER;
// TODO: This will also depend on the number of projects
let numberOfTopHeldTokens: number;
$: numberOfTopHeldTokens = topHeldTokens.length;
let showInfoRow: boolean;
$: showInfoRow = topHeldTokens.length > 0 && topHeldTokens.length < 3;
$: showInfoRow = shouldShowInfoRow({
currentCardNumberOfTokens: numberOfTopHeldTokens,
otherCardNumberOfTokens: numberOfTopStakedTokens,
});
</script>

<Card testId="held-tokens-card">
Expand Down Expand Up @@ -78,44 +86,54 @@
</div>

<div class="list" role="rowgroup">
{#each topHeldTokens as topHeldToken (topHeldToken.domKey)}
{#each topHeldTokens as heldToken (heldToken.domKey)}
<div class="row" data-tid="held-token-card-row" role="row">
<div class="info" role="cell">
<div>
<Logo
src={topHeldToken.logo}
alt={topHeldToken.title}
src={heldToken.logo}
alt={heldToken.title}
size="medium"
framed
/>
</div>
<span data-tid="title">{topHeldToken.title}</span>
<span data-tid="title">{heldToken.title}</span>
</div>

<div
class="balance-native"
data-tid="balance-in-native"
role="cell"
>
<AmountDisplay singleLine amount={topHeldToken.balance} />
{heldToken.balance instanceof TokenAmountV2
? formatTokenV2({
value: heldToken.balance,
detailed: true,
})
: PRICE_NOT_AVAILABLE_PLACEHOLDER}
<span class="symbol">
{heldToken.balance.token.symbol}
</span>
</div>
<div
class="balance-usd"
data-tid="balance-in-usd"
role="cell"
aria-label={`${topHeldToken.title} USD: ${topHeldToken?.balanceInUsd ?? 0}`}
aria-label={`${heldToken.title} USD: ${heldToken?.balanceInUsd ?? 0}`}
>
${formatNumber(topHeldToken?.balanceInUsd ?? 0)}
${formatNumber(heldToken?.balanceInUsd ?? 0)}
</div>
</div>
{/each}
{#if showInfoRow}
<div class="info-row desktop-only" role="note" data-tid="info-row">
<div class="icon" aria-hidden="true">
<IconAccountsPage />
</div>
<div class="message">
{$i18n.portfolio.held_tokens_card_info_row}
<div class="content">
<div class="icon" aria-hidden="true">
<IconAccountsPage />
</div>
<div class="message">
{$i18n.portfolio.held_tokens_card_info_row}
</div>
</div>
</div>
{/if}
Expand Down Expand Up @@ -176,7 +194,6 @@
.header {
display: grid;
grid-template-columns: 1fr 1fr;
justify-content: space-between;
font-size: 0.875rem;
color: var(--text-description);
Expand Down Expand Up @@ -225,9 +242,16 @@
.balance-native {
grid-area: balance;
font-size: 0.75rem;
font-size: 0.875rem;
color: var(--text-description);
@include media.min-width(medium) {
font-size: var(--font-size-standard);
color: var(--text-color);
}
.symbol {
color: var(--text-description);
}
}
Expand All @@ -238,24 +262,29 @@
}
.info-row {
display: flex;
justify-content: center;
align-items: center;
gap: var(--padding-2x);
flex-grow: 1;
border-top: 1px solid var(--elements-divider);
max-width: 90%;
margin: 0 auto;
.content {
display: flex;
justify-content: center;
align-items: center;
gap: var(--padding-2x);
padding: var(--padding-2x) 0;
.icon {
min-width: 50px;
height: 50px;
}
max-width: 90%;
margin: 0 auto;
.message {
font-size: 0.875rem;
color: var(--text-description);
max-width: 400px;
.icon {
min-width: 50px;
height: 50px;
}
.message {
font-size: 0.875rem;
color: var(--text-description);
max-width: 400px;
}
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/lib/components/portfolio/StakedTokensCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { i18n } from "$lib/stores/i18n";
import type { TableProject } from "$lib/types/staking";
import { formatNumber } from "$lib/utils/format.utils";
import { shouldShowInfoRow } from "$lib/utils/portfolio.utils";
import { formatTokenV2 } from "$lib/utils/token.utils";
import { IconNeuronsPage, IconRight } from "@dfinity/gix-components";
import { TokenAmountV2 } from "@dfinity/utils";
Expand All @@ -25,11 +26,11 @@
let numberOfTopStakedTokens: number;
$: numberOfTopStakedTokens = topStakedTokens.length;
// Show an informational row when there are fewer staked tokens than held tokens.
// This ensures both cards have consistent heights by filling empty space
// with a message instead of leaving blank space.
let showInfoRow: boolean;
$: showInfoRow = numberOfTopHeldTokens - numberOfTopStakedTokens > 0;
$: showInfoRow = shouldShowInfoRow({
currentCardNumberOfTokens: numberOfTopStakedTokens,
otherCardNumberOfTokens: numberOfTopHeldTokens,
});
</script>

<Card testId="staked-tokens-card">
Expand Down Expand Up @@ -287,7 +288,7 @@
justify-content: center;
align-items: center;
gap: var(--padding-2x);
padding: var(--padding-1_5x) 0;
padding: var(--padding-2x) 0;
max-width: 90%;
margin: 0 auto;
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/lib/pages/Portfolio.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@
{#if showNoHeldTokensCard}
<NoHeldTokensCard />
{:else}
<HeldTokensCard {topHeldTokens} usdAmount={totalTokensBalanceInUsd} />
<HeldTokensCard
{topHeldTokens}
usdAmount={totalTokensBalanceInUsd}
numberOfTopStakedTokens={topStakedTokens.length}
/>
{/if}
{#if showNoStakedTokensCard}
<NoStakedTokensCard primaryCard={hasNoStakedTokensCardAPrimaryAction} />
Expand Down Expand Up @@ -141,8 +145,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
20 changes: 15 additions & 5 deletions frontend/src/tests/lib/components/portfolio/HeldTokensCard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@ import { ICPToken, TokenAmountV2 } from "@dfinity/utils";
import { render } from "@testing-library/svelte";

describe("HeldTokensCard", () => {
const renderComponent = (props: {
topHeldTokens: UserTokenData[];
usdAmount: number;
const renderComponent = ({
topHeldTokens = [],
usdAmount = 0,
numberOfTopStakedTokens = 0,
}: {
topHeldTokens?: UserTokenData[];
usdAmount?: number;
numberOfTopStakedTokens?: number;
}) => {
const { container } = render(HeldTokensCard, {
props,
props: {
topHeldTokens,
usdAmount,
numberOfTopStakedTokens,
},
});

return HeldTokensCardPo.under(new JestPageObjectElement(container));
Expand Down Expand Up @@ -130,10 +139,11 @@ describe("HeldTokensCard", () => {
]);
});

it("should not show info row when tokens length is 3 or more", async () => {
it("should not show info row when numberOfTopHeldTokens is the same as the number of topStakedTokens", async () => {
const po = renderComponent({
topHeldTokens: mockTokens.slice(0, 3),
usdAmount: 600,
numberOfTopStakedTokens: 3,
});
const titles = await po.getHeldTokensTitles();
const balances = await po.getHeldTokensBalanceInUsd();
Expand Down
82 changes: 68 additions & 14 deletions frontend/src/tests/lib/pages/Portfolio.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { CKUSDC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckusdc-canister-ids.constants";
import { NNS_TOKEN_DATA } from "$lib/constants/tokens.constants";
import Portfolio from "$lib/pages/Portfolio.svelte";
import { icpSwapTickersStore } from "$lib/stores/icp-swap.store";
import type { TableProject } from "$lib/types/staking";
import type { UserToken } from "$lib/types/tokens-page";
import type { UserToken, UserTokenData } from "$lib/types/tokens-page";
import { UnavailableTokenAmount } from "$lib/utils/token.utils";
import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock";
import { mockIcpSwapTicker } from "$tests/mocks/icp-swap.mock";
import { mockToken, principal } from "$tests/mocks/sns-projects.mock";
import { mockTableProject } from "$tests/mocks/staking.mock";
import { createUserToken } from "$tests/mocks/tokens-page.mock";
import {
ckBTCTokenBase,
ckETHTokenBase,
ckTESTBTCTokenBase,
createIcpUserToken,
createUserToken,
} from "$tests/mocks/tokens-page.mock";
import { PortfolioPagePo } from "$tests/page-objects/PortfolioPage.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import { ICPToken, TokenAmountV2 } from "@dfinity/utils";
Expand All @@ -30,6 +37,54 @@ describe("Portfolio page", () => {
};

describe("when not logged in", () => {
const mockIcpToken = createIcpUserToken();
const mockCkBTCToken = createUserToken(ckBTCTokenBase);
const mockCkTESTBTCToken = createUserToken(ckTESTBTCTokenBase);
const mockCkETHToken = createUserToken(ckETHTokenBase);

const mockTokens = [
mockIcpToken,
mockCkBTCToken,
mockCkTESTBTCToken,
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 mockTableProjects: TableProject[] = [
icpProject,
tableProject1,
tableProject2,
tableProject3,
];

beforeEach(() => {
setNoIdentity();
});
Expand All @@ -40,23 +95,22 @@ describe("Portfolio page", () => {
expect(await po.getLoginCard().isPresent()).toBe(true);
});

it("should show the HeldTokensCard default data", async () => {
const po = renderPage();

const tokensCardPo = po.getHeldTokensCardPo();

expect(await po.getNoHeldTokensCard().isPresent()).toBe(false);
expect(await tokensCardPo.isPresent()).toBe(true);
expect(await tokensCardPo.getInfoRow().isPresent()).toBe(false);
});

it("should show the StakedTokensCard default data", async () => {
const po = renderPage();
it("should show both cards with default data", async () => {
const po = renderPage({
tableProjects: mockTableProjects,
userTokens: mockTokens,
});

const heldTokensCardPo = po.getHeldTokensCardPo();
const stakedTokensCardPo = po.getStakedTokensCardPo();

expect(await po.getNoHeldTokensCard().isPresent()).toBe(false);
expect(await po.getNoStakedTokensCarPo().isPresent()).toBe(false);

expect(await heldTokensCardPo.isPresent()).toBe(true);
expect(await stakedTokensCardPo.isPresent()).toBe(true);

expect(await heldTokensCardPo.getInfoRow().isPresent()).toBe(false);
expect(await stakedTokensCardPo.getInfoRow().isPresent()).toBe(false);
});
});
Expand Down

0 comments on commit 7df24e4

Please sign in to comment.