From 56fb817b61474989f433590932eacdfefc4a3835 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Fri, 13 Sep 2024 19:26:36 +0700 Subject: [PATCH 01/29] Update react-query refetchOnMount option to always --- .../profile/[name]/registration/useMoonpayRegistration.ts | 2 +- src/hooks/chain/useBlockTimestamp.ts | 2 +- src/utils/query/reactQuery.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts b/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts index 769876eae..0329264ad 100644 --- a/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts +++ b/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts @@ -81,7 +81,7 @@ export const useMoonpayRegistration = ( return result || {} }, refetchOnWindowFocus: true, - refetchOnMount: true, + refetchOnMount: 'always', refetchInterval: 1000, refetchIntervalInBackground: true, enabled: !!currentExternalTransactionId && !isCompleted, diff --git a/src/hooks/chain/useBlockTimestamp.ts b/src/hooks/chain/useBlockTimestamp.ts index db8bd192e..865dde78d 100644 --- a/src/hooks/chain/useBlockTimestamp.ts +++ b/src/hooks/chain/useBlockTimestamp.ts @@ -12,7 +12,7 @@ export const useBlockTimestamp = ({ enabled = true }: UseBlockTimestampParameter blockTag: 'latest', query: { enabled, - refetchOnMount: true, + refetchOnMount: 'always', refetchInterval: 1000 * 60 * 5 /* 5 minutes */, staleTime: 1000 * 60 /* 1 minute */, select: (b) => b.timestamp * 1000n, diff --git a/src/utils/query/reactQuery.ts b/src/utils/query/reactQuery.ts index d297288c2..7dd6fef80 100644 --- a/src/utils/query/reactQuery.ts +++ b/src/utils/query/reactQuery.ts @@ -4,8 +4,8 @@ import { hashFn } from 'wagmi/query' export const queryClient = new QueryClient({ defaultOptions: { queries: { - refetchOnWindowFocus: false, - refetchOnMount: true, + refetchOnWindowFocus: true, + refetchOnMount: 'always', staleTime: 1_000 * 12, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: hashFn, @@ -21,7 +21,7 @@ export const refetchOptions: DefaultOptions = { meta: { isRefetchQuery: true, }, - refetchOnMount: true, + refetchOnMount: 'always', queryKeyHashFn: hashFn, }, } From 61ba6ce7091de9f89e22ca9842f07b963d4093fd Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 16 Sep 2024 13:30:29 +0800 Subject: [PATCH 02/29] add ens credentials check --- e2e/specs/stateless/verifications.spec.ts | 73 +++++++++++++ .../pages/profile/[name]/tabs/ProfileTab.tsx | 2 + .../useVerifiedRecords.test.ts | 6 +- .../useVerifiedRecords/useVerifiedRecords.ts | 9 +- .../parseVerificationData.ts | 24 +++-- .../parseDentityVerifiablePresentation.ts | 27 +++++ .../parseOpenIdVerifiablePresentation.test.ts | 4 +- .../parseOpenIdVerifiablePresentation.ts | 23 ++-- .../utils/parseVerifiedCredential.test.ts | 28 +++-- .../utils/parseVerifiedCredential.ts | 101 ++++++++++-------- 10 files changed, 220 insertions(+), 77 deletions(-) create mode 100644 src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts diff --git a/e2e/specs/stateless/verifications.spec.ts b/e2e/specs/stateless/verifications.spec.ts index 706bb1848..1ca030b7d 100644 --- a/e2e/specs/stateless/verifications.spec.ts +++ b/e2e/specs/stateless/verifications.spec.ts @@ -194,6 +194,79 @@ test.describe('Verified records', () => { await expect(profilePage.record('verification', 'dentity')).toBeVisible() await expect(profilePage.record('verification', 'dentity')).toBeVisible() }) + + test('Should not show badges if records match but ens credential fails', async ({ + page, + accounts, + makePageObject, + makeName, + }) => { + const name = await makeName({ + label: 'dentity', + type: 'wrapped', + owner: 'user', + records: { + texts: [ + { + key: 'com.twitter', + value: '@name2', + }, + { + key: 'org.telegram', + value: 'name2', + }, + { + key: 'com.discord', + value: 'name2', + }, + { + key: 'com.github', + value: 'name2', + }, + { + key: VERIFICATION_RECORD_KEY, + value: JSON.stringify([ + `${DENTITY_VPTOKEN_ENDPOINT}?name=name.eth&federated_token=federated_token`, + ]), + }, + { + key: 'com.twitter', + value: '@name2', + }, + ], + }, + }) + + const profilePage = makePageObject('ProfilePage') + + await page.route(`${DENTITY_VPTOKEN_ENDPOINT}*`, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + ens_name: name, + eth_address: accounts.getAddress('user2'), + vp_token: makeMockVPToken(['com.twitter', 'com.github', 'com.discord', 'org.telegram']), + }), + }) + }) + + await page.goto(`/${name}`) + + await page.pause() + + await expect(page.getByTestId('profile-section-verifications')).toBeVisible() + + await profilePage.isRecordVerified('text', 'com.twitter', false) + await profilePage.isRecordVerified('text', 'org.telegram', false) + await profilePage.isRecordVerified('text', 'com.github', false) + await profilePage.isRecordVerified('text', 'com.discord', false) + await profilePage.isRecordVerified('verification', 'dentity', false) + await profilePage.isPersonhoodVerified(false) + + await expect(profilePage.record('verification', 'dentity')).toBeVisible() + await expect(profilePage.record('verification', 'dentity')).toBeVisible() + }) }) test.describe('Verify profile', () => { diff --git a/src/components/pages/profile/[name]/tabs/ProfileTab.tsx b/src/components/pages/profile/[name]/tabs/ProfileTab.tsx index 0f75b4be3..67e98b23f 100644 --- a/src/components/pages/profile/[name]/tabs/ProfileTab.tsx +++ b/src/components/pages/profile/[name]/tabs/ProfileTab.tsx @@ -79,6 +79,8 @@ const ProfileTab = ({ nameDetails, name }: Props) => { const { data: verifiedData, appendVerificationProps } = useVerifiedRecords({ verificationsRecord: profile?.texts?.find(({ key }) => key === VERIFICATION_RECORD_KEY)?.value, + ownerAddress: ownerData?.registrant || ownerData?.owner, + name: normalisedName, }) const isOffchainImport = useIsOffchainName({ diff --git a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts index d4f7224fa..d1a2b69c9 100644 --- a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts +++ b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts @@ -1,6 +1,6 @@ import { match } from 'ts-pattern'; import { getVerifiedRecords, parseVerificationRecord } from './useVerifiedRecords'; -import { describe, it, vi, expect, afterAll } from 'vitest'; +import { describe, it, vi, expect } from 'vitest'; import { makeMockVerifiablePresentationData } from '@root/test/mock/makeMockVerifiablePresentationData'; describe('parseVerificationRecord', () => { @@ -23,12 +23,12 @@ describe('parseVerificationRecord', () => { -describe('getVerifiedRecords', () => { +describe.only('getVerifiedRecords', () => { const mockFetch = vi.fn().mockImplementation(async (uri) => match(uri).with('error', () => Promise.reject('error')).otherwise(() => Promise.resolve({ json: () => Promise.resolve(makeMockVerifiablePresentationData('openid'))}))) vi.stubGlobal('fetch', mockFetch) it('should exclude fetches that error from results ', async () => { - const result = await getVerifiedRecords({ queryKey: [{ verificationsRecord: '["error", "regular", "error"]'}]} as any) + const result = await getVerifiedRecords({ queryKey: [{ verificationsRecord: '["error", "regular", "error"]'}, '0x123']} as any) expect(result).toHaveLength(6) }) diff --git a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts index 5caac58ac..2e785db36 100644 --- a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts +++ b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts @@ -1,4 +1,5 @@ import { QueryFunctionContext } from '@tanstack/react-query' +import { Hash } from 'viem' import { useQueryOptions } from '@app/hooks/useQueryOptions' import { CreateQueryKey, QueryConfig } from '@app/types' @@ -14,6 +15,8 @@ import { type UseVerifiedRecordsParameters = { verificationsRecord?: string + ownerAddress?: Hash + name?: string } export type UseVerifiedRecordsReturnType = VerifiedRecord[] @@ -41,7 +44,7 @@ export const parseVerificationRecord = (verificationRecord?: string): string[] = } export const getVerifiedRecords = async ({ - queryKey: [{ verificationsRecord }], + queryKey: [{ verificationsRecord, ownerAddress }], }: QueryFunctionContext>): Promise => { const verifiablePresentationUris = parseVerificationRecord(verificationsRecord) const responses = await Promise.allSettled( @@ -53,7 +56,7 @@ export const getVerifiedRecords = async => response.status === 'fulfilled', ) .map(({ value }) => value) - .map(parseVerificationData), + .map(parseVerificationData({ ownerAddress })), ).then((records) => records.flat()) } @@ -74,7 +77,7 @@ export const useVerifiedRecords = const preparedOptions = prepareQueryOptions({ queryKey: initialOptions.queryKey, queryFn: initialOptions.queryFn, - enabled: enabled && !!params.verificationsRecord, + enabled: enabled && !!params.verificationsRecord && !!params.ownerAddress && !!params.name, gcTime, staleTime, }) diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts index 11a36fdb3..2b17fcc79 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts @@ -1,7 +1,13 @@ +import { Hash } from 'viem' + import { - isOpenIdVerifiablePresentation, - parseOpenIdVerifiablePresentation, -} from './utils/parseOpenIdVerifiablePresentation' + isDentityVerifiablePresentation, + parseDentityVerifiablePresentation, +} from './utils/parseDentityVerifiablePresentation' + +export type ParseVerificationDataDependencies = { + ownerAddress?: Hash +} export type VerifiedRecord = { verified: boolean @@ -11,7 +17,11 @@ export type VerifiedRecord = { } // TODO: Add more formats here -export const parseVerificationData = async (data: unknown): Promise => { - if (isOpenIdVerifiablePresentation(data)) return parseOpenIdVerifiablePresentation(data) - return [] -} +export const parseVerificationData = + (dependencies: ParseVerificationDataDependencies) => + async (data: unknown): Promise => { + console.log('data', data) + if (isDentityVerifiablePresentation(data)) + return parseDentityVerifiablePresentation(dependencies)(data) + return [] + } diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts new file mode 100644 index 000000000..2279b0a1a --- /dev/null +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts @@ -0,0 +1,27 @@ +import { type ParseVerificationDataDependencies } from '../parseVerificationData' +import { + isOpenIdVerifiablePresentation, + OpenIdVerifiablePresentation, + parseOpenIdVerifiablePresentation, +} from './parseOpenIdVerifiablePresentation' + +export const isDentityVerifiablePresentation = ( + data: unknown, +): data is OpenIdVerifiablePresentation => { + if (!isOpenIdVerifiablePresentation(data)) return false + const credentials = Array.isArray(data.vp_token) ? data.vp_token : [data.vp_token] + return credentials.some((credential) => credential?.type.includes('VerifiedENS')) +} + +export const parseDentityVerifiablePresentation = + ({ ownerAddress }: ParseVerificationDataDependencies) => + async (data: OpenIdVerifiablePresentation) => { + const credentials = Array.isArray(data.vp_token) ? data.vp_token : [data.vp_token] + const ownershipVerified = credentials.some( + (credential) => + !!credential && + credential.type.includes('VerifiedENS') && + credential.credentialSubject?.ethAddress?.toLowerCase() === ownerAddress?.toLowerCase(), + ) + return parseOpenIdVerifiablePresentation({ ownershipVerified })(data) + } diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.test.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.test.ts index 3a9537e63..a199ba522 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.test.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.test.ts @@ -4,7 +4,7 @@ import { makeMockVerifiablePresentationData } from '@root/test/mock/makeMockVeri import { match } from 'ts-pattern'; vi.mock('../../parseVerifiedCredential', () => ({ - parseVerifiableCredential: async (type: string) => match(type).with('error', () => null).with('twitter', () => ({ + parseVerifiableCredential: () => async (type: string) => match(type).with('error', () => null).with('twitter', () => ({ issuer: 'dentity', key: 'com.twitter', value: 'name', @@ -37,7 +37,7 @@ describe('isOpenIdVerifiablePresentation', () => { describe('parseOpenIdVerifiablePresentation', () => { it('should return an array of verified credentials an exclude any null values', async () => { - const result = await parseOpenIdVerifiablePresentation({ vp_token: ['twitter', 'error', 'other'] as any}) + const result = await parseOpenIdVerifiablePresentation({ ownershipVerified: true })({ vp_token: ['twitter', 'error', 'other'] as any}) expect(result).toEqual([{ issuer: 'dentity', key: 'com.twitter', value: 'name', verified: true}]) }) }) \ No newline at end of file diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.ts index 46de4525b..c8c5d92ea 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseOpenIdVerifiablePresentation.ts @@ -1,11 +1,14 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type { VerifiableCredential } from '@app/types/verification' -import { parseVerifiableCredential } from '../../parseVerifiedCredential' +import { + parseVerifiableCredential, + ParseVerifiedCredentialDependencies, +} from '../../parseVerifiedCredential' import type { VerifiedRecord } from '../parseVerificationData' export type OpenIdVerifiablePresentation = { - vp_token: VerifiableCredential | VerifiableCredential[] + vp_token: VerifiableCredential | VerifiableCredential[] | undefined } export const isOpenIdVerifiablePresentation = ( @@ -20,9 +23,13 @@ export const isOpenIdVerifiablePresentation = ( ) } -export const parseOpenIdVerifiablePresentation = async (data: OpenIdVerifiablePresentation) => { - const { vp_token } = data - const credentials = Array.isArray(vp_token) ? vp_token : [vp_token] - const verifiedRecords = await Promise.all(credentials.map(parseVerifiableCredential)) - return verifiedRecords.filter((records): records is VerifiedRecord => !!records) -} +export const parseOpenIdVerifiablePresentation = + (dependencies: ParseVerifiedCredentialDependencies) => + async (data: OpenIdVerifiablePresentation) => { + const { vp_token } = data + const credentials = Array.isArray(vp_token) ? vp_token : [vp_token] + const verifiedRecords = await Promise.all( + credentials.map(parseVerifiableCredential(dependencies)), + ) + return verifiedRecords.filter((records): records is VerifiedRecord => !!records) + } diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.test.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.test.ts index 9084593cf..96ef21563 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.test.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.test.ts @@ -5,7 +5,7 @@ import { parseVerifiableCredential } from './parseVerifiedCredential' describe('parseVerifiedCredential', () => { it('should parse x account verified credential', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedXAccount'], credentialSubject: { username: 'name' }, } as any), @@ -19,7 +19,7 @@ describe('parseVerifiedCredential', () => { it('should parse twitter account verified credential', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedTwitterAccount'], credentialSubject: { username: 'name' }, } as any), @@ -33,7 +33,7 @@ describe('parseVerifiedCredential', () => { it('should parse discord account verified credential', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedDiscordAccount'], credentialSubject: { name: 'name' }, } as any), @@ -47,7 +47,7 @@ describe('parseVerifiedCredential', () => { it('should parse telegram account verified credential', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedTelegramAccount'], credentialSubject: { name: 'name' }, } as any), @@ -61,7 +61,7 @@ describe('parseVerifiedCredential', () => { it('should parse github account verified credential', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedGithubAccount'], credentialSubject: { name: 'name' }, } as any), @@ -75,7 +75,7 @@ describe('parseVerifiedCredential', () => { it('should parse personhood verified credential', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedPersonhood'], credentialSubject: { name: 'name' }, } as any), @@ -89,10 +89,24 @@ describe('parseVerifiedCredential', () => { it('should return null otherwise', async () => { expect( - await parseVerifiableCredential({ + await parseVerifiableCredential({ ownershipVerified: true })({ type: ['VerifiedIddentity'], credentialSubject: { name: 'name' }, } as any), ).toEqual(null) }) + + it('should return verified = false for verified credential if ownershipVerified is false', async () => { + expect( + await parseVerifiableCredential({ ownershipVerified: false })({ + type: ['VerifiedPersonhood'], + credentialSubject: { name: 'name' }, + } as any), + ).toEqual({ + issuer: 'dentity', + key: 'personhood', + value: '', + verified: false, + }) + }) }) diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts index ff3236036..84aeced23 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts @@ -8,53 +8,60 @@ import { tryVerifyVerifiableCredentials } from './parseVerificationData/utils/tr // TODO: parse issuer from verifiableCredential when dentity fixes their verifiable credentials -export const parseVerifiableCredential = async ( - verifiableCredential: VerifiableCredential, -): Promise => { - const verified = await tryVerifyVerifiableCredentials(verifiableCredential) - const baseResult = match(verifiableCredential) - .with( - { - type: P.when( - (type) => type?.includes('VerifiedTwitterAccount') || type?.includes('VerifiedXAccount'), - ), - }, - (vc) => ({ +export type ParseVerifiedCredentialDependencies = { + ownershipVerified: boolean +} + +export const parseVerifiableCredential = + ({ ownershipVerified }: ParseVerifiedCredentialDependencies) => + async (verifiableCredential?: VerifiableCredential): Promise => { + if (!verifiableCredential) return null + + const verified = await tryVerifyVerifiableCredentials(verifiableCredential) + const baseResult = match(verifiableCredential) + .with( + { + type: P.when( + (type) => + type?.includes('VerifiedTwitterAccount') || type?.includes('VerifiedXAccount'), + ), + }, + (vc) => ({ + issuer: 'dentity', + key: 'com.twitter', + value: normaliseTwitterRecordValue(vc?.credentialSubject?.username), + }), + ) + .with({ type: P.when((type) => type?.includes('VerifiedDiscordAccount')) }, (vc) => ({ + issuer: 'dentity', + key: 'com.discord', + value: vc?.credentialSubject?.name || '', + })) + .with({ type: P.when((type) => type?.includes('VerifiedGithubAccount')) }, (vc) => ({ issuer: 'dentity', - key: 'com.twitter', - value: normaliseTwitterRecordValue(vc?.credentialSubject?.username), - }), - ) - .with({ type: P.when((type) => type?.includes('VerifiedDiscordAccount')) }, (vc) => ({ - issuer: 'dentity', - key: 'com.discord', - value: vc?.credentialSubject?.name || '', - })) - .with({ type: P.when((type) => type?.includes('VerifiedGithubAccount')) }, (vc) => ({ - issuer: 'dentity', - key: 'com.github', - value: vc?.credentialSubject?.name || '', - })) - .with({ type: P.when((type) => type?.includes('VerifiedPersonhood')) }, () => ({ - issuer: 'dentity', - key: 'personhood', - value: '', - })) - .with({ type: P.when((type) => type?.includes('VerifiedTelegramAccount')) }, (vc) => ({ - issuer: 'dentity', - key: 'org.telegram', - value: vc?.credentialSubject?.name || '', - })) - .with({ type: P.when((type) => type?.includes('VerifiedEmail')) }, (vc) => ({ - issuer: 'dentity', - key: 'email', - value: vc?.credentialSubject?.verifiedEmail || '', - })) - .otherwise(() => null) + key: 'com.github', + value: vc?.credentialSubject?.name || '', + })) + .with({ type: P.when((type) => type?.includes('VerifiedPersonhood')) }, () => ({ + issuer: 'dentity', + key: 'personhood', + value: '', + })) + .with({ type: P.when((type) => type?.includes('VerifiedTelegramAccount')) }, (vc) => ({ + issuer: 'dentity', + key: 'org.telegram', + value: vc?.credentialSubject?.name || '', + })) + .with({ type: P.when((type) => type?.includes('VerifiedEmail')) }, (vc) => ({ + issuer: 'dentity', + key: 'email', + value: vc?.credentialSubject?.verifiedEmail || '', + })) + .otherwise(() => null) - if (!baseResult) return null - return { - verified, - ...baseResult, + if (!baseResult) return null + return { + verified: ownershipVerified && verified, + ...baseResult, + } } -} From 2091dbeedb42495bd83bb2b83be9ec2c1da8f7f4 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Mon, 16 Sep 2024 19:06:29 +0700 Subject: [PATCH 03/29] Apply unit test for reactQuery client configuration --- src/utils/query/reactQuery.test.tsx | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/utils/query/reactQuery.test.tsx diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx new file mode 100644 index 000000000..30985f70c --- /dev/null +++ b/src/utils/query/reactQuery.test.tsx @@ -0,0 +1,83 @@ +import { render, waitFor } from '@app/test-utils' + +import { QueryClientProvider, useQuery } from '@tanstack/react-query' +import { ReactNode } from 'react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { WagmiProvider } from 'wagmi' + +import { queryClient } from './reactQuery' +import { wagmiConfig } from './wagmi' + +const mockFetchData = vi.fn().mockResolvedValue('Test data') + +const TestComponentWrapper = ({ children }: { children: ReactNode }) => { + console.log('queryClient', queryClient.getDefaultOptions()) + return ( + + {children} + + ) +} + +const TestComponentWithHook = () => { + const { data, isFetching } = useQuery({ + queryKey: ['test-hook'], + queryFn: mockFetchData, + enabled: true, + }) + + return ( +
{isFetching ? Loading... : Data: {data}}
+ ) +} + +describe('reactQuery', () => { + beforeEach(() => { + vi.clearAllMocks() + queryClient.clear() + }) + + afterEach(() => { + queryClient.clear() + }) + + it('should create a query client with default options', () => { + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { + refetchOnWindowFocus: true, + refetchOnMount: 'always', + staleTime: 1_000 * 12, + gcTime: 1_000 * 60 * 60 * 24, + queryKeyHashFn: expect.any(Function), + }, + }) + }) + + it('should refetch queries on mount', async () => { + const { getByTestId, unmount } = render( + + + , + ) + + await waitFor(() => { + expect(mockFetchData).toHaveBeenCalledTimes(1) + expect(getByTestId('test')).toHaveTextContent('Test data') + }) + + unmount() + const { getByTestId: getByTestId2 } = render( + + + , + ) + + await waitFor( + () => { + expect(mockFetchData).toHaveBeenCalledTimes(2) + expect(getByTestId2('test')).toHaveTextContent('Test data') + }, + { timeout: 2000 }, + ) + }) +}) From f2f1beecb9ca4b926a05399d3f7743594aaf349c Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Mon, 16 Sep 2024 19:12:31 +0700 Subject: [PATCH 04/29] Remove debug log --- src/utils/query/reactQuery.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx index 30985f70c..4ab83d9f3 100644 --- a/src/utils/query/reactQuery.test.tsx +++ b/src/utils/query/reactQuery.test.tsx @@ -11,7 +11,6 @@ import { wagmiConfig } from './wagmi' const mockFetchData = vi.fn().mockResolvedValue('Test data') const TestComponentWrapper = ({ children }: { children: ReactNode }) => { - console.log('queryClient', queryClient.getDefaultOptions()) return ( {children} From 740a615d626b7412a33610a434a0c09e15177590 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 16 Sep 2024 22:11:39 +0800 Subject: [PATCH 05/29] update verification tests and add check for address and name in verification parsers --- e2e/specs/stateless/verifications.spec.ts | 269 +++++++++++++++--- playwright/pageObjects/profilePage.ts | 5 + .../useVerifiedRecords/useVerifiedRecords.ts | 4 +- .../parseVerificationData.ts | 2 +- .../parseDentityVerifiablePresentation.ts | 5 +- .../VerifyProfile/VerifyProfile-flow.tsx | 2 + 6 files changed, 239 insertions(+), 48 deletions(-) diff --git a/e2e/specs/stateless/verifications.spec.ts b/e2e/specs/stateless/verifications.spec.ts index 1ca030b7d..244cf8fa2 100644 --- a/e2e/specs/stateless/verifications.spec.ts +++ b/e2e/specs/stateless/verifications.spec.ts @@ -16,12 +16,19 @@ import { import { createAccounts } from '../../../playwright/fixtures/accounts' import { testClient } from '../../../playwright/fixtures/contracts/utils/addTestContracts' +type MakeMockVPTokenRecordKey = + | 'com.twitter' + | 'com.github' + | 'com.discord' + | 'org.telegram' + | 'personhood' + | 'email' + | 'ens' + const makeMockVPToken = ( - records: Array< - 'com.twitter' | 'com.github' | 'com.discord' | 'org.telegram' | 'personhood' | 'email' - >, + records: Array<{ key: MakeMockVPTokenRecordKey; value?: string; name?: string }>, ) => { - return records.map((record) => ({ + return records.map(({ key, value, name }) => ({ type: [ 'VerifiableCredential', { @@ -31,15 +38,22 @@ const makeMockVPToken = ( 'org.telegram': 'VerifiedTelegramAccount', personhood: 'VerifiedPersonhood', email: 'VerifiedEmail', - }[record], + ens: 'VerifiedENS', + }[key], ], credentialSubject: { credentialIssuer: 'Dentity', - ...(record === 'com.twitter' ? { username: '@name' } : {}), - ...(['com.twitter', 'com.github', 'com.discord', 'org.telegram'].includes(record) - ? { name: 'name' } + ...(key === 'com.twitter' ? { username: value ?? '@name' } : {}), + ...(['com.twitter', 'com.github', 'com.discord', 'org.telegram'].includes(key) + ? { name: value ?? 'name' } + : {}), + ...(key === 'email' ? { verifiedEmail: value ?? 'name@email.com' } : {}), + ...(key === 'ens' + ? { + ensName: name ?? 'name.eth', + ethAddress: value ?? (createAccounts().getAddress('user') as Hash), + } : {}), - ...(record === 'email' ? { verifiedEmail: 'name@email.com' } : {}), }, })) } @@ -94,12 +108,13 @@ test.describe('Verified records', () => { contentType: 'application/json', body: JSON.stringify({ vp_token: makeMockVPToken([ - 'com.twitter', - 'com.github', - 'com.discord', - 'org.telegram', - 'personhood', - 'email', + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'personhood' }, + { key: 'email' }, + { key: 'ens', name }, ]), }), }) @@ -173,7 +188,13 @@ test.describe('Verified records', () => { body: JSON.stringify({ ens_name: name, eth_address: accounts.getAddress('user2'), - vp_token: makeMockVPToken(['com.twitter', 'com.github', 'com.discord', 'org.telegram']), + vp_token: makeMockVPToken([ + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'ens', name }, + ]), }), }) }) @@ -195,7 +216,7 @@ test.describe('Verified records', () => { await expect(profilePage.record('verification', 'dentity')).toBeVisible() }) - test('Should not show badges if records match but ens credential fails', async ({ + test('Should not show badges if records match but ens credential address does not match', async ({ page, accounts, makePageObject, @@ -209,19 +230,19 @@ test.describe('Verified records', () => { texts: [ { key: 'com.twitter', - value: '@name2', + value: '@name', }, { key: 'org.telegram', - value: 'name2', + value: 'name', }, { key: 'com.discord', - value: 'name2', + value: 'name', }, { key: 'com.github', - value: 'name2', + value: 'name', }, { key: VERIFICATION_RECORD_KEY, @@ -229,9 +250,80 @@ test.describe('Verified records', () => { `${DENTITY_VPTOKEN_ENDPOINT}?name=name.eth&federated_token=federated_token`, ]), }, + ], + }, + }) + + const profilePage = makePageObject('ProfilePage') + + await page.route(`${DENTITY_VPTOKEN_ENDPOINT}*`, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + ens_name: name, + eth_address: accounts.getAddress('user2'), + vp_token: makeMockVPToken([ + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'ens', name, value: accounts.getAddress('user2') }, + ]), + }), + }) + }) + + await page.goto(`/${name}`) + + await page.pause() + + await expect(page.getByTestId('profile-section-verifications')).toBeVisible() + + await profilePage.isRecordVerified('text', 'com.twitter', false) + await profilePage.isRecordVerified('text', 'org.telegram', false) + await profilePage.isRecordVerified('text', 'com.github', false) + await profilePage.isRecordVerified('text', 'com.discord', false) + await profilePage.isRecordVerified('verification', 'dentity', false) + await profilePage.isPersonhoodVerified(false) + + await expect(profilePage.record('verification', 'dentity')).toBeVisible() + await expect(profilePage.record('verification', 'dentity')).toBeVisible() + }) + + test('Should not show badges if records match but ens credential name does not match', async ({ + page, + accounts, + makePageObject, + makeName, + }) => { + const name = await makeName({ + label: 'dentity', + type: 'wrapped', + owner: 'user', + records: { + texts: [ { key: 'com.twitter', - value: '@name2', + value: '@name', + }, + { + key: 'org.telegram', + value: 'name', + }, + { + key: 'com.discord', + value: 'name', + }, + { + key: 'com.github', + value: 'name', + }, + { + key: VERIFICATION_RECORD_KEY, + value: JSON.stringify([ + `${DENTITY_VPTOKEN_ENDPOINT}?name=name.eth&federated_token=federated_token`, + ]), }, ], }, @@ -246,7 +338,13 @@ test.describe('Verified records', () => { body: JSON.stringify({ ens_name: name, eth_address: accounts.getAddress('user2'), - vp_token: makeMockVPToken(['com.twitter', 'com.github', 'com.discord', 'org.telegram']), + vp_token: makeMockVPToken([ + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'ens', name: 'differentName.eth' }, + ]), }), }) }) @@ -267,6 +365,89 @@ test.describe('Verified records', () => { await expect(profilePage.record('verification', 'dentity')).toBeVisible() await expect(profilePage.record('verification', 'dentity')).toBeVisible() }) + + test('Should show error icon on verication button if VerifiedENS credential is not validated', async ({ + page, + login, + makePageObject, + makeName, + }) => { + const name = await makeName({ + label: 'dentity', + type: 'wrapped', + owner: 'user', + records: { + texts: [ + { + key: 'com.twitter', + value: '@name', + }, + { + key: 'org.telegram', + value: 'name', + }, + { + key: 'com.discord', + value: 'name', + }, + { + key: 'com.github', + value: 'name', + }, + { + key: 'email', + value: 'name@email.com', + }, + { + key: VERIFICATION_RECORD_KEY, + value: JSON.stringify([ + `${DENTITY_VPTOKEN_ENDPOINT}?name=name.eth&federated_token=federated_token`, + ]), + }, + { + key: 'com.twitter', + value: '@name', + }, + ], + }, + }) + + const profilePage = makePageObject('ProfilePage') + + await page.route(`${DENTITY_VPTOKEN_ENDPOINT}*`, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + vp_token: makeMockVPToken([ + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'personhood' }, + { key: 'email' }, + { key: 'ens', name: 'othername.eth' }, + ]), + }), + }) + }) + + await page.goto(`/${name}`) + await login.connect() + + await page.pause() + + await expect(page.getByTestId('profile-section-verifications')).toBeVisible() + + await profilePage.isRecordVerified('text', 'com.twitter', false) + await profilePage.isRecordVerified('text', 'org.telegram', false) + await profilePage.isRecordVerified('text', 'com.github', false) + await profilePage.isRecordVerified('text', 'com.discord', false) + await profilePage.isRecordVerified('verification', 'dentity', false) + await profilePage.isPersonhoodErrored() + + await expect(profilePage.record('verification', 'dentity')).toBeVisible() + }) }) test.describe('Verify profile', () => { @@ -292,11 +473,11 @@ test.describe('Verify profile', () => { contentType: 'application/json', body: JSON.stringify({ vp_token: makeMockVPToken([ - 'personhood', - 'com.twitter', - 'com.github', - 'com.discord', - 'org.telegram', + { key: 'personhood' }, + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, ]), }), }) @@ -336,11 +517,11 @@ test.describe('Verify profile', () => { contentType: 'application/json', body: JSON.stringify({ vp_token: makeMockVPToken([ - 'personhood', - 'com.twitter', - 'com.github', - 'com.discord', - 'org.telegram', + { key: 'personhood' }, + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, ]), }), }) @@ -405,11 +586,12 @@ test.describe('Verify profile', () => { contentType: 'application/json', body: JSON.stringify({ vp_token: makeMockVPToken([ - 'personhood', - 'com.twitter', - 'com.github', - 'com.discord', - 'org.telegram', + { key: 'personhood' }, + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'ens', name, value: createAccounts().getAddress('user2') }, ]), }), }) @@ -505,11 +687,12 @@ test.describe('OAuth flow', () => { contentType: 'application/json', body: JSON.stringify({ vp_token: makeMockVPToken([ - 'personhood', - 'com.twitter', - 'com.github', - 'com.discord', - 'org.telegram', + { key: 'personhood' }, + { key: 'com.twitter' }, + { key: 'com.github' }, + { key: 'com.discord' }, + { key: 'org.telegram' }, + { key: 'ens', name, value: createAccounts().getAddress('user2') }, ]), }), }) diff --git a/playwright/pageObjects/profilePage.ts b/playwright/pageObjects/profilePage.ts index e14abfdba..dcc179fb9 100644 --- a/playwright/pageObjects/profilePage.ts +++ b/playwright/pageObjects/profilePage.ts @@ -83,6 +83,11 @@ export class ProfilePage { return expect(this.page.getByTestId("profile-snippet-person-icon")).toHaveCount(count) } + isPersonhoodErrored(errored = true) { + const count = errored ? 1 : 0 + return expect(this.page.getByTestId("verification-badge-error-icon")).toHaveCount(count) + } + contentHash(): Locator { return this.page.getByTestId('other-profile-button-contenthash') } diff --git a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts index 2e785db36..6c6de02b3 100644 --- a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts +++ b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.ts @@ -44,7 +44,7 @@ export const parseVerificationRecord = (verificationRecord?: string): string[] = } export const getVerifiedRecords = async ({ - queryKey: [{ verificationsRecord, ownerAddress }], + queryKey: [{ verificationsRecord, ownerAddress, name }], }: QueryFunctionContext>): Promise => { const verifiablePresentationUris = parseVerificationRecord(verificationsRecord) const responses = await Promise.allSettled( @@ -56,7 +56,7 @@ export const getVerifiedRecords = async => response.status === 'fulfilled', ) .map(({ value }) => value) - .map(parseVerificationData({ ownerAddress })), + .map(parseVerificationData({ ownerAddress, name })), ).then((records) => records.flat()) } diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts index 2b17fcc79..0f34c4ae4 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/parseVerificationData.ts @@ -7,6 +7,7 @@ import { export type ParseVerificationDataDependencies = { ownerAddress?: Hash + name?: string } export type VerifiedRecord = { @@ -20,7 +21,6 @@ export type VerifiedRecord = { export const parseVerificationData = (dependencies: ParseVerificationDataDependencies) => async (data: unknown): Promise => { - console.log('data', data) if (isDentityVerifiablePresentation(data)) return parseDentityVerifiablePresentation(dependencies)(data) return [] diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts index 2279b0a1a..e4c4db5fd 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts @@ -14,14 +14,15 @@ export const isDentityVerifiablePresentation = ( } export const parseDentityVerifiablePresentation = - ({ ownerAddress }: ParseVerificationDataDependencies) => + ({ ownerAddress, name }: ParseVerificationDataDependencies) => async (data: OpenIdVerifiablePresentation) => { const credentials = Array.isArray(data.vp_token) ? data.vp_token : [data.vp_token] const ownershipVerified = credentials.some( (credential) => !!credential && credential.type.includes('VerifiedENS') && - credential.credentialSubject?.ethAddress?.toLowerCase() === ownerAddress?.toLowerCase(), + credential.credentialSubject?.ethAddress?.toLowerCase() === ownerAddress?.toLowerCase() && + credential.credentialSubject?.ensName?.toLowerCase() === name.toLowerCase(), ) return parseOpenIdVerifiablePresentation({ ownershipVerified })(data) } diff --git a/src/transaction-flow/input/VerifyProfile/VerifyProfile-flow.tsx b/src/transaction-flow/input/VerifyProfile/VerifyProfile-flow.tsx index a3fd6eedc..e0f7057e4 100644 --- a/src/transaction-flow/input/VerifyProfile/VerifyProfile-flow.tsx +++ b/src/transaction-flow/input/VerifyProfile/VerifyProfile-flow.tsx @@ -32,6 +32,8 @@ const VerifyProfile = ({ data: { name }, dispatch, onDismiss }: Props) => { const { data: verificationData, isLoading: isVerificationLoading } = useVerifiedRecords({ verificationsRecord: profile?.texts?.find(({ key }) => key === VERIFICATION_RECORD_KEY)?.value, + ownerAddress, + name, }) const isLoading = isProfileLoading || isVerificationLoading || isOwnerLoading From 445c25aad7ac5b35eddfb77baa3ddbad68311166 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 16 Sep 2024 22:41:49 +0800 Subject: [PATCH 06/29] fix some test errors --- .../useVerifiedRecords/useVerifiedRecords.test.ts | 2 +- .../utils/parseDentityVerifiablePresentation.ts | 4 +++- .../useVerifiedRecords/utils/parseVerifiedCredential.ts | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts index d1a2b69c9..e8af7a3ee 100644 --- a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts +++ b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts @@ -23,7 +23,7 @@ describe('parseVerificationRecord', () => { -describe.only('getVerifiedRecords', () => { +describe('getVerifiedRecords', () => { const mockFetch = vi.fn().mockImplementation(async (uri) => match(uri).with('error', () => Promise.reject('error')).otherwise(() => Promise.resolve({ json: () => Promise.resolve(makeMockVerifiablePresentationData('openid'))}))) vi.stubGlobal('fetch', mockFetch) diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts index e4c4db5fd..6e9060235 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerificationData/utils/parseDentityVerifiablePresentation.ts @@ -21,8 +21,10 @@ export const parseDentityVerifiablePresentation = (credential) => !!credential && credential.type.includes('VerifiedENS') && + !!credential.credentialSubject.ethAddress && + !!credential.credentialSubject.ensName && credential.credentialSubject?.ethAddress?.toLowerCase() === ownerAddress?.toLowerCase() && - credential.credentialSubject?.ensName?.toLowerCase() === name.toLowerCase(), + credential.credentialSubject?.ensName?.toLowerCase() === name?.toLowerCase(), ) return parseOpenIdVerifiablePresentation({ ownershipVerified })(data) } diff --git a/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts b/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts index 84aeced23..a6dcb2046 100644 --- a/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts +++ b/src/hooks/verification/useVerifiedRecords/utils/parseVerifiedCredential.ts @@ -57,6 +57,11 @@ export const parseVerifiableCredential = key: 'email', value: vc?.credentialSubject?.verifiedEmail || '', })) + .with({ type: P.when((type) => type?.includes('VerifiedENS')) }, () => ({ + issuer: 'dentity', + key: 'ens', + value: '', + })) .otherwise(() => null) if (!baseResult) return null From 00b4efa3a735cecaa3b1d890c6ae7bdcb41b18b2 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 16 Sep 2024 23:48:23 +0800 Subject: [PATCH 07/29] fix unit test --- .../useVerifiedRecords/useVerifiedRecords.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts index e8af7a3ee..e87264342 100644 --- a/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts +++ b/src/hooks/verification/useVerifiedRecords/useVerifiedRecords.test.ts @@ -29,12 +29,12 @@ describe('getVerifiedRecords', () => { it('should exclude fetches that error from results ', async () => { const result = await getVerifiedRecords({ queryKey: [{ verificationsRecord: '["error", "regular", "error"]'}, '0x123']} as any) - expect(result).toHaveLength(6) + expect(result).toHaveLength(7) }) it('should return a flat array of verified credentials', async () => { const result = await getVerifiedRecords({ queryKey: [{ verificationsRecord: '["one", "two", "error", "three"]'}]} as any) - expect(result).toHaveLength(18) + expect(result).toHaveLength(21) expect(result.every((item) => !Array.isArray(item))).toBe(true) }) }) \ No newline at end of file From 47363e34aafffb5cc53db9388f092df356332cc6 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Tue, 17 Sep 2024 19:46:25 +0700 Subject: [PATCH 08/29] Revert refetchOnMount to true for refetchOptions and useMoonpayRegistration --- .../pages/profile/[name]/registration/useMoonpayRegistration.ts | 2 +- src/utils/query/reactQuery.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts b/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts index 0329264ad..769876eae 100644 --- a/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts +++ b/src/components/pages/profile/[name]/registration/useMoonpayRegistration.ts @@ -81,7 +81,7 @@ export const useMoonpayRegistration = ( return result || {} }, refetchOnWindowFocus: true, - refetchOnMount: 'always', + refetchOnMount: true, refetchInterval: 1000, refetchIntervalInBackground: true, enabled: !!currentExternalTransactionId && !isCompleted, diff --git a/src/utils/query/reactQuery.ts b/src/utils/query/reactQuery.ts index 7dd6fef80..c93241bb2 100644 --- a/src/utils/query/reactQuery.ts +++ b/src/utils/query/reactQuery.ts @@ -21,7 +21,7 @@ export const refetchOptions: DefaultOptions = { meta: { isRefetchQuery: true, }, - refetchOnMount: 'always', + refetchOnMount: true, queryKeyHashFn: hashFn, }, } From 2efca82fa2d2d6df9cc7ead278640947f60a8b47 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 18 Sep 2024 11:21:12 +0700 Subject: [PATCH 09/29] Apply queryClient.invalidateQueries on PersistQueryClientProvider. Revert refetchOnMount=true for useBlockTimestamp and reactQuery --- src/hooks/chain/useBlockTimestamp.ts | 2 +- src/utils/query/providers.tsx | 6 ++++++ src/utils/query/reactQuery.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hooks/chain/useBlockTimestamp.ts b/src/hooks/chain/useBlockTimestamp.ts index 865dde78d..db8bd192e 100644 --- a/src/hooks/chain/useBlockTimestamp.ts +++ b/src/hooks/chain/useBlockTimestamp.ts @@ -12,7 +12,7 @@ export const useBlockTimestamp = ({ enabled = true }: UseBlockTimestampParameter blockTag: 'latest', query: { enabled, - refetchOnMount: 'always', + refetchOnMount: true, refetchInterval: 1000 * 60 * 5 /* 5 minutes */, staleTime: 1000 * 60 /* 1 minute */, select: (b) => b.timestamp * 1000n, diff --git a/src/utils/query/providers.tsx b/src/utils/query/providers.tsx index 9227d1217..0eca8ebf2 100644 --- a/src/utils/query/providers.tsx +++ b/src/utils/query/providers.tsx @@ -1,3 +1,4 @@ +import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import type { ReactNode } from 'react' import { WagmiProvider } from 'wagmi' @@ -16,8 +17,13 @@ export function QueryProviders({ children }: Props) { { + return queryClient.invalidateQueries() + }} > {children} + + ) diff --git a/src/utils/query/reactQuery.ts b/src/utils/query/reactQuery.ts index c93241bb2..81047d639 100644 --- a/src/utils/query/reactQuery.ts +++ b/src/utils/query/reactQuery.ts @@ -5,7 +5,7 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: true, - refetchOnMount: 'always', + refetchOnMount: true, staleTime: 1_000 * 12, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: hashFn, From 20069e4a040687115dc7ebbf8452924e2167dcdf Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 18 Sep 2024 11:22:22 +0700 Subject: [PATCH 10/29] Remove ReactQueryDevtoolsPanel --- src/utils/query/providers.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/query/providers.tsx b/src/utils/query/providers.tsx index 0eca8ebf2..1ad065a2d 100644 --- a/src/utils/query/providers.tsx +++ b/src/utils/query/providers.tsx @@ -1,4 +1,3 @@ -import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import type { ReactNode } from 'react' import { WagmiProvider } from 'wagmi' @@ -22,8 +21,6 @@ export function QueryProviders({ children }: Props) { }} > {children} - - ) From 61be95f0fb2b6628293c5157850882bbb8c91bf4 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 18 Sep 2024 14:15:22 +0700 Subject: [PATCH 11/29] Update unit test for reactQuery --- src/utils/query/reactQuery.test.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx index 4ab83d9f3..c27fd3e71 100644 --- a/src/utils/query/reactQuery.test.tsx +++ b/src/utils/query/reactQuery.test.tsx @@ -44,7 +44,7 @@ describe('reactQuery', () => { expect(queryClient.getDefaultOptions()).toEqual({ queries: { refetchOnWindowFocus: true, - refetchOnMount: 'always', + refetchOnMount: true, staleTime: 1_000 * 12, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: expect.any(Function), @@ -70,13 +70,11 @@ describe('reactQuery', () => { , ) + await queryClient.invalidateQueries() - await waitFor( - () => { - expect(mockFetchData).toHaveBeenCalledTimes(2) - expect(getByTestId2('test')).toHaveTextContent('Test data') - }, - { timeout: 2000 }, - ) + await waitFor(() => { + expect(mockFetchData).toHaveBeenCalledTimes(2) + expect(getByTestId2('test')).toHaveTextContent('Test data') + }) }) }) From 8454e10bb70cbb90f1c0a4be04cae86ad56c12eb Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Tue, 24 Sep 2024 17:16:21 +0700 Subject: [PATCH 12/29] Update refetchOnMount to always for defaultOptions, update unit test for reactQuery --- src/utils/query/providers.tsx | 3 --- src/utils/query/reactQuery.test.tsx | 28 +++++++++++++++++++++++++--- src/utils/query/reactQuery.ts | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/utils/query/providers.tsx b/src/utils/query/providers.tsx index 1ad065a2d..9227d1217 100644 --- a/src/utils/query/providers.tsx +++ b/src/utils/query/providers.tsx @@ -16,9 +16,6 @@ export function QueryProviders({ children }: Props) { { - return queryClient.invalidateQueries() - }} > {children} diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx index c27fd3e71..f97c9f337 100644 --- a/src/utils/query/reactQuery.test.tsx +++ b/src/utils/query/reactQuery.test.tsx @@ -44,7 +44,7 @@ describe('reactQuery', () => { expect(queryClient.getDefaultOptions()).toEqual({ queries: { refetchOnWindowFocus: true, - refetchOnMount: true, + refetchOnMount: 'always', staleTime: 1_000 * 12, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: expect.any(Function), @@ -52,7 +52,30 @@ describe('reactQuery', () => { }) }) - it('should refetch queries on mount', async () => { + it('should not refetch query on rerender', async () => { + const { getByTestId, rerender } = render( + + + , + ) + + await waitFor(() => { + expect(mockFetchData).toHaveBeenCalledTimes(1) + expect(getByTestId('test')).toHaveTextContent('Test data') + }) + + rerender( + + + , + ) + + await waitFor(() => { + expect(mockFetchData).toHaveBeenCalledTimes(1) + }) + }) + + it('should refetch query on mount', async () => { const { getByTestId, unmount } = render( @@ -70,7 +93,6 @@ describe('reactQuery', () => { , ) - await queryClient.invalidateQueries() await waitFor(() => { expect(mockFetchData).toHaveBeenCalledTimes(2) diff --git a/src/utils/query/reactQuery.ts b/src/utils/query/reactQuery.ts index 81047d639..c93241bb2 100644 --- a/src/utils/query/reactQuery.ts +++ b/src/utils/query/reactQuery.ts @@ -5,7 +5,7 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: true, - refetchOnMount: true, + refetchOnMount: 'always', staleTime: 1_000 * 12, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: hashFn, From b965851bf8b735de0062ec79c3fc03af4a7a85aa Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 9 Oct 2024 13:22:42 +0800 Subject: [PATCH 13/29] add react-query-dev-tools --- package.json | 1 + pnpm-lock.yaml | 20 +++++++++ src/hooks/ensjs/public/useRecords.ts | 1 + src/utils/query/providers.tsx | 2 + src/utils/query/reactQuery.test.tsx | 64 +++++++++++++++++++++++----- 5 files changed, 77 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 17cb30b03..ec3f01af5 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@tanstack/query-persist-client-core": "5.22.2", "@tanstack/query-sync-storage-persister": "5.22.2", "@tanstack/react-query": "5.22.2", + "@tanstack/react-query-devtools": "^5.59.0", "@tanstack/react-query-persist-client": "5.22.2", "@wagmi/core": "2.13.3", "@walletconnect/ethereum-provider": "^2.11.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96fc7eb4e..a7e2a8274 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: '@tanstack/react-query': specifier: 5.22.2 version: 5.22.2(react@18.3.1) + '@tanstack/react-query-devtools': + specifier: ^5.59.0 + version: 5.59.0(@tanstack/react-query@5.22.2(react@18.3.1))(react@18.3.1) '@tanstack/react-query-persist-client': specifier: 5.22.2 version: 5.22.2(@tanstack/react-query@5.22.2(react@18.3.1))(react@18.3.1) @@ -3093,12 +3096,21 @@ packages: '@tanstack/query-core@5.22.2': resolution: {integrity: sha512-z3PwKFUFACMUqe1eyesCIKg3Jv1mysSrYfrEW5ww5DCDUD4zlpTKBvUDaEjsfZzL3ULrFLDM9yVUxI/fega1Qg==} + '@tanstack/query-devtools@5.58.0': + resolution: {integrity: sha512-iFdQEFXaYYxqgrv63ots+65FGI+tNp5ZS5PdMU1DWisxk3fez5HG3FyVlbUva+RdYS5hSLbxZ9aw3yEs97GNTw==} + '@tanstack/query-persist-client-core@5.22.2': resolution: {integrity: sha512-sFDgWoN54uclIDIoImPmDzxTq8HhZEt9pO0JbVHjI6LPZqunMMF9yAq9zFKrpH//jD5f+rBCQsdGyhdpUo9e8Q==} '@tanstack/query-sync-storage-persister@5.22.2': resolution: {integrity: sha512-mDxXURiMPzWXVc+FwDu94VfIt/uHk5+9EgcxJRYtj8Vsx18T0DiiKk1VgVOBLd97C+Sa7z7ujP2D6Y5lphW+hQ==} + '@tanstack/react-query-devtools@5.59.0': + resolution: {integrity: sha512-Kz7577FQGU8qmJxROIT/aOwmkTcxfBqgTP6r1AIvuJxVMVHPkp8eQxWQ7BnfBsy/KTJHiV9vMtRVo1+R1tB3vg==} + peerDependencies: + '@tanstack/react-query': ^5.59.0 + react: ^18.2.0 + '@tanstack/react-query-persist-client@5.22.2': resolution: {integrity: sha512-osAaQn2PDTaa2ApTLOAus7g8Y96LHfS2+Pgu/RoDlEJUEkX7xdEn0YuurxbnJaDJDESMfr+CH/eAX2y+lx02Fg==} peerDependencies: @@ -13816,6 +13828,8 @@ snapshots: '@tanstack/query-core@5.22.2': {} + '@tanstack/query-devtools@5.58.0': {} + '@tanstack/query-persist-client-core@5.22.2': dependencies: '@tanstack/query-core': 5.22.2 @@ -13825,6 +13839,12 @@ snapshots: '@tanstack/query-core': 5.22.2 '@tanstack/query-persist-client-core': 5.22.2 + '@tanstack/react-query-devtools@5.59.0(@tanstack/react-query@5.22.2(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-devtools': 5.58.0 + '@tanstack/react-query': 5.22.2(react@18.3.1) + react: 18.3.1 + '@tanstack/react-query-persist-client@5.22.2(@tanstack/react-query@5.22.2(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/query-persist-client-core': 5.22.2 diff --git a/src/hooks/ensjs/public/useRecords.ts b/src/hooks/ensjs/public/useRecords.ts index 6e95d1e10..dafbf538b 100644 --- a/src/hooks/ensjs/public/useRecords.ts +++ b/src/hooks/ensjs/public/useRecords.ts @@ -86,6 +86,7 @@ export const getRecordsQueryFn = > => { if (!name) throw new Error('name is required') + console.log('getRecordsQueryFn', name) const client = config.getClient({ chainId }) const res = await getRecords(client, { name, diff --git a/src/utils/query/providers.tsx b/src/utils/query/providers.tsx index 9227d1217..8224a50d0 100644 --- a/src/utils/query/providers.tsx +++ b/src/utils/query/providers.tsx @@ -1,3 +1,4 @@ +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import type { ReactNode } from 'react' import { WagmiProvider } from 'wagmi' @@ -18,6 +19,7 @@ export function QueryProviders({ children }: Props) { persistOptions={createPersistConfig({ queryClient })} > {children} + ) diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx index f97c9f337..aac560125 100644 --- a/src/utils/query/reactQuery.test.tsx +++ b/src/utils/query/reactQuery.test.tsx @@ -1,7 +1,8 @@ import { render, waitFor } from '@app/test-utils' -import { QueryClientProvider, useQuery } from '@tanstack/react-query' -import { ReactNode } from 'react' +import { QueryClientProvider } from '@tanstack/react-query' +import { useQuery } from './useQuery' +import { PropsWithChildren, ReactNode } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { WagmiProvider } from 'wagmi' @@ -18,15 +19,24 @@ const TestComponentWrapper = ({ children }: { children: ReactNode }) => { ) } -const TestComponentWithHook = () => { - const { data, isFetching } = useQuery({ +const TestComponentWithHook = ({ children, ...props }: PropsWithChildren<{}>) => { + const { data, isFetching, isLoading } = useQuery({ queryKey: ['test-hook'], queryFn: mockFetchData, enabled: true, }) return ( -
{isFetching ? Loading... : Data: {data}}
+
+ {isLoading ? ( + Loading... + ) : ( + + Data: {data} + {children} + + )} +
) } @@ -44,8 +54,8 @@ describe('reactQuery', () => { expect(queryClient.getDefaultOptions()).toEqual({ queries: { refetchOnWindowFocus: true, - refetchOnMount: 'always', - staleTime: 1_000 * 12, + refetchOnMount: true, + staleTime: 0, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: expect.any(Function), }, @@ -55,7 +65,7 @@ describe('reactQuery', () => { it('should not refetch query on rerender', async () => { const { getByTestId, rerender } = render( - + , ) @@ -66,11 +76,12 @@ describe('reactQuery', () => { rerender( - + , ) await waitFor(() => { + expect(getByTestId('test')).toHaveTextContent('Test data') expect(mockFetchData).toHaveBeenCalledTimes(1) }) }) @@ -78,7 +89,7 @@ describe('reactQuery', () => { it('should refetch query on mount', async () => { const { getByTestId, unmount } = render( - + , ) @@ -90,13 +101,44 @@ describe('reactQuery', () => { unmount() const { getByTestId: getByTestId2 } = render( - + , ) await waitFor(() => { + expect(getByTestId2('test')).toHaveTextContent('Test data') expect(mockFetchData).toHaveBeenCalledTimes(2) + }) + }) + + it('should not fetch twice on nested query', async () => { + const { getByTestId, unmount } = render( + + + + + , + ) + + await waitFor(() => { + expect(getByTestId('test')).toHaveTextContent('Test data') + expect(getByTestId('nested')).toHaveTextContent('Test data') + expect(mockFetchData).toHaveBeenCalledTimes(1) + }) + + unmount() + const { getByTestId: getByTestId2 } = render( + + + + + , + ) + + await waitFor(() => { expect(getByTestId2('test')).toHaveTextContent('Test data') + expect(getByTestId2('nested')).toHaveTextContent('Test data') + expect(mockFetchData).toHaveBeenCalledTimes(2) }) }) }) From 3c19154b34a5e2ed13198799f71704c402745b1e Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 9 Oct 2024 15:36:54 +0800 Subject: [PATCH 14/29] update react query defaults and react query test --- src/utils/query/providers.tsx | 2 +- src/utils/query/reactQuery.test.tsx | 6 +++--- src/utils/query/reactQuery.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/query/providers.tsx b/src/utils/query/providers.tsx index 8224a50d0..387a4e1ce 100644 --- a/src/utils/query/providers.tsx +++ b/src/utils/query/providers.tsx @@ -19,7 +19,7 @@ export function QueryProviders({ children }: Props) { persistOptions={createPersistConfig({ queryClient })} > {children} - + ) diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx index aac560125..f89d42bc1 100644 --- a/src/utils/query/reactQuery.test.tsx +++ b/src/utils/query/reactQuery.test.tsx @@ -111,7 +111,7 @@ describe('reactQuery', () => { }) }) - it('should not fetch twice on nested query', async () => { + it('should fetch twice on nested query with no cache and once with cache', async () => { const { getByTestId, unmount } = render( @@ -123,7 +123,7 @@ describe('reactQuery', () => { await waitFor(() => { expect(getByTestId('test')).toHaveTextContent('Test data') expect(getByTestId('nested')).toHaveTextContent('Test data') - expect(mockFetchData).toHaveBeenCalledTimes(1) + expect(mockFetchData).toHaveBeenCalledTimes(2) }) unmount() @@ -138,7 +138,7 @@ describe('reactQuery', () => { await waitFor(() => { expect(getByTestId2('test')).toHaveTextContent('Test data') expect(getByTestId2('nested')).toHaveTextContent('Test data') - expect(mockFetchData).toHaveBeenCalledTimes(2) + expect(mockFetchData).toHaveBeenCalledTimes(3) }) }) }) diff --git a/src/utils/query/reactQuery.ts b/src/utils/query/reactQuery.ts index c93241bb2..80ba1fb8c 100644 --- a/src/utils/query/reactQuery.ts +++ b/src/utils/query/reactQuery.ts @@ -5,8 +5,8 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: true, - refetchOnMount: 'always', - staleTime: 1_000 * 12, + refetchOnMount: true, + staleTime: 0, gcTime: 1_000 * 60 * 60 * 24, queryKeyHashFn: hashFn, }, From 161447b28714b83dbf5656f4ad90efc55116c7ec Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 9 Oct 2024 16:05:25 +0800 Subject: [PATCH 15/29] clean up console logs --- .../pages/profile/[name]/registration/steps/Transactions.tsx | 2 -- src/hooks/ensjs/public/useRecords.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/components/pages/profile/[name]/registration/steps/Transactions.tsx b/src/components/pages/profile/[name]/registration/steps/Transactions.tsx index 4e245ea57..f934e8ef2 100644 --- a/src/components/pages/profile/[name]/registration/steps/Transactions.tsx +++ b/src/components/pages/profile/[name]/registration/steps/Transactions.tsx @@ -336,8 +336,6 @@ const Transactions = ({ registrationData, name, callback, onStart }: Props) => { endDate: commitTimestamp ? new Date(commitTimestamp + ONE_DAY * 1000) : undefined, }) - console.log('duration', duration, commitTimestamp) - return ( setResetOpen(false)}> diff --git a/src/hooks/ensjs/public/useRecords.ts b/src/hooks/ensjs/public/useRecords.ts index dafbf538b..6e95d1e10 100644 --- a/src/hooks/ensjs/public/useRecords.ts +++ b/src/hooks/ensjs/public/useRecords.ts @@ -86,7 +86,6 @@ export const getRecordsQueryFn = > => { if (!name) throw new Error('name is required') - console.log('getRecordsQueryFn', name) const client = config.getClient({ chainId }) const res = await getRecords(client, { name, From 3f27a767a2a137bbd7d55a22e746a65e4fad25bf Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 9 Oct 2024 19:38:19 +0800 Subject: [PATCH 16/29] clean up extendNames test --- e2e/specs/stateless/extendNames.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index ae14d1346..f16352fbb 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -77,6 +77,7 @@ test('should be able to register multiple names on the address page', async ({ // increment and save await page.getByTestId('plus-minus-control-plus').click() await page.getByTestId('plus-minus-control-plus').click() + await page.waitForTimeout(500) await page.getByTestId('extend-names-confirm').click() await transactionModal.autoComplete() @@ -88,13 +89,11 @@ test('should be able to register multiple names on the address page', async ({ // Should be able to remove this after useQuery is fixed. Using to force a refetch. await time.increaseTime({ seconds: 15 }) - await page.pause() await page.reload() for (const name of extendableNameItems) { const label = name.replace('.eth', '') await addresPage.search(label) await expect(addresPage.getNameRow(name)).toBeVisible({ timeout: 5000 }) - await page.pause() await expect(await addresPage.getTimestamp(name)).not.toBe(timestampDict[name]) await expect(await addresPage.getTimestamp(name)).toBe(timestampDict[name] + 31536000000 * 3) } @@ -194,7 +193,6 @@ test('should be able to extend a single unwrapped name in grace period from prof const timestamp = await profilePage.getExpiryTimestamp() - await page.pause() await profilePage.getExtendButton.click() const extendNamesModal = makePageObject('ExtendNamesModal') @@ -353,7 +351,6 @@ test('should be able to extend a name by a month', async ({ await profilePage.goto(name) await login.connect() - await page.pause() const timestamp = await profilePage.getExpiryTimestamp() await profilePage.getExtendButton.click() @@ -418,7 +415,6 @@ test('should be able to extend a name by a day', async ({ await profilePage.goto(name) await login.connect() - await page.pause() const timestamp = await profilePage.getExpiryTimestamp() await profilePage.getExtendButton.click() @@ -485,7 +481,6 @@ test('should be able to extend a name in grace period by a month', async ({ const timestamp = await profilePage.getExpiryTimestamp() - await page.pause() await profilePage.getExtendButton.click() const extendNamesModal = makePageObject('ExtendNamesModal') @@ -568,7 +563,6 @@ test('should be able to extend a name in grace period by 1 day', async ({ const timestamp = await profilePage.getExpiryTimestamp() - await page.pause() await profilePage.getExtendButton.click() const extendNamesModal = makePageObject('ExtendNamesModal') From 2dca3da976b50f2147dc9c069435a760e558e7fa Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 22 Oct 2024 11:04:07 +0800 Subject: [PATCH 17/29] remove additional react-query option --- src/utils/query/reactQuery.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/query/reactQuery.ts b/src/utils/query/reactQuery.ts index 80ba1fb8c..37fd0b132 100644 --- a/src/utils/query/reactQuery.ts +++ b/src/utils/query/reactQuery.ts @@ -4,7 +4,6 @@ import { hashFn } from 'wagmi/query' export const queryClient = new QueryClient({ defaultOptions: { queries: { - refetchOnWindowFocus: true, refetchOnMount: true, staleTime: 0, gcTime: 1_000 * 60 * 60 * 24, From d480cd12f02738f86959a5e6e48776945cfc79d0 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 23 Oct 2024 11:29:30 +0800 Subject: [PATCH 18/29] fix unit test --- src/utils/query/reactQuery.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/query/reactQuery.test.tsx b/src/utils/query/reactQuery.test.tsx index f89d42bc1..5ac7c41da 100644 --- a/src/utils/query/reactQuery.test.tsx +++ b/src/utils/query/reactQuery.test.tsx @@ -53,7 +53,6 @@ describe('reactQuery', () => { it('should create a query client with default options', () => { expect(queryClient.getDefaultOptions()).toEqual({ queries: { - refetchOnWindowFocus: true, refetchOnMount: true, staleTime: 0, gcTime: 1_000 * 60 * 60 * 24, From 847a68f3fb49247da7182dbbdad6b41391c7c01c Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Thu, 24 Oct 2024 23:14:54 +0800 Subject: [PATCH 19/29] split up verification oauth process --- e2e/specs/stateless/verifications.spec.ts | 39 ++++------- .../useDentityProfile.ts} | 62 +++++++---------- .../useDentityToken/useDentityToken.ts | 67 +++++++++++++++++++ .../utils/getAPIEndpointForVerifier.ts | 9 --- .../useVerificationOAuthHandler.ts | 28 ++++---- .../createVerificationTransactionFlow.ts | 4 +- .../utils/dentityHandler.ts | 4 +- 7 files changed, 123 insertions(+), 90 deletions(-) rename src/hooks/verification/{useVerificationOAuth/useVerificationOAuth.ts => useDentityProfile/useDentityProfile.ts} (66%) create mode 100644 src/hooks/verification/useDentityToken/useDentityToken.ts delete mode 100644 src/hooks/verification/useVerificationOAuth/utils/getAPIEndpointForVerifier.ts diff --git a/e2e/specs/stateless/verifications.spec.ts b/e2e/specs/stateless/verifications.spec.ts index 3f23e22e4..0db792646 100644 --- a/e2e/specs/stateless/verifications.spec.ts +++ b/e2e/specs/stateless/verifications.spec.ts @@ -108,8 +108,6 @@ test.describe('Verified records', () => { await page.goto(`/${name}`) // await login.connect() - await page.pause() - await expect(page.getByTestId('profile-section-verifications')).toBeVisible() await profilePage.isRecordVerified('text', 'com.twitter') @@ -180,8 +178,6 @@ test.describe('Verified records', () => { await page.goto(`/${name}`) - await page.pause() - await expect(page.getByTestId('profile-section-verifications')).toBeVisible() await profilePage.isRecordVerified('text', 'com.twitter', false) @@ -345,8 +341,6 @@ test.describe('Verify profile', () => { await profilePage.goto(name) await login.connect() - await page.pause() - await expect(page.getByTestId('profile-section-verifications')).toBeVisible() await profilePage.isRecordVerified('text', 'com.twitter') @@ -374,7 +368,6 @@ test.describe('Verify profile', () => { await profilePage.isRecordVerified('text', 'com.discord', false) await profilePage.isRecordVerified('verification', 'dentity', false) await profilePage.isPersonhoodVerified(false) - await page.pause() }) }) @@ -488,8 +481,6 @@ test.describe('OAuth flow', () => { await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) await login.connect() - await page.pause() - await expect(page.getByText('Verification failed')).toBeVisible() await expect( page.getByText( @@ -497,13 +488,10 @@ test.describe('OAuth flow', () => { ), ).toBeVisible() - await page.pause() await login.switchTo('user2') // Page should redirect to the profile page await expect(page).toHaveURL(`/${name}`) - - await page.pause() }) test('Should show an error message if user is not logged in', async ({ @@ -531,8 +519,6 @@ test.describe('OAuth flow', () => { await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) - await page.pause() - await expect(page.getByText('Verification failed')).toBeVisible() await expect( page.getByText('You must be connected as 0x709...c79C8 to set the verification record.'), @@ -540,13 +526,21 @@ test.describe('OAuth flow', () => { await page.locator('.modal').getByRole('button', { name: 'Done' }).click() - await page.pause() + await page.route(`${VERIFICATION_OAUTH_BASE_URL}/dentity/token`, async (route) => { + await route.fulfill({ + status: 401, + contentType: 'application/json', + body: JSON.stringify({ + error_msg: 'Unauthorized', + }), + }) + }) + await login.connect('user2') + await page.reload() // Page should redirect to the profile page await expect(page).toHaveURL(`/${name}`) - - await page.pause() }) test('Should redirect to profile page without showing set verification record if it already set', async ({ @@ -594,15 +588,9 @@ test.describe('OAuth flow', () => { await expect(page).toHaveURL(`/${name}`) await expect(transactionModal.transactionModal).not.toBeVisible() - - await page.pause() }) - test('Should show general error message if other problems occur', async ({ - page, - login, - makeName, - }) => { + test('Should show general error message if other problems occur', async ({ page, makeName }) => { const name = await makeName({ label: 'dentity', type: 'legacy', @@ -622,9 +610,6 @@ test.describe('OAuth flow', () => { }) await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) - await login.connect('user') - - await page.pause() await expect(page.getByText('Verification failed')).toBeVisible() await expect( diff --git a/src/hooks/verification/useVerificationOAuth/useVerificationOAuth.ts b/src/hooks/verification/useDentityProfile/useDentityProfile.ts similarity index 66% rename from src/hooks/verification/useVerificationOAuth/useVerificationOAuth.ts rename to src/hooks/verification/useDentityProfile/useDentityProfile.ts index b77123ccd..0bb5c9846 100644 --- a/src/hooks/verification/useVerificationOAuth/useVerificationOAuth.ts +++ b/src/hooks/verification/useDentityProfile/useDentityProfile.ts @@ -9,27 +9,25 @@ import { VerificationProtocol } from '@app/transaction-flow/input/VerifyProfile/ import { ConfigWithEns, CreateQueryKey, QueryConfig } from '@app/types' import { prepareQueryOptions } from '@app/utils/prepareQueryOptions' -import { getAPIEndpointForVerifier } from './utils/getAPIEndpointForVerifier' +import { type DentityFederatedToken } from '../useDentityToken/useDentityToken' type UseVerificationOAuthParameters = { - verifier?: VerificationProtocol | null - code?: string | null - onSuccess?: (resp: UseVerificationOAuthReturnType) => void + token?: DentityFederatedToken } -export type UseVerificationOAuthReturnType = { +export type UseDentityProfileReturnType = { verifier: VerificationProtocol - name: string - owner: Hash + name?: string + owner?: Hash | null manager?: Hash primaryName?: string - address: Hash - resolverAddress: Hash - verifiedPresentationUri: string + address?: Hash + resolverAddress?: Hash + verifiedPresentationUri?: string verificationRecord?: string } -type UseVerificationOAuthConfig = QueryConfig +type UseVerificationOAuthConfig = QueryConfig type QueryKey = CreateQueryKey< TParams, @@ -37,58 +35,46 @@ type QueryKey = CreateQueryKey< 'standard' > -export const getVerificationOAuth = +export const getDentityProfile = (config: ConfigWithEns) => async ({ - queryKey: [{ verifier, code }, chainId], - }: QueryFunctionContext>): Promise => { - // Get federated token from oidc worker - const url = getAPIEndpointForVerifier(verifier) - const response = await fetch(url, { - method: 'POST', - body: JSON.stringify({ code }), - }) - const json = await response.json() - - const { name } = json as UseVerificationOAuthReturnType - - if (!name) + queryKey: [{ token }, chainId], + }: QueryFunctionContext>): Promise => { + if (!token || !token.name || !token.verifiedPresentationUri) { return { - verifier, - ...json, + verifier: 'dentity', + ...token, } + } + + const { name } = token // Get resolver address since it will be needed for setting verification record const client = config.getClient({ chainId }) const records = await getRecords(client, { name, texts: [VERIFICATION_RECORD_KEY] }) - // Get owner data to const ownerData = await getOwner(client, { name }) const { owner, registrant, ownershipLevel } = ownerData || {} - const _owner = ownershipLevel === 'registrar' ? registrant : owner const manager = ownershipLevel === 'registrar' ? owner : undefined - const userWithSetRecordAbility = manager ?? _owner const primaryName = userWithSetRecordAbility ? await getName(client, { address: userWithSetRecordAbility }) : undefined - const data = { - ...json, - verifier, + ...token, + verifier: 'dentity' as const, owner: _owner, manager, - primaryName, + primaryName: primaryName?.name, resolverAddress: records.resolverAddress, verificationRecord: records.texts.find((text) => text.key === VERIFICATION_RECORD_KEY)?.value, } return data } -export const useVerificationOAuth = ({ +export const useDentityProfile = ({ enabled = true, - onSuccess, gcTime, staleTime, scopeKey, @@ -99,13 +85,13 @@ export const useVerificationOAuth = + +type QueryKey = CreateQueryKey< + TParams, + 'getDentityToken', + 'independent' +> + +export const getDentityToken = async ({ + queryKey: [{ code }], +}: QueryFunctionContext>): Promise => { + // Get federated token from oidc worker + const url = `${VERIFICATION_OAUTH_BASE_URL}/dentity/token` + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify({ code }), + }) + const json = await response.json() + + return json as UseDentityTokenReturnType +} + +export const useDentityToken = ({ + enabled = true, + gcTime, + scopeKey, + ...params +}: TParams & UseVerificationOAuthConfig) => { + const initialOptions = useQueryOptions({ + params, + scopeKey, + functionName: 'getDentityToken', + queryDependencyType: 'independent', + queryFn: getDentityToken, + }) + + const preparedOptions = prepareQueryOptions({ + queryKey: initialOptions.queryKey, + queryFn: initialOptions.queryFn, + enabled: enabled && !!params.code, + gcTime, + staleTime: Infinity, + retry: 0, + }) + + const query = useQuery(preparedOptions) + + return query +} diff --git a/src/hooks/verification/useVerificationOAuth/utils/getAPIEndpointForVerifier.ts b/src/hooks/verification/useVerificationOAuth/utils/getAPIEndpointForVerifier.ts deleted file mode 100644 index b49c6ce8c..000000000 --- a/src/hooks/verification/useVerificationOAuth/utils/getAPIEndpointForVerifier.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { match } from 'ts-pattern' - -import { VERIFICATION_OAUTH_BASE_URL } from '@app/constants/verification' - -export const getAPIEndpointForVerifier = (verifier?: string | null): string => { - return match(verifier) - .with('dentity', () => `${VERIFICATION_OAUTH_BASE_URL}/dentity/token`) - .otherwise(() => '') -} diff --git a/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts b/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts index fd1f2bcbe..105c8e370 100644 --- a/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts +++ b/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts @@ -7,17 +7,12 @@ import { useAccount } from 'wagmi' import type { VerificationErrorDialogProps } from '@app/components/pages/VerificationErrorDialog' import { DENTITY_ISS } from '@app/constants/verification' import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory' -import { VerificationProtocol } from '@app/transaction-flow/input/VerifyProfile/VerifyProfile-flow' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' -import { useVerificationOAuth } from '../useVerificationOAuth/useVerificationOAuth' +import { useDentityProfile } from '../useDentityProfile/useDentityProfile' +import { useDentityToken } from '../useDentityToken/useDentityToken' import { dentityVerificationHandler } from './utils/dentityHandler' -const issToVerificationProtocol = (iss: string | null): VerificationProtocol | null => { - if (iss === DENTITY_ISS) return 'dentity' - return null -} - type UseVerificationOAuthHandlerReturnType = { dialogProps: VerificationErrorDialogProps } @@ -32,14 +27,23 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn const { address: userAddress } = useAccount() - const isReady = !!createTransactionFlow && !!router && !!iss && !!code - - const { data, isLoading, error } = useVerificationOAuth({ - verifier: issToVerificationProtocol(iss), + const isReady = !!createTransactionFlow && !!router && !!iss && !!code && iss === DENTITY_ISS + const { data: dentityToken, isLoading: isDentityTokenLoading } = useDentityToken({ code, enabled: isReady, }) + const isReadyToFetchProfile = !!dentityToken && !isDentityTokenLoading + const { + data, + isLoading: isDentityProfileLoading, + error, + } = useDentityProfile({ + token: dentityToken, + enabled: isReadyToFetchProfile, + }) + + const isLoading = isDentityTokenLoading || isDentityProfileLoading const [dialogProps, setDialogProps] = useState() const onClose = () => setDialogProps(undefined) const onDismiss = () => setDialogProps(undefined) @@ -64,7 +68,7 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn setDialogProps(newProps) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, isLoading, error]) + }, [data, isDentityProfileLoading, error]) return { dialogProps, diff --git a/src/hooks/verification/useVerificationOAuthHandler/utils/createVerificationTransactionFlow.ts b/src/hooks/verification/useVerificationOAuthHandler/utils/createVerificationTransactionFlow.ts index 4a2dd12a0..4115d060a 100644 --- a/src/hooks/verification/useVerificationOAuthHandler/utils/createVerificationTransactionFlow.ts +++ b/src/hooks/verification/useVerificationOAuthHandler/utils/createVerificationTransactionFlow.ts @@ -3,10 +3,10 @@ import { Hash } from 'viem' import { createTransactionItem } from '@app/transaction-flow/transaction' import { CreateTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' -import { UseVerificationOAuthReturnType } from '../../useVerificationOAuth/useVerificationOAuth' +import { UseDentityProfileReturnType } from '../../useDentityProfile/useDentityProfile' type Props = Pick< - UseVerificationOAuthReturnType, + UseDentityProfileReturnType, 'name' | 'verifier' | 'resolverAddress' | 'verifiedPresentationUri' > & { userAddress?: Hash diff --git a/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts b/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts index 5a00fb73f..19e85809a 100644 --- a/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts +++ b/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts @@ -11,7 +11,7 @@ import { getDestination } from '@app/routes' import { CreateTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { shortenAddress } from '../../../../utils/utils' -import { UseVerificationOAuthReturnType } from '../../useVerificationOAuth/useVerificationOAuth' +import { UseDentityProfileReturnType } from '../../useDentityProfile/useDentityProfile' import { createVerificationTransactionFlow } from './createVerificationTransactionFlow' // Patterns @@ -49,7 +49,7 @@ export const dentityVerificationHandler = createTransactionFlow: CreateTransactionFlow t: TFunction }) => - (json: UseVerificationOAuthReturnType): VerificationErrorDialogProps => { + (json: UseDentityProfileReturnType): VerificationErrorDialogProps => { return match(json) .with( { From bec7dab3d67d988395ef9bdff6b5b63ea1895278 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Fri, 25 Oct 2024 00:05:36 +0800 Subject: [PATCH 20/29] used wrong boolean flag in dependency array --- .../useVerificationOAuthHandler/useVerificationOAuthHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts b/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts index 105c8e370..811e2e45c 100644 --- a/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts +++ b/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts @@ -68,7 +68,7 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn setDialogProps(newProps) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, isDentityProfileLoading, error]) + }, [data, isLoading, error]) return { dialogProps, From 021c6de05759b885e54ad46b3b0503fc11f30712 Mon Sep 17 00:00:00 2001 From: sugh01 <19183308+sugh01@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:31:50 +0100 Subject: [PATCH 21/29] rename a test --- e2e/specs/stateless/extendNames.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index e0dbea69b..7cadb3c67 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -11,7 +11,7 @@ import { daysToSeconds } from '@app/utils/time' import { test } from '../../../playwright' -test('should be able to register multiple names on the address page', async ({ +test('should be able to extend multiple names on the address page', async ({ page, accounts, login, @@ -91,7 +91,7 @@ test('should be able to register multiple names on the address page', async ({ await transactionModal.autoComplete() await expect(page.getByText('Your "Extend names" transaction was successful')).toBeVisible({ - timeout: 10000, + timeout: 15000, }) await subgraph.sync() From 3b6c34b5d26939924ba9071fce4a9466da6a6133 Mon Sep 17 00:00:00 2001 From: sugh01 <19183308+sugh01@users.noreply.github.com> Date: Tue, 29 Oct 2024 20:53:49 +0100 Subject: [PATCH 22/29] fix flakiness --- e2e/specs/stateless/extendNames.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index 7cadb3c67..b8f7cb05c 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -90,9 +90,7 @@ test('should be able to extend multiple names on the address page', async ({ await transactionModal.autoComplete() - await expect(page.getByText('Your "Extend names" transaction was successful')).toBeVisible({ - timeout: 15000, - }) + expect(page.getByText('Your "Extend names" transaction was successful')).toBeDefined() await subgraph.sync() // Should be able to remove this after useQuery is fixed. Using to force a refetch. From cf8d9010044a9963c2bba3941ca414d651df94f8 Mon Sep 17 00:00:00 2001 From: sugh01 <19183308+sugh01@users.noreply.github.com> Date: Tue, 29 Oct 2024 22:24:18 +0100 Subject: [PATCH 23/29] fix flakiness --- e2e/specs/stateless/extendNames.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index b8f7cb05c..fb2e7f9d8 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -89,8 +89,11 @@ test('should be able to extend multiple names on the address page', async ({ await page.waitForLoadState('networkidle') await transactionModal.autoComplete() + await page.waitForLoadState('networkidle') - expect(page.getByText('Your "Extend names" transaction was successful')).toBeDefined() + await expect(page.getByText('Your "Extend names" transaction was successful')).toBeVisible({ + timeout: 10000, + }) await subgraph.sync() // Should be able to remove this after useQuery is fixed. Using to force a refetch. From 5860ec0a1d4c8b060fd3a34d47b1823886788091 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Thu, 7 Nov 2024 01:52:35 +0800 Subject: [PATCH 24/29] fix e2e issues --- e2e/specs/stateless/extendNames.spec.ts | 111 ++++++++- package.json | 2 +- pnpm-lock.yaml | 231 +++++++++++++++--- .../PlusMinusControl/PlusMinusControl.tsx | 2 +- .../input/ExtendNames/ExtendNames-flow.tsx | 146 +++++------ 5 files changed, 381 insertions(+), 111 deletions(-) diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index fb2e7f9d8..745ce4b69 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -82,12 +82,16 @@ test('should be able to extend multiple names on the address page', async ({ await page.waitForLoadState('networkidle') await expect(page.getByTestId('invoice-item-0-amount')).not.toBeEmpty() await expect(page.getByTestId('invoice-item-1-amount')).not.toBeEmpty() - await expect(page.getByTestId('invoice-total')).not.toBeEmpty() + await expect(page.getByTestId('invoice-total')).not.toHaveText('0.0000 ETH') - page.locator('button:has-text("Next")').waitFor({ state: 'visible' }) - await page.locator('button:has-text("Next")').click() - await page.waitForLoadState('networkidle') + console.log(await page.getByTestId('invoice-total').textContent()) + // await page.locator('button:has-text("Next")').waitFor({ state: 'visible' }) + // await page.locator('button:has-text("Next")').click() + // await page.waitForLoadState('networkidle') + await page.getByTestId('extend-names-confirm').click() + + await expect(transactionModal.transactionModal).toBeVisible({ timeout: 10000 }) await transactionModal.autoComplete() await page.waitForLoadState('networkidle') @@ -109,6 +113,105 @@ test('should be able to extend multiple names on the address page', async ({ } }) +test('should be able to extend multiple names in grace period on the address page', async ({ + page, + accounts, + login, + subgraph, + makePageObject, + makeName, + time, +}) => { + // Generating names in not neccessary but we want to make sure that there are names to extend + await makeName([ + { + label: 'extend-legacy', + type: 'legacy', + owner: 'user2', + duration: -24 * 60 * 60, + }, + { + label: 'wrapped', + type: 'wrapped', + owner: 'user2', + duration: -24 * 60 * 60, + }, + ]) + + const address = accounts.getAddress('user2') + const addresPage = makePageObject('AddressPage') + const transactionModal = makePageObject('TransactionModal') + + await addresPage.goto(address) + await login.connect() + + await addresPage.selectToggle.click() + + // await page.pause() + await expect(await page.locator('.name-detail-item').count()).toBeGreaterThan(0) + const nameItems = await page.locator('.name-detail-item').all() + const nameItemTestIds = await Promise.all( + nameItems.map((item) => item.getAttribute('data-testid')), + ) + const extendableNameItems = nameItemTestIds + .filter((testid): testid is string => !!testid) + .map((testid) => testid.replace('name-item-', '')) + .filter((name) => { + const nameParts = name?.split('.') ?? [] + return nameParts.length === 2 && nameParts[1] === 'eth' + }) + + const timestampDict: { [key: string]: number } = {} + for (const name of extendableNameItems) { + const timestamp = await addresPage.getTimestamp(name) + timestampDict[name] = timestamp + } + await addresPage.extendNamesButton.click() + + // warning message + await expect(page.getByText('You do not own all these names')).toBeVisible() + await page.getByTestId('extend-names-confirm').click() + + // name list + await expect(page.getByText(`Extend ${extendableNameItems.length} Names`)).toBeVisible() + await page.locator('button:has-text("Next")').waitFor({ state: 'visible' }) + await page.locator('button:has-text("Next")').click() + + // check the invoice details + // TODO: Reimplement when date duration bug is fixed + // await expect(page.getByText('1 year extension', { exact: true })).toBeVisible() + await expect(page.getByTestId('plus-minus-control-label')).toHaveText('1 year') + await page.getByTestId('plus-minus-control-plus').click() + await expect(page.getByTestId('plus-minus-control-label')).toHaveText('2 years') + await page.getByTestId('plus-minus-control-plus').click() + await expect(page.getByTestId('plus-minus-control-label')).toHaveText('3 years') + await expect(page.getByTestId('invoice-item-0-amount')).not.toBeEmpty() + await expect(page.getByTestId('invoice-item-1-amount')).not.toBeEmpty() + await expect(page.getByTestId('invoice-total')).not.toHaveText('0.0000 ETH') + + // increment and save + await page.getByTestId('extend-names-confirm').click() + await expect(transactionModal.transactionModal).toBeVisible({ timeout: 10000 }) + await transactionModal.autoComplete() + + await expect(page.getByText('Your "Extend names" transaction was successful')).toBeVisible({ + timeout: 10000, + }) + await subgraph.sync() + + // Should be able to remove this after useQuery is fixed. Using to force a refetch. + await time.increaseTime({ seconds: 15 }) + await page.reload() + await page.waitForLoadState('networkidle') + for (const name of extendableNameItems) { + const label = name.replace('.eth', '') + await addresPage.search(label) + await expect(addresPage.getNameRow(name)).toBeVisible({ timeout: 5000 }) + await expect(await addresPage.getTimestamp(name)).not.toBe(timestampDict[name]) + await expect(await addresPage.getTimestamp(name)).toBe(timestampDict[name] + 31536000000 * 3) + } +}) + test('should be able to extend a single unwrapped name from profile', async ({ page, login, diff --git a/package.json b/package.json index d223d48ec..f7556d335 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13", "@openzeppelin/contracts": "^4.7.3", "@openzeppelin/test-helpers": "^0.5.16", - "@playwright/test": "^1.48.0", + "@playwright/test": "^1.48.2", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.0.0", "@testing-library/react-hooks": "^8.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bd38f822..aafa93edd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,8 +205,8 @@ importers: specifier: ^0.5.16 version: 0.5.16(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) '@playwright/test': - specifier: ^1.48.0 - version: 1.48.0 + specifier: ^1.48.2 + version: 1.48.2 '@testing-library/jest-dom': specifier: ^6.4.2 version: 6.4.5(@types/jest@29.5.12)(vitest@2.0.5(@types/node@18.19.33)(jsdom@24.1.0(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.31.5)) @@ -296,7 +296,7 @@ importers: version: 0.3.9 eslint-plugin-import: specifier: ^2.28.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: specifier: ^6.7.1 version: 6.8.0(eslint@8.50.0) @@ -424,6 +424,48 @@ importers: specifier: ^1.0.0-pre.53 version: 1.0.0-pre.53 + .yalc/@ensdomains/ens-test-env: + dependencies: + '@ethersproject/wallet': + specifier: ^5.6.0 + version: 5.7.0 + ansi-colors: + specifier: ^4.1.1 + version: 4.1.3 + cli-progress: + specifier: ^3.10.0 + version: 3.12.0 + commander: + specifier: ^9.3.0 + version: 9.5.0 + concurrently: + specifier: ^7.1.0 + version: 7.6.0 + docker-compose: + specifier: ^0.24.7 + version: 0.24.8 + dotenv: + specifier: ^16.0.1 + version: 16.4.5 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + lz4: + specifier: ^0.6.5 + version: 0.6.5 + progress-stream: + specifier: ^2.0.0 + version: 2.0.0 + tar-fs: + specifier: ^2.1.1 + version: 2.1.1 + viem: + specifier: ^2.21.37 + version: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + wait-on: + specifier: ^6.0.1 + version: 6.0.1 + packages: '@adobe/css-tools@4.3.3': @@ -435,6 +477,9 @@ packages: '@adraffy/ens-normalize@1.10.1': resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + '@adraffy/ens-normalize@1.11.0': + resolution: {integrity: sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -2327,6 +2372,10 @@ packages: '@noble/curves@1.4.0': resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==} + '@noble/curves@1.6.0': + resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.2.0': resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} @@ -2338,6 +2387,10 @@ packages: resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} + '@noble/secp256k1@1.7.1': resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} @@ -2577,8 +2630,8 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.48.0': - resolution: {integrity: sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==} + '@playwright/test@1.48.2': + resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==} engines: {node: '>=18'} hasBin: true @@ -2816,6 +2869,9 @@ packages: '@scure/base@1.1.6': resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==} + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + '@scure/bip32@1.1.5': resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} @@ -2825,6 +2881,9 @@ packages: '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + '@scure/bip32@1.5.0': + resolution: {integrity: sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==} + '@scure/bip39@1.1.1': resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} @@ -2834,6 +2893,9 @@ packages: '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + '@scure/bip39@1.4.0': + resolution: {integrity: sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==} + '@sentry/browser@7.43.0': resolution: {integrity: sha512-NlRkBYKb9o5IQdGY8Ktps19Hz9RdSuqS1tlLC7Sjr+MqZqSHmhKq8MWJKciRynxBeMbeGt0smExi9BqpVQdCEg==} engines: {node: '>=8'} @@ -3768,6 +3830,17 @@ packages: zod: optional: true + abitype@1.0.6: + resolution: {integrity: sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -6547,6 +6620,11 @@ packages: peerDependencies: ws: '*' + isows@1.0.6: + resolution: {integrity: sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==} + peerDependencies: + ws: '*' + isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} @@ -7909,13 +7987,13 @@ packages: pkg-types@1.1.1: resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} - playwright-core@1.48.0: - resolution: {integrity: sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==} + playwright-core@1.48.2: + resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==} engines: {node: '>=18'} hasBin: true - playwright@1.48.0: - resolution: {integrity: sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==} + playwright@1.48.2: + resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==} engines: {node: '>=18'} hasBin: true @@ -9706,6 +9784,14 @@ packages: typescript: optional: true + viem@2.21.40: + resolution: {integrity: sha512-no/mE3l7B0mdUTtvO7z/cTLENttQ/M7+ombqFGXJqsQrxv9wrYsTIGpS3za+FA5a447hY+x9D8Wxny84q1zAaA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + vite-node@2.0.5: resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} engines: {node: ^18.0.0 || >=20.0.0} @@ -10012,6 +10098,9 @@ packages: resolution: {integrity: sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==} engines: {node: '>=8.0.0'} + webauthn-p256@0.0.10: + resolution: {integrity: sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==} + webauthn-p256@0.0.5: resolution: {integrity: sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==} @@ -10255,6 +10344,18 @@ packages: utf-8-validate: optional: true + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xhr-request-promise@0.1.3: resolution: {integrity: sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==} @@ -10423,6 +10524,8 @@ snapshots: '@adraffy/ens-normalize@1.10.1': {} + '@adraffy/ens-normalize@1.11.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -12686,7 +12789,7 @@ snapshots: '@motionone/easing': 10.17.0 '@motionone/types': 10.17.0 '@motionone/utils': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/dom@10.17.0': dependencies: @@ -12695,12 +12798,12 @@ snapshots: '@motionone/types': 10.17.0 '@motionone/utils': 10.17.0 hey-listen: 1.0.8 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/easing@10.17.0': dependencies: '@motionone/utils': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/generators@10.17.0': dependencies: @@ -12711,7 +12814,7 @@ snapshots: '@motionone/svelte@10.16.4': dependencies: '@motionone/dom': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/types@10.17.0': {} @@ -12719,12 +12822,12 @@ snapshots: dependencies: '@motionone/types': 10.17.0 hey-listen: 1.0.8 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/vue@10.16.4': dependencies: '@motionone/dom': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@mswjs/cookies@0.2.2': dependencies: @@ -12797,12 +12900,18 @@ snapshots: dependencies: '@noble/hashes': 1.4.0 + '@noble/curves@1.6.0': + dependencies: + '@noble/hashes': 1.5.0 + '@noble/hashes@1.2.0': {} '@noble/hashes@1.3.3': {} '@noble/hashes@1.4.0': {} + '@noble/hashes@1.5.0': {} + '@noble/secp256k1@1.7.1': {} '@nodelib/fs.scandir@2.1.5': @@ -12998,9 +13107,9 @@ snapshots: '@pkgr/core@0.1.1': {} - '@playwright/test@1.48.0': + '@playwright/test@1.48.2': dependencies: - playwright: 1.48.0 + playwright: 1.48.2 '@polka/url@1.0.0-next.25': {} @@ -13415,7 +13524,7 @@ snapshots: '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.21.1 - viem: 2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - bufferutil - typescript @@ -13426,6 +13535,8 @@ snapshots: '@scure/base@1.1.6': {} + '@scure/base@1.1.9': {} + '@scure/bip32@1.1.5': dependencies: '@noble/hashes': 1.2.0 @@ -13444,6 +13555,12 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.6 + '@scure/bip32@1.5.0': + dependencies: + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.9 + '@scure/bip39@1.1.1': dependencies: '@noble/hashes': 1.2.0 @@ -13459,6 +13576,11 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.6 + '@scure/bip39@1.4.0': + dependencies: + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.9 + '@sentry/browser@7.43.0': dependencies: '@sentry/core': 7.43.0 @@ -14955,6 +15077,11 @@ snapshots: typescript: 5.4.5 zod: 3.23.8 + abitype@1.0.6(typescript@5.4.5)(zod@3.23.8): + optionalDependencies: + typescript: 5.4.5 + zod: 3.23.8 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -16326,7 +16453,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.6.3 dotenv@16.4.5: {} @@ -16657,7 +16784,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.50.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 @@ -16668,13 +16795,13 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint-plugin-jsx-a11y@6.8.0(eslint@8.50.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.50.0))(eslint-plugin-react@7.34.1(eslint@8.50.0))(eslint@8.50.0): dependencies: eslint: 8.50.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.50.0) eslint-plugin-react: 7.34.1(eslint@8.50.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.50.0) @@ -16688,8 +16815,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.50.0) eslint-plugin-react: 7.34.1(eslint@8.50.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.50.0) @@ -16711,13 +16838,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0): dependencies: debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.16.1 eslint: 8.50.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -16728,18 +16855,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -16749,7 +16876,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -18337,6 +18464,10 @@ snapshots: dependencies: ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + isows@1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)): + dependencies: + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + isstream@0.1.2: {} istanbul-lib-coverage@3.2.2: {} @@ -19469,7 +19600,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.6.3 nocache@3.0.4: {} @@ -19951,11 +20082,11 @@ snapshots: mlly: 1.7.0 pathe: 1.1.2 - playwright-core@1.48.0: {} + playwright-core@1.48.2: {} - playwright@1.48.0: + playwright@1.48.2: dependencies: - playwright-core: 1.48.0 + playwright-core: 1.48.2 optionalDependencies: fsevents: 2.3.2 @@ -20486,7 +20617,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.25.0 regexp.prototype.flags@1.5.2: dependencies: @@ -21949,6 +22080,24 @@ snapshots: - utf-8-validate - zod + viem@2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.5.0 + '@scure/bip39': 1.4.0 + abitype: 1.0.6(typescript@5.4.5)(zod@3.23.8) + isows: 1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + webauthn-p256: 0.0.10 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-node@2.0.5(@types/node@18.19.33)(terser@5.31.5): dependencies: cac: 6.7.14 @@ -22541,6 +22690,11 @@ snapshots: - supports-color - utf-8-validate + webauthn-p256@0.0.10: + dependencies: + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + webauthn-p256@0.0.5: dependencies: '@noble/curves': 1.4.0 @@ -22812,6 +22966,11 @@ snapshots: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + xhr-request-promise@0.1.3: dependencies: xhr-request: 1.1.0 diff --git a/src/components/@atoms/PlusMinusControl/PlusMinusControl.tsx b/src/components/@atoms/PlusMinusControl/PlusMinusControl.tsx index 188bae25b..e83ae7b64 100644 --- a/src/components/@atoms/PlusMinusControl/PlusMinusControl.tsx +++ b/src/components/@atoms/PlusMinusControl/PlusMinusControl.tsx @@ -252,7 +252,7 @@ export const PlusMinusControl = forwardRef( }} onBlur={handleBlur} /> - + } - trailing={ -