Skip to content

Commit

Permalink
feat(earn): show risks in safety card on pool info screen (#6163)
Browse files Browse the repository at this point in the history
### Description

Implement expandable container to show / hide risks in safety card

### Test plan



https://github.com/user-attachments/assets/97c55657-9980-4150-83a2-ab1ff2b4a334



### Related issues

- Part of ACT-1405

### Backwards compatibility

Yes

### Network scalability

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

- [x] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
satish-ravi authored Oct 17, 2024
1 parent 422ce06 commit eeafff3
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 57 deletions.
3 changes: 2 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2723,7 +2723,8 @@
"dailyYieldRateDescription": "The daily rate displayed reflects the daily rate provided by {{providerName}}.",
"dailyYieldRateLink": "View More Daily Rate Details On {{providerName}}"
},
"viewMoreDetails": "View More Details"
"viewMoreDetails": "View More Details",
"viewLessDetails": "View Less Details"
},
"beforeDepositBottomSheet": {
"youNeedTitle": "You Need {{tokenSymbol}} on {{tokenNetwork}} to Deposit",
Expand Down
1 change: 1 addition & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -682,4 +682,5 @@ export enum EarnEvents {
earn_pool_info_tap_info_icon = 'earn_pool_info_tap_info_icon',
earn_pool_info_tap_withdraw = 'earn_pool_info_tap_withdraw',
earn_pool_info_tap_deposit = 'earn_pool_info_tap_deposit',
earn_pool_info_tap_safety_details = 'earn_pool_info_tap_safety_details',
}
3 changes: 3 additions & 0 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,9 @@ interface EarnEventsProperties {
hasTokensOnSameNetwork: boolean
hasTokensOnOtherNetworks: boolean
}
[EarnEvents.earn_pool_info_tap_safety_details]: EarnCommonProperties & {
action: 'expand' | 'collapse'
}
}

export type AnalyticsPropertiesList = AppEventsProperties &
Expand Down
1 change: 1 addition & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[EarnEvents.earn_pool_info_tap_info_icon]: `When the user taps an info icon on the earn pool info screen`,
[EarnEvents.earn_pool_info_tap_withdraw]: `When the user taps the withdraw button on the pool info screen`,
[EarnEvents.earn_pool_info_tap_deposit]: `When the user taps the deposit button on the pool info screen`,
[EarnEvents.earn_pool_info_tap_safety_details]: `When the user taps the view more/less details on the safety card on the pool info screen`,

// Legacy event docs
// The below events had docs, but are no longer produced by the latest app version.
Expand Down
65 changes: 21 additions & 44 deletions src/earn/EarnPoolInfoScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { EarnEvents } from 'src/analytics/Events'
import { EarnCommonProperties } from 'src/analytics/Properties'
import { openUrl } from 'src/app/actions'
import BottomSheet, { BottomSheetModalRefType } from 'src/components/BottomSheet'
import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
Expand Down Expand Up @@ -37,7 +38,6 @@ import variables from 'src/styles/variables'
import { useTokenInfo, useTokensInfo } from 'src/tokens/hooks'
import { tokensByIdSelector } from 'src/tokens/selectors'
import { TokenBalance } from 'src/tokens/slice'
import { NetworkId } from 'src/transactions/types'
import { navigateToURI } from 'src/utils/linking'
import { formattedDuration } from 'src/utils/time'

Expand Down Expand Up @@ -411,30 +411,19 @@ function AgeCard({ ageOfPool, onInfoIconPress }: { ageOfPool: Date; onInfoIconPr
function LearnMoreTouchable({
url,
providerName,
appId,
positionId,
networkId,
depositTokenId,
commonAnalyticsProps,
}: {
url: string
providerName: string
appId: string
positionId: string
networkId: NetworkId
depositTokenId: string
commonAnalyticsProps: EarnCommonProperties
}) {
const { t } = useTranslation()
return (
<View style={styles.learnMoreContainer}>
<Touchable
borderRadius={8}
onPress={() => {
AppAnalytics.track(EarnEvents.earn_pool_info_view_pool, {
providerId: appId,
poolId: positionId,
networkId,
depositTokenId,
})
AppAnalytics.track(EarnEvents.earn_pool_info_view_pool, commonAnalyticsProps)
navigateToURI(url)
}}
>
Expand Down Expand Up @@ -516,6 +505,13 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
throw new Error(`Token ${dataProps.depositTokenId} not found`)
}

const commonAnalyticsProps: EarnCommonProperties = {
providerId: appId,
poolId: positionId,
networkId,
depositTokenId: dataProps.depositTokenId,
}

const {
hasDepositToken,
hasTokensOnSameNetwork,
Expand All @@ -526,10 +522,7 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {

const onPressDeposit = () => {
AppAnalytics.track(EarnEvents.earn_pool_info_tap_deposit, {
poolId: positionId,
providerId: appId,
networkId: networkId,
depositTokenId: dataProps.depositTokenId,
...commonAnalyticsProps,
hasDepositToken,
hasTokensOnSameNetwork,
hasTokensOnOtherNetworks,
Expand Down Expand Up @@ -582,11 +575,8 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
earnPosition={pool}
onInfoIconPress={() => {
AppAnalytics.track(EarnEvents.earn_pool_info_tap_info_icon, {
providerId: appId,
poolId: positionId,
type: 'deposit',
networkId,
depositTokenId: dataProps.depositTokenId,
...commonAnalyticsProps,
})
depositInfoBottomSheetRef.current?.snapToIndex(0)
}}
Expand All @@ -595,11 +585,8 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
<YieldCard
onInfoIconPress={() => {
AppAnalytics.track(EarnEvents.earn_pool_info_tap_info_icon, {
providerId: appId,
poolId: positionId,
type: 'yieldRate',
networkId,
depositTokenId: dataProps.depositTokenId,
...commonAnalyticsProps,
})
yieldRateInfoBottomSheetRef.current?.snapToIndex(0)
}}
Expand All @@ -611,26 +598,22 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
dailyYieldRate={dataProps.dailyYieldRatePercentage}
onInfoIconPress={() => {
AppAnalytics.track(EarnEvents.earn_pool_info_tap_info_icon, {
providerId: appId,
poolId: positionId,
type: 'dailyYieldRate',
networkId,
depositTokenId: dataProps.depositTokenId,
...commonAnalyticsProps,
})
dailyYieldRateInfoBottomSheetRef.current?.snapToIndex(0)
}}
/>
)}
{!!dataProps.safety && <SafetyCard safety={dataProps.safety} />}
{!!dataProps.safety && (
<SafetyCard safety={dataProps.safety} commonAnalyticsProps={commonAnalyticsProps} />
)}
<TvlCard
earnPosition={pool}
onInfoIconPress={() => {
AppAnalytics.track(EarnEvents.earn_pool_info_tap_info_icon, {
providerId: appId,
poolId: positionId,
type: 'tvl',
networkId,
depositTokenId: dataProps.depositTokenId,
...commonAnalyticsProps,
})
tvlInfoBottomSheetRef.current?.snapToIndex(0)
}}
Expand All @@ -640,11 +623,8 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
ageOfPool={new Date(dataProps.contractCreatedAt)}
onInfoIconPress={() => {
AppAnalytics.track(EarnEvents.earn_pool_info_tap_info_icon, {
providerId: appId,
poolId: positionId,
type: 'age',
networkId,
depositTokenId: dataProps.depositTokenId,
...commonAnalyticsProps,
})
ageInfoBottomSheetRef.current?.snapToIndex(0)
}}
Expand All @@ -654,10 +634,7 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
<LearnMoreTouchable
url={dataProps.manageUrl}
providerName={appName}
appId={appId}
positionId={positionId}
networkId={networkId}
depositTokenId={dataProps.depositTokenId}
commonAnalyticsProps={commonAnalyticsProps}
/>
) : null}
</View>
Expand Down
87 changes: 80 additions & 7 deletions src/earn/SafetyCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,104 @@
import { render } from '@testing-library/react-native'
import { fireEvent, render } from '@testing-library/react-native'
import React from 'react'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { EarnEvents } from 'src/analytics/Events'
import { SafetyCard } from 'src/earn/SafetyCard'
import Colors from 'src/styles/colors'
import { NetworkId } from 'src/transactions/types'

const mockAnalyticsProps = {
poolId: 'poolId',
providerId: 'providerId',
networkId: NetworkId['arbitrum-sepolia'],
depositTokenId: 'depositTokenId',
}

describe('SafetyCard', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('renders correctly', () => {
const { getByTestId, getAllByTestId } = render(
<SafetyCard safety={{ level: 'low', risks: [] }} />
<SafetyCard safety={{ level: 'low', risks: [] }} commonAnalyticsProps={mockAnalyticsProps} />
)

expect(getByTestId('SafetyCard')).toBeDefined()
expect(getByTestId('SafetyCardInfoIcon')).toBeDefined()
expect(getByTestId('SafetyCard')).toBeTruthy()
expect(getByTestId('SafetyCardInfoIcon')).toBeTruthy()
expect(getAllByTestId('SafetyCard/Bar')).toHaveLength(3)
expect(getByTestId('SafetyCard/ViewDetails')).toBeDefined()
expect(getByTestId('SafetyCard/ViewDetails')).toBeTruthy()
expect(getByTestId('SafetyCard/ViewDetails')).toHaveTextContent(
'earnFlow.poolInfoScreen.viewMoreDetails'
)
})

it.each([
{ level: 'low', colors: [Colors.primary, Colors.gray2, Colors.gray2] },
{ level: 'medium', colors: [Colors.primary, Colors.primary, Colors.gray2] },
{ level: 'high', colors: [Colors.primary, Colors.primary, Colors.primary] },
] as const)('should render correct triple bars for safety level $level', ({ level, colors }) => {
const { getAllByTestId } = render(<SafetyCard safety={{ level, risks: [] }} />)
const { getAllByTestId } = render(
<SafetyCard safety={{ level, risks: [] }} commonAnalyticsProps={mockAnalyticsProps} />
)

const bars = getAllByTestId('SafetyCard/Bar')
expect(bars.length).toBe(3)
expect(bars).toHaveLength(3)
expect(bars[0]).toHaveStyle({ backgroundColor: colors[0] })
expect(bars[1]).toHaveStyle({ backgroundColor: colors[1] })
expect(bars[2]).toHaveStyle({ backgroundColor: colors[2] })
})

it('expands and collapses card and displays risks when View More/Less Details is pressed', () => {
const { getByTestId, getAllByTestId, queryByTestId } = render(
<SafetyCard
safety={{
level: 'low',
risks: [
{ title: 'Risk 1', category: 'Category 1', isPositive: true },
{ title: 'Risk 2', category: 'Category 2', isPositive: false },
],
}}
commonAnalyticsProps={mockAnalyticsProps}
/>
)

expect(queryByTestId('SafetyCard/Risk')).toBeFalsy()
expect(getByTestId('SafetyCard/ViewDetails')).toHaveTextContent(
'earnFlow.poolInfoScreen.viewMoreDetails'
)

// expand
fireEvent.press(getByTestId('SafetyCard/ViewDetails'))
expect(getAllByTestId('SafetyCard/Risk')).toHaveLength(2)
expect(getByTestId('SafetyCard/ViewDetails')).toHaveTextContent(
'earnFlow.poolInfoScreen.viewLessDetails'
)
expect(getAllByTestId('SafetyCard/Risk')[0].children[0]).toContainElement(
getByTestId('SafetyCard/RiskPositive')
)
expect(getAllByTestId('SafetyCard/Risk')[0]).toHaveTextContent('Risk 1')
expect(getAllByTestId('SafetyCard/Risk')[0]).toHaveTextContent('Category 1')
expect(getAllByTestId('SafetyCard/Risk')[1].children[0]).toContainElement(
getByTestId('SafetyCard/RiskNegative')
)
expect(getAllByTestId('SafetyCard/Risk')[1]).toHaveTextContent('Risk 2')
expect(getAllByTestId('SafetyCard/Risk')[1]).toHaveTextContent('Category 2')
expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_safety_details, {
action: 'expand',
...mockAnalyticsProps,
})
expect(AppAnalytics.track).toHaveBeenCalledTimes(1)

// collapse
fireEvent.press(getByTestId('SafetyCard/ViewDetails'))
expect(queryByTestId('SafetyCard/Risk')).toBeFalsy()
expect(getByTestId('SafetyCard/ViewDetails')).toHaveTextContent(
'earnFlow.poolInfoScreen.viewMoreDetails'
)
expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_safety_details, {
action: 'collapse',
...mockAnalyticsProps,
})
expect(AppAnalytics.track).toHaveBeenCalledTimes(2)
})
})
Loading

0 comments on commit eeafff3

Please sign in to comment.