Skip to content

Commit

Permalink
feat: add user profile modal in ConnectButton (#103)
Browse files Browse the repository at this point in the history
* test: add ConnectButton profile modal

* fix: add browser util for fix review issue

---------

Co-authored-by: yutingzhao1991 <[email protected]>
  • Loading branch information
yutingzhao1991 and yutingzhao1991 authored Nov 23, 2023
1 parent dc922a7 commit 68b656c
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 28 deletions.
18 changes: 12 additions & 6 deletions packages/web3/src/address/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { CopyOutlined } from '@ant-design/icons';
import type { TooltipProps } from 'antd';
import { Space, Tooltip, message } from 'antd';
import React from 'react';
import { Space, Tooltip, message, ConfigProvider } from 'antd';
import React, { useContext } from 'react';
import { useStyle } from './style';
import classNames from 'classnames';
import { writeCopyText } from '../utils';

export interface AddressProps {
ellipsis?:
Expand All @@ -17,6 +20,9 @@ export interface AddressProps {

export const Address: React.FC<AddressProps> = (props) => {
const { ellipsis, address, copyable, tooltip } = props;
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('web3-address');
const { wrapSSR, hashId } = useStyle(prefixCls);

const isEllipsis = !!ellipsis;
const { headClip = 6, tailClip = 4 } =
Expand All @@ -34,8 +40,8 @@ export const Address: React.FC<AddressProps> = (props) => {
const filledAddress = address.startsWith('0x') ? address : `0x${address}`;
const displayTooltip = tooltip === undefined || tooltip === true ? filledAddress : tooltip;

return (
<Space>
return wrapSSR(
<Space className={classNames(prefixCls, hashId)}>
<Tooltip title={displayTooltip}>
{isEllipsis
? `${filledAddress.slice(0, headClip)}...${filledAddress.slice(-tailClip)}`
Expand All @@ -45,12 +51,12 @@ export const Address: React.FC<AddressProps> = (props) => {
<CopyOutlined
title="Copy Address"
onClick={() => {
navigator.clipboard.writeText(filledAddress).then(() => {
writeCopyText(filledAddress).then(() => {
message.success('Address Copied!');
});
}}
/>
)}
</Space>
</Space>,
);
};
28 changes: 28 additions & 0 deletions packages/web3/src/address/style/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type React from 'react';
import {
useStyle as useAntdStyle,
type GenerateStyle,
type Web3AliasToken,
} from '../../theme/useStyle';

export interface AddressToken extends Web3AliasToken {
componentCls: string;
}

const genAddressStyle: GenerateStyle<AddressToken> = (token) => {
return {
[token.componentCls]: {
wordBreak: 'break-all',
},
};
};

export function useStyle(prefixCls: string) {
return useAntdStyle('Address', (token) => {
const proListToken: AddressToken = {
...token,
componentCls: `.${prefixCls}`,
};
return [genAddressStyle(proListToken)];
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ProfileModal > match snapshot 1`] = `
<body>
<div />
<div>
<div
class="ant-modal-root css-dev-only-do-not-override-1ck3jst"
>
<div
class="ant-modal-mask ant-fade-appear ant-fade-appear-start ant-fade"
/>
<div
class="ant-modal-wrap"
tabindex="-1"
>
<div
aria-modal="true"
class="ant-modal css-dev-only-do-not-override-1ck3jst hashId ant-web3-connect-button-profile-modal ant-zoom-appear ant-zoom-appear-prepare ant-zoom"
role="dialog"
style="width: 280px;"
>
<div
aria-hidden="true"
style="width: 0px; height: 0px; overflow: hidden; outline: none;"
tabindex="0"
/>
<div
class="ant-modal-content"
>
<button
aria-label="Close"
class="ant-modal-close"
type="button"
>
<span
class="ant-modal-close-x"
>
<span
aria-label="close"
class="anticon anticon-close ant-modal-close-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</span>
</button>
<div
class="ant-modal-body"
>
<div
class="ant-space css-dev-only-do-not-override-1ck3jst ant-space-vertical ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-space-item"
>
<span
class="ant-avatar ant-avatar-circle ant-avatar-image css-dev-only-do-not-override-1ck3jst"
>
<img
src="https://metamask.io/images/metamask-logo.png"
/>
</span>
</div>
<div
class="ant-space-item"
>
<div
class="ant-web3-connect-button-profile-modal-name hashId"
>
wanderingearth.eth
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-space css-dev-only-do-not-override-1ck3jst ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small ant-web3-address css-dev-only-do-not-override-1ck3jst"
>
<div
class="ant-space-item"
>
<span>
0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B
</span>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-space css-dev-only-do-not-override-1ck3jst ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-space-item"
>
<button
class="ant-btn css-dev-only-do-not-override-1ck3jst ant-btn-default"
type="button"
>
<span>
Copy Address
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn css-dev-only-do-not-override-1ck3jst ant-btn-default"
type="button"
>
<span>
Disconnect
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-modal-footer"
/>
</div>
<div
aria-hidden="true"
style="width: 0px; height: 0px; overflow: hidden; outline: none;"
tabindex="0"
/>
</div>
</div>
</div>
</div>
</body>
`;
12 changes: 8 additions & 4 deletions packages/web3/src/connect-button/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConnectButton } from '..';
import { fireEvent, render } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mockClipboard } from '../../utils/test-utils';
import { readCopyText } from '../../utils';

describe('ConnectButton', () => {
let resetMockClipboard: () => void;
Expand Down Expand Up @@ -62,6 +63,7 @@ describe('ConnectButton', () => {
expect(baseElement.querySelector('.ant-tooltip-inner')?.textContent).toBe('0x3ea2cf...097c18');
expect(baseElement.querySelector('.anticon-copy')).not.toBeNull();
});

it('disabled copyable in tooltip', () => {
const { baseElement } = render(
<ConnectButton
Expand All @@ -75,6 +77,7 @@ describe('ConnectButton', () => {
);
expect(baseElement.querySelector('.anticon-copy')).toBeNull();
});

it('custom title in tooltip', () => {
const { baseElement } = render(
<ConnectButton
Expand All @@ -87,10 +90,12 @@ describe('ConnectButton', () => {
'aaaaaabbbbbbcccccc',
);
});

it('should not display tooltip when not custom title and without address in tooltip', () => {
const { baseElement } = render(<ConnectButton tooltip />);
expect(baseElement.querySelector('.ant-tooltip')).toBeNull();
});

it('should copy text after click copy icon', async () => {
const { baseElement } = render(
<ConnectButton address="3ea2cfd153b8d8505097b81c87c11f5d05097c18" tooltip={{ open: true }} />,
Expand All @@ -104,11 +109,10 @@ describe('ConnectButton', () => {
expect(baseElement.querySelector('.ant-message-notice-content')?.textContent).toBe(
'Address Copied!',
);
expect(navigator.clipboard.readText()).resolves.toBe(
'0x3ea2cfd153b8d8505097b81c87c11f5d05097c18',
);
expect(readCopyText()).resolves.toBe('0x3ea2cfd153b8d8505097b81c87c11f5d05097c18');
});
});

it('should copy text after click copy icon in custom title mode', async () => {
const { baseElement } = render(
<ConnectButton tooltip={{ open: true, title: 'aaaaaabbbbbbcccccc' }} />,
Expand All @@ -124,7 +128,7 @@ describe('ConnectButton', () => {
expect(baseElement.querySelector('.ant-message-notice-content')?.textContent).toBe(
'Address Copied!',
);
expect(navigator.clipboard.readText()).resolves.toBe('aaaaaabbbbbbcccccc');
expect(readCopyText()).resolves.toBe('aaaaaabbbbbbcccccc');
});
});
});
68 changes: 68 additions & 0 deletions packages/web3/src/connect-button/__tests__/profile-modal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { render, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mockClipboard } from '../../utils/test-utils';
import { ProfileModal } from '../profile-modal';
import { ConnectButton } from '..';
import { readCopyText } from '../../utils';

describe('ProfileModal', () => {
let resetMockClipboard: () => void;
beforeEach(() => {
resetMockClipboard = mockClipboard();
});
afterEach(() => {
resetMockClipboard();
});

it('match snapshot', () => {
const App = () => (
<ProfileModal
open
hashId="hashId"
address="0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B"
name="wanderingearth.eth"
avatar={{
src: 'https://metamask.io/images/metamask-logo.png',
}}
/>
);
const { baseElement } = render(<App />);
expect(baseElement).toMatchSnapshot();
});

it('open profile modal in ConnectButton', async () => {
const App = () => (
<ConnectButton connected address="0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B" />
);
const { baseElement } = render(<App />);
expect(baseElement.querySelector('.ant-web3-connect-button-text')).not.toBeNull();

fireEvent.click(baseElement.querySelector('.ant-web3-connect-button-text')!);

await vi.waitFor(() => {
expect(
baseElement.querySelector('.ant-web3-connect-button-profile-modal .ant-web3-address')
?.textContent,
).toBe('0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B');
});
});

it('Disconnect & Copy Address Button', () => {
const disconnectTestFn = vi.fn();
const App = () => (
<ProfileModal
open
hashId="hashId"
address="0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B"
onDisconnect={disconnectTestFn}
/>
);
const { baseElement } = render(<App />);
const btns = baseElement.querySelectorAll('.ant-web3-connect-button-profile-modal .ant-btn');
expect(btns.length).toBe(2);
fireEvent.click(btns[1]);
expect(disconnectTestFn).toBeCalled();
fireEvent.click(btns[0]);
expect(readCopyText()).resolves.toBe('0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B');
});
});
Loading

0 comments on commit 68b656c

Please sign in to comment.