diff --git a/packages/ui/cypress/fixtures/watchAccounts/watchMultisigs.ts b/packages/ui/cypress/fixtures/watchAccounts/watchMultisigs.ts new file mode 100644 index 00000000..c5693ad4 --- /dev/null +++ b/packages/ui/cypress/fixtures/watchAccounts/watchMultisigs.ts @@ -0,0 +1,18 @@ +import { watchSignatories } from './watchSignatories' + +export const watchMultisigs = { + 'multisig-with-pure': { + name: 'Multisig With Pure', + address: '5Fa3UUF3S6SVdXZtPrCw2tGUqxJiRJLxEGfujozfZ4xFeAKn', + pureAddress: '5EfdqwwuyjjtEa4UhdjbZJu3UxHEHbzh8LMRvE13xTD7z6Wd', + threshold: 2, + signatories: [watchSignatories[0].address, watchSignatories[1].address] + }, + + 'multisig-without-pure': { + name: 'Multisig No Pure', + address: '5GysXAKXrGjNvpQruKWH3RwxtYrJqqWLN1A15gUMht6EXmzC', + threshold: 2, + signatories: [watchSignatories[2].address, watchSignatories[3].address] + } +} diff --git a/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts b/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts new file mode 100644 index 00000000..69f56b21 --- /dev/null +++ b/packages/ui/cypress/fixtures/watchAccounts/watchSignatories.ts @@ -0,0 +1,29 @@ +export const watchSignatories = [ + // signatories of multisig-with-pure + { + address: '5GGjPYsz8B8mxAzNScFNDPkZ1g97VWFCPCMexPSkPnibPBez', + name: 'Pure Signatory 1', + type: 'sr25519', + mnemonic: 'citizen heavy warrior cattle enter chef label split differ seek turtle gorilla' + }, + { + address: '5EkbU3anZKYP98aXF5MvmCUxvwvM4kxp7osc2Xhj1wHYL6ym', + name: 'Pure Signatory 2', + type: 'sr25519', + mnemonic: 'script spoon elder spawn kite burst theme property hip fatal flight amount' + }, + + // signatories of multisig-without-pure + { + address: '5HfzjVSWj6mxBnqgJhPfUTpkAJKro9BKToxXB3nozbu2MTpV', + name: 'No Pure Signatory 1', + type: 'sr25519', + mnemonic: 'spring banana desert horse ecology resist tag matrix burden heart stereo fix' + }, + { + address: '5Df1JyC6KSbjSp3pQEn85PCnvTtknGiN7JyE7bSZ9zqNL76E', + name: 'No Pure Signatory 2', + type: 'sr25519', + mnemonic: 'mutual pluck punch boy gym key brush dune master aunt track dynamic' + } +] diff --git a/packages/ui/cypress/support/page-objects/components/accountDisplay.ts b/packages/ui/cypress/support/page-objects/components/accountDisplay.ts new file mode 100644 index 00000000..a9810b08 --- /dev/null +++ b/packages/ui/cypress/support/page-objects/components/accountDisplay.ts @@ -0,0 +1,7 @@ +export const accountDisplay = { + identicon: () => cy.get('[data-cy=icon-identicon]'), + pureBadge: () => cy.get('[data-cy=badge-pure]'), + multisigBadge: () => cy.get('[data-cy=badge-multi]'), + nameLabel: () => cy.get('[data-cy=label-account-name]'), + addressLabel: () => cy.get('[data-cy=label-account-address]') +} diff --git a/packages/ui/cypress/support/page-objects/modals/editNamesModal.ts b/packages/ui/cypress/support/page-objects/modals/editNamesModal.ts new file mode 100644 index 00000000..04c4d362 --- /dev/null +++ b/packages/ui/cypress/support/page-objects/modals/editNamesModal.ts @@ -0,0 +1,7 @@ +export const editNamesModal = { + body: () => cy.get('[data-cy=modal-edit-names]'), + inputEditPureName: () => cy.get('[data-cy=input-edit-pure-name]'), + inputEditMultisigName: () => cy.get('[data-cy=input-edit-multisig-name]'), + inputEditSignatoryName: () => cy.get('[data-cy=input-edit-signatory-name]'), + saveButton: () => cy.get('[data-cy=button-save-edited-names]') +} diff --git a/packages/ui/cypress/support/page-objects/multisigPage.ts b/packages/ui/cypress/support/page-objects/multisigPage.ts index ec018975..189461bf 100644 --- a/packages/ui/cypress/support/page-objects/multisigPage.ts +++ b/packages/ui/cypress/support/page-objects/multisigPage.ts @@ -1,3 +1,9 @@ export const multisigPage = { - newTransactionButton: () => cy.get('[data-cy="button-new-transaction"]') + // header elements + accountHeader: () => cy.get('[data-cy=header-account]'), + seeOverviewButton: () => cy.get('[data-cy=button-see-overview]'), + newTransactionButton: () => cy.get('[data-cy=button-new-transaction]'), + optionsMenuButton: () => cy.get('[data-cy=button-options-menu]'), + editNamesMenuOption: () => cy.get('[data-cy=menu-option-edit-names]'), + subscanMenuOption: () => cy.get('[data-cy=menu-option-subscan]') } diff --git a/packages/ui/cypress/support/page-objects/notifications.ts b/packages/ui/cypress/support/page-objects/notifications.ts index 6494ce4b..dd7c7bdb 100644 --- a/packages/ui/cypress/support/page-objects/notifications.ts +++ b/packages/ui/cypress/support/page-objects/notifications.ts @@ -1,6 +1,6 @@ export const notifications = { - successNotificationIcon: () => cy.get('[data-cy="notification-icon-success"]'), - errorNotificationIcon: () => cy.get('[data-cy="notification-icon-error"]'), - loadingNotificationIcon: () => cy.get('[data-cy="notification-icon-loading"]'), - notificationWrapper: () => cy.get('[data-cy="notification-wrapper"]') + successNotificationIcon: () => cy.get('[data-cy=notification-icon-success]'), + errorNotificationIcon: () => cy.get('[data-cy=notification-icon-error]'), + loadingNotificationIcon: () => cy.get('[data-cy=notification-icon-loading]'), + notificationWrapper: () => cy.get('[data-cy=notification-wrapper]') } diff --git a/packages/ui/cypress/support/page-objects/sendTxModal.ts b/packages/ui/cypress/support/page-objects/sendTxModal.ts index 185ab428..c8d222a5 100644 --- a/packages/ui/cypress/support/page-objects/sendTxModal.ts +++ b/packages/ui/cypress/support/page-objects/sendTxModal.ts @@ -1,6 +1,6 @@ export const sendTxModal = { - sendTxTitle: () => cy.get('[data-cy="title-send-tx"]'), - fieldTo: () => cy.get('[data-cy="field-to"]'), - fieldAmount: () => cy.get('[data-cy="field-amount"]'), - buttonSend: () => cy.get('[data-cy="button-send"]') + sendTxTitle: () => cy.get('[data-cy=title-send-tx]'), + fieldTo: () => cy.get('[data-cy=field-to]'), + fieldAmount: () => cy.get('[data-cy=field-amount]'), + buttonSend: () => cy.get('[data-cy=button-send]') } diff --git a/packages/ui/cypress/support/page-objects/settingsPage.ts b/packages/ui/cypress/support/page-objects/settingsPage.ts index fe15e109..6b7254a9 100644 --- a/packages/ui/cypress/support/page-objects/settingsPage.ts +++ b/packages/ui/cypress/support/page-objects/settingsPage.ts @@ -3,9 +3,6 @@ export const settingsPage = { accountNameInput: () => cy.get('[data-cy=input-account-name]'), addButton: () => cy.get('[data-cy=button-add-watched-account]'), accountContainer: () => cy.get('[data-cy=container-account-details]', { timeout: 20000 }), - accountIcon: () => cy.get('[data-cy=icon-identicon]'), - accountNameLabel: () => cy.get('[data-cy=label-account-name]'), - accountAddressLabel: () => cy.get('[data-cy=label-account-address]'), accountDeleteButton: () => cy.get('[data-cy=button-delete-watched-account]'), errorLabel: () => cy.get('[data-cy=label-watch-account-error]') } diff --git a/packages/ui/cypress/support/page-objects/topMenuItems.ts b/packages/ui/cypress/support/page-objects/topMenuItems.ts index 57d88179..04cc6b72 100644 --- a/packages/ui/cypress/support/page-objects/topMenuItems.ts +++ b/packages/ui/cypress/support/page-objects/topMenuItems.ts @@ -1,4 +1,10 @@ export const topMenuItems = { - connectButton: () => cy.get('[data-cy="button-menu-connect"]'), - multiproxySelector: () => cy.get('[data-cy="select-multiproxy"]') + homeButton: () => cy.get('[data-cy=button-navigate-home]'), + newMultisigButton: () => cy.get('[data-cy=button-new-multisig]'), + settingsButton: () => cy.get('[data-cy=button-navigate-settings]'), + overviewButton: () => cy.get('[data-cy=button-navigate-overview]'), + 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]') } diff --git a/packages/ui/cypress/tests/watched-accounts.cy.ts b/packages/ui/cypress/tests/watched-accounts.cy.ts index 4c68958b..9de966d6 100644 --- a/packages/ui/cypress/tests/watched-accounts.cy.ts +++ b/packages/ui/cypress/tests/watched-accounts.cy.ts @@ -1,7 +1,12 @@ import { addresses } from '../fixtures/accounts' +import { accountDisplay } from '../support/page-objects/components/accountDisplay' import { landingPageUrl, settingsPageWatchAccountUrl } from '../fixtures/landingData' import { landingPage } from '../support/page-objects/landingPage' import { settingsPage } from '../support/page-objects/settingsPage' +import { topMenuItems } from '../support/page-objects/topMenuItems' +import { watchMultisigs } from '../fixtures/watchAccounts/watchMultisigs' +import { multisigPage } from '../support/page-objects/multisigPage' +import { editNamesModal } from '../support/page-objects/modals/editNamesModal' const addWatchAccount = (address: string, name?: string) => { settingsPage.accountAddressInput().type(`${address}{enter}`, { delay: 20 }) @@ -19,9 +24,9 @@ describe('Watched Accounts', () => { landingPage.watchAccountButton().click() addWatchAccount(addresses.Alice, 'Alice') settingsPage.accountContainer().within(() => { - settingsPage.accountIcon().should('be.visible') - settingsPage.accountAddressLabel().should('be.visible') - settingsPage.accountNameLabel().should('be.visible') + accountDisplay.identicon().should('be.visible') + accountDisplay.addressLabel().should('be.visible') + accountDisplay.nameLabel().should('be.visible') settingsPage.accountDeleteButton().should('be.visible') }) }) @@ -33,8 +38,8 @@ describe('Watched Accounts', () => { // now remove it settingsPage.accountContainer().within(() => { settingsPage.accountDeleteButton().click() - settingsPage.accountIcon().should('not.exist') - settingsPage.accountAddressLabel().should('not.exist') + accountDisplay.identicon().should('not.exist') + accountDisplay.addressLabel().should('not.exist') }) settingsPage.accountContainer().should('have.length', 0) }) @@ -58,4 +63,127 @@ describe('Watched Accounts', () => { settingsPage.accountContainer().should('have.length', 0) settingsPage.addButton().should('be.disabled') }) + + it('can see the expected account details displayed for a watched multisig', () => { + cy.visit(settingsPageWatchAccountUrl) + addWatchAccount( + watchMultisigs['multisig-without-pure'].address, + watchMultisigs['multisig-without-pure'].name + ) + // ensure the multisig name is displayed in the settings account container + settingsPage.accountContainer().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay + .nameLabel() + .should('be.visible') + .should('have.text', watchMultisigs['multisig-without-pure'].name) + }) + // ensure the name is included in the selectable drop-down option + topMenuItems.multiproxySelector().should('be.visible').first().click() + topMenuItems.multiproxySelectorOption().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay.multisigBadge().should('be.visible') + accountDisplay.pureBadge().should('not.exist') + accountDisplay.nameLabel().should('have.text', watchMultisigs['multisig-without-pure'].name) + }) + // ensure the name is displayed in the home page header + topMenuItems.homeButton().click() + multisigPage.accountHeader().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay.multisigBadge().should('be.visible') + accountDisplay.pureBadge().should('not.exist') + accountDisplay.nameLabel().should('have.text', watchMultisigs['multisig-without-pure'].name) + }) + }) + + it('can see the expected account details displayed for a watched pure', () => { + cy.visit(settingsPageWatchAccountUrl) + addWatchAccount( + watchMultisigs['multisig-with-pure'].pureAddress, + watchMultisigs['multisig-with-pure'].name + ) + // ensure the multisig name is displayed in the settings account container + settingsPage.accountContainer().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay + .nameLabel() + .should('be.visible') + .should('have.text', watchMultisigs['multisig-with-pure'].name) + }) + // ensure the name is included in the selectable drop-down option + topMenuItems.multiproxySelector().should('be.visible').first().click() + topMenuItems.multiproxySelectorOption().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay.pureBadge().should('be.visible') + accountDisplay.multisigBadge().should('not.exist') + accountDisplay.nameLabel().should('have.text', watchMultisigs['multisig-with-pure'].name) + }) + // navigate to the multisig page and ensure the name is included in the home page header + topMenuItems.homeButton().click() + multisigPage.accountHeader().within(() => { + accountDisplay.identicon().should('be.visible') + accountDisplay.pureBadge().should('be.visible') + accountDisplay.multisigBadge().should('not.exist') + accountDisplay.nameLabel().should('have.text', watchMultisigs['multisig-with-pure'].name) + }) + }) + + it('can edit the name of a watched pure', () => { + cy.visit(settingsPageWatchAccountUrl) + addWatchAccount( + watchMultisigs['multisig-with-pure'].pureAddress, + watchMultisigs['multisig-with-pure'].name + ) + // navigate to the home page and edit the name + topMenuItems.homeButton().click() + multisigPage.optionsMenuButton().click() + multisigPage.editNamesMenuOption().click() + editNamesModal.body().should('be.visible') + cy.clock() + editNamesModal.inputEditPureName().type(`{selectall}{del}${`Edited Name Test`}`) + // name edition is debounced by 300ms + cy.tick(300) + editNamesModal.saveButton().should('be.enabled').click() + // ensure the edited name is now displayed in the home page header + multisigPage.accountHeader().within(() => { + accountDisplay.nameLabel().should('have.text', 'Edited Name Test') + }) + // navigate to settings and ensure the edited name is displayed + cy.visit(settingsPageWatchAccountUrl) + settingsPage.accountContainer().within(() => { + accountDisplay.nameLabel().should('have.text', 'Edited Name Test') + }) + }) + + it('can open the correct subscan link for a watched pure', () => { + cy.visit(settingsPageWatchAccountUrl) + addWatchAccount( + watchMultisigs['multisig-with-pure'].pureAddress, + watchMultisigs['multisig-with-pure'].name + ) + topMenuItems.homeButton().click() + multisigPage.optionsMenuButton().click() + multisigPage.subscanMenuOption().should('be.visible') + // stub window.open to prevent opening a new tab + cy.window().then((win) => { + cy.stub(win, 'open').as('open') + }) + multisigPage.subscanMenuOption().click() + // ensure the correct subscan url is opened + cy.get('@open').should( + 'have.been.calledOnceWith', + `https://rococo.subscan.io/account/${watchMultisigs['multisig-with-pure'].pureAddress}` + ) + }) + + it('can not see the "New Transaction" button when only a watched account', () => { + cy.visit(settingsPageWatchAccountUrl) + addWatchAccount( + watchMultisigs['multisig-with-pure'].pureAddress, + watchMultisigs['multisig-with-pure'].name + ) + topMenuItems.homeButton().click() + multisigPage.accountHeader().should('be.visible') + multisigPage.newTransactionButton().should('not.exist') + }) }) diff --git a/packages/ui/src/components/AccountEditName.tsx b/packages/ui/src/components/AccountEditName.tsx index 300c52f9..f0e9484f 100644 --- a/packages/ui/src/components/AccountEditName.tsx +++ b/packages/ui/src/components/AccountEditName.tsx @@ -15,9 +15,10 @@ interface Props { className?: string address: string onNameChange: (args: OnChangeArgs) => void + testId?: string } -const AccountEditName = ({ address, onNameChange, className }: Props) => { +const AccountEditName = ({ address, onNameChange, className, testId }: Props) => { const { getNamesWithExtension } = useAccountNames() const { ownAddressList } = useAccounts() const [name, setName] = useState(getNamesWithExtension(address) || '') @@ -66,6 +67,7 @@ const AccountEditName = ({ address, onNameChange, className }: Props) => { onChange={onChange} disabled={isExtensionAccount} value={name} + data-cy={`input-edit-${testId}-name`} // onKeyDown={handleSpecialKeys} /> diff --git a/packages/ui/src/components/CallInfo.tsx b/packages/ui/src/components/CallInfo.tsx index 92cc7eef..031b5f3c 100644 --- a/packages/ui/src/components/CallInfo.tsx +++ b/packages/ui/src/components/CallInfo.tsx @@ -88,8 +88,8 @@ const handleBalanceDisplay = ({ const getTypeName = (index: number, name: string, value: any, api: ApiPromise) => { const [palletFromName, methodFromName] = name.split('.') - const pallet = value.section || palletFromName - const method = value.method || methodFromName + const pallet = value?.section || palletFromName + const method = value?.method || methodFromName const metaArgs = !!pallet && !!method && api.tx[pallet][method].meta.args return ( diff --git a/packages/ui/src/components/EasySetup/FromCallData.tsx b/packages/ui/src/components/EasySetup/FromCallData.tsx index c19ccc43..3b11b22d 100644 --- a/packages/ui/src/components/EasySetup/FromCallData.tsx +++ b/packages/ui/src/components/EasySetup/FromCallData.tsx @@ -30,8 +30,6 @@ const FromCallData = ({ className, onSetExtrinsic, isProxySelected, onSetErrorMe setIsProxyProxyRemoved(false) if (!api) return call - if (!isProxySelected) return call - const proxyProxyString = u8aToHex(api?.tx.proxy?.proxy.callIndex).toString() // check if this call is a proxy.proxy @@ -46,7 +44,7 @@ const FromCallData = ({ className, onSetExtrinsic, isProxySelected, onSetErrorMe setIsProxyProxyRemoved(true) return `0x${call.substring(74)}` as HexString }, - [api, isProxySelected] + [api] ) // users may erroneously paste callData from the multisig calldata @@ -106,12 +104,12 @@ const FromCallData = ({ className, onSetExtrinsic, isProxySelected, onSetErrorMe error={!!callDataError} fullWidth /> - {!!pastedCallData && !!pastedCallInfo && !callDataError && ( + {!!callInfo && !!pastedCallInfo && !callDataError && ( { {name} diff --git a/packages/ui/src/components/IdenticonBadge.tsx b/packages/ui/src/components/IdenticonBadge.tsx index b7ddbf19..85d2fe09 100644 --- a/packages/ui/src/components/IdenticonBadge.tsx +++ b/packages/ui/src/components/IdenticonBadge.tsx @@ -44,6 +44,7 @@ export const IdenticonBadge = ({ color="primary" badgeContent={badge} anchorOrigin={{ horizontal: 'left', vertical: 'top' }} + data-cy={`badge-${badge}`} > diff --git a/packages/ui/src/components/OptionsMenu.tsx b/packages/ui/src/components/OptionsMenu.tsx index 4d581714..8ee663ca 100644 --- a/packages/ui/src/components/OptionsMenu.tsx +++ b/packages/ui/src/components/OptionsMenu.tsx @@ -49,6 +49,7 @@ const OptionsMenu = ({ className, options, menuButtonBorder }: Props) => { aria-expanded={open ? 'true' : undefined} aria-haspopup="true" onClick={handleMenuClick} + data-cy="button-options-menu" > @@ -74,6 +75,7 @@ const OptionsMenu = ({ className, options, menuButtonBorder }: Props) => { className="menuEntry" key={option.text} onClick={() => handleClick(option.onClick)} + data-cy={`menu-option-${option.text.toLowerCase().replace(/ /g, '-')}`} > {option.icon} {option.text} diff --git a/packages/ui/src/components/modals/EditNames.tsx b/packages/ui/src/components/modals/EditNames.tsx index fdc33448..f9bb3bd9 100644 --- a/packages/ui/src/components/modals/EditNames.tsx +++ b/packages/ui/src/components/modals/EditNames.tsx @@ -52,6 +52,7 @@ const EditNames = ({ onClose, className }: Props) => { maxWidth={'sm'} open className={className} + data-cy="modal-edit-names" > Edit names @@ -68,6 +69,7 @@ const EditNames = ({ onClose, className }: Props) => { className="accountEdition" address={selectedMultiProxy.proxy || ''} onNameChange={onNameChange} + testId="pure" /> )} @@ -87,6 +89,7 @@ const EditNames = ({ onClose, className }: Props) => { className="accountEdition" address={address} onNameChange={onNameChange} + testId="multisig" /> ))} @@ -101,6 +104,7 @@ const EditNames = ({ onClose, className }: Props) => { className="accountEdition" address={signatory} onNameChange={onNameChange} + testId="signatory" /> ))} @@ -112,6 +116,7 @@ const EditNames = ({ onClose, className }: Props) => { diff --git a/packages/ui/src/components/select/MultiProxySelection.tsx b/packages/ui/src/components/select/MultiProxySelection.tsx index 65631fef..b4ba0403 100644 --- a/packages/ui/src/components/select/MultiProxySelection.tsx +++ b/packages/ui/src/components/select/MultiProxySelection.tsx @@ -88,10 +88,12 @@ const MultiProxySelection = ({ className }: Props) => { key={displayAddress} component="li" {...props} + data-cy="select-multiproxy-option" > ) diff --git a/packages/ui/src/pages/Home/HeaderView.tsx b/packages/ui/src/pages/Home/HeaderView.tsx index bdd1ce6e..0c9a9800 100644 --- a/packages/ui/src/pages/Home/HeaderView.tsx +++ b/packages/ui/src/pages/Home/HeaderView.tsx @@ -19,11 +19,12 @@ const HeaderView = () => { }, [selectedHasProxy, selectedMultiProxy]) return ( - + navigate('/overview')} variant="link" + data-cy="button-see-overview" > See overview @@ -38,7 +39,7 @@ const HeaderView = () => { Balance - +