From c4daca52030a78a5fb9cabec7b49df2935490873 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Fri, 15 Sep 2023 02:09:06 +0800 Subject: [PATCH] base implementation --- e2e/specs/stateless/ownership.spec.ts | 1110 +++++++++++++++++ e2e/specs/stateless/sendFlow.spec.ts | 494 -------- package.json | 2 +- playwright/pageObjects/editRolesModal.ts | 33 + playwright/pageObjects/index.ts | 4 + playwright/pageObjects/ownershipPage.ts | 51 + playwright/pageObjects/sendNameModal.ts | 28 +- pnpm-lock.yaml | 8 +- public/locales/en/common.json | 4 +- public/locales/en/profile.json | 11 +- .../[name]/tabs/OwnershipTab/OwnershipTab.tsx | 9 +- .../OwnershipTab/hooks/useOwnershipWarning.ts | 26 - .../hooks/useOwnershipWarning.tsx | 110 ++ .../sections/ExpirySection/ExpirySection.tsx | 9 +- .../ExpirySection/components/ExpiryPanel.tsx | 3 +- .../ExpirySection/hooks/useExpiryDetails.ts | 5 +- .../sections/RolesSection/RolesSection.tsx | 10 +- .../RolesSection/components/RoleRow.tsx | 4 +- .../RolesSection/components/RoleTag.tsx | 6 +- .../RolesSection/hooks/useRoleActions.tsx | 20 +- src/constants/resolverAddressData.ts | 376 ++++++ src/constants/resolverInterfaceIds.ts | 14 + src/hooks/abilities/useAbilities.ts | 87 +- src/hooks/abilities/utils/getSendAbilities.ts | 38 +- src/hooks/ownership/useRoles/useRoles.test.ts | 62 + src/hooks/ownership/useRoles/useRoles.ts | 83 +- .../useRoles/utils/getAvailableRoles.test.ts | 32 + .../ownership/useRoles/utils/getRoles.test.ts | 18 + .../ownership/useRoles/utils/getRoles.ts | 70 ++ .../resolver/useResolverSupportsInterface.ts | 55 +- src/hooks/useNameType.ts | 61 +- .../views/EditRoleView/EditRoleView.tsx | 1 + .../EditRoleView/views/EditRoleIntroView.tsx | 1 + .../views/EditRoleResultsView.tsx | 2 +- .../EditRoles/views/MainView/MainView.tsx | 2 +- .../views/MainView/components/RoleCard.tsx | 4 +- .../input/SendName/SendName-flow.tsx | 57 +- .../components/SearchViewResultsRow.tsx | 7 +- .../input/SendName/utils/checkCanSend.ts | 30 +- .../input/SendName/views/ConfirmationView.tsx | 6 +- .../SendName/views/SearchView/SearchView.tsx | 11 +- .../components/SearchViewResult.tsx | 14 +- .../views/SearchViewResultsView.tsx | 5 +- .../views/SummaryView/SummaryView.tsx | 40 +- .../SummaryView/components/SummarySection.tsx | 10 +- .../SyncManager/utils/checkCanSyncManager.ts | 20 +- src/transaction-flow/input/index.tsx | 7 +- src/transaction-flow/transaction/index.ts | 2 + .../transaction/resetProfileWithRecords.ts | 67 + src/utils/cacheKeyFactory.ts | 9 + src/utils/utils.ts | 7 + 51 files changed, 2356 insertions(+), 789 deletions(-) create mode 100644 e2e/specs/stateless/ownership.spec.ts delete mode 100644 e2e/specs/stateless/sendFlow.spec.ts create mode 100644 playwright/pageObjects/editRolesModal.ts create mode 100644 playwright/pageObjects/ownershipPage.ts delete mode 100644 src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.ts create mode 100644 src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.tsx create mode 100644 src/constants/resolverAddressData.ts create mode 100644 src/constants/resolverInterfaceIds.ts create mode 100644 src/hooks/ownership/useRoles/useRoles.test.ts create mode 100644 src/hooks/ownership/useRoles/utils/getAvailableRoles.test.ts create mode 100644 src/hooks/ownership/useRoles/utils/getRoles.test.ts create mode 100644 src/hooks/ownership/useRoles/utils/getRoles.ts create mode 100644 src/transaction-flow/transaction/resetProfileWithRecords.ts diff --git a/e2e/specs/stateless/ownership.spec.ts b/e2e/specs/stateless/ownership.spec.ts new file mode 100644 index 000000000..d59a03b22 --- /dev/null +++ b/e2e/specs/stateless/ownership.spec.ts @@ -0,0 +1,1110 @@ +import { expect } from '@playwright/test' +import { test } from '@root/playwright' + +test.describe('Send name', () => { + test('should be able to send owner, manager and eth-record of unwrapped 2ld name if you are the owner and manager on old resolver', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'other-manager', + type: 'legacy', + records: { + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user') }], + }, + }) + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + + // Should not be able to reset profile since old resolver does not support VersionableResolver + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toBeVisible() + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toBeVisible() + await expect(sendNameModal.summaryItem('reset-profile')).toHaveCount(0) + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') + }) + + test('should be able to send owner and manager of an unwrapped 2ld name if you are the owner but not manager', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'other-manager', + type: 'legacy', + manager: 'user2', + records: { + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user2') }], + }, + }) + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + + // Should not be able to reset profile since old resolver does not support VersionableResolver + // Should not be able to set eth record because user is not the manager + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toBeVisible() + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toHaveCount(0) + await expect(sendNameModal.summaryItem('reset-profile')).toHaveCount(0) + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('ETH record') + }) + + test('should not be able to send name if user is manager but not owner', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'other-manager', + type: 'legacy', + owner: 'user2', + manager: 'user', + records: { + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user2') }], + }, + }) + + const ownershipPage = makePageObject('OwnershipPage') + + await ownershipPage.goto(name) + await login.connect() + + await page.waitForTimeout(2000) + await expect(ownershipPage.sendNameButton).toHaveCount(0) + }) + + test('should be able to send owner, eth-record and reset profile of a wrapped 2ld name if you are the owner', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user2') }], + }, + }) + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const profilePage = makePageObject('ProfilePage') + const transactionModal = makePageObject('TransactionModal') + + await profilePage.goto(name) + await login.connect() + + await expect(profilePage.record('text', 'nickname')).toContainText('test') + + await ownershipPage.goto(name) + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await sendNameModal.resetProfileSwitch.check() + + // Should not be able to set manager because name is wrapped + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toBeVisible() + await expect(sendNameModal.summaryItem('manager')).toHaveCount(0) + await expect(sendNameModal.summaryItem('eth-record')).toBeVisible() + await expect(sendNameModal.summaryItem('reset-profile')).toBeVisible() + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).not.toContainText('manager') + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') + + await profilePage.goto(name) + await expect(profilePage.record('text', 'nickname')).toHaveCount(0) + }) + + test('should be able to send manager, eth-record of a unwrapped subname name if you are the manager', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'legacy', + owner: 'user2', + subnames: [ + { + label: 'test', + owner: 'user', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user') }], + }, + }, + ], + }) + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await expect(sendNameModal.resetProfileSwitch).toHaveCount(0) + + // Should not be able to set owner because name is unwrapped + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toHaveCount(0) + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toBeVisible() + await expect(sendNameModal.summaryItem('reset-profile')).toHaveCount(0) + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).not.toContainText('owner') + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') + }) + + test('should be able to send manager if user is parent owner and not manager of unwrapped subname', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'legacy', + owner: 'user', + subnames: [ + { + label: 'test', + owner: 'user2', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user2') }], + }, + }, + ], + }) + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await expect(sendNameModal.resetProfileSwitch).toHaveCount(0) + + // Should not be able to set owner because name is unwrapped + // Should not be able to set eth record because user is not the manager + // Should not be able to reset profile since old resolver does not support VersionableResolver + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toHaveCount(0) + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toHaveCount(0) + await expect(sendNameModal.summaryItem('reset-profile')).toHaveCount(0) + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user'))).not.toContainText( + 'parent-owner', + ) + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('ETH record') + }) + + test('should be able to send manager and eth-record if user is manager and not parent owner of unwrapped subname', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'legacy', + owner: 'user2', + subnames: [ + { + label: 'test', + owner: 'user', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user') }], + }, + }, + ], + }) + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await expect(sendNameModal.resetProfileSwitch).toHaveCount(0) + + // Should not be able to set owner because name is unwrapped + // Should not be able to set eth record because user is not the manager + // Should not be able to reset profile since old resolver does not support VersionableResolver + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toHaveCount(0) + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toBeVisible() + await expect(sendNameModal.summaryItem('reset-profile')).toHaveCount(0) + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { + timeout: 15000, + }) + await page.pause() + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('Parent owner') + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') + }) + + test('should be able to send manager if user is parent owner and not manager of wrapped subname', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user', + subnames: [ + { + label: 'test', + owner: 'user2', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user2') }], + }, + }, + ], + }) + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const profilePage = makePageObject('ProfilePage') + const transactionModal = makePageObject('TransactionModal') + + await profilePage.goto(subname) + await login.connect() + await expect(profilePage.record('text', 'nickname')).toContainText('test') + + await ownershipPage.goto(subname) + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await expect(sendNameModal.resetProfileSwitch).toHaveCount(0) + + // Should not be able to set owner because name is unwrapped + // Should not be able to set eth record because user is not the manager + // Should not be able to reset profile since user is not manager + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toHaveCount(0) + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toHaveCount(0) + await expect(sendNameModal.summaryItem('reset-profile')).toHaveCount(0) + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { + timeout: 15000, + }) + await page.pause() + await expect(ownershipPage.roleRow(accounts.getAddress('user'))).toContainText('Parent owner') + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('ETH record') + + await profilePage.goto(subname) + await page.waitForTimeout(2000) + await expect(profilePage.record('text', 'nickname')).toContainText('test') + }) + + test('should be able to send manager and eth-record if user is manager and not parent owner of wrapped subname', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user2', + subnames: [ + { + label: 'test', + owner: 'user', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user') }], + }, + }, + ], + }) + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const sendNameModal = makePageObject('SendNameModal') + const profilePage = makePageObject('ProfilePage') + const transactionModal = makePageObject('TransactionModal') + + await profilePage.goto(subname) + await login.connect() + await expect(profilePage.record('text', 'nickname')).toContainText('test') + + await ownershipPage.goto(subname) + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await sendNameModal.resetProfileSwitch.click() + + // Should not be able to set owner because name is unwrapped + // Should not be able to set eth record because user is not the manager + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toHaveCount(0) + await expect(sendNameModal.summaryItem('manager')).toBeVisible() + await expect(sendNameModal.summaryItem('eth-record')).toBeVisible() + await expect(sendNameModal.summaryItem('reset-profile')).toBeVisible() + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { + timeout: 15000, + }) + await page.pause() + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('Parent owner') + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') + + await profilePage.goto(subname) + await page.waitForTimeout(2000) + await expect(profilePage.record('text', 'nickname')).toHaveCount(0) + }) + + test('should be able to send owner and eth-record if user is owner and not parent owner of emancipated subname', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user2', + fuses: ['CANNOT_UNWRAP'], + subnames: [ + { + label: 'test', + owner: 'user', + records: { + texts: [{ key: 'name', value: 'test' }], + coinTypes: [{ key: 'ETH', value: accounts.getAddress('user') }], + }, + fuses: ['PARENT_CANNOT_CONTROL'], + }, + ], + }) + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const profilePage = makePageObject('ProfilePage') + const sendNameModal = makePageObject('SendNameModal') + const transactionModal = makePageObject('TransactionModal') + + await profilePage.goto(subname) + await login.connect() + await expect(profilePage.record('text', 'nickname')).toContainText('test') + + await ownershipPage.goto(subname) + await ownershipPage.sendNameButton.click() + await sendNameModal.searchInput.fill(accounts.getAddress('user3')) + await sendNameModal.searchResult(accounts.getAddress('user3')).click() + await sendNameModal.resetProfileSwitch.check() + + await page.pause() + // Should not be able to set owner because name is unwrapped + // Should not be able to set eth record because user is not the manager + // Should not be able to reset profile since old resolver does not support VersionableResolver + await sendNameModal.summaryHeader.click() + await expect(sendNameModal.summaryItem('owner')).toBeVisible() + await expect(sendNameModal.summaryItem('manager')).toHaveCount(0) + await expect(sendNameModal.summaryItem('eth-record')).toBeVisible() + await expect(sendNameModal.summaryItem('reset-profile')).toBeVisible() + + await sendNameModal.sendButton.click() + await sendNameModal.confirmButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).not.toContainText('manager') + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') + + await profilePage.goto(subname) + await page.waitForTimeout(2000) + await expect(profilePage.record('text', 'nickname')).toHaveCount(0) + }) +}) + +test.describe('Edit roles: Happy ', () => { + test('Should allow owner to change manager', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + manager: 'user2', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.setToSelfButton.click() + await editRolesModal.saveButton.click() + await transactionModal.autoComplete() + + await expect(ownershipPage.roleRow(accounts.getAddress('user'))).toContainText('manager') + }) + + test('Should allow manager to change manager when they are not the owner', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + owner: 'user2', + manager: 'user', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.editRolesButton.click() + + // Should not allow the manager to change the owner + await expect(editRolesModal.roleCard('owner')).toHaveCount(0) + await editRolesModal.roleCardChangeButton('manager').click() + await page.pause() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) + + test('Should allow owner to change owner', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + owner: 'user', + manager: 'user2', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('owner').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner') + }) + + test('Should allow owner to change manager if they are not the manager', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + owner: 'user', + manager: 'user2', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) + + test('Should allow owner to change owner and manager', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + owner: 'user', + manager: 'user2', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('owner').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner', { + timeout: 15000, + }) + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) +}) + +test.describe('Edit roles: Unwrapped subnames', () => { + test('Should allow unwrapped subname to be sent by owner (setOwner)', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + owner: 'user2', + manager: 'user2', + subnames: [ + { + label: 'test', + owner: 'user', + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) + + test('Should allow unwrapped subname to be sent by unwrapped parent owner (setSubnodeOwner)', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + subnames: [ + { + label: 'test', + owner: 'user2', + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) + + test('should NOT show send button when subname is wrapped and parent is unwrapped', async ({ + page, + login, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + subnames: [ + { + label: 'test', + type: 'wrapped', + owner: 'user2', + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + + await ownershipPage.goto(subname) + await login.connect() + + await page.waitForTimeout(2000) + await expect(ownershipPage.sendNameButton).toHaveCount(0) + await expect(ownershipPage.editRolesButton).toHaveCount(0) + }) +}) + +test.describe('Edit roles: Unwrapped name', () => { + test('should NOT show send button when parent is owner and not manager', async ({ + page, + login, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'unwrapped', + type: 'legacy', + owner: 'user', + manager: 'user2', + subnames: [ + { + label: 'test', + owner: 'user2', + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + + await ownershipPage.goto(subname) + await login.connect() + + await page.waitForTimeout(2000) + await expect(ownershipPage.sendNameButton).toHaveCount(0) + await expect(ownershipPage.editRolesButton).toHaveCount(0) + }) +}) + +test.describe('Edit roles: Wrapped subnames', () => { + test('should allow namewrapper subname owner to send name', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user2', + subnames: [ + { + label: 'test', + owner: 'user', + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.editRolesButton.click() + + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) + + test('should allow parent owner to send name', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user', + subnames: [ + { + label: 'test', + owner: 'user2', + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('manager').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager') + }) +}) + +test.describe('Edit roles: Wrapped subname with PCC burned', () => { + test('should NOT allow parent owner to transfer', async ({ + page, + login, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user', + fuses: ['CANNOT_UNWRAP'], + subnames: [ + { + label: 'test', + owner: 'user2', + fuses: ['PARENT_CANNOT_CONTROL'], + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + + await ownershipPage.goto(subname) + await login.connect() + + await page.waitForTimeout(2000) + await expect(ownershipPage.sendNameButton).toHaveCount(0) + await expect(ownershipPage.editRolesButton).toHaveCount(0) + }) + + test('should allow name owner to transfer', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + owner: 'user2', + fuses: ['CANNOT_UNWRAP'], + subnames: [ + { + label: 'test', + owner: 'user', + fuses: ['PARENT_CANNOT_CONTROL'], + }, + ], + }) + + const subname = `test.${name}` + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(subname) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('owner').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner') + }) +}) + +test.describe('Edit roles: Wrapped name', () => { + test('Should allow namewrapper owner to send name', async ({ + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'wrapped', + type: 'wrapped', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + const editRolesModal = makePageObject('EditRolesModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.editRolesButton.click() + await editRolesModal.roleCardChangeButton('owner').click() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) + await editRolesModal.searchResult(accounts.getAddress('user3')).click() + await editRolesModal.saveButton.click() + + await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner') + }) +}) + +test.describe('Extend name', () => { + test('should be able to extend unwrapped name', async ({ + page, + login, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'legacy', + type: 'legacy', + owner: 'user2', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const extendNamesModal = makePageObject('ExtendNamesModal') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(name) + await login.connect() + + const timestamp = await ownershipPage.getExpiryTimestamp() + + await ownershipPage.extendButton.click() + + await test.step('should show the correct price data', async () => { + await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('0.0033') + await expect(extendNamesModal.getInvoiceTransactionFee).toContainText('0.0001') + await expect(extendNamesModal.getInvoiceTotal).toContainText('0.0034') + await expect(page.getByText('1 year extension')).toBeVisible() + }) + + await test.step('should show the cost comparison data', async () => { + await expect(page.getByTestId('year-marker-0')).toContainText('4% gas') + await expect(page.getByTestId('year-marker-1')).toContainText('2% gas') + await expect(page.getByTestId('year-marker-2')).toContainText('1% gas') + }) + + await test.step('should work correctly with plus minus control', async () => { + await expect(extendNamesModal.getCounterMinusButton).toBeDisabled() + await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('0.0033') + await extendNamesModal.getCounterPlusButton.click() + await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('0.0065') + await expect(page.locator('text=2 year extension')).toBeVisible() + }) + + await test.step('should show correct fiat values', async () => { + await extendNamesModal.getCurrencyToggle.click({ force: true }) + await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('$10.00') + await expect(extendNamesModal.getInvoiceTransactionFee).toContainText('$0.20') + await expect(extendNamesModal.getInvoiceTotal).toContainText('$10.20') + await extendNamesModal.getCounterMinusButton.click() + await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('$5.00') + await expect(extendNamesModal.getInvoiceTransactionFee).toContainText('$0.20') + await expect(extendNamesModal.getInvoiceTotal).toContainText('$5.20') + }) + + await test.step('should extend', async () => { + await extendNamesModal.getExtendButton.click() + await expect( + page.getByText('Extending this name will not give you ownership of it'), + ).toBeVisible() + await transactionModal.autoComplete() + const newTimestamp = await ownershipPage.getExpiryTimestamp() + await expect(newTimestamp).toEqual(timestamp + 31536000000) + }) + }) +}) + +test.describe('Sync manager', () => { + test('should be able to sync manager as owner of unwrapped 2ld', async ({ + page, + login, + accounts, + makeName, + makePageObject, + }) => { + const name = await makeName({ + label: 'other-manager', + type: 'legacy', + owner: 'user', + manager: 'user2', + }) + + const ownershipPage = makePageObject('OwnershipPage') + const transactionModal = makePageObject('TransactionModal') + + await ownershipPage.goto(name) + await login.connect() + + await ownershipPage.syncManagerButton.click() + await page.getByRole('button', { name: 'Next' }).click() + + await await transactionModal.autoComplete() + await expect(ownershipPage.roleRow(accounts.getAddress('user'))).toContainText('manager') + await expect(ownershipPage.roleRow(accounts.getAddress('user'))).toContainText('owner') + }) +}) diff --git a/e2e/specs/stateless/sendFlow.spec.ts b/e2e/specs/stateless/sendFlow.spec.ts deleted file mode 100644 index cdf2ce604..000000000 --- a/e2e/specs/stateless/sendFlow.spec.ts +++ /dev/null @@ -1,494 +0,0 @@ -import { expect } from '@playwright/test' -import { test } from '@root/playwright' - -test.describe('Happy', () => { - test('Should allow owner to change manager', async ({ - page, - login, - makeName, - accounts, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - manager: 'user2', - }) - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(name) - await login.connect() - - await morePage.sendButton.click() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await page.getByTestId('owner-checkbox').click() - await sendNameModal.clickNextButton() - await transactionModal.autoComplete() - - await expect(page.getByTestId('owner-button-name-name.manager')).toHaveText( - new RegExp(accounts.getAddress('user3', 4)), - ) - }) - - test('Should allow manager to change manager when they are not the owner', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - owner: 'user2', - manager: 'user', - }) - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(name) - await login.connect() - - await morePage.sendButton.click() - - // Should not allow the manager to change the owner - await expect(page.getByTestId('Make Owner')).toHaveCount(0) - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - - await transactionModal.autoComplete() - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) - - test('Should allow owner to change owner', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - owner: 'user', - manager: 'user2', - }) - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(name) - await login.connect() - - await morePage.sendButton.click() - await page.getByTestId('manager-checkbox').click() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - - await transactionModal.autoComplete() - - await expect(page.getByTestId('owner-button-name-name.owner')).toContainText( - accounts.getAddress('user3', 4), - ) - }) - - test('Should allow owner to change manager if they are not the manager', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - owner: 'user', - manager: 'user2', - }) - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(name) - await login.connect() - // () - - await morePage.sendButton.click() - - await page.getByTestId('owner-checkbox').click() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - - await transactionModal.autoComplete() - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) - - test('Should allow owner to change owner and manager', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - owner: 'user', - manager: 'user2', - }) - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(name) - await login.connect() - - await morePage.sendButton.click() - - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - await transactionModal.confirm() - await sendNameModal.clickNextButton() - await page.getByText('Back').click() - - // Should work after going back after first transaction - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - - await transactionModal.autoComplete() - await expect(page.getByTestId('owner-button-name-name.owner')).toContainText( - accounts.getAddress('user3', 4), - { timeout: 15000 }, - ) - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) -}) - -test.describe('Unwrapped subnames', () => { - test('Should allow unwrapped subname to be sent by owner (setOwner)', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - owner: 'user2', - manager: 'user2', - subnames: [ - { - label: 'test', - owner: 'user', - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(subname) - await login.connect() - - await morePage.sendButton.click() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - - await transactionModal.autoComplete() - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) - - test('Should allow unwrapped subname to be sent by unwrapped parent owner (setSubnodeOwner)', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - subnames: [ - { - label: 'test', - owner: 'user2', - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(subname) - await login.connect() - - await morePage.sendButton.click() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - await transactionModal.autoComplete() - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) - - test('should NOT show send button when subname is wrapped and parent is unwrapped', async ({ - login, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - subnames: [ - { - label: 'test', - type: 'wrapped', - owner: 'user2', - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - - await morePage.goto(subname) - await login.connect() - - await expect(morePage.sendButton).toHaveCount(0) - }) -}) - -test.describe('Unwrapped name', () => { - test('should NOT show send button when parent is owner and not manager', async ({ - login, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'unwrapped', - type: 'legacy', - owner: 'user', - manager: 'user2', - subnames: [ - { - label: 'test', - owner: 'user2', - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - - await morePage.goto(subname) - await login.connect() - - await expect(morePage.sendButton).toHaveCount(0) - }) -}) - -test.describe('Wrapped subnames', () => { - test('should allow namewrapper subname owner to send name', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'wrapped', - type: 'wrapped', - owner: 'user2', - subnames: [ - { - label: 'test', - owner: 'user', - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(subname) - await login.connect() - - await morePage.sendButton.click() - - await expect(page.getByText('Make manager')).toBeVisible() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - await transactionModal.autoComplete() - - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) - - test('should allow parent owner to send name', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'wrapped', - type: 'wrapped', - owner: 'user', - subnames: [ - { - label: 'test', - owner: 'user2', - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(subname) - await login.connect() - - await morePage.sendButton.click() - await expect(page.getByText('Make manager')).toBeVisible() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - await transactionModal.autoComplete() - - await expect(page.getByTestId('owner-button-name-name.manager')).toContainText( - accounts.getAddress('user3', 4), - ) - }) -}) - -test.describe('Wrapped subname with PCC burned', () => { - test('should NOT allow parent owner to transfer', async ({ login, makeName, makePageObject }) => { - const name = await makeName({ - label: 'wrapped', - type: 'wrapped', - owner: 'user', - fuses: ['CANNOT_UNWRAP'], - subnames: [ - { - label: 'test', - owner: 'user2', - fuses: ['PARENT_CANNOT_CONTROL'], - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - - await morePage.goto(subname) - await login.connect() - - await expect(morePage.sendButton).toHaveCount(0) - }) - - test('should allow name owner to transfer', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'wrapped', - type: 'wrapped', - owner: 'user2', - fuses: ['CANNOT_UNWRAP'], - subnames: [ - { - label: 'test', - owner: 'user', - fuses: ['PARENT_CANNOT_CONTROL'], - }, - ], - }) - - const subname = `test.${name}` - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(subname) - await login.connect() - - await morePage.sendButton.click() - await expect(page.getByText('Make owner')).toBeVisible() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - await transactionModal.autoComplete() - - await page.getByTestId('profile-tab').click() - await expect(page.getByTestId('owner-profile-button-name.owner')).toContainText( - accounts.getAddress('user3', 4), - ) - }) -}) - -test.describe('Wrapped name', () => { - test('Should allow namewrapper owner to send name', async ({ - page, - login, - accounts, - makeName, - makePageObject, - }) => { - const name = await makeName({ - label: 'wrapped', - type: 'wrapped', - }) - - const morePage = makePageObject('MorePage') - const transactionModal = makePageObject('TransactionModal') - const sendNameModal = makePageObject('SendNameModal') - - await morePage.goto(name) - await login.connect() - - await morePage.sendButton.click() - await expect(page.getByText('Make owner')).toBeVisible() - await page.getByTestId('dogfood').type(accounts.getAddress('user3')) - await sendNameModal.clickNextButton() - await transactionModal.autoComplete() - await expect(page.getByTestId('owner-button-name-name.owner')).toContainText( - accounts.getAddress('user3', 4), - ) - }) -}) diff --git a/package.json b/package.json index 8d09f1e47..b793ebbff 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@ensdomains/ens-validation": "^0.1.0", "@ensdomains/ensjs": "3.0.0-alpha.66", "@ensdomains/eth-ens-namehash": "^2.0.15", - "@ensdomains/thorin": "0.6.43", + "@ensdomains/thorin": "0.6.44", "@ethersproject/abi": "^5.6.4", "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", diff --git a/playwright/pageObjects/editRolesModal.ts b/playwright/pageObjects/editRolesModal.ts new file mode 100644 index 000000000..1cc19565e --- /dev/null +++ b/playwright/pageObjects/editRolesModal.ts @@ -0,0 +1,33 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { Locator, Page } from '@playwright/test' + +import type { Role } from '@app/hooks/ownership/useRoles/useRoles' + +export class EditRolesModal { + readonly page: Page + + readonly searchInput: Locator + + readonly setToSelfButton: Locator + + readonly saveButton: Locator + + constructor(page: Page) { + this.page = page + this.searchInput = this.page.getByTestId('edit-roles-search-input') + this.setToSelfButton = this.page.getByTestId('edit-roles-set-to-self-button') + this.saveButton = this.page.getByTestId('edit-roles-save-button') + } + + roleCard(role: Role) { + return this.page.getByTestId(`role-card-${role}`) + } + + roleCardChangeButton(role: Role) { + return this.roleCard(role).getByTestId('role-card-change-button') + } + + searchResult(address: string) { + return this.page.getByTestId(`search-result-${address}`) + } +} diff --git a/playwright/pageObjects/index.ts b/playwright/pageObjects/index.ts index 767bbd706..f163b8895 100644 --- a/playwright/pageObjects/index.ts +++ b/playwright/pageObjects/index.ts @@ -3,9 +3,11 @@ import { Page } from '@playwright/test' import { Web3ProviderBackend } from 'headless-web3-provider' import { AddressPage } from './addressPage' +import { EditRolesModal } from './editRolesModal' import { ExtendNamesModal } from './extendNamesModal' import { HomePage } from './homePage' import { MorePage } from './morePage' +import { OwnershipPage } from './ownershipPage' import { PermissionsPage } from './permissionsPage' import { ProfilePage } from './profilePage' import { RegistrationPage } from './registrationPage' @@ -17,9 +19,11 @@ type Dependencies = { page: Page; wallet: Web3ProviderBackend } const pageObjects = { AddressPage, + EditRolesModal, ExtendNamesModal, HomePage, MorePage, + OwnershipPage, PermissionsPage, ProfilePage, RegistrationPage, diff --git a/playwright/pageObjects/ownershipPage.ts b/playwright/pageObjects/ownershipPage.ts new file mode 100644 index 000000000..868e4b9e9 --- /dev/null +++ b/playwright/pageObjects/ownershipPage.ts @@ -0,0 +1,51 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { Locator, Page } from '@playwright/test' + +import { Role } from '@app/hooks/ownership/useRoles/useRoles' + +export class OwnershipPage { + readonly page: Page + + readonly editRolesButton: Locator + + readonly sendNameButton: Locator + + readonly sendDNSButton: Locator + + readonly refreshDNSButton: Locator + + readonly extendButton: Locator + + readonly syncManagerButton: Locator + + constructor(page: Page) { + this.page = page + this.editRolesButton = this.page.getByTestId('role-action-edit-roles') + this.sendNameButton = this.page.getByTestId('role-action-send-name') + this.sendDNSButton = this.page.getByTestId('role-action-send-dns') + this.syncManagerButton = this.page.getByTestId('role-action-sync-manager') + this.refreshDNSButton = this.page.getByTestId('role-action-refresh-dns') + this.extendButton = this.page.getByTestId('expiry-action-extend') + } + + async goto(name: string) { + await this.page.goto(`/${name}?tab=ownership`) + } + + roleRow(address: string) { + return this.page.getByTestId(`role-row-${address}`) + } + + roleTag(role: Role) { + return this.page.getByTestId(`role-tag-${role}`) + } + + async getExpiryTimestamp() { + const timestamp = await this.page + .getByTestId('expiry-panel-expiry') + .getAttribute('data-timestamp') + console.log('>>>>', timestamp) + const parsed = parseInt(timestamp || '') + return parsed + } +} diff --git a/playwright/pageObjects/sendNameModal.ts b/playwright/pageObjects/sendNameModal.ts index f76a828f4..c0cedebed 100644 --- a/playwright/pageObjects/sendNameModal.ts +++ b/playwright/pageObjects/sendNameModal.ts @@ -4,18 +4,30 @@ import { Locator, Page } from '@playwright/test' export class SendNameModal { readonly page: Page - readonly nextButton: Locator + readonly searchInput: Locator + + readonly resetProfileSwitch: Locator + + readonly sendButton: Locator + + readonly confirmButton: Locator + + readonly summaryHeader: Locator constructor(page: Page) { this.page = page - this.nextButton = this.page.getByRole('button', { name: 'Next' }) + this.searchInput = this.page.getByTestId('send-name-search-input') + this.resetProfileSwitch = this.page.getByTestId('send-name-reset-profile-switch') + this.sendButton = this.page.getByTestId('send-name-send-button') + this.confirmButton = this.page.getByTestId('send-name-confirm-button') + this.summaryHeader = this.page.getByRole('button', { name: 'Summary of changes' }) + } + + searchResult(address: string) { + return this.page.getByTestId(`search-result-${address}`) } - async clickNextButton() { - try { - return await this.nextButton.click() - } catch { - await this.page.pause() - } + summaryItem(type: 'owner' | 'manager' | 'eth-record' | 'reset-profile') { + return this.page.getByTestId(`send-name-summary-${type}`) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bdf05bf6..6f754f522 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,7 +41,7 @@ importers: '@ensdomains/ens-validation': ^0.1.0 '@ensdomains/ensjs': 3.0.0-alpha.66 '@ensdomains/eth-ens-namehash': ^2.0.15 - '@ensdomains/thorin': 0.6.43 + '@ensdomains/thorin': 0.6.44 '@ethersproject/abi': ^5.6.4 '@ethersproject/abstract-provider': ^5.7.0 '@ethersproject/abstract-signer': ^5.7.0 @@ -186,7 +186,7 @@ importers: '@ensdomains/ens-validation': 0.1.0 '@ensdomains/ensjs': 3.0.0-alpha.66_4wk6sblqc5kfzwjkcabkhle2du '@ensdomains/eth-ens-namehash': 2.0.15 - '@ensdomains/thorin': 0.6.43_6haozuff5ofofiwuoke6ixqiri + '@ensdomains/thorin': 0.6.44_6haozuff5ofofiwuoke6ixqiri '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 @@ -2312,8 +2312,8 @@ packages: hash-test-vectors: 1.3.2 dev: false - /@ensdomains/thorin/0.6.43_6haozuff5ofofiwuoke6ixqiri: - resolution: {integrity: sha512-+5cm2ZtrBB+/BBjEj9ei7IQzR5LfGXUbkP80R0vL+Cxn11paS2NX/6bN30NuxPhEoI+2eQ4HNBsnJw6WHOJlhA==} + /@ensdomains/thorin/0.6.44_6haozuff5ofofiwuoke6ixqiri: + resolution: {integrity: sha512-MoW0csfH/ql1q85IrWQvAFU/7gTlhM7GWBbSCN7uFN/v5hUr+f+C8zON/17QM9UT7bhigCur48DeL172eqiIMA==} peerDependencies: react: ^17 react-dom: ^17 diff --git a/public/locales/en/common.json b/public/locales/en/common.json index de5f8511a..06337ab58 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -202,6 +202,7 @@ "approveNameWrapper": "Approve NameWrapper", "clearRecords": "Clear records", "updateRecords": "Update records", + "resetProfileWithRecords": "Reset profile with records", "transferName": "Transfer name", "transferSubname": "Transfer subname", "changePermissions": "Change permissions", @@ -252,7 +253,8 @@ "duration": "Duration", "cost": "Cost", "update": "Update", - "resolver": "Resolver" + "resolver": "Resolver", + "records": "Records" }, "itemValue": { "records_one": "{{count}} record", diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index bf0275653..affbed479 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -51,6 +51,12 @@ }, "ownership": { "name": "Ownership", + "warning": { + "ownerNotManager": "You are the owner but not the manager. This may be unintended if you’ve recently recieved this name from another address.", + "managerNotParentOwner": "The owner of {{parent}} can change ownership, roles, and settings. They cannot change the profile.", + "managerNotDNSOwner": "You are the Manager but not DNS Owner of this name. DNS names can be reclaimed by the DNS Owner at any time. You can send this name to the Owner, or update the DNS record to match.", + "dnsOwnerNotManager": "You cannot make changes to this name because you are the DNS Owner, but not the Manager. You can sync the manager to fix this." + }, "sections": { "roles": { "title": "Roles", @@ -64,9 +70,8 @@ }, "grace-period": { "title": "Grace period ends", - "tooltip": { - "content": "A 90 day grace window after expiration, when the name can still be extended but not re-registered." - } + "tooltip": "A 90 day grace window after expiration, when the name can still be extended but not re-registered." + }, "registration": { "title": "Registered" diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx index 0914af539..771dcdc66 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx @@ -1,8 +1,12 @@ import styled, { css } from 'styled-components' +import { Banner } from '@ensdomains/thorin' + import useRoles from '@app/hooks/ownership/useRoles/useRoles' import type { useNameDetails } from '@app/hooks/useNameDetails' +import { useNameType } from '@app/hooks/useNameType' +import { useOwnershipWarning } from './hooks/useOwnershipWarning' import { ContractSection } from './sections/ContractSection/ContractSection' import { ExpirySection } from './sections/ExpirySection/ExpirySection' import { RolesSection } from './sections/RolesSection/RolesSection' @@ -22,11 +26,14 @@ type Props = { export const OwnershipTab = ({ name, details }: Props) => { const roles = useRoles(name, { grouped: true }) - + const nameType = useNameType(name) + const warning = useOwnershipWarning({ name, details, nameType }) const isLoading = roles.isLoading || details.isLoading + if (isLoading) return null return ( + {warning.data && {warning.data}} diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.ts b/src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.ts deleted file mode 100644 index 767311598..000000000 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useMemo } from 'react' - -import { GroupedRoleRecord } from '@app/hooks/ownership/useRoles/useRoles' -import { useAccountSafely } from '@app/hooks/useAccountSafely' - -type Input = { - roles: GroupedRoleRecord[] -} - -export const useOwnershipWarning = ({ roles }: Input) => { - const account = useAccountSafely() - const isLoading = !account.address - const data = useMemo(() => { - if (isLoading) return undefined - }, [isLoading]) - console.log('roles', roles) - // isRegistrant not manager - // isManager not parent owner - // is Manager not DNS owner - // isDNSOwner but not manager - - return { - data, - isLoading, - } -} diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.tsx new file mode 100644 index 000000000..1dc268605 --- /dev/null +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/hooks/useOwnershipWarning.tsx @@ -0,0 +1,110 @@ +import { useMemo } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { P, match } from 'ts-pattern' + +import { useAccountSafely } from '@app/hooks/useAccountSafely' +import type { useNameDetails } from '@app/hooks/useNameDetails' +import type { useNameType } from '@app/hooks/useNameType' +import useParentBasicName from '@app/hooks/useParentBasicName' + +const getParentNameFromName = (name: string) => name.split('.').slice(1).join('.') + +type Input = { + name: string + details: ReturnType + nameType: ReturnType +} + +export const useOwnershipWarning = ({ name, nameType, details }: Input) => { + const { t } = useTranslation('profile') + const account = useAccountSafely() + const parent = useParentBasicName(name) + const isLoading = !account.address || nameType.isLoading || details.isLoading || parent.isLoading + + const data = useMemo(() => { + if (isLoading) return undefined + return match([ + nameType.data, + { + isRegistrant: details.ownerData?.registrant === account.address, + isParentOwner: parent?.ownerData?.owner === account.address, + isManager: details.ownerData?.owner === account.address, + isDNSOwner: details.dnsOwner === account.address, + }, + ]) + .with( + [ + 'eth-unwrapped-2ld', + { + isRegistrant: true, + isManager: false, + isParentOwner: P._, + isDNSOwner: P._, + }, + ], + () => t('tabs.ownership.warning.ownerNotManager'), + ) + .with( + [ + P.union('dns-unwrapped-2ld', 'dns-wrapped-2ld'), + { + isRegistrant: P._, + isManager: true, + isParentOwner: P._, + isDNSOwner: false, + }, + ], + () => t('tabs.ownership.warning.managerNotDNSOwner'), + ) + .with( + [ + P.union('dns-unwrapped-2ld', 'dns-wrapped-2ld'), + { + isRegistrant: P._, + isManager: false, + isParentOwner: P._, + isDNSOwner: true, + }, + ], + () => t('tabs.ownership.warning.dnsOwnerNotManager'), + ) + .with( + [ + P.union( + 'eth-unwrapped-subname', + 'eth-wrapped-subname', + 'dns-unwrapped-subname', + 'dns-wrapped-subname', + ), + { + isRegistrant: P._, + isManager: true, + isParentOwner: false, + isDNSOwner: P._, + }, + ], + () => ( + + ), + ) + .otherwise(() => undefined) + }, [ + isLoading, + name, + account.address, + parent.ownerData, + details.ownerData, + details.dnsOwner, + nameType.data, + t, + ]) + + return { + data, + isLoading, + } +} diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/ExpirySection.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/ExpirySection.tsx index f2bd67058..5982ca71b 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/ExpirySection.tsx +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/ExpirySection.tsx @@ -5,6 +5,7 @@ import styled, { css } from 'styled-components' import { Button, Card, Dropdown, mq } from '@ensdomains/thorin' +import { cacheableComponentStyles } from '@app/components/@atoms/CacheableComponent' import { useNameDetails } from '@app/hooks/useNameDetails' import { EarnifiDialog } from '../../../MoreTab/Miscellaneous/EarnifiDialog' @@ -106,6 +107,8 @@ const Container = styled.div(({ theme }) => [ `), ]) +const StyledCard = styled(Card)(cacheableComponentStyles) + type Props = { name: string details: ReturnType @@ -127,7 +130,7 @@ export const ExpirySection = ({ name, details }: Props) => { open={showEarnifiDialog} onDismiss={() => setShowEarnifiDialog(false)} /> - +
@@ -165,6 +168,7 @@ export const ExpirySection = ({ name, details }: Props) => { ]} > } trailing={ - } diff --git a/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx b/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx index c52b7c87a..0aa03295e 100644 --- a/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx +++ b/src/transaction-flow/input/EditRoles/views/MainView/components/RoleCard.tsx @@ -97,7 +97,7 @@ export const RoleCard = ({ address, role, dirty, onClick }: Props) => { const isAddressEmpty = !address || address === emptyAddress return ( - + {t(`roles.${role}.title`, { ns: 'common' })} @@ -105,7 +105,7 @@ export const RoleCard = ({ address, role, dirty, onClick }: Props) => { -