Skip to content

Commit

Permalink
Use member-avatar component, add getChecksum util to address utils
Browse files Browse the repository at this point in the history
  • Loading branch information
cgero-eth committed Mar 13, 2024
1 parent 6dd5067 commit b33f50c
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 8 deletions.
33 changes: 33 additions & 0 deletions src/modules/components/address/addressInput/addressInput.test.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
import { act, render, screen, within } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import type { Address } from 'viem';
import type { UseEnsAddressReturnType, UseEnsNameReturnType } from 'wagmi';
import * as wagmi from 'wagmi';
import { IconType, clipboardUtils } from '../../../../core';
import { addressUtils } from '../../../utils';
import { OdsModulesProvider } from '../../odsModulesProvider';
import { AddressInput, type IAddressInputProps } from './addressInput';

jest.mock('../../member', () => ({
MemberAvatar: () => <div data-testid="member-avatar-mock" />,
}));

describe('<AddressInput /> component', () => {
const pasteMock = jest.spyOn(clipboardUtils, 'paste');
const copyMock = jest.spyOn(clipboardUtils, 'copy');

const getChecksumMock = jest.spyOn(addressUtils, 'getChecksum');

const useEnsAddressMock = jest.spyOn(wagmi, 'useEnsAddress');
const useEnsNameMock = jest.spyOn(wagmi, 'useEnsName');

beforeEach(() => {
getChecksumMock.mockImplementation((value) => value as Address);
useEnsAddressMock.mockReturnValue({ data: undefined, isFetching: false } as UseEnsAddressReturnType);
useEnsNameMock.mockReturnValue({ data: undefined, isFetching: false } as UseEnsNameReturnType);
});

afterEach(() => {
pasteMock.mockReset();
copyMock.mockReset();

useEnsAddressMock.mockReset();
useEnsNameMock.mockReset();
});

const createTestComponent = (props?: Partial<IAddressInputProps>) => {
Expand Down Expand Up @@ -92,4 +114,15 @@ describe('<AddressInput /> component', () => {
expect(linkButton).toBeInTheDocument();
expect(linkButton.href).toEqual(`https://etherscan.io/address/${value}`);
});

it('renders a loder as avatar when loading the user address', () => {
useEnsAddressMock.mockReturnValue({ isFetching: true } as UseEnsAddressReturnType);
render(createTestComponent());
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});

it('renders the avatar for the current address', () => {
render(createTestComponent());
expect(screen.getByTestId('member-avatar-mock')).toBeInTheDocument();
});
});
12 changes: 7 additions & 5 deletions src/modules/components/address/addressInput/addressInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useQueryClient } from '@tanstack/react-query';
import classNames from 'classnames';
import { forwardRef, useEffect, useRef, useState, type ChangeEvent, type FocusEvent } from 'react';
import { getAddress, type Address } from 'viem';
import { type Address } from 'viem';
import { normalize } from 'viem/ens';
import { useConfig, useEnsAddress, useEnsName, type UseEnsAddressParameters, type UseEnsNameParameters } from 'wagmi';
import {
Avatar,
Button,
IconType,
InputContainer,
Expand All @@ -18,6 +17,7 @@ import {
} from '../../../../core';
import type { IWeb3ComponentProps } from '../../../types';
import { addressUtils, ensUtils } from '../../../utils';
import { MemberAvatar } from '../../member';

export interface IAddressInputResolvedValue {
/**
Expand Down Expand Up @@ -137,7 +137,7 @@ export const AddressInput = forwardRef<HTMLTextAreaElement, IAddressInputProps>(
onAccept?.({ address: ensAddress, name: normalizedEns });
} else if (addressUtils.isAddress(debouncedValue)) {
// User input is a valid address with or without a ENS name linked to it
const checksumAddress = getAddress(debouncedValue);
const checksumAddress = addressUtils.getChecksum(debouncedValue);
onAccept?.({ address: checksumAddress, name: ensName ?? undefined });
} else {
// User input is not a valid address nor ENS name
Expand Down Expand Up @@ -180,6 +180,8 @@ export const AddressInput = forwardRef<HTMLTextAreaElement, IAddressInputProps>(
const displayTruncatedAddress = value != null && addressUtils.isAddress(value) && !isFocused;
const displayTruncatedEns = value != null && ensUtils.isEnsName(value) && !isFocused;

const addressValue = ensAddress ?? (addressUtils.isAddress(value) ? value : undefined);

const processedValue = displayTruncatedAddress
? addressUtils.truncateAddress(value)
: displayTruncatedEns
Expand All @@ -190,7 +192,7 @@ export const AddressInput = forwardRef<HTMLTextAreaElement, IAddressInputProps>(
<InputContainer {...containerProps}>
<div className="ml-3 shrink-0">
{isLoading && <Spinner variant="neutral" size="lg" />}
{!isLoading && <Avatar />}
{!isLoading && <MemberAvatar address={addressValue} />}
</div>
<textarea
type="text"
Expand All @@ -213,7 +215,7 @@ export const AddressInput = forwardRef<HTMLTextAreaElement, IAddressInputProps>(
{displayMode === 'ens' ? '0x...' : 'ENS'}
</Button>
)}
{(ensAddress != null || addressUtils.isAddress(value)) && !isFocused && (
{addressValue != null && !isFocused && (
<>
<Button
variant="tertiary"
Expand Down
15 changes: 15 additions & 0 deletions src/modules/utils/addressUtils/addressUtils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* (See here https://github.com/jestjs/jest/issues/7780#issuecomment-865077151)
* @jest-environment node
*/
import { testLogger } from '../../../core/test';
import { addressUtils } from './addressUtils';

describe('address utils', () => {
Expand Down Expand Up @@ -45,4 +46,18 @@ describe('address utils', () => {
expect(addressUtils.truncateAddress(value)).toEqual(expectedValue);
});
});

describe('getChecksum', () => {
it('returns the address on its checksum format', () => {
const value = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045';
const checksum = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
expect(addressUtils.getChecksum(value)).toEqual(checksum);
});

it('throws error when input value is not a valid address ', () => {
testLogger.suppressErrors();
const value = 'test';
expect(() => addressUtils.getChecksum(value)).toThrow();
});
});
});
13 changes: 10 additions & 3 deletions src/modules/utils/addressUtils/addressUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAddress } from 'viem';
import { getAddress, isAddress, type Address } from 'viem';

export interface IIsAddressParams {
/**
Expand All @@ -15,17 +15,24 @@ class AddressUtils {
* @param options Options for the address check (@see IIsAddressParams)
* @returns True when the given address is a valid address, false otherwise.
*/
isAddress = (address = '', options: IIsAddressParams = { strict: false }) => isAddress(address, options);
isAddress = (address = '', options: IIsAddressParams = { strict: false }): boolean => isAddress(address, options);

/**
* Truncates the input address by displaying the first and last 4 characters.
* @param address The address to truncate
* @returns The truncated address when the address input is valid, the address input as is otherwise.
*/
truncateAddress = (address = '') =>
truncateAddress = (address = ''): string =>
this.isAddress(address)
? `${address.slice(0, 4)}...${address.slice(address.length - 4, address.length)}`
: address;

/**
* Returns the address on its checksum format
* @param address The address to be formatted
* @returns The address in checksum format
*/
getChecksum = (address = ''): Address => getAddress(address);
}

export const addressUtils = new AddressUtils();

0 comments on commit b33f50c

Please sign in to comment.