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: Update view transaction link #1016

Merged
merged 21 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/internal/svg/errorSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const errorSvg = (
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-testid="ockErrorSvg"
>
<title>Error SVG</title>
<path
Expand Down
1 change: 1 addition & 0 deletions src/internal/svg/successSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const successSvg = (
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-testid="ockSuccessSvg"
>
<title>Success SVG</title>
<path
Expand Down
83 changes: 78 additions & 5 deletions src/transaction/components/TransactionButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useChainId } from 'wagmi';
import { useShowCallsStatus } from 'wagmi/experimental';
import { getChainExplorer } from '../../network/getChainExplorer';
import { TransactionButton } from './TransactionButton';
import { useTransactionContext } from './TransactionProvider';

vi.mock('./TransactionProvider', () => ({
useTransactionContext: vi.fn(),
}));

vi.mock('wagmi', () => ({
useChainId: vi.fn(),
}));

vi.mock('wagmi/experimental', () => ({
useShowCallsStatus: vi.fn(),
}));

vi.mock('../../network/getChainExplorer', () => ({
getChainExplorer: vi.fn(),
}));

describe('TransactionButton', () => {
beforeEach(() => {
(useChainId as vi.Mock).mockReturnValue(123);
(useShowCallsStatus as vi.Mock).mockReturnValue({
showCallsStatus: vi.fn(),
});
});
it('renders correctly', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
isLoading: false,
Expand All @@ -34,16 +51,16 @@ describe('TransactionButton', () => {
expect(spinner).toBeInTheDocument();
});

it('renders checkmark svg correctly when receipt exists', () => {
it('renders view txn text when receipt exists', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
isLoading: true,
receipt: '123',
});

render(<TransactionButton text="Transact" />);

const checkmark = screen.getByTestId('ockCheckmarkSvg');
expect(checkmark).toBeInTheDocument();
const text = screen.getByText('View transaction');
expect(text).toBeInTheDocument();
});

it('renders try again when error exists', () => {
Expand Down Expand Up @@ -86,6 +103,21 @@ describe('TransactionButton', () => {
expect(button).toBeDisabled();
});

it('should call showCallsStatus when receipt and transactionId exist', () => {
const showCallsStatus = vi.fn();
(useShowCallsStatus as vi.Mock).mockReturnValue({ showCallsStatus });
(useTransactionContext as vi.Mock).mockReturnValue({
receipt: '123',
transactionId: '456',
});

render(<TransactionButton text="Transact" />);
const button = screen.getByText('View transaction');
fireEvent.click(button);

expect(showCallsStatus).toHaveBeenCalledWith({ id: '456' });
});

it('should enable button when not in progress, not missing props, and not waiting for receipt', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
isLoading: false,
Expand All @@ -100,4 +132,45 @@ describe('TransactionButton', () => {
const button = getByRole('button');
expect(button).not.toBeDisabled();
});

it('should open transaction link when only receipt exists', () => {
const onSubmit = vi.fn();
const chainExplorerUrl = 'https://explorer.com';
(useTransactionContext as vi.Mock).mockReturnValue({
receipt: 'receipt-123',
transactionId: undefined,
transactionHash: 'hash-789',
onSubmit,
});
(getChainExplorer as vi.Mock).mockReturnValue(chainExplorerUrl);
window.open = vi.fn();

render(<TransactionButton text="Transact" />);
const button = screen.getByText('View transaction');
fireEvent.click(button);

expect(window.open).toHaveBeenCalledWith(
`${chainExplorerUrl}/tx/hash-789`,
'_blank',
'noopener,noreferrer',
);
expect(onSubmit).not.toHaveBeenCalled();
});

it('should call onSubmit when neither receipt nor transactionId exists', () => {
const onSubmit = vi.fn();
(useTransactionContext as vi.Mock).mockReturnValue({
receipt: undefined,
transactionId: undefined,
onSubmit,
address: '123',
contracts: [{}],
});

render(<TransactionButton text="Transact" />);
const button = screen.getByText('Transact');
fireEvent.click(button);

expect(onSubmit).toHaveBeenCalled();
});
});
40 changes: 36 additions & 4 deletions src/transaction/components/TransactionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { useChainId } from 'wagmi';
import { useShowCallsStatus } from 'wagmi/experimental';
import { Spinner } from '../../internal/components/Spinner';
import { checkmarkSvg } from '../../internal/svg/checkmarkSvg';
import { getChainExplorer } from '../../network/getChainExplorer';
import { background, cn, color, pressable, text } from '../../styles/theme';
import type { TransactionButtonReact } from '../types';
import { isSpinnerDisplayed } from '../utils/isSpinnerDisplayed';
Expand All @@ -14,6 +16,7 @@ export function TransactionButton({
const {
address,
contracts,
chainId,
errorMessage,
isLoading,
onSubmit,
Expand All @@ -24,6 +27,9 @@ export function TransactionButton({
transactionId,
} = useTransactionContext();

const accountChainId = chainId ?? useChainId();
const { showCallsStatus } = useShowCallsStatus();

const isInProgress =
statusWriteContract === 'pending' ||
statusWriteContracts === 'pending' ||
Expand All @@ -46,15 +52,41 @@ export function TransactionButton({
});

const buttonContent = useMemo(() => {
// txn successful
if (receipt) {
return checkmarkSvg;
return 'View transaction';
}
if (errorMessage) {
return 'Try again';
}
return buttonText;
}, [buttonText, errorMessage, receipt]);

const handleSubmit = useCallback(() => {
// SW will have txn id so open in wallet
if (receipt && transactionId) {
showCallsStatus({ id: transactionId });
// EOA will not have txn id so open in explorer
} else if (receipt) {
const chainExplorer = getChainExplorer(accountChainId);
window.open(
`${chainExplorer}/tx/${transactionHash}`,
'_blank',
'noopener,noreferrer',
);
} else {
// if no receipt, submit txn
onSubmit();
}
}, [
accountChainId,
onSubmit,
receipt,
showCallsStatus,
transactionHash,
transactionId,
]);

return (
<button
className={cn(
Expand All @@ -65,7 +97,7 @@ export function TransactionButton({
text.headline,
className,
)}
onClick={onSubmit}
onClick={handleSubmit}
type="button"
disabled={isDisabled}
>
Expand Down
8 changes: 4 additions & 4 deletions src/transaction/components/TransactionStatusAction.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useGetTransactionStatus } from '../hooks/useGetTransactionStatus';
import { useGetTransactionStatusAction } from '../hooks/useGetTransactionStatusAction';
import { TransactionStatusAction } from './TransactionStatusAction';

vi.mock('../hooks/useGetTransactionStatus', () => ({
useGetTransactionStatus: vi.fn(),
vi.mock('../hooks/useGetTransactionStatusAction', () => ({
useGetTransactionStatusAction: vi.fn(),
}));

describe('TransactionStatusAction', () => {
it('renders transaction status action', () => {
(useGetTransactionStatus as vi.Mock).mockReturnValue({
(useGetTransactionStatusAction as vi.Mock).mockReturnValue({
actionElement: <button type="button">Try again</button>,
});

Expand Down
4 changes: 2 additions & 2 deletions src/transaction/components/TransactionStatusAction.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { cn, text } from '../../styles/theme';
import { useGetTransactionStatus } from '../hooks/useGetTransactionStatus';
import { useGetTransactionStatusAction } from '../hooks/useGetTransactionStatusAction';
import type { TransactionStatusActionReact } from '../types';

export function TransactionStatusAction({
className,
}: TransactionStatusActionReact) {
const { actionElement } = useGetTransactionStatus();
const { actionElement } = useGetTransactionStatusAction();

return (
<div className={cn(text.label2, 'min-w-[70px]', className)}>
Expand Down
8 changes: 4 additions & 4 deletions src/transaction/components/TransactionStatusLabel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useGetTransactionStatus } from '../hooks/useGetTransactionStatus';
import { useGetTransactionStatusLabel } from '../hooks/useGetTransactionStatusLabel';
import { TransactionStatusLabel } from './TransactionStatusLabel';

vi.mock('../hooks/useGetTransactionStatus', () => ({
useGetTransactionStatus: vi.fn(),
vi.mock('../hooks/useGetTransactionStatusLabel', () => ({
useGetTransactionStatusLabel: vi.fn(),
}));

describe('TransactionStatusLabel', () => {
it('renders transaction status label', () => {
(useGetTransactionStatus as vi.Mock).mockReturnValue({
(useGetTransactionStatusLabel as vi.Mock).mockReturnValue({
label: 'Successful!',
labelClassName: 'text-ock-foreground-muted',
});
Expand Down
4 changes: 2 additions & 2 deletions src/transaction/components/TransactionStatusLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { cn, text } from '../../styles/theme';
import { useGetTransactionStatus } from '../hooks/useGetTransactionStatus';
import { useGetTransactionStatusLabel } from '../hooks/useGetTransactionStatusLabel';
import type { TransactionStatusLabelReact } from '../types';

export function TransactionStatusLabel({
className,
}: TransactionStatusLabelReact) {
const { label, labelClassName } = useGetTransactionStatus();
const { label, labelClassName } = useGetTransactionStatusLabel();

return (
<div className={cn(text.label2, className)}>
Expand Down
8 changes: 4 additions & 4 deletions src/transaction/components/TransactionToastAction.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useGetTransactionToast } from '../hooks/useGetTransactionToast';
import { useGetTransactionToastAction } from '../hooks/useGetTransactionToastAction';
import { TransactionToastAction } from './TransactionToastAction';

vi.mock('../hooks/useGetTransactionToast', () => ({
useGetTransactionToast: vi.fn(),
vi.mock('../hooks/useGetTransactionToastAction', () => ({
useGetTransactionToastAction: vi.fn(),
}));

describe('TransactionToastAction', () => {
it('renders transaction status action', () => {
(useGetTransactionToast as vi.Mock).mockReturnValue({
(useGetTransactionToastAction as vi.Mock).mockReturnValue({
actionElement: <button type="button">Try again</button>,
});

Expand Down
4 changes: 2 additions & 2 deletions src/transaction/components/TransactionToastAction.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { cn, text } from '../../styles/theme';
import { useGetTransactionToast } from '../hooks/useGetTransactionToast';
import { useGetTransactionToastAction } from '../hooks/useGetTransactionToastAction';
import type { TransactionToastActionReact } from '../types';

export function TransactionToastAction({
className,
}: TransactionToastActionReact) {
const { actionElement } = useGetTransactionToast();
const { actionElement } = useGetTransactionToastAction();

return (
<div className={cn(text.label1, 'text-nowrap', className)}>
Expand Down
46 changes: 39 additions & 7 deletions src/transaction/components/TransactionToastIcon.test.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useGetTransactionToast } from '../hooks/useGetTransactionToast';
import { useTransactionContext } from '../components/TransactionProvider';
import { TransactionToastIcon } from './TransactionToastIcon';

vi.mock('../hooks/useGetTransactionToast', () => ({
useGetTransactionToast: vi.fn(),
vi.mock('../components/TransactionProvider', () => ({
useTransactionContext: vi.fn(),
}));

describe('TransactionToastIcon', () => {
it('renders transaction toast icon', () => {
(useGetTransactionToast as vi.Mock).mockReturnValue({
icon: <div>icon</div>,
it('renders success icon when receipt exists', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
receipt: '123',
});

render(<TransactionToastIcon className="custom-class" />);

const iconElement = screen.getByText('icon');
const iconElement = screen.getByTestId('ockSuccessSvg');
expect(iconElement).toBeInTheDocument();
});
it('renders error icon when error exists', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
errorMessage: 'error',
});

render(<TransactionToastIcon className="custom-class" />);

const iconElement = screen.getByTestId('ockErrorSvg');
expect(iconElement).toBeInTheDocument();
});
it('renders loading icon when txn is in progress', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
isLoading: true,
});

render(<TransactionToastIcon className="custom-class" />);

const iconElement = screen.getByTestId('ockSpinner');
expect(iconElement).toBeInTheDocument();
});
it('renders null when if no status exists', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
isLoading: false,
});

const { container } = render(
<TransactionToastIcon className="test-class" />,
);

// Assert that nothing is rendered (container should be empty)
expect(container.firstChild).toBeNull();
});
});
Loading