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
75 changes: 43 additions & 32 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,6 +77,9 @@ function TokenBalance({
: undefined
const { decimalSeparator } = getNumberFormatSettings()

const hideBalanceSelectorReturn = useSelector(hideHomeBalancesSelector)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const hideBalanceSelectorReturn = useSelector(hideHomeBalancesSelector)
const hideHomeBalanceState = useSelector(hideHomeBalancesSelector)

?
here and other places.

const hideBalance = showHideHomeBalancesToggle && hideBalanceSelectorReturn

const balanceDisplay = hideBalance ? `XX${decimalSeparator}XX` : totalBalanceLocal?.toFormat(2)

if (tokenFetchError || tokenFetchLoading || tokensAreStale) {
Expand All @@ -97,10 +100,13 @@ 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>
<View style={styles.row}>
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? '-'}
</Text>
{showHideHomeBalancesToggle && <HideBalanceButton hideBalance={hideBalance} />}
</View>
{!hideBalance && (
<Text style={styles.tokenBalance}>
{formatValueToDisplay(tokenBalance)} {tokensWithUsdValue[0].symbol}
Expand All @@ -111,14 +117,30 @@ function TokenBalance({
)
} else {
return (
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? new BigNumber(0).toFormat(2)}
</Text>
<View style={styles.row}>
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? new BigNumber(0).toFormat(2)}
</Text>
{showHideHomeBalancesToggle && <HideBalanceButton hideBalance={hideBalance} />}
</View>
Copy link
Contributor

@satish-ravi satish-ravi Nov 6, 2023

Choose a reason for hiding this comment

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

looks like a lot of boolean expressions are duplicated. Maybe you can make this DRY by having an inner render instead of just balance display?
E.g.,

const renderTotalTokenBalance(default: string) = (
  <View style={styles.row}>
      <Text style={style} testID={'TotalTokenBalance'}>
        {!hideBalance && localCurrencySymbol}
        {hideBalance ? `XX${decimalSeparator}XX` : totalBalanceLocal?.toFormat(2) ?? default}
      </Text>
      {showHideHomeBalancesToggle && <HideBalanceButton hideBalance={hideBalance} />}
    </View>
)

and then do renderTotalTokenBalance('-') and renderTotalTokenBalance(new BigNumber(0).toFormat(2)) in the two places. Also not sure why one uses 0 and other - for the default.

)
}
}

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 +240,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 +263,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 +292,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
32 changes: 31 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 @@ -32,7 +35,13 @@ jest.mock('src/web3/networkConfig', () => {
}
})

jest.mocked(getFeatureGate).mockReturnValue(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

is this required? Looks like the one in beforeEach should suffice.


describe('SwapFeedItem', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(true)
})
function renderScreen({
storeOverrides = {},
inAmount,
Expand Down Expand Up @@ -117,7 +126,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 hideBalanceSelectorReturn = useSelector(hideHomeBalancesSelector)
const hideBalance =
getFeatureGate(StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE) && hideBalanceSelectorReturn

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
6 changes: 5 additions & 1 deletion src/transactions/feed/TransferFeedItem.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 Down Expand Up @@ -38,7 +40,9 @@ function TransferFeedItem({ transfer }: Props) {

const colorStyle = new BigNumber(amount.value).isPositive() ? { color: colors.greenUI } : {}

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

return (
<Touchable testID="TransferFeedItem" disabled={false} onPress={openTransferDetails}>
Expand Down