Skip to content

Commit

Permalink
feat: add feature gate for zerion transaction feed (#6181)
Browse files Browse the repository at this point in the history
### Description

As the title.

While updating the TabHome tests, I noticed every test was throwing an
act warning (below) so this PR also updates the tests to remove the
errors.
```
Warning: An update to TransactionFeed inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
```

### Test plan

Tested via unit tests and manually.

### Related issues

n/a

### Backwards compatibility

Y

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [ ] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
kathaypacific authored Oct 22, 2024
1 parent 16e2f2e commit fe2ca49
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 36 deletions.
94 changes: 59 additions & 35 deletions src/home/TabHome.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { act, render } from '@testing-library/react-native'
import { render, waitFor } from '@testing-library/react-native'
import { FetchMock } from 'jest-fetch-mock/types'
import * as React from 'react'
import { Provider } from 'react-redux'
import TabHome from 'src/home/TabHome'
import { Actions as IdentityActions } from 'src/identity/actions'
import { RootState } from 'src/redux/reducers'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import { NetworkId } from 'src/transactions/types'
import MockedNavigator from 'test/MockedNavigator'
import { RecursivePartial, createMockStore } from 'test/utils'
Expand All @@ -22,6 +22,16 @@ import {
mockStoreRewardReayWithDifferentNft,
} from 'test/values'

// Mocking the TransactionFeedV2 component, as it has comprehensive unit tests
// already. For the purposes of the TabHome test, we only need to verify that
// the component is rendered at the expected time.
jest.mock('src/transactions/feed/TransactionFeedV2', () => {
const React = require('react')
const { Text } = require('react-native')

return jest.fn(() => <Text>Transaction feed v2</Text>)
})

jest.mock('src/web3/networkConfig', () => {
const originalModule = jest.requireActual('src/web3/networkConfig')
return {
Expand Down Expand Up @@ -122,21 +132,19 @@ describe('TabHome', () => {
},
})

await act(() => {
jest.runOnlyPendingTimers()
})

// Multiple elements use this text with the scroll aware header
expect(tree.getByTestId('HomeActionsCarousel')).toBeTruthy()
expect(tree.queryByText('notificationCenterSpotlight.message')).toBeFalsy()
expect(tree.queryByTestId('HomeTokenBalance')).toBeFalsy()
expect(tree.queryByTestId('cashInBtn')).toBeFalsy()
expect(store.getActions().map((action) => action.type)).toEqual(
expect.arrayContaining([
'HOME/VISIT_HOME',
'HOME/REFRESH_BALANCES',
'IDENTITY/IMPORT_CONTACTS',
])
await waitFor(() =>
expect(store.getActions().map((action) => action.type)).toEqual(
expect.arrayContaining([
'HOME/VISIT_HOME',
'HOME/REFRESH_BALANCES',
'IDENTITY/IMPORT_CONTACTS',
])
)
)
})

Expand All @@ -150,50 +158,66 @@ describe('TabHome', () => {
},
})

await act(() => {
jest.runOnlyPendingTimers()
await waitFor(() =>
expect(store.getActions().map((action) => action.type)).toEqual(
expect.arrayContaining(['HOME/VISIT_HOME', 'HOME/REFRESH_BALANCES'])
)
)
})

it('renders the v2 transaction feed when the feature gate is enabled', async () => {
jest.mocked(getFeatureGate).mockImplementation((featureGate) => {
if (featureGate === StatsigFeatureGates.SHOW_ZERION_TRANSACTION_FEED) {
return true
}
return false
})
const { tree } = renderScreen()

const importContactsAction = store
.getActions()
.find((action) => action.type === IdentityActions.IMPORT_CONTACTS)
expect(importContactsAction).toBeFalsy()
await waitFor(() => expect(tree.getByText('Transaction feed v2')).toBeTruthy())
})

describe('nft reward bottom sheet', () => {
beforeEach(() => {
jest.mocked(getFeatureGate).mockReturnValue(true)
jest.mocked(getFeatureGate).mockImplementation((featureGate) => {
if (featureGate === StatsigFeatureGates.SHOW_NFT_REWARD) {
return true
}
return false
})
})

afterEach(() => {
jest.clearAllMocks()
jest.useFakeTimers({ doNotFake: ['Date'] })
})

it('renders correctly when status is "reward ready"', () => {
it('renders correctly when status is "reward ready"', async () => {
const { getByText } = renderScreen({
...mockStoreRewardReady,
app: {
showNotificationSpotlight: false,
},
})

expect(getByText('nftCelebration.rewardBottomSheet.title')).toBeTruthy()
await waitFor(() => expect(getByText('nftCelebration.rewardBottomSheet.title')).toBeTruthy())
expect(
getByText('nftCelebration.rewardBottomSheet.description, {"nftName":"John Doe.fizzBuzz"}')
).toBeTruthy()
expect(getByText('nftCelebration.rewardBottomSheet.cta')).toBeTruthy()
})

it('renders correctly when status is "reminder ready"', () => {
it('renders correctly when status is "reminder ready"', async () => {
const { getByText } = renderScreen({
...mockStoreReminderReady,
app: {
showNotificationSpotlight: false,
},
})

expect(getByText('nftCelebration.rewardReminderBottomSheet.title')).toBeTruthy()
await waitFor(() =>
expect(getByText('nftCelebration.rewardReminderBottomSheet.title')).toBeTruthy()
)
expect(
getByText(
'nftCelebration.rewardReminderBottomSheet.description, {"nftName":"John Doe.fizzBuzz"}'
Expand All @@ -202,33 +226,33 @@ describe('TabHome', () => {
expect(getByText('nftCelebration.rewardReminderBottomSheet.cta')).toBeTruthy()
})

it('does not render when status is other than "reward ready" or "reminder ready"', () => {
it('does not render when status is other than "reward ready" or "reminder ready"', async () => {
const { queryByText } = renderScreen({
...mockStoreCelebrationReady,
app: {
showNotificationSpotlight: false,
},
})

expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull()
await waitFor(() => expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull())
expect(queryByText('nftCelebration.rewardBottomSheet.description')).toBeNull()
expect(queryByText('nftCelebration.rewardBottomSheet.cta')).toBeNull()
})

it('does not render when celebrated contract does not match with user nft', () => {
it('does not render when celebrated contract does not match with user nft', async () => {
const { queryByText } = renderScreen({
...mockStoreRewardReayWithDifferentNft,
app: {
showNotificationSpotlight: false,
},
})

expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull()
await waitFor(() => expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull())
expect(queryByText('nftCelebration.rewardBottomSheet.description')).toBeNull()
expect(queryByText('nftCelebration.rewardBottomSheet.cta')).toBeNull()
})

it('does not render when feature gate is closed', () => {
it('does not render when feature gate is closed', async () => {
jest.mocked(getFeatureGate).mockReturnValue(false)

const { queryByText } = renderScreen({
Expand All @@ -238,38 +262,38 @@ describe('TabHome', () => {
},
})

expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull()
await waitFor(() => expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull())
expect(queryByText('nftCelebration.rewardBottomSheet.description')).toBeNull()
expect(queryByText('nftCelebration.rewardBottomSheet.cta')).toBeNull()
})

it('does not render if reward is already displayed', () => {
it('does not render if reward is already displayed', async () => {
const { queryByText } = renderScreen({
...mockStoreRewardDisplayed,
app: {
showNotificationSpotlight: false,
},
})

expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull()
await waitFor(() => expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull())
expect(queryByText('nftCelebration.rewardBottomSheet.description')).toBeNull()
expect(queryByText('nftCelebration.rewardBottomSheet.cta')).toBeNull()
})

it('does not render if reminder is already displayed', () => {
it('does not render if reminder is already displayed', async () => {
const { queryByText } = renderScreen({
...mockStoreReminderDisplayed,
app: {
showNotificationSpotlight: false,
},
})

expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull()
await waitFor(() => expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull())
expect(queryByText('nftCelebration.rewardBottomSheet.description')).toBeNull()
expect(queryByText('nftCelebration.rewardBottomSheet.cta')).toBeNull()
})

it('does not render if expired', () => {
it('does not render if expired', async () => {
jest.useFakeTimers({ now: new Date('3001-01-01T00:00:00.000Z') })

const { queryByText } = renderScreen({
Expand All @@ -279,7 +303,7 @@ describe('TabHome', () => {
},
})

expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull()
await waitFor(() => expect(queryByText('nftCelebration.rewardBottomSheet.title')).toBeNull())
expect(queryByText('nftCelebration.rewardBottomSheet.description')).toBeNull()
expect(queryByText('nftCelebration.rewardBottomSheet.cta')).toBeNull()
})
Expand Down
10 changes: 9 additions & 1 deletion src/home/TabHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ import { StackParamList } from 'src/navigator/types'
import { phoneRecipientCacheSelector } from 'src/recipients/reducer'
import { useDispatch, useSelector } from 'src/redux/hooks'
import { initializeSentryUserContext } from 'src/sentry/actions'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import colors from 'src/styles/colors'
import TransactionFeed from 'src/transactions/feed/TransactionFeed'
import TransactionFeedV2 from 'src/transactions/feed/TransactionFeedV2'
import { hasGrantedContactsPermission } from 'src/utils/contacts'

const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
Expand All @@ -53,6 +56,8 @@ function TabHome(_props: Props) {
const canShowNftReward = useSelector(showNftRewardSelector)
const showNftReward = canShowNftReward && isFocused && !showNotificationSpotlight

const showZerionTransactionFeed = getFeatureGate(StatsigFeatureGates.SHOW_ZERION_TRANSACTION_FEED)

useEffect(() => {
dispatch(visitHome())
}, [])
Expand Down Expand Up @@ -120,7 +125,10 @@ function TabHome(_props: Props) {
key: 'NotificationBox',
component: <NotificationBox showOnlyHomeScreenNotifications={true} />,
},
{ key: 'TransactionFeed', component: <TransactionFeed /> },
{
key: 'TransactionFeed',
component: showZerionTransactionFeed ? <TransactionFeedV2 /> : <TransactionFeed />,
},
]

const renderItem = ({ item }: { item: any }) => item.component
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 {
SHOW_SWAP_AND_DEPOSIT = 'show_swap_and_deposit',
SHOW_UK_COMPLIANT_VARIANT = 'show_uk_compliant_variant',
ALLOW_EARN_PARTIAL_WITHDRAWAL = 'allow_earn_partial_withdrawal',
SHOW_ZERION_TRANSACTION_FEED = 'show_zerion_transaction_feed',
}

export enum StatsigExperiments {
Expand Down

0 comments on commit fe2ca49

Please sign in to comment.