Skip to content

Commit

Permalink
E2E tests (#68)
Browse files Browse the repository at this point in the history
* Add and configure cypress

* Add cypress namespace declaration

* Merge stashed changes

* Add `e2e` test

* Run eslint and prettier

* Fix minor problems causing build and test to fail

* Add cypress e2e test to CI

* Add cypress binary cache to CI

* Replace ci e2e with simple script call

* Fix ci syntax error

* Fix casing in e2e test

* Move e2e to separate job

* Add rest of the config. to the e2e_test job

* Add build

* Add requested changes
  • Loading branch information
7sne authored Jul 27, 2022
1 parent 8ffd676 commit f5a37ce
Show file tree
Hide file tree
Showing 13 changed files with 1,049 additions and 31 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/node_modules
/public
/styles
.eslintrc.js
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,18 @@ jobs:
- run: yarn test
- name: 'Build storybook'
run: yarn build-storybook
e2e_test:
strategy:
matrix:
node: ['14.x']
runs-on: ubuntu-latest

name: Run e2e tests
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- run: yarn --no-progress --non-interactive --frozen-lockfile
- run: yarn build
- run: yarn test:e2e
11 changes: 11 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
specPattern: 'test/e2e/**/*.cy.{ts,tsx}',
supportFile: false,
},
});
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"build": "next build",
"start": "next start",
"test": "mocha",
"cy:run": "cypress run",
"test:e2e": "yarn start & wait-on http://localhost:3000 & yarn cy:run",
"test:fix": "yarn lint:fix && yarn format:fix && yarn typecheck && yarn test",
"format": "prettier --config ./.prettierrc --ignore-path ./.prettierignore --check \"./**/*.{ts,tsx,js}\"",
"format:fix": "prettier --config ./.prettierrc --ignore-path ./.prettierignore --write \"./**/*.{ts,tsx,js}\"",
Expand Down Expand Up @@ -63,6 +65,8 @@
"@typescript-eslint/parser": "4.15.1",
"autoprefixer": "^10.4.0",
"babel-loader": "^8.2.4",
"cypress": "^10.3.0",
"cypress-fail-on-console-error": "^3.2.0",
"earljs": "0.2.2",
"eslint": "8.10.0",
"eslint-config-react-app": "^7.0.0",
Expand All @@ -79,7 +83,7 @@
"jsdom": "^19.0.0",
"jsdom-global": "^3.0.2",
"mocha": "^8.2.0",
"next-themes": "^0.0.14",
"next-themes": "^0.2.0",
"postcss": "^8.4.5",
"prettier": "2.7.1",
"prettier-plugin-tailwindcss": "^0.1.8",
Expand All @@ -88,6 +92,7 @@
"ts-essentials": "^9.2.0",
"ts-node": "^10.6.0",
"typescript": "4.6.2",
"wait-on": "^6.0.1",
"webpack": "^5"
}
}
5 changes: 3 additions & 2 deletions pages/calldata-decoder.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export default function CalldataDecoder(): ReactElement {
/>
<p
aria-label="encoded calldata error"
role="alert"
className="pt-1 text-right text-error"
>
{!encodedCalldata.isOk && encodedCalldata.errorMsg}
Expand Down Expand Up @@ -293,7 +294,7 @@ export default function CalldataDecoder(): ReactElement {
Possible decoded calldata:
</p>
) : (
'No results found'
<p aria-label="no results found">No results found</p>
)
) : (
<p> Decoded output will appear here </p>
Expand Down Expand Up @@ -322,7 +323,7 @@ export default function CalldataDecoder(): ReactElement {
border border-gray-600 py-2 px-3 duration-200 hover:bg-gray-700
hover:outline active:bg-gray-800"
>
<b>{signatureHash}</b>
<b aria-label="signature hash">{signatureHash}</b>
</div>
</div>
)}
Expand Down
9 changes: 5 additions & 4 deletions pages/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ export default function Home(): ReactElement {
return (
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<title>Deth tools</title>
<meta
name="description"
content="A handy toolset for every ethereum developer"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="mx-auto max-w-6xl"></main>
<footer className="mx-auto mt-12 max-w-4xl"></footer>
</div>
);
}
1 change: 1 addition & 0 deletions pages/unix-epoch-utc-conversion.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expect } from 'earljs';

import { UnixTimestampFormat } from '../src/lib/convertUnixEpochToUtc';
import { utcUnits } from '../src/lib/convertUtcProperties';
import { currentEpochTime } from '../src/lib/currentEpochTime';
import UnixEpochUtcConversion from './unix-epoch-utc-conversion.page';

type Root = any;
Expand Down
8 changes: 1 addition & 7 deletions src/components/CurrentEpochTime.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { ReactElement, useEffect, useRef, useState } from 'react';

import { currentEpochTime } from '../lib/currentEpochTime';
import { CopyIcon } from './icons/CopyIcon';
import { OkIcon } from './icons/OkIcon';

Expand Down Expand Up @@ -62,10 +63,3 @@ export function CurrentEpochTime(): ReactElement {
</div>
);
}

// @internal
export const currentEpochTime = {
get(): number {
return Math.floor(new Date().getTime() / 1000);
},
};
6 changes: 6 additions & 0 deletions src/lib/currentEpochTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @internal
export const currentEpochTime = {
get(): number {
return Math.floor(new Date().getTime() / 1000);
},
};
144 changes: 144 additions & 0 deletions test/e2e/e2e.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/// <reference types="cypress" />

import sinon from 'sinon';
import failOnConsoleError, { consoleType } from 'cypress-fail-on-console-error';

import { getInputByLabel } from './e2e.helpers';
import { convertUnixEpochToUtc } from '../../src/lib/convertUnixEpochToUtc';
import { currentEpochTime } from '../../src/lib/currentEpochTime';

failOnConsoleError({
includeConsoleTypes: [consoleType.ERROR, consoleType.WARN, consoleType.INFO],
cypressLog: true,
});

describe('Calldata Decoder Page', () => {
afterEach(() => {
sinon.restore();
});

it('no errors written to the console in token-unit-conversion', () => {
cy.visit('http://localhost:3000/token-unit-conversion');

getInputByLabel('Decimals').should('have.value', '18').clear();
cy.get('[role="alert"]').contains(
'The decimals must be a number greater than 0',
);
getInputByLabel('Decimals').type('10');
getInputByLabel('Units')
.should('have.value', '')
.type('1')
.should('have.value', '1');
getInputByLabel('Base')
.should('have.value', '0.0000000001')
.clear()
.type('1337.22');
getInputByLabel('Units').should('have.value', '13372200000000');

getInputByLabel('Decimals').clear().type('@@%').type('6');
getInputByLabel('Units').type('@%%');
cy.get('[role="alert"]').contains(
"The value mustn't contain letters or any special signs except dot",
);
getInputByLabel('Units').clear().type('1000000.000000005');
getInputByLabel('Base').should('have.value', '1.000000000000005');
});

it('no errors written to the console in unix-epoch-utc-conversion', () => {
cy.visit('http://localhost:3000/unix-epoch-utc-conversion');

const currentEpoch = Math.floor(new Date().getTime() / 1000);
sinon.stub(currentEpochTime, 'get').returns(currentEpoch);
const currentEpochUtc = convertUnixEpochToUtc(String(currentEpoch));

getInputByLabel('UnixEpoch')
.should('have.value', '')
.type(String(currentEpoch));
cy.get(`[aria-label="convert unix epoch"]`).click();
cy.get(`[aria-label="utc date"]`).should(
'contain',
String(currentEpochUtc?.utcDate),
);

getInputByLabel('sec').should('have.value', '').type('10');
getInputByLabel('min').should('have.value', '').type('10');
getInputByLabel('hr').should('have.value', '').type('10');
getInputByLabel('day').should('have.value', '').type('10');
getInputByLabel('mon').should('have.value', '').type('10');
getInputByLabel('year').should('have.value', '').type('2021');
cy.get(`[aria-label="convert utc"]`).click();
cy.get(`[aria-label="unix epoch"]`).should('contain', '1633860610000');
});

it('no errors written to the console in base-conversion', () => {
cy.visit('http://localhost:3000/base-conversion');

getInputByLabel('Binary').type('10101000010011110000001010001');
getInputByLabel('Octal').should('have.value', '02502360121');
getInputByLabel('Decimal').should('have.value', '352968785');
getInputByLabel('Hexadecimal').should('have.value', '0x1509e051');

getInputByLabel('Binary').clear().type('%%');
cy.get('[role="alert"]').contains(
'The value must be a valid, binary number',
);
getInputByLabel('Octal').clear().type('1234');
cy.get('[role="alert"]').contains(
'The value must be a valid, octal number, 0-prefix is required',
);
});

it('no errors written to the console in calldata-decoder', () => {
cy.visit('http://localhost:3000/calldata-decoder');

getInputByLabel('Calldata').should('have.value', '').type('10');
cy.get('[role="alert"]').contains(
'The value must be a hexadecimal number, 0x-prefix is required',
);
getInputByLabel('Calldata').clear().type('0x1234');
cy.get('[role="tab"]').contains('ABI').click();
cy.get('[aria-label="text area for abi"]').type('function', {
parseSpecialCharSequences: false,
force: true,
});
cy.get('[aria-label="raw abi error"]').contains(
'invalid function signature',
);

getInputByLabel('Calldata')
.should('have.value', '0x1234')
.clear()
.type(
'0x6a947f74000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000021c',
);
cy.get('[role="tab"]').contains('ABI').click();
cy.get('[aria-label="text area for abi"]')
.clear()
.type(
'[{"inputs":[{"components":[{"components":[{"internalType":"uint256","name":"parameter1","type":"uint256"},{"components":[{"internalType":"uint256","name":"parameter1","type":"uint256"}],"internalType":"struct MyStruct1","name":"parameter2","type":"tuple"}],"internalType":"struct MyStruct2","name":"parameter3","type":"tuple"},{"components":[{"internalType":"uint256","name":"parameter1","type":"uint256"},{"components":[{"internalType":"uint256","name":"parameter1","type":"uint256"}],"internalType":"struct MyStruct1","name":"parameter2","type":"tuple"}],"internalType":"struct MyStruct2","name":"parameter4","type":"tuple"}],"internalType":"struct MyType2","name":"myType","type":"tuple"},{"internalType":"uint256","name":"myUint","type":"uint256"}],"name":"store","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]',
{ parseSpecialCharSequences: false, force: true },
);
cy.get('button').contains('Decode').click();
cy.get('[aria-label="signature hash"]').contains('0x6a947f74');
const correctDecodedValues = ['123', '123', '123', '123', '540'];
cy.get('[aria-label="decoded value"]').each((item, index) =>
cy.wrap(item).contains(correctDecodedValues[index]),
);
});

it('no errors written to the console in eth-unit-conversion', () => {
cy.visit('http://localhost:3000/eth-unit-conversion');

getInputByLabel('WEI')
.should('have.value', '')
.type('1')
.should('have.value', '1');
getInputByLabel('GWEI').should('have.value', '0.000000001');
getInputByLabel('ETH').should('have.value', '0.000000000000000001');
getInputByLabel('ETH').clear();
cy.get('[role="alert"]').contains('The value must be longer than 1 digit');
getInputByLabel('ETH').type('2137.1337.1000');
getInputByLabel('GWEI').should('have.value', '2137133700000');
getInputByLabel('WEI').should('have.value', '2137133700000000000000');
});
});
10 changes: 10 additions & 0 deletions test/e2e/e2e.helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function getInputByLabel(
label: string,
): Cypress.Chainable<string | undefined> {
return cy
.contains('label', label)
.invoke('attr', 'for')
.then((id) => {
cy.get('#' + id);
});
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"compilerOptions": {
"target": "ES2019",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["cypress", "node"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
Expand Down
Loading

0 comments on commit f5a37ce

Please sign in to comment.