From 87854ddacbf803eb3e54c50aa431565499b3b7b8 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan Date: Mon, 30 Oct 2023 13:13:08 +0100 Subject: [PATCH 01/15] happy path --- .../src/components/ConnectCreateOrWatch.tsx | 23 ++++- packages/ui/src/components/Header/Header.tsx | 2 + .../ui/src/contexts/MultiProxyContext.tsx | 90 ++++++++++++------- packages/ui/src/contexts/NetworkContext.tsx | 5 +- packages/ui/src/hooks/useSwitchAddress.tsx | 56 ++++++++++++ packages/ui/src/pages/Creation/index.tsx | 13 ++- packages/ui/src/pages/Home/HeaderView.tsx | 13 ++- packages/ui/src/pages/Home/Home.tsx | 5 +- packages/ui/src/utils/getMultiProxyAddress.ts | 10 +++ 9 files changed, 175 insertions(+), 42 deletions(-) create mode 100644 packages/ui/src/hooks/useSwitchAddress.tsx create mode 100644 packages/ui/src/utils/getMultiProxyAddress.ts diff --git a/packages/ui/src/components/ConnectCreateOrWatch.tsx b/packages/ui/src/components/ConnectCreateOrWatch.tsx index e8950c62..73169e2e 100644 --- a/packages/ui/src/components/ConnectCreateOrWatch.tsx +++ b/packages/ui/src/components/ConnectCreateOrWatch.tsx @@ -1,6 +1,6 @@ import { styled } from '@mui/material' import { useAccounts } from '../contexts/AccountsContext' -import { useNavigate } from 'react-router-dom' +import { createSearchParams, useNavigate, useSearchParams } from 'react-router-dom' import { Button } from './library' import { WATCH_ACCOUNT_ANCHOR } from '../pages/Settings/Settings' import { useWatchedAddresses } from '../contexts/WatchedAddressesContext' @@ -9,6 +9,7 @@ export const ConnectOrWatch = () => { const { isAllowedToConnectToExtension, allowConnectionToExtension } = useAccounts() const { watchedAddresses } = useWatchedAddresses() const navigate = useNavigate() + const [searchParams] = useSearchParams() return ( @@ -30,7 +31,15 @@ export const ConnectOrWatch = () => { {isAllowedToConnectToExtension ? ( + + + ) + } + return null } diff --git a/packages/ui/src/hooks/useMultisigsBySignatoriesOrWatchedSubscription.tsx b/packages/ui/src/hooks/useMultisigsBySignatoriesOrWatchedSubscription.tsx index 5609556c..4d02bb0e 100644 --- a/packages/ui/src/hooks/useMultisigsBySignatoriesOrWatchedSubscription.tsx +++ b/packages/ui/src/hooks/useMultisigsBySignatoriesOrWatchedSubscription.tsx @@ -57,6 +57,7 @@ export const useMultisigsBySignatoriesOrWatchedSubscription = ({ const { error, data, isLoading, refetch } = useSubscription( [`KeyMultisigsBySignatoriesOrWatched-${accountIds}-${watchedAccountIds}-${selectedNetwork}`], () => { + onUpdate(null) if (!client) return new Observable() return fromWsClientSubscription<{ diff --git a/packages/ui/src/hooks/usePureByIdSubscription.tsx b/packages/ui/src/hooks/usePureByIdSubscription.tsx index 728fba8c..b71e45f2 100644 --- a/packages/ui/src/hooks/usePureByIdSubscription.tsx +++ b/packages/ui/src/hooks/usePureByIdSubscription.tsx @@ -46,6 +46,7 @@ export const usePureByIdsSubscription = ({ onUpdate, pureIds }: Args) => { const { error, data, isLoading, refetch } = useSubscription( [`KeyWatchedPureById-${pureIds}-${selectedNetwork}`], () => { + onUpdate(null) if (!client) return new Observable() return fromWsClientSubscription<{ diff --git a/packages/ui/src/hooks/useSwitchAddress.tsx b/packages/ui/src/hooks/useSwitchAddress.tsx index b94dcdb2..6c2c6e06 100644 --- a/packages/ui/src/hooks/useSwitchAddress.tsx +++ b/packages/ui/src/hooks/useSwitchAddress.tsx @@ -15,7 +15,8 @@ export const useSwtichAddress = () => { isLoading: isMultiproxyLoading, selectMultiProxy, defaultAddress, - selectedMultiProxyAddress + selectedMultiProxyAddress, + setCanFindMultiProxyFromUrl } = useMultiProxy() const setAddress = useCallback( @@ -29,25 +30,48 @@ export const useSwtichAddress = () => { ) useEffect(() => { - if (isMultiproxyLoading || multiProxyList.length === 0) { + if (isMultiproxyLoading) { // we're not yet initialized return } - // no address in the url, init with the first multiProxy from the list if (!urlAddress && !!defaultAddress) { + // no address in the url, init with the first multiProxy from the list setAddress(defaultAddress) + return } - }, [defaultAddress, isMultiproxyLoading, multiProxyList.length, setAddress, urlAddress]) + }, [ + defaultAddress, + isMultiproxyLoading, + multiProxyList, + setAddress, + setCanFindMultiProxyFromUrl, + urlAddress + ]) // the url address is driving the UI if there's a mismatch // force the url address to match useEffect(() => { - if (!!urlAddress && urlAddress !== selectedMultiProxyAddress) { - console.log('missmatch use last', selectedMultiProxyAddress, urlAddress) - selectMultiProxy(urlAddress) + if (isMultiproxyLoading) { + // we're not yet initialized + return } - }, [urlAddress, multiProxyList, selectMultiProxy, defaultAddress, selectedMultiProxyAddress]) - return { currentAddress: urlAddress, setAddress } + if (!urlAddress || urlAddress === selectedMultiProxyAddress) { + setCanFindMultiProxyFromUrl(true) + } + + if (!!urlAddress && urlAddress !== selectedMultiProxyAddress) { + const isSuccess = selectMultiProxy(urlAddress) + setCanFindMultiProxyFromUrl(isSuccess) + } + }, [ + urlAddress, + multiProxyList, + selectMultiProxy, + defaultAddress, + selectedMultiProxyAddress, + isMultiproxyLoading, + setCanFindMultiProxyFromUrl + ]) } From f976561a827876e77cd8227ea318090d2919fef2 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan Date: Fri, 3 Nov 2023 00:19:16 +0000 Subject: [PATCH 06/15] fix tests --- packages/ui/src/contexts/MultiProxyContext.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/contexts/MultiProxyContext.tsx b/packages/ui/src/contexts/MultiProxyContext.tsx index e798209d..4d0c1638 100644 --- a/packages/ui/src/contexts/MultiProxyContext.tsx +++ b/packages/ui/src/contexts/MultiProxyContext.tsx @@ -115,6 +115,7 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { if (!!data?.accountMultisigs && data.accountMultisigs.length === 0) { setPureToQuery([]) setMultisigList([]) + setPureProxyList([]) } if (!!data?.accountMultisigs && data.accountMultisigs.length > 0) { @@ -155,6 +156,9 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { // add the selection to the pure to query setPureToQuery(Array.from(pureToQuerySet)) + // if there is nothing to query set the list to empty + // to signify that the pure proxies are done loading + pureToQuerySet.size === 0 && setPureProxyList([]) } setIsRefreshingMultiProxyList(false) @@ -314,7 +318,7 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { } return true - }, [multisigList, ownAddressList.length, pureProxyList, watchedAddresses.length]) + }, [multisigList, ownAddressList, pureProxyList, watchedAddresses]) const isLoading = useMemo( () => From e33ebd2a584314cc0883c17f54675f9e517bd071 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan Date: Fri, 3 Nov 2023 11:46:22 +0000 Subject: [PATCH 07/15] fix tests bis --- packages/ui/cypress/tests/watched-accounts.cy.ts | 4 +--- packages/ui/src/contexts/MultiProxyContext.tsx | 12 +++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/ui/cypress/tests/watched-accounts.cy.ts b/packages/ui/cypress/tests/watched-accounts.cy.ts index 4c2399c3..345ab260 100644 --- a/packages/ui/cypress/tests/watched-accounts.cy.ts +++ b/packages/ui/cypress/tests/watched-accounts.cy.ts @@ -142,14 +142,12 @@ describe('Watched Accounts', () => { const { name: pureName, purePublicKey } = watchMultisigs['multisig-with-pure'] cy.visitWithLocalStorage({ - url: settingsPageWatchAccountUrl, + url: landingPageUrl, accountNames: { [purePublicKey]: pureName }, watchedAccounts: [purePublicKey] }) - // navigate to the home page and edit the name - topMenuItems.homeButton().click() multisigPage.optionsMenuButton().click() multisigPage.editNamesMenuOption().click() editNamesModal.body().should('be.visible') diff --git a/packages/ui/src/contexts/MultiProxyContext.tsx b/packages/ui/src/contexts/MultiProxyContext.tsx index 4d0c1638..34c500d5 100644 --- a/packages/ui/src/contexts/MultiProxyContext.tsx +++ b/packages/ui/src/contexts/MultiProxyContext.tsx @@ -69,7 +69,6 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { const multiProxyList = useMemo(() => { return [...(pureProxyList || []), ...(multisigList || [])] }, [multisigList, pureProxyList]) - const selectedMultiProxyAddress = useMemo( () => getMultiProxyAddress(selectedMultiProxy), [selectedMultiProxy] @@ -115,7 +114,9 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { if (!!data?.accountMultisigs && data.accountMultisigs.length === 0) { setPureToQuery([]) setMultisigList([]) - setPureProxyList([]) + // watched addresses are part of the pure to query + // only signal we're done querying if there are no watched addresses + watchedAddresses.length === 0 && setPureProxyList([]) } if (!!data?.accountMultisigs && data.accountMultisigs.length > 0) { @@ -156,14 +157,15 @@ const MultiProxyContextProvider = ({ children }: MultisigContextProps) => { // add the selection to the pure to query setPureToQuery(Array.from(pureToQuerySet)) - // if there is nothing to query set the list to empty + + // if there is no pure to query set the PureProxyList to empty array // to signify that the pure proxies are done loading - pureToQuerySet.size === 0 && setPureProxyList([]) + pureToQuerySet.size === 0 && watchedAddresses.length === 0 && setPureProxyList([]) } setIsRefreshingMultiProxyList(false) }, - [] + [watchedAddresses] ) const refreshWatchedPureList = useCallback((data: PureByIdsSubscription | null) => { From a3e44d8a9bb0f2299d42ab970a832cfce0dfbe26 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan Date: Mon, 6 Nov 2023 12:28:55 +0000 Subject: [PATCH 08/15] lots of tests --- .../ui/cypress/fixtures/knownMultisigs.ts | 5 +- packages/ui/cypress/fixtures/landingData.ts | 2 + .../fixtures/setIdentity/addressBar.ts | 0 packages/ui/cypress/fixtures/testAccounts.ts | 15 +- .../watchAccounts/watchSignatories.ts | 3 +- packages/ui/cypress/support/Extension.ts | 84 +++--- packages/ui/cypress/support/commands.ts | 2 +- .../support/page-objects/landingPage.ts | 2 + .../support/page-objects/topMenuItems.ts | 6 +- packages/ui/cypress/tests/address-bar.cy.ts | 260 ++++++++++++++++++ .../ui/cypress/tests/landing-messaging.cy.ts | 2 +- packages/ui/cypress/tests/login.cy.ts | 4 +- packages/ui/cypress/tests/transactions.cy.ts | 8 +- .../ui/cypress/tests/watched-accounts.cy.ts | 3 +- .../components/select/MultiProxySelection.tsx | 1 + .../components/select/NetworkSelection.tsx | 2 + packages/ui/src/hooks/useDisplayError.tsx | 1 + 17 files changed, 351 insertions(+), 49 deletions(-) create mode 100644 packages/ui/cypress/fixtures/setIdentity/addressBar.ts create mode 100644 packages/ui/cypress/tests/address-bar.cy.ts diff --git a/packages/ui/cypress/fixtures/knownMultisigs.ts b/packages/ui/cypress/fixtures/knownMultisigs.ts index 95877cfc..c07b2c22 100644 --- a/packages/ui/cypress/fixtures/knownMultisigs.ts +++ b/packages/ui/cypress/fixtures/knownMultisigs.ts @@ -4,6 +4,9 @@ export const knownMultisigs = { 'test-multisig-1': { address: '5CmwqwwLEkEtsmB9gFaTJdCfurz33xyggHnvwHaGKtvmQNxq', threshold: 2, - signatories: [testAccounts['Test Account 1'].address, testAccounts['Test Account 2'].address] + signatories: [ + testAccounts['Multisig Member Account 1'].address, + testAccounts['Multisig Member Account 2'].address + ] } } diff --git a/packages/ui/cypress/fixtures/landingData.ts b/packages/ui/cypress/fixtures/landingData.ts index 43c566d6..fae9f141 100644 --- a/packages/ui/cypress/fixtures/landingData.ts +++ b/packages/ui/cypress/fixtures/landingData.ts @@ -4,3 +4,5 @@ export const landingPageUrl = `${baseUrl}?${networkParams}` export const settingsPageUrl = `${baseUrl}/settings?${networkParams}` const WATCH_ACCOUNT_ANCHOR = 'watched-accounts' export const settingsPageWatchAccountUrl = `${settingsPageUrl}#${WATCH_ACCOUNT_ANCHOR}` +export const landingPageAddressUrl = (address: string) => + `${baseUrl}?${networkParams}&address=${address}` diff --git a/packages/ui/cypress/fixtures/setIdentity/addressBar.ts b/packages/ui/cypress/fixtures/setIdentity/addressBar.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/ui/cypress/fixtures/testAccounts.ts b/packages/ui/cypress/fixtures/testAccounts.ts index 58eea27a..7f46ab3b 100644 --- a/packages/ui/cypress/fixtures/testAccounts.ts +++ b/packages/ui/cypress/fixtures/testAccounts.ts @@ -6,17 +6,17 @@ export interface InjectedAccountWitMnemonic extends InjectedAccount { } export const testAccounts = { - 'Test Account 1': { + 'Multisig Member Account 1': { address: '5H679cx9tkuHqyReUgBxeTqXKjVikVwLySDH1buiYuoqhi2w', publicKey: '0xde3ed24acdfe71c13b4d42539c5390ddee147ba6b29b0593ce842e77ff034445', - name: 'Test Account 1', + name: 'Multisig Member Account 1', type: 'sr25519', mnemonic: 'climb worth pioneer mushroom cloth expose tube high half final curtain toward' } as InjectedAccountWitMnemonic, - 'Test Account 2': { + 'Multisig Member Account 2': { address: '5GCXBrumiRDQ8KQsgbG39HdBNLQKt6XCQbeHZJccGdZbYTgt', publicKey: '0xb6e6fb4f2a2268bf6e8a211d958cbf602881418bcc533216cadfae3e24785f28', - name: 'Test Account 2', + name: 'Multisig Member Account 2', type: 'sr25519', mnemonic: 'divorce lottery slender again adapt process slow pigeon suit chase news begin' } as InjectedAccountWitMnemonic, @@ -33,5 +33,12 @@ export const testAccounts = { name: 'Non Multisig Member 2', type: 'sr25519', mnemonic: 'erosion never fee pill vocal fetch enforce soap betray zero answer hollow' + } as InjectedAccountWitMnemonic, + 'Many Multisig And Pure Member 1': { + address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', + publicKey: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', + name: 'Many Multisig And Pure Member 1', + type: 'sr25519', + mnemonic: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice' } as InjectedAccountWitMnemonic } diff --git a/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts b/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts index 8db4d0f8..2983720e 100644 --- a/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts +++ b/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts @@ -4,7 +4,8 @@ export const watchSignatories = [ address: '5GGjPYsz8B8mxAzNScFNDPkZ1g97VWFCPCMexPSkPnibPBez', name: 'Pure Signatory 1', type: 'sr25519', - mnemonic: 'citizen heavy warrior cattle enter chef label split differ seek turtle gorilla' + mnemonic: 'citizen heavy warrior cattle enter chef label split differ seek turtle gorilla', + publickey: '0xba1d098e50bdca49f03b9f0c702a65762a04dfc5868ae1c3788a2bc1939dbf4b' }, { address: '5E9XHcUfeDCL2HEvH8c8rcfroNDSzbLwhV5A1fq7J7RUwAkd', diff --git a/packages/ui/cypress/support/Extension.ts b/packages/ui/cypress/support/Extension.ts index 452f64a0..96f7fbce 100644 --- a/packages/ui/cypress/support/Extension.ts +++ b/packages/ui/cypress/support/Extension.ts @@ -4,6 +4,7 @@ import { InjectedAccountWitMnemonic } from '../fixtures/testAccounts' import { TypeRegistry } from '@polkadot/types' import { SignerPayloadJSON } from '@polkadot/types/types' import { cryptoWaitReady } from '@polkadot/util-crypto' +import { SignerResult } from '@polkadot/api/types' export interface AuthRequest { id: number @@ -29,6 +30,7 @@ export class Extension { accounts: InjectedAccountWitMnemonic[] = [] txRequests: TxRequests = {} keyring: Keyring | undefined + allowedOrigins: Record = {} reset = () => { this.authRequests = {} @@ -51,6 +53,49 @@ export class Extension { return { 'polkadot-js': { enable: (origin: string) => { + const resolvedObject = (selectedAccounts: InjectedAccountWitMnemonic[]) => ({ + accounts: { + get: () => selectedAccounts, + subscribe: (cb: (accounts: InjectedAccountWitMnemonic[]) => void) => + cb(selectedAccounts) + } as unknown as InjectedAccounts, + signer: { + signPayload: (payload: SignerPayloadJSON): Promise => { + return new Promise((resolve, reject) => { + const id = Date.now() + const res = () => { + const registry = new TypeRegistry() + registry.setSignedExtensions(payload.signedExtensions) + const pair = this.keyring?.getPair(this.accounts[0].address) + if (!pair) { + console.error('Pair not found') + return + } + const signature = registry + .createType('ExtrinsicPayload', payload, { + version: payload.version + }) + .sign(pair) + resolve({ id, signature: signature.signature }) + } + + const rej = (reason: string) => reject(new Error(reason)) + + this.txRequests[id] = { id, payload, resolve: res, reject: rej } + }) + } + } + }) + + // this app has already allowed some accounts + if (this.allowedOrigins[origin]?.length) { + console.log('Previously allowed origin', origin) + const res = resolvedObject( + this.accounts.filter(({ address }) => this.allowedOrigins[origin].includes(address)) + ) + return Promise.resolve(res) + } + return new Promise((resolve, reject) => { const timestamp = Date.now() const res = (accountAddresses: string[]) => { @@ -58,39 +103,12 @@ export class Extension { accountAddresses.includes(address) ) - resolve({ - accounts: { - get: () => selectedAccounts, - subscribe: (cb: (accounts: InjectedAccountWitMnemonic[]) => void) => - cb(selectedAccounts) - } as unknown as InjectedAccounts, - signer: { - signPayload: (payload: SignerPayloadJSON) => { - return new Promise((resolve, reject) => { - const id = Date.now() - const res = () => { - const registry = new TypeRegistry() - registry.setSignedExtensions(payload.signedExtensions) - const pair = this.keyring?.getPair(this.accounts[0].address) - if (!pair) { - console.error('Pair not found') - return - } - const signature = registry - .createType('ExtrinsicPayload', payload, { - version: payload.version - }) - .sign(pair) - resolve({ id, signature: signature.signature }) - } - - const rej = (reason: string) => reject(new Error(reason)) - - this.txRequests[id] = { id, payload, resolve: res, reject: rej } - }) - } - } - }) + // store the allowed accounts for this orgin + this.allowedOrigins[origin] = accountAddresses + console.log('storing for future', origin, accountAddresses) + + const res = resolvedObject(selectedAccounts) + resolve(res) } const rej = (reason: string) => reject(new Error(reason)) diff --git a/packages/ui/cypress/support/commands.ts b/packages/ui/cypress/support/commands.ts index bd0db879..c764f0ea 100644 --- a/packages/ui/cypress/support/commands.ts +++ b/packages/ui/cypress/support/commands.ts @@ -45,7 +45,7 @@ const LOCALSTORAGE_WATCHED_ACCOUNTS_KEY = 'multix.watchedAccount' // } const extension = new Extension() -const Account1 = testAccounts['Test Account 1'].address +const Account1 = testAccounts['Multisig Member Account 1'].address Cypress.Commands.add('initExtension', (accounts: InjectedAccountWitMnemonic[]) => { cy.log('Initializing extension') diff --git a/packages/ui/cypress/support/page-objects/landingPage.ts b/packages/ui/cypress/support/page-objects/landingPage.ts index 32a880d1..e31d0762 100644 --- a/packages/ui/cypress/support/page-objects/landingPage.ts +++ b/packages/ui/cypress/support/page-objects/landingPage.ts @@ -11,6 +11,8 @@ export const landingPage = { rpcLoader: () => cy.get('[data-cy=loader-rpc-connection]'), polkadotWikiLink: () => cy.get('[data-cy=link-polkadot-wiki]'), noAccountFoundError: () => cy.get('[data-cy=label-no-account-found]', { timeout: 10000 }), + linkedAddressNotFound: () => cy.get('[data-cy=label-linked-address-not-found]'), + resetLinkedAddressButton: () => cy.get('[data-cy=button-reset-linked-address]'), // page specific assertion shouldHaveNoAccountErrorAndWikiLink() { diff --git a/packages/ui/cypress/support/page-objects/topMenuItems.ts b/packages/ui/cypress/support/page-objects/topMenuItems.ts index d8dbfa9c..ca893399 100644 --- a/packages/ui/cypress/support/page-objects/topMenuItems.ts +++ b/packages/ui/cypress/support/page-objects/topMenuItems.ts @@ -7,5 +7,9 @@ export const topMenuItems = { aboutButton: () => cy.get('[data-cy=button-navigate-about]'), connectButton: () => cy.get('[data-cy=button-menu-connect]'), multiproxySelector: () => cy.get('[data-cy=select-multiproxy]', { timeout: 20000 }), - multiproxySelectorOption: () => cy.get('[data-cy=select-multiproxy-option]') + multiproxySelectorInput: () => cy.get('[data-cy=input-select-multiproxy]'), + multiproxySelectorOption: () => cy.get('[data-cy=select-multiproxy-option]'), + networkSelector: () => cy.get('[data-cy=select-networks]'), + networkSelectorOption: (networkName: string) => + cy.get(`[data-cy=select-network-option-${networkName}]`) } diff --git a/packages/ui/cypress/tests/address-bar.cy.ts b/packages/ui/cypress/tests/address-bar.cy.ts new file mode 100644 index 00000000..30714df6 --- /dev/null +++ b/packages/ui/cypress/tests/address-bar.cy.ts @@ -0,0 +1,260 @@ +import { knownMultisigs } from '../fixtures/knownMultisigs' +import { landingPageAddressUrl, landingPageUrl } from '../fixtures/landingData' +import { InjectedAccountWitMnemonic, testAccounts } from '../fixtures/testAccounts' +import { watchMultisigs } from '../fixtures/watchAccounts/watchMultisigs' +import { watchSignatories } from '../fixtures/watchAccounts/watchSignatories' +import { accountDisplay } from '../support/page-objects/components/accountDisplay' +import { landingPage } from '../support/page-objects/landingPage' +import { multisigPage } from '../support/page-objects/multisigPage' +import { topMenuItems } from '../support/page-objects/topMenuItems' +import { clickOnConnect } from '../utils/clickOnConnect' + +const initConnectAndRefreshWithAddress = ( + initAccounts: InjectedAccountWitMnemonic[], + connectedAddresses: string[], + addressUrl?: string +) => { + cy.visit(landingPageUrl) + cy.initExtension(Object.values(initAccounts)) + clickOnConnect() + cy.connectAccounts(connectedAddresses) + + //refresh the page now that the extension is allowed to connect + cy.visit(addressUrl ? landingPageAddressUrl(addressUrl) : landingPageUrl) + // inject the extension + cy.initExtension(initAccounts) +} + +describe('Account address in the address bar', () => { + it('shows multi and update address with 1 watched (multi), 0 connected account, no linked address', () => { + const { address, publicKey } = watchMultisigs['multisig-without-pure'] + + // we have a watched account that is a multisig + cy.visitWithLocalStorage({ + // any account + url: landingPageUrl, + watchedAccounts: [publicKey] + }) + + cy.url().should('include', address) + topMenuItems.multiproxySelectorInput().should('have.value', address) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', address.slice(0, 6)) + }) + }) + + it('shows multi and update address with 0 watched, 1 connected account (multi), no linked address', () => { + const { address } = knownMultisigs['test-multisig-1'] + + initConnectAndRefreshWithAddress( + [testAccounts['Multisig Member Account 1']], + [testAccounts['Multisig Member Account 1'].address], + undefined + ) + + cy.url().should('include', address) + topMenuItems.multiproxySelectorInput().should('have.value', address) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', address.slice(0, 6)) + }) + }) + + it('shows an error with 0 watched, 0 connected account, unknown linked address', () => { + cy.visit(landingPageAddressUrl(testAccounts['Non Multisig Member 1'].address)) + landingPage.linkedAddressNotFound().should('contain.text', "The linked address can't be found") + topMenuItems.multiproxySelector().should('not.exist') + }) + + it('shows an error and can reset with 1 watched (multi), 0 connected account, unknown linked address', () => { + const { publicKey, address: multisigAddress } = watchMultisigs['multisig-without-pure'] + + // we have a watched account that is a multisig + cy.visitWithLocalStorage({ + // any account + url: landingPageAddressUrl(testAccounts['Non Multisig Member 1'].address), + watchedAccounts: [publicKey] + }) + + landingPage.linkedAddressNotFound().should('contain.text', "The linked address can't be found") + cy.url().should('include', testAccounts['Non Multisig Member 1'].address) + topMenuItems.multiproxySelector().should('be.visible') + topMenuItems.multiproxySelectorInput().should('have.value', '') + + // click reset leads to the multisig + landingPage.resetLinkedAddressButton().click() + cy.url().should('not.include', testAccounts['Non Multisig Member 1'].address) + cy.url().should('include', multisigAddress) + topMenuItems.multiproxySelectorInput().should('have.value', multisigAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', multisigAddress.slice(0, 6)) + }) + }) + + it('shows an error and can reset with 1 watched (pure), 0 connected account, unknown linked address', () => { + const { purePublicKey, pureAddress } = watchMultisigs['multisig-with-pure'] + + // we have a watched account that is a pure + cy.visitWithLocalStorage({ + // unknown account in the url + url: landingPageAddressUrl(testAccounts['Non Multisig Member 1'].address), + watchedAccounts: [purePublicKey] + }) + landingPage.linkedAddressNotFound().should('contain.text', "The linked address can't be found") + cy.url().should('include', testAccounts['Non Multisig Member 1'].address) + topMenuItems.multiproxySelector().should('be.visible') + topMenuItems.multiproxySelectorInput().should('have.value', '') + + // click reset leads to the pure + landingPage.resetLinkedAddressButton().click() + cy.url().should('not.include', testAccounts['Non Multisig Member 1'].address) + cy.url().should('include', pureAddress) + topMenuItems.multiproxySelectorInput().should('have.value', pureAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', pureAddress.slice(0, 6)) + }) + }) + + it('shows an error and can reset with 0 watched, 1 connected account (multi), unknown linked address', () => { + const { address } = knownMultisigs['test-multisig-1'] + const nonMulitisigAccountAddress = testAccounts['Non Multisig Member 1'].address + + initConnectAndRefreshWithAddress( + [testAccounts['Multisig Member Account 1']], + [testAccounts['Multisig Member Account 1'].address], + nonMulitisigAccountAddress + ) + landingPage.linkedAddressNotFound().should('contain.text', "The linked address can't be found") + cy.url().should('include', nonMulitisigAccountAddress) + topMenuItems.multiproxySelector().should('be.visible') + topMenuItems.multiproxySelectorInput().should('have.value', '') + + // click reset leads to the multi + landingPage.resetLinkedAddressButton().click() + cy.url().should('not.include', nonMulitisigAccountAddress) + cy.url().should('include', address) + topMenuItems.multiproxySelectorInput().should('have.value', address) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', address.slice(0, 6)) + }) + }) + + it('shows the pure with 1 watched (pure), 0 connected account, pure linked address', () => { + const { purePublicKey, pureAddress } = watchMultisigs['multisig-with-pure'] + + // we have a watched account that is a pure + cy.visitWithLocalStorage({ + url: landingPageAddressUrl(pureAddress), + watchedAccounts: [purePublicKey] + }) + + cy.url().should('include', pureAddress) + topMenuItems.multiproxySelectorInput().should('have.value', pureAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', pureAddress.slice(0, 6)) + }) + }) + + it('shows the pure with 1 watched (multi), 0 connected account, pure linked address', () => { + const { publicKey, pureAddress } = watchMultisigs['multisig-with-pure'] + + // we have a watched account that is a pure + cy.visitWithLocalStorage({ + url: landingPageAddressUrl(pureAddress), + // here is the difference compared to previous test + watchedAccounts: [publicKey] + }) + + cy.url().should('include', pureAddress) + topMenuItems.multiproxySelectorInput().should('have.value', pureAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', pureAddress.slice(0, 6)) + }) + }) + + it('shows the pure with 1 watched (signatory pure), 0 connected account, pure linked address', () => { + const { pureAddress } = watchMultisigs['multisig-with-pure'] + const { publickey: signatoryPublicKey } = watchSignatories[0] + + // we have a watched account that is a pure + cy.visitWithLocalStorage({ + url: landingPageAddressUrl(pureAddress), + // here is the difference compared to previous test + watchedAccounts: [signatoryPublicKey!] + }) + + cy.url().should('include', pureAddress) + topMenuItems.multiproxySelectorInput().should('have.value', pureAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', pureAddress.slice(0, 6)) + }) + }) + + it('shows a pure with 0 watched, 1 connected account (many multi & pure), pure linked address', () => { + const expectedPureAddress = '5EXePPDNnucmLgrirMPQatFfu4WjncVbVoDZXx1gq75e3JcF' + initConnectAndRefreshWithAddress( + [testAccounts['Many Multisig And Pure Member 1']], + [testAccounts['Many Multisig And Pure Member 1'].address], + expectedPureAddress + ) + cy.url().should('include', expectedPureAddress) + topMenuItems.multiproxySelectorInput().should('have.value', expectedPureAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', expectedPureAddress.slice(0, 6)) + }) + }) + + it('shows a multi with 0 watched, 1 connected account (many multi & pure), multi linked address', () => { + const expectedMultiAddress = '5DxNgjvfJLfDTAAgFD1kWtJAh2KVNTTkwytr7S37dZwVpXd7' + initConnectAndRefreshWithAddress( + [testAccounts['Many Multisig And Pure Member 1']], + [testAccounts['Many Multisig And Pure Member 1'].address], + expectedMultiAddress + ) + cy.url().should('include', expectedMultiAddress) + topMenuItems.multiproxySelectorInput().should('have.value', expectedMultiAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', expectedMultiAddress.slice(0, 6)) + }) + }) + + it('switching accounts changes the address in the address bar', () => { + const expectedPureAddress = '5EXePPDNnucmLgrirMPQatFfu4WjncVbVoDZXx1gq75e3JcF' + const multiAddress = '5DxNgjvfJLfDTAAgFD1kWtJAh2KVNTTkwytr7S37dZwVpXd7' + const first6Letters = multiAddress.slice(0, 6) + + cy.visitWithLocalStorage({ + url: landingPageAddressUrl(expectedPureAddress), + watchedAccounts: [testAccounts['Many Multisig And Pure Member 1'].publicKey!] + }) + + // check that there is the pure address in the address bar + cy.url().should('include', expectedPureAddress) + topMenuItems + .desktopMenu() + .within(() => + topMenuItems.multiproxySelector().click().type(`${first6Letters}{downArrow}{enter}`) + ) + cy.url().should('include', multiAddress) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', multiAddress.slice(0, 6)) + }) + }) + + it('switching networks resets address in the address bar', () => { + const { address, publicKey } = watchMultisigs['multisig-without-pure'] + + // we have a watched account that is a multisig + cy.visitWithLocalStorage({ + // any account + url: landingPageUrl, + watchedAccounts: [publicKey] + }) + + // check that there is an address in the address bar + cy.url().should('include', address) + + topMenuItems.desktopMenu().within(() => topMenuItems.networkSelector().click()) + topMenuItems.networkSelectorOption('kusama').click() + cy.url().should('not.include', 'address=') + }) +}) diff --git a/packages/ui/cypress/tests/landing-messaging.cy.ts b/packages/ui/cypress/tests/landing-messaging.cy.ts index 45d75ff6..bd911874 100644 --- a/packages/ui/cypress/tests/landing-messaging.cy.ts +++ b/packages/ui/cypress/tests/landing-messaging.cy.ts @@ -23,7 +23,7 @@ describe('Landing Page Messaging', () => { it('can see an error when extension is connected but no account is shared', () => { cy.visit(landingPageUrl) - cy.initExtension([testAccounts['Test Account 1']]) + cy.initExtension([testAccounts['Multisig Member Account 1']]) clickOnConnect() // don't connect any of the initialized accounts cy.connectAccounts([]) diff --git a/packages/ui/cypress/tests/login.cy.ts b/packages/ui/cypress/tests/login.cy.ts index 922f436d..f1d4bc30 100644 --- a/packages/ui/cypress/tests/login.cy.ts +++ b/packages/ui/cypress/tests/login.cy.ts @@ -33,8 +33,8 @@ describe('Connect Account', () => { }) it('Connect Accounts', () => { - const address1 = testAccounts['Test Account 1'].address - const address2 = testAccounts['Test Account 2'].address + const address1 = testAccounts['Multisig Member Account 1'].address + const address2 = testAccounts['Multisig Member Account 2'].address cy.connectAccounts([address1, address2]) diff --git a/packages/ui/cypress/tests/transactions.cy.ts b/packages/ui/cypress/tests/transactions.cy.ts index 39eb13db..726c1104 100644 --- a/packages/ui/cypress/tests/transactions.cy.ts +++ b/packages/ui/cypress/tests/transactions.cy.ts @@ -7,7 +7,7 @@ import { sendTxModal } from '../support/page-objects/sendTxModal' import { clickOnConnect } from '../utils/clickOnConnect' import { waitForTxRequest } from '../utils/waitForTxRequests' -const testAccount1Address = testAccounts['Test Account 1'].address +const testAccount1Address = testAccounts['Multisig Member Account 1'].address const fillAndSubmitTransactionForm = () => { sendTxModal.sendTokensFieldTo().click().type(`${testAccount1Address}{enter}`) @@ -23,7 +23,7 @@ describe('Perform transactions', () => { cy.connectAccounts([testAccount1Address]) }) - it('Abort a tx with Alice', () => { + it('Abort a multisig tx', () => { multisigPage.newTransactionButton().click() sendTxModal.sendTxTitle().should('be.visible') fillAndSubmitTransactionForm() @@ -41,9 +41,9 @@ describe('Perform transactions', () => { }) }) - it('Makes a balance transfer with Alice', () => { + it('Makes a balance transfer multisig tx', () => { cy.rejectCurrentMultisigTx({ - account: testAccounts['Test Account 1'], + account: testAccounts['Multisig Member Account 1'], multisigInfo: { address: knownMultisigs['test-multisig-1'].address, threshold: knownMultisigs['test-multisig-1'].threshold, diff --git a/packages/ui/cypress/tests/watched-accounts.cy.ts b/packages/ui/cypress/tests/watched-accounts.cy.ts index 345ab260..72be1eef 100644 --- a/packages/ui/cypress/tests/watched-accounts.cy.ts +++ b/packages/ui/cypress/tests/watched-accounts.cy.ts @@ -22,7 +22,8 @@ const addWatchAccount = (address: string, name?: string) => { settingsPage.addButton().click() } -const { name: testAccountName, address: testAccountAddress } = testAccounts['Test Account 1'] +const { name: testAccountName, address: testAccountAddress } = + testAccounts['Multisig Member Account 1'] describe('Watched Accounts', () => { it('can add an account to the watch list', () => { diff --git a/packages/ui/src/components/select/MultiProxySelection.tsx b/packages/ui/src/components/select/MultiProxySelection.tsx index d4dc26d6..bb0c7e63 100644 --- a/packages/ui/src/components/select/MultiProxySelection.tsx +++ b/packages/ui/src/components/select/MultiProxySelection.tsx @@ -121,6 +121,7 @@ const MultiProxySelection = ({ className }: Props) => { ) }} + inputProps={{ ...params.inputProps, 'data-cy': 'input-select-multiproxy' }} onKeyDown={handleSpecialKeys} /> ) diff --git a/packages/ui/src/components/select/NetworkSelection.tsx b/packages/ui/src/components/select/NetworkSelection.tsx index 84ff08d6..27ef87cd 100644 --- a/packages/ui/src/components/select/NetworkSelection.tsx +++ b/packages/ui/src/components/select/NetworkSelection.tsx @@ -41,6 +41,7 @@ const NetworkSelection = () => { { autoWidth={true} onChange={handleNetworkSelection} MenuProps={MenuPropsStyles} + data-cy={`select-networks`} > Polkadot & Parachains {renderNetworks(polkadotNetworksAndParachains)} diff --git a/packages/ui/src/hooks/useDisplayError.tsx b/packages/ui/src/hooks/useDisplayError.tsx index 6db13993..24267fae 100644 --- a/packages/ui/src/hooks/useDisplayError.tsx +++ b/packages/ui/src/hooks/useDisplayError.tsx @@ -60,6 +60,7 @@ export const useDisplayError = () => { The linked address can't be found in your accounts or watched accounts.