Skip to content

Commit

Permalink
GIX-1980: Setup icp accounts with tokens table (#3775)
Browse files Browse the repository at this point in the history
# Motivation

When we migrate to the tokens page, we also want a similar view for the
ICP accounts, subaccounts and hardware wallet accounts.

In this PR, I introduce the TokensTable for NNS Accounts.

# Changes

* Render `TokensTable` in `NnsAccounts` depending on feature flag.
* Expect an optional list of `UserTokenData` in `NnsAccounts`.
* Hard code a list of `UserTokenData` in the Accounts route and pass it
to `NnsAccounts`.

# Tests

* Add test in NnsAccounts.spec that the tokens table is there if feature
flag is enabled. I added myself some TODOs comments to remember
improving the test before adding more features here.
* Add methods to NnsAccountsPo.
* Add test in Accounts route to check that tokens table is rendered if
flag is enabled.

# Todos

- [ ] Add entry to changelog (if necessary).
  • Loading branch information
lmuntaner committed Nov 17, 2023
1 parent 320497a commit 63b3c4b
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 43 deletions.
59 changes: 37 additions & 22 deletions frontend/src/lib/pages/NnsAccounts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,52 @@
pollAccounts,
} from "$lib/services/icp-accounts.services";
import { ICPToken } from "@dfinity/utils";
import type { UserTokenData } from "$lib/types/tokens-page";
import { ENABLE_MY_TOKENS } from "$lib/stores/feature-flags.store";
import MainWrapper from "$lib/components/tokens/MainWrapper.svelte";
import TokensTable from "$lib/components/tokens/TokensTable/TokensTable.svelte";
onMount(() => {
pollAccounts();
if (!$ENABLE_MY_TOKENS) {
pollAccounts();
}
});
onDestroy(() => {
cancelPollAccounts();
});
// TODO: Remove default value when we remove the feature flag
export let userTokensData: UserTokenData[] = [];
</script>

<div class="card-grid" data-tid="accounts-body">
{#if nonNullish($icpAccountsStore?.main)}
<!-- Workaround: Type checker does not get $accountsStore.main is defined here -->
{@const mainAccount = $icpAccountsStore.main}
{#if $ENABLE_MY_TOKENS}
<MainWrapper testId="accounts-body">
<TokensTable {userTokensData} />
</MainWrapper>
{:else}
<div class="card-grid" data-tid="accounts-body">
{#if nonNullish($icpAccountsStore?.main)}
<!-- Workaround: Type checker does not get $accountsStore.main is defined here -->
{@const mainAccount = $icpAccountsStore.main}

<AccountCard account={mainAccount} token={ICPToken}
>{$i18n.accounts.main}</AccountCard
>
{#each $icpAccountsStore.subAccounts ?? [] as subAccount}
<AccountCard account={subAccount} token={ICPToken}
>{subAccount.name}</AccountCard
>
{/each}
{#each $icpAccountsStore.hardwareWallets ?? [] as walletAccount}
<AccountCard account={walletAccount} token={ICPToken}
>{walletAccount.name}</AccountCard
<AccountCard account={mainAccount} token={ICPToken}
>{$i18n.accounts.main}</AccountCard
>
{/each}
{#each $icpAccountsStore.subAccounts ?? [] as subAccount}
<AccountCard account={subAccount} token={ICPToken}
>{subAccount.name}</AccountCard
>
{/each}
{#each $icpAccountsStore.hardwareWallets ?? [] as walletAccount}
<AccountCard account={walletAccount} token={ICPToken}
>{walletAccount.name}</AccountCard
>
{/each}

<NnsAddAccount />
{:else}
<SkeletonCard size="medium" />
{/if}
</div>
<NnsAddAccount />
{:else}
<SkeletonCard size="medium" />
{/if}
</div>
{/if}
22 changes: 20 additions & 2 deletions frontend/src/lib/routes/Accounts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
snsProjectsCommittedStore,
type SnsFullProject,
} from "$lib/derived/sns/sns-projects.derived";
import { nonNullish } from "@dfinity/utils";
import { TokenAmount, nonNullish } from "@dfinity/utils";
import { snsProjectSelectedStore } from "$lib/derived/sns/sns-selected-project.derived";
import { uncertifiedLoadCkBTCAccountsBalance } from "$lib/services/ckbtc-accounts-balance.services";
import CkBTCAccounts from "$lib/pages/CkBTCAccounts.svelte";
Expand All @@ -25,6 +25,10 @@
import { isArrayEmpty } from "$lib/utils/utils";
import AccountsModals from "$lib/modals/accounts/AccountsModals.svelte";
import CkBTCAccountsModals from "$lib/modals/accounts/CkBTCAccountsModals.svelte";
import { UserTokenAction, type UserTokenData } from "$lib/types/tokens-page";
import { OWN_CANISTER_ID } from "$lib/constants/canister-ids.constants";
import { NNS_TOKEN_DATA } from "$lib/constants/tokens.constants";
import IC_LOGO_ROUNDED from "$lib/assets/icp-rounded.svg";
// TODO: This component is mounted twice. Understand why and fix it.
Expand Down Expand Up @@ -77,14 +81,28 @@
loadSnsAccountsBalances($snsProjectsCommittedStore),
loadCkBTCAccountsBalances($ckBTCUniversesStore),
]))();
// TODO: Use derived store https://dfinity.atlassian.net/browse/GIX-2083
const data: UserTokenData[] = [
{
universeId: OWN_CANISTER_ID,
title: "Main",
balance: TokenAmount.fromE8s({
amount: 314000000n,
token: NNS_TOKEN_DATA,
}),
logo: IC_LOGO_ROUNDED,
actions: [UserTokenAction.Send, UserTokenAction.Receive],
},
];
</script>

<TestIdWrapper testId="accounts-component">
<main>
<SummaryUniverse />

{#if $isNnsUniverseStore}
<NnsAccounts />
<NnsAccounts userTokensData={data} />
{:else if $isCkBTCUniverseStore}
<CkBTCAccounts />
{:else if nonNullish($snsProjectSelectedStore)}
Expand Down
23 changes: 21 additions & 2 deletions frontend/src/tests/lib/pages/NnsAccounts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as nnsDappApi from "$lib/api/nns-dapp.api";
import { SYNC_ACCOUNTS_RETRY_SECONDS } from "$lib/constants/accounts.constants";
import NnsAccounts from "$lib/pages/NnsAccounts.svelte";
import { cancelPollAccounts } from "$lib/services/icp-accounts.services";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { icpAccountsStore } from "$lib/stores/icp-accounts.store";
import { formatToken } from "$lib/utils/token.utils";
import { resetIdentity } from "$tests/mocks/auth.store.mock";
Expand All @@ -12,6 +13,8 @@ import {
mockMainAccount,
mockSubAccount,
} from "$tests/mocks/icp-accounts.store.mock";
import { NnsAccountsPo } from "$tests/page-objects/NnsAccounts.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import {
advanceTime,
runResolvedPromises,
Expand All @@ -23,14 +26,30 @@ vi.mock("$lib/api/nns-dapp.api");
vi.mock("$lib/api/icp-ledger.api");

describe("NnsAccounts", () => {
const renderComponent = () => {
const { container } = render(NnsAccounts);
return NnsAccountsPo.under(new JestPageObjectElement(container));
};

beforeEach(() => {
vi.clearAllMocks();
resetIdentity();
// TODO: Move this inside a describe with tokens flag disabled
overrideFeatureFlagsStore.setFlag("ENABLE_MY_TOKENS", false);
});

afterEach(() => {
vi.clearAllMocks();
describe("when tokens flag is enabled", () => {
beforeEach(() => {
overrideFeatureFlagsStore.setFlag("ENABLE_MY_TOKENS", true);
});

it("should render tokens table", async () => {
const po = renderComponent();
expect(await po.hasTokensTable()).toBe(true);
});
});

// TODO: Move this inside a describe with tokens flag disabled
describe("when there are accounts", () => {
beforeEach(() => {
icpAccountsStore.setForTesting({
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/tests/page-objects/NnsAccounts.page-object.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseAccountsPo } from "$tests/page-objects/BaseAccounts.page-object";
import { NnsAddAccountPo } from "$tests/page-objects/NnsAddAccount.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
import { TokensTablePo } from "./TokensTable.page-object";

export class NnsAccountsPo extends BaseAccountsPo {
private static readonly TID = "accounts-body";
Expand All @@ -9,6 +10,14 @@ export class NnsAccountsPo extends BaseAccountsPo {
return new NnsAccountsPo(element.byTestId(NnsAccountsPo.TID));
}

getTokensTablePo() {
return TokensTablePo.under(this.root);
}

hasTokensTable(): Promise<boolean> {
return this.getTokensTablePo().isPresent();
}

getNnsAddAccountPo(): NnsAddAccountPo {
return NnsAddAccountPo.under(this.root);
}
Expand Down
76 changes: 59 additions & 17 deletions frontend/src/tests/routes/app/accounts/page.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,71 @@
import { authStore } from "$lib/stores/auth.store";
import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
import { AppPath } from "$lib/constants/routes.constants";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { page } from "$mocks/$app/stores";
import AccountsPage from "$routes/(app)/(u)/(accounts)/accounts/+page.svelte";
import {
authStoreMock,
mutableMockAuthStoreSubscribe,
} from "$tests/mocks/auth.store.mock";
import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock";
import { AccountsPo } from "$tests/page-objects/Accounts.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import { render } from "@testing-library/svelte";

vi.mock("$lib/api/ckbtc-ledger.api");
vi.mock("$lib/api/sns-ledger.api");

describe("Accounts page", () => {
vi.spyOn(authStore, "subscribe").mockImplementation(
mutableMockAuthStoreSubscribe
);
const renderComponent = () => {
const { container } = render(AccountsPage);
return AccountsPo.under(new JestPageObjectElement(container));
};

beforeAll(() => {
authStoreMock.next({
identity: undefined,
});
beforeEach(() => {
vi.clearAllMocks();
});

afterAll(() => {
vi.clearAllMocks();
describe("not logged in", () => {
beforeEach(() => {
setNoIdentity();
});

it("should render sign-in if not logged in", () => {
const { getByTestId } = render(AccountsPage);

expect(getByTestId("login-button")).not.toBeNull();
});
});

it("should render sign-in if not logged in", () => {
const { getByTestId } = render(AccountsPage);
describe("logged in NNS Accounts", () => {
beforeEach(() => {
resetIdentity();
page.mock({
routeId: AppPath.Accounts,
data: { universe: OWN_CANISTER_ID_TEXT },
});
});

describe("tokens flag enabled", () => {
beforeEach(() => {
overrideFeatureFlagsStore.setFlag("ENABLE_MY_TOKENS", true);
});

it("renders tokens table for NNS accounts", async () => {
const po = renderComponent();

const pagePo = po.getNnsAccountsPo();
expect(await pagePo.hasTokensTable()).toBe(true);
});
});

describe("tokens flag disabled", () => {
beforeEach(() => {
overrideFeatureFlagsStore.setFlag("ENABLE_MY_TOKENS", false);
});

expect(getByTestId("login-button")).not.toBeNull();
it("does not render tokens table for NNS accounts", async () => {
const po = renderComponent();

const pagePo = po.getNnsAccountsPo();
expect(await pagePo.hasTokensTable()).toBe(false);
});
});
});
});

0 comments on commit 63b3c4b

Please sign in to comment.