Skip to content

Commit

Permalink
fix(points): hide live links when user has no jumpstart tokens (#5751)
Browse files Browse the repository at this point in the history
### Description

Hide live links activity when a user has no jumpstart tokens (or live
links are disabled).
This is consistent with the send flow behavior when the live link option
is not shown when a user has no jumpstart tokens.

If the user tries to create a jumpstart link without having the
jumpstart tokens the app will crash.

Context:
https://valora-app.slack.com/archives/C04B61SJ6DS/p1723075264724759

### Test plan

* Tested manually
* Updated unit test

### Related issues

NA

### 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
bakoushin authored Aug 9, 2024
1 parent 4b1dfda commit 2a68163
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 8 deletions.
19 changes: 18 additions & 1 deletion src/points/PointsHome.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { fireEvent, render, waitFor } from '@testing-library/react-native'
import * as React from 'react'
import { Provider } from 'react-redux'
import { PointsEvents } from 'src/analytics/Events'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { PointsEvents } from 'src/analytics/Events'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import PointsHome from 'src/points/PointsHome'
import { getHistoryStarted, getPointsConfigRetry } from 'src/points/slice'
import { RootState } from 'src/redux/store'
import { getDynamicConfigParams, getFeatureGate } from 'src/statsig'
import { NetworkId } from 'src/transactions/types'
import { RecursivePartial, createMockStore, getMockStackScreenProps } from 'test/utils'

jest.mock('src/statsig')
jest.mock('src/points/PointsHistoryBottomSheet')

const mockScreenProps = () => getMockStackScreenProps(Screens.PointsHome)
Expand All @@ -36,6 +39,16 @@ const renderPointsHome = (storeOverrides?: RecursivePartial<RootState>) => {
},
pointsConfigStatus: 'success',
},
tokens: {
tokenBalances: {
['celo-alfajores:0xusd']: {
tokenId: 'celo-alfajores:0xabcd',
address: '0xabcd',
networkId: NetworkId['celo-alfajores'],
balance: '10',
},
},
},
}
)
const tree = render(
Expand All @@ -53,6 +66,10 @@ const renderPointsHome = (storeOverrides?: RecursivePartial<RootState>) => {
describe(PointsHome, () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(true)
jest
.mocked(getDynamicConfigParams)
.mockReturnValue({ jumpstartContracts: { 'celo-alfajores': '0x1234' } })
})

it('renders a loading state while loading config', async () => {
Expand Down
91 changes: 91 additions & 0 deletions src/points/selectors.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { pointsActivitiesSelector, pointsHistorySelector } from 'src/points/selectors'
import { getDynamicConfigParams, getFeatureGate } from 'src/statsig'
import { NetworkId } from 'src/transactions/types'
import { getMockStoreData } from 'test/utils'

jest.mock('src/statsig')

describe('pointsHistorySelector', () => {
it('returns UNIX timestamp', () => {
const stateWithPointsHistory = getMockStoreData({
Expand All @@ -27,6 +31,12 @@ describe('pointsHistorySelector', () => {
})

describe('pointsActivitiesSelector', () => {
beforeEach(() => {
jest
.mocked(getDynamicConfigParams)
.mockReturnValue({ jumpstartContracts: { 'celo-alfajores': '0x1234' } })
})

it('should return an empty array if there are no activities', () => {
const stateWithoutPointsConfig = getMockStoreData({
points: {
Expand Down Expand Up @@ -61,4 +71,85 @@ describe('pointsActivitiesSelector', () => {
{ activityId: 'create-wallet', pointsAmount: 10, completed: true },
])
})

it('should return points activities with live links when enabled and user has jumpstart tokens', () => {
jest.mocked(getFeatureGate).mockReturnValue(true)

const stateWithPointsConfig = getMockStoreData({
points: {
pointsConfig: {
activitiesById: {
'create-live-link': { pointsAmount: 10 },
},
},
},
tokens: {
tokenBalances: {
['celo-alfajores:0xusd']: {
tokenId: 'celo-alfajores:0xabcd',
address: '0xabcd',
networkId: NetworkId['celo-alfajores'],
balance: '10',
},
},
},
})
const result = pointsActivitiesSelector(stateWithPointsConfig)

expect(result).toEqual([{ activityId: 'create-live-link', pointsAmount: 10, completed: false }])
})

it('should return points activities without live links if they are disabled', () => {
jest.mocked(getFeatureGate).mockReturnValue(false)

const stateWithPointsConfig = getMockStoreData({
points: {
pointsConfig: {
activitiesById: {
'create-live-link': { pointsAmount: 10 },
},
},
},
tokens: {
tokenBalances: {
['celo-alfajores:0xusd']: {
tokenId: 'celo-alfajores:0xabcd',
address: '0xabcd',
networkId: NetworkId['celo-alfajores'],
balance: '10',
},
},
},
})
const result = pointsActivitiesSelector(stateWithPointsConfig)

expect(result).toEqual([])
})

it('should return points activities without live links if user has no jumpstart tokens', () => {
jest.mocked(getFeatureGate).mockReturnValue(true)

const stateWithPointsConfig = getMockStoreData({
points: {
pointsConfig: {
activitiesById: {
'create-live-link': { pointsAmount: 10 },
},
},
},
tokens: {
tokenBalances: {
['celo-alfajores:0xusd']: {
tokenId: 'celo-alfajores:0xabcd',
address: '0xabcd',
networkId: NetworkId['celo-alfajores'],
balance: '0',
},
},
},
})
const result = pointsActivitiesSelector(stateWithPointsConfig)

expect(result).toEqual([])
})
})
32 changes: 25 additions & 7 deletions src/points/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { createSelector } from 'reselect'
import { ClaimHistoryCardItem, PointsActivity, PointsActivityId } from 'src/points/types'
import { RootState } from 'src/redux/reducers'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import { jumpstartSendTokensSelector } from 'src/tokens/selectors'

export const nextPageUrlSelector = (state: RootState) => {
return state.points.nextPageUrl
Expand Down Expand Up @@ -35,14 +38,29 @@ export const pointsConfigStatusSelector = (state: RootState) => state.points.poi

const pointsConfigSelector = (state: RootState) => state.points.pointsConfig

const showJumpstartSendSelector = () => getFeatureGate(StatsigFeatureGates.SHOW_JUMPSTART_SEND)

export const pointsActivitiesSelector = createSelector(
[pointsConfigSelector, trackOnceActivitiesSelector],
(pointsConfig, trackOnceActivities) => {
return Object.entries(pointsConfig.activitiesById).map(([activityId, metadata]) => ({
...metadata,
activityId,
completed: trackOnceActivities[activityId as PointsActivityId] ?? false,
})) as PointsActivity[]
[
pointsConfigSelector,
trackOnceActivitiesSelector,
jumpstartSendTokensSelector,
showJumpstartSendSelector,
],
(pointsConfig, trackOnceActivities, jumpstartTokens, jumpstartSendEnabled) => {
const showJumpstart = jumpstartSendEnabled && jumpstartTokens.length > 0
const excludedActivities = new Set<PointsActivityId>()
if (!showJumpstart) {
excludedActivities.add('create-live-link')
}

return (
Object.entries(pointsConfig.activitiesById).map(([activityId, metadata]) => ({
...metadata,
activityId,
completed: trackOnceActivities[activityId as PointsActivityId] ?? false,
})) as PointsActivity[]
).filter(({ activityId }) => !excludedActivities.has(activityId))
}
)

Expand Down

0 comments on commit 2a68163

Please sign in to comment.