Skip to content

Commit

Permalink
OCT-1119 Cypress: projects archives (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
aziolek authored Mar 11, 2024
1 parent 8f0263e commit 0c013b2
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 72 deletions.
107 changes: 107 additions & 0 deletions client/cypress/e2e/proposalsArchive.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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(`proposals archive: ${device}`, { viewportHeight, viewportWidth }, () => {
beforeEach(() => {
localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false');
localStorage.setItem(IS_ONBOARDING_DONE, 'true');
visitWithLoader(ROOT_ROUTES.proposals.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 ProposalsListItem opens ProposalView for particular epoch and project', () => {
cy.get('[data-test=MainLayout__body]').then(el => {
const mainLayoutPaddingTop = parseInt(el.css('paddingTop'), 10);

cy.get('[data-test=ProposalsView__ProposalsList]')
.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=ProposalsView__ProposalsList__header--archive]').should(
'be.visible',
);

// list test
cy.get('[data-test=ProposalsView__ProposalsList--archive]')
.first()
.should('be.visible');
cy.get('[data-test=ProposalsView__ProposalsList--archive]')
.first()
.children()
.then(childrenArchive => {
const numberOfArchivedProposals = childrenArchive.length - 2; // archived proposals tiles - (header + divider)[2]
for (let i = 0; i < numberOfArchivedProposals; i++) {
cy.get(`[data-test=ProposalsView__ProposalsListItem--archive--${i}]`)
.first()
.scrollIntoView();
cy.window().then(window =>
window.scrollTo(0, window.scrollY - mainLayoutPaddingTop),
);
// list item test
cy.get(`[data-test=ProposalsView__ProposalsListItem--archive--${i}]`)
.first()
.should('be.visible')
.within(() => {
// rewards test
cy.get('[data-test=ProposalRewards]').should('be.visible');
});

if (numberOfArchivedProposals - 1) {
cy.get('[data-test=ProposalsView__ProposalsList--archive]')
.first()
.should('have.length', 1);
}

cy.get(`[data-test=ProposalsView__ProposalsListItem--archive--${i}]`)
.first()
.invoke('data', 'address')
.then(address => {
cy.get(`[data-test=ProposalsView__ProposalsListItem--archive--${i}]`)
.first()
.invoke('data', 'epoch')
.then(epoch => {
cy.get(`[data-test=ProposalsView__ProposalsListItem--archive--${i}]`)
.first()
.click();
checkLocationWithLoader(
`${ROOT_ROUTES.proposal.absolute}/${epoch}/${address}`,
);
cy.go('back');
checkLocationWithLoader(ROOT_ROUTES.proposals.absolute);
});
});
}
});
});
});
});
});
});
9 changes: 9 additions & 0 deletions client/cypress/support/e2e.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference types="cypress" />

declare namespace Cypress {
interface ApplicationWindow {
// Importing QueryClient breaks <reference types="cypress" /> making these types not visible.
clientReactQuery: any;
mutateAsyncMoveEpoch: () => Promise<void>;
}
}
69 changes: 0 additions & 69 deletions client/cypress/utils/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import clientReactQuery from 'src/api/clients/client-react-query';
import { wagmiConfig } from 'src/api/clients/client-wagmi';
import { QUERY_KEYS } from 'src/api/queryKeys';
import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs';
import { readContractEpochs } from 'src/hooks/contracts/readContracts';

import Chainable = Cypress.Chainable;

Expand Down Expand Up @@ -33,71 +29,6 @@ export const mockCoinPricesServer = (): Chainable<any> => {
statusCode: 200,
});
};
// How to use moveToNextEpoch method?
// Example test below
// it('changes epoch', () => {
// cy.wrap(null, { timeout: 60000 }).then(() => {
// return moveToNextEpoch().then(isEpochChanged => {
// expect(isEpochChanged).to.be.true;
// });
// });
// });

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const moveToNextEpoch = () =>
new Cypress.Promise(async (resolve, reject) => {
const currentEpochPromise = clientReactQuery.fetchQuery({
queryFn: () =>
readContractEpochs({
functionName: 'getCurrentEpoch',
publicClient: wagmiConfig.publicClient,
}),
queryKey: QUERY_KEYS.currentEpoch,
});

const blockPromise = wagmiConfig.publicClient.getBlock();

const currentEpochEndPromise = await clientReactQuery.fetchQuery({
queryFn: () =>
readContractEpochs({
functionName: 'getCurrentEpochEnd',
publicClient: wagmiConfig.publicClient,
}),
queryKey: QUERY_KEYS.currentEpochEnd,
});

const [currentEpochEnd, block, currentEpoch] = await Promise.all([
currentEpochEndPromise,
blockPromise,
currentEpochPromise,
]);

if (currentEpoch === undefined || block === undefined || currentEpoch === undefined) {
reject('Undefined data');
}

const blockTimestamp = Number(block.timestamp);
const currentEpochEndTimestamp = Number(currentEpochEnd);

const timeToIncrease = currentEpochEndTimestamp - blockTimestamp + 10; // [s]
await wagmiConfig.publicClient.request({
method: 'evm_increaseTime' as any,
params: [timeToIncrease] as any,
});
await wagmiConfig.publicClient.request({ method: 'evm_mine' as any, params: [] as any });

const currentEpochAfter = await clientReactQuery.fetchQuery({
queryFn: () =>
readContractEpochs({
functionName: 'getCurrentEpoch',
publicClient: wagmiConfig.publicClient,
}),
queryKey: QUERY_KEYS.currentEpoch,
});

// isEpochChanged
resolve(Number(currentEpoch) + 1 === Number(currentEpochAfter));
});

export const connectWallet = (
isTOSAccepted: boolean,
Expand Down
7 changes: 5 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { ReactElement, useState, Fragment } from 'react';

import 'react-toastify/dist/ReactToastify.css';

import AppLoader from 'components/shared/AppLoader';
import ModalOnboarding from 'components/shared/ModalOnboarding/ModalOnboarding';
import useAppConnectManager from 'hooks/helpers/useAppConnectManager';
import useAppIsLoading from 'hooks/helpers/useAppIsLoading';
import useAppPopulateState from 'hooks/helpers/useAppPopulateState';
import useCypressHelpers from 'hooks/helpers/useCypressHelpers';
import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode';
import useManageTransactionsPending from 'hooks/helpers/useManageTransactionsPending';
import RootRoutes from 'routes/RootRoutes/RootRoutes';
import 'react-toastify/dist/ReactToastify.css';

import 'styles/index.scss';
import 'i18n';
Expand All @@ -23,6 +23,9 @@ const App = (): ReactElement => {
const isLoading = useAppIsLoading(isFlushRequired);
const isProjectAdminMode = useIsProjectAdminMode();

// useCypressHelpers needs to be called after all the initial sets done above.
useCypressHelpers();

if (isLoading) {
return <AppLoader />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ const ProposalsList: FC<ProposalsListProps> = ({
{areCurrentEpochsProjectsHiddenOutsideAllocationWindow && isFirstArchive ? null : (
<div className={styles.divider} />
)}
<div className={styles.epochArchive}>
<div
className={styles.epochArchive}
data-test="ProposalsView__ProposalsList__header--archive"
>
{t('epochArchive', { epoch })}
<span
className={cx(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const ProposalsListItem: FC<ProposalsListItemProps> = ({
!isLoadingError && styles.isClickable,
isEpoch1 && styles.isEpoch1,
)}
data-address={address}
data-epoch={epoch}
data-test={dataTest}
onClick={
isLoadingError
Expand Down
1 change: 1 addition & 0 deletions client/src/components/shared/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ const Layout: FC<LayoutProps> = ({
!!navigationBottomSuffix && styles.isNavigationBottomSuffix,
classNameBody,
)}
data-test="MainLayout__body"
>
{isLoading ? <Loader dataTest="MainLayout__Loader" /> : children}
</div>
Expand Down
27 changes: 27 additions & 0 deletions client/src/hooks/helpers/useCypressHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useEffect } from 'react';

import useCypressMoveEpoch from 'hooks/mutations/useCypressMoveEpoch';

export default function useCypressHelpers(): void {
const { mutateAsync: mutateAsyncMoveEpoch } = useCypressMoveEpoch();

useEffect(() => {
/**
* In Cypress E2E tests some exclusive interactions with contracts are required.
* Currently, it's moving the epoch to the next one via JSON RPC API.
*
* These need to be exposed from within the application instead of being called from Cypress itself.
* Numerous attempts were made to call contracts via wagmiConfig.publicClient from Cypress,
* all of which were unsuccessful (1).
*
* The only working solution for the moment is exposing these calls from within the applicaiton.
*
* (1) History of commits here: https://github.com/golemfoundation/octant/pull/13.
*/
if (window.Cypress) {
// @ts-expect-error Left for debug purposes.
window.mutateAsyncMoveEpoch = mutateAsyncMoveEpoch;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}
78 changes: 78 additions & 0 deletions client/src/hooks/mutations/useCypressMoveEpoch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query';
import { useConfig } from 'wagmi';

import { QUERY_KEYS } from 'api/queryKeys';
import { readContractEpochs } from 'hooks/contracts/readContracts';

export default function useCypressMoveEpoch(): UseMutationResult<boolean, unknown, bigint> {
const queryClient = useQueryClient();
const wagmiConfig = useConfig();

return useMutation({
mutationFn: () => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (!window.Cypress) {
reject(new Error('useCypressMoveEpoch was called outside Cypress.'));
}

const currentEpochPromise = queryClient.fetchQuery({
queryFn: () =>
readContractEpochs({
functionName: 'getCurrentEpoch',
publicClient: wagmiConfig.publicClient,
}),
queryKey: QUERY_KEYS.currentEpoch,
});

const blockPromise = wagmiConfig.publicClient.getBlock();

const currentEpochEndPromise = queryClient.fetchQuery({
queryFn: () =>
readContractEpochs({
functionName: 'getCurrentEpochEnd',
publicClient: wagmiConfig.publicClient,
}),
queryKey: QUERY_KEYS.currentEpochEnd,
});

const [block, currentEpochEnd, currentEpoch] = await Promise.all([
blockPromise,
currentEpochEndPromise,
currentEpochPromise,
]);

if (block === undefined || currentEpoch === undefined || currentEpochEnd === undefined) {
// eslint-disable-next-line prefer-promise-reject-errors
reject(
new Error(
'useCypressMoveEpoch fetched undefined block or currentEpoch or currentEpochEnd.',
),
);
}

const blockTimestamp = Number(block.timestamp);
const currentEpochEndTimestamp = Number(currentEpochEnd);

const timeToIncrease = currentEpochEndTimestamp - blockTimestamp + 10; // [s]
await wagmiConfig.publicClient.request({
method: 'evm_increaseTime' as any,
params: [timeToIncrease] as any,
});
await wagmiConfig.publicClient.request({ method: 'evm_mine' as any, params: [] as any });

const currentEpochAfter = await queryClient.fetchQuery({
queryFn: () =>
readContractEpochs({
functionName: 'getCurrentEpoch',
publicClient: wagmiConfig.publicClient,
}),
queryKey: QUERY_KEYS.currentEpoch,
});

// isEpochChanged
resolve(Number(currentEpoch) + 1 === Number(currentEpochAfter));
});
},
});
}

0 comments on commit 0c013b2

Please sign in to comment.