From 2bf77336249f4c8a57560ea37061a19a9afd5f1b Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 16:12:25 -0700 Subject: [PATCH 01/21] add useBreakpoints hook --- src/useBreakpoints.ts | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/useBreakpoints.ts diff --git a/src/useBreakpoints.ts b/src/useBreakpoints.ts new file mode 100644 index 0000000000..832a1c4c23 --- /dev/null +++ b/src/useBreakpoints.ts @@ -0,0 +1,44 @@ +import { useState, useEffect } from "react"; + +// tailwind breakpoints +const breakpoints = { + sm: "(max-width: 640px)", + md: "(min-width: 641px) and (max-width: 768px)", + lg: "(min-width: 769px) and (max-width: 1023px)", + xl: "(min-width: 1024px) and (max-width: 1279px)", + "2xl": "(min-width: 1280px)", +}; + +export function useBreakpoints() { + const [currentBreakpoint, setCurrentBreakpoint] = useState("md"); + + // handles SSR case where window would be undefined, + // once component mounts on client, hook sets correct breakpoint + useEffect(() => { + // get the current breakpoint based on media queries + const getCurrentBreakpoint = () => { + const entries = Object.entries(breakpoints) as [string, string][]; + for (const [key, query] of entries) { + if (window.matchMedia(query).matches) { + return key; + } + } + return 'md'; + }; + + // set initial breakpoint + setCurrentBreakpoint(getCurrentBreakpoint()); + + // listen changes in the window size + const handleResize = () => { + setCurrentBreakpoint(getCurrentBreakpoint()); + }; + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + return currentBreakpoint; +} + +export default useBreakpoints; From c3ca3ba446efe5d3cc3a54f179b365650f79de60 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 16:33:41 -0700 Subject: [PATCH 02/21] update hook to use undefined as default --- src/useBreakpoints.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/useBreakpoints.ts b/src/useBreakpoints.ts index 832a1c4c23..ea995d33fc 100644 --- a/src/useBreakpoints.ts +++ b/src/useBreakpoints.ts @@ -1,16 +1,18 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect } from 'react'; // tailwind breakpoints const breakpoints = { - sm: "(max-width: 640px)", - md: "(min-width: 641px) and (max-width: 768px)", - lg: "(min-width: 769px) and (max-width: 1023px)", - xl: "(min-width: 1024px) and (max-width: 1279px)", - "2xl": "(min-width: 1280px)", + sm: '(max-width: 640px)', + md: '(min-width: 641px) and (max-width: 768px)', + lg: '(min-width: 769px) and (max-width: 1023px)', + xl: '(min-width: 1024px) and (max-width: 1279px)', + '2xl': '(min-width: 1280px)', }; export function useBreakpoints() { - const [currentBreakpoint, setCurrentBreakpoint] = useState("md"); + const [currentBreakpoint, setCurrentBreakpoint] = useState< + string | undefined + >(undefined); // handles SSR case where window would be undefined, // once component mounts on client, hook sets correct breakpoint @@ -23,7 +25,7 @@ export function useBreakpoints() { return key; } } - return 'md'; + return undefined; }; // set initial breakpoint @@ -34,8 +36,8 @@ export function useBreakpoints() { setCurrentBreakpoint(getCurrentBreakpoint()); }; - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); }, []); return currentBreakpoint; From 8a20acf32098cb7630bf4e29bed9b5b60edf7d9e Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 16:34:32 -0700 Subject: [PATCH 03/21] add wallet details components --- src/wallet/components/Wallet.tsx | 8 ++--- src/wallet/components/WalletBottomSheet.tsx | 40 +++++++++++++++++++++ src/wallet/components/WalletDetails.tsx | 23 ++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/wallet/components/WalletBottomSheet.tsx create mode 100644 src/wallet/components/WalletDetails.tsx diff --git a/src/wallet/components/Wallet.tsx b/src/wallet/components/Wallet.tsx index 9fa2176b51..12876ab6be 100644 --- a/src/wallet/components/Wallet.tsx +++ b/src/wallet/components/Wallet.tsx @@ -2,18 +2,18 @@ import { Children, useEffect, useMemo, useRef } from 'react'; import { findComponent } from '../../internal/utils/findComponent'; import type { WalletReact } from '../types'; import { ConnectWallet } from './ConnectWallet'; -import { WalletDropdown } from './WalletDropdown'; +import { WalletDetails } from './WalletDetails'; import { WalletProvider, useWalletContext } from './WalletProvider'; const WalletContent = ({ children }: WalletReact) => { const { isOpen, setIsOpen } = useWalletContext(); const walletContainerRef = useRef(null); - const { connect, dropdown } = useMemo(() => { + const { menu, connect } = useMemo(() => { const childrenArray = Children.toArray(children); return { connect: childrenArray.find(findComponent(ConnectWallet)), - dropdown: childrenArray.find(findComponent(WalletDropdown)), + menu: childrenArray.find(findComponent(WalletDetails)), }; }, [children]); @@ -37,7 +37,7 @@ const WalletContent = ({ children }: WalletReact) => { return (
{connect} - {isOpen && dropdown} + {isOpen && menu}
); }; diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx new file mode 100644 index 0000000000..8f263fee3a --- /dev/null +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -0,0 +1,40 @@ +import { Children, cloneElement, isValidElement, useMemo } from 'react'; +import { useAccount } from 'wagmi'; +import { Identity } from '../../identity/components/Identity'; +import { background, cn } from '../../styles/theme'; +import type { WalletDropdownReact } from '../types'; + +export function WalletBottomSheet({ + children, + className, +}: WalletDropdownReact) { + const { address } = useAccount(); + + const childrenArray = useMemo(() => { + return Children.toArray(children).map((child) => { + if (isValidElement(child) && child.type === Identity) { + // @ts-ignore + return cloneElement(child, { address }); + } + return child; + }); + }, [children, address]); + + console.log({ address }); + + if (!address) { + return null; + } + + return ( +
+ {childrenArray} +
+ ); +} diff --git a/src/wallet/components/WalletDetails.tsx b/src/wallet/components/WalletDetails.tsx new file mode 100644 index 0000000000..8974bd9d11 --- /dev/null +++ b/src/wallet/components/WalletDetails.tsx @@ -0,0 +1,23 @@ +import { useAccount } from 'wagmi'; +import useBreakpoints from '../../useBreakpoints'; +import { WalletDropdownReact } from '../types'; +import { WalletDropdown } from './WalletDropdown'; +import { WalletBottomSheet } from './WalletBottomSheet'; + +export function WalletDetails({ children }: WalletDropdownReact) { + const breakpoint = useBreakpoints(); + const { address } = useAccount(); + if (!address) { + return null; + } + + if (!breakpoint) { + return null; + } + + if (breakpoint === 'sm') { + return {children}; + } + + return {children}; +} From 082b700b8acbd8355dd0a50333f26a28dbb0808a Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 16:38:06 -0700 Subject: [PATCH 04/21] update component name and types --- src/wallet/components/Wallet.tsx | 4 ++-- src/wallet/components/WalletBottomSheet.tsx | 4 ++-- .../{WalletDetails.tsx => WalletMenu.tsx} | 10 ++++++---- src/wallet/types.ts | 17 +++++++++++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) rename src/wallet/components/{WalletDetails.tsx => WalletMenu.tsx} (57%) diff --git a/src/wallet/components/Wallet.tsx b/src/wallet/components/Wallet.tsx index 12876ab6be..7ee8710440 100644 --- a/src/wallet/components/Wallet.tsx +++ b/src/wallet/components/Wallet.tsx @@ -2,7 +2,7 @@ import { Children, useEffect, useMemo, useRef } from 'react'; import { findComponent } from '../../internal/utils/findComponent'; import type { WalletReact } from '../types'; import { ConnectWallet } from './ConnectWallet'; -import { WalletDetails } from './WalletDetails'; +import { WalletMenu } from './WalletMenu'; import { WalletProvider, useWalletContext } from './WalletProvider'; const WalletContent = ({ children }: WalletReact) => { @@ -13,7 +13,7 @@ const WalletContent = ({ children }: WalletReact) => { const childrenArray = Children.toArray(children); return { connect: childrenArray.find(findComponent(ConnectWallet)), - menu: childrenArray.find(findComponent(WalletDetails)), + menu: childrenArray.find(findComponent(WalletMenu)), }; }, [children]); diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index 8f263fee3a..2220985b85 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -2,12 +2,12 @@ import { Children, cloneElement, isValidElement, useMemo } from 'react'; import { useAccount } from 'wagmi'; import { Identity } from '../../identity/components/Identity'; import { background, cn } from '../../styles/theme'; -import type { WalletDropdownReact } from '../types'; +import type { WalletBottomSheetReact } from '../types'; export function WalletBottomSheet({ children, className, -}: WalletDropdownReact) { +}: WalletBottomSheetReact) { const { address } = useAccount(); const childrenArray = useMemo(() => { diff --git a/src/wallet/components/WalletDetails.tsx b/src/wallet/components/WalletMenu.tsx similarity index 57% rename from src/wallet/components/WalletDetails.tsx rename to src/wallet/components/WalletMenu.tsx index 8974bd9d11..2fd414bea6 100644 --- a/src/wallet/components/WalletDetails.tsx +++ b/src/wallet/components/WalletMenu.tsx @@ -1,10 +1,10 @@ import { useAccount } from 'wagmi'; import useBreakpoints from '../../useBreakpoints'; -import { WalletDropdownReact } from '../types'; +import { WalletMenuReact } from '../types'; import { WalletDropdown } from './WalletDropdown'; import { WalletBottomSheet } from './WalletBottomSheet'; -export function WalletDetails({ children }: WalletDropdownReact) { +export function WalletMenu({ children, className }: WalletMenuReact) { const breakpoint = useBreakpoints(); const { address } = useAccount(); if (!address) { @@ -16,8 +16,10 @@ export function WalletDetails({ children }: WalletDropdownReact) { } if (breakpoint === 'sm') { - return {children}; + return ( + {children} + ); } - return {children}; + return {children}; } diff --git a/src/wallet/types.ts b/src/wallet/types.ts index 7782411331..82ca5561ec 100644 --- a/src/wallet/types.ts +++ b/src/wallet/types.ts @@ -73,6 +73,14 @@ export type WalletReact = { children: React.ReactNode; }; +/** + * Note: exported as public Type + */ +export type WalletBottomSheetReact = { + children: React.ReactNode; + className?: string; // Optional className override for top div element +}; + /** * Note: exported as public Type */ @@ -122,6 +130,15 @@ export type WalletDropdownLinkReact = { target?: string; }; +/** + * Note: exported as public Type + */ +export type WalletMenuReact = { + children: React.ReactNode; + className?: string; // Optional className override for top div element +}; + + export type WindowSizes = Record< 'sm' | 'md' | 'lg', { From b1b0e854e4c460f940e19d959042841084642a23 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 16:54:28 -0700 Subject: [PATCH 05/21] add overlay when drawer is open --- src/wallet/components/WalletBottomSheet.tsx | 28 +++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index 2220985b85..a978082530 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -3,11 +3,13 @@ import { useAccount } from 'wagmi'; import { Identity } from '../../identity/components/Identity'; import { background, cn } from '../../styles/theme'; import type { WalletBottomSheetReact } from '../types'; +import { useWalletContext } from './WalletProvider'; export function WalletBottomSheet({ children, className, }: WalletBottomSheetReact) { + const { isOpen, setIsOpen } = useWalletContext(); const { address } = useAccount(); const childrenArray = useMemo(() => { @@ -27,14 +29,24 @@ export function WalletBottomSheet({ } return ( -
+ {isOpen && ( +
setIsOpen(false)} // Close drawer on overlay click + /> )} - > - {childrenArray} -
+
+ {childrenArray} +
+ ); } From 9aeeb64e152d8722a7dfde7a1c741405d5f3a5d0 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 17:00:44 -0700 Subject: [PATCH 06/21] create wallet with mobile drawer component --- src/wallet/components/Wallet.tsx | 8 +-- .../components/WalletWithMobileDrawer.tsx | 51 +++++++++++++++++++ src/wallet/index.ts | 3 ++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/wallet/components/WalletWithMobileDrawer.tsx diff --git a/src/wallet/components/Wallet.tsx b/src/wallet/components/Wallet.tsx index 7ee8710440..9fa2176b51 100644 --- a/src/wallet/components/Wallet.tsx +++ b/src/wallet/components/Wallet.tsx @@ -2,18 +2,18 @@ import { Children, useEffect, useMemo, useRef } from 'react'; import { findComponent } from '../../internal/utils/findComponent'; import type { WalletReact } from '../types'; import { ConnectWallet } from './ConnectWallet'; -import { WalletMenu } from './WalletMenu'; +import { WalletDropdown } from './WalletDropdown'; import { WalletProvider, useWalletContext } from './WalletProvider'; const WalletContent = ({ children }: WalletReact) => { const { isOpen, setIsOpen } = useWalletContext(); const walletContainerRef = useRef(null); - const { menu, connect } = useMemo(() => { + const { connect, dropdown } = useMemo(() => { const childrenArray = Children.toArray(children); return { connect: childrenArray.find(findComponent(ConnectWallet)), - menu: childrenArray.find(findComponent(WalletMenu)), + dropdown: childrenArray.find(findComponent(WalletDropdown)), }; }, [children]); @@ -37,7 +37,7 @@ const WalletContent = ({ children }: WalletReact) => { return (
{connect} - {isOpen && menu} + {isOpen && dropdown}
); }; diff --git a/src/wallet/components/WalletWithMobileDrawer.tsx b/src/wallet/components/WalletWithMobileDrawer.tsx new file mode 100644 index 0000000000..b201cb6c36 --- /dev/null +++ b/src/wallet/components/WalletWithMobileDrawer.tsx @@ -0,0 +1,51 @@ +import { Children, useEffect, useMemo, useRef } from 'react'; +import { findComponent } from '../../internal/utils/findComponent'; +import type { WalletReact } from '../types'; +import { ConnectWallet } from './ConnectWallet'; +import { WalletMenu } from './WalletMenu'; +import { WalletProvider, useWalletContext } from './WalletProvider'; + +const WalletContent = ({ children }: WalletReact) => { + const { isOpen, setIsOpen } = useWalletContext(); + const walletContainerRef = useRef(null); + + const { menu, connect } = useMemo(() => { + const childrenArray = Children.toArray(children); + return { + connect: childrenArray.find(findComponent(ConnectWallet)), + menu: childrenArray.find(findComponent(WalletMenu)), + }; + }, [children]); + + // Handle clicking outside the wallet component to close the dropdown. + useEffect(() => { + const handleClickOutsideComponent = (event: MouseEvent) => { + if ( + walletContainerRef.current && + !walletContainerRef.current.contains(event.target as Node) && + isOpen + ) { + setIsOpen(false); + } + }; + + document.addEventListener('click', handleClickOutsideComponent); + return () => + document.removeEventListener('click', handleClickOutsideComponent); + }, [isOpen, setIsOpen]); + + return ( +
+ {connect} + {isOpen && menu} +
+ ); +}; + +export const WalletWithMobileDrawer = ({ children }: WalletReact) => { + return ( + + {children} + + ); +}; diff --git a/src/wallet/index.ts b/src/wallet/index.ts index f26161188d..3e546e1ac1 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -2,10 +2,13 @@ export { ConnectWallet } from './components/ConnectWallet'; export { Wallet } from './components/Wallet'; export { WalletDropdown } from './components/WalletDropdown'; +export { WalletMenu } from './components/WalletMenu'; +export { WalletBottomSheet } from './components/WalletBottomSheet'; export { WalletDropdownBaseName } from './components/WalletDropdownBaseName'; export { WalletDropdownDisconnect } from './components/WalletDropdownDisconnect'; export { WalletDropdownFundLink } from './components/WalletDropdownFundLink'; export { WalletDropdownLink } from './components/WalletDropdownLink'; +export { WalletWithMobileDrawer } from './components/WalletWithMobileDrawer'; export { isValidAAEntrypoint } from './utils/isValidAAEntrypoint'; export { isWalletACoinbaseSmartWallet } from './utils/isWalletACoinbaseSmartWallet'; export type { From dbe682324a5fbd6a375f327cc831cfd462d59750 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 17:02:46 -0700 Subject: [PATCH 07/21] remove console.log --- src/wallet/components/WalletBottomSheet.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index a978082530..2efe85ee70 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -22,8 +22,6 @@ export function WalletBottomSheet({ }); }, [children, address]); - console.log({ address }); - if (!address) { return null; } From 3d0d6bae9076e7d6c639943c81388d73475f2b5b Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 20:00:03 -0700 Subject: [PATCH 08/21] fix lint --- src/useBreakpoints.ts | 2 +- src/wallet/components/WalletBottomSheet.tsx | 31 +++++++++++++++++---- src/wallet/components/WalletMenu.tsx | 4 +-- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/useBreakpoints.ts b/src/useBreakpoints.ts index ea995d33fc..9e61017360 100644 --- a/src/useBreakpoints.ts +++ b/src/useBreakpoints.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; // tailwind breakpoints const breakpoints = { diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index 2efe85ee70..23e94855ff 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -1,4 +1,10 @@ -import { Children, cloneElement, isValidElement, useMemo } from 'react'; +import { + Children, + cloneElement, + isValidElement, + useCallback, + useMemo, +} from 'react'; import { useAccount } from 'wagmi'; import { Identity } from '../../identity/components/Identity'; import { background, cn } from '../../styles/theme'; @@ -22,6 +28,19 @@ export function WalletBottomSheet({ }); }, [children, address]); + const handleOverlayClick = useCallback(() => { + setIsOpen(false); + }, []); + + const handleEscKeyPress = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Escape') { + setIsOpen(false); + } + }, + [], + ); + if (!address) { return null; } @@ -30,15 +49,17 @@ export function WalletBottomSheet({ <> {isOpen && (
setIsOpen(false)} // Close drawer on overlay click + className="fixed inset-0 z-40 bg-black bg-opacity-20" + onClick={handleOverlayClick} + onKeyDown={handleEscKeyPress} + tabIndex={0} /> )}
Date: Tue, 13 Aug 2024 20:13:46 -0700 Subject: [PATCH 09/21] add test --- src/useBreakpoints.test.ts | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/useBreakpoints.test.ts diff --git a/src/useBreakpoints.test.ts b/src/useBreakpoints.test.ts new file mode 100644 index 0000000000..0b438c2680 --- /dev/null +++ b/src/useBreakpoints.test.ts @@ -0,0 +1,47 @@ +import { renderHook, act } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { useBreakpoints } from './useBreakpoints'; + +const createMatchMediaMock = (query: string) => ({ + matches: query === '(min-width: 769px) and (max-width: 1023px)', + addEventListener: vi.fn(), + removeEventListener: vi.fn(), +}); + +const mockMatchMedia = (query: string) => createMatchMediaMock(query); + +describe('useBreakpoints', () => { + beforeEach(() => { + (window.matchMedia as jest.Mock) = mockMatchMedia; + }); + it('should set the breakpoint based on the window size', () => { + (window.matchMedia as jest.Mock) = createMatchMediaMock; + + const { result } = renderHook(() => useBreakpoints()); + + act(() => { + window.dispatchEvent(new Event('resize')); + }); + + expect(result.current).toBe('lg'); + }); + + it('should update the breakpoint on resize', () => { + (window.matchMedia as jest.Mock) = createMatchMediaMock; + + const { result } = renderHook(() => useBreakpoints()); + + (window.matchMedia as jest.Mock) = (query: string) => + ({ + matches: query === '(max-width: 640px)', + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }) as unknown as MediaQueryList; + + act(() => { + window.dispatchEvent(new Event('resize')); + }); + + expect(result.current).toBe('sm'); + }); +}); From e239e0e5e4700059b6969a2a29debb990ed30cc6 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 20:22:34 -0700 Subject: [PATCH 10/21] fix lint --- src/useBreakpoints.test.ts | 2 +- src/wallet/components/WalletBottomSheet.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/useBreakpoints.test.ts b/src/useBreakpoints.test.ts index 0b438c2680..5acf90e460 100644 --- a/src/useBreakpoints.test.ts +++ b/src/useBreakpoints.test.ts @@ -1,4 +1,4 @@ -import { renderHook, act } from '@testing-library/react'; +import { act, renderHook } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useBreakpoints } from './useBreakpoints'; diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index 23e94855ff..f7021f68cc 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -30,7 +30,7 @@ export function WalletBottomSheet({ const handleOverlayClick = useCallback(() => { setIsOpen(false); - }, []); + }, [setIsOpen]); const handleEscKeyPress = useCallback( (event: React.KeyboardEvent) => { @@ -38,7 +38,7 @@ export function WalletBottomSheet({ setIsOpen(false); } }, - [], + [setIsOpen], ); if (!address) { From b85b9e61488585d20627f5415d52a381ed7c2389 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Tue, 13 Aug 2024 20:28:18 -0700 Subject: [PATCH 11/21] fix lint --- src/wallet/components/WalletBottomSheet.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index f7021f68cc..62b4ccb61e 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -52,6 +52,7 @@ export function WalletBottomSheet({ className="fixed inset-0 z-40 bg-black bg-opacity-20" onClick={handleOverlayClick} onKeyDown={handleEscKeyPress} + role="button" tabIndex={0} /> )} From cd54cf7a0b4cf841590829789cd8aa7301f0b64a Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 14:17:46 -0700 Subject: [PATCH 12/21] add test coverage --- .../components/WalletBottomSheet.test.tsx | 104 +++++++++++++ src/wallet/components/WalletBottomSheet.tsx | 1 + src/wallet/components/WalletDropdown.tsx | 1 + src/wallet/components/WalletMenu.test.tsx | 63 ++++++++ .../WalletWithMobileDrawer.test.tsx | 138 ++++++++++++++++++ 5 files changed, 307 insertions(+) create mode 100644 src/wallet/components/WalletBottomSheet.test.tsx create mode 100644 src/wallet/components/WalletMenu.test.tsx create mode 100644 src/wallet/components/WalletWithMobileDrawer.test.tsx diff --git a/src/wallet/components/WalletBottomSheet.test.tsx b/src/wallet/components/WalletBottomSheet.test.tsx new file mode 100644 index 0000000000..af38d9e198 --- /dev/null +++ b/src/wallet/components/WalletBottomSheet.test.tsx @@ -0,0 +1,104 @@ +import '@testing-library/jest-dom'; +import { + fireEvent, + render, + renderHook, + screen, + waitFor, +} from '@testing-library/react'; +import { useAccount } from 'wagmi'; +import { Identity } from '../../identity/components/Identity'; +import { + IdentityProvider, + useIdentityContext, +} from '../../identity/components/IdentityProvider'; +import { WalletBottomSheet } from './WalletBottomSheet'; +import { useWalletContext } from './WalletProvider'; + +vi.mock('wagmi', () => ({ + useAccount: vi.fn(), +})); + +vi.mock('./WalletProvider', () => ({ + useWalletContext: vi.fn(), +})); + +vi.mock('../../identity/components/Identity', () => ({ + Identity: vi.fn(({ address, children }) => ( + {children} + )), +})); + +const useWalletContextMock = useWalletContext as vi.Mock; +const useAccountMock = useAccount as vi.Mock; + +describe('WalletBottomSheet', () => { + it('renders null when address is not provided', () => { + useWalletContextMock.mockReturnValue({ isOpen: true }); + useAccountMock.mockReturnValue({ address: null }); + + render(Test Children); + + expect(screen.queryByText('Test Children')).not.toBeInTheDocument(); + }); + + it('renders children when isOpen is true and address is provided', () => { + useWalletContextMock.mockReturnValue({ isOpen: true }); + useAccountMock.mockReturnValue({ address: '0x123' }); + + render(Test Children); + + expect(screen.getByText('Test Children')).toBeInTheDocument(); + }); + + it('injects address prop to Identity component', async () => { + const address = '0x123'; + useWalletContextMock.mockReturnValue({ isOpen: true }); + useAccountMock.mockReturnValue({ address }); + + const { result } = renderHook(() => useIdentityContext(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + await waitFor(() => { + expect(result.current.address).toEqual(address); + }); + }); + + it('does not render overlay when isOpen is false', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useWalletContextMock.mockReturnValue({ isOpen: false, setIsOpen: vi.fn() }); + + render(Content); + + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + + it('renders overlay when isOpen is true', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useWalletContextMock.mockReturnValue({ isOpen: true, setIsOpen: vi.fn() }); + + render(Content); + + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('closes the bottom sheet when the overlay is clicked', () => { + const setIsOpenMock = vi.fn(); + useAccountMock.mockReturnValue({ address: '0x123' }); + useWalletContextMock.mockReturnValue({ + isOpen: true, + setIsOpen: setIsOpenMock, + }); + + render(Content); + + fireEvent.click(screen.getByRole('button')); + + expect(setIsOpenMock).toHaveBeenCalledWith(false); + }); +}); diff --git a/src/wallet/components/WalletBottomSheet.tsx b/src/wallet/components/WalletBottomSheet.tsx index 62b4ccb61e..f200f134f4 100644 --- a/src/wallet/components/WalletBottomSheet.tsx +++ b/src/wallet/components/WalletBottomSheet.tsx @@ -64,6 +64,7 @@ export function WalletBottomSheet({ `${isOpen ? 'translate-y-0' : 'translate-y-full'}`, className, )} + data-testid="ockWalletBottomSheet" > {childrenArray}
diff --git a/src/wallet/components/WalletDropdown.tsx b/src/wallet/components/WalletDropdown.tsx index cb7910d594..6f98a6dbfe 100644 --- a/src/wallet/components/WalletDropdown.tsx +++ b/src/wallet/components/WalletDropdown.tsx @@ -28,6 +28,7 @@ export function WalletDropdown({ children, className }: WalletDropdownReact) { 'absolute right-0 z-10 mt-1 flex w-max min-w-[250px] flex-col overflow-hidden rounded-xl pb-2', className, )} + data-testid="ockWalletDropdown" > {childrenArray}
diff --git a/src/wallet/components/WalletMenu.test.tsx b/src/wallet/components/WalletMenu.test.tsx new file mode 100644 index 0000000000..4676840020 --- /dev/null +++ b/src/wallet/components/WalletMenu.test.tsx @@ -0,0 +1,63 @@ +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/react'; +import { useAccount } from 'wagmi'; +import { WalletMenu } from './WalletMenu'; +import useBreakpoints from '../../useBreakpoints'; + +vi.mock('wagmi', () => ({ + useAccount: vi.fn(), +})); + +vi.mock('../../useBreakpoints', () => ({ + default: vi.fn(), +})); + +const useAccountMock = useAccount as vi.Mock; +const useBreakpointsMock = useBreakpoints as vi.Mock; + +describe('WalletMenu', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('does not render anything if address is not available', () => { + useAccountMock.mockReturnValue({ address: null }); + useBreakpointsMock.mockReturnValue('sm'); + + render(Content); + + expect(screen.queryByText('Content')).not.toBeInTheDocument(); + }); + + it('does not render anything if breakpoint is not defined', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useBreakpointsMock.mockReturnValue(null); + + render(Content); + + expect(screen.queryByText('Content')).not.toBeInTheDocument(); + }); + + it('renders WalletBottomSheet when breakpoint is "sm"', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useBreakpointsMock.mockReturnValue('sm'); + + render(Content); + + const bottomSheet = screen.getByTestId('ockWalletBottomSheet'); + expect(bottomSheet).toBeInTheDocument(); + expect(bottomSheet).toHaveClass('bottom-sheet'); + }); + + it('renders WalletDropdown when breakpoint is not "sm"', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useBreakpointsMock.mockReturnValue('md'); + + render(Content); + + const dropdown = screen.getByTestId('ockWalletDropdown'); + + expect(dropdown).toBeInTheDocument(); + expect(dropdown).toHaveClass('dropdown'); + }); +}); diff --git a/src/wallet/components/WalletWithMobileDrawer.test.tsx b/src/wallet/components/WalletWithMobileDrawer.test.tsx new file mode 100644 index 0000000000..beb689329d --- /dev/null +++ b/src/wallet/components/WalletWithMobileDrawer.test.tsx @@ -0,0 +1,138 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { ConnectWallet } from './ConnectWallet'; +import { WalletWithMobileDrawer } from './WalletWithMobileDrawer'; +import { WalletMenu } from './WalletMenu'; +import { useWalletContext } from './WalletProvider'; +import { useAccount } from 'wagmi'; +import useBreakpoints from '../../useBreakpoints'; + +vi.mock('./WalletProvider', () => ({ + useWalletContext: vi.fn(), + WalletProvider: ({ children }) => <>{children}, +})); + +vi.mock('./ConnectWallet', () => ({ + ConnectWallet: () =>
Connect Wallet
, +})); + +vi.mock('./WalletDropdown', () => ({ + WalletDropdown: () => ( +
Wallet Dropdown
+ ), +})); + +vi.mock('wagmi', () => ({ + useAccount: vi.fn(), +})); + +vi.mock('../../useBreakpoints', () => ({ + default: vi.fn(), +})); + +const useAccountMock = useAccount as vi.Mock; +const useBreakpointsMock = useBreakpoints as vi.Mock; + +describe('WalletWithMobileDrawer Component', () => { + let mockSetIsOpen: ReturnType; + + beforeEach(() => { + mockSetIsOpen = vi.fn(); + (useWalletContext as ReturnType).mockReturnValue({ + isOpen: false, + setIsOpen: mockSetIsOpen, + }); + }); + + it('should render the Wallet component with ConnectWallet', () => { + render( + + + + , + ); + + expect(screen.getByTestId('connect-wallet')).toBeDefined(); + expect(screen.queryByTestId('wallet-dropdown')).toBeNull(); + }); + + it('should close the wallet when clicking outside', () => { + (useWalletContext as ReturnType).mockReturnValue({ + isOpen: true, + setIsOpen: mockSetIsOpen, + }); + + useAccountMock.mockReturnValue({ address: '123' }); + useBreakpointsMock.mockReturnValue('sm'); + + render( + + + + , + ); + + expect(screen.getByTestId('ockWalletBottomSheet')).toBeDefined(); + + fireEvent.click(document.body); + + expect(mockSetIsOpen).toHaveBeenCalledWith(false); + }); + + it('should not close the wallet when clicking inside', () => { + (useWalletContext as ReturnType).mockReturnValue({ + isOpen: true, + setIsOpen: mockSetIsOpen, + }); + + useAccountMock.mockReturnValue({ address: '123' }); + useBreakpointsMock.mockReturnValue('sm'); + + render( + + + + , + ); + + const walletMenu = screen.getByTestId('ockWalletBottomSheet'); + expect(walletMenu).toBeDefined(); + + fireEvent.click(walletMenu); + + expect(mockSetIsOpen).not.toHaveBeenCalled(); + }); + + it('should not trigger click handler when wallet is closed', () => { + render( + + + + , + ); + + expect(screen.queryByTestId('wallet-dropdown')).toBeNull(); + + fireEvent.click(document.body); + + expect(mockSetIsOpen).not.toHaveBeenCalled(); + }); + + it('should remove event listener on unmount', () => { + const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener'); + + const { unmount } = render( + + + + , + ); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith( + 'click', + expect.any(Function), + ); + }); +}); From edb78211d2d807ee390ad35ec8c6544562a8361f Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 14:19:41 -0700 Subject: [PATCH 13/21] add test coverage --- .../components/WalletBottomSheet.test.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/wallet/components/WalletBottomSheet.test.tsx b/src/wallet/components/WalletBottomSheet.test.tsx index af38d9e198..75d0eb8145 100644 --- a/src/wallet/components/WalletBottomSheet.test.tsx +++ b/src/wallet/components/WalletBottomSheet.test.tsx @@ -101,4 +101,22 @@ describe('WalletBottomSheet', () => { expect(setIsOpenMock).toHaveBeenCalledWith(false); }); + + it('closes the bottom sheet when Escape key is pressed', () => { + const setIsOpenMock = vi.fn(); + useAccountMock.mockReturnValue({ address: '0x123' }); + useWalletContextMock.mockReturnValue({ + isOpen: true, + setIsOpen: setIsOpenMock, + }); + + render(Content); + + fireEvent.keyDown(screen.getByRole('button'), { + key: 'Escape', + code: 'Escape', + }); + + expect(setIsOpenMock).toHaveBeenCalledWith(false); + }); }); From d6bd7cf5fe7a78b71cedb563db9cb71be6e6ae56 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 14:30:05 -0700 Subject: [PATCH 14/21] fix imports --- src/wallet/components/WalletMenu.test.tsx | 2 +- src/wallet/components/WalletWithMobileDrawer.test.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/components/WalletMenu.test.tsx b/src/wallet/components/WalletMenu.test.tsx index 4676840020..3aef93367d 100644 --- a/src/wallet/components/WalletMenu.test.tsx +++ b/src/wallet/components/WalletMenu.test.tsx @@ -1,8 +1,8 @@ import '@testing-library/jest-dom'; import { render, screen } from '@testing-library/react'; import { useAccount } from 'wagmi'; -import { WalletMenu } from './WalletMenu'; import useBreakpoints from '../../useBreakpoints'; +import { WalletMenu } from './WalletMenu'; vi.mock('wagmi', () => ({ useAccount: vi.fn(), diff --git a/src/wallet/components/WalletWithMobileDrawer.test.tsx b/src/wallet/components/WalletWithMobileDrawer.test.tsx index beb689329d..eaf18e9961 100644 --- a/src/wallet/components/WalletWithMobileDrawer.test.tsx +++ b/src/wallet/components/WalletWithMobileDrawer.test.tsx @@ -1,11 +1,11 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { useAccount } from 'wagmi'; +import useBreakpoints from '../../useBreakpoints'; import { ConnectWallet } from './ConnectWallet'; -import { WalletWithMobileDrawer } from './WalletWithMobileDrawer'; import { WalletMenu } from './WalletMenu'; import { useWalletContext } from './WalletProvider'; -import { useAccount } from 'wagmi'; -import useBreakpoints from '../../useBreakpoints'; +import { WalletWithMobileDrawer } from './WalletWithMobileDrawer'; vi.mock('./WalletProvider', () => ({ useWalletContext: vi.fn(), From 4d8a3f144c725b938db36e8dbd60af47a87b5451 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 14:36:12 -0700 Subject: [PATCH 15/21] update default breakpoint value --- src/useBreakpoints.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useBreakpoints.ts b/src/useBreakpoints.ts index 9e61017360..56a3d4853e 100644 --- a/src/useBreakpoints.ts +++ b/src/useBreakpoints.ts @@ -25,7 +25,7 @@ export function useBreakpoints() { return key; } } - return undefined; + return 'md'; }; // set initial breakpoint From 4a87cd579b1b59bb86ac0f5ca009f8cc7c636a97 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 20:02:13 -0700 Subject: [PATCH 16/21] refactor and remove unused component --- src/wallet/components/WalletDropdown.test.tsx | 79 +++++----- src/wallet/components/WalletDropdown.tsx | 13 ++ src/wallet/components/WalletMenu.test.tsx | 63 -------- src/wallet/components/WalletMenu.tsx | 25 ---- .../WalletWithMobileDrawer.test.tsx | 138 ------------------ .../components/WalletWithMobileDrawer.tsx | 51 ------- src/wallet/index.ts | 2 - src/wallet/types.ts | 2 +- 8 files changed, 53 insertions(+), 320 deletions(-) delete mode 100644 src/wallet/components/WalletMenu.test.tsx delete mode 100644 src/wallet/components/WalletMenu.tsx delete mode 100644 src/wallet/components/WalletWithMobileDrawer.test.tsx delete mode 100644 src/wallet/components/WalletWithMobileDrawer.tsx diff --git a/src/wallet/components/WalletDropdown.test.tsx b/src/wallet/components/WalletDropdown.test.tsx index cbc9030db8..7554aee856 100644 --- a/src/wallet/components/WalletDropdown.test.tsx +++ b/src/wallet/components/WalletDropdown.test.tsx @@ -1,65 +1,64 @@ import '@testing-library/jest-dom'; -import { render, renderHook, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { useAccount } from 'wagmi'; -import { Identity } from '../../identity/components/Identity'; -import { - IdentityProvider, - useIdentityContext, -} from '../../identity/components/IdentityProvider'; +import useBreakpoints from '../../useBreakpoints'; import { WalletDropdown } from './WalletDropdown'; -import { useWalletContext } from './WalletProvider'; vi.mock('wagmi', () => ({ useAccount: vi.fn(), })); -vi.mock('./WalletProvider', () => ({ - useWalletContext: vi.fn(), +vi.mock('../../useBreakpoints', () => ({ + default: vi.fn(), })); -vi.mock('../../identity/components/Identity', () => ({ - Identity: vi.fn(({ address, children }) => ( - {children} - )), -})); - -const useWalletContextMock = useWalletContext as vi.Mock; const useAccountMock = useAccount as vi.Mock; +const useBreakpointsMock = useBreakpoints as vi.Mock; describe('WalletDropdown', () => { - it('renders null when address is not provided', () => { - useWalletContextMock.mockReturnValue({ isOpen: true }); + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('does not render anything if address is not available', () => { useAccountMock.mockReturnValue({ address: null }); + useBreakpointsMock.mockReturnValue('sm'); + + render(Content); + + expect(screen.queryByText('Content')).not.toBeInTheDocument(); + }); - render(Test Children); + it('does not render anything if breakpoint is not defined', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useBreakpointsMock.mockReturnValue(null); + + render(Content); - expect(screen.queryByText('Test Children')).not.toBeInTheDocument(); + expect(screen.queryByText('Content')).not.toBeInTheDocument(); }); - it('renders children when isOpen is true and address is provided', () => { - useWalletContextMock.mockReturnValue({ isOpen: true }); + it('renders WalletBottomSheet when breakpoint is "sm"', () => { useAccountMock.mockReturnValue({ address: '0x123' }); + useBreakpointsMock.mockReturnValue('sm'); - render(Test Children); + render(Content); - expect(screen.getByText('Test Children')).toBeInTheDocument(); + const bottomSheet = screen.getByTestId('ockWalletBottomSheet'); + + expect(bottomSheet).toBeInTheDocument(); + expect(bottomSheet).toHaveClass('bottom-sheet'); }); - it('injects address prop to Identity component', async () => { - const address = '0x123'; - useWalletContextMock.mockReturnValue({ isOpen: true }); - useAccountMock.mockReturnValue({ address }); - - const { result } = renderHook(() => useIdentityContext(), { - wrapper: ({ children }) => ( - - {children} - - ), - }); - - await waitFor(() => { - expect(result.current.address).toEqual(address); - }); + it('renders WalletDropdown when breakpoint is not "sm"', () => { + useAccountMock.mockReturnValue({ address: '0x123' }); + useBreakpointsMock.mockReturnValue('md'); + + render(Content); + + const dropdown = screen.getByTestId('ockWalletDropdown'); + + expect(dropdown).toBeInTheDocument(); + expect(dropdown).toHaveClass('dropdown'); }); }); diff --git a/src/wallet/components/WalletDropdown.tsx b/src/wallet/components/WalletDropdown.tsx index 6f98a6dbfe..6fe56bd2a0 100644 --- a/src/wallet/components/WalletDropdown.tsx +++ b/src/wallet/components/WalletDropdown.tsx @@ -1,10 +1,13 @@ import { Children, cloneElement, isValidElement, useMemo } from 'react'; import { useAccount } from 'wagmi'; +import useBreakpoints from '../../useBreakpoints'; import { Identity } from '../../identity/components/Identity'; import { background, cn } from '../../styles/theme'; import type { WalletDropdownReact } from '../types'; +import { WalletBottomSheet } from './WalletBottomSheet'; export function WalletDropdown({ children, className }: WalletDropdownReact) { + const breakpoint = useBreakpoints(); const { address } = useAccount(); const childrenArray = useMemo(() => { @@ -21,6 +24,16 @@ export function WalletDropdown({ children, className }: WalletDropdownReact) { return null; } + if (!breakpoint) { + return null; + } + + if (breakpoint === 'sm') { + return ( + {children} + ); + } + return (
({ - useAccount: vi.fn(), -})); - -vi.mock('../../useBreakpoints', () => ({ - default: vi.fn(), -})); - -const useAccountMock = useAccount as vi.Mock; -const useBreakpointsMock = useBreakpoints as vi.Mock; - -describe('WalletMenu', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('does not render anything if address is not available', () => { - useAccountMock.mockReturnValue({ address: null }); - useBreakpointsMock.mockReturnValue('sm'); - - render(Content); - - expect(screen.queryByText('Content')).not.toBeInTheDocument(); - }); - - it('does not render anything if breakpoint is not defined', () => { - useAccountMock.mockReturnValue({ address: '0x123' }); - useBreakpointsMock.mockReturnValue(null); - - render(Content); - - expect(screen.queryByText('Content')).not.toBeInTheDocument(); - }); - - it('renders WalletBottomSheet when breakpoint is "sm"', () => { - useAccountMock.mockReturnValue({ address: '0x123' }); - useBreakpointsMock.mockReturnValue('sm'); - - render(Content); - - const bottomSheet = screen.getByTestId('ockWalletBottomSheet'); - expect(bottomSheet).toBeInTheDocument(); - expect(bottomSheet).toHaveClass('bottom-sheet'); - }); - - it('renders WalletDropdown when breakpoint is not "sm"', () => { - useAccountMock.mockReturnValue({ address: '0x123' }); - useBreakpointsMock.mockReturnValue('md'); - - render(Content); - - const dropdown = screen.getByTestId('ockWalletDropdown'); - - expect(dropdown).toBeInTheDocument(); - expect(dropdown).toHaveClass('dropdown'); - }); -}); diff --git a/src/wallet/components/WalletMenu.tsx b/src/wallet/components/WalletMenu.tsx deleted file mode 100644 index aca6090cc1..0000000000 --- a/src/wallet/components/WalletMenu.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useAccount } from 'wagmi'; -import useBreakpoints from '../../useBreakpoints'; -import type { WalletMenuReact } from '../types'; -import { WalletBottomSheet } from './WalletBottomSheet'; -import { WalletDropdown } from './WalletDropdown'; - -export function WalletMenu({ children, className }: WalletMenuReact) { - const breakpoint = useBreakpoints(); - const { address } = useAccount(); - if (!address) { - return null; - } - - if (!breakpoint) { - return null; - } - - if (breakpoint === 'sm') { - return ( - {children} - ); - } - - return {children}; -} diff --git a/src/wallet/components/WalletWithMobileDrawer.test.tsx b/src/wallet/components/WalletWithMobileDrawer.test.tsx deleted file mode 100644 index eaf18e9961..0000000000 --- a/src/wallet/components/WalletWithMobileDrawer.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { useAccount } from 'wagmi'; -import useBreakpoints from '../../useBreakpoints'; -import { ConnectWallet } from './ConnectWallet'; -import { WalletMenu } from './WalletMenu'; -import { useWalletContext } from './WalletProvider'; -import { WalletWithMobileDrawer } from './WalletWithMobileDrawer'; - -vi.mock('./WalletProvider', () => ({ - useWalletContext: vi.fn(), - WalletProvider: ({ children }) => <>{children}, -})); - -vi.mock('./ConnectWallet', () => ({ - ConnectWallet: () =>
Connect Wallet
, -})); - -vi.mock('./WalletDropdown', () => ({ - WalletDropdown: () => ( -
Wallet Dropdown
- ), -})); - -vi.mock('wagmi', () => ({ - useAccount: vi.fn(), -})); - -vi.mock('../../useBreakpoints', () => ({ - default: vi.fn(), -})); - -const useAccountMock = useAccount as vi.Mock; -const useBreakpointsMock = useBreakpoints as vi.Mock; - -describe('WalletWithMobileDrawer Component', () => { - let mockSetIsOpen: ReturnType; - - beforeEach(() => { - mockSetIsOpen = vi.fn(); - (useWalletContext as ReturnType).mockReturnValue({ - isOpen: false, - setIsOpen: mockSetIsOpen, - }); - }); - - it('should render the Wallet component with ConnectWallet', () => { - render( - - - - , - ); - - expect(screen.getByTestId('connect-wallet')).toBeDefined(); - expect(screen.queryByTestId('wallet-dropdown')).toBeNull(); - }); - - it('should close the wallet when clicking outside', () => { - (useWalletContext as ReturnType).mockReturnValue({ - isOpen: true, - setIsOpen: mockSetIsOpen, - }); - - useAccountMock.mockReturnValue({ address: '123' }); - useBreakpointsMock.mockReturnValue('sm'); - - render( - - - - , - ); - - expect(screen.getByTestId('ockWalletBottomSheet')).toBeDefined(); - - fireEvent.click(document.body); - - expect(mockSetIsOpen).toHaveBeenCalledWith(false); - }); - - it('should not close the wallet when clicking inside', () => { - (useWalletContext as ReturnType).mockReturnValue({ - isOpen: true, - setIsOpen: mockSetIsOpen, - }); - - useAccountMock.mockReturnValue({ address: '123' }); - useBreakpointsMock.mockReturnValue('sm'); - - render( - - - - , - ); - - const walletMenu = screen.getByTestId('ockWalletBottomSheet'); - expect(walletMenu).toBeDefined(); - - fireEvent.click(walletMenu); - - expect(mockSetIsOpen).not.toHaveBeenCalled(); - }); - - it('should not trigger click handler when wallet is closed', () => { - render( - - - - , - ); - - expect(screen.queryByTestId('wallet-dropdown')).toBeNull(); - - fireEvent.click(document.body); - - expect(mockSetIsOpen).not.toHaveBeenCalled(); - }); - - it('should remove event listener on unmount', () => { - const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener'); - - const { unmount } = render( - - - - , - ); - - unmount(); - - expect(removeEventListenerSpy).toHaveBeenCalledWith( - 'click', - expect.any(Function), - ); - }); -}); diff --git a/src/wallet/components/WalletWithMobileDrawer.tsx b/src/wallet/components/WalletWithMobileDrawer.tsx deleted file mode 100644 index b201cb6c36..0000000000 --- a/src/wallet/components/WalletWithMobileDrawer.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Children, useEffect, useMemo, useRef } from 'react'; -import { findComponent } from '../../internal/utils/findComponent'; -import type { WalletReact } from '../types'; -import { ConnectWallet } from './ConnectWallet'; -import { WalletMenu } from './WalletMenu'; -import { WalletProvider, useWalletContext } from './WalletProvider'; - -const WalletContent = ({ children }: WalletReact) => { - const { isOpen, setIsOpen } = useWalletContext(); - const walletContainerRef = useRef(null); - - const { menu, connect } = useMemo(() => { - const childrenArray = Children.toArray(children); - return { - connect: childrenArray.find(findComponent(ConnectWallet)), - menu: childrenArray.find(findComponent(WalletMenu)), - }; - }, [children]); - - // Handle clicking outside the wallet component to close the dropdown. - useEffect(() => { - const handleClickOutsideComponent = (event: MouseEvent) => { - if ( - walletContainerRef.current && - !walletContainerRef.current.contains(event.target as Node) && - isOpen - ) { - setIsOpen(false); - } - }; - - document.addEventListener('click', handleClickOutsideComponent); - return () => - document.removeEventListener('click', handleClickOutsideComponent); - }, [isOpen, setIsOpen]); - - return ( -
- {connect} - {isOpen && menu} -
- ); -}; - -export const WalletWithMobileDrawer = ({ children }: WalletReact) => { - return ( - - {children} - - ); -}; diff --git a/src/wallet/index.ts b/src/wallet/index.ts index 3e546e1ac1..3f7fcafd43 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -2,13 +2,11 @@ export { ConnectWallet } from './components/ConnectWallet'; export { Wallet } from './components/Wallet'; export { WalletDropdown } from './components/WalletDropdown'; -export { WalletMenu } from './components/WalletMenu'; export { WalletBottomSheet } from './components/WalletBottomSheet'; export { WalletDropdownBaseName } from './components/WalletDropdownBaseName'; export { WalletDropdownDisconnect } from './components/WalletDropdownDisconnect'; export { WalletDropdownFundLink } from './components/WalletDropdownFundLink'; export { WalletDropdownLink } from './components/WalletDropdownLink'; -export { WalletWithMobileDrawer } from './components/WalletWithMobileDrawer'; export { isValidAAEntrypoint } from './utils/isValidAAEntrypoint'; export { isWalletACoinbaseSmartWallet } from './utils/isWalletACoinbaseSmartWallet'; export type { diff --git a/src/wallet/types.ts b/src/wallet/types.ts index 82ca5561ec..7ec9b092c8 100644 --- a/src/wallet/types.ts +++ b/src/wallet/types.ts @@ -138,7 +138,6 @@ export type WalletMenuReact = { className?: string; // Optional className override for top div element }; - export type WindowSizes = Record< 'sm' | 'md' | 'lg', { @@ -146,3 +145,4 @@ export type WindowSizes = Record< height: string; } >; + From e2fe05f7668a225ff887acba7179d210b48694be Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 20:10:15 -0700 Subject: [PATCH 17/21] address pr comments --- src/useBreakpoints.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/useBreakpoints.ts b/src/useBreakpoints.ts index 56a3d4853e..daaab84522 100644 --- a/src/useBreakpoints.ts +++ b/src/useBreakpoints.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; // tailwind breakpoints -const breakpoints = { +const BREAKPOINTS = { sm: '(max-width: 640px)', md: '(min-width: 641px) and (max-width: 768px)', lg: '(min-width: 769px) and (max-width: 1023px)', @@ -19,7 +19,7 @@ export function useBreakpoints() { useEffect(() => { // get the current breakpoint based on media queries const getCurrentBreakpoint = () => { - const entries = Object.entries(breakpoints) as [string, string][]; + const entries = Object.entries(BREAKPOINTS) as [string, string][]; for (const [key, query] of entries) { if (window.matchMedia(query).matches) { return key; From 0d7a532a5e3565448537151b2be0d81bf2daefce Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 20:11:23 -0700 Subject: [PATCH 18/21] remove unused type --- src/wallet/types.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wallet/types.ts b/src/wallet/types.ts index 7ec9b092c8..c89e82fefe 100644 --- a/src/wallet/types.ts +++ b/src/wallet/types.ts @@ -130,14 +130,6 @@ export type WalletDropdownLinkReact = { target?: string; }; -/** - * Note: exported as public Type - */ -export type WalletMenuReact = { - children: React.ReactNode; - className?: string; // Optional className override for top div element -}; - export type WindowSizes = Record< 'sm' | 'md' | 'lg', { From ae451ce9bc9c8c0927f0735cdc32eabd83b87a40 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 20:42:27 -0700 Subject: [PATCH 19/21] fix tests and lint --- src/useBreakpoints.test.ts | 18 +++++-- src/wallet/components/WalletDropdown.test.tsx | 50 ++++++++++++++++--- src/wallet/components/WalletDropdown.tsx | 2 +- src/wallet/types.ts | 1 - 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/useBreakpoints.test.ts b/src/useBreakpoints.test.ts index 5acf90e460..93d3d10810 100644 --- a/src/useBreakpoints.test.ts +++ b/src/useBreakpoints.test.ts @@ -8,12 +8,7 @@ const createMatchMediaMock = (query: string) => ({ removeEventListener: vi.fn(), }); -const mockMatchMedia = (query: string) => createMatchMediaMock(query); - describe('useBreakpoints', () => { - beforeEach(() => { - (window.matchMedia as jest.Mock) = mockMatchMedia; - }); it('should set the breakpoint based on the window size', () => { (window.matchMedia as jest.Mock) = createMatchMediaMock; @@ -44,4 +39,17 @@ describe('useBreakpoints', () => { expect(result.current).toBe('sm'); }); + + it('should return md when no breakpoints match', () => { + (window.matchMedia as jest.Mock) = (query: string) => + ({ + matches: false, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }) as unknown as MediaQueryList; + + const { result } = renderHook(() => useBreakpoints()); + + expect(result.current).toBe('md'); + }); }); diff --git a/src/wallet/components/WalletDropdown.test.tsx b/src/wallet/components/WalletDropdown.test.tsx index 7554aee856..a0fbf495bf 100644 --- a/src/wallet/components/WalletDropdown.test.tsx +++ b/src/wallet/components/WalletDropdown.test.tsx @@ -1,17 +1,35 @@ import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; +import { render, renderHook, screen, waitFor } from '@testing-library/react'; import { useAccount } from 'wagmi'; +import { Identity } from '../../identity/components/Identity'; +import { + IdentityProvider, + useIdentityContext, +} from '../../identity/components/IdentityProvider'; import useBreakpoints from '../../useBreakpoints'; import { WalletDropdown } from './WalletDropdown'; +import { useWalletContext } from './WalletProvider'; vi.mock('wagmi', () => ({ useAccount: vi.fn(), })); +vi.mock('./WalletProvider', () => ({ + useWalletContext: vi.fn(), +})); + vi.mock('../../useBreakpoints', () => ({ default: vi.fn(), })); +vi.mock('../../identity/components/Identity', () => ({ + Identity: vi.fn(({ address, children }) => ( + {children} + )), +})); + +const useWalletContextMock = useWalletContext as vi.Mock; + const useAccountMock = useAccount as vi.Mock; const useBreakpointsMock = useBreakpoints as vi.Mock; @@ -20,13 +38,11 @@ describe('WalletDropdown', () => { vi.clearAllMocks(); }); - it('does not render anything if address is not available', () => { - useAccountMock.mockReturnValue({ address: null }); - useBreakpointsMock.mockReturnValue('sm'); - - render(Content); - - expect(screen.queryByText('Content')).not.toBeInTheDocument(); + it('renders null when address is not provided', () => { + useAccountMock.mockReturnValue({ address: undefined }); + useWalletContextMock.mockReturnValue({ isOpen: true }); + render(Test Children); + expect(screen.queryByText('Test Children')).not.toBeInTheDocument(); }); it('does not render anything if breakpoint is not defined', () => { @@ -61,4 +77,22 @@ describe('WalletDropdown', () => { expect(dropdown).toBeInTheDocument(); expect(dropdown).toHaveClass('dropdown'); }); + + it('injects address prop to Identity component', async () => { + const address = '0x123'; + useWalletContextMock.mockReturnValue({ isOpen: true }); + useAccountMock.mockReturnValue({ address }); + + const { result } = renderHook(() => useIdentityContext(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + await waitFor(() => { + expect(result.current.address).toEqual(address); + }); + }); }); diff --git a/src/wallet/components/WalletDropdown.tsx b/src/wallet/components/WalletDropdown.tsx index 6fe56bd2a0..f89ee3b71c 100644 --- a/src/wallet/components/WalletDropdown.tsx +++ b/src/wallet/components/WalletDropdown.tsx @@ -1,8 +1,8 @@ import { Children, cloneElement, isValidElement, useMemo } from 'react'; import { useAccount } from 'wagmi'; -import useBreakpoints from '../../useBreakpoints'; import { Identity } from '../../identity/components/Identity'; import { background, cn } from '../../styles/theme'; +import useBreakpoints from '../../useBreakpoints'; import type { WalletDropdownReact } from '../types'; import { WalletBottomSheet } from './WalletBottomSheet'; diff --git a/src/wallet/types.ts b/src/wallet/types.ts index c89e82fefe..76ef8764a3 100644 --- a/src/wallet/types.ts +++ b/src/wallet/types.ts @@ -137,4 +137,3 @@ export type WindowSizes = Record< height: string; } >; - From f4da50807f74c110440dba0531a181cc8c38e370 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 20:50:40 -0700 Subject: [PATCH 20/21] fix lint --- src/useBreakpoints.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/useBreakpoints.test.ts b/src/useBreakpoints.test.ts index 93d3d10810..b8a3731b6e 100644 --- a/src/useBreakpoints.test.ts +++ b/src/useBreakpoints.test.ts @@ -1,5 +1,5 @@ import { act, renderHook } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { useBreakpoints } from './useBreakpoints'; const createMatchMediaMock = (query: string) => ({ @@ -41,7 +41,7 @@ describe('useBreakpoints', () => { }); it('should return md when no breakpoints match', () => { - (window.matchMedia as jest.Mock) = (query: string) => + (window.matchMedia as jest.Mock) = (_query: string) => ({ matches: false, addEventListener: vi.fn(), From 1cff3b8f307a282dfe5df68b0a83503b9e3a91e0 Mon Sep 17 00:00:00 2001 From: Alissa Crane Date: Wed, 14 Aug 2024 22:04:03 -0700 Subject: [PATCH 21/21] remove unused export --- src/wallet/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wallet/index.ts b/src/wallet/index.ts index 3f7fcafd43..f26161188d 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -2,7 +2,6 @@ export { ConnectWallet } from './components/ConnectWallet'; export { Wallet } from './components/Wallet'; export { WalletDropdown } from './components/WalletDropdown'; -export { WalletBottomSheet } from './components/WalletBottomSheet'; export { WalletDropdownBaseName } from './components/WalletDropdownBaseName'; export { WalletDropdownDisconnect } from './components/WalletDropdownDisconnect'; export { WalletDropdownFundLink } from './components/WalletDropdownFundLink';