Skip to content
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: Send flow sub components #1906

Merged
merged 31 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3924850
hardcode autocomplete values
brendan-defi Jan 31, 2025
f0e2626
textClassName override
brendan-defi Jan 31, 2025
11f8904
actionable token balance component
brendan-defi Jan 31, 2025
4309e09
trim and hide long token names
brendan-defi Jan 31, 2025
0717b1c
export token balance
brendan-defi Jan 31, 2025
d1b36cd
send components
brendan-defi Jan 31, 2025
f829c34
send utils
brendan-defi Jan 31, 2025
35cf20a
simplify address resolution
brendan-defi Jan 31, 2025
321f471
fix lints
brendan-defi Jan 31, 2025
37ddab5
separate components, add tests
brendan-defi Jan 31, 2025
e0f30ce
add test
brendan-defi Jan 31, 2025
eeaedeb
add tests
brendan-defi Jan 31, 2025
367f325
fix typos
brendan-defi Jan 31, 2025
8d3c4e3
add tests
brendan-defi Jan 31, 2025
1d6b923
add tests
brendan-defi Jan 31, 2025
db5deaf
add tests
brendan-defi Jan 31, 2025
34d0a1d
fix lints
brendan-defi Jan 31, 2025
037d9a6
hanlde optional subtitle
brendan-defi Jan 31, 2025
4bf85a0
add tests
brendan-defi Jan 31, 2025
46b70b8
fix typos
brendan-defi Jan 31, 2025
ae4a93b
add loadingDisplay override to AmountInputTypeSwitch
brendan-defi Jan 31, 2025
fe818ab
refactor TokenBaance for readability
brendan-defi Jan 31, 2025
a27d194
add classname override and remove unnecessary object
brendan-defi Jan 31, 2025
e20e4f1
classNames object
brendan-defi Jan 31, 2025
79c64d4
classNames object
brendan-defi Jan 31, 2025
86b19c1
add classNames overrides, tests
brendan-defi Jan 31, 2025
da1a65b
update tests
brendan-defi Jan 31, 2025
30decf8
fix lints
brendan-defi Jan 31, 2025
1aa46ca
add comments
brendan-defi Feb 3, 2025
5170682
fix lints
brendan-defi Feb 3, 2025
7cfb954
remove extraneous div
brendan-defi Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions src/internal/components/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ type TextInputReact = {
setValue?: (s: string) => void;
value: string;
inputValidator?: (s: string) => boolean;
/** autocomplete attribute handles browser autocomplete, defaults to 'off' */
autoComplete?: string;
/** data-1p-ignore attribute handles password manager autocomplete, defaults to true */
'data-1p-ignore'?: boolean;
};

export const TextInput = forwardRef<HTMLInputElement, TextInputReact>(
Expand All @@ -41,8 +37,6 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputReact>(
inputMode,
value,
inputValidator = () => true,
autoComplete = 'off',
'data-1p-ignore': data1pIgnore = true,
},
ref,
) => {
Expand Down Expand Up @@ -80,8 +74,8 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputReact>(
onChange={handleChange}
onFocus={onFocus}
disabled={disabled}
autoComplete={autoComplete}
data-1p-ignore={data1pIgnore}
autoComplete="off" // autocomplete attribute handles browser autocomplete
data-1p-ignore={true} // data-1p-ignore attribute handles password manager autocomplete
/>
);
},
Expand Down
12 changes: 10 additions & 2 deletions src/internal/components/amount-input/AmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type AmountInputProps = {
setCryptoAmount: (value: string) => void;
exchangeRate: string;
className?: string;
textClassName?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use an object structure here with classNames like dan suggested in quality TDD

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will that be a breaking change for FundCard though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah good point i guess we can revisit here and i saw you updated to classNames in other component 👍

};

export function AmountInput({
Expand All @@ -24,10 +25,11 @@ export function AmountInput({
asset,
selectedInputType,
currency,
className,
setFiatAmount,
setCryptoAmount,
exchangeRate,
className,
textClassName,
}: AmountInputProps) {
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -105,6 +107,7 @@ export function AmountInput({
'[appearance:textfield]',
'[&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none',
'[&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none',
textClassName,
)}
value={value}
onChange={handleAmountChange}
Expand All @@ -114,7 +117,11 @@ export function AmountInput({
placeholder="0"
/>
<div className="ml-1">
<CurrencyLabel ref={labelRef} label={currencyOrAsset} />
<CurrencyLabel
ref={labelRef}
label={currencyOrAsset}
className={textClassName}
/>
</div>
</div>
</div>
Expand All @@ -123,6 +130,7 @@ export function AmountInput({
{/* Hidden span for measuring text width
Without this span the input field would not adjust its width based on the text width and would look like this:
[0.12--------Empty Space-------][ETH] - As you can see the currency symbol is far away from the inputed value

With this span we can measure the width of the text in the input field and set the width of the input field to match the text width
[0.12][ETH] - Now the currency symbol is displayed next to the input field
*/}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type AmountInputTypeSwitchPropsReact = {
cryptoAmount: string;
exchangeRate: number;
exchangeRateLoading: boolean;
loadingDisplay?: React.ReactNode;
currency: string;
className?: string;
};
Expand All @@ -26,6 +27,7 @@ export function AmountInputTypeSwitch({
exchangeRate,
exchangeRateLoading,
currency,
loadingDisplay = <Skeleton className="h-[1.625rem]" />,
className,
}: AmountInputTypeSwitchPropsReact) {
const iconSvg = useIcon({ icon: 'toggle' });
Expand Down Expand Up @@ -56,7 +58,7 @@ export function AmountInputTypeSwitch({
}, [cryptoAmount, fiatAmount, selectedInputType, formatCrypto, currency]);

if (exchangeRateLoading || !exchangeRate) {
return <Skeleton className="h-[1.625rem]" />;
return loadingDisplay;
}

return (
Expand Down
4 changes: 3 additions & 1 deletion src/internal/components/amount-input/CurrencyLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { forwardRef } from 'react';

type CurrencyLabelProps = {
label: string;
className?: string;
};

export const CurrencyLabel = forwardRef<HTMLSpanElement, CurrencyLabelProps>(
({ label }, ref) => {
({ label, className }, ref) => {
return (
<span
ref={ref}
Expand All @@ -15,6 +16,7 @@ export const CurrencyLabel = forwardRef<HTMLSpanElement, CurrencyLabelProps>(
color.disabled,
'flex items-center justify-center bg-transparent',
'text-6xl leading-none outline-none',
className,
)}
data-testid="ockCurrencySpan"
>
Expand Down
188 changes: 188 additions & 0 deletions src/token/components/TokenBalance.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { TokenBalance } from './TokenBalance';

const mockToken = {
symbol: 'ETH',
address: '' as const,
chainId: 8453,
decimals: 18,
image: null,
name: 'Ethereum',
cryptoBalance: 1,
fiatBalance: 3300,
};

describe('TokenBalance', () => {
describe('Main TokenBalance component', () => {
it('renders as div when no onClick provided', () => {
render(<TokenBalance token={mockToken} />);
expect(screen.queryByRole('button')).toBeNull();
});

it('renders as button with onClick handler', () => {
const onClick = vi.fn();
render(<TokenBalance token={mockToken} onClick={onClick} />);

const button = screen.getByRole('button');
fireEvent.click(button);
expect(onClick).toHaveBeenCalledWith(mockToken);
});

it('applies custom classNames to the div container', () => {
const customClassNames = { container: 'custom-class' };
render(<TokenBalance token={mockToken} classNames={customClassNames} />);
expect(screen.getByTestId('ockTokenBalance')).toHaveClass(
customClassNames.container,
);
});

it('applies custom classNames to the button container', () => {
const customClassNames = { container: 'custom-class' };
const handleClick = vi.fn();
render(
<TokenBalance
token={mockToken}
onClick={handleClick}
classNames={customClassNames}
/>,
);
expect(screen.getByTestId('ockTokenBalanceButton')).toHaveClass(
customClassNames.container,
);
});
});

describe('TokenBalanceContent', () => {
it('renders token details correctly', () => {
render(<TokenBalance token={mockToken} subtitle="Test subtitle" />);

expect(screen.getByText('Ethereum')).toBeInTheDocument();
expect(screen.getByText('0.000 ETH Test subtitle')).toBeInTheDocument();
expect(screen.getByText('$3,300.00')).toBeInTheDocument();
});

it('shows/hides token image based on showImage prop', () => {
const { rerender } = render(
<TokenBalance
token={mockToken}
showImage={true}
subtitle="Test subtitle"
/>,
);
expect(screen.getByTestId('ockTokenImage_NoImage')).toBeInTheDocument();

rerender(
<TokenBalance
token={mockToken}
showImage={false}
subtitle="Test subtitle"
/>,
);
expect(screen.queryByTestId('ockTokenImage_NoImage')).toBeNull();
});

it('renders subtitle when provided', () => {
const subtitle = '(50% of balance)';
render(<TokenBalance token={mockToken} subtitle={subtitle} />);
expect(screen.getByText(`0.000 ETH ${subtitle}`)).toBeInTheDocument();
});

it('renders action button when showAction is true', () => {
const onActionPress = vi.fn();
render(
<TokenBalance
token={mockToken}
actionText="Custom Action"
onActionPress={onActionPress}
/>,
);

const actionButton = screen.getByRole('button', {
name: 'Custom Action',
});
expect(actionButton).toBeInTheDocument();

fireEvent.click(actionButton);
expect(onActionPress).toHaveBeenCalled();
});

it('handles keyboard events on action button', () => {
const onActionPress = vi.fn();
render(<TokenBalance token={mockToken} onActionPress={onActionPress} />);

const actionButton = screen.getByRole('button', { name: 'Use max' });
fireEvent.keyDown(actionButton);
expect(onActionPress).toHaveBeenCalled();
});

it('applies custom class names to token elements when no action is provided', () => {
const customClassNames = {
tokenName: 'custom-name',
tokenValue: 'custom-value',
fiatValue: 'custom-fiat',
};

render(<TokenBalance token={mockToken} classNames={customClassNames} />);

expect(screen.getByText('Ethereum')).toHaveClass('custom-name');
expect(screen.getByText('0.000 ETH')).toHaveClass('custom-value');
expect(screen.getByText('$3,300.00')).toHaveClass('custom-fiat');
});

it('applies custom class names to token elements when action is provided', () => {
const customClassNames = {
tokenName: 'custom-name',
tokenValue: 'custom-value',
action: 'custom-action',
};

render(
<TokenBalance
token={mockToken}
classNames={customClassNames}
onActionPress={() => {}}
/>,
);

expect(screen.getByText('Ethereum')).toHaveClass('custom-name');
expect(screen.getByText('0.000 ETH')).toHaveClass('custom-value');
expect(screen.getByTestId('ockTokenBalanceAction')).toHaveClass(
'custom-action',
);
});

it('handles token with empty/null name', () => {
const tokenWithoutName = {
...mockToken,
name: null as unknown as string,
};
render(
<TokenBalance token={tokenWithoutName} subtitle="Test subtitle" />,
);

const nameElement = screen.getByText('', {
selector: 'span.ock-font-family.font-semibold',
});
expect(nameElement).toBeInTheDocument();
});

it('handles token size prop correctly', () => {
const customSize = 60;
render(
<TokenBalance
token={mockToken}
tokenSize={customSize}
subtitle="Test subtitle"
/>,
);
const imageContainer = screen.getByTestId('ockTokenImage_NoImage');
expect(imageContainer).toHaveStyle({
width: `${customSize}px`,
height: `${customSize}px`,
minWidth: `${customSize}px`,
minHeight: `${customSize}px`,
});
});
});
});
Loading