mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Add unit & integration testing
* Add Cypress component testing * Add SeedAddress component testing * Add Error component test * Add Search component test * Add code coverage instrumentation * Add BranchSelector component testing * Add coverage check to CI * Require node version 14 Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
name: check-components
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm ci
|
||||
- run: ./scripts/test
|
||||
shell: bash
|
||||
@@ -1,3 +1,7 @@
|
||||
build/
|
||||
node_modules/
|
||||
NOTES
|
||||
|
||||
# Coverage
|
||||
.nyc_output
|
||||
coverage
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"all": true,
|
||||
"extension": [
|
||||
".svelte",
|
||||
".ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"video": false,
|
||||
"componentFolder": "src",
|
||||
"testFiles": "**/*spec.{js,ts}",
|
||||
"pluginsFile": "cypress/plugins/index.cjs",
|
||||
"baseUrl": "http://localhost:3000"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { startDevServer } = require('@cypress/vite-dev-server');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (on, config) => {
|
||||
on('dev-server:start', (options) => {
|
||||
return startDevServer({
|
||||
options,
|
||||
viteConfig: {
|
||||
configFile: path.resolve(__dirname, '..', '..', 'vite.config.ts'),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
require('@cypress/code-coverage/task')(on, config);
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import '@cypress/code-coverage/support';
|
||||
import '@testing-library/cypress/add-commands';
|
||||
|
||||
export const styles = {
|
||||
cssFile: "public/index.css",
|
||||
style: `
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="wrapper">
|
||||
<div id="here" />
|
||||
</div>
|
||||
`
|
||||
};
|
||||
Generated
+5817
-5349
File diff suppressed because it is too large
Load Diff
+14
-6
@@ -5,20 +5,28 @@
|
||||
"serve": "vite preview",
|
||||
"build": "scripts/build",
|
||||
"check": "scripts/check",
|
||||
"test": "echo \"This template does not include a test runner by default.\" && exit 1"
|
||||
"test": "cypress run-ct"
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "1.0.0-next.10",
|
||||
"@tsconfig/svelte": "^1.0.10",
|
||||
"@types/node": "^15.12.2",
|
||||
"@cypress/code-coverage": "^3.9.12",
|
||||
"@cypress/vite-dev-server": "^2.2.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||
"@testing-library/cypress": "^8.0.2",
|
||||
"@tsconfig/svelte": "^1.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||
"@typescript-eslint/parser": "^4.26.1",
|
||||
"cypress": "^9.3.1",
|
||||
"cypress-svelte-unit-test": "git+https://github.com/flakolefluk/cypress-svelte-unit-test.git",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-plugin-svelte3": "^3.2.0",
|
||||
"svelte-check": "^1.5.2",
|
||||
"typescript": "^4.2.4",
|
||||
"vite": "^2.3.7",
|
||||
"vite": "^2.7.13",
|
||||
"vite-plugin-istanbul": "^2.4.0",
|
||||
"vite-plugin-rewrite-all": "^0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -44,7 +52,7 @@
|
||||
"multibase": "^4.0.4",
|
||||
"multihashes": "^4.0.2",
|
||||
"pure-svg-code": "^1.0.6",
|
||||
"svelte": "^3.32.3",
|
||||
"svelte": "^3.44.3",
|
||||
"svelte-preprocess": "^4.7.2",
|
||||
"svelte-routing": "^1.6.0"
|
||||
}
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
# For now we only check the Svelte files.
|
||||
|
||||
npm test
|
||||
npx nyc report
|
||||
@@ -0,0 +1,42 @@
|
||||
import Error from "./Error.svelte";
|
||||
import { mount } from "cypress-svelte-unit-test";
|
||||
import { Failure } from '@app/error';
|
||||
import { styles } from "@test/support/index";
|
||||
|
||||
describe('Error', function () {
|
||||
it("Open Error modal with default props", () => {
|
||||
mount(Error, { props: {
|
||||
subtitle: "Subtitle of Modal",
|
||||
error: {
|
||||
type: Failure.InsufficientBalance,
|
||||
txHash: "0x8b678e51f970c5307bf45a8bcea373b597f9acbcea5c5ba784a1d383361a89d1",
|
||||
message: "Not enough RAD"
|
||||
}
|
||||
}
|
||||
}, styles);
|
||||
cy.get("[slot=title]").should("have.text", " Error");
|
||||
cy.get("[slot=subtitle]").should("have.text", "Subtitle of Modal");
|
||||
cy.get("[slot=body]").should("have.text", "Error: Not enough RAD");
|
||||
cy.get("button").should("have.text", "Back");
|
||||
});
|
||||
|
||||
it("Open Error modal with custom message", () => {
|
||||
mount(Error, { props: {
|
||||
subtitle: "Subtitle of Modal",
|
||||
message: "Error message to check for",
|
||||
} }, styles);
|
||||
cy.get("[slot=subtitle]").should("have.text", "Subtitle of Modal");
|
||||
cy.get("[slot=body]").should("have.text", "Error: Error message to check for");
|
||||
cy.get("button").should("have.text", "Back");
|
||||
});
|
||||
|
||||
it("Check floating modal changes button label to Close", () => {
|
||||
mount(Error, { props: {
|
||||
title: "Title of Modal",
|
||||
subtitle: "Subtitle of Modal",
|
||||
message: "Error message to check for",
|
||||
floating: true
|
||||
} }, styles);
|
||||
cy.get("button").should("have.text", "Close");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import Search from "./Search.svelte";
|
||||
import { mount } from "cypress-svelte-unit-test";
|
||||
import { styles } from "@test/support/index";
|
||||
|
||||
describe('Search', function () {
|
||||
it("Renders correctly", () => {
|
||||
mount(Search, {}, styles);
|
||||
cy.get("input").should("exist");
|
||||
cy.get("input").should("have.attr", "placeholder", "Search a name or address...");
|
||||
});
|
||||
|
||||
it("Search input displays and navigates correctly", () => {
|
||||
mount(Search, {}, styles);
|
||||
cy.get("input").type("cloudhead{enter}");
|
||||
cy.get("input").should("have.value", "cloudhead");
|
||||
cy.url().should("contain", "/resolver/query?q=cloudhead");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import SeedAddress from "./SeedAddress.svelte";
|
||||
import { mount } from "cypress-svelte-unit-test";
|
||||
import { styles } from "@test/support/index";
|
||||
|
||||
describe('SeedAddress', function () {
|
||||
it("Renders correctly", () => {
|
||||
mount(SeedAddress, {
|
||||
props: {
|
||||
id: "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1",
|
||||
host: "seed.cloudhead.io",
|
||||
port: 8776
|
||||
}
|
||||
}, styles);
|
||||
cy.findByText("hydkkk…coygh1@seed.cloudhead.io").should("exist").should("have.attr", "href", "/seeds/seed.cloudhead.io");
|
||||
cy.findByText(":8776").should("exist");
|
||||
cy.findByText("Copy").should("exist");
|
||||
});
|
||||
|
||||
it("Copies seed address correctly", () => {
|
||||
cy.findByText("Copy").click().should("exist");
|
||||
cy.findByText("Copy ✓").should("have.attr", "disabled");
|
||||
// We invoke the cy.window here since doing the clipboard action in the runner window, would throw an error
|
||||
cy.window().its("navigator.clipboard").invoke("readText").should("equal", "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1@seed.cloudhead.io:8776");
|
||||
cy.wait(4000);
|
||||
cy.findByText("Copy").click().should("exist").should("not.have.attr", "disabled");
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Config } from "./config";
|
||||
import { formatSeedAddress, formatSeedId, toClipboard } from "./utils";
|
||||
|
||||
export let config: Config;
|
||||
export let id: string;
|
||||
export let host: string;
|
||||
export let port: number;
|
||||
@@ -10,7 +8,7 @@
|
||||
let seedCopied = false;
|
||||
|
||||
const copySeed = (seedId: string, seedHost: string) => {
|
||||
return () => toClipboard(formatSeedAddress(seedId, seedHost, config)).then(() => {
|
||||
return () => toClipboard(formatSeedAddress(seedId, seedHost, port)).then(() => {
|
||||
seedCopied = true;
|
||||
setTimeout(() => {
|
||||
seedCopied = false;
|
||||
|
||||
@@ -251,7 +251,7 @@
|
||||
<!-- Seed Address -->
|
||||
{#if profile.seedId && profile.seedHost}
|
||||
<div class="label">Seed</div>
|
||||
<SeedAddress {config} id={profile.seedId} host={profile.seedHost} port={config.seed.link.port} />
|
||||
<SeedAddress id={profile.seedId} host={profile.seedHost} port={config.seed.link.port} />
|
||||
{/if}
|
||||
<!-- Name/Profile -->
|
||||
<div class="label">Profile</div>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import BranchSelector from "./BranchSelector.svelte";
|
||||
import { mount } from "cypress-svelte-unit-test";
|
||||
import { styles } from "@test/support/index";
|
||||
|
||||
const defaultProps = {
|
||||
project: {
|
||||
head: "e678629cd37c770c640a2cd997fc76303c815772",
|
||||
meta: {
|
||||
name: "nakamoto",
|
||||
description: "Privacy-preserving Bitcoin light-client implementation in Rust",
|
||||
defaultBranch: "master",
|
||||
maintainers: [
|
||||
"rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio"
|
||||
],
|
||||
delegates: [
|
||||
"hyn9diwfnytahjq8u3iw63h9jte1ydcatxax3saymwdxqu1zo645pe"
|
||||
]
|
||||
}
|
||||
},
|
||||
branches: [
|
||||
["master", "e678629cd37c770c640a2cd997fc76303c815772"]
|
||||
],
|
||||
revision: "e678629cd37c770c640a2cd997fc76303c815772",
|
||||
};
|
||||
|
||||
describe('BranchSelector', function () {
|
||||
it("Render with commit = head and branch listing", () => {
|
||||
mount(BranchSelector, {
|
||||
props: defaultProps
|
||||
}, styles
|
||||
);
|
||||
cy.findByText("master").should("exist");
|
||||
cy.get("div.commit > div.hash.desktop").should("have.text", "e678629");
|
||||
});
|
||||
|
||||
it("Test Branch selection", () => {
|
||||
// The viewport here is to simulate desktop behaviour
|
||||
cy.viewport(800, 300);
|
||||
mount(BranchSelector, {
|
||||
props: {
|
||||
...defaultProps, branches: [
|
||||
["master", "e678629cd37c770c640a2cd997fc76303c815772"],
|
||||
["feature-branch", "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5"],
|
||||
["xyz", "debf82ef3623ec11751a993bda85bac2ff1c6f00"],
|
||||
],
|
||||
branchesDropdown: true,
|
||||
}, callbacks: {
|
||||
revisionChanged: cy.stub().as("revisionChanged")
|
||||
}
|
||||
}, styles
|
||||
);
|
||||
cy.get("div.dropdown > div").first().click();
|
||||
cy.get("@revisionChanged")
|
||||
.should("be.calledOnce")
|
||||
.its("firstCall.args.0.detail")
|
||||
.should("equal", "feature-branch");
|
||||
});
|
||||
|
||||
it("Render with commit != head, passing a branch as rev and branch listing", () => {
|
||||
mount(BranchSelector, {
|
||||
props: {
|
||||
...defaultProps, branches: [
|
||||
["master", "e678629cd37c770c640a2cd997fc76303c815772"],
|
||||
["feature-branch", "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5"],
|
||||
["xyz", "debf82ef3623ec11751a993bda85bac2ff1c6f00"],
|
||||
["xyz", "8b18e8006849e36872cb928d87179e4e222fb689"]
|
||||
],
|
||||
revision: "feature-branch"
|
||||
}
|
||||
}, styles
|
||||
);
|
||||
cy.findByText("feature-branch").should("exist");
|
||||
cy.get("div.commit > div.hash.desktop").should("have.text", "29e8b7b");
|
||||
});
|
||||
|
||||
it("Render with commit != head passing a commit as rev and branch listing", () => {
|
||||
mount(BranchSelector, {
|
||||
props: {
|
||||
...defaultProps,
|
||||
revision: "debf82ef3623ec11751a993bda85bac2ff1c6f00"
|
||||
}
|
||||
}, styles
|
||||
);
|
||||
cy.get("div.commit > div.hash.desktop").should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
|
||||
});
|
||||
|
||||
it("Render with commit = head, without branch listing", () => {
|
||||
mount(BranchSelector, {
|
||||
props: {
|
||||
...defaultProps,
|
||||
revision: "e678629cd37c770c640a2cd997fc76303c815772",
|
||||
branches: [],
|
||||
}
|
||||
}, styles
|
||||
);
|
||||
cy.findByText("master").should("exist");
|
||||
cy.get("div.commit > div.hash").should("have.text", "e678629");
|
||||
});
|
||||
|
||||
it("Render without branch listing, commit != head", () => {
|
||||
mount(BranchSelector, {
|
||||
props: {
|
||||
...defaultProps,
|
||||
revision: "6b84e519d3c535879eb2b9ee8457bb70ca275a75",
|
||||
branches: [],
|
||||
}
|
||||
}, styles
|
||||
);
|
||||
cy.get("div.commit > div.hash.desktop").should("have.text", "6b84e519d3c535879eb2b9ee8457bb70ca275a75");
|
||||
});
|
||||
});
|
||||
@@ -96,7 +96,7 @@
|
||||
<!-- Seed Address -->
|
||||
<div class="label">Address</div>
|
||||
{#if info.version === "0.2.0" && info.host}
|
||||
<SeedAddress {config} id={info.id} host={info.host} port={config.seed.link.port} />
|
||||
<SeedAddress id={info.id} host={info.host} port={config.seed.link.port} />
|
||||
{:else}
|
||||
<div class="seed-address subtle">N/A</div>
|
||||
<div class="desktop" />
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<!-- Seed Address -->
|
||||
{#if profile.seedId && profile.seedHost}
|
||||
<div class="label">Seed</div>
|
||||
<SeedAddress {config} id={profile.seedId} host={profile.seedHost} port={config.seed.link.port} />
|
||||
<SeedAddress id={profile.seedId} host={profile.seedHost} port={config.seed.link.port} />
|
||||
{/if}
|
||||
<!-- Profile -->
|
||||
<div class="label">Profile</div>
|
||||
|
||||
+8
-7
@@ -72,8 +72,8 @@ export function isAddressEqual(left: string, right: string): boolean {
|
||||
return left.toLowerCase() === right.toLowerCase();
|
||||
}
|
||||
|
||||
export function formatSeedAddress(id: string, host: string, config: Config): string {
|
||||
return `${id}@${host}:${config.seed.link.port}`;
|
||||
export function formatSeedAddress(id: string, host: string, port: number): string {
|
||||
return `${id}@${host}:${port}`;
|
||||
}
|
||||
|
||||
export function formatSeedId(id: string): string {
|
||||
@@ -111,13 +111,14 @@ export function formatIpfsFile(ipfs: string | undefined): string | undefined {
|
||||
}
|
||||
|
||||
export function formatHash(hash: string): string {
|
||||
if (hash.length < 10) return hash;
|
||||
return hash.substring(0, 6)
|
||||
+ '...'
|
||||
+ hash.substring(hash.length - 4, hash.length);
|
||||
}
|
||||
|
||||
export function formatCommit(oid: string): string {
|
||||
return oid.substring(0,7);
|
||||
return oid.substring(0, 7);
|
||||
}
|
||||
|
||||
export function formatOrg(input: string, config: Config): string {
|
||||
@@ -130,7 +131,7 @@ export function formatOrg(input: string, config: Config): string {
|
||||
|
||||
export function capitalize(s: string): string {
|
||||
if (s === "") return s;
|
||||
return s[0].toUpperCase() + s.substr(1);
|
||||
return s[0].toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
// Takes a domain name, eg. 'cloudhead.radicle.eth' and
|
||||
@@ -196,7 +197,7 @@ export function isENSName(input: string, config: Config): boolean {
|
||||
return regEx.test(input);
|
||||
}
|
||||
|
||||
// Check whether the input is an Ethereum address.
|
||||
// Check whether the input is an checksummed or all lowercase Ethereum address.
|
||||
export function isAddress(input: string): boolean {
|
||||
return ethers.utils.isAddress(input);
|
||||
}
|
||||
@@ -424,7 +425,7 @@ export function isMarkdownPath(path: string): boolean {
|
||||
// Also accepts in dev env 0.0.0.0 as domain
|
||||
export function isDomain(input: string): boolean {
|
||||
return (/^[a-z][a-z0-9.-]+$/.test(input) && /\.[a-z]+$/.test(input))
|
||||
|| (!import.meta.env.PROD && /^0.0.0.0$/.test(input)) ;
|
||||
|| (! import.meta.env.PROD && /^0.0.0.0$/.test(input));
|
||||
}
|
||||
|
||||
// Propose a Gnosis Safe multi-sig transaction.
|
||||
@@ -504,7 +505,7 @@ export async function executeSignedSafeTransaction(
|
||||
...signedTx,
|
||||
gasPrice: Number(signedTx.gasPrice),
|
||||
data: signedTx.data
|
||||
} );
|
||||
});
|
||||
|
||||
signedTx.confirmations.forEach(confirmation => {
|
||||
const signature = new EthSignSignature(confirmation.owner, confirmation.signature);
|
||||
|
||||
+7
-3
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"include": [
|
||||
"src"
|
||||
"src",
|
||||
"cypress"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/*"
|
||||
@@ -11,7 +12,9 @@
|
||||
"module": "es2020",
|
||||
"types": [
|
||||
"svelte",
|
||||
"vite/client"
|
||||
"vite/client",
|
||||
"cypress",
|
||||
"@testing-library/cypress"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
@@ -22,7 +25,8 @@
|
||||
"importsNotUsedAsValues": "error",
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@app/*": ["./src/*"]
|
||||
"@app/*": ["./src/*"],
|
||||
"@test/*": ["./cypress/*"]
|
||||
}
|
||||
},
|
||||
"noEmit": true
|
||||
|
||||
+15
-2
@@ -1,18 +1,31 @@
|
||||
/// <reference types="vitest" />
|
||||
import path from 'path';
|
||||
import { UserConfig } from 'vite';
|
||||
import svelte from '@sveltejs/vite-plugin-svelte';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import rewriteAll from 'vite-plugin-rewrite-all';
|
||||
import IstanbulPlugin from 'vite-plugin-istanbul';
|
||||
|
||||
|
||||
const config: UserConfig = {
|
||||
optimizeDeps: {
|
||||
exclude: ['svelte-routing', '@pedrouid/environment', '@pedrouid/iso-crypto']
|
||||
},
|
||||
plugins: [svelte(), rewriteAll()],
|
||||
plugins: [
|
||||
svelte({ hot: !process.env.VITEST }),
|
||||
rewriteAll(),
|
||||
IstanbulPlugin({
|
||||
include: "src/**/*",
|
||||
exclude: ["node_modules"],
|
||||
extension: [".ts", ".svelte"],
|
||||
cypress: true
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
// This is needed for vite not to choke.
|
||||
"caip": path.resolve("./node_modules/caip/dist/umd/index.min.js"),
|
||||
'@app': path.resolve('./src'),
|
||||
'@test': path.resolve('./cypress'),
|
||||
// Polyfill for Node.js 'stream' library.
|
||||
'stream': path.resolve('./src/polyfills/stream.ts'),
|
||||
'typedarray-to-buffer': path.resolve('./src/polyfills/typedarray-to-buffer.js'),
|
||||
|
||||
Reference in New Issue
Block a user