From f66052d2fe33acd4ef926954ae9b584e5c6b3ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 09:01:48 +0100 Subject: [PATCH 01/22] test: wait for DoubleValueSkeleton to disappear --- client/cypress/e2e/earn.cy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index ea1c8192ce..133a123723 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -234,6 +234,8 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); cy.get('[data-test=GlmLockTabs__Button]').click(); cy.window().then(async win => { + // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. + cy.get('[data-test^=DoubleValueSkeleton').should('not.be.visible'); await moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, From b303c31b36b4497646a02a0daffc76148a7cc289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 09:03:58 +0100 Subject: [PATCH 02/22] test: increase timeout to 60 seconds --- client/cypress/e2e/earn.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 133a123723..863feed89b 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -235,7 +235,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.window().then(async win => { // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test^=DoubleValueSkeleton').should('not.be.visible'); + cy.get('[data-test^=DoubleValueSkeleton', { timeout: 60000 }).should('not.be.visible'); await moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, From 6d03c11edc4dffd37fb5909f00fcf9546f9ae505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 09:28:23 +0100 Subject: [PATCH 03/22] fix: typo --- client/cypress/e2e/earn.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 863feed89b..274accc825 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -235,7 +235,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.window().then(async win => { // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test^=DoubleValueSkeleton', { timeout: 60000 }).should('not.be.visible'); + cy.get('[data-test^=DoubleValueSkeleton]', { timeout: 60000 }).should('not.be.visible'); await moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, From 614dbddfe79e5ad890c0eb62fc90a6a1e93ac783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 09:59:41 +0100 Subject: [PATCH 04/22] test: assert not.exist --- client/cypress/e2e/earn.cy.ts | 6 +- client/cypress/e2e/layout.cy.ts | 126 ---- client/cypress/e2e/metrics.cy.ts | 126 ---- client/cypress/e2e/onboarding.cy.ts | 380 ----------- client/cypress/e2e/patronMode.cy.ts | 724 --------------------- client/cypress/e2e/project.cy.ts | 181 ------ client/cypress/e2e/projects.cy.ts | 239 ------- client/cypress/e2e/projectsArchive.cy.ts | 103 --- client/cypress/e2e/rewardsCalculator.cy.ts | 252 ------- client/cypress/e2e/routes.cy.ts | 42 -- client/cypress/e2e/settings.cy.ts | 188 ------ 11 files changed, 2 insertions(+), 2365 deletions(-) delete mode 100644 client/cypress/e2e/layout.cy.ts delete mode 100644 client/cypress/e2e/metrics.cy.ts delete mode 100644 client/cypress/e2e/onboarding.cy.ts delete mode 100644 client/cypress/e2e/patronMode.cy.ts delete mode 100644 client/cypress/e2e/project.cy.ts delete mode 100644 client/cypress/e2e/projects.cy.ts delete mode 100644 client/cypress/e2e/projectsArchive.cy.ts delete mode 100644 client/cypress/e2e/rewardsCalculator.cy.ts delete mode 100644 client/cypress/e2e/routes.cy.ts delete mode 100644 client/cypress/e2e/settings.cy.ts diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index b6bb50188a..12f6f1db66 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -210,9 +210,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes }); }); - // TODO OCT-1506 enable this scenario. - // eslint-disable-next-line jest/no-disabled-tests - it.skip('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { + it('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { connectWallet(); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') @@ -237,7 +235,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.window().then(async win => { // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test^=DoubleValueSkeleton]', { timeout: 60000 }).should('not.be.visible'); + cy.get('[data-test^=DoubleValueSkeleton]', { timeout: 60000 }).should('not.exist'); await moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, diff --git a/client/cypress/e2e/layout.cy.ts b/client/cypress/e2e/layout.cy.ts deleted file mode 100644 index 9ea9954d24..0000000000 --- a/client/cypress/e2e/layout.cy.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`layout: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - cy.clearLocalStorage(); - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - cy.disconnectMetamaskWalletFromAllDapps(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - cy.visit(ROOT.absolute); - }); - - it('renders top bar', () => { - cy.get('[data-test=MainLayout__Header]').should('be.visible'); - }); - - it('Clicking on Octant logo scrolls view to the top on logo click (projects view)', () => { - cy.scrollTo(0, 500); - cy.get('[data-test=MainLayout__Logo]').click(); - // waiting for scrolling to finish - cy.wait(2000); - cy.window().then(cyWindow => { - expect(cyWindow.scrollY).to.be.eq(0); - }); - }); - - it('Clicking on Octant logo redirects to projects view (outside projects view)', () => { - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - cy.get('[data-test=MainLayout__Logo]').click(); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('Clicking on Octant logo redirects to projects view (outside projects view) with memorized scrollY', () => { - cy.scrollTo(0, 500); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - cy.get('[data-test=MainLayout__Logo]').click(); - cy.get('[data-test=ProjectsView]').should('be.visible'); - cy.window().then(cyWindow => { - expect(cyWindow.scrollY).to.be.eq(500); - }); - }); - - it('renders bottom navbar', () => { - cy.get('[data-test=Navbar]').should('be.visible'); - }); - - it('bottom navbar allows to change views', () => { - navigationTabs.forEach(({ to }) => { - navigateWithCheck(to); - }); - }); - - it('"Connect" button is visible when wallet is disconnected', () => { - cy.get('[data-test=MainLayout__Button--connect]').should('be.visible'); - cy.get('[data-test=MainLayout__Button--connect]').click(); - }); - - it('"Connect" button opens "ModalConnectWallet"', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').should('be.visible'); - }); - - it('"ModalConnectWallet" always shows "WalletConnect" option', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').within(() => { - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); - }); - }); - - it('"ModalConnectWallet" shows "Browser wallet" and "WalletConnect" options (MetaMask wallet detected)', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').within(() => { - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').should('be.visible'); - }); - }); - - it('"ModalConnectWallet" has overflow enabled', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__overflow]').should('exist'); - }); - - it('Clicking background when "ModalConnectWallet" is open, closes Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__overflow]').click({ force: true }); - cy.get('[data-test=ModalConnectWallet]').should('not.exist'); - }); - - it('"ModalConnectWallet" has "cross" icon button in header', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__Button]').should('be.visible'); - }); - - it('Clicking on "X" mark in "ModalConnectWallet", closes Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__Button]').click(); - cy.get('[data-test=ModalConnectWallet]').should('not.exist'); - }); - - it('Clicking on "WalletConnect" option, opens Web3Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').click(); - cy.get('w3m-modal').find('#w3m-modal', { includeShadowDom: true }).should('be.visible'); - }); - - it('Clicking on "Browser wallet" option connects with MetaMask wallet', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); - cy.switchToMetamaskNotification(); - cy.acceptMetamaskAccess(); - cy.get('[data-test=MainLayout__Button--connect]').should('not.exist'); - cy.get('[data-test=ProfileInfo]').should('exist'); - }); - }); -}); diff --git a/client/cypress/e2e/metrics.cy.ts b/client/cypress/e2e/metrics.cy.ts deleted file mode 100644 index 829fbfbf1c..0000000000 --- a/client/cypress/e2e/metrics.cy.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`metrics: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.metrics.absolute); - }); - - it('renders total projects tile', () => { - cy.get('[data-test=MetricsGeneralGridTotalProjects]').should('be.visible'); - }); - - it('renders total eth staked tile', () => { - cy.get('[data-test=MetricsGeneralGridTotalEthStaked]').should('be.visible'); - }); - - it('renders tile with total glm locked and % of 1B total supply groups', () => { - cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]').should('be.visible'); - cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]') - .children() - .should('have.length', 2); - }); - - it('renders wallet with glm locked graph tile', () => { - cy.get('[data-test=MetricsGeneralGridWalletsWithGlmLocked]').should('be.visible'); - }); - - it('renders cumulative glm locked graph tile', () => { - cy.get('[data-test=MetricsGeneralGridCumulativeGlmLocked]').should('be.visible'); - }); - - it('renders tiles in correct order', () => { - const metricsEpochGridTilesDataTest = [ - 'MetricsEpochGridTopProjects', - 'MetricsEpochGridTotalDonationsAndPersonal', - 'MetricsEpochGridDonationsVsPersonalAllocations', - 'MetricsEpochGridFundsUsage', - 'MetricsEpochGridTotalUsers', - 'MetricsEpochGridPatrons', - 'MetricsEpochGridCurrentDonors', - 'MetricsEpochGridAverageLeverage', - 'MetricsEpochGridRewardsUnusedAndUnallocatedValue', - 'MetricsEpochGridBelowThreshold', - ]; - - const metricsGeneralGridTilesDataTest = [ - 'MetricsGeneralGridTotalGlmLockedAndTotalSupply', - 'MetricsGeneralGridTotalProjects', - 'MetricsGeneralGridTotalEthStaked', - 'MetricsGeneralGridCumulativeGlmLocked', - 'MetricsGeneralGridWalletsWithGlmLocked', - ]; - - cy.get('[data-test=MetricsEpoch__MetricsGrid]') - .children() - .should('have.length', metricsEpochGridTilesDataTest.length); - - for (let i = 0; i < metricsEpochGridTilesDataTest.length; i++) { - cy.get('[data-test=MetricsEpoch__MetricsGrid]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', metricsEpochGridTilesDataTest[i]); - } - - cy.get('[data-test=MetricsGeneral__MetricsGrid]') - .children() - .should('have.length', metricsGeneralGridTilesDataTest.length); - - for (let i = 0; i < metricsGeneralGridTilesDataTest.length; i++) { - cy.get('[data-test=MetricsGeneral__MetricsGrid]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', metricsGeneralGridTilesDataTest[i]); - } - }); - - it('renders grid with 4 columns on desktop or with 2 columns on other devices', () => { - cy.get('[data-test=MetricsEpoch__MetricsGrid]').then(el => { - const width = parseInt(el.css('width'), 10); - const rowGap = parseInt(el.css('rowGap'), 10); - - const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; - - cy.get('[data-test=MetricsEpoch__MetricsGrid]').should( - 'have.css', - 'grid-template-columns', - isDesktop - ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` - : `${columnWidth}px ${columnWidth}px`, - ); - }); - - cy.get('[data-test=MetricsGeneral__MetricsGrid]').then(el => { - const width = parseInt(el.css('width'), 10); - const rowGap = parseInt(el.css('rowGap'), 10); - - const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; - - cy.get('[data-test=MetricsGeneral__MetricsGrid]').should( - 'have.css', - 'grid-template-columns', - isDesktop - ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` - : `${columnWidth}px ${columnWidth}px`, - ); - }); - }); - }); -}); diff --git a/client/cypress/e2e/onboarding.cy.ts b/client/cypress/e2e/onboarding.cy.ts deleted file mode 100644 index fea5414626..0000000000 --- a/client/cypress/e2e/onboarding.cy.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { stepsDecisionWindowClosed } from 'src/hooks/helpers/useOnboardingSteps/steps'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -const connectWallet = ( - isTOSAccepted: boolean, - shouldVisit = true, - shouldReload = false, -): Chainable => { - cy.intercept('GET', '/user/*/tos', { body: { accepted: isTOSAccepted } }); - cy.disconnectMetamaskWalletFromAllDapps(); - if (shouldVisit) { - visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); - } - if (shouldReload) { - cy.reload(); - } - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); - cy.switchToMetamaskNotification(); - return cy.acceptMetamaskAccess(); -}; - -const beforeSetup = () => { - mockCoinPricesServer(); - cy.clearLocalStorage(); - cy.setupMetamask(); - window.innerWidth = Cypress.config().viewportWidth; - window.innerHeight = Cypress.config().viewportHeight; -}; - -const checkCurrentElement = (el: number, isCurrent: boolean): Chainable => { - return cy - .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(el) - .invoke('attr', 'data-iscurrent') - .should('eq', `${isCurrent}`); -}; - -const checkProgressStepperSlimIsCurrentAndClickNext = (index, isCurrent = true): Chainable => { - checkCurrentElement(index - 1, isCurrent); - return cy - .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(index) - .click({ force: true }); -}; - -const checkChangeStepsWithArrowKeys = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - [ - { el: 1, key: 'ArrowRight' }, - { el: 2, key: 'ArrowRight' }, - // { el: 3, key: 'ArrowRight' }, - // { el: 3, key: 'ArrowRight' }, - // { el: 2, key: 'ArrowLeft' }, - { el: 1, key: 'ArrowLeft' }, - { el: 0, key: 'ArrowLeft' }, - { el: 0, key: 'ArrowLeft' }, - ].forEach(({ key, el }) => { - cy.get('body').trigger('keydown', { key }); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); -}; - -const checkChangeStepsByClickingEdgeOfTheScreenUpTo25px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - cy.get('[data-test=ModalOnboarding]').then(element => { - const leftEdgeX = element.offsetParent().offset()?.left as number; - const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; - - [ - { clientX: rightEdgeX - 25, el: 1 }, - { clientX: rightEdgeX - 10, el: 2 }, - // { clientX: rightEdgeX - 5, el: 3 }, - // rightEdgeX === browser right frame - // { clientX: rightEdgeX - 1, el: 3 }, - // { clientX: leftEdgeX + 25, el: 2 }, - { clientX: leftEdgeX + 10, el: 1 }, - { clientX: leftEdgeX + 5, el: 0 }, - { clientX: leftEdgeX, el: 0 }, - ].forEach(({ clientX, el }) => { - cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); - }); -}; - -const checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - cy.get('[data-test=ModalOnboarding]').then(element => { - const leftEdgeX = element.offsetParent().offset()?.left as number; - const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; - - [ - { clientX: rightEdgeX - 25, el: 1 }, - { clientX: rightEdgeX - 26, el: 1 }, - { clientX: leftEdgeX + 26, el: 1 }, - { clientX: leftEdgeX + 25, el: 0 }, - ].forEach(({ clientX, el }) => { - cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); - }); -}; - -const checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - [ - { - el: 1, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 2, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 2, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - // { - // el: 3, - // touchMoveClientX: window.innerWidth / 2 - 5, - // touchStartClientX: window.innerWidth / 2, - // }, - { - el: 2, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 1, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 0, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 0, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { - cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { - touches: [{ clientX: touchStartClientX }], - }); - cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { - touches: [{ clientX: touchMoveClientX }], - }); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); -}; - -const checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - [ - { - el: 1, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 1, - touchMoveClientX: window.innerWidth / 2 - 4, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 1, - touchMoveClientX: window.innerWidth / 2 + 4, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 0, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { - cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { - touches: [{ clientX: touchStartClientX }], - }); - cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { - touches: [{ clientX: touchMoveClientX }], - }); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`onboarding (TOS accepted): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - beforeSetup(); - }); - - beforeEach(() => { - connectWallet(true); - }); - - it('user is able to click through entire onboarding flow', () => { - for (let i = 1; i < stepsDecisionWindowClosed.length - 1; i++) { - checkProgressStepperSlimIsCurrentAndClickNext(i); - } - - cy.get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(stepsDecisionWindowClosed.length - 1) - .click(); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); - }); - - it('user is able to close the modal by clicking button in the top-right', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); - }); - - it('renders every time page is refreshed when "Always show Allocate onboarding" option is checked', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').check().should('be.checked'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('renders only once when "Always show Allocate onboarding" option is not checked', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').should('not.be.checked'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - - it('user can change steps with arrow keys (left, right)', () => { - checkChangeStepsWithArrowKeys(true); - }); - - it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(true); - }); - - it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(true); - }); - - it('user can change steps by swiping on screen (difference more than or equal 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(true); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); - }); - - it('user is able to close the onboarding, and after disconnecting & connecting, onboarding does not show up again', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - connectWallet(true, false, true); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - }); -}); - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`onboarding (TOS not accepted): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - beforeSetup(); - }); - - beforeEach(() => { - cy.intercept( - { - method: 'POST', - url: '/user/*/tos', - }, - { body: { accepted: true }, statusCode: 200 }, - ); - connectWallet(false); - }); - - it('onboarding TOS step should be first and active', () => { - checkCurrentElement(0, true); - cy.get('[data-test=ModalOnboardingTOS]').should('be.visible'); - }); - - it('user is not able to click through entire onboarding flow', () => { - for (let i = 1; i < stepsDecisionWindowClosed.length; i++) { - checkProgressStepperSlimIsCurrentAndClickNext(i, i === 1); - } - }); - - it('user is not able to close the modal by clicking button in the top-right', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click({ force: true }); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('renders every time page is refreshed', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('user cannot change steps with arrow keys (left, right)', () => { - checkChangeStepsWithArrowKeys(false); - }); - - it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(false); - }); - - it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(false); - }); - - it('user cannot change steps by swiping on screen (difference more than or equal 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(false); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(false); - }); - - it('TOS acceptance changes onboarding step to next step', () => { - checkCurrentElement(0, true); - cy.get('[data-test=TOS_InputCheckbox]').check(); - cy.switchToMetamaskNotification(); - cy.confirmMetamaskSignatureRequest(); - checkCurrentElement(1, true); - }); - - it('TOS acceptance allows the user to close the modal by clicking button in the top-right', () => { - checkCurrentElement(0, true); - cy.get('[data-test=TOS_InputCheckbox]').check(); - cy.switchToMetamaskNotification(); - cy.confirmMetamaskSignatureRequest(); - checkCurrentElement(1, true); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - }); -}); diff --git a/client/cypress/e2e/patronMode.cy.ts b/client/cypress/e2e/patronMode.cy.ts deleted file mode 100644 index 369160c656..0000000000 --- a/client/cypress/e2e/patronMode.cy.ts +++ /dev/null @@ -1,724 +0,0 @@ -import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`patron mode (disabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - connectWallet(true, false); - }); - - it('patron badge should not exist ', () => { - cy.get('[data-test=ProfileInfo__badge]').should('not.exist'); - }); - - it('Patron mode toggle is not checked', () => { - cy.get('[data-test=InputToggle__PatronMode]').should('not.be.checked'); - }); - - if (isDesktop) { - it('Patron mode tooltip is visible on hover and has correct text', () => { - cy.get('[data-test=Tooltip__patronMode]').trigger('mouseover'); - cy.get('[data-test=Tooltip__patronMode__content]').should('be.visible'); - cy.get('[data-test=Tooltip__patronMode__content]') - .invoke('text') - .should( - 'eq', - 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', - ); - }); - } - - it('Checking patron mode opens patron mode modal', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=ModalPatronMode]').should('be.visible'); - }); - - it('Patron mode modal last paragraph has correct text', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=PatronMode__fourthParagraph]') - .invoke('text') - .should('eq', 'Slide the switch below all the way to the right to enable patron mode.'); - }); - - it('Slider is visible', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=PatronModeSlider]').should('be.visible'); - }); - - it('Slider has correct label', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=PatronModeSlider__label]') - .invoke('text') - .should('eq', 'Slide right to confirm'); - }); - - it('Slider button is visible ', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); - }); - - it('Slider button has right arrow inside', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=PatronModeSlider__button__arrow]') - .should('be.visible') - .should('have.css', 'transform', 'none'); - }); - - it('Slider button is on the left side', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderLeftDistance = sliderEl[0].getBoundingClientRect().left; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonLeftDistance = sliderButtonEl[0].getBoundingClientRect().left; - - expect(sliderButtonLeftDistance).to.be.eq(sliderLeftDistance + sliderLeftPadding); - }); - }); - }); - - it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.x; - const pointerMovePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerDownPageX); - }); - }); - }); - }); - - it('Slider elements change color while moving slider button', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const slider0PercentagePageX = sliderButtonDimensions.x; - const slider25PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 4; - const slider50PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; - const slider75PercentagePageX = sliderButtonDimensions.x + 3 * (sliderTrackWidth / 4); - const slider100PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth; - - // 0% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { - pageX: slider0PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgb(243, 243, 243)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(23, 23, 23)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '1'); - - // 25% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider25PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(212, 224, 221, 0.95)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(222, 234, 231)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(129, 129, 129)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.75'); - - // 50% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider50PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(175, 204, 197, 0.9)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(183, 211, 204)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(181, 181, 181)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.5'); - - // 75% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider75PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(128, 181, 169, 0.85)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(133, 185, 173)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(221, 221, 221)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.25'); - - // 100% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider100PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(45, 155, 135, 0.8)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(45, 155, 135)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0'); - }); - }); - }); - - it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { - cy.get('[data-test=InputToggle__PatronMode]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.x; - const pointerMovePageX = sliderButtonDimensions.x + (sliderTrackWidth / 2 + 1); - const pointerUpPageX = sliderDimensions.right - sliderRightPadding - sliderButtonWidth; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); - }); - - cy.confirmMetamaskSignatureRequest(); - cy.switchToCypressWindow(); - cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__status-label]') - .invoke('text') - .should('eq', 'Patron mode enabled'); - cy.wait(500); - cy.get('[data-test=ModalPatronMode]').should('not.exist'); - }); - }); - }); - }); - - describe(`patron mode (enabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - connectWallet(true, true); - }); - - it('patron badge is visible and has correct label, background and text-transform prop', () => { - cy.get('[data-test=ProfileInfo__badge]').should('be.visible'); - cy.get('[data-test=ProfileInfo__badge]').invoke('text').should('eq', 'Patron'); - cy.get('[data-test=ProfileInfo__badge]') - .should('have.css', 'background-color', 'rgb(104, 91, 138)') - .should('have.css', 'text-transform', 'uppercase'); - }); - - it('Navbar has 4 items - projects, earn, metrics, settings', () => { - const navbarChildrenDataTest = [ - 'Navbar__Button--Projects', - 'Navbar__Button--Earn', - 'Navbar__Button--Metrics', - 'Navbar__Button--Settings', - ]; - - cy.get('[data-test=Navbar__buttons]') - .children() - .should('have.length', navbarChildrenDataTest.length); - - for (let i = 0; i < navbarChildrenDataTest.length; i++) { - cy.get('[data-test=Navbar__buttons]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', navbarChildrenDataTest[i]); - } - }); - - it('route /allocate redirects to /projects', () => { - visitWithLoader(ROOT_ROUTES.allocation.absolute, ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('BoxPersonalAllocation has correct title and sections labels', () => { - visitWithLoader(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=BoxPersonalAllocation__title]') - .invoke('text') - .should('eq', 'Patron earnings'); - cy.get('[data-test=BoxPersonalAllocation__Section__label]') - .eq(0) - .invoke('text') - .should('eq', 'Current epoch'); - cy.get('[data-test=BoxPersonalAllocation__Section__label]') - .eq(1) - .invoke('text') - .should('eq', 'All time'); - }); - - it('Patron mode toggle is checked', () => { - cy.get('[data-test=InputToggle__PatronMode]').should('be.checked'); - }); - - if (isDesktop) { - it('Patron mode tooltip is visible on hover and has correct text', () => { - cy.get('[data-test=Tooltip__patronMode]').trigger('mouseover'); - cy.get('[data-test=Tooltip__patronMode__content]').should('be.visible'); - cy.get('[data-test=Tooltip__patronMode__content]') - .invoke('text') - .should( - 'eq', - 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', - ); - }); - } - - it('Unchecking patron mode opens patron mode modal', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=ModalPatronMode]').should('be.visible'); - }); - - it('Patron mode modal last paragraph has correct text', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=PatronMode__fourthParagraph]') - .invoke('text') - .should('eq', 'Slide the switch below all the way to the left to disable patron mode.'); - }); - - it('Slider is visible', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=PatronModeSlider]').should('be.visible'); - }); - - it('Slider has correct label', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=PatronModeSlider__label]') - .invoke('text') - .should('eq', 'Slide left to confirm'); - }); - - it('Slider button is visible', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); - }); - - it('Slider button has left arrow inside', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=PatronModeSlider__button__arrow]') - .should('be.visible') - .should('have.css', 'transform', 'matrix(-1, 0, 0, -1, 0, 0)'); - }); - - it('Slider button is on the right side', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderRightDistance = sliderEl[0].getBoundingClientRect().right; - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonRightDistance = sliderButtonEl[0].getBoundingClientRect().right; - - expect(sliderButtonRightDistance).to.be.eq(sliderRightDistance - sliderRightPadding); - }); - }); - }); - - it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.right; - const pointerMovePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.right).eq(pointerDownPageX); - }); - }); - }); - }); - - it('Slider elements change color while moving slider button', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const slider0PercentagePageX = sliderButtonDimensions.right; - const slider25PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 4; - const slider50PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; - const slider75PercentagePageX = sliderButtonDimensions.right - 3 * (sliderTrackWidth / 4); - const slider100PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth; - - // 0% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { - pageX: slider0PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgb(243, 243, 243)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(23, 23, 23)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '1'); - - // 25% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider25PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(212, 224, 221, 0.95)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(222, 234, 231)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(129, 129, 129)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.75'); - - // 50% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider50PercentagePageX, - waitForAnimations: true, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(175, 204, 197, 0.9)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(183, 211, 204)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(181, 181, 181)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.5'); - - // 75% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider75PercentagePageX, - waitForAnimations: true, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(128, 181, 169, 0.85)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(133, 185, 173)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(221, 221, 221)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.25'); - - // 100% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider100PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(45, 155, 135, 0.8)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(45, 155, 135)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0'); - }); - }); - }); - - it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { - cy.get('[data-test=InputToggle__PatronMode]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.right; - const pointerMovePageX = sliderButtonDimensions.right - (sliderTrackWidth / 2 + 1); - const pointerUpPageX = sliderDimensions.left + sliderLeftPadding; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); - }); - - cy.confirmMetamaskSignatureRequest(); - cy.switchToCypressWindow(); - cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__status-label]') - .invoke('text') - .should('eq', 'Patron mode disabled'); - cy.wait(500); - cy.get('[data-test=ModalPatronMode]').should('not.exist'); - }); - }); - }); - - it('when entering project view, button icon changes to chevronLeft', () => { - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.get('[data-test=Navbar__Button--Projects]') - .find('svg') - // HTML tag can't be self-closing in CY. - .should( - 'have.html', - '', - ); - }); - }); -}); diff --git a/client/cypress/e2e/project.cy.ts b/client/cypress/e2e/project.cy.ts deleted file mode 100644 index e811fb70aa..0000000000 --- a/client/cypress/e2e/project.cy.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import { getNamesOfProjects } from 'cypress/utils/projects'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -const getButtonAddToAllocate = (): Chainable => { - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - - return projectListItemFirst.find('[data-test=ProjectListItemHeader__ButtonAddToAllocate]'); -}; - -const checkProjectItemElements = (): Chainable => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); - getButtonAddToAllocate().should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Button]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItem__Description]').should('be.visible'); - - cy.get('[data-test=ProjectListItem__Donors]') - .first() - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - cy.get('[data-test=ProjectListItem__Donors]').first().should('be.visible'); - cy.get('[data-test=ProjectListItem__Donors__DonorsHeader__count]') - .first() - .should('be.visible') - .should('have.text', '0'); - return cy.get('[data-test=ProjectListItem__Donors__noDonationsYet]').first().should('be.visible'); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`project: ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('entering project view directly renders content', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.reload(); - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); - }); - - it('entering project view renders all its elements', () => { - checkProjectItemElements(); - }); - - it('entering project view renders all its elements with fallback IPFS provider', () => { - cy.intercept('GET', '**/ipfs/**', req => { - if (req.url.includes('infura')) { - req.destroy(); - } - }); - - checkProjectItemElements(); - }); - - it('entering project view shows Toast with info about IPFS failure when all providers fail', () => { - cy.intercept('GET', '**/ipfs/**', req => { - req.destroy(); - }); - - cy.get('[data-test=Toast--ipfsMessage').should('be.visible'); - }); - - it('entering project view allows to add it to allocation and remove, triggering change of the icon, change of the number in navbar', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - - getButtonAddToAllocate().click(); - - // cy.get('@buttonAddToAllocate').click(); - cy.get('[data-test=Navbar__numberOfAllocations]').contains(1); - getButtonAddToAllocate().click(); - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - }); - - it('Entering project view allows scroll only to the last project', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test=ProjectListItem]').should( - 'have.length.greaterThan', - i === projectNames.length - 1 ? projectNames.length - 1 : i, - ); - cy.get('[data-test=ProjectListItemHeader__name]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: -150 } }) - .contains(projectNames[i]); - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: -150 } }) - .should('be.visible'); - } - }); - - it('"Back to top" button is displayed if the user has scrolled past the start of the final project description', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length - 1; i++) { - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - if (i === projectNames.length - 1) { - cy.get('[data-test=ProjectBackToTopButton__Button]').should('be.visible'); - } - } - }); - - it('Clicking on "Back to top" button scrolls to the top of view (first project is visible)', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length - 1; i++) { - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - if (i === projectNames.length - 1) { - cy.get('[data-test=ProjectBackToTopButton__Button]').click(); - cy.get('[data-test=ProjectListItem]').eq(0).should('be.visible'); - } - } - }); - }); - - describe(`project (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet(true, true); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('button "add to allocate" is disabled', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).click(); - getButtonAddToAllocate().should('be.visible').should('be.disabled'); - cy.go('back'); - } - }); - }); -}); diff --git a/client/cypress/e2e/projects.cy.ts b/client/cypress/e2e/projects.cy.ts deleted file mode 100644 index 9d096bcdf2..0000000000 --- a/client/cypress/e2e/projects.cy.ts +++ /dev/null @@ -1,239 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import { getNamesOfProjects } from 'cypress/utils/projects'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import getMilestones from 'src/constants/milestones'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -chai.use(chaiColors); - -function checkProjectItemElements(index, name, isPatronMode = false): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__imageProfile]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem]') - .eq(index) - .should('be.visible') - .find('[data-test=ProjectsListItem__name]') - .should('be.visible') - .contains(name); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__IntroDescription]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .should('be.visible'); - - if (isPatronMode) { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .should('be.disabled'); - } - - return cy - .get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectRewards]') - .should('be.visible'); - // TODO OCT-663 Make CY check if rewards are available (Epoch 2, decision window open). - // return cy - // .get('[data-test^=ProjectsView__ProjectsListItem') - // .eq(index) - // .find('[data-test=ProjectRewards__currentTotal__label]') - // .should('be.visible'); -} - -function addProjectToAllocate(index, numberOfAddedProjects): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__imageProfile]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__IntroDescription]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .scrollIntoView(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .find('svg') - .find('path') - .then($el => $el.css('fill')) - .should('be.colored', '#FF6157'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .find('svg') - .find('path') - .then($el => $el.css('stroke')) - .should('be.colored', '#FF6157'); - cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects + 1); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects + 1); - return cy.go('back'); -} - -function removeProjectFromAllocate(numberOfProjects, numberOfAddedProjects, index): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .scrollIntoView(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects - 1); - if (index < numberOfProjects - 1) { - cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects - 1); - } else { - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - } - return cy.go('back'); -} - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`projects: ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('user is able to see all the projects in the view', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); - checkProjectItemElements(i, projectNames[i]); - } - }); - - it('user is able to add & remove the first and the last project to/from allocation, triggering change of the icon, change of the number in navbar', () => { - // This test checks the first and the last elements only to save time. - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - - addProjectToAllocate(0, 0); - addProjectToAllocate(projectNames.length - 1, 1); - removeProjectFromAllocate(projectNames.length, 2, 0); - removeProjectFromAllocate(projectNames.length, 1, projectNames.length - 1); - }); - - it('user is able to add project to allocation in ProjectsView and remove it from allocation in AllocationView', () => { - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - addProjectToAllocate(0, 0); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItemSkeleton]').should('not.exist'); - cy.get('[data-test=AllocationItem]').then(el => { - const { x } = el[0].getBoundingClientRect(); - cy.get('[data-test=AllocationItem]') - .trigger('pointerdown') - .trigger('pointermove', { pageX: x - 20 }) - .trigger('pointerup'); - cy.get('[data-test=AllocationItem__removeButton]').should('be.visible'); - cy.get('[data-test=AllocationItem__removeButton]').click(); - cy.get('[data-test=AllocationItem__removeButton]').should('not.exist'); - cy.get('[data-test=AllocationItem]').should('not.exist'); - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - }); - - it('ProjectsTimelineWidgetItem with href opens link when clicked without mouse movement', () => { - const milestones = getMilestones(); - cy.get('[data-test=ProjectsTimelineWidget]').should('be.visible'); - cy.get('[data-test=ProjectsTimelineWidgetItem]').should('have.length', milestones.length); - for (let i = 0; i < milestones.length; i++) { - if (milestones[i].href) { - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .within(() => { - cy.get('[data-test=ProjectsTimelineWidgetItem__Svg--arrowTopRight]').should( - 'be.visible', - ); - }); - - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .then(el => { - const { x } = el[0].getBoundingClientRect(); - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .trigger('mousedown') - .trigger('mouseup', { clientX: x + 10 }); - cy.location('pathname').should('eq', ROOT_ROUTES.projects.absolute); - - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .trigger('mousedown') - .trigger('mouseup'); - cy.location('pathname').should('not.eq', ROOT_ROUTES.projects.absolute); - }); - } - } - }); - }); - }); - - describe(`projects (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet(true, true); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('button "add to allocate" is disabled', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); - checkProjectItemElements(i, projectNames[i], true); - } - }); - }); -}); diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts deleted file mode 100644 index d2abe9b5d0..0000000000 --- a/client/cypress/e2e/projectsArchive.cy.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { checkLocationWithLoader, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -let wasEpochMoved = false; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`projects archive: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - }); - - it('moves to the next epoch', () => { - // Move time only once, for the first device. - if (!wasEpochMoved) { - cy.window().then(async win => { - const currentEpochBefore = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - await win.mutateAsyncMoveEpoch(); - const currentEpochAfter = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - wasEpochMoved = true; - expect(currentEpochBefore + 1).to.eq(currentEpochAfter); - }); - } else { - expect(true).to.be.true; - } - }); - - it('renders archive elements + clicking on epoch archive ProjectsListItem opens ProjectView for particular epoch and project', () => { - cy.get('[data-test=MainLayout__body]').then(el => { - const mainLayoutPaddingTop = parseInt(el.css('paddingTop'), 10); - - cy.get('[data-test=ProjectsView__ProjectsList]') - .should('be.visible') - .children() - .then(children => { - children[children.length - 1].scrollIntoView(); - cy.window().then(window => window.scrollTo(0, window.scrollY - mainLayoutPaddingTop)); - cy.wait(1000); - // header test - cy.get('[data-test=ProjectsView__ProjectsList__header--archive]').should('be.visible'); - - // list test - cy.get('[data-test=ProjectsView__ProjectsList--archive]').first().should('be.visible'); - cy.get('[data-test=ProjectsView__ProjectsList--archive]') - .first() - .children() - .then(childrenArchive => { - const numberOfArchivedProjects = childrenArchive.length - 2; // archived projects tiles - (header + divider)[2] - for (let i = 0; i < numberOfArchivedProjects; i++) { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .scrollIntoView(); - cy.window().then(window => - window.scrollTo(0, window.scrollY - mainLayoutPaddingTop), - ); - // list item test - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .should('be.visible') - .within(() => { - // rewards test - cy.get('[data-test=ProjectRewards]').should('be.visible'); - }); - - if (numberOfArchivedProjects - 1) { - cy.get('[data-test=ProjectsView__ProjectsList--archive]') - .first() - .should('have.length', 1); - } - - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .invoke('data', 'address') - .then(address => { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .invoke('data', 'epoch') - .then(epoch => { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .click(); - checkLocationWithLoader( - `${ROOT_ROUTES.project.absolute}/${epoch}/${address}`, - ); - cy.go('back'); - checkLocationWithLoader(ROOT_ROUTES.projects.absolute); - }); - }); - } - }); - }); - }); - }); - }); -}); diff --git a/client/cypress/e2e/rewardsCalculator.cy.ts b/client/cypress/e2e/rewardsCalculator.cy.ts deleted file mode 100644 index 361153ff90..0000000000 --- a/client/cypress/e2e/rewardsCalculator.cy.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { ETH_USD, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; -import getFormattedEthValue from 'src/utils/getFormattedEthValue'; -import { parseUnitsBigInt } from 'src/utils/parseUnitsBigInt'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`rewards calculator: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.earn.absolute); - }); - - it('renders calculator icon inside box', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').should('be.visible'); - }); - - if (isDesktop) { - it('tooltip is visible on calculator icon hover and has correct text', () => { - cy.get('[data-test=Tooltip__rewardsCalculator').trigger('mouseover'); - cy.get('[data-test=Tooltip__rewardsCalculator__content') - .should('be.visible') - .invoke('text') - .should('eq', 'Calculate rewards'); - }); - } - - it('clicking on rewards calculator icon opens rewards calculator modal', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=ModalRewardsCalculator]').should('be.visible'); - }); - - it('default values in rewards calculator are 90 days and 5000 GLM', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=RewardsCalculator__InputText--crypto]').invoke('val').should('eq', '5000'); - cy.get('[data-test=RewardsCalculator__InputText--days]').invoke('val').should('eq', '90'); - }); - - it('calculator fetches rewards values in ETH and USD based on DAYS and GLM fields', () => { - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'be.visible', - ); - cy.wait('@postEstimatedRewards'); - - cy.get('@postEstimatedRewards').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get( - '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', - ).should('not.exist'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - }, - ); - - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsGlmValueChange'); - cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('500000'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'be.visible', - ); - cy.wait('@postEstimatedRewardsGlmValueChange'); - - cy.get('@postEstimatedRewardsGlmValueChange').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get( - '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', - ).should('not.exist'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - }, - ); - - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsDaysValueChange'); - cy.get('[data-test=RewardsCalculator__InputText--days]').clear().type('900'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'be.visible', - ); - cy.wait('@postEstimatedRewardsDaysValueChange'); - - cy.get('@postEstimatedRewardsDaysValueChange').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get( - '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', - ).should('not.exist'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - }, - ); - }); - - it('If DAYS or GLM input is empty rewards inputs are empty too', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=RewardsCalculator__InputText--crypto]').clear(); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - - cy.get('[data-test=RewardsCalculator__InputText--days]').clear(); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - - cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('5000'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - }); - - it('Max GLM amount is 1000000000', () => { - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - - cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('1000000000'); - cy.wait('@postEstimatedRewards'); - - cy.get('@postEstimatedRewards').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - - cy.get('[data-test=RewardsCalculator__InputText--crypto]') - .clear() - .type('1000000001') - .should('have.css', 'border-color', 'rgb(255, 97, 87)'); - cy.get('[data-test=RewardsCalculator__InputText--crypto__error]') - .should('be.visible') - .invoke('text') - .should('eq', 'That isn’t a valid amount'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - }, - ); - }); - - it('Closing the modal successfully cancels the request /estimated_budget', () => { - cy.window().then(win => { - cy.spy(win.console, 'error').as('consoleErrSpy'); - }); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - - cy.get('[data-test=ModalRewardsCalculator__Button]').click(); - cy.get('[data-test=ModalRewardsCalculator').should('not.be.visible'); - - cy.on('uncaught:exception', error => { - expect(error.code).to.equal('ERR_CANCELED'); - }); - }); - }); -}); diff --git a/client/cypress/e2e/routes.cy.ts b/client/cypress/e2e/routes.cy.ts deleted file mode 100644 index 1d2dc113b8..0000000000 --- a/client/cypress/e2e/routes.cy.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`routes (wallet not connected): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - mockCoinPricesServer(); - cy.clearLocalStorage(); - }); - - it('empty route redirects to projects view', () => { - visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('allocation route redirects to allocation view', () => { - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationView]').should('be.visible'); - }); - - it('earn route redirects to earn view', () => { - visitWithLoader(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView]').should('be.visible'); - }); - - it('metrics route redirects to metrics view', () => { - visitWithLoader(ROOT_ROUTES.metrics.absolute); - cy.get('[data-test=MetricsView]').should('be.visible'); - }); - - it('projects route redirects to projects view', () => { - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('settings route redirects to settings view', () => { - visitWithLoader(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - }); - }); -}); diff --git a/client/cypress/e2e/settings.cy.ts b/client/cypress/e2e/settings.cy.ts deleted file mode 100644 index 127c276477..0000000000 --- a/client/cypress/e2e/settings.cy.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { FIAT_CURRENCIES_SYMBOLS, DISPLAY_CURRENCIES } from 'src/constants/currencies'; -import { - ARE_OCTANT_TIPS_ALWAYS_VISIBLE, - DISPLAY_CURRENCY, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; -import getValueCryptoToDisplay from 'src/utils/getValueCryptoToDisplay'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`settings: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - }); - - it('"Always show Allocate onboarding" option toggle works', () => { - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').check(); - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); - }); - - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').click(); - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('false'); - }); - - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').click(); - cy.get('[data-test=InputToggle__AlwaysShowOnboarding]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); - }); - }); - - it('"Use crypto as main value display" option is checked by default', () => { - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - }); - - it('"Use crypto as main value display" option toggle works', () => { - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').check(); - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').click(); - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('false'); - }); - - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').click(); - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - }); - - it('"Use crypto as main value display" option by default displays crypto value as primary in DoubleValue component', () => { - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - const cryptoValue = getValueCryptoToDisplay({ - cryptoCurrency: 'golem', - valueCrypto: BigInt(0), - }); - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('eq', cryptoValue); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('not.eq', cryptoValue); - }); - - it('"Use crypto as main value display" option changes DoubleValue sections order', () => { - cy.get('[data-test=InputToggle__UseCryptoAsMainValueDisplay]').uncheck(); - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - const cryptoValue = getValueCryptoToDisplay({ - cryptoCurrency: 'golem', - valueCrypto: BigInt(0), - }); - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('not.eq', cryptoValue); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('eq', cryptoValue); - }); - - it('"Choose a display currency" option works', () => { - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(DISPLAY_CURRENCY)).eq('"usd"'); - }); - - for (let i = 0; i < DISPLAY_CURRENCIES.length - 1; i++) { - const displayCurrency = DISPLAY_CURRENCIES[i]; - const displayCurrencyToUppercase = displayCurrency.toUpperCase(); - const nextDisplayCurrencyToUppercase = - i < DISPLAY_CURRENCIES.length - 1 ? DISPLAY_CURRENCIES[i + 1].toUpperCase() : undefined; - - cy.get('[data-test=SettingsView__InputSelect--currency__SingleValue]').contains( - displayCurrencyToUppercase, - ); - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - if (FIAT_CURRENCIES_SYMBOLS[displayCurrency]) { - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( - FIAT_CURRENCIES_SYMBOLS[displayCurrency], - ); - } else { - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( - displayCurrencyToUppercase, - ); - } - - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView__InputSelect--currency]').click(); - cy.get( - `[data-test=SettingsView__InputSelect--currency__Option--${nextDisplayCurrencyToUppercase}]`, - ).click(); - } - }); - - it('"Always show Octant tips" option toggle works', () => { - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').check(); - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); - }); - - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').click(); - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('false'); - }); - - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').click(); - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); - }); - }); - - it('"Always show Octant tips" works (checked)', () => { - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').check(); - - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - - cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - - cy.reload(); - - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - }); - - it('"Always show Octant tips" works (unchecked)', () => { - cy.get('[data-test=AlwaysShowOctantTips__InputCheckbox]').uncheck(); - - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - - cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - - cy.reload(); - - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - }); - }); -}); From 816259c5eaca5f2a0001b1a1eb5c542558c31d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 11:13:13 +0100 Subject: [PATCH 05/22] test: retry 0 --- client/cypress/e2e/earn.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 12f6f1db66..9a197282f1 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -14,7 +14,7 @@ const connectWallet = (): Chainable => { }; Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }, idx) => { - describe(`earn: ${device}`, { viewportHeight, viewportWidth }, () => { + describe(`earn: ${device}`, { retries: { runMode: 0, openMode: 0 }, viewportHeight, viewportWidth }, () => { before(() => { /** * Global Metamask setup done by Synpress is not always done. From 27119f2c3c44b420f816c16ac9c6af0d7ad19da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 11:58:59 +0100 Subject: [PATCH 06/22] test: adjustment --- client/cypress/e2e/earn.cy.ts | 4 ++-- client/cypress/utils/e2e.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 9a197282f1..f7dcbef588 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -14,7 +14,7 @@ const connectWallet = (): Chainable => { }; Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }, idx) => { - describe(`earn: ${device}`, { retries: { runMode: 0, openMode: 0 }, viewportHeight, viewportWidth }, () => { + describe(`earn: ${device}`, { retries: { openMode: 0, runMode: 0 }, viewportHeight, viewportWidth }, () => { before(() => { /** * Global Metamask setup done by Synpress is not always done. @@ -236,7 +236,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.window().then(async win => { // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. cy.get('[data-test^=DoubleValueSkeleton]', { timeout: 60000 }).should('not.exist'); - await moveEpoch(win); + moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, }) diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index a673ac807a..705c4623f3 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -50,14 +50,14 @@ export const connectWallet = ( return cy.acceptMetamaskAccess(); }; -export const moveEpoch = async (cypressWindow: Cypress.AUTWindow): Promise => { - await cypressWindow.mutateAsyncMoveEpoch(); +export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Chainable => { + cy.wrap(cypressWindow.mutateAsyncMoveEpoch()); // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); // Manually taking a pending snapshot after the epoch shift ensures that the snapshot is taken. Passing epoch multiple times without manually triggering pending snapshot in a short period of time may cause the e2e environment to fail. - await axios.post(`${env.serverEndpoint}snapshots/pending`); + cy.wrap(axios.post(`${env.serverEndpoint}snapshots/pending`)); // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); // reload is needed to get updated data in the app - cy.reload(); + return cy.reload(); }; From a31f4f96312921e49165cd0a525f3bfa2935e0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 13:49:22 +0100 Subject: [PATCH 07/22] test: adjustment --- client/cypress/utils/e2e.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index 705c4623f3..a48eaa203e 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -51,11 +51,12 @@ export const connectWallet = ( }; export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Chainable => { - cy.wrap(cypressWindow.mutateAsyncMoveEpoch()); + // https://docs.cypress.io/api/utilities/promise#Waiting-for-Promises + cy.wrap(null).then(() => cypressWindow.mutateAsyncMoveEpoch()); // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); // Manually taking a pending snapshot after the epoch shift ensures that the snapshot is taken. Passing epoch multiple times without manually triggering pending snapshot in a short period of time may cause the e2e environment to fail. - cy.wrap(axios.post(`${env.serverEndpoint}snapshots/pending`)); + cy.wrap(null).then(() => axios.post(`${env.serverEndpoint}snapshots/pending`)); // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); // reload is needed to get updated data in the app From e6d2fc1186d43c70d90034e522d8f01e432e54c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 17:01:48 +0100 Subject: [PATCH 08/22] style: restor rest of tests, specify skeleton check --- client/cypress/e2e/earn.cy.ts | 440 +++++++------ client/cypress/e2e/layout.cy.ts | 126 ++++ client/cypress/e2e/metrics.cy.ts | 126 ++++ client/cypress/e2e/onboarding.cy.ts | 380 +++++++++++ client/cypress/e2e/patronMode.cy.ts | 724 +++++++++++++++++++++ client/cypress/e2e/project.cy.ts | 181 ++++++ client/cypress/e2e/projects.cy.ts | 239 +++++++ client/cypress/e2e/projectsArchive.cy.ts | 103 +++ client/cypress/e2e/rewardsCalculator.cy.ts | 252 +++++++ client/cypress/e2e/routes.cy.ts | 42 ++ client/cypress/e2e/settings.cy.ts | 188 ++++++ 11 files changed, 2592 insertions(+), 209 deletions(-) create mode 100644 client/cypress/e2e/layout.cy.ts create mode 100644 client/cypress/e2e/metrics.cy.ts create mode 100644 client/cypress/e2e/onboarding.cy.ts create mode 100644 client/cypress/e2e/patronMode.cy.ts create mode 100644 client/cypress/e2e/project.cy.ts create mode 100644 client/cypress/e2e/projects.cy.ts create mode 100644 client/cypress/e2e/projectsArchive.cy.ts create mode 100644 client/cypress/e2e/rewardsCalculator.cy.ts create mode 100644 client/cypress/e2e/routes.cy.ts create mode 100644 client/cypress/e2e/settings.cy.ts diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index f7dcbef588..f41aaddff1 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -14,247 +14,269 @@ const connectWallet = (): Chainable => { }; Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }, idx) => { - describe(`earn: ${device}`, { retries: { openMode: 0, runMode: 0 }, viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.earn.absolute); - }); + describe( + `earn: ${device}`, + { retries: { openMode: 0, runMode: 0 }, viewportHeight, viewportWidth }, + () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); - it('renders "Locked balance" box', () => { - cy.get('[data-test=BoxGlmLock__BoxRounded]').should('be.visible'); - }); + beforeEach(() => { + cy.disconnectMetamaskWalletFromAllDapps(); + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.earn.absolute); + }); - it('renders "Personal allocation" box', () => { - cy.get('[data-test=BoxPersonalAllocation]').should('be.visible'); - }); + it('renders "Locked balance" box', () => { + cy.get('[data-test=BoxGlmLock__BoxRounded]').should('be.visible'); + }); - it('renders "History"', () => { - cy.get('[data-test=History]').should('be.visible'); - }); + it('renders "Personal allocation" box', () => { + cy.get('[data-test=BoxPersonalAllocation]').should('be.visible'); + }); - it('"Lock GLM" button is visible', () => { - cy.get('[data-test=BoxGlmLock__Button]').should('be.visible'); - }); + it('renders "History"', () => { + cy.get('[data-test=History]').should('be.visible'); + }); - it('"Lock GLM" button is disabled', () => { - cy.get('[data-test=BoxGlmLock__Button]').should('be.disabled'); - }); + it('"Lock GLM" button is visible', () => { + cy.get('[data-test=BoxGlmLock__Button]').should('be.visible'); + }); - it('"Withdraw to wallet" button is visible', () => { - cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.visible'); - }); + it('"Lock GLM" button is disabled', () => { + cy.get('[data-test=BoxGlmLock__Button]').should('be.disabled'); + }); - it('"Withdraw to wallet" button is disabled', () => { - cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.disabled'); - }); + it('"Withdraw to wallet" button is visible', () => { + cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.visible'); + }); - it('"Effective" section has tooltip', () => { - cy.get('[data-test=BoxGlmLock__Section--effective]').should('be.visible'); - }); + it('"Withdraw to wallet" button is disabled', () => { + cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.disabled'); + }); - if (!isDesktop) { - it('"Effective" section tooltip svg icon opens "TooltipEffectiveLockedBalance"', () => { - cy.get('[data-test=BoxGlmLock__Section--effective__Svg]').click(); - cy.get('[data-test=TooltipEffectiveLockedBalance]').should('be.visible'); + it('"Effective" section has tooltip', () => { + cy.get('[data-test=BoxGlmLock__Section--effective]').should('be.visible'); }); - } - it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button is active', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').should('not.be.disabled'); - }); + if (!isDesktop) { + it('"Effective" section tooltip svg icon opens "TooltipEffectiveLockedBalance"', () => { + cy.get('[data-test=BoxGlmLock__Section--effective__Svg]').click(); + cy.get('[data-test=TooltipEffectiveLockedBalance]').should('be.visible'); + }); + } + + it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button is active', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').should('not.be.disabled'); + }); - it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button opens "ModalGlmLock"', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - }); + it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button opens "ModalGlmLock"', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock]').should('be.visible'); + }); - it('Wallet connected: "ModalGlmLock" has overflow', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock__overflow]').should('exist'); - }); + it('Wallet connected: "ModalGlmLock" has overflow', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock__overflow]').should('exist'); + }); - it('Wallet connected: inputs allow to type multiple characters without focus problems', () => { - /** - * In EarnGlmLock there are multiple autofocus rules set. - * This test checks if user is still able to type without any autofocus disruption. - */ - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); - }); + it('Wallet connected: inputs allow to type multiple characters without focus problems', () => { + /** + * In EarnGlmLock there are multiple autofocus rules set. + * This test checks if user is still able to type without any autofocus disruption. + */ + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock]').should('be.visible'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); + cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); + }); - it('Wallet connected: "ModalGlmLock" - changing tabs keep focus on first input', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=EarnGlmLockTabs__tab--0]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - }); + it('Wallet connected: "ModalGlmLock" - changing tabs keep focus on first input', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock]').should('be.visible'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + cy.get('[data-test=EarnGlmLockTabs__tab--0]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + }); - it('Wallet connected: Lock 1 GLM', () => { - connectWallet(); + it('Wallet connected: Lock 1 GLM', () => { + connectWallet(); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToLock = 1; - const lockedGlms = parseInt(text, 10); + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') + .invoke('text') + .then(text => { + const amountToLock = 1; + const lockedGlms = parseInt(text, 10); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type(`${amountToLock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - // Workaround for two notifications during first transaction. - // 1. Allow the third party to spend TKN from your current balance. - // 2. Confirm permission to spend - if (Cypress.env('CI') === 'true' && idx === 0) { - cy.wait(1000); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') + .clear() + .type(`${amountToLock}`); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get('[data-test=GlmLockTabs__Button]').should( + 'have.text', + 'Waiting for confirmation', + ); cy.confirmMetamaskPermissionToSpend({ spendLimit: '99999999999999999999', }); - } - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - ).should('be.visible'); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText, 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Locked GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', '1 GLM'); - }); - }); - - it('Wallet connected: Unlock 1 GLM', () => { - connectWallet(); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToUnlock = 1; - const lockedGlms = parseInt(text, 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') - .clear() - .type(`${amountToUnlock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Unlock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); - cy.confirmMetamaskPermissionToSpend({ - shouldWaitForPopupClosure: true, - spendLimit: '99999999999999999999', - }); - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 60000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - ).should('be.visible'); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterUnlock = parseInt(nextText, 10); - expect(lockedGlms - amountToUnlock).to.be.eq(lockedGlmsAfterUnlock); - }); - cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Unlocked GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', '1 GLM'); - }); - }); - - it('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { - connectWallet(); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToLock = 1000; - const lockedGlms = parseInt(text.replace(/\u200a/g, ''), 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type(`${amountToLock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.window().then(async win => { - // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test^=DoubleValueSkeleton]', { timeout: 60000 }).should('not.exist'); - moveEpoch(win); + // Workaround for two notifications during first transaction. + // 1. Allow the third party to spend TKN from your current balance. + // 2. Confirm permission to spend + if (Cypress.env('CI') === 'true' && idx === 0) { + cy.wait(1000); + cy.confirmMetamaskPermissionToSpend({ + spendLimit: '99999999999999999999', + }); + } + cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( + 'have.text', + 'Close', + ); + cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get( + '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + ).should('be.visible'); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, }) .invoke('text') .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); + const lockedGlmsAfterLock = parseInt(nextText, 10); expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); }); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { + cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Locked GLM'); + cy.get('[data-test=HistoryItem__DoubleValue__primary]') + .first() + .should('have.text', '1 GLM'); + }); + }); + + it('Wallet connected: Unlock 1 GLM', () => { + connectWallet(); + + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') + .invoke('text') + .then(text => { + const amountToUnlock = 1; + const lockedGlms = parseInt(text, 10); + + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') + .clear() + .type(`${amountToUnlock}`); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Unlock'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get('[data-test=GlmLockTabs__Button]').should( + 'have.text', + 'Waiting for confirmation', + ); + cy.confirmMetamaskPermissionToSpend({ + shouldWaitForPopupClosure: true, + spendLimit: '99999999999999999999', + }); + cy.get('[data-test=GlmLockTabs__Button]', { timeout: 60000 }).should( + 'have.text', + 'Close', + ); + cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get( + '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + ).should('be.visible'); + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, }) .invoke('text') .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + const lockedGlmsAfterUnlock = parseInt(nextText, 10); + expect(lockedGlms - amountToUnlock).to.be.eq(lockedGlmsAfterUnlock); }); + cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Unlocked GLM'); + cy.get('[data-test=HistoryItem__DoubleValue__primary]') + .first() + .should('have.text', '1 GLM'); }); - }); - }); - }); + }); + + it('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { + connectWallet(); + + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') + .invoke('text') + .then(text => { + const amountToLock = 1000; + const lockedGlms = parseInt(text.replace(/\u200a/g, ''), 10); + + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') + .clear() + .type(`${amountToLock}`); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get('[data-test=GlmLockTabs__Button]').should( + 'have.text', + 'Waiting for confirmation', + ); + cy.confirmMetamaskPermissionToSpend({ + spendLimit: '99999999999999999999', + }); + cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( + 'have.text', + 'Close', + ); + cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get( + '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + ).should('be.visible'); + cy.window().then(async win => { + // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { + timeout: 60000, + }).should('not.exist'); + moveEpoch(win); + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { + timeout: 60000, + }) + .invoke('text') + .then(nextText => { + const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); + expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + }); + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { + timeout: 60000, + }) + .invoke('text') + .then(nextText => { + const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); + expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + }); + }); + }); + }); + }, + ); }); diff --git a/client/cypress/e2e/layout.cy.ts b/client/cypress/e2e/layout.cy.ts new file mode 100644 index 0000000000..9ea9954d24 --- /dev/null +++ b/client/cypress/e2e/layout.cy.ts @@ -0,0 +1,126 @@ +import { navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; +import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`layout: ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + cy.clearLocalStorage(); + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + cy.disconnectMetamaskWalletFromAllDapps(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + cy.visit(ROOT.absolute); + }); + + it('renders top bar', () => { + cy.get('[data-test=MainLayout__Header]').should('be.visible'); + }); + + it('Clicking on Octant logo scrolls view to the top on logo click (projects view)', () => { + cy.scrollTo(0, 500); + cy.get('[data-test=MainLayout__Logo]').click(); + // waiting for scrolling to finish + cy.wait(2000); + cy.window().then(cyWindow => { + expect(cyWindow.scrollY).to.be.eq(0); + }); + }); + + it('Clicking on Octant logo redirects to projects view (outside projects view)', () => { + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsView]').should('be.visible'); + cy.get('[data-test=MainLayout__Logo]').click(); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('Clicking on Octant logo redirects to projects view (outside projects view) with memorized scrollY', () => { + cy.scrollTo(0, 500); + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsView]').should('be.visible'); + cy.get('[data-test=MainLayout__Logo]').click(); + cy.get('[data-test=ProjectsView]').should('be.visible'); + cy.window().then(cyWindow => { + expect(cyWindow.scrollY).to.be.eq(500); + }); + }); + + it('renders bottom navbar', () => { + cy.get('[data-test=Navbar]').should('be.visible'); + }); + + it('bottom navbar allows to change views', () => { + navigationTabs.forEach(({ to }) => { + navigateWithCheck(to); + }); + }); + + it('"Connect" button is visible when wallet is disconnected', () => { + cy.get('[data-test=MainLayout__Button--connect]').should('be.visible'); + cy.get('[data-test=MainLayout__Button--connect]').click(); + }); + + it('"Connect" button opens "ModalConnectWallet"', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet]').should('be.visible'); + }); + + it('"ModalConnectWallet" always shows "WalletConnect" option', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet]').within(() => { + cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); + }); + }); + + it('"ModalConnectWallet" shows "Browser wallet" and "WalletConnect" options (MetaMask wallet detected)', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet]').within(() => { + cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); + cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').should('be.visible'); + }); + }); + + it('"ModalConnectWallet" has overflow enabled', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__overflow]').should('exist'); + }); + + it('Clicking background when "ModalConnectWallet" is open, closes Modal', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__overflow]').click({ force: true }); + cy.get('[data-test=ModalConnectWallet]').should('not.exist'); + }); + + it('"ModalConnectWallet" has "cross" icon button in header', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__Button]').should('be.visible'); + }); + + it('Clicking on "X" mark in "ModalConnectWallet", closes Modal', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__Button]').click(); + cy.get('[data-test=ModalConnectWallet]').should('not.exist'); + }); + + it('Clicking on "WalletConnect" option, opens Web3Modal', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').click(); + cy.get('w3m-modal').find('#w3m-modal', { includeShadowDom: true }).should('be.visible'); + }); + + it('Clicking on "Browser wallet" option connects with MetaMask wallet', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); + cy.switchToMetamaskNotification(); + cy.acceptMetamaskAccess(); + cy.get('[data-test=MainLayout__Button--connect]').should('not.exist'); + cy.get('[data-test=ProfileInfo]').should('exist'); + }); + }); +}); diff --git a/client/cypress/e2e/metrics.cy.ts b/client/cypress/e2e/metrics.cy.ts new file mode 100644 index 0000000000..829fbfbf1c --- /dev/null +++ b/client/cypress/e2e/metrics.cy.ts @@ -0,0 +1,126 @@ +import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { + describe(`metrics: ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.metrics.absolute); + }); + + it('renders total projects tile', () => { + cy.get('[data-test=MetricsGeneralGridTotalProjects]').should('be.visible'); + }); + + it('renders total eth staked tile', () => { + cy.get('[data-test=MetricsGeneralGridTotalEthStaked]').should('be.visible'); + }); + + it('renders tile with total glm locked and % of 1B total supply groups', () => { + cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]').should('be.visible'); + cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]') + .children() + .should('have.length', 2); + }); + + it('renders wallet with glm locked graph tile', () => { + cy.get('[data-test=MetricsGeneralGridWalletsWithGlmLocked]').should('be.visible'); + }); + + it('renders cumulative glm locked graph tile', () => { + cy.get('[data-test=MetricsGeneralGridCumulativeGlmLocked]').should('be.visible'); + }); + + it('renders tiles in correct order', () => { + const metricsEpochGridTilesDataTest = [ + 'MetricsEpochGridTopProjects', + 'MetricsEpochGridTotalDonationsAndPersonal', + 'MetricsEpochGridDonationsVsPersonalAllocations', + 'MetricsEpochGridFundsUsage', + 'MetricsEpochGridTotalUsers', + 'MetricsEpochGridPatrons', + 'MetricsEpochGridCurrentDonors', + 'MetricsEpochGridAverageLeverage', + 'MetricsEpochGridRewardsUnusedAndUnallocatedValue', + 'MetricsEpochGridBelowThreshold', + ]; + + const metricsGeneralGridTilesDataTest = [ + 'MetricsGeneralGridTotalGlmLockedAndTotalSupply', + 'MetricsGeneralGridTotalProjects', + 'MetricsGeneralGridTotalEthStaked', + 'MetricsGeneralGridCumulativeGlmLocked', + 'MetricsGeneralGridWalletsWithGlmLocked', + ]; + + cy.get('[data-test=MetricsEpoch__MetricsGrid]') + .children() + .should('have.length', metricsEpochGridTilesDataTest.length); + + for (let i = 0; i < metricsEpochGridTilesDataTest.length; i++) { + cy.get('[data-test=MetricsEpoch__MetricsGrid]') + .children() + .eq(i) + .invoke('data', 'test') + .should('eq', metricsEpochGridTilesDataTest[i]); + } + + cy.get('[data-test=MetricsGeneral__MetricsGrid]') + .children() + .should('have.length', metricsGeneralGridTilesDataTest.length); + + for (let i = 0; i < metricsGeneralGridTilesDataTest.length; i++) { + cy.get('[data-test=MetricsGeneral__MetricsGrid]') + .children() + .eq(i) + .invoke('data', 'test') + .should('eq', metricsGeneralGridTilesDataTest[i]); + } + }); + + it('renders grid with 4 columns on desktop or with 2 columns on other devices', () => { + cy.get('[data-test=MetricsEpoch__MetricsGrid]').then(el => { + const width = parseInt(el.css('width'), 10); + const rowGap = parseInt(el.css('rowGap'), 10); + + const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; + + cy.get('[data-test=MetricsEpoch__MetricsGrid]').should( + 'have.css', + 'grid-template-columns', + isDesktop + ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` + : `${columnWidth}px ${columnWidth}px`, + ); + }); + + cy.get('[data-test=MetricsGeneral__MetricsGrid]').then(el => { + const width = parseInt(el.css('width'), 10); + const rowGap = parseInt(el.css('rowGap'), 10); + + const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; + + cy.get('[data-test=MetricsGeneral__MetricsGrid]').should( + 'have.css', + 'grid-template-columns', + isDesktop + ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` + : `${columnWidth}px ${columnWidth}px`, + ); + }); + }); + }); +}); diff --git a/client/cypress/e2e/onboarding.cy.ts b/client/cypress/e2e/onboarding.cy.ts new file mode 100644 index 0000000000..e528f9e87f --- /dev/null +++ b/client/cypress/e2e/onboarding.cy.ts @@ -0,0 +1,380 @@ +import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { stepsDecisionWindowClosed } from 'src/hooks/helpers/useOnboardingSteps/steps'; +import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +import Chainable = Cypress.Chainable; + +const connectWallet = ( + isTOSAccepted: boolean, + shouldVisit = true, + shouldReload = false, +): Chainable => { + cy.intercept('GET', '/user/*/tos', { body: { accepted: isTOSAccepted } }); + cy.disconnectMetamaskWalletFromAllDapps(); + if (shouldVisit) { + visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); + } + if (shouldReload) { + cy.reload(); + } + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); + cy.switchToMetamaskNotification(); + return cy.acceptMetamaskAccess(); +}; + +const beforeSetup = () => { + mockCoinPricesServer(); + cy.clearLocalStorage(); + cy.setupMetamask(); + window.innerWidth = Cypress.config().viewportWidth; + window.innerHeight = Cypress.config().viewportHeight; +}; + +const checkCurrentElement = (el: number, isCurrent: boolean): Chainable => { + return cy + .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') + .eq(el) + .invoke('attr', 'data-iscurrent') + .should('eq', `${isCurrent}`); +}; + +const checkProgressStepperSlimIsCurrentAndClickNext = (index, isCurrent = true): Chainable => { + checkCurrentElement(index - 1, isCurrent); + return cy + .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') + .eq(index) + .click({ force: true }); +}; + +const checkChangeStepsWithArrowKeys = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + [ + { el: 1, key: 'ArrowRight' }, + { el: 2, key: 'ArrowRight' }, + // { el: 3, key: 'ArrowRight' }, + // { el: 3, key: 'ArrowRight' }, + // { el: 2, key: 'ArrowLeft' }, + { el: 1, key: 'ArrowLeft' }, + { el: 0, key: 'ArrowLeft' }, + { el: 0, key: 'ArrowLeft' }, + ].forEach(({ key, el }) => { + cy.get('body').trigger('keydown', { key }); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); +}; + +const checkChangeStepsByClickingEdgeOfTheScreenUpTo25px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + cy.get('[data-test=ModalOnboarding]').then(element => { + const leftEdgeX = element.offsetParent().offset()?.left as number; + const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; + + [ + { clientX: rightEdgeX - 25, el: 1 }, + { clientX: rightEdgeX - 10, el: 2 }, + // { clientX: rightEdgeX - 5, el: 3 }, + // rightEdgeX === browser right frame + // { clientX: rightEdgeX - 1, el: 3 }, + // { clientX: leftEdgeX + 25, el: 2 }, + { clientX: leftEdgeX + 10, el: 1 }, + { clientX: leftEdgeX + 5, el: 0 }, + { clientX: leftEdgeX, el: 0 }, + ].forEach(({ clientX, el }) => { + cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); + }); +}; + +const checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + cy.get('[data-test=ModalOnboarding]').then(element => { + const leftEdgeX = element.offsetParent().offset()?.left as number; + const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; + + [ + { clientX: rightEdgeX - 25, el: 1 }, + { clientX: rightEdgeX - 26, el: 1 }, + { clientX: leftEdgeX + 26, el: 1 }, + { clientX: leftEdgeX + 25, el: 0 }, + ].forEach(({ clientX, el }) => { + cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); + }); +}; + +const checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + [ + { + el: 1, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 2, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 2, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + // { + // el: 3, + // touchMoveClientX: window.innerWidth / 2 - 5, + // touchStartClientX: window.innerWidth / 2, + // }, + { + el: 2, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 1, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 0, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 0, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { + cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { + touches: [{ clientX: touchStartClientX }], + }); + cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { + touches: [{ clientX: touchMoveClientX }], + }); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); +}; + +const checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + [ + { + el: 1, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 1, + touchMoveClientX: window.innerWidth / 2 - 4, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 1, + touchMoveClientX: window.innerWidth / 2 + 4, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 0, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { + cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { + touches: [{ clientX: touchStartClientX }], + }); + cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { + touches: [{ clientX: touchMoveClientX }], + }); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); +}; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`onboarding (TOS accepted): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + beforeSetup(); + }); + + beforeEach(() => { + connectWallet(true); + }); + + it('user is able to click through entire onboarding flow', () => { + for (let i = 1; i < stepsDecisionWindowClosed.length - 1; i++) { + checkProgressStepperSlimIsCurrentAndClickNext(i); + } + + cy.get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') + .eq(stepsDecisionWindowClosed.length - 1) + .click(); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); + }); + + it('user is able to close the modal by clicking button in the top-right', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); + }); + + it('renders every time page is refreshed when "Always show Allocate onboarding" option is checked', () => { + cy.get('[data-test=ModalOnboarding__Button]').click(); + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check().should('be.checked'); + cy.reload(); + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + }); + + it('renders only once when "Always show Allocate onboarding" option is not checked', () => { + cy.get('[data-test=ModalOnboarding__Button]').click(); + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); + cy.reload(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + }); + + it('user can change steps with arrow keys (left, right)', () => { + checkChangeStepsWithArrowKeys(true); + }); + + it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(true); + }); + + it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(true); + }); + + it('user can change steps by swiping on screen (difference more than or equal 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(true); + }); + + it('user cannot change steps by swiping on screen (difference less than 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); + }); + + it('user cannot change steps by swiping on screen (difference less than 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); + }); + + it('user is able to close the onboarding, and after disconnecting & connecting, onboarding does not show up again', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + connectWallet(true, false, true); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + }); + }); +}); + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`onboarding (TOS not accepted): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + beforeSetup(); + }); + + beforeEach(() => { + cy.intercept( + { + method: 'POST', + url: '/user/*/tos', + }, + { body: { accepted: true }, statusCode: 200 }, + ); + connectWallet(false); + }); + + it('onboarding TOS step should be first and active', () => { + checkCurrentElement(0, true); + cy.get('[data-test=ModalOnboardingTOS]').should('be.visible'); + }); + + it('user is not able to click through entire onboarding flow', () => { + for (let i = 1; i < stepsDecisionWindowClosed.length; i++) { + checkProgressStepperSlimIsCurrentAndClickNext(i, i === 1); + } + }); + + it('user is not able to close the modal by clicking button in the top-right', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.get('[data-test=ModalOnboarding__Button]').click({ force: true }); + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + }); + + it('renders every time page is refreshed', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.reload(); + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + }); + + it('user cannot change steps with arrow keys (left, right)', () => { + checkChangeStepsWithArrowKeys(false); + }); + + it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(false); + }); + + it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(false); + }); + + it('user cannot change steps by swiping on screen (difference more than or equal 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(false); + }); + + it('user cannot change steps by swiping on screen (difference less than 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(false); + }); + + it('TOS acceptance changes onboarding step to next step', () => { + checkCurrentElement(0, true); + cy.get('[data-test=TOS_InputCheckbox]').check(); + cy.switchToMetamaskNotification(); + cy.confirmMetamaskSignatureRequest(); + checkCurrentElement(1, true); + }); + + it('TOS acceptance allows the user to close the modal by clicking button in the top-right', () => { + checkCurrentElement(0, true); + cy.get('[data-test=TOS_InputCheckbox]').check(); + cy.switchToMetamaskNotification(); + cy.confirmMetamaskSignatureRequest(); + checkCurrentElement(1, true); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + }); + }); +}); diff --git a/client/cypress/e2e/patronMode.cy.ts b/client/cypress/e2e/patronMode.cy.ts new file mode 100644 index 0000000000..b5f3455c47 --- /dev/null +++ b/client/cypress/e2e/patronMode.cy.ts @@ -0,0 +1,724 @@ +import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { + describe(`patron mode (disabled): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.settings.absolute); + connectWallet(true, false); + }); + + it('patron badge should not exist ', () => { + cy.get('[data-test=ProfileInfo__badge]').should('not.exist'); + }); + + it('Patron mode toggle is not checked', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('not.be.checked'); + }); + + if (isDesktop) { + it('Patron mode tooltip is visible on hover and has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') + .invoke('text') + .should( + 'eq', + 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', + ); + }); + } + + it('Checking patron mode opens patron mode modal', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=ModalPatronMode]').should('be.visible'); + }); + + it('Patron mode modal last paragraph has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=SettingsPatronMode__fourthParagraph]') + .invoke('text') + .should('eq', 'Slide the switch below all the way to the right to enable patron mode.'); + }); + + it('Slider is visible', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider]').should('be.visible'); + }); + + it('Slider has correct label', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider__label]') + .invoke('text') + .should('eq', 'Slide right to confirm'); + }); + + it('Slider button is visible ', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); + }); + + it('Slider button has right arrow inside', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider__button__arrow]') + .should('be.visible') + .should('have.css', 'transform', 'none'); + }); + + it('Slider button is on the left side', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderLeftDistance = sliderEl[0].getBoundingClientRect().left; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonLeftDistance = sliderButtonEl[0].getBoundingClientRect().left; + + expect(sliderButtonLeftDistance).to.be.eq(sliderLeftDistance + sliderLeftPadding); + }); + }); + }); + + it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.x; + const pointerMovePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerDownPageX); + }); + }); + }); + }); + + it('Slider elements change color while moving slider button', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const slider0PercentagePageX = sliderButtonDimensions.x; + const slider25PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 4; + const slider50PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; + const slider75PercentagePageX = sliderButtonDimensions.x + 3 * (sliderTrackWidth / 4); + const slider100PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth; + + // 0% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { + pageX: slider0PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgb(243, 243, 243)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(23, 23, 23)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '1'); + + // 25% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider25PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(212, 224, 221, 0.95)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(222, 234, 231)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(129, 129, 129)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.75'); + + // 50% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider50PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(175, 204, 197, 0.9)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(183, 211, 204)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(181, 181, 181)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.5'); + + // 75% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider75PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(128, 181, 169, 0.85)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(133, 185, 173)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(221, 221, 221)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.25'); + + // 100% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider100PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(45, 155, 135, 0.8)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(45, 155, 135)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0'); + }); + }); + }); + + it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.x; + const pointerMovePageX = sliderButtonDimensions.x + (sliderTrackWidth / 2 + 1); + const pointerUpPageX = sliderDimensions.right - sliderRightPadding - sliderButtonWidth; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); + }); + + cy.confirmMetamaskSignatureRequest(); + cy.switchToCypressWindow(); + cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__status-label]') + .invoke('text') + .should('eq', 'Patron mode enabled'); + cy.wait(500); + cy.get('[data-test=ModalPatronMode]').should('not.exist'); + }); + }); + }); + }); + + describe(`patron mode (enabled): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.settings.absolute); + connectWallet(true, true); + }); + + it('patron badge is visible and has correct label, background and text-transform prop', () => { + cy.get('[data-test=ProfileInfo__badge]').should('be.visible'); + cy.get('[data-test=ProfileInfo__badge]').invoke('text').should('eq', 'Patron'); + cy.get('[data-test=ProfileInfo__badge]') + .should('have.css', 'background-color', 'rgb(104, 91, 138)') + .should('have.css', 'text-transform', 'uppercase'); + }); + + it('Navbar has 4 items - projects, earn, metrics, settings', () => { + const navbarChildrenDataTest = [ + 'Navbar__Button--Projects', + 'Navbar__Button--Earn', + 'Navbar__Button--Metrics', + 'Navbar__Button--Settings', + ]; + + cy.get('[data-test=Navbar__buttons]') + .children() + .should('have.length', navbarChildrenDataTest.length); + + for (let i = 0; i < navbarChildrenDataTest.length; i++) { + cy.get('[data-test=Navbar__buttons]') + .children() + .eq(i) + .invoke('data', 'test') + .should('eq', navbarChildrenDataTest[i]); + } + }); + + it('route /allocate redirects to /projects', () => { + visitWithLoader(ROOT_ROUTES.allocation.absolute, ROOT_ROUTES.projects.absolute); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('BoxPersonalAllocation has correct title and sections labels', () => { + visitWithLoader(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=BoxPersonalAllocation__title]') + .invoke('text') + .should('eq', 'Patron earnings'); + cy.get('[data-test=BoxPersonalAllocation__Section__label]') + .eq(0) + .invoke('text') + .should('eq', 'Current epoch'); + cy.get('[data-test=BoxPersonalAllocation__Section__label]') + .eq(1) + .invoke('text') + .should('eq', 'All time'); + }); + + it('Patron mode toggle is checked', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('be.checked'); + }); + + if (isDesktop) { + it('Patron mode tooltip is visible on hover and has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') + .invoke('text') + .should( + 'eq', + 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', + ); + }); + } + + it('Unchecking patron mode opens patron mode modal', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=ModalPatronMode]').should('be.visible'); + }); + + it('Patron mode modal last paragraph has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=SettingsPatronMode__fourthParagraph]') + .invoke('text') + .should('eq', 'Slide the switch below all the way to the left to disable patron mode.'); + }); + + it('Slider is visible', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider]').should('be.visible'); + }); + + it('Slider has correct label', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider__label]') + .invoke('text') + .should('eq', 'Slide left to confirm'); + }); + + it('Slider button is visible', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); + }); + + it('Slider button has left arrow inside', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider__button__arrow]') + .should('be.visible') + .should('have.css', 'transform', 'matrix(-1, 0, 0, -1, 0, 0)'); + }); + + it('Slider button is on the right side', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderRightDistance = sliderEl[0].getBoundingClientRect().right; + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonRightDistance = sliderButtonEl[0].getBoundingClientRect().right; + + expect(sliderButtonRightDistance).to.be.eq(sliderRightDistance - sliderRightPadding); + }); + }); + }); + + it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.right; + const pointerMovePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.right).eq(pointerDownPageX); + }); + }); + }); + }); + + it('Slider elements change color while moving slider button', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const slider0PercentagePageX = sliderButtonDimensions.right; + const slider25PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 4; + const slider50PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; + const slider75PercentagePageX = sliderButtonDimensions.right - 3 * (sliderTrackWidth / 4); + const slider100PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth; + + // 0% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { + pageX: slider0PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgb(243, 243, 243)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(23, 23, 23)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '1'); + + // 25% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider25PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(212, 224, 221, 0.95)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(222, 234, 231)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(129, 129, 129)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.75'); + + // 50% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider50PercentagePageX, + waitForAnimations: true, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(175, 204, 197, 0.9)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(183, 211, 204)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(181, 181, 181)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.5'); + + // 75% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider75PercentagePageX, + waitForAnimations: true, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(128, 181, 169, 0.85)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(133, 185, 173)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(221, 221, 221)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.25'); + + // 100% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider100PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(45, 155, 135, 0.8)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(45, 155, 135)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0'); + }); + }); + }); + + it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.right; + const pointerMovePageX = sliderButtonDimensions.right - (sliderTrackWidth / 2 + 1); + const pointerUpPageX = sliderDimensions.left + sliderLeftPadding; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); + }); + + cy.confirmMetamaskSignatureRequest(); + cy.switchToCypressWindow(); + cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__status-label]') + .invoke('text') + .should('eq', 'Patron mode disabled'); + cy.wait(500); + cy.get('[data-test=ModalPatronMode]').should('not.exist'); + }); + }); + }); + + it('when entering project view, button icon changes to chevronLeft', () => { + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + cy.get('[data-test=Navbar__Button--Projects]') + .find('svg') + // HTML tag can't be self-closing in CY. + .should( + 'have.html', + '', + ); + }); + }); +}); diff --git a/client/cypress/e2e/project.cy.ts b/client/cypress/e2e/project.cy.ts new file mode 100644 index 0000000000..e811fb70aa --- /dev/null +++ b/client/cypress/e2e/project.cy.ts @@ -0,0 +1,181 @@ +import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import { getNamesOfProjects } from 'cypress/utils/projects'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +import Chainable = Cypress.Chainable; + +const getButtonAddToAllocate = (): Chainable => { + const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); + + return projectListItemFirst.find('[data-test=ProjectListItemHeader__ButtonAddToAllocate]'); +}; + +const checkProjectItemElements = (): Chainable => { + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); + projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); + getButtonAddToAllocate().should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItemHeader__Button]').should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItem__Description]').should('be.visible'); + + cy.get('[data-test=ProjectListItem__Donors]') + .first() + .scrollIntoView({ offset: { left: 0, top: 100 } }); + + cy.get('[data-test=ProjectListItem__Donors]').first().should('be.visible'); + cy.get('[data-test=ProjectListItem__Donors__DonorsHeader__count]') + .first() + .should('be.visible') + .should('have.text', '0'); + return cy.get('[data-test=ProjectListItem__Donors__noDonationsYet]').first().should('be.visible'); +}; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`project: ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + it('entering project view directly renders content', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + cy.reload(); + const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); + projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); + }); + + it('entering project view renders all its elements', () => { + checkProjectItemElements(); + }); + + it('entering project view renders all its elements with fallback IPFS provider', () => { + cy.intercept('GET', '**/ipfs/**', req => { + if (req.url.includes('infura')) { + req.destroy(); + } + }); + + checkProjectItemElements(); + }); + + it('entering project view shows Toast with info about IPFS failure when all providers fail', () => { + cy.intercept('GET', '**/ipfs/**', req => { + req.destroy(); + }); + + cy.get('[data-test=Toast--ipfsMessage').should('be.visible'); + }); + + it('entering project view allows to add it to allocation and remove, triggering change of the icon, change of the number in navbar', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + + getButtonAddToAllocate().click(); + + // cy.get('@buttonAddToAllocate').click(); + cy.get('[data-test=Navbar__numberOfAllocations]').contains(1); + getButtonAddToAllocate().click(); + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + }); + + it('Entering project view allows scroll only to the last project', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); + + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test=ProjectListItem]').should( + 'have.length.greaterThan', + i === projectNames.length - 1 ? projectNames.length - 1 : i, + ); + cy.get('[data-test=ProjectListItemHeader__name]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: -150 } }) + .contains(projectNames[i]); + cy.get('[data-test=ProjectListItem__Donors]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: -150 } }) + .should('be.visible'); + } + }); + + it('"Back to top" button is displayed if the user has scrolled past the start of the final project description', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); + + for (let i = 0; i < projectNames.length - 1; i++) { + cy.get('[data-test=ProjectListItem__Donors]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: 100 } }); + + if (i === projectNames.length - 1) { + cy.get('[data-test=ProjectBackToTopButton__Button]').should('be.visible'); + } + } + }); + + it('Clicking on "Back to top" button scrolls to the top of view (first project is visible)', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); + + for (let i = 0; i < projectNames.length - 1; i++) { + cy.get('[data-test=ProjectListItem__Donors]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: 100 } }); + + if (i === projectNames.length - 1) { + cy.get('[data-test=ProjectBackToTopButton__Button]').click(); + cy.get('[data-test=ProjectListItem]').eq(0).should('be.visible'); + } + } + }); + }); + + describe(`project (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + connectWallet(true, true); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + it('button "add to allocate" is disabled', () => { + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).click(); + getButtonAddToAllocate().should('be.visible').should('be.disabled'); + cy.go('back'); + } + }); + }); +}); diff --git a/client/cypress/e2e/projects.cy.ts b/client/cypress/e2e/projects.cy.ts new file mode 100644 index 0000000000..9d096bcdf2 --- /dev/null +++ b/client/cypress/e2e/projects.cy.ts @@ -0,0 +1,239 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import chaiColors from 'chai-colors'; + +import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import { getNamesOfProjects } from 'cypress/utils/projects'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import getMilestones from 'src/constants/milestones'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +import Chainable = Cypress.Chainable; + +chai.use(chaiColors); + +function checkProjectItemElements(index, name, isPatronMode = false): Chainable { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__imageProfile]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem]') + .eq(index) + .should('be.visible') + .find('[data-test=ProjectsListItem__name]') + .should('be.visible') + .contains(name); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__IntroDescription]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .should('be.visible'); + + if (isPatronMode) { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .should('be.disabled'); + } + + return cy + .get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectRewards]') + .should('be.visible'); + // TODO OCT-663 Make CY check if rewards are available (Epoch 2, decision window open). + // return cy + // .get('[data-test^=ProjectsView__ProjectsListItem') + // .eq(index) + // .find('[data-test=ProjectRewards__currentTotal__label]') + // .should('be.visible'); +} + +function addProjectToAllocate(index, numberOfAddedProjects): Chainable { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__imageProfile]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__IntroDescription]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .scrollIntoView(); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .click(); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .find('svg') + .find('path') + .then($el => $el.css('fill')) + .should('be.colored', '#FF6157'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .find('svg') + .find('path') + .then($el => $el.css('stroke')) + .should('be.colored', '#FF6157'); + cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects + 1); + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects + 1); + return cy.go('back'); +} + +function removeProjectFromAllocate(numberOfProjects, numberOfAddedProjects, index): Chainable { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .scrollIntoView(); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .click(); + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects - 1); + if (index < numberOfProjects - 1) { + cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects - 1); + } else { + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + } + return cy.go('back'); +} + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`projects: ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + it('user is able to see all the projects in the view', () => { + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); + checkProjectItemElements(i, projectNames[i]); + } + }); + + it('user is able to add & remove the first and the last project to/from allocation, triggering change of the icon, change of the number in navbar', () => { + // This test checks the first and the last elements only to save time. + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + + addProjectToAllocate(0, 0); + addProjectToAllocate(projectNames.length - 1, 1); + removeProjectFromAllocate(projectNames.length, 2, 0); + removeProjectFromAllocate(projectNames.length, 1, projectNames.length - 1); + }); + + it('user is able to add project to allocation in ProjectsView and remove it from allocation in AllocationView', () => { + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + addProjectToAllocate(0, 0); + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationItemSkeleton]').should('not.exist'); + cy.get('[data-test=AllocationItem]').then(el => { + const { x } = el[0].getBoundingClientRect(); + cy.get('[data-test=AllocationItem]') + .trigger('pointerdown') + .trigger('pointermove', { pageX: x - 20 }) + .trigger('pointerup'); + cy.get('[data-test=AllocationItem__removeButton]').should('be.visible'); + cy.get('[data-test=AllocationItem__removeButton]').click(); + cy.get('[data-test=AllocationItem__removeButton]').should('not.exist'); + cy.get('[data-test=AllocationItem]').should('not.exist'); + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + }); + + it('ProjectsTimelineWidgetItem with href opens link when clicked without mouse movement', () => { + const milestones = getMilestones(); + cy.get('[data-test=ProjectsTimelineWidget]').should('be.visible'); + cy.get('[data-test=ProjectsTimelineWidgetItem]').should('have.length', milestones.length); + for (let i = 0; i < milestones.length; i++) { + if (milestones[i].href) { + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .within(() => { + cy.get('[data-test=ProjectsTimelineWidgetItem__Svg--arrowTopRight]').should( + 'be.visible', + ); + }); + + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .then(el => { + const { x } = el[0].getBoundingClientRect(); + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .trigger('mousedown') + .trigger('mouseup', { clientX: x + 10 }); + cy.location('pathname').should('eq', ROOT_ROUTES.projects.absolute); + + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .trigger('mousedown') + .trigger('mouseup'); + cy.location('pathname').should('not.eq', ROOT_ROUTES.projects.absolute); + }); + } + } + }); + }); + }); + + describe(`projects (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + connectWallet(true, true); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + after(() => { + cy.disconnectMetamaskWalletFromAllDapps(); + }); + + it('button "add to allocate" is disabled', () => { + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); + checkProjectItemElements(i, projectNames[i], true); + } + }); + }); +}); diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts new file mode 100644 index 0000000000..d2abe9b5d0 --- /dev/null +++ b/client/cypress/e2e/projectsArchive.cy.ts @@ -0,0 +1,103 @@ +import { checkLocationWithLoader, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { QUERY_KEYS } from 'src/api/queryKeys'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +let wasEpochMoved = false; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`projects archive: ${device}`, { viewportHeight, viewportWidth }, () => { + beforeEach(() => { + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + }); + + it('moves to the next epoch', () => { + // Move time only once, for the first device. + if (!wasEpochMoved) { + cy.window().then(async win => { + const currentEpochBefore = Number( + win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), + ); + await win.mutateAsyncMoveEpoch(); + const currentEpochAfter = Number( + win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), + ); + wasEpochMoved = true; + expect(currentEpochBefore + 1).to.eq(currentEpochAfter); + }); + } else { + expect(true).to.be.true; + } + }); + + it('renders archive elements + clicking on epoch archive ProjectsListItem opens ProjectView for particular epoch and project', () => { + cy.get('[data-test=MainLayout__body]').then(el => { + const mainLayoutPaddingTop = parseInt(el.css('paddingTop'), 10); + + cy.get('[data-test=ProjectsView__ProjectsList]') + .should('be.visible') + .children() + .then(children => { + children[children.length - 1].scrollIntoView(); + cy.window().then(window => window.scrollTo(0, window.scrollY - mainLayoutPaddingTop)); + cy.wait(1000); + // header test + cy.get('[data-test=ProjectsView__ProjectsList__header--archive]').should('be.visible'); + + // list test + cy.get('[data-test=ProjectsView__ProjectsList--archive]').first().should('be.visible'); + cy.get('[data-test=ProjectsView__ProjectsList--archive]') + .first() + .children() + .then(childrenArchive => { + const numberOfArchivedProjects = childrenArchive.length - 2; // archived projects tiles - (header + divider)[2] + for (let i = 0; i < numberOfArchivedProjects; i++) { + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .scrollIntoView(); + cy.window().then(window => + window.scrollTo(0, window.scrollY - mainLayoutPaddingTop), + ); + // list item test + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .should('be.visible') + .within(() => { + // rewards test + cy.get('[data-test=ProjectRewards]').should('be.visible'); + }); + + if (numberOfArchivedProjects - 1) { + cy.get('[data-test=ProjectsView__ProjectsList--archive]') + .first() + .should('have.length', 1); + } + + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .invoke('data', 'address') + .then(address => { + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .invoke('data', 'epoch') + .then(epoch => { + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .click(); + checkLocationWithLoader( + `${ROOT_ROUTES.project.absolute}/${epoch}/${address}`, + ); + cy.go('back'); + checkLocationWithLoader(ROOT_ROUTES.projects.absolute); + }); + }); + } + }); + }); + }); + }); + }); +}); diff --git a/client/cypress/e2e/rewardsCalculator.cy.ts b/client/cypress/e2e/rewardsCalculator.cy.ts new file mode 100644 index 0000000000..361153ff90 --- /dev/null +++ b/client/cypress/e2e/rewardsCalculator.cy.ts @@ -0,0 +1,252 @@ +import { ETH_USD, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; +import getFormattedEthValue from 'src/utils/getFormattedEthValue'; +import { parseUnitsBigInt } from 'src/utils/parseUnitsBigInt'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { + describe(`rewards calculator: ${device}`, { viewportHeight, viewportWidth }, () => { + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.earn.absolute); + }); + + it('renders calculator icon inside box', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').should('be.visible'); + }); + + if (isDesktop) { + it('tooltip is visible on calculator icon hover and has correct text', () => { + cy.get('[data-test=Tooltip__rewardsCalculator').trigger('mouseover'); + cy.get('[data-test=Tooltip__rewardsCalculator__content') + .should('be.visible') + .invoke('text') + .should('eq', 'Calculate rewards'); + }); + } + + it('clicking on rewards calculator icon opens rewards calculator modal', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=ModalRewardsCalculator]').should('be.visible'); + }); + + it('default values in rewards calculator are 90 days and 5000 GLM', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=RewardsCalculator__InputText--crypto]').invoke('val').should('eq', '5000'); + cy.get('[data-test=RewardsCalculator__InputText--days]').invoke('val').should('eq', '90'); + }); + + it('calculator fetches rewards values in ETH and USD based on DAYS and GLM fields', () => { + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'be.visible', + ); + cy.wait('@postEstimatedRewards'); + + cy.get('@postEstimatedRewards').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get( + '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', + ).should('not.exist'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + }, + ); + + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsGlmValueChange'); + cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('500000'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'be.visible', + ); + cy.wait('@postEstimatedRewardsGlmValueChange'); + + cy.get('@postEstimatedRewardsGlmValueChange').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get( + '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', + ).should('not.exist'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + }, + ); + + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsDaysValueChange'); + cy.get('[data-test=RewardsCalculator__InputText--days]').clear().type('900'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'be.visible', + ); + cy.wait('@postEstimatedRewardsDaysValueChange'); + + cy.get('@postEstimatedRewardsDaysValueChange').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get( + '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', + ).should('not.exist'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + }, + ); + }); + + it('If DAYS or GLM input is empty rewards inputs are empty too', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=RewardsCalculator__InputText--crypto]').clear(); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + + cy.get('[data-test=RewardsCalculator__InputText--days]').clear(); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + + cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('5000'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + }); + + it('Max GLM amount is 1000000000', () => { + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); + + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + + cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('1000000000'); + cy.wait('@postEstimatedRewards'); + + cy.get('@postEstimatedRewards').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + + cy.get('[data-test=RewardsCalculator__InputText--crypto]') + .clear() + .type('1000000001') + .should('have.css', 'border-color', 'rgb(255, 97, 87)'); + cy.get('[data-test=RewardsCalculator__InputText--crypto__error]') + .should('be.visible') + .invoke('text') + .should('eq', 'That isn’t a valid amount'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + }, + ); + }); + + it('Closing the modal successfully cancels the request /estimated_budget', () => { + cy.window().then(win => { + cy.spy(win.console, 'error').as('consoleErrSpy'); + }); + + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + + cy.get('[data-test=ModalRewardsCalculator__Button]').click(); + cy.get('[data-test=ModalRewardsCalculator').should('not.be.visible'); + + cy.on('uncaught:exception', error => { + expect(error.code).to.equal('ERR_CANCELED'); + }); + }); + }); +}); diff --git a/client/cypress/e2e/routes.cy.ts b/client/cypress/e2e/routes.cy.ts new file mode 100644 index 0000000000..1d2dc113b8 --- /dev/null +++ b/client/cypress/e2e/routes.cy.ts @@ -0,0 +1,42 @@ +import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`routes (wallet not connected): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + mockCoinPricesServer(); + cy.clearLocalStorage(); + }); + + it('empty route redirects to projects view', () => { + visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('allocation route redirects to allocation view', () => { + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationView]').should('be.visible'); + }); + + it('earn route redirects to earn view', () => { + visitWithLoader(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=EarnView]').should('be.visible'); + }); + + it('metrics route redirects to metrics view', () => { + visitWithLoader(ROOT_ROUTES.metrics.absolute); + cy.get('[data-test=MetricsView]').should('be.visible'); + }); + + it('projects route redirects to projects view', () => { + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('settings route redirects to settings view', () => { + visitWithLoader(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsView]').should('be.visible'); + }); + }); +}); diff --git a/client/cypress/e2e/settings.cy.ts b/client/cypress/e2e/settings.cy.ts new file mode 100644 index 0000000000..087b77329e --- /dev/null +++ b/client/cypress/e2e/settings.cy.ts @@ -0,0 +1,188 @@ +import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { FIAT_CURRENCIES_SYMBOLS, DISPLAY_CURRENCIES } from 'src/constants/currencies'; +import { + ARE_OCTANT_TIPS_ALWAYS_VISIBLE, + DISPLAY_CURRENCY, + IS_CRYPTO_MAIN_VALUE_DISPLAY, + IS_ONBOARDING_ALWAYS_VISIBLE, + IS_ONBOARDING_DONE, +} from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; +import getValueCryptoToDisplay from 'src/utils/getValueCryptoToDisplay'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`settings: ${device}`, { viewportHeight, viewportWidth }, () => { + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.settings.absolute); + }); + + it('"Always show Allocate onboarding" option toggle works', () => { + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check(); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); + }); + + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('false'); + }); + + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); + }); + }); + + it('"Use crypto as main value display" option is checked by default', () => { + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); + }); + }); + + it('"Use crypto as main value display" option toggle works', () => { + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').check(); + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); + }); + + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('not.be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('false'); + }); + + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); + }); + }); + + it('"Use crypto as main value display" option by default displays crypto value as primary in DoubleValue component', () => { + navigateWithCheck(ROOT_ROUTES.earn.absolute); + + const cryptoValue = getValueCryptoToDisplay({ + cryptoCurrency: 'golem', + valueCrypto: BigInt(0), + }); + + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') + .invoke('text') + .should('eq', cryptoValue); + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') + .invoke('text') + .should('not.eq', cryptoValue); + }); + + it('"Use crypto as main value display" option changes DoubleValue sections order', () => { + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').uncheck(); + navigateWithCheck(ROOT_ROUTES.earn.absolute); + + const cryptoValue = getValueCryptoToDisplay({ + cryptoCurrency: 'golem', + valueCrypto: BigInt(0), + }); + + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') + .invoke('text') + .should('not.eq', cryptoValue); + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') + .invoke('text') + .should('eq', cryptoValue); + }); + + it('"Choose a display currency" option works', () => { + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(DISPLAY_CURRENCY)).eq('"usd"'); + }); + + for (let i = 0; i < DISPLAY_CURRENCIES.length - 1; i++) { + const displayCurrency = DISPLAY_CURRENCIES[i]; + const displayCurrencyToUppercase = displayCurrency.toUpperCase(); + const nextDisplayCurrencyToUppercase = + i < DISPLAY_CURRENCIES.length - 1 ? DISPLAY_CURRENCIES[i + 1].toUpperCase() : undefined; + + cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency__SingleValue]').contains( + displayCurrencyToUppercase, + ); + navigateWithCheck(ROOT_ROUTES.earn.absolute); + + if (FIAT_CURRENCIES_SYMBOLS[displayCurrency]) { + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( + FIAT_CURRENCIES_SYMBOLS[displayCurrency], + ); + } else { + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( + displayCurrencyToUppercase, + ); + } + + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency]').click(); + cy.get( + `[data-test=SettingsCurrencyBox__InputSelect--currency__Option--${nextDisplayCurrencyToUppercase}]`, + ).click(); + } + }); + + it('"Always show Octant tips" option toggle works', () => { + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); + }); + + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('not.be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('false'); + }); + + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); + }); + }); + + it('"Always show Octant tips" works (checked)', () => { + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); + + navigateWithCheck(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); + + cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); + + cy.reload(); + + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); + }); + + it('"Always show Octant tips" works (unchecked)', () => { + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').uncheck(); + + navigateWithCheck(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); + + cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); + + cy.reload(); + + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); + }); + }); +}); From 3147d3a38c31e97fca42c2da6c58ba2d77a9b459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 17:04:35 +0100 Subject: [PATCH 09/22] style: formatting --- client/cypress/e2e/earn.cy.ts | 447 ++++++++++++++++------------------ 1 file changed, 216 insertions(+), 231 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index f41aaddff1..2729bbb6e6 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -14,269 +14,254 @@ const connectWallet = (): Chainable => { }; Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }, idx) => { - describe( - `earn: ${device}`, - { retries: { openMode: 0, runMode: 0 }, viewportHeight, viewportWidth }, - () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); + describe(`earn: ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.earn.absolute); - }); + beforeEach(() => { + cy.disconnectMetamaskWalletFromAllDapps(); + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.earn.absolute); + }); - it('renders "Locked balance" box', () => { - cy.get('[data-test=BoxGlmLock__BoxRounded]').should('be.visible'); - }); + it('renders "Locked balance" box', () => { + cy.get('[data-test=BoxGlmLock__BoxRounded]').should('be.visible'); + }); - it('renders "Personal allocation" box', () => { - cy.get('[data-test=BoxPersonalAllocation]').should('be.visible'); - }); + it('renders "Personal allocation" box', () => { + cy.get('[data-test=BoxPersonalAllocation]').should('be.visible'); + }); - it('renders "History"', () => { - cy.get('[data-test=History]').should('be.visible'); - }); - - it('"Lock GLM" button is visible', () => { - cy.get('[data-test=BoxGlmLock__Button]').should('be.visible'); - }); + it('renders "History"', () => { + cy.get('[data-test=History]').should('be.visible'); + }); - it('"Lock GLM" button is disabled', () => { - cy.get('[data-test=BoxGlmLock__Button]').should('be.disabled'); - }); + it('"Lock GLM" button is visible', () => { + cy.get('[data-test=BoxGlmLock__Button]').should('be.visible'); + }); - it('"Withdraw to wallet" button is visible', () => { - cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.visible'); - }); + it('"Lock GLM" button is disabled', () => { + cy.get('[data-test=BoxGlmLock__Button]').should('be.disabled'); + }); - it('"Withdraw to wallet" button is disabled', () => { - cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.disabled'); - }); + it('"Withdraw to wallet" button is visible', () => { + cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.visible'); + }); - it('"Effective" section has tooltip', () => { - cy.get('[data-test=BoxGlmLock__Section--effective]').should('be.visible'); - }); + it('"Withdraw to wallet" button is disabled', () => { + cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.disabled'); + }); - if (!isDesktop) { - it('"Effective" section tooltip svg icon opens "TooltipEffectiveLockedBalance"', () => { - cy.get('[data-test=BoxGlmLock__Section--effective__Svg]').click(); - cy.get('[data-test=TooltipEffectiveLockedBalance]').should('be.visible'); - }); - } + it('"Effective" section has tooltip', () => { + cy.get('[data-test=BoxGlmLock__Section--effective]').should('be.visible'); + }); - it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button is active', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').should('not.be.disabled'); + if (!isDesktop) { + it('"Effective" section tooltip svg icon opens "TooltipEffectiveLockedBalance"', () => { + cy.get('[data-test=BoxGlmLock__Section--effective__Svg]').click(); + cy.get('[data-test=TooltipEffectiveLockedBalance]').should('be.visible'); }); + } - it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button opens "ModalGlmLock"', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - }); + it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button is active', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').should('not.be.disabled'); + }); - it('Wallet connected: "ModalGlmLock" has overflow', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock__overflow]').should('exist'); - }); + it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button opens "ModalGlmLock"', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock]').should('be.visible'); + }); - it('Wallet connected: inputs allow to type multiple characters without focus problems', () => { - /** - * In EarnGlmLock there are multiple autofocus rules set. - * This test checks if user is still able to type without any autofocus disruption. - */ - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); - }); + it('Wallet connected: "ModalGlmLock" has overflow', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock__overflow]').should('exist'); + }); - it('Wallet connected: "ModalGlmLock" - changing tabs keep focus on first input', () => { - connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=EarnGlmLockTabs__tab--0]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - }); + it('Wallet connected: inputs allow to type multiple characters without focus problems', () => { + /** + * In EarnGlmLock there are multiple autofocus rules set. + * This test checks if user is still able to type without any autofocus disruption. + */ + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock]').should('be.visible'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); + cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); + }); - it('Wallet connected: Lock 1 GLM', () => { - connectWallet(); + it('Wallet connected: "ModalGlmLock" - changing tabs keep focus on first input', () => { + connectWallet(); + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=ModalGlmLock]').should('be.visible'); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + cy.get('[data-test=EarnGlmLockTabs__tab--0]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); + }); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToLock = 1; - const lockedGlms = parseInt(text, 10); + it('Wallet connected: Lock 1 GLM', () => { + connectWallet(); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') - .clear() - .type(`${amountToLock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should( - 'have.text', - 'Waiting for confirmation', - ); + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') + .invoke('text') + .then(text => { + const amountToLock = 1; + const lockedGlms = parseInt(text, 10); + + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type(`${amountToLock}`); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); + cy.confirmMetamaskPermissionToSpend({ + spendLimit: '99999999999999999999', + }); + // Workaround for two notifications during first transaction. + // 1. Allow the third party to spend TKN from your current balance. + // 2. Confirm permission to spend + if (Cypress.env('CI') === 'true' && idx === 0) { + cy.wait(1000); cy.confirmMetamaskPermissionToSpend({ spendLimit: '99999999999999999999', }); - // Workaround for two notifications during first transaction. - // 1. Allow the third party to spend TKN from your current balance. - // 2. Confirm permission to spend - if (Cypress.env('CI') === 'true' && idx === 0) { - cy.wait(1000); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - } - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - ).should('be.visible'); + } + cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( + 'have.text', + 'Close', + ); + cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get( + '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + ).should('be.visible'); + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { + timeout: 60000, + }) + .invoke('text') + .then(nextText => { + const lockedGlmsAfterLock = parseInt(nextText, 10); + expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + }); + cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Locked GLM'); + cy.get('[data-test=HistoryItem__DoubleValue__primary]') + .first() + .should('have.text', '1 GLM'); + }); + }); + + it('Wallet connected: Unlock 1 GLM', () => { + connectWallet(); + + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') + .invoke('text') + .then(text => { + const amountToUnlock = 1; + const lockedGlms = parseInt(text, 10); + + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') + .clear() + .type(`${amountToUnlock}`); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Unlock'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); + cy.confirmMetamaskPermissionToSpend({ + shouldWaitForPopupClosure: true, + spendLimit: '99999999999999999999', + }); + cy.get('[data-test=GlmLockTabs__Button]', { timeout: 60000 }).should( + 'have.text', + 'Close', + ); + cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get( + '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + ).should('be.visible'); + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { + timeout: 60000, + }) + .invoke('text') + .then(nextText => { + const lockedGlmsAfterUnlock = parseInt(nextText, 10); + expect(lockedGlms - amountToUnlock).to.be.eq(lockedGlmsAfterUnlock); + }); + cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Unlocked GLM'); + cy.get('[data-test=HistoryItem__DoubleValue__primary]') + .first() + .should('have.text', '1 GLM'); + }); + }); + + // TODO OCT-1506 enable this scenario. + // eslint-disable-next-line jest/no-disabled-tests + it.skip('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { + connectWallet(); + + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') + .invoke('text') + .then(text => { + const amountToLock = 1000; + const lockedGlms = parseInt(text.replace(/\u200a/g, ''), 10); + + cy.get('[data-test=BoxGlmLock__Button]').click(); + cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type(`${amountToLock}`); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); + cy.confirmMetamaskPermissionToSpend({ + spendLimit: '99999999999999999999', + }); + cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( + 'have.text', + 'Close', + ); + cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); + cy.get('[data-test=GlmLockTabs__Button]').click(); + cy.get( + '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + ).should('be.visible'); + cy.window().then(async win => { + // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { + timeout: 60000, + }).should('not.exist'); + moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, }) .invoke('text') .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText, 10); + const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); }); - cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Locked GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', '1 GLM'); - }); - }); - - it('Wallet connected: Unlock 1 GLM', () => { - connectWallet(); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToUnlock = 1; - const lockedGlms = parseInt(text, 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') - .clear() - .type(`${amountToUnlock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Unlock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should( - 'have.text', - 'Waiting for confirmation', - ); - cy.confirmMetamaskPermissionToSpend({ - shouldWaitForPopupClosure: true, - spendLimit: '99999999999999999999', - }); - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 60000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - ).should('be.visible'); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { timeout: 60000, }) .invoke('text') .then(nextText => { - const lockedGlmsAfterUnlock = parseInt(nextText, 10); - expect(lockedGlms - amountToUnlock).to.be.eq(lockedGlmsAfterUnlock); + const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); + expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); }); - cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Unlocked GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', '1 GLM'); }); - }); - - it('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { - connectWallet(); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToLock = 1000; - const lockedGlms = parseInt(text.replace(/\u200a/g, ''), 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') - .clear() - .type(`${amountToLock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should( - 'have.text', - 'Waiting for confirmation', - ); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - ).should('be.visible'); - cy.window().then(async win => { - // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { - timeout: 60000, - }).should('not.exist'); - moveEpoch(win); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - }); - }); - }); - }, - ); + }); + }); + }); }); From 78bae6ac05cc241c08642283a370c9d0a5f639bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 18:06:46 +0100 Subject: [PATCH 10/22] test: skeleton should appear immediately --- client/cypress/e2e/earn.cy.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 2729bbb6e6..3937bc574d 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -149,6 +149,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.get( '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + { timeout: 1000 }, ).should('be.visible'); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, @@ -194,6 +195,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.get( '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + { timeout: 1000 }, ).should('be.visible'); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, @@ -210,9 +212,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes }); }); - // TODO OCT-1506 enable this scenario. - // eslint-disable-next-line jest/no-disabled-tests - it.skip('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { + it('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { connectWallet(); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') @@ -237,6 +237,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.get( '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + { timeout: 1000 }, ).should('be.visible'); cy.window().then(async win => { // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. From 9d875a18913289cbdb5e717f0028af8532dda88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 28 Mar 2024 18:07:38 +0100 Subject: [PATCH 11/22] test: skeleton should appear immediately --- client/cypress/e2e/earn.cy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 3937bc574d..c64e6a58d7 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -149,6 +149,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.get( '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + // Small timeout ensures skeleton shows up quickly after the transaction. { timeout: 1000 }, ).should('be.visible'); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { @@ -195,6 +196,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.get( '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + // Small timeout ensures skeleton shows up quickly after the transaction. { timeout: 1000 }, ).should('be.visible'); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { @@ -237,6 +239,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=GlmLockTabs__Button]').click(); cy.get( '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', + // Small timeout ensures skeleton shows up quickly after the transaction. { timeout: 1000 }, ).should('be.visible'); cy.window().then(async win => { From ed415d31ce08da84b4151b48d34e66334e0ca5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 29 Mar 2024 00:24:41 +0100 Subject: [PATCH 12/22] style: simplify moveEpoc --- client/cypress/e2e/earn.cy.ts | 2 +- client/cypress/utils/e2e.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index c64e6a58d7..159556e5cd 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -247,7 +247,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { timeout: 60000, }).should('not.exist'); - moveEpoch(win); + await moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, }) diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index a48eaa203e..a673ac807a 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -50,15 +50,14 @@ export const connectWallet = ( return cy.acceptMetamaskAccess(); }; -export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Chainable => { - // https://docs.cypress.io/api/utilities/promise#Waiting-for-Promises - cy.wrap(null).then(() => cypressWindow.mutateAsyncMoveEpoch()); +export const moveEpoch = async (cypressWindow: Cypress.AUTWindow): Promise => { + await cypressWindow.mutateAsyncMoveEpoch(); // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); // Manually taking a pending snapshot after the epoch shift ensures that the snapshot is taken. Passing epoch multiple times without manually triggering pending snapshot in a short period of time may cause the e2e environment to fail. - cy.wrap(null).then(() => axios.post(`${env.serverEndpoint}snapshots/pending`)); + await axios.post(`${env.serverEndpoint}snapshots/pending`); // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); // reload is needed to get updated data in the app - return cy.reload(); + cy.reload(); }; From b5f221fc6e3397e26526e30e5157881b716855fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 29 Mar 2024 15:16:08 +0100 Subject: [PATCH 13/22] feat: move epoch by an util --- client/cypress/e2e/projectsArchive.cy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts index d2abe9b5d0..43e05b930d 100644 --- a/client/cypress/e2e/projectsArchive.cy.ts +++ b/client/cypress/e2e/projectsArchive.cy.ts @@ -1,4 +1,4 @@ -import { checkLocationWithLoader, visitWithLoader } from 'cypress/utils/e2e'; +import { checkLocationWithLoader, moveEpoch, visitWithLoader } from 'cypress/utils/e2e'; import viewports from 'cypress/utils/viewports'; import { QUERY_KEYS } from 'src/api/queryKeys'; import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; @@ -21,7 +21,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => const currentEpochBefore = Number( win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), ); - await win.mutateAsyncMoveEpoch(); + await moveEpoch(win); const currentEpochAfter = Number( win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), ); From 73aaeeb7e1d6a21bb7a854a1a1100584c89a8257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 29 Mar 2024 22:57:59 +0100 Subject: [PATCH 14/22] test: enforce wait fro skeleton to disappear --- client/cypress/e2e/earn.cy.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index 159556e5cd..a3da533a5c 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -242,11 +242,14 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes // Small timeout ensures skeleton shows up quickly after the transaction. { timeout: 1000 }, ).should('be.visible'); + // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]').as( + 'BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton', + ); + cy.wait('@BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton', { + timeout: 60000, + }).should('not.exist'); cy.window().then(async win => { - // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { - timeout: 60000, - }).should('not.exist'); await moveEpoch(win); cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { timeout: 60000, From f03f250213f35be38aeaa397a3c14cd8b701ab84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 29 Mar 2024 23:39:31 +0100 Subject: [PATCH 15/22] test: enforce wait fro skeleton to disappear --- client/cypress/e2e/earn.cy.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index a3da533a5c..ec7b26cddf 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -243,10 +243,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes { timeout: 1000 }, ).should('be.visible'); // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]').as( - 'BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton', - ); - cy.wait('@BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton', { + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { timeout: 60000, }).should('not.exist'); cy.window().then(async win => { From 626686e5fa439760911524e6a97143d4381c029d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 29 Mar 2024 23:54:15 +0100 Subject: [PATCH 16/22] test: adjustments --- client/cypress/e2e/layout.cy.ts | 126 ---- client/cypress/e2e/metrics.cy.ts | 126 ---- client/cypress/e2e/onboarding.cy.ts | 380 ----------- client/cypress/e2e/patronMode.cy.ts | 724 --------------------- client/cypress/e2e/project.cy.ts | 181 ------ client/cypress/e2e/projects.cy.ts | 239 ------- client/cypress/e2e/projectsArchive.cy.ts | 103 --- client/cypress/e2e/rewardsCalculator.cy.ts | 252 ------- client/cypress/e2e/routes.cy.ts | 42 -- client/cypress/e2e/settings.cy.ts | 188 ------ client/cypress/utils/e2e.ts | 25 +- 11 files changed, 15 insertions(+), 2371 deletions(-) delete mode 100644 client/cypress/e2e/layout.cy.ts delete mode 100644 client/cypress/e2e/metrics.cy.ts delete mode 100644 client/cypress/e2e/onboarding.cy.ts delete mode 100644 client/cypress/e2e/patronMode.cy.ts delete mode 100644 client/cypress/e2e/project.cy.ts delete mode 100644 client/cypress/e2e/projects.cy.ts delete mode 100644 client/cypress/e2e/projectsArchive.cy.ts delete mode 100644 client/cypress/e2e/rewardsCalculator.cy.ts delete mode 100644 client/cypress/e2e/routes.cy.ts delete mode 100644 client/cypress/e2e/settings.cy.ts diff --git a/client/cypress/e2e/layout.cy.ts b/client/cypress/e2e/layout.cy.ts deleted file mode 100644 index 9ea9954d24..0000000000 --- a/client/cypress/e2e/layout.cy.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`layout: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - cy.clearLocalStorage(); - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - cy.disconnectMetamaskWalletFromAllDapps(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - cy.visit(ROOT.absolute); - }); - - it('renders top bar', () => { - cy.get('[data-test=MainLayout__Header]').should('be.visible'); - }); - - it('Clicking on Octant logo scrolls view to the top on logo click (projects view)', () => { - cy.scrollTo(0, 500); - cy.get('[data-test=MainLayout__Logo]').click(); - // waiting for scrolling to finish - cy.wait(2000); - cy.window().then(cyWindow => { - expect(cyWindow.scrollY).to.be.eq(0); - }); - }); - - it('Clicking on Octant logo redirects to projects view (outside projects view)', () => { - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - cy.get('[data-test=MainLayout__Logo]').click(); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('Clicking on Octant logo redirects to projects view (outside projects view) with memorized scrollY', () => { - cy.scrollTo(0, 500); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - cy.get('[data-test=MainLayout__Logo]').click(); - cy.get('[data-test=ProjectsView]').should('be.visible'); - cy.window().then(cyWindow => { - expect(cyWindow.scrollY).to.be.eq(500); - }); - }); - - it('renders bottom navbar', () => { - cy.get('[data-test=Navbar]').should('be.visible'); - }); - - it('bottom navbar allows to change views', () => { - navigationTabs.forEach(({ to }) => { - navigateWithCheck(to); - }); - }); - - it('"Connect" button is visible when wallet is disconnected', () => { - cy.get('[data-test=MainLayout__Button--connect]').should('be.visible'); - cy.get('[data-test=MainLayout__Button--connect]').click(); - }); - - it('"Connect" button opens "ModalConnectWallet"', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').should('be.visible'); - }); - - it('"ModalConnectWallet" always shows "WalletConnect" option', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').within(() => { - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); - }); - }); - - it('"ModalConnectWallet" shows "Browser wallet" and "WalletConnect" options (MetaMask wallet detected)', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').within(() => { - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').should('be.visible'); - }); - }); - - it('"ModalConnectWallet" has overflow enabled', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__overflow]').should('exist'); - }); - - it('Clicking background when "ModalConnectWallet" is open, closes Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__overflow]').click({ force: true }); - cy.get('[data-test=ModalConnectWallet]').should('not.exist'); - }); - - it('"ModalConnectWallet" has "cross" icon button in header', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__Button]').should('be.visible'); - }); - - it('Clicking on "X" mark in "ModalConnectWallet", closes Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__Button]').click(); - cy.get('[data-test=ModalConnectWallet]').should('not.exist'); - }); - - it('Clicking on "WalletConnect" option, opens Web3Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').click(); - cy.get('w3m-modal').find('#w3m-modal', { includeShadowDom: true }).should('be.visible'); - }); - - it('Clicking on "Browser wallet" option connects with MetaMask wallet', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); - cy.switchToMetamaskNotification(); - cy.acceptMetamaskAccess(); - cy.get('[data-test=MainLayout__Button--connect]').should('not.exist'); - cy.get('[data-test=ProfileInfo]').should('exist'); - }); - }); -}); diff --git a/client/cypress/e2e/metrics.cy.ts b/client/cypress/e2e/metrics.cy.ts deleted file mode 100644 index 829fbfbf1c..0000000000 --- a/client/cypress/e2e/metrics.cy.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`metrics: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.metrics.absolute); - }); - - it('renders total projects tile', () => { - cy.get('[data-test=MetricsGeneralGridTotalProjects]').should('be.visible'); - }); - - it('renders total eth staked tile', () => { - cy.get('[data-test=MetricsGeneralGridTotalEthStaked]').should('be.visible'); - }); - - it('renders tile with total glm locked and % of 1B total supply groups', () => { - cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]').should('be.visible'); - cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]') - .children() - .should('have.length', 2); - }); - - it('renders wallet with glm locked graph tile', () => { - cy.get('[data-test=MetricsGeneralGridWalletsWithGlmLocked]').should('be.visible'); - }); - - it('renders cumulative glm locked graph tile', () => { - cy.get('[data-test=MetricsGeneralGridCumulativeGlmLocked]').should('be.visible'); - }); - - it('renders tiles in correct order', () => { - const metricsEpochGridTilesDataTest = [ - 'MetricsEpochGridTopProjects', - 'MetricsEpochGridTotalDonationsAndPersonal', - 'MetricsEpochGridDonationsVsPersonalAllocations', - 'MetricsEpochGridFundsUsage', - 'MetricsEpochGridTotalUsers', - 'MetricsEpochGridPatrons', - 'MetricsEpochGridCurrentDonors', - 'MetricsEpochGridAverageLeverage', - 'MetricsEpochGridRewardsUnusedAndUnallocatedValue', - 'MetricsEpochGridBelowThreshold', - ]; - - const metricsGeneralGridTilesDataTest = [ - 'MetricsGeneralGridTotalGlmLockedAndTotalSupply', - 'MetricsGeneralGridTotalProjects', - 'MetricsGeneralGridTotalEthStaked', - 'MetricsGeneralGridCumulativeGlmLocked', - 'MetricsGeneralGridWalletsWithGlmLocked', - ]; - - cy.get('[data-test=MetricsEpoch__MetricsGrid]') - .children() - .should('have.length', metricsEpochGridTilesDataTest.length); - - for (let i = 0; i < metricsEpochGridTilesDataTest.length; i++) { - cy.get('[data-test=MetricsEpoch__MetricsGrid]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', metricsEpochGridTilesDataTest[i]); - } - - cy.get('[data-test=MetricsGeneral__MetricsGrid]') - .children() - .should('have.length', metricsGeneralGridTilesDataTest.length); - - for (let i = 0; i < metricsGeneralGridTilesDataTest.length; i++) { - cy.get('[data-test=MetricsGeneral__MetricsGrid]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', metricsGeneralGridTilesDataTest[i]); - } - }); - - it('renders grid with 4 columns on desktop or with 2 columns on other devices', () => { - cy.get('[data-test=MetricsEpoch__MetricsGrid]').then(el => { - const width = parseInt(el.css('width'), 10); - const rowGap = parseInt(el.css('rowGap'), 10); - - const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; - - cy.get('[data-test=MetricsEpoch__MetricsGrid]').should( - 'have.css', - 'grid-template-columns', - isDesktop - ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` - : `${columnWidth}px ${columnWidth}px`, - ); - }); - - cy.get('[data-test=MetricsGeneral__MetricsGrid]').then(el => { - const width = parseInt(el.css('width'), 10); - const rowGap = parseInt(el.css('rowGap'), 10); - - const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; - - cy.get('[data-test=MetricsGeneral__MetricsGrid]').should( - 'have.css', - 'grid-template-columns', - isDesktop - ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` - : `${columnWidth}px ${columnWidth}px`, - ); - }); - }); - }); -}); diff --git a/client/cypress/e2e/onboarding.cy.ts b/client/cypress/e2e/onboarding.cy.ts deleted file mode 100644 index e528f9e87f..0000000000 --- a/client/cypress/e2e/onboarding.cy.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { stepsDecisionWindowClosed } from 'src/hooks/helpers/useOnboardingSteps/steps'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -const connectWallet = ( - isTOSAccepted: boolean, - shouldVisit = true, - shouldReload = false, -): Chainable => { - cy.intercept('GET', '/user/*/tos', { body: { accepted: isTOSAccepted } }); - cy.disconnectMetamaskWalletFromAllDapps(); - if (shouldVisit) { - visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); - } - if (shouldReload) { - cy.reload(); - } - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); - cy.switchToMetamaskNotification(); - return cy.acceptMetamaskAccess(); -}; - -const beforeSetup = () => { - mockCoinPricesServer(); - cy.clearLocalStorage(); - cy.setupMetamask(); - window.innerWidth = Cypress.config().viewportWidth; - window.innerHeight = Cypress.config().viewportHeight; -}; - -const checkCurrentElement = (el: number, isCurrent: boolean): Chainable => { - return cy - .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(el) - .invoke('attr', 'data-iscurrent') - .should('eq', `${isCurrent}`); -}; - -const checkProgressStepperSlimIsCurrentAndClickNext = (index, isCurrent = true): Chainable => { - checkCurrentElement(index - 1, isCurrent); - return cy - .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(index) - .click({ force: true }); -}; - -const checkChangeStepsWithArrowKeys = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - [ - { el: 1, key: 'ArrowRight' }, - { el: 2, key: 'ArrowRight' }, - // { el: 3, key: 'ArrowRight' }, - // { el: 3, key: 'ArrowRight' }, - // { el: 2, key: 'ArrowLeft' }, - { el: 1, key: 'ArrowLeft' }, - { el: 0, key: 'ArrowLeft' }, - { el: 0, key: 'ArrowLeft' }, - ].forEach(({ key, el }) => { - cy.get('body').trigger('keydown', { key }); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); -}; - -const checkChangeStepsByClickingEdgeOfTheScreenUpTo25px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - cy.get('[data-test=ModalOnboarding]').then(element => { - const leftEdgeX = element.offsetParent().offset()?.left as number; - const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; - - [ - { clientX: rightEdgeX - 25, el: 1 }, - { clientX: rightEdgeX - 10, el: 2 }, - // { clientX: rightEdgeX - 5, el: 3 }, - // rightEdgeX === browser right frame - // { clientX: rightEdgeX - 1, el: 3 }, - // { clientX: leftEdgeX + 25, el: 2 }, - { clientX: leftEdgeX + 10, el: 1 }, - { clientX: leftEdgeX + 5, el: 0 }, - { clientX: leftEdgeX, el: 0 }, - ].forEach(({ clientX, el }) => { - cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); - }); -}; - -const checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - cy.get('[data-test=ModalOnboarding]').then(element => { - const leftEdgeX = element.offsetParent().offset()?.left as number; - const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; - - [ - { clientX: rightEdgeX - 25, el: 1 }, - { clientX: rightEdgeX - 26, el: 1 }, - { clientX: leftEdgeX + 26, el: 1 }, - { clientX: leftEdgeX + 25, el: 0 }, - ].forEach(({ clientX, el }) => { - cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); - }); -}; - -const checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - [ - { - el: 1, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 2, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 2, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - // { - // el: 3, - // touchMoveClientX: window.innerWidth / 2 - 5, - // touchStartClientX: window.innerWidth / 2, - // }, - { - el: 2, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 1, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 0, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 0, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { - cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { - touches: [{ clientX: touchStartClientX }], - }); - cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { - touches: [{ clientX: touchMoveClientX }], - }); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); -}; - -const checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px = (isTOSAccepted: boolean) => { - checkCurrentElement(0, true); - - [ - { - el: 1, - touchMoveClientX: window.innerWidth / 2 - 5, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 1, - touchMoveClientX: window.innerWidth / 2 - 4, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 1, - touchMoveClientX: window.innerWidth / 2 + 4, - touchStartClientX: window.innerWidth / 2, - }, - { - el: 0, - touchMoveClientX: window.innerWidth / 2 + 5, - touchStartClientX: window.innerWidth / 2, - }, - ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { - cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { - touches: [{ clientX: touchStartClientX }], - }); - cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { - touches: [{ clientX: touchMoveClientX }], - }); - checkCurrentElement(el, isTOSAccepted || el === 0); - - if (!isTOSAccepted) { - checkCurrentElement(0, true); - } - }); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`onboarding (TOS accepted): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - beforeSetup(); - }); - - beforeEach(() => { - connectWallet(true); - }); - - it('user is able to click through entire onboarding flow', () => { - for (let i = 1; i < stepsDecisionWindowClosed.length - 1; i++) { - checkProgressStepperSlimIsCurrentAndClickNext(i); - } - - cy.get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(stepsDecisionWindowClosed.length - 1) - .click(); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); - }); - - it('user is able to close the modal by clicking button in the top-right', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); - }); - - it('renders every time page is refreshed when "Always show Allocate onboarding" option is checked', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check().should('be.checked'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('renders only once when "Always show Allocate onboarding" option is not checked', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - - it('user can change steps with arrow keys (left, right)', () => { - checkChangeStepsWithArrowKeys(true); - }); - - it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(true); - }); - - it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(true); - }); - - it('user can change steps by swiping on screen (difference more than or equal 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(true); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); - }); - - it('user is able to close the onboarding, and after disconnecting & connecting, onboarding does not show up again', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - connectWallet(true, false, true); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - }); -}); - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`onboarding (TOS not accepted): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - beforeSetup(); - }); - - beforeEach(() => { - cy.intercept( - { - method: 'POST', - url: '/user/*/tos', - }, - { body: { accepted: true }, statusCode: 200 }, - ); - connectWallet(false); - }); - - it('onboarding TOS step should be first and active', () => { - checkCurrentElement(0, true); - cy.get('[data-test=ModalOnboardingTOS]').should('be.visible'); - }); - - it('user is not able to click through entire onboarding flow', () => { - for (let i = 1; i < stepsDecisionWindowClosed.length; i++) { - checkProgressStepperSlimIsCurrentAndClickNext(i, i === 1); - } - }); - - it('user is not able to close the modal by clicking button in the top-right', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click({ force: true }); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('renders every time page is refreshed', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('user cannot change steps with arrow keys (left, right)', () => { - checkChangeStepsWithArrowKeys(false); - }); - - it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(false); - }); - - it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(false); - }); - - it('user cannot change steps by swiping on screen (difference more than or equal 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(false); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(false); - }); - - it('TOS acceptance changes onboarding step to next step', () => { - checkCurrentElement(0, true); - cy.get('[data-test=TOS_InputCheckbox]').check(); - cy.switchToMetamaskNotification(); - cy.confirmMetamaskSignatureRequest(); - checkCurrentElement(1, true); - }); - - it('TOS acceptance allows the user to close the modal by clicking button in the top-right', () => { - checkCurrentElement(0, true); - cy.get('[data-test=TOS_InputCheckbox]').check(); - cy.switchToMetamaskNotification(); - cy.confirmMetamaskSignatureRequest(); - checkCurrentElement(1, true); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - }); -}); diff --git a/client/cypress/e2e/patronMode.cy.ts b/client/cypress/e2e/patronMode.cy.ts deleted file mode 100644 index b5f3455c47..0000000000 --- a/client/cypress/e2e/patronMode.cy.ts +++ /dev/null @@ -1,724 +0,0 @@ -import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`patron mode (disabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - connectWallet(true, false); - }); - - it('patron badge should not exist ', () => { - cy.get('[data-test=ProfileInfo__badge]').should('not.exist'); - }); - - it('Patron mode toggle is not checked', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('not.be.checked'); - }); - - if (isDesktop) { - it('Patron mode tooltip is visible on hover and has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') - .invoke('text') - .should( - 'eq', - 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', - ); - }); - } - - it('Checking patron mode opens patron mode modal', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=ModalPatronMode]').should('be.visible'); - }); - - it('Patron mode modal last paragraph has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=SettingsPatronMode__fourthParagraph]') - .invoke('text') - .should('eq', 'Slide the switch below all the way to the right to enable patron mode.'); - }); - - it('Slider is visible', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider]').should('be.visible'); - }); - - it('Slider has correct label', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider__label]') - .invoke('text') - .should('eq', 'Slide right to confirm'); - }); - - it('Slider button is visible ', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); - }); - - it('Slider button has right arrow inside', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider__button__arrow]') - .should('be.visible') - .should('have.css', 'transform', 'none'); - }); - - it('Slider button is on the left side', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderLeftDistance = sliderEl[0].getBoundingClientRect().left; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonLeftDistance = sliderButtonEl[0].getBoundingClientRect().left; - - expect(sliderButtonLeftDistance).to.be.eq(sliderLeftDistance + sliderLeftPadding); - }); - }); - }); - - it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.x; - const pointerMovePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerDownPageX); - }); - }); - }); - }); - - it('Slider elements change color while moving slider button', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const slider0PercentagePageX = sliderButtonDimensions.x; - const slider25PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 4; - const slider50PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; - const slider75PercentagePageX = sliderButtonDimensions.x + 3 * (sliderTrackWidth / 4); - const slider100PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth; - - // 0% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { - pageX: slider0PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgb(243, 243, 243)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(23, 23, 23)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '1'); - - // 25% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider25PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(212, 224, 221, 0.95)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(222, 234, 231)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(129, 129, 129)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.75'); - - // 50% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider50PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(175, 204, 197, 0.9)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(183, 211, 204)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(181, 181, 181)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.5'); - - // 75% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider75PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(128, 181, 169, 0.85)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(133, 185, 173)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(221, 221, 221)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.25'); - - // 100% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider100PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(45, 155, 135, 0.8)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(45, 155, 135)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0'); - }); - }); - }); - - it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.x; - const pointerMovePageX = sliderButtonDimensions.x + (sliderTrackWidth / 2 + 1); - const pointerUpPageX = sliderDimensions.right - sliderRightPadding - sliderButtonWidth; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); - }); - - cy.confirmMetamaskSignatureRequest(); - cy.switchToCypressWindow(); - cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__status-label]') - .invoke('text') - .should('eq', 'Patron mode enabled'); - cy.wait(500); - cy.get('[data-test=ModalPatronMode]').should('not.exist'); - }); - }); - }); - }); - - describe(`patron mode (enabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - connectWallet(true, true); - }); - - it('patron badge is visible and has correct label, background and text-transform prop', () => { - cy.get('[data-test=ProfileInfo__badge]').should('be.visible'); - cy.get('[data-test=ProfileInfo__badge]').invoke('text').should('eq', 'Patron'); - cy.get('[data-test=ProfileInfo__badge]') - .should('have.css', 'background-color', 'rgb(104, 91, 138)') - .should('have.css', 'text-transform', 'uppercase'); - }); - - it('Navbar has 4 items - projects, earn, metrics, settings', () => { - const navbarChildrenDataTest = [ - 'Navbar__Button--Projects', - 'Navbar__Button--Earn', - 'Navbar__Button--Metrics', - 'Navbar__Button--Settings', - ]; - - cy.get('[data-test=Navbar__buttons]') - .children() - .should('have.length', navbarChildrenDataTest.length); - - for (let i = 0; i < navbarChildrenDataTest.length; i++) { - cy.get('[data-test=Navbar__buttons]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', navbarChildrenDataTest[i]); - } - }); - - it('route /allocate redirects to /projects', () => { - visitWithLoader(ROOT_ROUTES.allocation.absolute, ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('BoxPersonalAllocation has correct title and sections labels', () => { - visitWithLoader(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=BoxPersonalAllocation__title]') - .invoke('text') - .should('eq', 'Patron earnings'); - cy.get('[data-test=BoxPersonalAllocation__Section__label]') - .eq(0) - .invoke('text') - .should('eq', 'Current epoch'); - cy.get('[data-test=BoxPersonalAllocation__Section__label]') - .eq(1) - .invoke('text') - .should('eq', 'All time'); - }); - - it('Patron mode toggle is checked', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('be.checked'); - }); - - if (isDesktop) { - it('Patron mode tooltip is visible on hover and has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') - .invoke('text') - .should( - 'eq', - 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', - ); - }); - } - - it('Unchecking patron mode opens patron mode modal', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=ModalPatronMode]').should('be.visible'); - }); - - it('Patron mode modal last paragraph has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=SettingsPatronMode__fourthParagraph]') - .invoke('text') - .should('eq', 'Slide the switch below all the way to the left to disable patron mode.'); - }); - - it('Slider is visible', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider]').should('be.visible'); - }); - - it('Slider has correct label', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider__label]') - .invoke('text') - .should('eq', 'Slide left to confirm'); - }); - - it('Slider button is visible', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); - }); - - it('Slider button has left arrow inside', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider__button__arrow]') - .should('be.visible') - .should('have.css', 'transform', 'matrix(-1, 0, 0, -1, 0, 0)'); - }); - - it('Slider button is on the right side', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderRightDistance = sliderEl[0].getBoundingClientRect().right; - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonRightDistance = sliderButtonEl[0].getBoundingClientRect().right; - - expect(sliderButtonRightDistance).to.be.eq(sliderRightDistance - sliderRightPadding); - }); - }); - }); - - it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.right; - const pointerMovePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.right).eq(pointerDownPageX); - }); - }); - }); - }); - - it('Slider elements change color while moving slider button', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const slider0PercentagePageX = sliderButtonDimensions.right; - const slider25PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 4; - const slider50PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; - const slider75PercentagePageX = sliderButtonDimensions.right - 3 * (sliderTrackWidth / 4); - const slider100PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth; - - // 0% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { - pageX: slider0PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgb(243, 243, 243)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(23, 23, 23)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '1'); - - // 25% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider25PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(212, 224, 221, 0.95)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(222, 234, 231)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(129, 129, 129)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.75'); - - // 50% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider50PercentagePageX, - waitForAnimations: true, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(175, 204, 197, 0.9)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(183, 211, 204)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(181, 181, 181)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.5'); - - // 75% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider75PercentagePageX, - waitForAnimations: true, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(128, 181, 169, 0.85)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(133, 185, 173)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(221, 221, 221)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.25'); - - // 100% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider100PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(45, 155, 135, 0.8)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(45, 155, 135)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0'); - }); - }); - }); - - it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.right; - const pointerMovePageX = sliderButtonDimensions.right - (sliderTrackWidth / 2 + 1); - const pointerUpPageX = sliderDimensions.left + sliderLeftPadding; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); - }); - - cy.confirmMetamaskSignatureRequest(); - cy.switchToCypressWindow(); - cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__status-label]') - .invoke('text') - .should('eq', 'Patron mode disabled'); - cy.wait(500); - cy.get('[data-test=ModalPatronMode]').should('not.exist'); - }); - }); - }); - - it('when entering project view, button icon changes to chevronLeft', () => { - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.get('[data-test=Navbar__Button--Projects]') - .find('svg') - // HTML tag can't be self-closing in CY. - .should( - 'have.html', - '', - ); - }); - }); -}); diff --git a/client/cypress/e2e/project.cy.ts b/client/cypress/e2e/project.cy.ts deleted file mode 100644 index e811fb70aa..0000000000 --- a/client/cypress/e2e/project.cy.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import { getNamesOfProjects } from 'cypress/utils/projects'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -const getButtonAddToAllocate = (): Chainable => { - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - - return projectListItemFirst.find('[data-test=ProjectListItemHeader__ButtonAddToAllocate]'); -}; - -const checkProjectItemElements = (): Chainable => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); - getButtonAddToAllocate().should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Button]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItem__Description]').should('be.visible'); - - cy.get('[data-test=ProjectListItem__Donors]') - .first() - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - cy.get('[data-test=ProjectListItem__Donors]').first().should('be.visible'); - cy.get('[data-test=ProjectListItem__Donors__DonorsHeader__count]') - .first() - .should('be.visible') - .should('have.text', '0'); - return cy.get('[data-test=ProjectListItem__Donors__noDonationsYet]').first().should('be.visible'); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`project: ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('entering project view directly renders content', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.reload(); - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); - }); - - it('entering project view renders all its elements', () => { - checkProjectItemElements(); - }); - - it('entering project view renders all its elements with fallback IPFS provider', () => { - cy.intercept('GET', '**/ipfs/**', req => { - if (req.url.includes('infura')) { - req.destroy(); - } - }); - - checkProjectItemElements(); - }); - - it('entering project view shows Toast with info about IPFS failure when all providers fail', () => { - cy.intercept('GET', '**/ipfs/**', req => { - req.destroy(); - }); - - cy.get('[data-test=Toast--ipfsMessage').should('be.visible'); - }); - - it('entering project view allows to add it to allocation and remove, triggering change of the icon, change of the number in navbar', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - - getButtonAddToAllocate().click(); - - // cy.get('@buttonAddToAllocate').click(); - cy.get('[data-test=Navbar__numberOfAllocations]').contains(1); - getButtonAddToAllocate().click(); - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - }); - - it('Entering project view allows scroll only to the last project', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test=ProjectListItem]').should( - 'have.length.greaterThan', - i === projectNames.length - 1 ? projectNames.length - 1 : i, - ); - cy.get('[data-test=ProjectListItemHeader__name]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: -150 } }) - .contains(projectNames[i]); - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: -150 } }) - .should('be.visible'); - } - }); - - it('"Back to top" button is displayed if the user has scrolled past the start of the final project description', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length - 1; i++) { - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - if (i === projectNames.length - 1) { - cy.get('[data-test=ProjectBackToTopButton__Button]').should('be.visible'); - } - } - }); - - it('Clicking on "Back to top" button scrolls to the top of view (first project is visible)', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length - 1; i++) { - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - if (i === projectNames.length - 1) { - cy.get('[data-test=ProjectBackToTopButton__Button]').click(); - cy.get('[data-test=ProjectListItem]').eq(0).should('be.visible'); - } - } - }); - }); - - describe(`project (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet(true, true); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('button "add to allocate" is disabled', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).click(); - getButtonAddToAllocate().should('be.visible').should('be.disabled'); - cy.go('back'); - } - }); - }); -}); diff --git a/client/cypress/e2e/projects.cy.ts b/client/cypress/e2e/projects.cy.ts deleted file mode 100644 index 9d096bcdf2..0000000000 --- a/client/cypress/e2e/projects.cy.ts +++ /dev/null @@ -1,239 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import { getNamesOfProjects } from 'cypress/utils/projects'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import getMilestones from 'src/constants/milestones'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -chai.use(chaiColors); - -function checkProjectItemElements(index, name, isPatronMode = false): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__imageProfile]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem]') - .eq(index) - .should('be.visible') - .find('[data-test=ProjectsListItem__name]') - .should('be.visible') - .contains(name); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__IntroDescription]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .should('be.visible'); - - if (isPatronMode) { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .should('be.disabled'); - } - - return cy - .get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectRewards]') - .should('be.visible'); - // TODO OCT-663 Make CY check if rewards are available (Epoch 2, decision window open). - // return cy - // .get('[data-test^=ProjectsView__ProjectsListItem') - // .eq(index) - // .find('[data-test=ProjectRewards__currentTotal__label]') - // .should('be.visible'); -} - -function addProjectToAllocate(index, numberOfAddedProjects): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__imageProfile]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__IntroDescription]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .scrollIntoView(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .find('svg') - .find('path') - .then($el => $el.css('fill')) - .should('be.colored', '#FF6157'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .find('svg') - .find('path') - .then($el => $el.css('stroke')) - .should('be.colored', '#FF6157'); - cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects + 1); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects + 1); - return cy.go('back'); -} - -function removeProjectFromAllocate(numberOfProjects, numberOfAddedProjects, index): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .scrollIntoView(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects - 1); - if (index < numberOfProjects - 1) { - cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects - 1); - } else { - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - } - return cy.go('back'); -} - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`projects: ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('user is able to see all the projects in the view', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); - checkProjectItemElements(i, projectNames[i]); - } - }); - - it('user is able to add & remove the first and the last project to/from allocation, triggering change of the icon, change of the number in navbar', () => { - // This test checks the first and the last elements only to save time. - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - - addProjectToAllocate(0, 0); - addProjectToAllocate(projectNames.length - 1, 1); - removeProjectFromAllocate(projectNames.length, 2, 0); - removeProjectFromAllocate(projectNames.length, 1, projectNames.length - 1); - }); - - it('user is able to add project to allocation in ProjectsView and remove it from allocation in AllocationView', () => { - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - addProjectToAllocate(0, 0); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItemSkeleton]').should('not.exist'); - cy.get('[data-test=AllocationItem]').then(el => { - const { x } = el[0].getBoundingClientRect(); - cy.get('[data-test=AllocationItem]') - .trigger('pointerdown') - .trigger('pointermove', { pageX: x - 20 }) - .trigger('pointerup'); - cy.get('[data-test=AllocationItem__removeButton]').should('be.visible'); - cy.get('[data-test=AllocationItem__removeButton]').click(); - cy.get('[data-test=AllocationItem__removeButton]').should('not.exist'); - cy.get('[data-test=AllocationItem]').should('not.exist'); - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - }); - - it('ProjectsTimelineWidgetItem with href opens link when clicked without mouse movement', () => { - const milestones = getMilestones(); - cy.get('[data-test=ProjectsTimelineWidget]').should('be.visible'); - cy.get('[data-test=ProjectsTimelineWidgetItem]').should('have.length', milestones.length); - for (let i = 0; i < milestones.length; i++) { - if (milestones[i].href) { - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .within(() => { - cy.get('[data-test=ProjectsTimelineWidgetItem__Svg--arrowTopRight]').should( - 'be.visible', - ); - }); - - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .then(el => { - const { x } = el[0].getBoundingClientRect(); - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .trigger('mousedown') - .trigger('mouseup', { clientX: x + 10 }); - cy.location('pathname').should('eq', ROOT_ROUTES.projects.absolute); - - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .trigger('mousedown') - .trigger('mouseup'); - cy.location('pathname').should('not.eq', ROOT_ROUTES.projects.absolute); - }); - } - } - }); - }); - }); - - describe(`projects (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet(true, true); - cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('button "add to allocate" is disabled', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); - checkProjectItemElements(i, projectNames[i], true); - } - }); - }); -}); diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts deleted file mode 100644 index 43e05b930d..0000000000 --- a/client/cypress/e2e/projectsArchive.cy.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { checkLocationWithLoader, moveEpoch, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -let wasEpochMoved = false; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`projects archive: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - }); - - it('moves to the next epoch', () => { - // Move time only once, for the first device. - if (!wasEpochMoved) { - cy.window().then(async win => { - const currentEpochBefore = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - await moveEpoch(win); - const currentEpochAfter = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - wasEpochMoved = true; - expect(currentEpochBefore + 1).to.eq(currentEpochAfter); - }); - } else { - expect(true).to.be.true; - } - }); - - it('renders archive elements + clicking on epoch archive ProjectsListItem opens ProjectView for particular epoch and project', () => { - cy.get('[data-test=MainLayout__body]').then(el => { - const mainLayoutPaddingTop = parseInt(el.css('paddingTop'), 10); - - cy.get('[data-test=ProjectsView__ProjectsList]') - .should('be.visible') - .children() - .then(children => { - children[children.length - 1].scrollIntoView(); - cy.window().then(window => window.scrollTo(0, window.scrollY - mainLayoutPaddingTop)); - cy.wait(1000); - // header test - cy.get('[data-test=ProjectsView__ProjectsList__header--archive]').should('be.visible'); - - // list test - cy.get('[data-test=ProjectsView__ProjectsList--archive]').first().should('be.visible'); - cy.get('[data-test=ProjectsView__ProjectsList--archive]') - .first() - .children() - .then(childrenArchive => { - const numberOfArchivedProjects = childrenArchive.length - 2; // archived projects tiles - (header + divider)[2] - for (let i = 0; i < numberOfArchivedProjects; i++) { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .scrollIntoView(); - cy.window().then(window => - window.scrollTo(0, window.scrollY - mainLayoutPaddingTop), - ); - // list item test - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .should('be.visible') - .within(() => { - // rewards test - cy.get('[data-test=ProjectRewards]').should('be.visible'); - }); - - if (numberOfArchivedProjects - 1) { - cy.get('[data-test=ProjectsView__ProjectsList--archive]') - .first() - .should('have.length', 1); - } - - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .invoke('data', 'address') - .then(address => { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .invoke('data', 'epoch') - .then(epoch => { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .click(); - checkLocationWithLoader( - `${ROOT_ROUTES.project.absolute}/${epoch}/${address}`, - ); - cy.go('back'); - checkLocationWithLoader(ROOT_ROUTES.projects.absolute); - }); - }); - } - }); - }); - }); - }); - }); -}); diff --git a/client/cypress/e2e/rewardsCalculator.cy.ts b/client/cypress/e2e/rewardsCalculator.cy.ts deleted file mode 100644 index 361153ff90..0000000000 --- a/client/cypress/e2e/rewardsCalculator.cy.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { ETH_USD, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; -import getFormattedEthValue from 'src/utils/getFormattedEthValue'; -import { parseUnitsBigInt } from 'src/utils/parseUnitsBigInt'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`rewards calculator: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.earn.absolute); - }); - - it('renders calculator icon inside box', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').should('be.visible'); - }); - - if (isDesktop) { - it('tooltip is visible on calculator icon hover and has correct text', () => { - cy.get('[data-test=Tooltip__rewardsCalculator').trigger('mouseover'); - cy.get('[data-test=Tooltip__rewardsCalculator__content') - .should('be.visible') - .invoke('text') - .should('eq', 'Calculate rewards'); - }); - } - - it('clicking on rewards calculator icon opens rewards calculator modal', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=ModalRewardsCalculator]').should('be.visible'); - }); - - it('default values in rewards calculator are 90 days and 5000 GLM', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=RewardsCalculator__InputText--crypto]').invoke('val').should('eq', '5000'); - cy.get('[data-test=RewardsCalculator__InputText--days]').invoke('val').should('eq', '90'); - }); - - it('calculator fetches rewards values in ETH and USD based on DAYS and GLM fields', () => { - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'be.visible', - ); - cy.wait('@postEstimatedRewards'); - - cy.get('@postEstimatedRewards').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get( - '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', - ).should('not.exist'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - }, - ); - - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsGlmValueChange'); - cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('500000'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'be.visible', - ); - cy.wait('@postEstimatedRewardsGlmValueChange'); - - cy.get('@postEstimatedRewardsGlmValueChange').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get( - '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', - ).should('not.exist'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - }, - ); - - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsDaysValueChange'); - cy.get('[data-test=RewardsCalculator__InputText--days]').clear().type('900'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'be.visible', - ); - cy.wait('@postEstimatedRewardsDaysValueChange'); - - cy.get('@postEstimatedRewardsDaysValueChange').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get( - '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', - ).should('not.exist'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - }, - ); - }); - - it('If DAYS or GLM input is empty rewards inputs are empty too', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=RewardsCalculator__InputText--crypto]').clear(); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - - cy.get('[data-test=RewardsCalculator__InputText--days]').clear(); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - - cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('5000'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( - 'not.exist', - ); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - }); - - it('Max GLM amount is 1000000000', () => { - cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - - cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('1000000000'); - cy.wait('@postEstimatedRewards'); - - cy.get('@postEstimatedRewards').then( - ({ - response: { - body: { budget }, - }, - }) => { - const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; - const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); - - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', rewardsEth); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', rewardsUsd); - - cy.get('[data-test=RewardsCalculator__InputText--crypto]') - .clear() - .type('1000000001') - .should('have.css', 'border-color', 'rgb(255, 97, 87)'); - cy.get('[data-test=RewardsCalculator__InputText--crypto__error]') - .should('be.visible') - .invoke('text') - .should('eq', 'That isn’t a valid amount'); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') - .invoke('val') - .should('eq', ''); - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') - .invoke('val') - .should('eq', ''); - }, - ); - }); - - it('Closing the modal successfully cancels the request /estimated_budget', () => { - cy.window().then(win => { - cy.spy(win.console, 'error').as('consoleErrSpy'); - }); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - - cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( - 'be.visible', - ); - - cy.get('[data-test=ModalRewardsCalculator__Button]').click(); - cy.get('[data-test=ModalRewardsCalculator').should('not.be.visible'); - - cy.on('uncaught:exception', error => { - expect(error.code).to.equal('ERR_CANCELED'); - }); - }); - }); -}); diff --git a/client/cypress/e2e/routes.cy.ts b/client/cypress/e2e/routes.cy.ts deleted file mode 100644 index 1d2dc113b8..0000000000 --- a/client/cypress/e2e/routes.cy.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`routes (wallet not connected): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - mockCoinPricesServer(); - cy.clearLocalStorage(); - }); - - it('empty route redirects to projects view', () => { - visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('allocation route redirects to allocation view', () => { - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationView]').should('be.visible'); - }); - - it('earn route redirects to earn view', () => { - visitWithLoader(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView]').should('be.visible'); - }); - - it('metrics route redirects to metrics view', () => { - visitWithLoader(ROOT_ROUTES.metrics.absolute); - cy.get('[data-test=MetricsView]').should('be.visible'); - }); - - it('projects route redirects to projects view', () => { - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('settings route redirects to settings view', () => { - visitWithLoader(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - }); - }); -}); diff --git a/client/cypress/e2e/settings.cy.ts b/client/cypress/e2e/settings.cy.ts deleted file mode 100644 index 087b77329e..0000000000 --- a/client/cypress/e2e/settings.cy.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { FIAT_CURRENCIES_SYMBOLS, DISPLAY_CURRENCIES } from 'src/constants/currencies'; -import { - ARE_OCTANT_TIPS_ALWAYS_VISIBLE, - DISPLAY_CURRENCY, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; -import getValueCryptoToDisplay from 'src/utils/getValueCryptoToDisplay'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`settings: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - }); - - it('"Always show Allocate onboarding" option toggle works', () => { - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check(); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); - }); - - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('false'); - }); - - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); - }); - }); - - it('"Use crypto as main value display" option is checked by default', () => { - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - }); - - it('"Use crypto as main value display" option toggle works', () => { - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').check(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('false'); - }); - - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - }); - - it('"Use crypto as main value display" option by default displays crypto value as primary in DoubleValue component', () => { - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - const cryptoValue = getValueCryptoToDisplay({ - cryptoCurrency: 'golem', - valueCrypto: BigInt(0), - }); - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('eq', cryptoValue); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('not.eq', cryptoValue); - }); - - it('"Use crypto as main value display" option changes DoubleValue sections order', () => { - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').uncheck(); - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - const cryptoValue = getValueCryptoToDisplay({ - cryptoCurrency: 'golem', - valueCrypto: BigInt(0), - }); - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('not.eq', cryptoValue); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('eq', cryptoValue); - }); - - it('"Choose a display currency" option works', () => { - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(DISPLAY_CURRENCY)).eq('"usd"'); - }); - - for (let i = 0; i < DISPLAY_CURRENCIES.length - 1; i++) { - const displayCurrency = DISPLAY_CURRENCIES[i]; - const displayCurrencyToUppercase = displayCurrency.toUpperCase(); - const nextDisplayCurrencyToUppercase = - i < DISPLAY_CURRENCIES.length - 1 ? DISPLAY_CURRENCIES[i + 1].toUpperCase() : undefined; - - cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency__SingleValue]').contains( - displayCurrencyToUppercase, - ); - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - if (FIAT_CURRENCIES_SYMBOLS[displayCurrency]) { - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( - FIAT_CURRENCIES_SYMBOLS[displayCurrency], - ); - } else { - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( - displayCurrencyToUppercase, - ); - } - - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency]').click(); - cy.get( - `[data-test=SettingsCurrencyBox__InputSelect--currency__Option--${nextDisplayCurrencyToUppercase}]`, - ).click(); - } - }); - - it('"Always show Octant tips" option toggle works', () => { - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); - }); - - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('false'); - }); - - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); - }); - }); - - it('"Always show Octant tips" works (checked)', () => { - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); - - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - - cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - - cy.reload(); - - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - }); - - it('"Always show Octant tips" works (unchecked)', () => { - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').uncheck(); - - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - - cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - - cy.reload(); - - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - }); - }); -}); diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index a673ac807a..89b442c89f 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -50,14 +50,19 @@ export const connectWallet = ( return cy.acceptMetamaskAccess(); }; -export const moveEpoch = async (cypressWindow: Cypress.AUTWindow): Promise => { - await cypressWindow.mutateAsyncMoveEpoch(); - // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). - cy.wait(2000); - // Manually taking a pending snapshot after the epoch shift ensures that the snapshot is taken. Passing epoch multiple times without manually triggering pending snapshot in a short period of time may cause the e2e environment to fail. - await axios.post(`${env.serverEndpoint}snapshots/pending`); - // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). - cy.wait(2000); - // reload is needed to get updated data in the app - cy.reload(); +export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Promise => { + return new Promise(resolve => { + cypressWindow.mutateAsyncMoveEpoch().then(() => { + // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). + cy.wait(2000); + // Manually taking a pending snapshot after the epoch shift ensures that the snapshot is taken. Passing epoch multiple times without manually triggering pending snapshot in a short period of time may cause the e2e environment to fail. + axios.post(`${env.serverEndpoint}snapshots/pending`).then(() => { + // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). + cy.wait(2000); + // reload is needed to get updated data in the app + cy.reload(); + resolve(true); + }); + }); + }); }; From f059b00fa49fe850fb682352b2061bb7132acf30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 29 Mar 2024 23:59:43 +0100 Subject: [PATCH 17/22] test: adjustments --- client/cypress/e2e/earn.cy.ts | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts index ec7b26cddf..31234e86ca 100644 --- a/client/cypress/e2e/earn.cy.ts +++ b/client/cypress/e2e/earn.cy.ts @@ -247,23 +247,26 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes timeout: 60000, }).should('not.exist'); cy.window().then(async win => { - await moveEpoch(win); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + cy.wrap(null).then(() => { + return moveEpoch(win).then(() => { + cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { + timeout: 60000, + }) + .invoke('text') + .then(nextText => { + const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); + expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + }); + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { + timeout: 60000, + }) + .invoke('text') + .then(nextText => { + const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); + expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); + }); }); + }); }); }); }); From 0a68f08dc088e012828e65baad68d3b70404d3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 30 Mar 2024 00:02:32 +0100 Subject: [PATCH 18/22] test: adjustments --- client/cypress/e2e/layout.cy.ts | 126 ++++ client/cypress/e2e/metrics.cy.ts | 126 ++++ client/cypress/e2e/onboarding.cy.ts | 380 +++++++++++ client/cypress/e2e/patronMode.cy.ts | 724 +++++++++++++++++++++ client/cypress/e2e/project.cy.ts | 181 ++++++ client/cypress/e2e/projects.cy.ts | 239 +++++++ client/cypress/e2e/projectsArchive.cy.ts | 103 +++ client/cypress/e2e/rewardsCalculator.cy.ts | 252 +++++++ client/cypress/e2e/routes.cy.ts | 42 ++ client/cypress/e2e/settings.cy.ts | 188 ++++++ 10 files changed, 2361 insertions(+) create mode 100644 client/cypress/e2e/layout.cy.ts create mode 100644 client/cypress/e2e/metrics.cy.ts create mode 100644 client/cypress/e2e/onboarding.cy.ts create mode 100644 client/cypress/e2e/patronMode.cy.ts create mode 100644 client/cypress/e2e/project.cy.ts create mode 100644 client/cypress/e2e/projects.cy.ts create mode 100644 client/cypress/e2e/projectsArchive.cy.ts create mode 100644 client/cypress/e2e/rewardsCalculator.cy.ts create mode 100644 client/cypress/e2e/routes.cy.ts create mode 100644 client/cypress/e2e/settings.cy.ts diff --git a/client/cypress/e2e/layout.cy.ts b/client/cypress/e2e/layout.cy.ts new file mode 100644 index 0000000000..9ea9954d24 --- /dev/null +++ b/client/cypress/e2e/layout.cy.ts @@ -0,0 +1,126 @@ +import { navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; +import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`layout: ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + cy.clearLocalStorage(); + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + cy.disconnectMetamaskWalletFromAllDapps(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + cy.visit(ROOT.absolute); + }); + + it('renders top bar', () => { + cy.get('[data-test=MainLayout__Header]').should('be.visible'); + }); + + it('Clicking on Octant logo scrolls view to the top on logo click (projects view)', () => { + cy.scrollTo(0, 500); + cy.get('[data-test=MainLayout__Logo]').click(); + // waiting for scrolling to finish + cy.wait(2000); + cy.window().then(cyWindow => { + expect(cyWindow.scrollY).to.be.eq(0); + }); + }); + + it('Clicking on Octant logo redirects to projects view (outside projects view)', () => { + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsView]').should('be.visible'); + cy.get('[data-test=MainLayout__Logo]').click(); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('Clicking on Octant logo redirects to projects view (outside projects view) with memorized scrollY', () => { + cy.scrollTo(0, 500); + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsView]').should('be.visible'); + cy.get('[data-test=MainLayout__Logo]').click(); + cy.get('[data-test=ProjectsView]').should('be.visible'); + cy.window().then(cyWindow => { + expect(cyWindow.scrollY).to.be.eq(500); + }); + }); + + it('renders bottom navbar', () => { + cy.get('[data-test=Navbar]').should('be.visible'); + }); + + it('bottom navbar allows to change views', () => { + navigationTabs.forEach(({ to }) => { + navigateWithCheck(to); + }); + }); + + it('"Connect" button is visible when wallet is disconnected', () => { + cy.get('[data-test=MainLayout__Button--connect]').should('be.visible'); + cy.get('[data-test=MainLayout__Button--connect]').click(); + }); + + it('"Connect" button opens "ModalConnectWallet"', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet]').should('be.visible'); + }); + + it('"ModalConnectWallet" always shows "WalletConnect" option', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet]').within(() => { + cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); + }); + }); + + it('"ModalConnectWallet" shows "Browser wallet" and "WalletConnect" options (MetaMask wallet detected)', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet]').within(() => { + cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); + cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').should('be.visible'); + }); + }); + + it('"ModalConnectWallet" has overflow enabled', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__overflow]').should('exist'); + }); + + it('Clicking background when "ModalConnectWallet" is open, closes Modal', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__overflow]').click({ force: true }); + cy.get('[data-test=ModalConnectWallet]').should('not.exist'); + }); + + it('"ModalConnectWallet" has "cross" icon button in header', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__Button]').should('be.visible'); + }); + + it('Clicking on "X" mark in "ModalConnectWallet", closes Modal', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ModalConnectWallet__Button]').click(); + cy.get('[data-test=ModalConnectWallet]').should('not.exist'); + }); + + it('Clicking on "WalletConnect" option, opens Web3Modal', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').click(); + cy.get('w3m-modal').find('#w3m-modal', { includeShadowDom: true }).should('be.visible'); + }); + + it('Clicking on "Browser wallet" option connects with MetaMask wallet', () => { + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); + cy.switchToMetamaskNotification(); + cy.acceptMetamaskAccess(); + cy.get('[data-test=MainLayout__Button--connect]').should('not.exist'); + cy.get('[data-test=ProfileInfo]').should('exist'); + }); + }); +}); diff --git a/client/cypress/e2e/metrics.cy.ts b/client/cypress/e2e/metrics.cy.ts new file mode 100644 index 0000000000..829fbfbf1c --- /dev/null +++ b/client/cypress/e2e/metrics.cy.ts @@ -0,0 +1,126 @@ +import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { + describe(`metrics: ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.metrics.absolute); + }); + + it('renders total projects tile', () => { + cy.get('[data-test=MetricsGeneralGridTotalProjects]').should('be.visible'); + }); + + it('renders total eth staked tile', () => { + cy.get('[data-test=MetricsGeneralGridTotalEthStaked]').should('be.visible'); + }); + + it('renders tile with total glm locked and % of 1B total supply groups', () => { + cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]').should('be.visible'); + cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]') + .children() + .should('have.length', 2); + }); + + it('renders wallet with glm locked graph tile', () => { + cy.get('[data-test=MetricsGeneralGridWalletsWithGlmLocked]').should('be.visible'); + }); + + it('renders cumulative glm locked graph tile', () => { + cy.get('[data-test=MetricsGeneralGridCumulativeGlmLocked]').should('be.visible'); + }); + + it('renders tiles in correct order', () => { + const metricsEpochGridTilesDataTest = [ + 'MetricsEpochGridTopProjects', + 'MetricsEpochGridTotalDonationsAndPersonal', + 'MetricsEpochGridDonationsVsPersonalAllocations', + 'MetricsEpochGridFundsUsage', + 'MetricsEpochGridTotalUsers', + 'MetricsEpochGridPatrons', + 'MetricsEpochGridCurrentDonors', + 'MetricsEpochGridAverageLeverage', + 'MetricsEpochGridRewardsUnusedAndUnallocatedValue', + 'MetricsEpochGridBelowThreshold', + ]; + + const metricsGeneralGridTilesDataTest = [ + 'MetricsGeneralGridTotalGlmLockedAndTotalSupply', + 'MetricsGeneralGridTotalProjects', + 'MetricsGeneralGridTotalEthStaked', + 'MetricsGeneralGridCumulativeGlmLocked', + 'MetricsGeneralGridWalletsWithGlmLocked', + ]; + + cy.get('[data-test=MetricsEpoch__MetricsGrid]') + .children() + .should('have.length', metricsEpochGridTilesDataTest.length); + + for (let i = 0; i < metricsEpochGridTilesDataTest.length; i++) { + cy.get('[data-test=MetricsEpoch__MetricsGrid]') + .children() + .eq(i) + .invoke('data', 'test') + .should('eq', metricsEpochGridTilesDataTest[i]); + } + + cy.get('[data-test=MetricsGeneral__MetricsGrid]') + .children() + .should('have.length', metricsGeneralGridTilesDataTest.length); + + for (let i = 0; i < metricsGeneralGridTilesDataTest.length; i++) { + cy.get('[data-test=MetricsGeneral__MetricsGrid]') + .children() + .eq(i) + .invoke('data', 'test') + .should('eq', metricsGeneralGridTilesDataTest[i]); + } + }); + + it('renders grid with 4 columns on desktop or with 2 columns on other devices', () => { + cy.get('[data-test=MetricsEpoch__MetricsGrid]').then(el => { + const width = parseInt(el.css('width'), 10); + const rowGap = parseInt(el.css('rowGap'), 10); + + const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; + + cy.get('[data-test=MetricsEpoch__MetricsGrid]').should( + 'have.css', + 'grid-template-columns', + isDesktop + ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` + : `${columnWidth}px ${columnWidth}px`, + ); + }); + + cy.get('[data-test=MetricsGeneral__MetricsGrid]').then(el => { + const width = parseInt(el.css('width'), 10); + const rowGap = parseInt(el.css('rowGap'), 10); + + const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; + + cy.get('[data-test=MetricsGeneral__MetricsGrid]').should( + 'have.css', + 'grid-template-columns', + isDesktop + ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` + : `${columnWidth}px ${columnWidth}px`, + ); + }); + }); + }); +}); diff --git a/client/cypress/e2e/onboarding.cy.ts b/client/cypress/e2e/onboarding.cy.ts new file mode 100644 index 0000000000..e528f9e87f --- /dev/null +++ b/client/cypress/e2e/onboarding.cy.ts @@ -0,0 +1,380 @@ +import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { stepsDecisionWindowClosed } from 'src/hooks/helpers/useOnboardingSteps/steps'; +import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +import Chainable = Cypress.Chainable; + +const connectWallet = ( + isTOSAccepted: boolean, + shouldVisit = true, + shouldReload = false, +): Chainable => { + cy.intercept('GET', '/user/*/tos', { body: { accepted: isTOSAccepted } }); + cy.disconnectMetamaskWalletFromAllDapps(); + if (shouldVisit) { + visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); + } + if (shouldReload) { + cy.reload(); + } + cy.get('[data-test=MainLayout__Button--connect]').click(); + cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); + cy.switchToMetamaskNotification(); + return cy.acceptMetamaskAccess(); +}; + +const beforeSetup = () => { + mockCoinPricesServer(); + cy.clearLocalStorage(); + cy.setupMetamask(); + window.innerWidth = Cypress.config().viewportWidth; + window.innerHeight = Cypress.config().viewportHeight; +}; + +const checkCurrentElement = (el: number, isCurrent: boolean): Chainable => { + return cy + .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') + .eq(el) + .invoke('attr', 'data-iscurrent') + .should('eq', `${isCurrent}`); +}; + +const checkProgressStepperSlimIsCurrentAndClickNext = (index, isCurrent = true): Chainable => { + checkCurrentElement(index - 1, isCurrent); + return cy + .get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') + .eq(index) + .click({ force: true }); +}; + +const checkChangeStepsWithArrowKeys = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + [ + { el: 1, key: 'ArrowRight' }, + { el: 2, key: 'ArrowRight' }, + // { el: 3, key: 'ArrowRight' }, + // { el: 3, key: 'ArrowRight' }, + // { el: 2, key: 'ArrowLeft' }, + { el: 1, key: 'ArrowLeft' }, + { el: 0, key: 'ArrowLeft' }, + { el: 0, key: 'ArrowLeft' }, + ].forEach(({ key, el }) => { + cy.get('body').trigger('keydown', { key }); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); +}; + +const checkChangeStepsByClickingEdgeOfTheScreenUpTo25px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + cy.get('[data-test=ModalOnboarding]').then(element => { + const leftEdgeX = element.offsetParent().offset()?.left as number; + const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; + + [ + { clientX: rightEdgeX - 25, el: 1 }, + { clientX: rightEdgeX - 10, el: 2 }, + // { clientX: rightEdgeX - 5, el: 3 }, + // rightEdgeX === browser right frame + // { clientX: rightEdgeX - 1, el: 3 }, + // { clientX: leftEdgeX + 25, el: 2 }, + { clientX: leftEdgeX + 10, el: 1 }, + { clientX: leftEdgeX + 5, el: 0 }, + { clientX: leftEdgeX, el: 0 }, + ].forEach(({ clientX, el }) => { + cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); + }); +}; + +const checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + cy.get('[data-test=ModalOnboarding]').then(element => { + const leftEdgeX = element.offsetParent().offset()?.left as number; + const rightEdgeX = (leftEdgeX as number) + element.innerWidth()!; + + [ + { clientX: rightEdgeX - 25, el: 1 }, + { clientX: rightEdgeX - 26, el: 1 }, + { clientX: leftEdgeX + 26, el: 1 }, + { clientX: leftEdgeX + 25, el: 0 }, + ].forEach(({ clientX, el }) => { + cy.get('[data-test=ModalOnboarding]').click(clientX, element.height()! / 2); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); + }); +}; + +const checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + [ + { + el: 1, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 2, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 2, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + // { + // el: 3, + // touchMoveClientX: window.innerWidth / 2 - 5, + // touchStartClientX: window.innerWidth / 2, + // }, + { + el: 2, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 1, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 0, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 0, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { + cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { + touches: [{ clientX: touchStartClientX }], + }); + cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { + touches: [{ clientX: touchMoveClientX }], + }); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); +}; + +const checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px = (isTOSAccepted: boolean) => { + checkCurrentElement(0, true); + + [ + { + el: 1, + touchMoveClientX: window.innerWidth / 2 - 5, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 1, + touchMoveClientX: window.innerWidth / 2 - 4, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 1, + touchMoveClientX: window.innerWidth / 2 + 4, + touchStartClientX: window.innerWidth / 2, + }, + { + el: 0, + touchMoveClientX: window.innerWidth / 2 + 5, + touchStartClientX: window.innerWidth / 2, + }, + ].forEach(({ touchStartClientX, touchMoveClientX, el }) => { + cy.get('[data-test=ModalOnboarding]').trigger('touchstart', { + touches: [{ clientX: touchStartClientX }], + }); + cy.get('[data-test=ModalOnboarding]').trigger('touchmove', { + touches: [{ clientX: touchMoveClientX }], + }); + checkCurrentElement(el, isTOSAccepted || el === 0); + + if (!isTOSAccepted) { + checkCurrentElement(0, true); + } + }); +}; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`onboarding (TOS accepted): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + beforeSetup(); + }); + + beforeEach(() => { + connectWallet(true); + }); + + it('user is able to click through entire onboarding flow', () => { + for (let i = 1; i < stepsDecisionWindowClosed.length - 1; i++) { + checkProgressStepperSlimIsCurrentAndClickNext(i); + } + + cy.get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') + .eq(stepsDecisionWindowClosed.length - 1) + .click(); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); + }); + + it('user is able to close the modal by clicking button in the top-right', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); + }); + + it('renders every time page is refreshed when "Always show Allocate onboarding" option is checked', () => { + cy.get('[data-test=ModalOnboarding__Button]').click(); + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check().should('be.checked'); + cy.reload(); + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + }); + + it('renders only once when "Always show Allocate onboarding" option is not checked', () => { + cy.get('[data-test=ModalOnboarding__Button]').click(); + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); + cy.reload(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + }); + + it('user can change steps with arrow keys (left, right)', () => { + checkChangeStepsWithArrowKeys(true); + }); + + it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(true); + }); + + it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(true); + }); + + it('user can change steps by swiping on screen (difference more than or equal 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(true); + }); + + it('user cannot change steps by swiping on screen (difference less than 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); + }); + + it('user cannot change steps by swiping on screen (difference less than 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); + }); + + it('user is able to close the onboarding, and after disconnecting & connecting, onboarding does not show up again', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + connectWallet(true, false, true); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + }); + }); +}); + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`onboarding (TOS not accepted): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + beforeSetup(); + }); + + beforeEach(() => { + cy.intercept( + { + method: 'POST', + url: '/user/*/tos', + }, + { body: { accepted: true }, statusCode: 200 }, + ); + connectWallet(false); + }); + + it('onboarding TOS step should be first and active', () => { + checkCurrentElement(0, true); + cy.get('[data-test=ModalOnboardingTOS]').should('be.visible'); + }); + + it('user is not able to click through entire onboarding flow', () => { + for (let i = 1; i < stepsDecisionWindowClosed.length; i++) { + checkProgressStepperSlimIsCurrentAndClickNext(i, i === 1); + } + }); + + it('user is not able to close the modal by clicking button in the top-right', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.get('[data-test=ModalOnboarding__Button]').click({ force: true }); + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + }); + + it('renders every time page is refreshed', () => { + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + cy.reload(); + cy.get('[data-test=ModalOnboarding]').should('be.visible'); + }); + + it('user cannot change steps with arrow keys (left, right)', () => { + checkChangeStepsWithArrowKeys(false); + }); + + it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(false); + }); + + it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { + checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(false); + }); + + it('user cannot change steps by swiping on screen (difference more than or equal 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(false); + }); + + it('user cannot change steps by swiping on screen (difference less than 5px)', () => { + checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(false); + }); + + it('TOS acceptance changes onboarding step to next step', () => { + checkCurrentElement(0, true); + cy.get('[data-test=TOS_InputCheckbox]').check(); + cy.switchToMetamaskNotification(); + cy.confirmMetamaskSignatureRequest(); + checkCurrentElement(1, true); + }); + + it('TOS acceptance allows the user to close the modal by clicking button in the top-right', () => { + checkCurrentElement(0, true); + cy.get('[data-test=TOS_InputCheckbox]').check(); + cy.switchToMetamaskNotification(); + cy.confirmMetamaskSignatureRequest(); + checkCurrentElement(1, true); + cy.get('[data-test=ModalOnboarding__Button]').click(); + cy.get('[data-test=ModalOnboarding]').should('not.exist'); + }); + }); +}); diff --git a/client/cypress/e2e/patronMode.cy.ts b/client/cypress/e2e/patronMode.cy.ts new file mode 100644 index 0000000000..b5f3455c47 --- /dev/null +++ b/client/cypress/e2e/patronMode.cy.ts @@ -0,0 +1,724 @@ +import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { + describe(`patron mode (disabled): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.settings.absolute); + connectWallet(true, false); + }); + + it('patron badge should not exist ', () => { + cy.get('[data-test=ProfileInfo__badge]').should('not.exist'); + }); + + it('Patron mode toggle is not checked', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('not.be.checked'); + }); + + if (isDesktop) { + it('Patron mode tooltip is visible on hover and has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') + .invoke('text') + .should( + 'eq', + 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', + ); + }); + } + + it('Checking patron mode opens patron mode modal', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=ModalPatronMode]').should('be.visible'); + }); + + it('Patron mode modal last paragraph has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=SettingsPatronMode__fourthParagraph]') + .invoke('text') + .should('eq', 'Slide the switch below all the way to the right to enable patron mode.'); + }); + + it('Slider is visible', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider]').should('be.visible'); + }); + + it('Slider has correct label', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider__label]') + .invoke('text') + .should('eq', 'Slide right to confirm'); + }); + + it('Slider button is visible ', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); + }); + + it('Slider button has right arrow inside', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider__button__arrow]') + .should('be.visible') + .should('have.css', 'transform', 'none'); + }); + + it('Slider button is on the left side', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderLeftDistance = sliderEl[0].getBoundingClientRect().left; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonLeftDistance = sliderButtonEl[0].getBoundingClientRect().left; + + expect(sliderButtonLeftDistance).to.be.eq(sliderLeftDistance + sliderLeftPadding); + }); + }); + }); + + it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.x; + const pointerMovePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerDownPageX); + }); + }); + }); + }); + + it('Slider elements change color while moving slider button', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const slider0PercentagePageX = sliderButtonDimensions.x; + const slider25PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 4; + const slider50PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; + const slider75PercentagePageX = sliderButtonDimensions.x + 3 * (sliderTrackWidth / 4); + const slider100PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth; + + // 0% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { + pageX: slider0PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgb(243, 243, 243)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(23, 23, 23)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '1'); + + // 25% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider25PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(212, 224, 221, 0.95)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(222, 234, 231)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(129, 129, 129)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.75'); + + // 50% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider50PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(175, 204, 197, 0.9)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(183, 211, 204)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(181, 181, 181)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.5'); + + // 75% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider75PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(128, 181, 169, 0.85)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(133, 185, 173)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(221, 221, 221)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.25'); + + // 100% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider100PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(45, 155, 135, 0.8)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(45, 155, 135)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0'); + }); + }); + }); + + it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.x; + const pointerMovePageX = sliderButtonDimensions.x + (sliderTrackWidth / 2 + 1); + const pointerUpPageX = sliderDimensions.right - sliderRightPadding - sliderButtonWidth; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); + }); + + cy.confirmMetamaskSignatureRequest(); + cy.switchToCypressWindow(); + cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__status-label]') + .invoke('text') + .should('eq', 'Patron mode enabled'); + cy.wait(500); + cy.get('[data-test=ModalPatronMode]').should('not.exist'); + }); + }); + }); + }); + + describe(`patron mode (enabled): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.settings.absolute); + connectWallet(true, true); + }); + + it('patron badge is visible and has correct label, background and text-transform prop', () => { + cy.get('[data-test=ProfileInfo__badge]').should('be.visible'); + cy.get('[data-test=ProfileInfo__badge]').invoke('text').should('eq', 'Patron'); + cy.get('[data-test=ProfileInfo__badge]') + .should('have.css', 'background-color', 'rgb(104, 91, 138)') + .should('have.css', 'text-transform', 'uppercase'); + }); + + it('Navbar has 4 items - projects, earn, metrics, settings', () => { + const navbarChildrenDataTest = [ + 'Navbar__Button--Projects', + 'Navbar__Button--Earn', + 'Navbar__Button--Metrics', + 'Navbar__Button--Settings', + ]; + + cy.get('[data-test=Navbar__buttons]') + .children() + .should('have.length', navbarChildrenDataTest.length); + + for (let i = 0; i < navbarChildrenDataTest.length; i++) { + cy.get('[data-test=Navbar__buttons]') + .children() + .eq(i) + .invoke('data', 'test') + .should('eq', navbarChildrenDataTest[i]); + } + }); + + it('route /allocate redirects to /projects', () => { + visitWithLoader(ROOT_ROUTES.allocation.absolute, ROOT_ROUTES.projects.absolute); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('BoxPersonalAllocation has correct title and sections labels', () => { + visitWithLoader(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=BoxPersonalAllocation__title]') + .invoke('text') + .should('eq', 'Patron earnings'); + cy.get('[data-test=BoxPersonalAllocation__Section__label]') + .eq(0) + .invoke('text') + .should('eq', 'Current epoch'); + cy.get('[data-test=BoxPersonalAllocation__Section__label]') + .eq(1) + .invoke('text') + .should('eq', 'All time'); + }); + + it('Patron mode toggle is checked', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('be.checked'); + }); + + if (isDesktop) { + it('Patron mode tooltip is visible on hover and has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); + cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') + .invoke('text') + .should( + 'eq', + 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', + ); + }); + } + + it('Unchecking patron mode opens patron mode modal', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=ModalPatronMode]').should('be.visible'); + }); + + it('Patron mode modal last paragraph has correct text', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=SettingsPatronMode__fourthParagraph]') + .invoke('text') + .should('eq', 'Slide the switch below all the way to the left to disable patron mode.'); + }); + + it('Slider is visible', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider]').should('be.visible'); + }); + + it('Slider has correct label', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider__label]') + .invoke('text') + .should('eq', 'Slide left to confirm'); + }); + + it('Slider button is visible', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); + }); + + it('Slider button has left arrow inside', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider__button__arrow]') + .should('be.visible') + .should('have.css', 'transform', 'matrix(-1, 0, 0, -1, 0, 0)'); + }); + + it('Slider button is on the right side', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderRightDistance = sliderEl[0].getBoundingClientRect().right; + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonRightDistance = sliderButtonEl[0].getBoundingClientRect().right; + + expect(sliderButtonRightDistance).to.be.eq(sliderRightDistance - sliderRightPadding); + }); + }); + }); + + it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.right; + const pointerMovePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.right).eq(pointerDownPageX); + }); + }); + }); + }); + + it('Slider elements change color while moving slider button', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const slider0PercentagePageX = sliderButtonDimensions.right; + const slider25PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 4; + const slider50PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; + const slider75PercentagePageX = sliderButtonDimensions.right - 3 * (sliderTrackWidth / 4); + const slider100PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth; + + // 0% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { + pageX: slider0PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgb(243, 243, 243)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(23, 23, 23)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '1'); + + // 25% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider25PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(212, 224, 221, 0.95)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(222, 234, 231)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(129, 129, 129)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.75'); + + // 50% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider50PercentagePageX, + waitForAnimations: true, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(175, 204, 197, 0.9)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(183, 211, 204)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(181, 181, 181)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.5'); + + // 75% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider75PercentagePageX, + waitForAnimations: true, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(128, 181, 169, 0.85)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(133, 185, 173)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(221, 221, 221)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0.25'); + + // 100% + cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { + pageX: slider100PercentagePageX, + }); + cy.get('[data-test=PatronModeSlider]').should( + 'have.css', + 'background-color', + 'rgba(45, 155, 135, 0.8)', + ); + cy.get('[data-test=PatronModeSlider__button]').should( + 'have.css', + 'background-color', + 'rgb(45, 155, 135)', + ); + cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( + 'have.css', + 'fill', + 'rgb(255, 255, 255)', + ); + cy.get('[data-test=PatronModeSlider__label]') + .should('have.css', 'color', 'rgb(158, 163, 158)') + .should('have.css', 'opacity', '0'); + }); + }); + }); + + it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { + cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); + + cy.get('[data-test=PatronModeSlider]').then(sliderEl => { + const sliderDimensions = sliderEl[0].getBoundingClientRect(); + const sliderWidth = sliderDimensions.width; + const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); + const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); + + cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { + const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); + + const sliderButtonWidth = sliderButtonDimensions.width; + + const sliderTrackWidth = + sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; + + const pointerDownPageX = sliderButtonDimensions.right; + const pointerMovePageX = sliderButtonDimensions.right - (sliderTrackWidth / 2 + 1); + const pointerUpPageX = sliderDimensions.left + sliderLeftPadding; + + cy.get('[data-test=PatronModeSlider__button]') + .trigger('pointerdown', { + pageX: pointerDownPageX, + }) + .trigger('pointermove', { + pageX: pointerMovePageX, + }) + .wait(1000) + .then(sliderButtonElAfterPointerMove => { + const sliderButtonDimensionsAfterPointerMove = + sliderButtonElAfterPointerMove[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); + }) + .trigger('pointerup') + .wait(1000) + .then(sliderButtonElAfterPointerUp => { + const sliderButtonDimensionsAfterPointerUp = + sliderButtonElAfterPointerUp[0].getBoundingClientRect(); + expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); + }); + + cy.confirmMetamaskSignatureRequest(); + cy.switchToCypressWindow(); + cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); + cy.get('[data-test=PatronModeSlider__status-label]') + .invoke('text') + .should('eq', 'Patron mode disabled'); + cy.wait(500); + cy.get('[data-test=ModalPatronMode]').should('not.exist'); + }); + }); + }); + + it('when entering project view, button icon changes to chevronLeft', () => { + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + cy.get('[data-test=Navbar__Button--Projects]') + .find('svg') + // HTML tag can't be self-closing in CY. + .should( + 'have.html', + '', + ); + }); + }); +}); diff --git a/client/cypress/e2e/project.cy.ts b/client/cypress/e2e/project.cy.ts new file mode 100644 index 0000000000..e811fb70aa --- /dev/null +++ b/client/cypress/e2e/project.cy.ts @@ -0,0 +1,181 @@ +import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import { getNamesOfProjects } from 'cypress/utils/projects'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +import Chainable = Cypress.Chainable; + +const getButtonAddToAllocate = (): Chainable => { + const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); + + return projectListItemFirst.find('[data-test=ProjectListItemHeader__ButtonAddToAllocate]'); +}; + +const checkProjectItemElements = (): Chainable => { + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); + projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); + getButtonAddToAllocate().should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItemHeader__Button]').should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItem__Description]').should('be.visible'); + + cy.get('[data-test=ProjectListItem__Donors]') + .first() + .scrollIntoView({ offset: { left: 0, top: 100 } }); + + cy.get('[data-test=ProjectListItem__Donors]').first().should('be.visible'); + cy.get('[data-test=ProjectListItem__Donors__DonorsHeader__count]') + .first() + .should('be.visible') + .should('have.text', '0'); + return cy.get('[data-test=ProjectListItem__Donors__noDonationsYet]').first().should('be.visible'); +}; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`project: ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + it('entering project view directly renders content', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + cy.reload(); + const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); + projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); + projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); + }); + + it('entering project view renders all its elements', () => { + checkProjectItemElements(); + }); + + it('entering project view renders all its elements with fallback IPFS provider', () => { + cy.intercept('GET', '**/ipfs/**', req => { + if (req.url.includes('infura')) { + req.destroy(); + } + }); + + checkProjectItemElements(); + }); + + it('entering project view shows Toast with info about IPFS failure when all providers fail', () => { + cy.intercept('GET', '**/ipfs/**', req => { + req.destroy(); + }); + + cy.get('[data-test=Toast--ipfsMessage').should('be.visible'); + }); + + it('entering project view allows to add it to allocation and remove, triggering change of the icon, change of the number in navbar', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); + + getButtonAddToAllocate().click(); + + // cy.get('@buttonAddToAllocate').click(); + cy.get('[data-test=Navbar__numberOfAllocations]').contains(1); + getButtonAddToAllocate().click(); + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + }); + + it('Entering project view allows scroll only to the last project', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); + + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test=ProjectListItem]').should( + 'have.length.greaterThan', + i === projectNames.length - 1 ? projectNames.length - 1 : i, + ); + cy.get('[data-test=ProjectListItemHeader__name]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: -150 } }) + .contains(projectNames[i]); + cy.get('[data-test=ProjectListItem__Donors]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: -150 } }) + .should('be.visible'); + } + }); + + it('"Back to top" button is displayed if the user has scrolled past the start of the final project description', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); + + for (let i = 0; i < projectNames.length - 1; i++) { + cy.get('[data-test=ProjectListItem__Donors]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: 100 } }); + + if (i === projectNames.length - 1) { + cy.get('[data-test=ProjectBackToTopButton__Button]').should('be.visible'); + } + } + }); + + it('Clicking on "Back to top" button scrolls to the top of view (first project is visible)', () => { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); + + for (let i = 0; i < projectNames.length - 1; i++) { + cy.get('[data-test=ProjectListItem__Donors]') + .eq(i) + .scrollIntoView({ offset: { left: 0, top: 100 } }); + + if (i === projectNames.length - 1) { + cy.get('[data-test=ProjectBackToTopButton__Button]').click(); + cy.get('[data-test=ProjectListItem]').eq(0).should('be.visible'); + } + } + }); + }); + + describe(`project (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + connectWallet(true, true); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + it('button "add to allocate" is disabled', () => { + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).click(); + getButtonAddToAllocate().should('be.visible').should('be.disabled'); + cy.go('back'); + } + }); + }); +}); diff --git a/client/cypress/e2e/projects.cy.ts b/client/cypress/e2e/projects.cy.ts new file mode 100644 index 0000000000..9d096bcdf2 --- /dev/null +++ b/client/cypress/e2e/projects.cy.ts @@ -0,0 +1,239 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import chaiColors from 'chai-colors'; + +import { connectWallet, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import { getNamesOfProjects } from 'cypress/utils/projects'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import getMilestones from 'src/constants/milestones'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +import Chainable = Cypress.Chainable; + +chai.use(chaiColors); + +function checkProjectItemElements(index, name, isPatronMode = false): Chainable { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__imageProfile]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem]') + .eq(index) + .should('be.visible') + .find('[data-test=ProjectsListItem__name]') + .should('be.visible') + .contains(name); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__IntroDescription]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .should('be.visible'); + + if (isPatronMode) { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .should('be.disabled'); + } + + return cy + .get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectRewards]') + .should('be.visible'); + // TODO OCT-663 Make CY check if rewards are available (Epoch 2, decision window open). + // return cy + // .get('[data-test^=ProjectsView__ProjectsListItem') + // .eq(index) + // .find('[data-test=ProjectRewards__currentTotal__label]') + // .should('be.visible'); +} + +function addProjectToAllocate(index, numberOfAddedProjects): Chainable { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__imageProfile]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__IntroDescription]') + .should('be.visible'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .scrollIntoView(); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .click(); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .find('svg') + .find('path') + .then($el => $el.css('fill')) + .should('be.colored', '#FF6157'); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .find('svg') + .find('path') + .then($el => $el.css('stroke')) + .should('be.colored', '#FF6157'); + cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects + 1); + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects + 1); + return cy.go('back'); +} + +function removeProjectFromAllocate(numberOfProjects, numberOfAddedProjects, index): Chainable { + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .scrollIntoView(); + cy.get('[data-test^=ProjectsView__ProjectsListItem') + .eq(index) + .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') + .click(); + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects - 1); + if (index < numberOfProjects - 1) { + cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects - 1); + } else { + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + } + return cy.go('back'); +} + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`projects: ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + it('user is able to see all the projects in the view', () => { + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); + checkProjectItemElements(i, projectNames[i]); + } + }); + + it('user is able to add & remove the first and the last project to/from allocation, triggering change of the icon, change of the number in navbar', () => { + // This test checks the first and the last elements only to save time. + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + + addProjectToAllocate(0, 0); + addProjectToAllocate(projectNames.length - 1, 1); + removeProjectFromAllocate(projectNames.length, 2, 0); + removeProjectFromAllocate(projectNames.length, 1, projectNames.length - 1); + }); + + it('user is able to add project to allocation in ProjectsView and remove it from allocation in AllocationView', () => { + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + addProjectToAllocate(0, 0); + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationItemSkeleton]').should('not.exist'); + cy.get('[data-test=AllocationItem]').then(el => { + const { x } = el[0].getBoundingClientRect(); + cy.get('[data-test=AllocationItem]') + .trigger('pointerdown') + .trigger('pointermove', { pageX: x - 20 }) + .trigger('pointerup'); + cy.get('[data-test=AllocationItem__removeButton]').should('be.visible'); + cy.get('[data-test=AllocationItem__removeButton]').click(); + cy.get('[data-test=AllocationItem__removeButton]').should('not.exist'); + cy.get('[data-test=AllocationItem]').should('not.exist'); + cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); + }); + + it('ProjectsTimelineWidgetItem with href opens link when clicked without mouse movement', () => { + const milestones = getMilestones(); + cy.get('[data-test=ProjectsTimelineWidget]').should('be.visible'); + cy.get('[data-test=ProjectsTimelineWidgetItem]').should('have.length', milestones.length); + for (let i = 0; i < milestones.length; i++) { + if (milestones[i].href) { + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .within(() => { + cy.get('[data-test=ProjectsTimelineWidgetItem__Svg--arrowTopRight]').should( + 'be.visible', + ); + }); + + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .then(el => { + const { x } = el[0].getBoundingClientRect(); + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .trigger('mousedown') + .trigger('mouseup', { clientX: x + 10 }); + cy.location('pathname').should('eq', ROOT_ROUTES.projects.absolute); + + cy.get('[data-test=ProjectsTimelineWidgetItem]') + .eq(i) + .trigger('mousedown') + .trigger('mouseup'); + cy.location('pathname').should('not.eq', ROOT_ROUTES.projects.absolute); + }); + } + } + }); + }); + }); + + describe(`projects (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { + let projectNames: string[] = []; + + before(() => { + /** + * Global Metamask setup done by Synpress is not always done. + * Since Synpress needs to have valid provider to fetch the data from contracts, + * setupMetamask is required in each test suite. + */ + cy.setupMetamask(); + }); + + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + connectWallet(true, true); + cy.get('[data-test^=ProjectItemSkeleton').should('not.exist'); + /** + * This could be done in before hook, but CY wipes the state after each test + * (could be disabled, but creates other problems) + */ + if (projectNames.length === 0) { + projectNames = getNamesOfProjects(); + } + }); + + after(() => { + cy.disconnectMetamaskWalletFromAllDapps(); + }); + + it('button "add to allocate" is disabled', () => { + for (let i = 0; i < projectNames.length; i++) { + cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); + checkProjectItemElements(i, projectNames[i], true); + } + }); + }); +}); diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts new file mode 100644 index 0000000000..d2abe9b5d0 --- /dev/null +++ b/client/cypress/e2e/projectsArchive.cy.ts @@ -0,0 +1,103 @@ +import { checkLocationWithLoader, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { QUERY_KEYS } from 'src/api/queryKeys'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +let wasEpochMoved = false; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`projects archive: ${device}`, { viewportHeight, viewportWidth }, () => { + beforeEach(() => { + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.projects.absolute); + }); + + it('moves to the next epoch', () => { + // Move time only once, for the first device. + if (!wasEpochMoved) { + cy.window().then(async win => { + const currentEpochBefore = Number( + win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), + ); + await win.mutateAsyncMoveEpoch(); + const currentEpochAfter = Number( + win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), + ); + wasEpochMoved = true; + expect(currentEpochBefore + 1).to.eq(currentEpochAfter); + }); + } else { + expect(true).to.be.true; + } + }); + + it('renders archive elements + clicking on epoch archive ProjectsListItem opens ProjectView for particular epoch and project', () => { + cy.get('[data-test=MainLayout__body]').then(el => { + const mainLayoutPaddingTop = parseInt(el.css('paddingTop'), 10); + + cy.get('[data-test=ProjectsView__ProjectsList]') + .should('be.visible') + .children() + .then(children => { + children[children.length - 1].scrollIntoView(); + cy.window().then(window => window.scrollTo(0, window.scrollY - mainLayoutPaddingTop)); + cy.wait(1000); + // header test + cy.get('[data-test=ProjectsView__ProjectsList__header--archive]').should('be.visible'); + + // list test + cy.get('[data-test=ProjectsView__ProjectsList--archive]').first().should('be.visible'); + cy.get('[data-test=ProjectsView__ProjectsList--archive]') + .first() + .children() + .then(childrenArchive => { + const numberOfArchivedProjects = childrenArchive.length - 2; // archived projects tiles - (header + divider)[2] + for (let i = 0; i < numberOfArchivedProjects; i++) { + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .scrollIntoView(); + cy.window().then(window => + window.scrollTo(0, window.scrollY - mainLayoutPaddingTop), + ); + // list item test + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .should('be.visible') + .within(() => { + // rewards test + cy.get('[data-test=ProjectRewards]').should('be.visible'); + }); + + if (numberOfArchivedProjects - 1) { + cy.get('[data-test=ProjectsView__ProjectsList--archive]') + .first() + .should('have.length', 1); + } + + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .invoke('data', 'address') + .then(address => { + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .invoke('data', 'epoch') + .then(epoch => { + cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) + .first() + .click(); + checkLocationWithLoader( + `${ROOT_ROUTES.project.absolute}/${epoch}/${address}`, + ); + cy.go('back'); + checkLocationWithLoader(ROOT_ROUTES.projects.absolute); + }); + }); + } + }); + }); + }); + }); + }); +}); diff --git a/client/cypress/e2e/rewardsCalculator.cy.ts b/client/cypress/e2e/rewardsCalculator.cy.ts new file mode 100644 index 0000000000..361153ff90 --- /dev/null +++ b/client/cypress/e2e/rewardsCalculator.cy.ts @@ -0,0 +1,252 @@ +import { ETH_USD, mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; +import getFormattedEthValue from 'src/utils/getFormattedEthValue'; +import { parseUnitsBigInt } from 'src/utils/parseUnitsBigInt'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { + describe(`rewards calculator: ${device}`, { viewportHeight, viewportWidth }, () => { + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.earn.absolute); + }); + + it('renders calculator icon inside box', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').should('be.visible'); + }); + + if (isDesktop) { + it('tooltip is visible on calculator icon hover and has correct text', () => { + cy.get('[data-test=Tooltip__rewardsCalculator').trigger('mouseover'); + cy.get('[data-test=Tooltip__rewardsCalculator__content') + .should('be.visible') + .invoke('text') + .should('eq', 'Calculate rewards'); + }); + } + + it('clicking on rewards calculator icon opens rewards calculator modal', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=ModalRewardsCalculator]').should('be.visible'); + }); + + it('default values in rewards calculator are 90 days and 5000 GLM', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=RewardsCalculator__InputText--crypto]').invoke('val').should('eq', '5000'); + cy.get('[data-test=RewardsCalculator__InputText--days]').invoke('val').should('eq', '90'); + }); + + it('calculator fetches rewards values in ETH and USD based on DAYS and GLM fields', () => { + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'be.visible', + ); + cy.wait('@postEstimatedRewards'); + + cy.get('@postEstimatedRewards').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get( + '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', + ).should('not.exist'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + }, + ); + + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsGlmValueChange'); + cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('500000'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'be.visible', + ); + cy.wait('@postEstimatedRewardsGlmValueChange'); + + cy.get('@postEstimatedRewardsGlmValueChange').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get( + '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', + ).should('not.exist'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + }, + ); + + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewardsDaysValueChange'); + cy.get('[data-test=RewardsCalculator__InputText--days]').clear().type('900'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'be.visible', + ); + cy.wait('@postEstimatedRewardsDaysValueChange'); + + cy.get('@postEstimatedRewardsDaysValueChange').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get( + '[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]', + ).should('not.exist'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + }, + ); + }); + + it('If DAYS or GLM input is empty rewards inputs are empty too', () => { + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + cy.get('[data-test=RewardsCalculator__InputText--crypto]').clear(); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + + cy.get('[data-test=RewardsCalculator__InputText--days]').clear(); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + + cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('5000'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat__Loader]').should( + 'not.exist', + ); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + }); + + it('Max GLM amount is 1000000000', () => { + cy.intercept('POST', '/rewards/estimated_budget').as('postEstimatedRewards'); + + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + + cy.get('[data-test=RewardsCalculator__InputText--crypto]').type('1000000000'); + cy.wait('@postEstimatedRewards'); + + cy.get('@postEstimatedRewards').then( + ({ + response: { + body: { budget }, + }, + }) => { + const rewardsEth = getFormattedEthValue(parseUnitsBigInt(budget, 'wei')).value; + const rewardsUsd = (parseFloat(rewardsEth) * ETH_USD).toFixed(2); + + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', rewardsEth); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', rewardsUsd); + + cy.get('[data-test=RewardsCalculator__InputText--crypto]') + .clear() + .type('1000000001') + .should('have.css', 'border-color', 'rgb(255, 97, 87)'); + cy.get('[data-test=RewardsCalculator__InputText--crypto__error]') + .should('be.visible') + .invoke('text') + .should('eq', 'That isn’t a valid amount'); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto]') + .invoke('val') + .should('eq', ''); + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--fiat]') + .invoke('val') + .should('eq', ''); + }, + ); + }); + + it('Closing the modal successfully cancels the request /estimated_budget', () => { + cy.window().then(win => { + cy.spy(win.console, 'error').as('consoleErrSpy'); + }); + + cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); + + cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should( + 'be.visible', + ); + + cy.get('[data-test=ModalRewardsCalculator__Button]').click(); + cy.get('[data-test=ModalRewardsCalculator').should('not.be.visible'); + + cy.on('uncaught:exception', error => { + expect(error.code).to.equal('ERR_CANCELED'); + }); + }); + }); +}); diff --git a/client/cypress/e2e/routes.cy.ts b/client/cypress/e2e/routes.cy.ts new file mode 100644 index 0000000000..1d2dc113b8 --- /dev/null +++ b/client/cypress/e2e/routes.cy.ts @@ -0,0 +1,42 @@ +import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`routes (wallet not connected): ${device}`, { viewportHeight, viewportWidth }, () => { + before(() => { + mockCoinPricesServer(); + cy.clearLocalStorage(); + }); + + it('empty route redirects to projects view', () => { + visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('allocation route redirects to allocation view', () => { + visitWithLoader(ROOT_ROUTES.allocation.absolute); + cy.get('[data-test=AllocationView]').should('be.visible'); + }); + + it('earn route redirects to earn view', () => { + visitWithLoader(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=EarnView]').should('be.visible'); + }); + + it('metrics route redirects to metrics view', () => { + visitWithLoader(ROOT_ROUTES.metrics.absolute); + cy.get('[data-test=MetricsView]').should('be.visible'); + }); + + it('projects route redirects to projects view', () => { + visitWithLoader(ROOT_ROUTES.projects.absolute); + cy.get('[data-test=ProjectsView]').should('be.visible'); + }); + + it('settings route redirects to settings view', () => { + visitWithLoader(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsView]').should('be.visible'); + }); + }); +}); diff --git a/client/cypress/e2e/settings.cy.ts b/client/cypress/e2e/settings.cy.ts new file mode 100644 index 0000000000..087b77329e --- /dev/null +++ b/client/cypress/e2e/settings.cy.ts @@ -0,0 +1,188 @@ +import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; +import viewports from 'cypress/utils/viewports'; +import { FIAT_CURRENCIES_SYMBOLS, DISPLAY_CURRENCIES } from 'src/constants/currencies'; +import { + ARE_OCTANT_TIPS_ALWAYS_VISIBLE, + DISPLAY_CURRENCY, + IS_CRYPTO_MAIN_VALUE_DISPLAY, + IS_ONBOARDING_ALWAYS_VISIBLE, + IS_ONBOARDING_DONE, +} from 'src/constants/localStorageKeys'; +import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; +import getValueCryptoToDisplay from 'src/utils/getValueCryptoToDisplay'; + +Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { + describe(`settings: ${device}`, { viewportHeight, viewportWidth }, () => { + beforeEach(() => { + mockCoinPricesServer(); + localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); + localStorage.setItem(IS_ONBOARDING_DONE, 'true'); + visitWithLoader(ROOT_ROUTES.settings.absolute); + }); + + it('"Always show Allocate onboarding" option toggle works', () => { + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check(); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); + }); + + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('false'); + }); + + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); + }); + }); + + it('"Use crypto as main value display" option is checked by default', () => { + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); + }); + }); + + it('"Use crypto as main value display" option toggle works', () => { + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').check(); + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); + }); + + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('not.be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('false'); + }); + + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); + }); + }); + + it('"Use crypto as main value display" option by default displays crypto value as primary in DoubleValue component', () => { + navigateWithCheck(ROOT_ROUTES.earn.absolute); + + const cryptoValue = getValueCryptoToDisplay({ + cryptoCurrency: 'golem', + valueCrypto: BigInt(0), + }); + + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') + .invoke('text') + .should('eq', cryptoValue); + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') + .invoke('text') + .should('not.eq', cryptoValue); + }); + + it('"Use crypto as main value display" option changes DoubleValue sections order', () => { + cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').uncheck(); + navigateWithCheck(ROOT_ROUTES.earn.absolute); + + const cryptoValue = getValueCryptoToDisplay({ + cryptoCurrency: 'golem', + valueCrypto: BigInt(0), + }); + + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') + .invoke('text') + .should('not.eq', cryptoValue); + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') + .invoke('text') + .should('eq', cryptoValue); + }); + + it('"Choose a display currency" option works', () => { + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(DISPLAY_CURRENCY)).eq('"usd"'); + }); + + for (let i = 0; i < DISPLAY_CURRENCIES.length - 1; i++) { + const displayCurrency = DISPLAY_CURRENCIES[i]; + const displayCurrencyToUppercase = displayCurrency.toUpperCase(); + const nextDisplayCurrencyToUppercase = + i < DISPLAY_CURRENCIES.length - 1 ? DISPLAY_CURRENCIES[i + 1].toUpperCase() : undefined; + + cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency__SingleValue]').contains( + displayCurrencyToUppercase, + ); + navigateWithCheck(ROOT_ROUTES.earn.absolute); + + if (FIAT_CURRENCIES_SYMBOLS[displayCurrency]) { + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( + FIAT_CURRENCIES_SYMBOLS[displayCurrency], + ); + } else { + cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( + displayCurrencyToUppercase, + ); + } + + navigateWithCheck(ROOT_ROUTES.settings.absolute); + cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency]').click(); + cy.get( + `[data-test=SettingsCurrencyBox__InputSelect--currency__Option--${nextDisplayCurrencyToUppercase}]`, + ).click(); + } + }); + + it('"Always show Octant tips" option toggle works', () => { + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); + }); + + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('not.be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('false'); + }); + + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); + cy.getAllLocalStorage().then(() => { + expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); + }); + }); + + it('"Always show Octant tips" works (checked)', () => { + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); + + navigateWithCheck(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); + + cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); + + cy.reload(); + + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); + }); + + it('"Always show Octant tips" works (unchecked)', () => { + cy.get('[data-test=SettingsShowTipsBox__InputToggle]').uncheck(); + + navigateWithCheck(ROOT_ROUTES.earn.absolute); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); + + cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); + + cy.reload(); + + cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); + }); + }); +}); From 78c42507c507703d36f83a033527d5987482a94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 30 Mar 2024 00:03:59 +0100 Subject: [PATCH 19/22] merge: w/ develop --- client/cypress/e2e/projectsArchive.cy.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts index 43e05b930d..d80fe1f16b 100644 --- a/client/cypress/e2e/projectsArchive.cy.ts +++ b/client/cypress/e2e/projectsArchive.cy.ts @@ -21,12 +21,16 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => const currentEpochBefore = Number( win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), ); - await moveEpoch(win); - const currentEpochAfter = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - wasEpochMoved = true; - expect(currentEpochBefore + 1).to.eq(currentEpochAfter); + + cy.wrap(null).then(() => { + return moveEpoch(win).then(() => { + const currentEpochAfter = Number( + win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), + ); + wasEpochMoved = true; + expect(currentEpochBefore + 1).to.eq(currentEpochAfter); + }); + }); }); } else { expect(true).to.be.true; From ee26e0acf6df253d0f053fddf0f48ebddff8e8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 30 Mar 2024 09:20:53 +0100 Subject: [PATCH 20/22] test: moveEpoch makes finalized snapshot too --- client/cypress/utils/e2e.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index 89b442c89f..1163a9ff7a 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -61,7 +61,9 @@ export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Promise => cy.wait(2000); // reload is needed to get updated data in the app cy.reload(); - resolve(true); + axios.post(`${env.serverEndpoint}snapshots/finalized`).then(() => { + resolve(true); + }); }); }); }); From 0dd71235e7470929e957d3bdf1c6a5c15c17399b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 30 Mar 2024 09:21:45 +0100 Subject: [PATCH 21/22] test: moveEpoch makes finalized snapshot too --- client/cypress/utils/e2e.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index 1163a9ff7a..9e321373b8 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -59,9 +59,11 @@ export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Promise => axios.post(`${env.serverEndpoint}snapshots/pending`).then(() => { // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); + cy.get('[data-test=SyncView]', { timeout: 60000 }).should('not.exist'); // reload is needed to get updated data in the app cy.reload(); axios.post(`${env.serverEndpoint}snapshots/finalized`).then(() => { + cy.get('[data-test=SyncView]', { timeout: 60000 }).should('not.exist'); resolve(true); }); }); From d7272bd4fc975069f255dc2ab3f6df8321bd2b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 30 Mar 2024 15:31:27 +0100 Subject: [PATCH 22/22] test: additional checks and reloads after snapshot --- client/cypress/utils/e2e.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index 9e321373b8..d5e8bd7580 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -59,11 +59,19 @@ export const moveEpoch = (cypressWindow: Cypress.AUTWindow): Promise => axios.post(`${env.serverEndpoint}snapshots/pending`).then(() => { // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). cy.wait(2000); + // reload is needed to get updated data in the app + cy.reload(); cy.get('[data-test=SyncView]', { timeout: 60000 }).should('not.exist'); // reload is needed to get updated data in the app cy.reload(); axios.post(`${env.serverEndpoint}snapshots/finalized`).then(() => { + // Waiting 2s is a way to prevent the effects of slowing down the e2e environment (data update). + cy.wait(2000); + // reload is needed to get updated data in the app + cy.reload(); cy.get('[data-test=SyncView]', { timeout: 60000 }).should('not.exist'); + // reload is needed to get updated data in the app + cy.reload(); resolve(true); }); });