Skip to content

Commit

Permalink
Merge branch 'main' into feat/optional-index-canister
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpeterparker authored Nov 5, 2024
2 parents 1ff7ce7 + bec3ace commit 1ffba52
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte';
import { anyBalanceNonZero } from '$lib/derived/balances.derived';
import { allBalancesZero } from '$lib/derived/balances.derived';
import { combinedDerivedSortedNetworkTokensUi } from '$lib/derived/network-tokens.derived';
import { HERO_CONTEXT_KEY, type HeroContext } from '$lib/stores/hero.store';
import { i18n } from '$lib/stores/i18n.store';
Expand All @@ -24,6 +24,6 @@
{/if}
</output>
<span class="max-w-48 text-xl font-medium text-brand-secondary-alt sm:max-w-none">
{$anyBalanceNonZero ? $i18n.hero.text.available_balance : $i18n.hero.text.top_up}
{$allBalancesZero ? $i18n.hero.text.top_up : $i18n.hero.text.available_balance}
</span>
</span>
6 changes: 3 additions & 3 deletions src/frontend/src/lib/components/hero/Actions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import Receive from '$lib/components/receive/Receive.svelte';
import Send from '$lib/components/send/Send.svelte';
import HeroButtonGroup from '$lib/components/ui/HeroButtonGroup.svelte';
import { anyBalanceNonZero } from '$lib/derived/balances.derived';
import { allBalancesZero } from '$lib/derived/balances.derived';
import {
networkEthereum,
networkICP,
Expand All @@ -43,8 +43,8 @@
let isTransactionsPage = false;
$: isTransactionsPage = isRouteTransactions($page);
let sendAction = false;
$: sendAction = $anyBalanceNonZero || isTransactionsPage;
let sendAction = true;
$: sendAction = !$allBalancesZero || isTransactionsPage;
</script>

<div role="toolbar" class="flex w-full justify-center pt-10">
Expand Down
10 changes: 8 additions & 2 deletions src/frontend/src/lib/components/hero/HeroContent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
import TokenLogo from '$lib/components/tokens/TokenLogo.svelte';
import SkeletonLogo from '$lib/components/ui/SkeletonLogo.svelte';
import { SLIDE_PARAMS } from '$lib/constants/transition.constants';
import { balance, balanceZero } from '$lib/derived/balances.derived';
import {
balance,
balanceZero,
noPositiveBalanceAndNotAllBalancesZero
} from '$lib/derived/balances.derived';
import { exchangeInitialized, exchanges } from '$lib/derived/exchange.derived';
import { networkBitcoin, networkEthereum, networkICP } from '$lib/derived/network.derived';
import { pageToken } from '$lib/derived/page-token.derived';
Expand Down Expand Up @@ -45,7 +49,9 @@
});
$: loading.set(
isRouteTransactions($page) ? isNullish(pageTokenUi?.balance) : !$exchangeInitialized
isRouteTransactions($page)
? isNullish(pageTokenUi?.balance)
: !$exchangeInitialized || $noPositiveBalanceAndNotAllBalancesZero
);
let isTransactionsPage = false;
Expand Down
22 changes: 21 additions & 1 deletion src/frontend/src/lib/derived/balances.derived.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { enabledNetworkTokens } from '$lib/derived/network-tokens.derived';
import { balancesStore } from '$lib/stores/balances.store';
import { token } from '$lib/stores/token.store';
import type { OptionBalance } from '$lib/types/balance';
import { checkAnyNonZeroBalance } from '$lib/utils/balances.utils';
import { checkAllBalancesZero, checkAnyNonZeroBalance } from '$lib/utils/balances.utils';
import { nonNullish } from '@dfinity/utils';
import { derived, type Readable } from 'svelte/store';

// TODO: Create tests for this store
export const balance: Readable<OptionBalance> = derived(
[balancesStore, token],
([$balanceStore, $token]) => (nonNullish($token) ? $balanceStore?.[$token.id]?.data : undefined)
);

// TODO: Create tests for this store
export const balanceZero: Readable<boolean> = derived(
[balancesStore, token],
([$balanceStore, $token]) =>
Expand All @@ -19,6 +22,23 @@ export const balanceZero: Readable<boolean> = derived(
$balanceStore[$token.id]?.data.isZero() === true
);

// TODO: Create tests for this store
export const anyBalanceNonZero: Readable<boolean> = derived([balancesStore], ([$balanceStore]) =>
checkAnyNonZeroBalance($balanceStore)
);

// TODO: Create tests for this store
export const allBalancesZero: Readable<boolean> = derived(
[balancesStore, enabledNetworkTokens],
([$balancesStore, $enabledNetworkTokens]) =>
checkAllBalancesZero({
$balancesStore: $balancesStore,
minLength: $enabledNetworkTokens.length
})
);

// TODO: Create tests for this store
export const noPositiveBalanceAndNotAllBalancesZero: Readable<boolean> = derived(
[anyBalanceNonZero, allBalancesZero],
([$anyBalanceNonZero, $allBalancesZero]) => !$anyBalanceNonZero && !$allBalancesZero
);
1 change: 1 addition & 0 deletions src/frontend/src/lib/derived/network-tokens.derived.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { derived, type Readable } from 'svelte/store';
/**
* All user-enabled tokens matching the selected network or chain fusion.
*/
// TODO: Create tests for this store
export const enabledNetworkTokens: Readable<Token[]> = derived(
[enabledTokens, selectedNetwork, pseudoNetworkChainFusion],
filterTokensForSelectedNetwork
Expand Down
28 changes: 28 additions & 0 deletions src/frontend/src/lib/utils/balances.utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import { type BalancesData } from '$lib/stores/balances.store';
import type { CertifiedStoreData } from '$lib/stores/certified.store';
import type { TokenId } from '$lib/types/token';
import type { Option } from '$lib/types/utils';
import { nonNullish } from '@dfinity/utils';

export const checkAnyNonZeroBalance = ($balancesStore: CertifiedStoreData<BalancesData>): boolean =>
nonNullish($balancesStore) &&
Object.getOwnPropertySymbols($balancesStore).some(
(tokenId) => !($balancesStore[tokenId as TokenId]?.data?.isZero() ?? true)
);

/**
* Check if all balances are zero.
*
* It requires a minimum length of the balance data to be considered valid.
* This is to avoid false positives when, for example, the list of tokens is still loading,
* and the number of tokens in the balance store is not the same as the number of tokens in the UI.
*
* @param $balancesStore - Certified store of balances.
* @param minLength - Minimum length of the store to be considered valid.
* @returns `true` if all balances are zero and the conditions are met, `false` otherwise.
*/

export const checkAllBalancesZero = ({
$balancesStore,
minLength
}: {
$balancesStore: CertifiedStoreData<BalancesData>;
minLength: number;
}): boolean =>
nonNullish($balancesStore) &&
Object.getOwnPropertySymbols($balancesStore).length >= Math.max(minLength, 1) &&
Object.getOwnPropertySymbols($balancesStore).every((tokenId) => {
const balance: Option<BalancesData> = $balancesStore[tokenId as TokenId];

return balance === null || (balance?.data?.isZero() ?? false) || balance?.data === null;
});
94 changes: 93 additions & 1 deletion src/frontend/src/tests/lib/utils/balances.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ZERO } from '$lib/constants/app.constants';
import type { BalancesData } from '$lib/stores/balances.store';
import type { CertifiedStoreData } from '$lib/stores/certified.store';
import { checkAnyNonZeroBalance } from '$lib/utils/balances.utils';
import { checkAllBalancesZero, checkAnyNonZeroBalance } from '$lib/utils/balances.utils';
import { bn1 } from '$tests/mocks/balances.mock';

describe('checkAnyNonZeroBalance', () => {
Expand Down Expand Up @@ -58,3 +58,95 @@ describe('checkAnyNonZeroBalance', () => {
expect(checkAnyNonZeroBalance(undefined)).toBe(false);
});
});

describe('checkAllBalancesZero', () => {
it('should return false if there is at least one non-zero balance', () => {
const mockBalancesStore: CertifiedStoreData<BalancesData> = {
[Symbol('token1')]: { data: bn1 },
[Symbol('token2')]: { data: ZERO }
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(false);
});

it('should return false if there is at least one non-zero balance and one nullish balance', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: bn1 },
[Symbol('token2')]: undefined
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(false);
});

it('should return false if there is at least one zero balance and one undefined balance', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: ZERO },
[Symbol('token2')]: undefined
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(false);
});

it('should return true if there is at least one zero balance and one null balance', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: ZERO },
[Symbol('token2')]: null
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(true);
});

it('should return true if all balances are zero', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: ZERO },
[Symbol('token2')]: { data: ZERO }
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(true);
});

it('should return false if balances data are nullish', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: null },
[Symbol('token2')]: { data: undefined }
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(false);
});

it('should return false if balances are nullish', () => {
const mockBalancesStore = {
[Symbol('token1')]: null,
[Symbol('token2')]: undefined
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 1 })).toBe(false);
});

it('should return false if store is empty and minimum length is 0', () => {
expect(checkAllBalancesZero({ $balancesStore: {}, minLength: 0 })).toBe(false);
});

it('should return false if store is empty and minimum length is 1', () => {
expect(checkAllBalancesZero({ $balancesStore: {}, minLength: 1 })).toBe(false);
});

it('should return false if minimum length is not met', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: ZERO },
[Symbol('token2')]: { data: ZERO }
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 3 })).toBe(false);
});

it('should return true if minimum length is met', () => {
const mockBalancesStore = {
[Symbol('token1')]: { data: ZERO },
[Symbol('token2')]: { data: ZERO },
[Symbol('token3')]: { data: ZERO }
} as unknown as CertifiedStoreData<BalancesData>;

expect(checkAllBalancesZero({ $balancesStore: mockBalancesStore, minLength: 3 })).toBe(true);
});
});
9 changes: 8 additions & 1 deletion src/frontend/src/tests/utils/derived.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
ethAddressNotLoaded
} from '$lib/derived/address.derived';
import { authIdentity, authNotSignedIn, authSignedIn } from '$lib/derived/auth.derived';
import { balance, balanceZero } from '$lib/derived/balances.derived';
import {
allBalancesZero,
anyBalanceNonZero,
balance,
balanceZero
} from '$lib/derived/balances.derived';
import { isBusy } from '$lib/derived/busy.derived';
import { exchangeInitialized, exchanges } from '$lib/derived/exchange.derived';
import { userHasPouhCredential } from '$lib/derived/has-pouh-credential';
Expand Down Expand Up @@ -51,6 +56,8 @@ import type { Readable, Unsubscriber } from 'svelte/store';
import type { MockInstance } from 'vitest';

const derivedList: Record<string, Readable<unknown>> = {
allBalancesZero,
anyBalanceNonZero,
authIdentity,
authNotSignedIn,
authSignedIn,
Expand Down

0 comments on commit 1ffba52

Please sign in to comment.