-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(TokenEnterAmount): Add new Enter Amount component and useEnterAmount hook #6242
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6242 +/- ##
==========================================
+ Coverage 88.94% 88.97% +0.02%
==========================================
Files 738 739 +1
Lines 31498 31643 +145
Branches 5838 5890 +52
==========================================
+ Hits 28016 28153 +137
- Misses 3282 3290 +8
Partials 200 200
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
src/components/TokenEnterAmount.tsx
Outdated
const formattedInputValue = useMemo(() => { | ||
const number = groupNumber(inputValue) | ||
.replaceAll('.', decimalSeparator) | ||
.replaceAll('group', groupingSeparator) | ||
if (amountType === 'token') return number | ||
return number !== '' ? `${localCurrencySymbol}${number}` : '' | ||
}, [inputValue, amountType, localCurrencySymbol]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured its easier to add currency sign before whatever is typed in the amount field rather than trying to parse it while having that sign and manage all the use-cases when it whether includes the sign (when typing in fiat) or not (when typing in crypto).
} | ||
} | ||
|
||
return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kudos to @kathaypacific for implementing 95% of this component 🚀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💥 looking great!! i had just a few small questions and suggestions for tests :)
inputValue: '1', | ||
tokenAmount: '1', | ||
localAmount: '$0.1', | ||
amountType: 'token' as AmountEnteredIn, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amountType: 'token' as AmountEnteredIn, | |
amountType: 'token' as const, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific considering this is more for the intellisense purpose I would keep it as is!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not sure i understand - explicit casting like this is generally dangerous because 'blah' as AmountEnteredIn
will not produce any errors whereas 'blah' as const
will
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific but calling intellisense on the empty string will provide you available options of token
and local
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i see, so we are prioritising the experience of writing this value. my concern comes down to as SomeType
is a way to ignore all the static type checking functionality that Typescript gives us, so I try to avoid it. if the allowed values of AmountEnteredIn
changes then this test will not be flagged, but i guess at runtime the tests will fail (slower time to uncover the problem). we've been quite inconsistent with casting using the type or const
though so it's also fine by me to keep it like this
toggleAmountType: mockToggleAmountType, | ||
onOpenTokenPicker: mockOnOpenTokenPicker, | ||
editable: true, | ||
testID: 'tokenEnterAmount', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: i think the pattern is to use TitleCase for test id's
testID: 'tokenEnterAmount', | |
testID: 'TokenEnterAmount', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific Sorry, this was the part generated by Chat GPT so I just kept it unchanged 🤦
Will change!
testID: 'tokenEnterAmount', | ||
} | ||
|
||
it('renders without crashing', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think that this test is unnecessary, the scenario is already covered by the following tests (since the input to the render
method is the same, and asserting on elements already ensures that the component has successfully rendered without errors)
<TokenEnterAmount {...defaultProps} /> | ||
</Provider> | ||
) | ||
expect(getByTestId('tokenEnterAmount/TokenName')).toBeTruthy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(getByTestId('tokenEnterAmount/TokenName')).toBeTruthy() | |
expect(getByTestId('tokenEnterAmount/TokenName')).toHaveTextContent('CELO on Celo Alfajores') |
expect(getByTestId('tokenEnterAmount/TokenSelect')).toBeTruthy() | ||
expect(getByTestId('tokenEnterAmount/TokenBalance')).toBeTruthy() | ||
expect(getByTestId('tokenEnterAmount/TokenBalance').props.children.props.i18nKey).toBe( | ||
'tokenEnterAmount.availableBalance' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it's possible we should avoid digging into props since then we test implementation details rather than user outcomes...could instead consider doing something like
expect(getByTestId('tokenEnterAmount/TokenBalance')).toHaveTextContent(
'tokenEnterAmount.availableBalance'
)
expect(getByTestId('tokenEnterAmount/TokenBalance')).toHaveTextContent('5.00 CELO')
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific This makes sense! I've tried initially with getByText
but for some reason for this particular test it couldn't find an element with that text (probably, cause it was including multiple nested Text
tags so it wasn't able to match it). But your suggestion might work! Will try it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works!
const input = getByTestId('tokenEnterAmount/TokenAmountInput') | ||
const converted = getByTestId('tokenEnterAmount/ExchangeAmount') | ||
expect(input.props.value).toBe('1') | ||
expect(converted.props.children).toBe(`${APPROX_SYMBOL} $0.1`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(converted.props.children).toBe(`${APPROX_SYMBOL} $0.1`) | |
expect(converted).toHaveTextContent(`${APPROX_SYMBOL} $0.1`) |
) | ||
const input = getByTestId('tokenEnterAmount/TokenAmountInput') | ||
|
||
expect(input.props.editable).toBe(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(input.props.editable).toBe(false) | |
expect(input).toBeDisabled() |
</Provider> | ||
) | ||
const exchangeAmount = getByTestId('tokenEnterAmount/ExchangeAmount') | ||
expect(exchangeAmount.props.children).toBe(`${APPROX_SYMBOL} $0.1`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(exchangeAmount.props.children).toBe(`${APPROX_SYMBOL} $0.1`) | |
expect(exchangeAmount).toHaveTextContent(`${APPROX_SYMBOL} $0.1`) |
src/components/TokenEnterAmount.tsx
Outdated
const BORDER_RADIUS = 12 | ||
|
||
function groupNumber(value: string) { | ||
return value.replace(/\B(?=(\d{3})+(?!\d))(?<!\.\d*)/g, 'group') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we please add a comment about what this regex does? 🙈 i would definitely benefit from this, i don't speak regex fluently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific I've refactored this function a bit in the next PR and added comments explaining how it works!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, i wish i hadn't spent time trying to understand this implementation then....😅 going forward it'd be helpful to consider not including changes that will be undone in subsequent PRs or adding a note about it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific I'm sorry, definitely my bad! I should've put this component and the new useEnterAmount
hook into a single PR. Sorry!
src/components/TokenEnterAmount.tsx
Outdated
const formattedInputValue = useMemo(() => { | ||
const number = groupNumber(inputValue) | ||
.replaceAll('.', decimalSeparator) | ||
.replaceAll('group', groupingSeparator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we add a couple of tests around the expected outcome of this formatting function? i think i may be missing something, but i'm surprised that we can freely use '.'
here - if the inputValue
is 1.234.567,888
, would this function still work? (unsure if this is a real scenario and if consumers of this component will pass in formatted values, but this formatting convention is real)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kathaypacific you're right! It was a bug that I've revealed once I was working on a useEnterAmount
hook in the next PR. I've refactored this part as well so it should be resolved there!
In order to not keep these issues in the codebase - I would wait for the next PR to also get approved and will merge them both in a batch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨🚀
expect(getByText('CELO on Celo Alfajores')).toBeTruthy() | ||
expect(getByTestId('TokenEnterAmount/SwitchTokens')).toBeTruthy() | ||
expect(getByTestId('TokenEnterAmount/TokenSelect')).toBeTruthy() | ||
expect(getByTestId('TokenEnterAmount/TokenBalance')).toBeTruthy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you can remove this line, since we are asserting on it toHaveTextContent
next. there might be other places where you can omit the toBeTruthy()
check too
### Description 2/5 PR for new Enter Amount component. This PR introduces `useEnterAmount` hook that uses the very same logic `EnterAmount.tsx` and `EarnEnterAmount.tsx` uses for handling: - text input management (regex check, onChange, setting the right value for the right type etc.) - whenever data changes - creates a derived state with all the necessary data for any processing currently present (can be extended in the future) This change is supposed to reduce a lot of boilerplate once it is used in the corresponding flows (please, check the follow-up PRs). ### Test plan No tests for now. Tests will be present starting the next (3/5) PR which is related to applying this hook and new component to the Send flow. ### Related issues - Relates to RET-1208 - ### 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)
Description
1/5 PR for new Enter Amount component. This PR implements a new component but doesn't use it anywhere. The tests are absent as this component will be covered with its usages in the follow-up PRs.
This is only done to reduce amount of lines to review per PR as at the end this whole feature is about 1k new lines and -1k of removed lines.
Test plan
Omit tests for now but in the follow-up PRs the following test files will be testing this component:
EnterAmount.test.tsx
EarnEnterAmount.test.tsx
SwapScreen.test.tsx
There will also be some test files that are gonna be fixed alongside. Please, see the follow-up PRs for more details.
Related issues
Backwards compatibility
Yes
Network scalability
If a new NetworkId and/or Network are added in the future, the changes in this PR will: