Skip to content

Commit

Permalink
Add jest testing to CI (w3f#3576)
Browse files Browse the repository at this point in the history
* add jest testing with a couple samples for react components

* run jest tests on CI

* run test on pull request

* rename action

* fix naming

* rename once more

* step name

* step name take 2

* add testing coverage for all used RPC paths in docs

* debugging

* fix bug when retrieving constants

* all tests passing, still a couple warnings

* handle one more default case

* clean up

* bump timeout

Co-authored-by: Radha <[email protected]>
  • Loading branch information
alfarok and DrW3RK authored Jul 28, 2022
1 parent 39f8f46 commit d262b3d
Show file tree
Hide file tree
Showing 6 changed files with 2,742 additions and 1,453 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
14 changes: 14 additions & 0 deletions .github/workflows/jest-testing-coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Jest Testing Coverage
on:
pull_request:
branches:
- master
jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: yarn
- name: Run jest tests
run: yarn polkadot:test
27 changes: 15 additions & 12 deletions components/RPC-Connection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function RPC({ network, path, defaultValue, filter=undefined }) {

useEffect(() => {
// Set default as a fallback if anything fails
if(filter !== undefined) {
if (filter !== undefined) {
// Apply filter to default value to match formatting of RPC result
applyFilter(defaultValue.toString(), filter, network, setReturnValue)
} else {
Expand Down Expand Up @@ -51,10 +51,16 @@ function RPC({ network, path, defaultValue, filter=undefined }) {
} else {
// Otherwise attempt to connect to RPC
const connect = async () => {
await syncData(network, path, setReturnValue);
// Apply filter to retrieved value if a filter is provided
if(filter !== undefined) {
applyFilter(defaultValue.toString(), filter, network, setReturnValue)
const newValue = await syncData(network, path, setReturnValue);
if (newValue === undefined) {
// There was an issue with the request, use default
return;
} else if (filter !== undefined) {
// Apply filter to retrieved value if a filter is provided
applyFilter(newValue, filter, network, setReturnValue)
} else {
// Apply value as-is
setReturnValue(newValue);
}
}
try {
Expand Down Expand Up @@ -100,7 +106,9 @@ async function syncData(network, path, setReturnValue) {
// Build API call
const pathParameters = path.split(".");
pathParameters.forEach(param => {
api = api[param];
if (param in api){
api = api[param];
}
});

// Process constants and queries based on parameters prefix
Expand All @@ -116,10 +124,7 @@ async function syncData(network, path, setReturnValue) {
console.log(`Unknown path prefix (${pathParameters[0]}) in ${path}`);
}

// If no value was successfully retrieved use default
if (chainValue === undefined) { return; }

setReturnValue(chainValue);
return chainValue;
}
}

Expand Down Expand Up @@ -163,8 +168,6 @@ function applyFilter(value, filter, network, setReturnValue) {
} else {
value = `${(value / values[network].precision).toFixed(decimals)} ${values[network].symbol}`;
}


break;
case "blocksToDays":
value = (value * 6) / 86400;
Expand Down
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"scripts": {
"kusama:clean": "shx rm -rf node_modules/.cache; docusaurus clear kusama-guide",
"kusama:test": "jest --env=jsdom",
"kusama:start": "docusaurus start kusama-guide",
"kusama:build": "cross-env BUILDING=true docusaurus build kusama-guide; node kusama-guide/postprocess",
"kusama:publish-gh-pages": "cross-env PUBLISHING=true docusaurus deploy kusama-guide",
Expand All @@ -9,6 +10,7 @@
"kusama:i18n:copy-translations": "cp -R polkadot-wiki/i18n/zh-CN/docusaurus-plugin-content-docs/current kusama-guide/i18n/zh-CN/docusaurus-plugin-content-docs/current",
"kusama:pdf": "yarn pdf --initialDocURLs \"http://localhost:3000/docs/kusama-index\" --outputPDFFilename=\"kusama-guide/static/kusama-wiki.pdf\" --coverTitle=\"KUSAMA GUIDE\"",
"polkadot:clean": "shx rm -rf node_modules/.cache; docusaurus clear polkadot-wiki",
"polkadot:test": "jest --env=jsdom",
"polkadot:start": "docusaurus start polkadot-wiki",
"polkadot:build": "cross-env BUILDING=true docusaurus build polkadot-wiki; node polkadot-wiki/postprocess",
"polkadot:publish-gh-pages": "cross-env PUBLISHING=true docusaurus deploy polkadot-wiki",
Expand Down Expand Up @@ -40,14 +42,18 @@
"@docusaurus/preset-classic": "^2.0.0-rc.1",
"@octokit/rest": "^19.0.3",
"@polkadot/api": "^8.14.1",
"@polkadot/keyring": "^9.7.2",
"@polkadot/keyring": "10.1.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.0.4",
"axios": "^0.27.2",
"chalk": "^4.1.2",
"cloudflare": "^2.9.1",
"cross-env": "^7.0.3",
"fs-extra": "^10.1.0",
"husky": "^8.0.1",
"ipfs-http-client": "^57.0.3",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"katex": "^0.16.0",
"loud-rejection": "^2.2.0",
"mr-pdf": "^1.1.0",
Expand All @@ -65,8 +71,8 @@
"dependencies": {
"@algolia/client-search": "^4.14.2",
"@mdx-js/runtime": "^1.6.22",
"@polkadot/util": "^9.6.2",
"@polkadot/util-crypto": "^10.1.1",
"@polkadot/util": "10.1.1",
"@polkadot/util-crypto": "10.1.1",
"@polkadot/wasm-crypto": "^6.2.3",
"@polkadot/x-randomvalues": "*",
"@types/react": "^18.0.9",
Expand Down
128 changes: 128 additions & 0 deletions tests/rpc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from "react";
import { render, act, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import RPC from "../components/RPC-Connection";
import { ApiPromise, WsProvider } from "@polkadot/api";

// Set max test duration before failing (2 min)
jest.setTimeout(120000);

test("Retrieves and applies a 'const' RPC value", async () => {
render(<RPC network="polkadot" path="consts.balances.existentialDeposit" defaultValue={0} />);
await waitFor(() => expect(screen.getByText("10000000000")).toBeInTheDocument(), { timeout: 2000 });
});

test("Retrieves and applies a 'query' RPC value", async () => {
render(<RPC network="polkadot" path="query.staking.minNominatorBond" defaultValue={0} filter="humanReadable"/>);
await waitFor(() => expect(screen.getByText("10 DOT")).toBeInTheDocument(), { timeout: 2000 });
});

test("RPC falls back to default", async () => {
render(<RPC network="polkadot" path="BAD.PATH" defaultValue={150} />);
await waitFor(() => expect(screen.getByText("150")).toBeInTheDocument(), { timeout: 2000 });
});

test("Human readable filter with integer value", async () => {
render(<RPC network="polkadot" path="BAD.PATH" defaultValue={50000000000} filter="humanReadable" />);
await waitFor(() => expect(screen.getByText("5 DOT")).toBeInTheDocument(), { timeout: 2000 });
});

test("Human readable filter with float value", async () => {
render(<RPC network="polkadot" path="BAD.PATH" defaultValue={202580000000} filter="humanReadable" />);
await waitFor(() => expect(screen.getByText("20.258 DOT")).toBeInTheDocument(), { timeout: 2000 });
});

// This test takes about a minute to execute synchronously
test("All leveraged RPC paths are valid", async () => {
const paths = [
// Polkadot & Kusama
{ networks: ["polkadot", "kusama"], path: "query.staking.validatorCount" },
{ networks: ["polkadot", "kusama"], path: "consts.staking.maxNominatorRewardedPerValidator" },
{ networks: ["polkadot", "kusama"], path: "consts.identity.basicDeposit" },
{ networks: ["polkadot", "kusama"], path: "consts.identity.subAccountDeposit" },
{ networks: ["polkadot", "kusama"], path: "consts.balances.existentialDeposit" },
{ networks: ["polkadot", "kusama"], path: "consts.identity.basicDeposit" },
{ networks: ["polkadot", "kusama"], path: "consts.crowdloan.minContribution" },
{ networks: ["polkadot", "kusama"], path: "consts.staking.maxNominations" },
{ networks: ["polkadot", "kusama"], path: "consts.democracy.voteLockingPeriod" },
{ networks: ["polkadot", "kusama"], path: "consts.identity.fieldDeposit" },
{ networks: ["polkadot", "kusama"], path: "consts.electionProviderMultiPhase.maxElectingVoters" },
{ networks: ["polkadot", "kusama"], path: "query.staking.maxNominatorsCount" },
{ networks: ["polkadot", "kusama"], path: "consts.proxy.proxyDepositBase" },
{ networks: ["polkadot", "kusama"], path: "consts.proxy.proxyDepositFactor" },
{ networks: ["polkadot", "kusama"], path: "query.staking.minNominatorBond" },
{ networks: ["polkadot", "kusama"], path: "query.staking.maxNominatorsCount" },
{ networks: ["polkadot", "kusama"], path: "consts.treasury.spendPeriod" },
{ networks: ["polkadot", "kusama"], path: "consts.treasury.proposalBondMinimum" },
{ networks: ["polkadot", "kusama"], path: "consts.treasury.proposalBondMaximum" },
{ networks: ["polkadot", "kusama"], path: "consts.tips.tipReportDepositBase" },
{ networks: ["polkadot", "kusama"], path: "consts.tips.tipFindersFee" },
// Statemine & Statemint
{ networks: ["statemine", "statemint"], path: "consts.assets.assetDeposit" },
{ networks: ["statemine", "statemint"], path: "consts.assets.metadataDepositBase" },
]

let attemptedConnections = 0;
let successfulResponses = 0;

for (let i = 0; i < paths.length; i++) {
const testObject = paths[i];
for (let j = 0; j < testObject.networks.length; j++) {
attemptedConnections += 1;
let chainValue = undefined;
try {
const network = testObject.networks[j];
const path = testObject.path;
let wsUrl = undefined;

switch (network) {
case "polkadot":
wsUrl = "wss://rpc.polkadot.io";
break;
case "kusama":
wsUrl = "wss://kusama-rpc.polkadot.io/";
break;
case "statemine":
wsUrl = "wss://statemine-rpc.polkadot.io/";
break;
case "statemint":
wsUrl = "wss://statemint-rpc.polkadot.io/";
break;
default:
fail("Unknown socket url provided, no connection made.");

}

const wsProvider = new WsProvider(wsUrl);
let api = await ApiPromise.create({ provider: wsProvider });

// Build API call
const pathParameters = path.split(".");
pathParameters.forEach(param => {
api = api[param];
});

// Process constants and queries based on parameters prefix
switch (pathParameters[0]) {
case "consts":
chainValue = api.toString();
break;
case "query":
chainValue = await api();
chainValue = chainValue.toString();
break;
default:
fail(`Unknown path prefix (${pathParameters[0]}) in ${path}`);
}
} catch (error) {
console.log(error);
}

// TODO - check chain value before marking successful response
if(chainValue !== undefined) { successfulResponses += 1; }
}
}

// All RPC paths should return valid responses
expect(attemptedConnections === successfulResponses).toEqual(true);
});
Loading

0 comments on commit d262b3d

Please sign in to comment.