Skip to content

Commit

Permalink
add a validation function to handle seconds parameter in ExtendNames
Browse files Browse the repository at this point in the history
  • Loading branch information
storywithoutend committed Dec 3, 2024
1 parent 6086c21 commit d8d2ee1
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 13 deletions.
3 changes: 2 additions & 1 deletion src/components/pages/profile/[name]/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) =>

const abilities = useAbilities({ name: normalisedName })

useRenew(normalisedName)
// hook for redirecting to the correct profile url
// profile.decryptedName fetches labels from NW/subgraph
// normalisedName fetches labels from localStorage
Expand All @@ -203,6 +202,8 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) =>
// }
// }, [name, router, transactions])

useRenew(normalisedName)

const infoBanner = useMemo(() => {
if (
registrationStatus !== 'gracePeriod' &&
Expand Down
170 changes: 170 additions & 0 deletions src/hooks/pages/profile/useRenew/useRenew.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { it, expect, describe} from "vitest";

import { calculateRenewState } from "./useRenew";

describe('calculateRenewState', () => {
it('should return connect-user if accountStatus is disconnected', () => {
expect(calculateRenewState({
registrationStatus: 'gracePeriod',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'disconnected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('connect-user')
})

it('should return connect-user if accountStatus is connected', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('display-extend-names')
})

it('should return idle if registration status is available', () => {
expect(calculateRenewState({
registrationStatus: 'available',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if registration status is loading', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: true,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if renewSeconds is null', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: null,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if connectModalOpen is true', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: true,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if abilities is loading', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: true,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if isRouterReady is false', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: false,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if name is empty', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: '',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if openedConnectModal is true', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: true,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if openConnectModal is undefined and accountStatus is disconnected', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'disconnected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: undefined
})).toBe('idle')
})
})
15 changes: 5 additions & 10 deletions src/hooks/pages/profile/useRenew/useRenew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { useAccount } from 'wagmi'
import { useAbilities } from '@app/hooks/abilities/useAbilities'
import { useBasicName } from '@app/hooks/useBasicName'
import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory'
import { validateExtendNamesDuration } from '@app/transaction-flow/input/ExtendNames/utils/validateExtendNamesDuration'
import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider'
import { RegistrationStatus } from '@app/utils/registrationStatus'
import { parseNumericString } from '@app/utils/string'

type RenewStatus = 'connect-user' | 'display-extend-names' | 'idle'

Expand Down Expand Up @@ -44,15 +44,10 @@ export const calculateRenewState = ({
!!name &&
isNameRegisteredOrGracePeriod &&
!!renewSeconds &&
!connectModalOpen

if (
isRenewActive &&
accountStatus === 'disconnected' &&
!!openConnectModal &&
!connectModalOpen &&
!openedConnectModal
)
return 'connect-user'

if (isRenewActive && accountStatus === 'disconnected' && !!openConnectModal) return 'connect-user'
if (isRenewActive && accountStatus === 'connected' && !isAbilitiesLoading)
return 'display-extend-names'
return 'idle'
Expand Down Expand Up @@ -86,7 +81,7 @@ export function useRenew(name: string) {

const { data: { canSelfExtend } = {}, isLoading: isAbilitiesLoading } = abilities

const renewSeconds = parseNumericString(searchParams.get('renew'))
const renewSeconds = validateExtendNamesDuration({ duration: searchParams.get('renew') })

const renewState = calculateRenewState({
registrationStatus,
Expand Down
7 changes: 5 additions & 2 deletions src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { deriveYearlyFee, formatDurationOfDates } from '@app/utils/utils'
import { ShortExpiry } from '../../../components/@atoms/ExpiryComponents/ExpiryComponents'
import GasDisplay from '../../../components/@atoms/GasDisplay'
import { SearchViewLoadingView } from '../SendName/views/SearchView/views/SearchViewLoadingView'
import { validateExtendNamesDuration } from './utils/validateExtendNamesDuration'

type View = 'name-list' | 'no-ownership-warning' | 'registration'

Expand Down Expand Up @@ -172,13 +173,15 @@ export type Props = {
const minSeconds = ONE_DAY

const ExtendNames = ({
data: { seconds: defaultSeconds = ONE_YEAR, names, isSelf },
data: { seconds: defaultSeconds, names, isSelf },
dispatch,
onDismiss,
}: Props) => {
const { t } = useTranslation(['transactionFlow', 'common'])

const [seconds, setSeconds] = useState(defaultSeconds)
const [seconds, setSeconds] = useState(
validateExtendNamesDuration({ duration: defaultSeconds ?? ONE_YEAR })!,
)
const years = secondsToYears(seconds)
const [durationType, setDurationType] = useState<'years' | 'date'>('years')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { it, describe, expect } from "vitest";
import { validateExtendNamesDuration } from "./validateExtendNamesDuration";
import { ONE_DAY, ONE_YEAR } from "@app/utils/time";

describe('validateExtendNamesDuration', () => {
it('should return an integer', () => {
expect(validateExtendNamesDuration({ duration: '90000'})).toBe(90000)
})

it('should return an integer for a decimal', () => {
expect(validateExtendNamesDuration({duration: '90000.123'})).toBe(90000)
})

it('should return minimum duration for a number less than the minimum', () => {
expect(validateExtendNamesDuration({duration: '0'})).toBe(ONE_DAY)
})

it('should return null duration for null', () => {
expect(validateExtendNamesDuration({duration: null})).toBe(null)
})

it('should return default duration for undefined', () => {
expect(validateExtendNamesDuration({duration: undefined})).toBe(ONE_YEAR)
})

it('should return default for a string', () => {
expect(validateExtendNamesDuration({duration: 'abc'})).toBe(ONE_YEAR)
})

it('should return default for an empty string', () => {
expect(validateExtendNamesDuration({ duration: ''})).toBe(ONE_YEAR)
})

it('should return default for a negative number', () => {
expect(validateExtendNamesDuration({duration: '-123'})).toBe(ONE_YEAR)
})

it('should return default for an object ', () => {
expect(validateExtendNamesDuration({ duration: {}})).toBe(ONE_YEAR)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ONE_DAY, ONE_YEAR } from '@app/utils/time'

export const validateExtendNamesDuration = ({
duration,
minDuration = ONE_DAY,
defaultDuration = ONE_YEAR,
}: {
duration?: unknown
minDuration?: number
defaultDuration?: number
}): number | null => {
if (duration === null) return null
const parsedDuration = parseInt(duration as string, 10)
if (Number.isNaN(parsedDuration) || parsedDuration < 0) return defaultDuration
if (parsedDuration < minDuration) return minDuration
return parsedDuration
}

0 comments on commit d8d2ee1

Please sign in to comment.