test tweaks

This commit is contained in:
Matthias Kretschmann 2021-09-13 23:55:29 +02:00
parent 459044b620
commit 52659efa97
Signed by: m
GPG Key ID: 606EEEF3C479A91F
31 changed files with 569 additions and 165 deletions

View File

@ -1,3 +1,18 @@
{
"extends": ["prettier", "plugin:prettier/recommended", "next/core-web-vitals"]
"extends": [
"prettier",
"plugin:prettier/recommended",
"next/core-web-vitals"
],
"plugins": ["prettier", "testing-library"],
"overrides": [
// Only uses Testing Library lint rules in test files
{
"files": [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)"
],
"extends": ["plugin:testing-library/react"]
}
]
}

View File

@ -29,3 +29,17 @@ jobs:
- run: npm run build
env:
NEXT_PUBLIC_TYPEKIT_ID: ${{ secrets.NEXT_PUBLIC_TYPEKIT_ID }}
coverage:
runs-on: ubuntu-latest
needs: [test]
if: ${{ success() && github.actor != 'dependabot[bot]' }}
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
with:
name: coverage
- uses: paambaati/codeclimate-action@v2.7.5
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}

View File

@ -4,6 +4,8 @@
> [ipfs.kretschmann.io](https://ipfs.kretschmann.io)
[![CI](https://github.com/kremalicious/ipfs/actions/workflows/ci.yml/badge.svg)](https://github.com/kremalicious/ipfs/actions/workflows/ci.yml)
[![Maintainability](https://api.codeclimate.com/v1/badges/c43bcf37192f95f3a154/maintainability)](https://codeclimate.com/github/kremalicious/ipfs/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/c43bcf37192f95f3a154/test_coverage)](https://codeclimate.com/github/kremalicious/ipfs/test_coverage)
This repo holds a React app built with [Next.js](https://nextjs.org) serving as the frontpage of [ipfs.kretschmann.io](https://ipfs.kretschmann.io) from where you can add files to IPFS via drag and drop.

View File

@ -1,3 +0,0 @@
module.exports = {
presets: ['next/babel', '@babel/preset-typescript']
}

View File

@ -1,22 +0,0 @@
module.exports = {
rootDir: '../',
transform: {
'^.+\\.[jt]sx?$': ['babel-jest', { configFile: './jest/babel.config.js' }]
},
moduleNameMapper: {
'.+\\.(css|styl|less|sass|scss)$': '<rootDir>/jest/__mocks__/styleMock.js',
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/jest/__mocks__/fileMock.js',
'\\.svg': '<rootDir>/jest/__mocks__/svgrMock.js'
},
testPathIgnorePatterns: [
'<rootDir>/.next',
'<rootDir>/node_modules',
'<rootDir>/build',
'<rootDir>/coverage'
],
setupFilesAfterEnv: ['<rootDir>/jest/setup.ts'],
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/@types/**/*'],
collectCoverage: true,
testEnvironment: 'jsdom'
}

294
package-lock.json generated
View File

@ -30,8 +30,9 @@
"eslint": "^7.32.0",
"eslint-config-next": "11.1.2",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-testing-library": "^4.12.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.1.1",
"prettier": "^2.4.0",
"typescript": "^4.4.3"
@ -3532,6 +3533,12 @@
"pretty-format": "^27.0.0"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -3617,6 +3624,146 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
"dev": true
},
"node_modules/@typescript-eslint/experimental-utils": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz",
"integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.31.1",
"@typescript-eslint/types": "4.31.1",
"@typescript-eslint/typescript-estree": "4.31.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "*"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz",
"integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "4.31.1",
"@typescript-eslint/visitor-keys": "4.31.1"
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz",
"integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==",
"dev": true,
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz",
"integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "4.31.1",
"@typescript-eslint/visitor-keys": "4.31.1",
"debug": "^4.3.1",
"globby": "^11.0.3",
"is-glob": "^4.0.1",
"semver": "^7.3.5",
"tsutils": "^3.21.0"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz",
"integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "4.31.1",
"eslint-visitor-keys": "^2.0.0"
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^2.0.0"
},
"engines": {
"node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": ">=5"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "4.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz",
@ -6120,6 +6267,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/eslint-plugin-testing-library": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-4.12.2.tgz",
"integrity": "sha512-lcysxW7I3oXmZIyFP2N1dFMPfuB3qyl3iDcDboCl7U+TAaeG9OOycNSkZUAsDWQLsfEtXYmRbwTH98qSgk6peA==",
"dev": true,
"dependencies": {
"@typescript-eslint/experimental-utils": "^4.30.0"
},
"engines": {
"node": "^10.12.0 || >=12.0.0",
"npm": ">=6"
},
"peerDependencies": {
"eslint": "^7.5.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -6889,6 +7052,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/harmony-reflect": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
"integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -7091,6 +7260,18 @@
"node": ">=0.10.0"
}
},
"node_modules/identity-obj-proxy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
"integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=",
"dev": true,
"dependencies": {
"harmony-reflect": "^1.4.6"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -16122,6 +16303,12 @@
"pretty-format": "^27.0.0"
}
},
"@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -16207,6 +16394,87 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
"dev": true
},
"@typescript-eslint/experimental-utils": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz",
"integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.31.1",
"@typescript-eslint/types": "4.31.1",
"@typescript-eslint/typescript-estree": "4.31.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz",
"integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.31.1",
"@typescript-eslint/visitor-keys": "4.31.1"
}
},
"@typescript-eslint/types": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz",
"integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz",
"integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.31.1",
"@typescript-eslint/visitor-keys": "4.31.1",
"debug": "^4.3.1",
"globby": "^11.0.3",
"is-glob": "^4.0.1",
"semver": "^7.3.5",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz",
"integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.31.1",
"eslint-visitor-keys": "^2.0.0"
}
},
"eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^2.0.0"
}
},
"eslint-visitor-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@typescript-eslint/parser": {
"version": "4.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz",
@ -18249,6 +18517,15 @@
"dev": true,
"requires": {}
},
"eslint-plugin-testing-library": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-4.12.2.tgz",
"integrity": "sha512-lcysxW7I3oXmZIyFP2N1dFMPfuB3qyl3iDcDboCl7U+TAaeG9OOycNSkZUAsDWQLsfEtXYmRbwTH98qSgk6peA==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "^4.30.0"
}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -18710,6 +18987,12 @@
"duplexer": "^0.1.2"
}
},
"harmony-reflect": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
"integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -18855,6 +19138,15 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"identity-obj-proxy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
"integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=",
"dev": true,
"requires": {
"harmony-reflect": "^1.4.6"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",

View File

@ -6,8 +6,8 @@
"start": "next dev",
"build": "next build",
"serve": "next start",
"test": "npm run lint && npm run type-check && NODE_ENV=test jest -c jest/jest.config.js",
"test:watch": "npm run lint && npm run type-check && NODE_ENV=test jest -c jest/jest.config.js --watch",
"test": "npm run lint && npm run type-check && NODE_ENV=test jest -c test/__config__/jest.config.js",
"test:watch": "npm run lint && npm run type-check && NODE_ENV=test jest -c test/__config__/jest.config.js --watch",
"lint": "next lint",
"type-check": "tsc --noEmit",
"format": "prettier --ignore-path .gitignore '**/*.{css,yml,js,jsx,ts,tsx,json}' --write",
@ -38,6 +38,8 @@
"eslint-config-next": "11.1.2",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-testing-library": "^4.12.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.1.1",
"prettier": "^2.4.0",
"typescript": "^4.4.3"

View File

@ -1,26 +0,0 @@
.add {
width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
}
.files {
text-align: left;
}
.title {
margin-bottom: calc(var(--spacer) / 4);
font-size: var(--font-size-base);
}
.link {
color: var(--color-text);
font-size: var(--font-size-small);
margin-bottom: calc(var(--spacer) / 4);
display: inline-block;
}
.link:hover {
color: var(--brand-cyan);
}

View File

@ -1,99 +0,0 @@
import React, { useState, ReactElement } from 'react'
import { ipfsGateway } from '../../site.config'
import Dropzone from './Dropzone'
import styles from './Add.module.css'
import Loader from './Loader'
import useIpfsApi from '../hooks/use-ipfs-api'
import { FileIpfs } from '../@types/ipfs'
import { FileWithPath } from 'react-dropzone'
function FileLink({
file,
cidFolder,
cid
}: {
file: FileIpfs
cidFolder: string
cid?: string
}) {
const title = cid ? `ipfs://${cid}` : `ipfs://${cidFolder}/${file.path}`
const href = cid
? `${ipfsGateway}/ipfs/${cid}`
: `${ipfsGateway}/ipfs/${cidFolder}/${file.path}`
return cidFolder !== cid ? (
<a
className={styles.link}
target="_blank"
rel="noopener noreferrer"
href={href}
>
{title}
</a>
) : null
}
function Files({ files }: { files: FileIpfs[] }) {
const cidFolder = files.filter((file) => file.path === '')[0].cid.toString()
return (
<ul className={styles.files}>
{files?.map((file) => (
<li key={file.path}>
<h3 className={styles.title}>
{file.path === '' ? 'Folder with all files' : file.path}
</h3>
<FileLink
file={file}
cidFolder={cidFolder}
cid={file.cid.toString()}
/>
<p>
<FileLink file={file} cidFolder={cidFolder} />
</p>
</li>
))}
</ul>
)
}
export default function Add(): ReactElement {
const { ipfs, isIpfsReady, ipfsError, addFiles } = useIpfsApi()
const [files, setFiles] = useState<FileIpfs[]>()
const [loading, setLoading] = useState(false)
const [message] = useState()
const [error, setError] = useState<string>()
async function handleOnDrop(acceptedFiles: FileWithPath[]): Promise<void> {
if (!acceptedFiles || !ipfs || !isIpfsReady) return
setLoading(true)
setError(undefined)
try {
const addedFiles = await addFiles(acceptedFiles)
setFiles(addedFiles)
setLoading(false)
} catch (error) {
setError(`Adding to IPFS failed: ${(error as Error).message}`)
return
}
}
return (
<div className={styles.add}>
{loading ? (
<Loader message={message} />
) : files?.length ? (
<Files files={files} />
) : (
<Dropzone
multiple
handleOnDrop={handleOnDrop}
disabled={!isIpfsReady}
error={error || ipfsError}
/>
)}
</div>
)
}

View File

@ -0,0 +1,10 @@
.link {
color: var(--color-text);
font-size: var(--font-size-small);
margin-bottom: calc(var(--spacer) / 4);
display: inline-block;
}
.link:hover {
color: var(--brand-cyan);
}

View File

@ -0,0 +1,30 @@
import React from 'react'
import { ipfsGateway } from '../../../site.config'
import { FileIpfs } from '../../@types/ipfs'
import styles from './FileLink.module.css'
export default function FileLink({
file,
cidFolder,
cid
}: {
file: FileIpfs
cidFolder: string
cid?: string
}) {
const title = cid ? `ipfs://${cid}` : `ipfs://${cidFolder}/${file.path}`
const href = cid
? `${ipfsGateway}/ipfs/${cid}`
: `${ipfsGateway}/ipfs/${cidFolder}/${file.path}`
return cidFolder !== cid ? (
<a
className={styles.link}
target="_blank"
rel="noopener noreferrer"
href={href}
>
{title}
</a>
) : null
}

View File

@ -0,0 +1,8 @@
.files {
text-align: left;
}
.title {
margin-bottom: calc(var(--spacer) / 4);
font-size: var(--font-size-base);
}

View File

@ -0,0 +1,28 @@
import React from 'react'
import { FileIpfs } from '../../@types/ipfs'
import FileLink from './FileLink'
import styles from './Files.module.css'
export default function Files({ files }: { files: FileIpfs[] }) {
const cidFolder = files.filter((file) => file.path === '')[0].cid.toString()
return (
<ul className={styles.files}>
{files?.map((file) => (
<li key={file.path}>
<h3 className={styles.title}>
{file.path === '' ? 'Folder with all files' : file.path}
</h3>
<FileLink
file={file}
cidFolder={cidFolder}
cid={file.cid.toString()}
/>
<p>
<FileLink file={file} cidFolder={cidFolder} />
</p>
</li>
))}
</ul>
)
}

View File

@ -0,0 +1,6 @@
.add {
width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
}

View File

@ -0,0 +1,49 @@
import React, { useState, ReactElement } from 'react'
import Dropzone from './Dropzone'
import Loader from '../Loader'
import useIpfsApi from '../../hooks/use-ipfs-api'
import { FileIpfs } from '../../@types/ipfs'
import { FileWithPath } from 'react-dropzone'
import Files from './Files'
import styles from './index.module.css'
export default function Add(): ReactElement {
const { ipfs, isIpfsReady, ipfsError, addFiles } = useIpfsApi()
const [files, setFiles] = useState<FileIpfs[]>()
const [loading, setLoading] = useState(false)
const [message] = useState()
const [error, setError] = useState<string>()
async function handleOnDrop(acceptedFiles: FileWithPath[]): Promise<void> {
if (!acceptedFiles || !ipfs || !isIpfsReady) return
setLoading(true)
setError(undefined)
try {
const addedFiles = await addFiles(acceptedFiles)
setFiles(addedFiles)
setLoading(false)
} catch (error) {
setError(`Adding to IPFS failed: ${(error as Error).message}`)
return
}
}
return (
<div className={styles.add}>
{loading ? (
<Loader message={message} />
) : files?.length ? (
<Files files={files} />
) : (
<Dropzone
multiple
handleOnDrop={handleOnDrop}
disabled={!isIpfsReady}
error={error || ipfsError}
/>
)}
</div>
)
}

View File

@ -66,7 +66,7 @@ export default function useIpfsApi(): IpfsApiValue {
setIpfs(ipfs)
const version = await ipfs.version()
setVersion(version.version)
setIpfsReady(Boolean(ipfs && (await ipfs.id())))
setIpfsReady(ipfs?.isOnline())
} catch (e) {
setIpfsError(`IPFS connection error: ${(e as Error).message}`)
setIpfsReady(false)

View File

@ -19,9 +19,3 @@ export async function pingUrl(url: string): Promise<boolean> {
return false
}
}
export function parseHTML(str: string): HTMLCollection {
const tmp = document.implementation.createHTMLDocument()
tmp.body.innerHTML = str
return tmp.body.children
}

25
test/Files.test.tsx Normal file
View File

@ -0,0 +1,25 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import Files from '../src/components/Add/Files'
import { CID } from 'multiformats/cid'
const files = [
{
path: 'hello',
cid: { toString: () => 'xxx' } as CID,
size: 1000
},
{
path: '',
cid: { toString: () => 'xxx' } as CID,
size: 1000
}
]
describe('Files', () => {
it('renders without crashing', async () => {
render(<Files files={files} />)
const element = await screen.findByText('ipfs://xxx/')
expect(element).toBeInTheDocument()
})
})

View File

@ -1,6 +1,6 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import Layout from '../Layout'
import Layout from '../src/Layout'
describe('Layout', () => {
it('renders without crashing', () => {

View File

@ -1,6 +1,6 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import Loader from '../components/Loader'
import Loader from '../src/components/Loader'
describe('Loader', () => {
it('renders without crashing', async () => {

9
test/Typekit.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react'
import { render } from '@testing-library/react'
import Typekit from '../src/components/Typekit'
describe('Typekit', () => {
it('renders without crashing', async () => {
render(<Typekit />)
})
})

View File

@ -0,0 +1,27 @@
module.exports = {
rootDir: '../../',
transform: {
'^.+\\.[jt]sx?$': ['babel-jest', { presets: ['next/babel'] }]
},
transformIgnorePatterns: [
'/node_modules/',
'^.+\\.module\\.(css|sass|scss)$'
],
moduleNameMapper: {
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
'^.+\\.(css|sass|scss)$': '<rootDir>/test/__mocks__/styleMock.js',
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/test/__mocks__/fileMock.js',
'\\.svg': '<rootDir>/test/__mocks__/svgrMock.js'
},
testPathIgnorePatterns: [
'<rootDir>/.next',
'<rootDir>/node_modules',
'<rootDir>/build',
'<rootDir>/coverage'
],
setupFilesAfterEnv: ['<rootDir>/test/__config__/jest.setup.ts'],
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/@types/**/*'],
collectCoverage: true,
testEnvironment: '<rootDir>/test/__config__/jest.env.ts'
}

View File

@ -0,0 +1,18 @@
import Environment from 'jest-environment-jsdom'
/**
* A custom environment to set the TextEncoder required by ipfs-http-client.
*/
module.exports = class CustomTestEnvironment extends Environment {
async setup() {
await super.setup()
if (typeof this.global.TextEncoder === 'undefined') {
const { TextEncoder } = require('util')
this.global.TextEncoder = TextEncoder
}
if (typeof this.global.TextDecoder === 'undefined') {
const { TextDecoder } = require('util')
this.global.TextDecoder = TextDecoder
}
}
}

View File

@ -1,11 +1,14 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import Home from '../pages'
import Home from '../src/pages'
describe('Home', () => {
it('renders without crashing', async () => {
render(<Home />)
await screen.findAllByTitle('Online', undefined, { timeout: 10000 })
const online = await screen.findAllByTitle('Online', undefined, {
timeout: 10000
})
expect(screen.getByText('A public IPFS Gateway')).toBeInTheDocument()
expect(online).toBeDefined()
})
})

22
test/use-ipfs.test.tsx Normal file
View File

@ -0,0 +1,22 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import useIpfs from '../src/hooks/use-ipfs-api'
export default function TestComponent() {
const { version, isIpfsReady, ipfsError } = useIpfs()
return (
<div>
{isIpfsReady && <span>Ready</span>}
{version} - {ipfsError}
</div>
)
}
describe('use-ipfs', () => {
it('renders without crashing', async () => {
render(<TestComponent />)
const element = await screen.findByText('Ready')
expect(element).toBeInTheDocument()
})
})