Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add feature gate for hide balances, fix eye icon alignment #4431

Merged
merged 10 commits into from
Nov 8, 2023
12 changes: 6 additions & 6 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export enum Actions {
PUSH_NOTIFICATIONS_PERMISSION_CHANGED = 'APP/PUSH_NOTIFICATIONS_PERMISSION_CHANGED',
IN_APP_REVIEW_REQUESTED = 'APP/IN_APP_REVIEW_REQUESTED',
NOTIFICATION_SPOTLIGHT_SEEN = 'APP/NOTIFICATION_SPOTLIGHT_SEEN',
TOGGLE_HIDE_BALANCES = 'APP/TOGGLE_HIDE_BALANCES',
TOGGLE_HIDE_HOME_BALANCES = 'APP/TOGGLE_HIDE_HOME_BALANCES',
}

export interface SetAppState {
Expand Down Expand Up @@ -170,8 +170,8 @@ export interface NotificationSpotlightSeen {
type: Actions.NOTIFICATION_SPOTLIGHT_SEEN
}

interface ToggleHideBalances {
type: Actions.TOGGLE_HIDE_BALANCES
interface ToggleHideHomeBalances {
type: Actions.TOGGLE_HIDE_HOME_BALANCES
}

export type ActionTypes =
Expand Down Expand Up @@ -201,7 +201,7 @@ export type ActionTypes =
| PushNotificationsPermissionChanged
| inAppReviewRequested
| NotificationSpotlightSeen
| ToggleHideBalances
| ToggleHideHomeBalances

export const setAppState = (state: string): SetAppState => ({
type: Actions.SET_APP_STATE,
Expand Down Expand Up @@ -369,8 +369,8 @@ export const notificationSpotlightSeen = (): NotificationSpotlightSeen => {
}
}

export const toggleHideBalances = (): ToggleHideBalances => {
export const toggleHideHomeBalances = (): ToggleHideHomeBalances => {
return {
type: Actions.TOGGLE_HIDE_BALANCES,
type: Actions.TOGGLE_HIDE_HOME_BALANCES,
}
}
2 changes: 1 addition & 1 deletion src/app/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@
...state,
inAppReviewLastInteractionTimestamp: action.inAppReviewLastInteractionTimestamp,
}
case Actions.TOGGLE_HIDE_BALANCES:
case Actions.TOGGLE_HIDE_HOME_BALANCES:

Check warning on line 271 in src/app/reducers.ts

View check run for this annotation

Codecov / codecov/patch

src/app/reducers.ts#L271

Added line #L271 was not covered by tests
return {
...state,
hideHomeBalances: !state.hideHomeBalances,
Expand Down
42 changes: 42 additions & 0 deletions src/components/TokenBalance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,48 @@ describe('FiatExchangeTokenBalance and HomeTokenBalance', () => {
expect(tree.getByTestId('EyeIcon')).toBeTruthy()
})

it('renders correctly when feature flag is off, hideBalance is false', async () => {
jest
.mocked(getFeatureGate)
.mockImplementation(
(featureGate) =>
featureGate !== StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN &&
featureGate !== StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE
)
const store = createMockStore(defaultStore)

const tree = render(
<Provider store={store}>
<HomeTokenBalance />
</Provider>
)

expect(getElementText(tree.getByTestId('TotalTokenBalance'))).toEqual('$8.41')
expect(tree.queryByTestId('EyeIcon')).toBeFalsy()
expect(tree.queryByTestId('HiddenEyeIcon')).toBeFalsy()
})

it('renders correctly when feature flag is off, hideBalance is true', async () => {
jest
.mocked(getFeatureGate)
.mockImplementation(
(featureGate) =>
featureGate !== StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN &&
featureGate !== StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE
)
const store = createMockStore({ ...defaultStore, app: { hideHomeBalances: true } })

const tree = render(
<Provider store={store}>
<HomeTokenBalance />
</Provider>
)

expect(getElementText(tree.getByTestId('TotalTokenBalance'))).toEqual('$8.41')
expect(tree.queryByTestId('EyeIcon')).toBeFalsy()
expect(tree.queryByTestId('HiddenEyeIcon')).toBeFalsy()
})

it('tracks analytics event when eye icon is pressed', async () => {
const store = createMockStore(defaultStore)

Expand Down
81 changes: 43 additions & 38 deletions src/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { hideAlert, showToast } from 'src/alert/actions'
import { AssetsEvents, FiatExchangeEvents, HomeEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import { toggleHideBalances } from 'src/app/actions'
import { toggleHideHomeBalances } from 'src/app/actions'
import { hideHomeBalancesSelector } from 'src/app/selectors'
import Dialog from 'src/components/Dialog'
import { formatValueToDisplay } from 'src/components/TokenDisplay'
Expand Down Expand Up @@ -55,11 +55,11 @@ import { getSupportedNetworkIdsForTokenBalances } from 'src/tokens/utils'
function TokenBalance({
style = styles.balance,
singleTokenViewEnabled = true,
hideBalance = false,
showHideHomeBalancesToggle = false,
}: {
style?: StyleProp<TextStyle>
singleTokenViewEnabled?: boolean
hideBalance?: boolean
showHideHomeBalancesToggle?: boolean
}) {
const supportedNetworkIds = getSupportedNetworkIdsForTokenBalances()
const tokensWithUsdValue = useTokensWithUsdValue(supportedNetworkIds)
Expand All @@ -77,16 +77,25 @@ function TokenBalance({
: undefined
const { decimalSeparator } = getNumberFormatSettings()

const hideHomeBalanceState = useSelector(hideHomeBalancesSelector)
const hideBalance = showHideHomeBalancesToggle && hideHomeBalanceState
const balanceDisplay = hideBalance ? `XX${decimalSeparator}XX` : totalBalanceLocal?.toFormat(2)

if (tokenFetchError || tokenFetchLoading || tokensAreStale) {
// Show '-' if we haven't fetched the tokens yet or prices are stale
const RenderTotalTokenBalance = ({ balanceDisplay }: { balanceDisplay: string }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non blocking naming nit: I think we use render prefix if we call this as a function. Since its used as a component, you can drop the Render prefix

return (
<Text style={style} testID={'TotalTokenBalance'}>
{localCurrencySymbol}
{'-'}
</Text>
<View style={styles.row}>
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay}
</Text>
{showHideHomeBalancesToggle && <HideBalanceButton hideBalance={hideBalance} />}
</View>
)
}

if (tokenFetchError || tokenFetchLoading || tokensAreStale) {
// Show '-' if we haven't fetched the tokens yet or prices are stale
return <RenderTotalTokenBalance balanceDisplay={'-'} />
} else if (
singleTokenViewEnabled &&
tokensWithUsdValue.length === 1 &&
Expand All @@ -97,10 +106,7 @@ function TokenBalance({
<View style={styles.oneBalance}>
<Image source={{ uri: tokensWithUsdValue[0].imageUrl }} style={styles.tokenImg} />
<View style={styles.column}>
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? '-'}
</Text>
<RenderTotalTokenBalance balanceDisplay={balanceDisplay ?? '-'} />
{!hideBalance && (
<Text style={styles.tokenBalance}>
{formatValueToDisplay(tokenBalance)} {tokensWithUsdValue[0].symbol}
Expand All @@ -111,14 +117,24 @@ function TokenBalance({
)
} else {
return (
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? new BigNumber(0).toFormat(2)}
</Text>
<RenderTotalTokenBalance balanceDisplay={balanceDisplay ?? new BigNumber(0).toFormat(2)} />
)
}
}

function HideBalanceButton({ hideBalance }: { hideBalance: boolean }) {
const dispatch = useDispatch()
const eyeIconOnPress = () => {
ValoraAnalytics.track(hideBalance ? HomeEvents.show_balances : HomeEvents.hide_balances)
dispatch(toggleHideHomeBalances())
}
return (
<Touchable onPress={eyeIconOnPress} hitSlop={variables.iconHitslop}>
{hideBalance ? <HiddenEyeIcon /> : <EyeIcon />}
</Touchable>
)
}

function useErrorMessageWithRefresh() {
const { t } = useTranslation()

Expand Down Expand Up @@ -218,7 +234,6 @@ export function AssetsTokenBalance({ showInfo }: { showInfo: boolean }) {

export function HomeTokenBalance() {
const { t } = useTranslation()
const dispatch = useDispatch()

const totalBalance = useTotalTokenBalance()
const tokenBalances = useTokensWithTokenBalance()
Expand All @@ -242,13 +257,6 @@ export function HomeTokenBalance() {

const [infoVisible, setInfoVisible] = useState(false)

const hideBalance = useSelector(hideHomeBalancesSelector)

const eyeIconOnPress = () => {
ValoraAnalytics.track(hideBalance ? HomeEvents.show_balances : HomeEvents.hide_balances)
dispatch(toggleHideBalances())
}

return (
<View style={styles.container} testID="HomeTokenBalance">
<View style={styles.title}>
Expand Down Expand Up @@ -278,19 +286,16 @@ export function HomeTokenBalance() {
</TouchableOpacity>
)}
</View>
<View style={styles.row}>
<TokenBalance
style={
getFeatureGate(StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN)
? styles.totalBalance
: styles.balance
}
hideBalance={hideBalance}
/>
<Touchable onPress={eyeIconOnPress} hitSlop={variables.iconHitslop}>
{hideBalance ? <HiddenEyeIcon /> : <EyeIcon />}
</Touchable>
</View>
<TokenBalance
style={
getFeatureGate(StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN)
? styles.totalBalance
: styles.balance
}
showHideHomeBalancesToggle={getFeatureGate(
StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE
)}
/>
</View>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/statsig/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const FeatureGates = {
[StatsigFeatureGates.USE_VIEM_FOR_WALLETCONNECT_TRANSACTIONS]: false,
[StatsigFeatureGates.USE_NEW_RECIPIENT_SCREEN]: false,
[StatsigFeatureGates.USE_NEW_SEND_FLOW]: false,
[StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE]: false,
}

export const ExperimentConfigs = {
Expand Down
1 change: 1 addition & 0 deletions src/statsig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export enum StatsigFeatureGates {
USE_VIEM_FOR_WALLETCONNECT_TRANSACTIONS = 'use_viem_for_walletconnect_transactions',
USE_NEW_RECIPIENT_SCREEN = 'use_new_recipient_screen',
USE_NEW_SEND_FLOW = 'use_new_send_flow',
SHOW_HIDE_HOME_BALANCES_TOGGLE = 'show_hide_home_balances_toggle',
}

export enum StatsigExperiments {
Expand Down
30 changes: 29 additions & 1 deletion src/transactions/feed/SwapFeedItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { render } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
import { RootState } from 'src/redux/reducers'
import { getFeatureGate } from 'src/statsig'
import SwapFeedItem from 'src/transactions/feed/SwapFeedItem'
import {
Fee,
Expand All @@ -16,6 +17,8 @@ import { mockCeurTokenId, mockCusdTokenId } from 'test/values'

const MOCK_TX_HASH = '0x006b866d20452a24d1d90c7514422188cc7c5d873e2f1ed661ec3f810ad5331c'

jest.mock('src/statsig')

jest.mock('src/web3/networkConfig', () => {
const originalModule = jest.requireActual('src/web3/networkConfig')
return {
Expand All @@ -33,6 +36,10 @@ jest.mock('src/web3/networkConfig', () => {
})

describe('SwapFeedItem', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(true)
})
function renderScreen({
storeOverrides = {},
inAmount,
Expand Down Expand Up @@ -117,7 +124,28 @@ describe('SwapFeedItem', () => {
expect(getElementText(getByTestId('SwapFeedItem/outgoingAmount'))).toEqual('-17.54 cEUR')
})

it('hides balance when flag is set', async () => {
it('still shows balances when feature gate false, hide balances root state true', async () => {
jest.mocked(getFeatureGate).mockReturnValue(false)
const { getByTestId } = renderScreen({
inAmount: {
tokenId: mockCeurTokenId,
value: 2.93,
},
outAmount: {
tokenId: mockCusdTokenId,
value: 2.87,
},
})

expect(getElementText(getByTestId('SwapFeedItem/title'))).toEqual('swapScreen.title')
expect(getElementText(getByTestId('SwapFeedItem/subtitle'))).toEqual(
'feedItemSwapPath, {"token1":"cUSD","token2":"cEUR"}'
)
expect(getElementText(getByTestId('SwapFeedItem/incomingAmount'))).toEqual('+2.93 cEUR')
expect(getElementText(getByTestId('SwapFeedItem/outgoingAmount'))).toEqual('-2.87 cUSD')
})

it('hides balance when feature gate true, root state hide home balances flag is set', async () => {
const { queryByTestId } = renderScreen({
inAmount: {
tokenId: mockCeurTokenId,
Expand Down
7 changes: 6 additions & 1 deletion src/transactions/feed/SwapFeedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import TokenDisplay from 'src/components/TokenDisplay'
import Touchable from 'src/components/Touchable'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import colors from 'src/styles/colors'
import fontStyles from 'src/styles/fonts'
import variables from 'src/styles/variables'
Expand All @@ -29,7 +31,10 @@ function SwapFeedItem({ exchange }: Props) {
navigate(Screens.TransactionDetailsScreen, { transaction: exchange })
ValoraAnalytics.track(HomeEvents.transaction_feed_item_select)
}
const hideBalance = useSelector(hideHomeBalancesSelector)

const hideHomeBalanceState = useSelector(hideHomeBalancesSelector)
const hideBalance =
getFeatureGate(StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE) && hideHomeBalanceState

return (
<Touchable testID="SwapFeedItem" onPress={handleTransferDetails}>
Expand Down
16 changes: 15 additions & 1 deletion src/transactions/feed/TransferFeedItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote'
import { CICOFlow } from 'src/fiatExchanges/utils'
import { LocalCurrencyCode } from 'src/localCurrency/consts'
import { RootState } from 'src/redux/reducers'
import { getFeatureGate } from 'src/statsig'
import TransferFeedItem from 'src/transactions/feed/TransferFeedItem'
import {
Fee,
Expand Down Expand Up @@ -41,6 +42,8 @@ const MOCK_CONTACT = {
address: MOCK_ADDRESS,
}

jest.mock('src/statsig')

jest.mock('src/web3/networkConfig', () => {
const originalModule = jest.requireActual('src/web3/networkConfig')
return {
Expand All @@ -58,6 +61,10 @@ jest.mock('src/web3/networkConfig', () => {
})

describe('TransferFeedItem', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(true)
})
function renderScreen({
storeOverrides = {},
type = TokenTransactionTypeV2.Sent,
Expand Down Expand Up @@ -618,7 +625,14 @@ describe('TransferFeedItem', () => {
})
})

it('hides balance when flag is set', async () => {
it('shows balance when feature gate false, root state hide home balances flag is set', async () => {
jest.mocked(getFeatureGate).mockReturnValue(false)
const { getByTestId } = renderScreen({ storeOverrides: { app: { hideHomeBalances: true } } })
expect(getByTestId('TransferFeedItem/amount')).toBeTruthy()
expect(getByTestId('TransferFeedItem/tokenAmount')).toBeTruthy()
})

it('hides balance when feature gate true, root state hide home balances flag is set', async () => {
const { queryByTestId } = renderScreen({ storeOverrides: { app: { hideHomeBalances: true } } })
expect(queryByTestId('TransferFeedItem/amount')).toBeNull()
expect(queryByTestId('TransferFeedItem/tokenAmount')).toBeNull()
Expand Down
Loading