Skip to content

Commit

Permalink
refactor: remove swap findChildren
Browse files Browse the repository at this point in the history
  • Loading branch information
alessey committed Mar 5, 2025
1 parent c2d4c67 commit eb1bdff
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 131 deletions.
138 changes: 101 additions & 37 deletions playground/nextjs-app-router/components/demo/Swap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,45 +103,109 @@ function SwapComponent() {
</div>
) : null}

<Swap
className="w-full border sm:w-[500px]"
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
onError={handleOnError}
config={{
maxSlippage: defaultMaxSlippage || FALLBACK_DEFAULT_MAX_SLIPPAGE,
}}
isSponsored={isSponsored}
>
<SwapSettings>
<SwapSettingsSlippageTitle>Max. slippage</SwapSettingsSlippageTitle>
<SwapSettingsSlippageDescription>
Your swap will revert if the prices change by more than the selected
percentage.
</SwapSettingsSlippageDescription>
<SwapSettingsSlippageInput />
</SwapSettings>
<SwapAmountInput
label="Sell"
<div className="relative flex flex-col gap-6">
<h2>Swap Default</h2>
<Swap
className="w-full border sm:w-[500px]"
swappableTokens={swappableTokens}
token={ethToken}
type="from"
toToken={usdcToken}
fromToken={ethToken}
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
onError={handleOnError}
config={{
maxSlippage: defaultMaxSlippage || FALLBACK_DEFAULT_MAX_SLIPPAGE,
}}
isSponsored={isSponsored}
headerLeftContent={<div>test</div>}
/>
<SwapToggleButton />
<SwapAmountInput
label="Buy"
swappableTokens={swappableTokens}
token={usdcToken}
type="to"
/>
<SwapButton
disabled={
ENVIRONMENT_VARIABLES[ENVIRONMENT.ENVIRONMENT] === 'production'
}
/>
<SwapMessage />
<SwapToast />
</Swap>

<h2>Swap with Children</h2>
<Swap
className="w-full border sm:w-[500px]"
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
onError={handleOnError}
config={{
maxSlippage: defaultMaxSlippage || FALLBACK_DEFAULT_MAX_SLIPPAGE,
}}
isSponsored={isSponsored}
headerLeftContent={<div>test</div>}
>
<SwapSettings>
<SwapSettingsSlippageTitle>Max. slippage</SwapSettingsSlippageTitle>
<SwapSettingsSlippageDescription>
Your swap will revert if the prices change by more than the
selected percentage.
</SwapSettingsSlippageDescription>
<SwapSettingsSlippageInput />
</SwapSettings>
<SwapAmountInput
label="Sell"
swappableTokens={swappableTokens}
token={ethToken}
type="from"
/>
<SwapToggleButton />
<SwapAmountInput
label="Buy"
swappableTokens={swappableTokens}
token={usdcToken}
type="to"
/>
<SwapButton
disabled={
ENVIRONMENT_VARIABLES[ENVIRONMENT.ENVIRONMENT] === 'production'
}
/>
<SwapMessage />
<SwapToast />
</Swap>

<h2>Swap with extra Children</h2>
<Swap
className="w-full border sm:w-[500px]"
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
onError={handleOnError}
config={{
maxSlippage: defaultMaxSlippage || FALLBACK_DEFAULT_MAX_SLIPPAGE,
}}
isSponsored={isSponsored}
headerLeftContent={<div>test</div>}
>
<SwapSettings>
<SwapSettingsSlippageTitle>Max. slippage</SwapSettingsSlippageTitle>
<SwapSettingsSlippageDescription>
Your swap will revert if the prices change by more than the
selected percentage.
</SwapSettingsSlippageDescription>
<SwapSettingsSlippageInput />
</SwapSettings>
<div className="pb-4">Buy my MemeCoin!!!</div>
<SwapAmountInput
label="Sell"
swappableTokens={swappableTokens}
token={ethToken}
type="from"
/>
<SwapToggleButton />
<SwapAmountInput
label="Buy"
swappableTokens={swappableTokens}
token={usdcToken}
type="to"
/>
<div className="pt-4">DO IT!!!</div>
<SwapButton
disabled={
ENVIRONMENT_VARIABLES[ENVIRONMENT.ENVIRONMENT] === 'production'
}
/>
<SwapMessage />
<SwapToast />
</Swap>
</div>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion playground/nextjs-app-router/onchainkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/onchainkit",
"version": "0.37.4",
"version": "0.37.5",
"type": "module",
"repository": "https://github.com/coinbase/onchainkit.git",
"license": "MIT",
Expand Down
76 changes: 75 additions & 1 deletion src/swap/components/Swap.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,52 @@ import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { Swap } from './Swap';

vi.mock('wagmi', async (importOriginal) => {
const actual = await importOriginal<typeof import('wagmi')>();
return {
...actual,
useAccount: vi.fn().mockReturnValue({
address: '0x0000000000000000000000000000000000000000' as `0x${string}`,
chainId: 1,
}),
useConfig: vi.fn(),
useSwitchChain: vi.fn(),
useWriteContract: vi.fn(),
};
});

vi.mock('@/internal/hooks/useBreakpoints', () => ({
useBreakpoints: vi.fn().mockReturnValue('md'),
}));

vi.mock('./SwapProvider', () => ({
SwapProvider: ({ children }: { children: React.ReactNode }) => (
<div data-testid="mock-SwapProvider">{children}</div>
),
useSwapContext: vi.fn(),
useSwapContext: vi.fn().mockReturnValue({
address: '0x0000000000000000000000000000000000000000' as `0x${string}`,
config: {
maxSlippage: 10,
},
from: {
amount: '100',
},
to: {
amount: '100',
},
lifecycleStatus: {
statusName: 'init',
statusData: {
isMissingRequiredField: true,
maxSlippage: 10,
},
},
handleAmountChange: vi.fn(),
handleSubmit: vi.fn(),
handleToggle: vi.fn(),
updateLifecycleStatus: vi.fn(),
isToastVisible: false,
}),
}));

vi.mock('@/internal/svg/closeSvg', () => ({
Expand Down Expand Up @@ -46,4 +87,37 @@ describe('Swap Component', () => {
const container = screen.getByTestId('ockSwap_Container');
expect(container).toHaveClass('custom-class');
});

it('should render children', () => {
render(
<Swap>
<div>Test Child</div>
</Swap>,
);

expect(screen.getByText('Test Child')).toBeInTheDocument();
});

it('should render children when to, from, and disabled are provided', () => {
const toToken = {
address: '0x0000000000000000000000000000000000000000' as `0x${string}`,
chainId: 1,
decimals: 18,
image: 'https://example.com/image.png',
name: 'toToken',
symbol: 'toToken',
};
const fromToken = {
address: '0x0000000000000000000000000000000000000001' as `0x${string}`,
chainId: 1,
decimals: 18,
image: 'https://example.com/image.png',
name: 'fromToken',
symbol: 'fromToken',
};
render(<Swap to={[toToken]} from={[fromToken]} disabled={true} />);

expect(screen.getByText('Buy')).toBeInTheDocument();
expect(screen.getByText('Sell')).toBeInTheDocument();
});
});
73 changes: 41 additions & 32 deletions src/swap/components/Swap.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use client';
import { Children, useMemo } from 'react';
import { useIsMounted } from '../../internal/hooks/useIsMounted';
import { useTheme } from '../../internal/hooks/useTheme';
import { findComponent } from '../../internal/utils/findComponent';
import { background, border, cn, color, text } from '../../styles/theme';
import { FALLBACK_DEFAULT_MAX_SLIPPAGE } from '../constants';
import type { SwapReact } from '../types';
Expand All @@ -14,12 +12,43 @@ import { SwapSettings } from './SwapSettings';
import { SwapToast } from './SwapToast';
import { SwapToggleButton } from './SwapToggleButton';

function SwapDefaultContent({
to,
from,
disabled,
}: Pick<SwapReact, 'to' | 'from' | 'disabled'>) {
return (
<>
<SwapSettings />
<SwapAmountInput
label="Sell"
swappableTokens={from}
token={from?.[0]}
type="from"
/>
<SwapToggleButton />
<SwapAmountInput
label="Buy"
swappableTokens={to}
token={to?.[0]}
type="to"
/>
<SwapButton disabled={disabled} />
<SwapMessage />
<SwapToast />
</>
);
}

export function Swap({
children,
config = {
maxSlippage: FALLBACK_DEFAULT_MAX_SLIPPAGE,
},
className,
disabled,
to,
from,
experimental = { useAggregator: false },
isSponsored = false,
onError,
Expand All @@ -30,32 +59,13 @@ export function Swap({
}: SwapReact) {
const componentTheme = useTheme();

const {
inputs,
toggleButton,
swapButton,
swapMessage,
swapSettings,
swapToast,
} = useMemo(() => {
const childrenArray = Children.toArray(children);

return {
inputs: childrenArray.filter(findComponent(SwapAmountInput)),
toggleButton: childrenArray.find(findComponent(SwapToggleButton)),
swapButton: childrenArray.find(findComponent(SwapButton)),
swapMessage: childrenArray.find(findComponent(SwapMessage)),
swapSettings: childrenArray.find(findComponent(SwapSettings)),
swapToast: childrenArray.find(findComponent(SwapToast)),
};
}, [children]);

const isMounted = useIsMounted();

// prevents SSR hydration issue
if (!isMounted) {
return null;
}

return (
<SwapProvider
config={config}
Expand All @@ -71,24 +81,23 @@ export function Swap({
background.default,
border.radius,
color.foreground,
'flex w-[500px] flex-col px-6 pt-6 pb-4',
'relative flex w-full max-w-[500px] flex-col px-6 pt-6 pb-4',
className,
)}
data-testid="ockSwap_Container"
>
<div className="mb-4 flex items-center justify-between">
<div className="absolute flex w-1/2 items-center justify-between">
{headerLeftContent}
<h3 className={cn(text.title3)} data-testid="ockSwap_Title">
<h3
className={cn(text.title3, 'text-center')}
data-testid="ockSwap_Title"
>
{title}
</h3>
{swapSettings}
</div>
{inputs[0]}
<div className="relative h-1">{toggleButton}</div>
{inputs[1]}
{swapButton}
{swapToast}
<div className="flex">{swapMessage}</div>
{children ?? (
<SwapDefaultContent to={to} from={from} disabled={disabled} />
)}
</div>
</SwapProvider>
);
Expand Down
Loading

0 comments on commit eb1bdff

Please sign in to comment.