From cd0e2cd7d3e50a3727e5955a83aa087ba490f0f4 Mon Sep 17 00:00:00 2001 From: Antonio Ventilii <169057656+AntonioVentilii-DFINITY@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:44:08 +0100 Subject: [PATCH 01/31] refactor(frontend): Separate module for token-group (#3257) # Motivation We re-organize the `token-group` related functions in a separate file. --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .../components/tokens/TokenGroupCard.svelte | 2 +- .../tokens/TokensDisplayHandler.svelte | 5 +- .../components/tokens/TokensSignedIn.svelte | 4 +- src/frontend/src/lib/types/token-group.ts | 12 ++ src/frontend/src/lib/types/token.ts | 11 -- .../src/lib/utils/token-card.utils.ts | 2 +- .../src/lib/utils/token-group.utils.ts | 87 +++++++++ src/frontend/src/lib/utils/token.utils.ts | 89 +-------- .../tests/lib/utils/token-group.utils.spec.ts | 174 ++++++++++++++++++ .../src/tests/lib/utils/token.utils.spec.ts | 171 +---------------- 10 files changed, 282 insertions(+), 275 deletions(-) create mode 100644 src/frontend/src/lib/types/token-group.ts create mode 100644 src/frontend/src/lib/utils/token-group.utils.ts create mode 100644 src/frontend/src/tests/lib/utils/token-group.utils.spec.ts diff --git a/src/frontend/src/lib/components/tokens/TokenGroupCard.svelte b/src/frontend/src/lib/components/tokens/TokenGroupCard.svelte index cbc35d79fe..c390931497 100644 --- a/src/frontend/src/lib/components/tokens/TokenGroupCard.svelte +++ b/src/frontend/src/lib/components/tokens/TokenGroupCard.svelte @@ -7,8 +7,8 @@ import { TOKEN_GROUP } from '$lib/constants/test-ids.constants'; import { SLIDE_PARAMS } from '$lib/constants/transition.constants'; import { tokenGroupStore } from '$lib/stores/token-group.store'; - import type { TokenUiGroup } from '$lib/types/token'; import type { CardData } from '$lib/types/token-card'; + import type { TokenUiGroup } from '$lib/types/token-group'; import { mapHeaderData } from '$lib/utils/token-card.utils'; export let tokenGroup: TokenUiGroup; diff --git a/src/frontend/src/lib/components/tokens/TokensDisplayHandler.svelte b/src/frontend/src/lib/components/tokens/TokensDisplayHandler.svelte index f8d11172a3..c67c0d41f2 100644 --- a/src/frontend/src/lib/components/tokens/TokensDisplayHandler.svelte +++ b/src/frontend/src/lib/components/tokens/TokensDisplayHandler.svelte @@ -2,8 +2,9 @@ import { debounce } from '@dfinity/utils'; import { combinedDerivedSortedNetworkTokensUi } from '$lib/derived/network-tokens.derived'; import { showZeroBalances } from '$lib/derived/settings.derived'; - import type { TokenUi, TokenUiOrGroupUi } from '$lib/types/token'; - import { groupTokensByTwin } from '$lib/utils/token.utils'; + import type { TokenUi } from '$lib/types/token'; + import type { TokenUiOrGroupUi } from '$lib/types/token-group'; + import { groupTokensByTwin } from '$lib/utils/token-group.utils'; // We start it as undefined to avoid showing an empty list before the first update. export let tokens: TokenUiOrGroupUi[] | undefined = undefined; diff --git a/src/frontend/src/lib/components/tokens/TokensSignedIn.svelte b/src/frontend/src/lib/components/tokens/TokensSignedIn.svelte index 9f4e1d3e2e..70928e1e8c 100644 --- a/src/frontend/src/lib/components/tokens/TokensSignedIn.svelte +++ b/src/frontend/src/lib/components/tokens/TokensSignedIn.svelte @@ -12,8 +12,8 @@ import TokensSkeletons from '$lib/components/tokens/TokensSkeletons.svelte'; import { modalManageTokens } from '$lib/derived/modal.derived'; import { i18n } from '$lib/stores/i18n.store'; - import type { TokenUiOrGroupUi } from '$lib/types/token'; - import { isTokenUiGroup } from '$lib/utils/token.utils'; + import type { TokenUiOrGroupUi } from '$lib/types/token-group'; + import { isTokenUiGroup } from '$lib/utils/token-group.utils'; let tokens: TokenUiOrGroupUi[] | undefined; diff --git a/src/frontend/src/lib/types/token-group.ts b/src/frontend/src/lib/types/token-group.ts new file mode 100644 index 0000000000..4e9ae7c77d --- /dev/null +++ b/src/frontend/src/lib/types/token-group.ts @@ -0,0 +1,12 @@ +import type { TokenFinancialData, TokenId, TokenUi } from '$lib/types/token'; + +//todo: separate typing from token id +type GroupId = TokenId; + +export type TokenUiGroup = { + id: GroupId; + nativeToken: TokenUi; + tokens: TokenUi[]; +} & TokenFinancialData; + +export type TokenUiOrGroupUi = TokenUi | TokenUiGroup; diff --git a/src/frontend/src/lib/types/token.ts b/src/frontend/src/lib/types/token.ts index af3bdb4ec8..a936f58ed6 100644 --- a/src/frontend/src/lib/types/token.ts +++ b/src/frontend/src/lib/types/token.ts @@ -84,14 +84,3 @@ export interface TokenFinancialData { export type TokenUi = Token & TokenFinancialData; export type OptionTokenUi = Option; - -//todo: separate typing from token id -export type GroupId = TokenId; - -export type TokenUiGroup = { - id: GroupId; - nativeToken: TokenUi; - tokens: TokenUi[]; -} & TokenFinancialData; - -export type TokenUiOrGroupUi = TokenUi | TokenUiGroup; diff --git a/src/frontend/src/lib/utils/token-card.utils.ts b/src/frontend/src/lib/utils/token-card.utils.ts index b92ed0ff80..9f618cb204 100644 --- a/src/frontend/src/lib/utils/token-card.utils.ts +++ b/src/frontend/src/lib/utils/token-card.utils.ts @@ -1,5 +1,5 @@ -import type { TokenUiGroup } from '$lib/types/token'; import type { CardData } from '$lib/types/token-card'; +import type { TokenUiGroup } from '$lib/types/token-group'; /** Maps the token group to the card data of the card that will be used as "summary" for the group. * diff --git a/src/frontend/src/lib/utils/token-group.utils.ts b/src/frontend/src/lib/utils/token-group.utils.ts new file mode 100644 index 0000000000..fb7ac679be --- /dev/null +++ b/src/frontend/src/lib/utils/token-group.utils.ts @@ -0,0 +1,87 @@ +import type { IcCkToken } from '$icp/types/ic'; +import { isIcCkToken } from '$icp/utils/icrc.utils'; +import type { TokenUi } from '$lib/types/token'; +import type { TokenUiGroup, TokenUiOrGroupUi } from '$lib/types/token-group'; +import { + isRequiredTokenWithLinkedData, + sumTokenBalances, + sumUsdBalances +} from '$lib/utils/token.utils'; + +/** + * Type guard to check if an object is of type TokenUiGroup. + * + * @param tokenUiOrGroupUi - The object to check. + * @returns A boolean indicating whether the object is a TokenUiGroup. + */ +export const isTokenUiGroup = ( + tokenUiOrGroupUi: TokenUiOrGroupUi +): tokenUiOrGroupUi is TokenUiGroup => + typeof tokenUiOrGroupUi === 'object' && + 'nativeToken' in tokenUiOrGroupUi && + 'tokens' in tokenUiOrGroupUi; + +/** + * Factory function to create a TokenUiGroup based on the provided tokens and network details. + * This function creates a group header and adds both the native token and the twin token to the group's tokens array. + * + * @param nativeToken - The native token used for the group, typically the original token or the one from the selected network. + * @param twinToken - The twin token to be grouped with the native token, usually representing the same asset on a different network. + * + * @returns A TokenUiGroup object that includes a header with network and symbol information and contains both the native and twin tokens. + */ +const createTokenGroup = ({ + nativeToken, + twinToken +}: { + nativeToken: TokenUi; + twinToken: TokenUi; +}): TokenUiGroup => ({ + // Setting the same ID of the native token to ensure the Group Card component is treated as the same component of the Native Token Card. + // This allows Svelte transitions/animations to handle the two cards as the same component, giving a smoother user experience. + id: nativeToken.id, + nativeToken, + tokens: [nativeToken, twinToken], + balance: sumTokenBalances([nativeToken, twinToken]), + usdBalance: sumUsdBalances([nativeToken.usdBalance, twinToken.usdBalance]) +}); + +/** + * Function to create a list of TokenUiOrGroupUi by grouping tokens with matching twinTokenSymbol. + * The group is placed in the position where the first token of the group was found. + * Tokens with no twin remain as individual tokens in their original position. + * + * @param tokens - The list of TokenUi objects to group. Each token may or may not have a twinTokenSymbol. + * Tokens with a twinTokenSymbol are grouped together. + * + * @returns A new list where tokens with twinTokenSymbols are grouped into a TokenUiGroup, + * and tokens without twins remain in their original place. + * The group replaces the first token of the group in the list. + */ +export const groupTokensByTwin = (tokens: TokenUi[]): TokenUiOrGroupUi[] => { + const groupedTokenTwins = new Set(); + const mappedTokensWithGroups: TokenUiOrGroupUi[] = tokens.map((token) => { + if (!isRequiredTokenWithLinkedData(token)) { + return token; + } + + const twinToken = tokens.find((t) => t.symbol === token.twinTokenSymbol && isIcCkToken(t)) as + | IcCkToken + | undefined; + + if (twinToken && twinToken.decimals === token.decimals) { + groupedTokenTwins.add(twinToken.symbol); + groupedTokenTwins.add(token.symbol); + return createTokenGroup({ + nativeToken: token, + twinToken: twinToken + }); + } + + return token; + }); + + return mappedTokensWithGroups.filter( + (t) => isTokenUiGroup(t) || !groupedTokenTwins.has(t.symbol) + ); +}; diff --git a/src/frontend/src/lib/utils/token.utils.ts b/src/frontend/src/lib/utils/token.utils.ts index b9f948d0d9..fd36cf1d7e 100644 --- a/src/frontend/src/lib/utils/token.utils.ts +++ b/src/frontend/src/lib/utils/token.utils.ts @@ -1,19 +1,10 @@ import { ICRC_CHAIN_FUSION_DEFAULT_LEDGER_CANISTER_IDS } from '$env/networks.icrc.env'; -import type { IcCkToken } from '$icp/types/ic'; -import { isIcCkToken } from '$icp/utils/icrc.utils'; import { ZERO } from '$lib/constants/app.constants'; import type { BalancesData } from '$lib/stores/balances.store'; import type { CertifiedStoreData } from '$lib/stores/certified.store'; import type { CanisterIdText } from '$lib/types/canister'; import type { ExchangesData } from '$lib/types/exchange'; -import type { - RequiredTokenWithLinkedData, - Token, - TokenStandard, - TokenUi, - TokenUiGroup, - TokenUiOrGroupUi -} from '$lib/types/token'; +import type { RequiredTokenWithLinkedData, Token, TokenStandard, TokenUi } from '$lib/types/token'; import type { TokenToggleable } from '$lib/types/token-toggleable'; import { mapCertifiedData } from '$lib/utils/certified-store.utils'; import { usdValue } from '$lib/utils/exchange.utils'; @@ -192,81 +183,3 @@ export const sumUsdBalances = ([usdBalance1, usdBalance2]: [ */ export const isRequiredTokenWithLinkedData = (token: Token): token is RequiredTokenWithLinkedData => 'twinTokenSymbol' in token && typeof token.twinTokenSymbol === 'string'; - -/** - * Type guard to check if an object is of type TokenUiGroup. - * - * @param tokenUiOrGroupUi - The object to check. - * @returns A boolean indicating whether the object is a TokenUiGroup. - */ -export const isTokenUiGroup = ( - tokenUiOrGroupUi: TokenUiOrGroupUi -): tokenUiOrGroupUi is TokenUiGroup => - typeof tokenUiOrGroupUi === 'object' && - 'nativeToken' in tokenUiOrGroupUi && - 'tokens' in tokenUiOrGroupUi; - -/** - * Factory function to create a TokenUiGroup based on the provided tokens and network details. - * This function creates a group header and adds both the native token and the twin token to the group's tokens array. - * - * @param nativeToken - The native token used for the group, typically the original token or the one from the selected network. - * @param twinToken - The twin token to be grouped with the native token, usually representing the same asset on a different network. - * - * @returns A TokenUiGroup object that includes a header with network and symbol information and contains both the native and twin tokens. - */ -const createTokenGroup = ({ - nativeToken, - twinToken -}: { - nativeToken: TokenUi; - twinToken: TokenUi; -}): TokenUiGroup => ({ - // Setting the same ID of the native token to ensure the Group Card component is treated as the same component of the Native Token Card. - // This allows Svelte transitions/animations to handle the two cards as the same component, giving a smoother user experience. - id: nativeToken.id, - nativeToken, - tokens: [nativeToken, twinToken], - balance: sumTokenBalances([nativeToken, twinToken]), - usdBalance: sumUsdBalances([nativeToken.usdBalance, twinToken.usdBalance]) -}); - -/** - * Function to create a list of TokenUiOrGroupUi by grouping tokens with matching twinTokenSymbol. - * The group is placed in the position where the first token of the group was found. - * Tokens with no twin remain as individual tokens in their original position. - * - * @param tokens - The list of TokenUi objects to group. Each token may or may not have a twinTokenSymbol. - * Tokens with a twinTokenSymbol are grouped together. - * - * @returns A new list where tokens with twinTokenSymbols are grouped into a TokenUiGroup, - * and tokens without twins remain in their original place. - * The group replaces the first token of the group in the list. - */ -export const groupTokensByTwin = (tokens: TokenUi[]): TokenUiOrGroupUi[] => { - const groupedTokenTwins = new Set(); - const mappedTokensWithGroups: TokenUiOrGroupUi[] = tokens.map((token) => { - if (!isRequiredTokenWithLinkedData(token)) { - return token; - } - - const twinToken = tokens.find((t) => t.symbol === token.twinTokenSymbol && isIcCkToken(t)) as - | IcCkToken - | undefined; - - if (twinToken && twinToken.decimals === token.decimals) { - groupedTokenTwins.add(twinToken.symbol); - groupedTokenTwins.add(token.symbol); - return createTokenGroup({ - nativeToken: token, - twinToken: twinToken - }); - } - - return token; - }); - - return mappedTokensWithGroups.filter( - (t) => isTokenUiGroup(t) || !groupedTokenTwins.has(t.symbol) - ); -}; diff --git a/src/frontend/src/tests/lib/utils/token-group.utils.spec.ts b/src/frontend/src/tests/lib/utils/token-group.utils.spec.ts new file mode 100644 index 0000000000..e07c66b38c --- /dev/null +++ b/src/frontend/src/tests/lib/utils/token-group.utils.spec.ts @@ -0,0 +1,174 @@ +import { ICP_NETWORK } from '$env/networks.env'; +import { BTC_MAINNET_TOKEN } from '$env/tokens.btc.env'; +import { ETHEREUM_TOKEN, ICP_TOKEN } from '$env/tokens.env'; +import type { TokenUi } from '$lib/types/token'; +import type { TokenUiGroup } from '$lib/types/token-group'; +import { groupTokensByTwin } from '$lib/utils/token-group.utils'; +import { BigNumber } from 'alchemy-sdk'; +import { describe, expect, it } from 'vitest'; + +const tokens = [ + { + ...BTC_MAINNET_TOKEN, + balance: BigNumber.from(1), + usdBalance: 50000 + }, + { + symbol: 'ckBTC', + network: ICP_NETWORK, + balance: BigNumber.from(2), + usdBalance: 100000, + standard: 'icrc', + category: 'default', + decimals: 8, + name: 'Chain key Bitcoin', + minterCanisterId: 'mc6ru-gyaaa-aaaar-qaaaq-cai' + }, + { + ...ETHEREUM_TOKEN, + balance: BigNumber.from(10), + usdBalance: 20000 + }, + { + symbol: 'ckETH', + network: ICP_NETWORK, + balance: BigNumber.from(5), + usdBalance: 15000, + standard: 'icrc', + category: 'default', + decimals: 18, + name: 'Chain key Ethereum', + minterCanisterId: 'apia6-jaaaa-aaaar-qabma-cai' + }, + { + ...ICP_TOKEN, + balance: BigNumber.from(50), + usdBalance: 1000 + } +]; + +const tokensWithMismatchedDecimals = [ + ...tokens, + { + symbol: 'FOO', + network: { + id: Symbol('FOO'), + name: 'Foo Network', + icon: 'foo-icon', + iconBW: 'foo-icon-bw', + env: 'mainnet' + }, + twinTokenSymbol: 'ckFOO', + balance: BigNumber.from(100), + usdBalance: 1000, + standard: 'ethereum', + category: 'default', + decimals: 8, + name: 'Foo Token' + }, + { + symbol: 'ckFOO', + network: ICP_NETWORK, + balance: BigNumber.from(200), + usdBalance: 2000, + standard: 'icrc', + category: 'default', + decimals: 9, // Mismatched decimals + name: 'Chain key Foo Token', + minterCanisterId: 'ckfoo-canister-id' + } +]; + +const reorderedTokens = [ + tokens[1], // ckBTC + tokens[0], // BTC + tokens[3], // ckETH + tokens[2], // ETH + tokens[4] // ICP +]; + +describe('groupTokensByTwin', () => { + it('should group tokens with matching twinTokenSymbol', () => { + const groupedTokens = groupTokensByTwin(tokens as TokenUi[]); + expect(groupedTokens).toHaveLength(3); + + const btcGroup = groupedTokens[0]; + expect(btcGroup).toHaveProperty('tokens'); + expect((btcGroup as TokenUiGroup).tokens).toHaveLength(2); + expect((btcGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('BTC'); + expect((btcGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('ckBTC'); + + const icpToken = groupedTokens[2]; + expect(icpToken).toHaveProperty('symbol', 'ICP'); + }); + + it('should handle tokens without twinTokenSymbol', () => { + const tokensWithoutTwins = [ICP_TOKEN]; + const groupedTokens = groupTokensByTwin(tokensWithoutTwins); + + expect(groupedTokens).toHaveLength(1); + expect(groupedTokens[0]).toHaveProperty('symbol', 'ICP'); + }); + + it('should place the group in the position of the first token', () => { + const groupedTokens = groupTokensByTwin(tokens as TokenUi[]); + const firstGroup = groupedTokens[0]; + expect(firstGroup).toHaveProperty('tokens'); + expect((firstGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('BTC'); + expect((firstGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('ckBTC'); + }); + + it('should not duplicate tokens in the result', () => { + const groupedTokens = groupTokensByTwin(tokens as TokenUi[]); + + const tokenSymbols = groupedTokens.flatMap((groupOrToken) => + 'tokens' in groupOrToken ? groupOrToken.tokens.map((t) => t.symbol) : [groupOrToken.symbol] + ); + const uniqueSymbols = new Set(tokenSymbols); + expect(uniqueSymbols.size).toBe(tokenSymbols.length); + }); + + it('should not group tokens when their decimals are mismatched', () => { + const groupedTokens = groupTokensByTwin(tokensWithMismatchedDecimals as TokenUi[]); + expect(groupedTokens).toHaveLength(5); + + const fooToken = groupedTokens.find((t) => 'symbol' in t && t.symbol === 'FOO'); + const ckFooToken = groupedTokens.find((t) => 'symbol' in t && t.symbol === 'ckFOO'); + + expect(fooToken).toBeDefined(); + expect(ckFooToken).toBeDefined(); + + expect(fooToken).not.toHaveProperty('tokens'); + expect(ckFooToken).not.toHaveProperty('tokens'); + }); + + it('should correctly group tokens even when the ckToken is declared before the native token', () => { + const groupedTokens = groupTokensByTwin(reorderedTokens as TokenUi[]); + + expect(groupedTokens).toHaveLength(3); + + const btcGroup = groupedTokens.find( + (groupOrToken) => + 'tokens' in groupOrToken && groupOrToken.tokens.some((t) => t.symbol === 'BTC') + ) as TokenUiGroup; + + expect(btcGroup).toBeDefined(); + expect(btcGroup.tokens).toHaveLength(2); + expect(btcGroup.tokens.map((t) => t.symbol)).toContain('BTC'); + expect(btcGroup.tokens.map((t) => t.symbol)).toContain('ckBTC'); + + const ethGroup = groupedTokens.find( + (groupOrToken) => + 'tokens' in groupOrToken && groupOrToken.tokens.some((t) => t.symbol === 'ETH') + ) as TokenUiGroup; + + expect(ethGroup).toBeDefined(); + expect(ethGroup.tokens).toHaveLength(2); + expect(ethGroup.tokens.map((t) => t.symbol)).toContain('ETH'); + expect(ethGroup.tokens.map((t) => t.symbol)).toContain('ckETH'); + + const icpToken = groupedTokens.find((t) => 'symbol' in t && t.symbol === 'ICP'); + + expect(icpToken).toBeDefined(); + }); +}); diff --git a/src/frontend/src/tests/lib/utils/token.utils.spec.ts b/src/frontend/src/tests/lib/utils/token.utils.spec.ts index 0977543341..4fdbc245e1 100644 --- a/src/frontend/src/tests/lib/utils/token.utils.spec.ts +++ b/src/frontend/src/tests/lib/utils/token.utils.spec.ts @@ -1,12 +1,9 @@ -import { ICP_NETWORK } from '$env/networks.env'; -import { BTC_MAINNET_TOKEN } from '$env/tokens.btc.env'; import { ETHEREUM_TOKEN, ICP_TOKEN } from '$env/tokens.env'; -import type { TokenStandard, TokenUi, TokenUiGroup } from '$lib/types/token'; +import type { TokenStandard, TokenUi } from '$lib/types/token'; import { usdValue } from '$lib/utils/exchange.utils'; import { calculateTokenUsdBalance, getMaxTransactionAmount, - groupTokensByTwin, mapTokenUi, sumTokenBalances, sumUsdBalances @@ -22,86 +19,6 @@ const tokenStandards: TokenStandard[] = ['ethereum', 'icp', 'icrc', 'bitcoin']; const balance = 1000000000n; const fee = 10000000n; -const tokens = [ - { - ...BTC_MAINNET_TOKEN, - balance: BigNumber.from(1), - usdBalance: 50000 - }, - { - symbol: 'ckBTC', - network: ICP_NETWORK, - balance: BigNumber.from(2), - usdBalance: 100000, - standard: 'icrc', - category: 'default', - decimals: 8, - name: 'Chain key Bitcoin', - minterCanisterId: 'mc6ru-gyaaa-aaaar-qaaaq-cai' - }, - { - ...ETHEREUM_TOKEN, - balance: BigNumber.from(10), - usdBalance: 20000 - }, - { - symbol: 'ckETH', - network: ICP_NETWORK, - balance: BigNumber.from(5), - usdBalance: 15000, - standard: 'icrc', - category: 'default', - decimals: 18, - name: 'Chain key Ethereum', - minterCanisterId: 'apia6-jaaaa-aaaar-qabma-cai' - }, - { - ...ICP_TOKEN, - balance: BigNumber.from(50), - usdBalance: 1000 - } -]; - -const tokensWithMismatchedDecimals = [ - ...tokens, - { - symbol: 'FOO', - network: { - id: Symbol('FOO'), - name: 'Foo Network', - icon: 'foo-icon', - iconBW: 'foo-icon-bw', - env: 'mainnet' - }, - twinTokenSymbol: 'ckFOO', - balance: BigNumber.from(100), - usdBalance: 1000, - standard: 'ethereum', - category: 'default', - decimals: 8, - name: 'Foo Token' - }, - { - symbol: 'ckFOO', - network: ICP_NETWORK, - balance: BigNumber.from(200), - usdBalance: 2000, - standard: 'icrc', - category: 'default', - decimals: 9, // Mismatched decimals - name: 'Chain key Foo Token', - minterCanisterId: 'ckfoo-canister-id' - } -]; - -const reorderedTokens = [ - tokens[1], // ckBTC - tokens[0], // BTC - tokens[3], // ckETH - tokens[2], // ETH - tokens[4] // ICP -]; - vi.mock('$lib/utils/exchange.utils', () => ({ usdValue: vi.fn() })); @@ -338,89 +255,3 @@ describe('sumUsdBalances', () => { expect(sumUsdBalances([undefined, undefined])).toBeUndefined(); }); }); - -describe('groupTokensByTwin', () => { - it('should group tokens with matching twinTokenSymbol', () => { - const groupedTokens = groupTokensByTwin(tokens as TokenUi[]); - expect(groupedTokens).toHaveLength(3); - - const btcGroup = groupedTokens[0]; - expect(btcGroup).toHaveProperty('tokens'); - expect((btcGroup as TokenUiGroup).tokens).toHaveLength(2); - expect((btcGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('BTC'); - expect((btcGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('ckBTC'); - - const icpToken = groupedTokens[2]; - expect(icpToken).toHaveProperty('symbol', 'ICP'); - }); - - it('should handle tokens without twinTokenSymbol', () => { - const tokensWithoutTwins = [ICP_TOKEN]; - const groupedTokens = groupTokensByTwin(tokensWithoutTwins); - - expect(groupedTokens).toHaveLength(1); - expect(groupedTokens[0]).toHaveProperty('symbol', 'ICP'); - }); - - it('should place the group in the position of the first token', () => { - const groupedTokens = groupTokensByTwin(tokens as TokenUi[]); - const firstGroup = groupedTokens[0]; - expect(firstGroup).toHaveProperty('tokens'); - expect((firstGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('BTC'); - expect((firstGroup as TokenUiGroup).tokens.map((t) => t.symbol)).toContain('ckBTC'); - }); - - it('should not duplicate tokens in the result', () => { - const groupedTokens = groupTokensByTwin(tokens as TokenUi[]); - - const tokenSymbols = groupedTokens.flatMap((groupOrToken) => - 'tokens' in groupOrToken ? groupOrToken.tokens.map((t) => t.symbol) : [groupOrToken.symbol] - ); - const uniqueSymbols = new Set(tokenSymbols); - expect(uniqueSymbols.size).toBe(tokenSymbols.length); - }); - - it('should not group tokens when their decimals are mismatched', () => { - const groupedTokens = groupTokensByTwin(tokensWithMismatchedDecimals as TokenUi[]); - expect(groupedTokens).toHaveLength(5); - - const fooToken = groupedTokens.find((t) => 'symbol' in t && t.symbol === 'FOO'); - const ckFooToken = groupedTokens.find((t) => 'symbol' in t && t.symbol === 'ckFOO'); - - expect(fooToken).toBeDefined(); - expect(ckFooToken).toBeDefined(); - - expect(fooToken).not.toHaveProperty('tokens'); - expect(ckFooToken).not.toHaveProperty('tokens'); - }); - - it('should correctly group tokens even when the ckToken is declared before the native token', () => { - const groupedTokens = groupTokensByTwin(reorderedTokens as TokenUi[]); - - expect(groupedTokens).toHaveLength(3); - - const btcGroup = groupedTokens.find( - (groupOrToken) => - 'tokens' in groupOrToken && groupOrToken.tokens.some((t) => t.symbol === 'BTC') - ) as TokenUiGroup; - - expect(btcGroup).toBeDefined(); - expect(btcGroup.tokens).toHaveLength(2); - expect(btcGroup.tokens.map((t) => t.symbol)).toContain('BTC'); - expect(btcGroup.tokens.map((t) => t.symbol)).toContain('ckBTC'); - - const ethGroup = groupedTokens.find( - (groupOrToken) => - 'tokens' in groupOrToken && groupOrToken.tokens.some((t) => t.symbol === 'ETH') - ) as TokenUiGroup; - - expect(ethGroup).toBeDefined(); - expect(ethGroup.tokens).toHaveLength(2); - expect(ethGroup.tokens.map((t) => t.symbol)).toContain('ETH'); - expect(ethGroup.tokens.map((t) => t.symbol)).toContain('ckETH'); - - const icpToken = groupedTokens.find((t) => 'symbol' in t && t.symbol === 'ICP'); - - expect(icpToken).toBeDefined(); - }); -}); From 13d66b94a9d6563520e751a61450318c4a75275b Mon Sep 17 00:00:00 2001 From: Antonio Ventilii <169057656+AntonioVentilii-DFINITY@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:55:38 +0100 Subject: [PATCH 02/31] refactor(frontend): Use colors mapping instead of blue-ribbon (#3256) # Motivation Only for Tailwind, we want to replace the use of `blue-ribbon` (`primary`) with the new colors mapping. --- src/frontend/src/lib/components/hero/HeroContent.svelte | 2 +- src/frontend/src/lib/components/signer/SignerAlert.svelte | 2 +- .../src/lib/components/signer/SignerAnimatedAstronaut.svelte | 4 ++-- src/frontend/src/lib/components/signer/SignerLoading.svelte | 2 +- src/frontend/src/lib/components/ui/ButtonAuthenticate.svelte | 2 +- src/frontend/src/lib/components/ui/Info.svelte | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/frontend/src/lib/components/hero/HeroContent.svelte b/src/frontend/src/lib/components/hero/HeroContent.svelte index f7770fa497..5ca56fc9f1 100644 --- a/src/frontend/src/lib/components/hero/HeroContent.svelte +++ b/src/frontend/src/lib/components/hero/HeroContent.svelte @@ -33,7 +33,7 @@
diff --git a/src/frontend/src/lib/components/signer/SignerAnimatedAstronaut.svelte b/src/frontend/src/lib/components/signer/SignerAnimatedAstronaut.svelte index f88a1f7727..ecf49ffb8c 100644 --- a/src/frontend/src/lib/components/signer/SignerAnimatedAstronaut.svelte +++ b/src/frontend/src/lib/components/signer/SignerAnimatedAstronaut.svelte @@ -4,10 +4,10 @@ diff --git a/src/frontend/src/lib/components/signer/SignerLoading.svelte b/src/frontend/src/lib/components/signer/SignerLoading.svelte index ee72aff435..6785db4017 100644 --- a/src/frontend/src/lib/components/signer/SignerLoading.svelte +++ b/src/frontend/src/lib/components/signer/SignerLoading.svelte @@ -4,7 +4,7 @@ -
+
diff --git a/src/frontend/src/lib/components/ui/ButtonAuthenticate.svelte b/src/frontend/src/lib/components/ui/ButtonAuthenticate.svelte index 83dd07fd57..1635b18c1f 100644 --- a/src/frontend/src/lib/components/ui/ButtonAuthenticate.svelte +++ b/src/frontend/src/lib/components/ui/ButtonAuthenticate.svelte @@ -7,7 +7,7 @@