Skip to content

Commit

Permalink
Merge branch 'main' into satish/act-842-1
Browse files Browse the repository at this point in the history
  • Loading branch information
satish-ravi authored Jun 30, 2023
2 parents 8448aaf + ad5f72f commit f48beb7
Show file tree
Hide file tree
Showing 15 changed files with 437 additions and 28 deletions.
39 changes: 37 additions & 2 deletions src/dappsExplorer/DappFeaturedActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react'
import { Provider } from 'react-redux'
import { DappFeaturedActions } from 'src/dappsExplorer/DappFeaturedActions'
import { createMockStore } from 'test/utils'
import { mockDappList } from 'test/values'
import { mockDappList, mockPositions, mockShortcuts } from 'test/values'

jest.mock('src/analytics/ValoraAnalytics')

Expand All @@ -29,6 +29,10 @@ describe('DappFeaturedActions', () => {
dappsList: mockDappList,
mostPopularDappIds: ['dapp2'],
},
positions: {
positions: mockPositions,
shortcuts: mockShortcuts,
},
})}
>
<DappFeaturedActions onPressShowDappRankings={onPressRankingsSpy} />
Expand All @@ -40,7 +44,7 @@ describe('DappFeaturedActions', () => {
expect(getByText('dappShortcuts.rewards.title')).toBeTruthy()
expect(getByText('dappShortcuts.rewards.description')).toBeTruthy()

fireEvent.press(getAllByTestId('DappFeaturedAction')[0])
fireEvent.press(getAllByTestId('DappFeaturedAction')[1])
expect(onPressRankingsSpy).toHaveBeenCalled()

// TODO add test for press dapp shortcuts card
Expand All @@ -57,6 +61,10 @@ describe('DappFeaturedActions', () => {
dappsList: mockDappList,
mostPopularDappIds: [],
},
positions: {
positions: mockPositions,
shortcuts: mockShortcuts,
},
})}
>
<DappFeaturedActions onPressShowDappRankings={jest.fn()} />
Expand All @@ -67,4 +75,31 @@ describe('DappFeaturedActions', () => {
expect(queryByText('dappRankings.title')).toBeFalsy()
expect(getByText('dappShortcuts.rewards.title')).toBeTruthy()
})

it('should not render dapp rewards shortcut if there are no claimable rewards', () => {
const { queryByText, getByText, getAllByTestId } = render(
<Provider
store={createMockStore({
dapps: {
dappListApiUrl: 'http://url.com',
dappsList: mockDappList,
mostPopularDappIds: ['dapp2'],
},
positions: {
positions: mockPositions.map((position) => ({
...position,
availableShortcutIds: [],
})),
shortcuts: mockShortcuts,
},
})}
>
<DappFeaturedActions onPressShowDappRankings={jest.fn()} />
</Provider>
)

expect(getAllByTestId('DappFeaturedAction')).toHaveLength(1)
expect(queryByText('dappShortcuts.rewards.title')).toBeFalsy()
expect(getByText('dappRankings.title')).toBeTruthy()
})
})
25 changes: 13 additions & 12 deletions src/dappsExplorer/DappFeaturedActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Touchable from 'src/components/Touchable'
import { mostPopularDappsSelector } from 'src/dapps/selectors'
import Trophy from 'src/icons/Trophy'
import Wallet from 'src/icons/Wallet'
import { positionsWithClaimableRewardsSelector } from 'src/positions/selectors'
import { getExperimentParams, getFeatureGate } from 'src/statsig'
import { ExperimentConfigs } from 'src/statsig/constants'
import { StatsigExperiments, StatsigFeatureGates } from 'src/statsig/types'
Expand Down Expand Up @@ -51,8 +52,8 @@ export function DappFeaturedActions({
const showDappRankings = dappRankingsEnabled && mostPopularDapps.length > 0

const dappShortcutsEnabled = getFeatureGate(StatsigFeatureGates.SHOW_CLAIM_SHORTCUTS)
// TODO add check for if there are any shortcuts to show
const showClaimRewards = dappShortcutsEnabled
const positionsWithClaimableRewards = useSelector(positionsWithClaimableRewardsSelector)
const showClaimRewards = dappShortcutsEnabled && positionsWithClaimableRewards.length > 0

// TODO impression analytics on scroll

Expand All @@ -74,22 +75,22 @@ export function DappFeaturedActions({
showsHorizontalScrollIndicator={false}
scrollEnabled={scrollEnabled}
>
{showDappRankings && (
<FeaturedAction
title={t('dappRankings.title')}
description={t('dappRankings.description')}
onPress={onPressShowDappRankings}
Image={<Trophy />}
style={scrollEnabled ? styles.reducedWidthCard : undefined}
/>
)}

{showClaimRewards && (
<FeaturedAction
title={t('dappShortcuts.rewards.title')}
description={t('dappShortcuts.rewards.description')}
Image={<Wallet />}
onPress={handleShowRewardsShortcuts}
style={scrollEnabled ? styles.reducedWidthCard : undefined}
/>
)}

{showDappRankings && (
<FeaturedAction
title={t('dappRankings.title')}
description={t('dappRankings.description')}
onPress={onPressShowDappRankings}
Image={<Trophy />}
style={
scrollEnabled
? [
Expand Down
68 changes: 66 additions & 2 deletions src/positions/saga.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import { FetchMock } from 'jest-fetch-mock/types'
import { expectSaga } from 'redux-saga-test-plan'
import { select } from 'redux-saga/effects'
import { fetchPositionsSaga } from 'src/positions/saga'
import { fetchPositionsSaga, fetchShortcutsSaga } from 'src/positions/saga'
import { shortcutsStatusSelector } from 'src/positions/selectors'
import {
fetchPositionsFailure,
fetchPositionsStart,
fetchPositionsSuccess,
fetchShortcutsFailure,
fetchShortcutsSuccess,
} from 'src/positions/slice'
import { getFeatureGate } from 'src/statsig'
import Logger from 'src/utils/Logger'
import { walletAddressSelector } from 'src/web3/selectors'
import { mockAccount, mockPositions } from 'test/values'
import { mockAccount, mockPositions, mockShortcuts } from 'test/values'
import { mocked } from 'ts-jest/utils'

jest.mock('src/sentry/SentryTransactionHub')
jest.mock('src/statsig')
jest.mock('src/utils/Logger')

const MOCK_RESPONSE = {
message: 'OK',
data: mockPositions,
}

const MOCK_SHORTCUTS_RESPONSE = {
message: 'OK',
data: mockShortcuts,
}

const mockFetch = fetch as FetchMock

beforeEach(() => {
Expand Down Expand Up @@ -72,3 +82,57 @@ describe(fetchPositionsSaga, () => {
.run()
})
})

describe(fetchShortcutsSaga, () => {
it('fetches shortcuts successfully', async () => {
mockFetch.mockResponse(JSON.stringify(MOCK_SHORTCUTS_RESPONSE))
mocked(getFeatureGate).mockReturnValue(true)

await expectSaga(fetchShortcutsSaga)
.provide([[select(shortcutsStatusSelector), 'idle']])
.put(fetchShortcutsSuccess(mockShortcuts))
.run()
})

it('fetches shortcuts if the previous fetch attempt failed', async () => {
mockFetch.mockResponse(JSON.stringify(MOCK_SHORTCUTS_RESPONSE))
mocked(getFeatureGate).mockReturnValue(true)

await expectSaga(fetchShortcutsSaga)
.provide([[select(shortcutsStatusSelector), 'error']])
.put(fetchShortcutsSuccess(mockShortcuts))
.run()
})

it("skips fetching shortcuts if the feature gate isn't enabled", async () => {
mocked(getFeatureGate).mockReturnValue(false)

await expectSaga(fetchShortcutsSaga).run()

expect(mockFetch).not.toHaveBeenCalled()
})

it("skips fetching shortcuts if they've already been fetched", async () => {
mocked(getFeatureGate).mockReturnValue(true)

await expectSaga(fetchShortcutsSaga)
.provide([[select(shortcutsStatusSelector), 'success']])
.run()

expect(mockFetch).not.toHaveBeenCalled()
})

it('updates the shortcuts status there is an error', async () => {
mockFetch.mockResponse(JSON.stringify({ message: 'something went wrong' }), { status: 500 })
mocked(getFeatureGate).mockReturnValue(true)

await expectSaga(fetchShortcutsSaga)
.provide([[select(shortcutsStatusSelector), 'idle']])
.put.actionType(fetchShortcutsFailure.type)
.not.put(fetchShortcutsSuccess(expect.anything()))
.run()

expect(mockFetch).toHaveBeenCalled()
expect(Logger.warn).toHaveBeenCalled()
})
})
33 changes: 32 additions & 1 deletion src/positions/saga.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { call, put, select, spawn, takeLeading } from 'redux-saga/effects'
import { DEFAULT_TESTNET } from 'src/config'
import { shortcutsStatusSelector } from 'src/positions/selectors'
import {
fetchPositionsFailure,
fetchPositionsStart,
fetchPositionsSuccess,
fetchShortcutsFailure,
fetchShortcutsSuccess,
} from 'src/positions/slice'
import { Position } from 'src/positions/types'
import { Position, Shortcut } from 'src/positions/types'
import { SentryTransactionHub } from 'src/sentry/SentryTransactionHub'
import { SentryTransaction } from 'src/sentry/SentryTransactions'
import { getFeatureGate } from 'src/statsig'
Expand Down Expand Up @@ -38,6 +41,33 @@ async function fetchPositions(walletAddress: string) {
return json.data as Position[]
}

export function* fetchShortcutsSaga() {
try {
if (!getFeatureGate(StatsigFeatureGates.SHOW_CLAIM_SHORTCUTS)) {
return
}

const shortcutsStatus = yield select(shortcutsStatusSelector)
if (shortcutsStatus === 'success') {
// no need to fetch shortcuts more than once per session
return
}

const response = yield call(fetchWithTimeout, networkConfig.getShortcutsUrl)
if (!response.ok) {
throw new Error(`Unable to fetch shortcuts: ${response.status} ${response.statusText}`)
}

const result: {
data: Shortcut[]
} = yield call([response, 'json'])
yield put(fetchShortcutsSuccess(result.data))
} catch (error) {
Logger.warn(TAG, 'Unable to fetch shortcuts', error)
yield put(fetchShortcutsFailure(error))
}
}

export function* fetchPositionsSaga() {
try {
const address: string | null = yield select(walletAddressSelector)
Expand All @@ -63,6 +93,7 @@ export function* fetchPositionsSaga() {
export function* watchFetchBalances() {
// Refresh positions when fetching token balances
yield takeLeading(fetchTokenBalances.type, safely(fetchPositionsSaga))
yield takeLeading(fetchTokenBalances.type, safely(fetchShortcutsSaga))
}

export function* positionsSaga() {
Expand Down
55 changes: 54 additions & 1 deletion src/positions/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import BigNumber from 'bignumber.js'
import { getPositionBalanceUsd } from 'src/positions/getPositionBalanceUsd'
import {
positionsByBalanceUsdSelector,
positionsWithClaimableRewardsSelector,
totalPositionsBalanceUsdSelector,
} from 'src/positions/selectors'
import { getFeatureGate } from 'src/statsig'
import { mockPositions } from 'test/values'
import { mockPositions, mockShortcuts } from 'test/values'
import { mocked } from 'ts-jest/utils'

jest.mock('src/statsig')
Expand Down Expand Up @@ -89,3 +90,55 @@ describe('positionsByBalanceUsdSelector', () => {
`)
})
})

describe('positionsWithClaimableRewardsSelector', () => {
it('should return positions with claimable rewards', () => {
const state: any = {
positions: {
positions: mockPositions,
shortcuts: mockShortcuts,
},
}
const positions = positionsWithClaimableRewardsSelector(state)
expect(
positions.map((position) => {
return position.claimableShortcut
})
).toMatchInlineSnapshot(`
Array [
Object {
"appId": "ubeswap",
"category": "claim",
"claimableTokens": Array [
Object {
"address": "0x00be915b9dcf56a3cbe739d9b9c202ca692409ec",
"balance": "0.098322815093446616",
"category": "claimable",
"decimals": 18,
"network": "celo",
"priceUsd": "0.00904673476946796903",
"symbol": "UBE",
"type": "base-token",
},
Object {
"address": "0x471ece3750da237f93b8e339c536989b8978a438",
"balance": "0.950545800159603456",
"category": "claimable",
"decimals": 18,
"network": "celo",
"priceUsd": "0.6959536890241361",
"symbol": "CELO",
"type": "base-token",
},
],
"description": "Claim rewards for staked liquidity",
"id": "claim-reward",
"name": "Claim",
"networks": Array [
"celo",
],
},
]
`)
})
})
Loading

0 comments on commit f48beb7

Please sign in to comment.