Test setup tweaks (#1415)

* unused package cleanup

* make storybook use webpack 5

* see https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#webpack-5

* bump react, cleanup

* button story tweaks

* add Alert stories

* bump Jest to v28.1.0

* try original storyshots initialization

* split up build & test CI jobs

* stop testing Node.js v14

* set jest coverage flag

* downgrade paambaati/codeclimate-action again

* move  jest config files, remove coverageReporter override

* collect coverage from `src/` only

* another paambaati/codeclimate-action bump test

* create additional button markup test

* downgrade paambaati/codeclimate-action again

* more downgrade

* render default button without optional style prop

* ignore some folders for Jest

* full coverage for Alert

* more package updates

* add eslint-plugin-testing-library & eslint-plugin-jest-dom

* bump ESLint packages, follow new rules

* start storybook in quiet mode

* update docs

* test storybook build as part of CI

* more testing docs clarification

* add jest:watch command

* add body background colors switch in toolbar

* TypeScript voodoo

* test codeclimate-action@v2.7.3 for default coverageCommand

* downgrade codeclimate-action and running in debug mode

* make coverage artifacts OS agnostic

* subgraph typings as artifact for coverage job

* disable coverage sending job for now

Co-authored-by: Enzo Vezzaro <enzo-vezzaro@live.it>
This commit is contained in:
Matthias Kretschmann 2022-05-12 11:35:07 +01:00 committed by GitHub
parent a2bf3b2a1c
commit 5f3ee32ca2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 14878 additions and 6114 deletions

View File

@ -30,9 +30,16 @@
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended", "plugin:prettier/recommended",
"plugin:react-hooks/recommended" "plugin:react-hooks/recommended",
"plugin:testing-library/react",
"plugin:jest-dom/recommended"
],
"plugins": [
"@typescript-eslint",
"prettier",
"testing-library",
"jest-dom"
], ],
"plugins": ["@typescript-eslint", "prettier"],
"rules": { "rules": {
"react/prop-types": "off", "react/prop-types": "off",
"react/no-unused-prop-types": "off", "react/no-unused-prop-types": "off",

View File

@ -12,14 +12,14 @@ on:
- '**' - '**'
jobs: jobs:
test: build:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
node: ['16', '14'] node: ['16']
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -38,24 +38,79 @@ jobs:
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
- run: npm test
- name: Upload coverage test:
uses: actions/upload-artifact@v2 runs-on: ${{ matrix.os }}
with:
name: coverage strategy:
path: coverage/ fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ['16']
coverage:
runs-on: ubuntu-latest
needs: [test]
if: ${{ success() && github.actor != 'dependabot[bot]' }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/download-artifact@v2 - uses: actions/setup-node@v2
with: with:
name: coverage node-version: ${{ matrix.node }}
- uses: paambaati/codeclimate-action@v2.7.1 # using 2.7.1 for issue: https://github.com/paambaati/codeclimate-action/issues/316 - name: Cache node_modules
uses: actions/cache@v2
env: env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-${{ matrix.node }}-test-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-${{ matrix.node }}-test-${{ env.cache-name }}-
- run: npm ci
- run: npm test
- name: Upload coverage artifact
uses: actions/upload-artifact@v2
with:
name: coverage-${{ runner.os }}
path: coverage/
# 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-${{ runner.os }}
# - uses: paambaati/codeclimate-action@v3.0.0
# env:
# CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
# with:
# debug: true
storybook:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ['16']
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: Cache node_modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-${{ matrix.node }}-storybook-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-${{ matrix.node }}-storybook-${{ env.cache-name }}-
- run: npm ci
- run: npm run storybook:build

2
.gitignore vendored
View File

@ -15,3 +15,5 @@ src/@types/apollo/
graphql.schema.json graphql.schema.json
src/@types/graph.types.ts src/@types/graph.types.ts
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
__snapshots__
storybook-static

View File

@ -7,10 +7,11 @@ const createJestConfig = nextJest({
// Add any custom config to be passed to Jest // Add any custom config to be passed to Jest
const customJestConfig = { const customJestConfig = {
rootDir: '../',
// Add more setup options before each test is run // Add more setup options before each test is run
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], setupFilesAfterEnv: ['<rootDir>/.jest/jest.setup.js'],
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
moduleDirectories: ['node_modules', '<rootDir>/'], moduleDirectories: ['node_modules', '<rootDir>/src'],
testEnvironment: 'jest-environment-jsdom', testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: { moduleNameMapper: {
// '^@/components/(.*)$': '<rootDir>/components/$1', // '^@/components/(.*)$': '<rootDir>/components/$1',
@ -22,7 +23,11 @@ const customJestConfig = {
'@content/(.*)$': '<rootDir>/@content/$1' '@content/(.*)$': '<rootDir>/@content/$1'
}, },
collectCoverage: true, collectCoverage: true,
coverageReporters: ['lcov'] collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.{stories,test}.{ts,tsx}'
],
testPathIgnorePatterns: ['node_modules', '\\.cache', '.next', 'coverage']
} }
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async

View File

@ -1,12 +1,9 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin') const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
module.exports = { module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.tsx'], core: { builder: 'webpack5' },
addons: [ stories: ['../src/**/*.stories.tsx'],
'@storybook/addon-links', addons: ['@storybook/addon-essentials'],
'@storybook/addon-essentials',
'@storybook/addon-interactions'
],
framework: '@storybook/react', framework: '@storybook/react',
webpackFinal: async (config) => { webpackFinal: async (config) => {
config.resolve.plugins = [ config.resolve.plugins = [
@ -15,6 +12,36 @@ module.exports = {
extensions: config.resolve.extensions extensions: config.resolve.extensions
}) })
] ]
// Mimic next.config.js webpack config
config.module.rules.push(
{
test: /\.svg$/,
issuer: /\.(tsx|ts)$/,
use: [
{ loader: require.resolve('@svgr/webpack'), options: { icon: true } }
]
},
{
test: /\.gif$/,
// yay for webpack 5
// https://webpack.js.org/guides/asset-management/#loading-images
type: 'asset/resource'
}
)
const fallback = config.resolve.fallback || {}
Object.assign(fallback, {
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
fs: false,
crypto: false,
os: false,
stream: false,
assert: false
})
config.resolve.fallback = fallback
return config return config
} }
} }

View File

@ -2,6 +2,13 @@ import '@oceanprotocol/typographies/css/ocean-typo.css'
import '../src/stylesGlobal/styles.css' import '../src/stylesGlobal/styles.css'
export const parameters = { export const parameters = {
backgrounds: {
default: 'light',
values: [
{ name: 'dark', value: 'rgb(10, 10, 10)' },
{ name: 'light', value: '#fcfcfc' }
]
},
actions: { argTypesRegex: '^on[A-Z].*' }, actions: { argTypesRegex: '^on[A-Z].*' },
controls: { controls: {
matchers: { matchers: {

View File

@ -19,6 +19,8 @@
- [3Box](#3box) - [3Box](#3box)
- [Purgatory](#purgatory) - [Purgatory](#purgatory)
- [Network Metadata](#network-metadata) - [Network Metadata](#network-metadata)
- [👩‍🎤 Storybook](#-storybook)
- [🤖 Testing](#-testing)
- [✨ Code Style](#-code-style) - [✨ Code Style](#-code-style)
- [🛳 Production](#-production) - [🛳 Production](#-production)
- [⬆️ Deployment](#-deployment) - [⬆️ Deployment](#-deployment)
@ -276,24 +278,6 @@ export default function NetworkName(): ReactElement {
} }
``` ```
## ✨ Code Style
Code style is automatically enforced through [ESLint](https://eslint.org) & [Prettier](https://prettier.io) rules:
- Git pre-commit hook runs `prettier` on staged files, setup with [Husky](https://typicode.github.io/husky)
- VS Code suggested extensions and settings for auto-formatting on file save
- CI runs a linting & TypeScript typings check with `npm run lint`, and fails if errors are found
For running linting and auto-formatting manually, you can use from the root of the project:
```bash
# linting check, also runs Typescript typings check
npm run lint
# auto format all files in the project with prettier, taking all configs into account
npm run format
```
## 👩‍🎤 Storybook ## 👩‍🎤 Storybook
Storybook helps us build UI components in isolation from our app's business logic, data, and context. That makes it easy to develop hard-to-reach states and save these UI states as stories to revisit during development, testing, or QA. Storybook helps us build UI components in isolation from our app's business logic, data, and context. That makes it easy to develop hard-to-reach states and save these UI states as stories to revisit during development, testing, or QA.
@ -311,36 +295,73 @@ src
│ │ index.test.tsx │ │ index.test.tsx
</pre> </pre>
You can also write a [test](https://storybook.js.org/docs/react/writing-tests/importing-stories-in-tests#example-with-testing-library) against your story by creating a `index.test.tsx` file.
Starting up the Storybook server with this command will make it accessible under `http://localhost:6006`: Starting up the Storybook server with this command will make it accessible under `http://localhost:6006`:
```bash ```bash
npm run storybook npm run storybook
``` ```
## 🤖 Testing If you want to build a portable static version under `storybook-static/`:
Interaction tests are setup with Storybook's Addon for [Testing Library](https://storybook.js.org/docs/react/writing-tests/importing-stories-in-tests#example-with-testing-library), which utilizes [Jest](https://jestjs.io/) as test runner. A combined coverage report is sent to CodeClimate via the coverage GitHub Actions job.
Executing linting, type checking, and interaction tests:
```bash ```bash
npm run test npm run storybook:build
``` ```
Executing only interaction tests: ## 🤖 Testing
Test runs utilize [Jest](https://jestjs.io/) as test runner and [Testing Library](https://testing-library.com/docs/react-testing-library/intro) for writing tests.
All created Storybook stories will automatically run as individual tests by using the [StoryShots Addon](https://storybook.js.org/addons/@storybook/addon-storyshots).
Creating Storybook stories for a component will provide good coverage of a component in many cases. Additional tests for dedicated component functionality which can't be done with Storybook are created as usual [Testing Library](https://testing-library.com/docs/react-testing-library/intro) tests, but you can also [import exisiting Storybook stories](https://storybook.js.org/docs/react/writing-tests/importing-stories-in-tests#example-with-testing-library) into those tests.
Executing linting, type checking, and full test run:
```bash ```bash
npm test
```
Which is a combination of multiple scripts which can also be run individually:
```bash
npm run lint
npm run type-check
npm run jest npm run jest
``` ```
A coverage report is automatically shown in console whenever `npm run jest` is called. Generated reports are sent to CodeClimate during CI runs.
During local development you can continously get coverage report feedback in your console by running Jest in watch mode:
```bash
npm run jest:watch
```
## ✨ Code Style
Code style is automatically enforced through [ESLint](https://eslint.org) & [Prettier](https://prettier.io) rules:
- Git pre-commit hook runs `prettier` on staged files, setup with [Husky](https://typicode.github.io/husky)
- VS Code suggested extensions and settings for auto-formatting on file save
- CI runs a linting & TypeScript typings check as part of `npm test`, and fails if errors are found
For running linting and auto-formatting manually, you can use from the root of the project:
```bash
# linting check
npm run lint
# auto format all files in the project with prettier, taking all configs into account
npm run format
```
## 🛳 Production ## 🛳 Production
To create a production build, run from the root of the project: To create a production build, run from the root of the project:
```bash ```bash
npm run build npm run build
# serve production build # serve production build
npm run serve npm run serve
``` ```

20500
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,15 +10,16 @@
"serve": "serve -s public/", "serve": "serve -s public/",
"pregenerate": "bash scripts/pregenerate.sh", "pregenerate": "bash scripts/pregenerate.sh",
"test": "npm run pregenerate && npm run lint && npm run type-check && npm run jest", "test": "npm run pregenerate && npm run lint && npm run type-check && npm run jest",
"jest": "jest", "jest": "jest -c .jest/jest.config.js",
"jest:watch": "jest -c .jest/jest.config.js --watch",
"lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .", "lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .",
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write", "format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"deploy:s3": "bash scripts/deploy-s3.sh", "deploy:s3": "bash scripts/deploy-s3.sh",
"postinstall": "husky install", "postinstall": "husky install",
"codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/", "codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/",
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006 --quiet",
"build-storybook": "build-storybook" "storybook:build": "build-storybook"
}, },
"dependencies": { "dependencies": {
"@coingecko/cryptoformat": "^0.4.4", "@coingecko/cryptoformat": "^0.4.4",
@ -27,17 +28,16 @@
"@oceanprotocol/lib": "^1.0.0-next.42", "@oceanprotocol/lib": "^1.0.0-next.42",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@portis/web3": "^4.0.7", "@portis/web3": "^4.0.7",
"@storybook/addon-storyshots": "^6.4.22",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@urql/exchange-refocus": "^0.2.5", "@urql/exchange-refocus": "^0.2.5",
"@walletconnect/web3-provider": "^1.7.7", "@walletconnect/web3-provider": "^1.7.8",
"axios": "^0.26.1", "axios": "^0.27.2",
"chart.js": "^3.7.1", "chart.js": "^3.7.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"decimal.js": "^10.3.1", "decimal.js": "^10.3.1",
"dom-confetti": "^0.2.2", "dom-confetti": "^0.2.2",
"dotenv": "^16.0.0", "dotenv": "^16.0.1",
"filesize": "^8.0.7", "filesize": "^8.0.7",
"formik": "^2.2.9", "formik": "^2.2.9",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
@ -47,17 +47,17 @@
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.omit": "^4.5.0", "lodash.omit": "^4.5.0",
"myetherwallet-blockies": "^0.1.1", "myetherwallet-blockies": "^0.1.1",
"next": "^12.1.5", "next": "^12.1.6",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"react": "^17.0.2", "react": "^18.1.0",
"react-chartjs-2": "^4.1.0", "react-chartjs-2": "^4.1.0",
"react-clipboard.js": "^2.0.16", "react-clipboard.js": "^2.0.16",
"react-data-table-component": "^6.11.7", "react-data-table-component": "^6.11.7",
"react-dom": "^17.0.2", "react-dom": "^18.1.0",
"react-dotdotdot": "^1.3.1", "react-dotdotdot": "^1.3.1",
"react-modal": "^3.14.4", "react-modal": "^3.15.1",
"react-paginate": "^8.1.3", "react-paginate": "^8.1.3",
"react-spring": "^9.4.4", "react-spring": "^9.4.5",
"react-tabs": "^3.2.3", "react-tabs": "^3.2.3",
"react-toastify": "^8.2.0", "react-toastify": "^8.2.0",
"remark": "^13.0.0", "remark": "^13.0.0",
@ -73,17 +73,16 @@
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-actions": "^6.4.22",
"@storybook/addon-essentials": "^6.4.22", "@storybook/addon-essentials": "^6.4.22",
"@storybook/addon-interactions": "^6.4.22", "@storybook/addon-storyshots": "^6.4.22",
"@storybook/addon-links": "^6.4.22", "@storybook/builder-webpack5": "^6.4.22",
"@storybook/manager-webpack5": "^6.4.22",
"@storybook/react": "^6.4.22", "@storybook/react": "^6.4.22",
"@storybook/testing-library": "^0.0.9", "@storybook/testing-library": "^0.0.11",
"@storybook/testing-react": "^1.2.4", "@storybook/testing-react": "^1.2.4",
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.5", "@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^14.1.1",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/d3": "^7.1.0", "@types/d3": "^7.1.0",
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
@ -91,34 +90,37 @@
"@types/lodash.debounce": "^4.0.3", "@types/lodash.debounce": "^4.0.3",
"@types/lodash.omit": "^4.5.6", "@types/lodash.omit": "^4.5.6",
"@types/node": "^17.0.13", "@types/node": "^17.0.13",
"@types/react": "^17.0.38", "@types/react": "^18.0.9",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^18.0.3",
"@types/react-modal": "^3.13.1", "@types/react-modal": "^3.13.1",
"@types/react-paginate": "^7.1.1", "@types/react-paginate": "^7.1.1",
"@types/react-tabs": "^2.3.4", "@types/react-tabs": "^2.3.4",
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
"@types/yup": "^0.29.11", "@types/yup": "^0.29.13",
"@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.15.0", "@typescript-eslint/parser": "^5.23.0",
"apollo": "^2.33.9", "apollo": "^2.33.9",
"eslint": "^7.27.0", "eslint": "^8.15.0",
"eslint-config-oceanprotocol": "^1.5.0", "eslint-config-oceanprotocol": "^2.0.1",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest-dom": "^4.0.1",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0", "eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-testing-library": "^5.4.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"husky": "^7.0.4", "husky": "^8.0.1",
"jest": "^27.5.1", "jest": "^28.1.0",
"prettier": "^2.6.0", "jest-environment-jsdom": "^28.1.0",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"process": "^0.11.10", "process": "^0.11.10",
"serve": "^13.0.2", "serve": "^13.0.2",
"stream-http": "^3.2.0", "stream-http": "^3.2.0",
"ts-jest": "^27.1.4", "ts-jest": "^28.0.2",
"tsconfig-paths-webpack-plugin": "^3.5.2", "tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.5.4" "typescript": "^4.6.4"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -41,6 +41,7 @@ function ConsentProvider({ children }: { children: ReactNode }): ReactElement {
cookies.optionalCookies?.map((cookie) => { cookies.optionalCookies?.map((cookie) => {
deleteCookie(cookie.cookieName) deleteCookie(cookie.cookieName)
resetCookieConsent[cookie.cookieName] = status resetCookieConsent[cookie.cookieName] = status
return status
}) })
setConsentStatus(resetCookieConsent) setConsentStatus(resetCookieConsent)
} }
@ -97,12 +98,15 @@ function ConsentProvider({ children }: { children: ReactNode }): ReactElement {
initialValues[cookie.cookieName] = CookieConsentStatus.NOT_AVAILABLE initialValues[cookie.cookieName] = CookieConsentStatus.NOT_AVAILABLE
break break
} }
return initialValues
}) })
setConsentStatus(initialValues) setConsentStatus(initialValues)
}, [cookies.optionalCookies, appConfig]) }, [cookies.optionalCookies, appConfig])
useEffect(() => { useEffect(() => {
// eslint-disable-next-line array-callback-return
Object.keys(consentStatus).map((cookieName) => { Object.keys(consentStatus).map((cookieName) => {
switch (consentStatus[cookieName]) { switch (consentStatus[cookieName]) {
case CookieConsentStatus.APPROVED: case CookieConsentStatus.APPROVED:

View File

@ -129,7 +129,7 @@ function ProfileProvider({
const [poolSharesInterval, setPoolSharesInterval] = useState<NodeJS.Timeout>() const [poolSharesInterval, setPoolSharesInterval] = useState<NodeJS.Timeout>()
const fetchPoolShares = useCallback( const fetchPoolShares = useCallback(
async (accountId, chainIds, isEthAddress) => { async (accountId: string, chainIds: number[], isEthAddress: boolean) => {
if (!accountId || !chainIds || !isEthAddress) return if (!accountId || !chainIds || !isEthAddress) return
try { try {

View File

@ -157,7 +157,7 @@ export async function getAssetsFromDidList(
if (!(didList.length > 0)) return if (!(didList.length > 0)) return
const baseParams = { const baseParams = {
chainIds: chainIds, chainIds,
filters: [getFilterTerm('_id', didList)], filters: [getFilterTerm('_id', didList)],
ignorePurgatory: true ignorePurgatory: true
} as BaseQueryParams } as BaseQueryParams
@ -179,7 +179,7 @@ export async function getAssetsFromDtList(
if (!(dtList.length > 0)) return if (!(dtList.length > 0)) return
const baseParams = { const baseParams = {
chainIds: chainIds, chainIds,
filters: [getFilterTerm('services.datatokenAddress', dtList)], filters: [getFilterTerm('services.datatokenAddress', dtList)],
ignorePurgatory: true ignorePurgatory: true
} as BaseQueryParams } as BaseQueryParams

View File

@ -279,10 +279,7 @@ export async function getPreviousOrders(
account: string, account: string,
assetTimeout: string assetTimeout: string
): Promise<string> { ): Promise<string> {
const variables = { const variables = { id, account }
id: id,
account: account
}
const fetchedPreviousOrders: OperationResult<AssetPreviousOrder> = const fetchedPreviousOrders: OperationResult<AssetPreviousOrder> =
await fetchData(PreviousOrderQuery, variables, null) await fetchData(PreviousOrderQuery, variables, null)
if (fetchedPreviousOrders.data?.orders?.length === 0) return null if (fetchedPreviousOrders.data?.orders?.length === 0) return null
@ -347,7 +344,7 @@ export async function getAccountLiquidityInOwnAssets(
): Promise<string> { ): Promise<string> {
const queryVariables = { const queryVariables = {
user: accountId.toLowerCase(), user: accountId.toLowerCase(),
pools: pools pools
} }
const results: PoolSharesList[] = await fetchDataForMultipleChains( const results: PoolSharesList[] = await fetchDataForMultipleChains(
UserSharesQuery, UserSharesQuery,

View File

@ -26,7 +26,7 @@ export default function AddToken({
const styleClasses = cx({ const styleClasses = cx({
button: true, button: true,
minimal: minimal, minimal,
[className]: className [className]: className
}) })

View File

@ -29,7 +29,7 @@ export default function FileIcon({
}): ReactElement { }): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
file: true, file: true,
small: small, small,
[className]: className [className]: className
}) })

View File

@ -16,7 +16,7 @@ export default function PageHeader({
}): ReactElement { }): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
header: true, header: true,
center: center center
}) })
return ( return (

View File

@ -1,5 +1,5 @@
.alert { .alert {
composes: box from './Box.module.css'; composes: box from '../Box.module.css';
max-width: 40rem; max-width: 40rem;
margin: auto; margin: auto;
border-width: 0; border-width: 0;

View File

@ -0,0 +1,37 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Alert, { AlertProps } from '@shared/atoms/Alert'
export default {
title: 'Component/@shared/atoms/Alert',
component: Alert
} as ComponentMeta<typeof Alert>
const Template: ComponentStory<typeof Alert> = (args) => <Alert {...args} />
interface Props {
args: AlertProps
}
export const Default: Props = Template.bind({})
Default.args = {
text: 'Alert text',
state: 'info',
onDismiss: () => console.log('Alert closed!')
}
export const Full: Props = Template.bind({})
Full.args = {
title: 'Alert',
text: 'Alert text',
state: 'info',
action: {
name: 'Action',
handleAction: () => null as any
},
badge: 'Hello',
onDismiss: () => {
console.log('Alert closed!')
}
}

View File

@ -1,21 +1,13 @@
import React, { ReactElement, FormEvent } from 'react' import React, { ReactElement, FormEvent } from 'react'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import styles from './Alert.module.css' import styles from './index.module.css'
import Button from './Button' import Button from '../Button'
import Markdown from '../Markdown' import Markdown from '../../Markdown'
import Badge from './Badge' import Badge from '../Badge'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
export default function Alert({ export interface AlertProps {
title,
badge,
text,
state,
action,
onDismiss,
className
}: {
title?: string title?: string
badge?: string badge?: string
text: string text: string
@ -28,7 +20,17 @@ export default function Alert({
} }
onDismiss?: () => void onDismiss?: () => void
className?: string className?: string
}): ReactElement { }
export default function Alert({
title,
badge,
text,
state,
action,
onDismiss,
className
}: AlertProps): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
alert: true, alert: true,
[state]: state, [state]: state,

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react' import { ComponentStory, ComponentMeta } from '@storybook/react'
import Button, { ButtonProps } from '@shared/atoms/Button' import Button, { ButtonProps } from '@shared/atoms/Button'
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
@ -15,11 +14,15 @@ const Template: ComponentStory<typeof Button> = (args: ButtonProps) => (
) )
interface Props { interface Props {
args: { args: ButtonProps
children: string }
style: string
size: string export const Default: Props = Template.bind({})
onClick: any // More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {
children: 'Button',
onClick: () => {
console.log('Button pressed!')
} }
} }
@ -28,6 +31,15 @@ export const Primary: Props = Template.bind({})
Primary.args = { Primary.args = {
children: 'Button', children: 'Button',
style: 'primary', style: 'primary',
onClick: () => {
console.log('Button pressed!')
}
}
export const Small: Props = Template.bind({})
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Small.args = {
children: 'Button',
size: 'small', size: 'small',
onClick: () => { onClick: () => {
console.log('Button pressed!') console.log('Button pressed!')

View File

@ -1,20 +1,25 @@
import React from 'react' import React from 'react'
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import { composeStory } from '@storybook/testing-react' import Button from './'
import Meta, { Primary as PrimaryStory } from './index.stories'
// Returns a component that already contain all decorators from story level, meta level and global level. test('returns correct markup when href or to is passed', async () => {
const Primary = composeStory(PrimaryStory, Meta) const { rerender } = render(
<Button href="https://oceanprotocol.com">Hello Button</Button>
)
test('onclick handler is called', () => { let button = screen.getByText('Hello Button')
render(<Primary />) expect(button).toHaveAttribute('href', 'https://oceanprotocol.com')
const buttonElement = screen.getByRole('button') expect(button).toContainHTML('<a')
buttonElement.click()
rerender(<Button to="/publish">Hello Button</Button>)
button = screen.getByText('Hello Button')
expect(button).toHaveAttribute('href', '/publish')
expect(button).toContainHTML('<a')
}) })
test('test against args', () => { test('returns correct markup when no href or to is passed', async () => {
render(<Primary />) render(<Button>Hello Button</Button>)
const buttonElement = screen.getByRole('button')
// Testing against values coming from the story itself! No need for duplication const button = screen.getByText('Hello Button')
expect(buttonElement.textContent).toEqual(Primary.args.children) expect(button).toContainHTML('<button')
}) })

View File

@ -15,7 +15,7 @@ export default function Container({
}): ReactElement { }): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
container: true, container: true,
narrow: narrow, narrow,
[className]: className [className]: className
}) })

View File

@ -1,10 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import loadable from '@loadable/component'
import styles from './Copy.module.css' import styles from './Copy.module.css'
import IconCopy from '@images/copy.svg' import IconCopy from '@images/copy.svg'
import Clipboard from 'react-clipboard.js'
// lazy load when needed only, as library is a bit big
const Clipboard = loadable(() => import('react-clipboard.js'))
export default function Copy({ text }: { text: string }): ReactElement { export default function Copy({ text }: { text: string }): ReactElement {
const [isCopied, setIsCopied] = useState(false) const [isCopied, setIsCopied] = useState(false)

View File

@ -1,4 +1,4 @@
import React, { ReactElement } from 'react' import React, { ReactElement, ReactNode } from 'react'
import DataTable, { IDataTableProps } from 'react-data-table-component' import DataTable, { IDataTableProps } from 'react-data-table-component'
import Loader from './Loader' import Loader from './Loader'
import Pagination from '@shared/Pagination' import Pagination from '@shared/Pagination'
@ -47,7 +47,7 @@ export default function Table({
noDataComponent={<Empty message={emptyMessage} />} noDataComponent={<Empty message={emptyMessage} />}
progressPending={isLoading} progressPending={isLoading}
progressComponent={<Loader />} progressComponent={<Loader />}
paginationComponent={Pagination} paginationComponent={Pagination as unknown as ReactNode}
defaultSortField={sortField} defaultSortField={sortField}
defaultSortAsc={sortAsc} defaultSortAsc={sortAsc}
{...props} {...props}

View File

@ -86,7 +86,8 @@ export default function Compute({
!hasAlgoAssetDatatoken && !hasAlgoAssetDatatoken &&
!isAlgoConsumablePrice) !isAlgoConsumablePrice)
const { timeout } = ddo?.services[0] const service = ddo?.services[0]
const { timeout } = service
async function checkPreviousOrders(ddo: DDO) { async function checkPreviousOrders(ddo: DDO) {
const { type } = ddo.metadata const { type } = ddo.metadata

View File

@ -9,7 +9,8 @@ export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
const { isInPurgatory } = useAsset() const { isInPurgatory } = useAsset()
function DockerImage() { function DockerImage() {
const { image, tag } = ddo?.metadata?.algorithm?.container const containerInfo = ddo?.metadata?.algorithm?.container
const { image, tag } = containerInfo
return <span>{`${image}:${tag}`}</span> return <span>{`${image}:${tag}`}</span>
} }

View File

@ -33,7 +33,7 @@ export default function FormEditComputeDataset({
const { publisherTrustedAlgorithms } = getServiceByName( const { publisherTrustedAlgorithms } = getServiceByName(
asset, asset,
'compute' 'compute'
)?.compute ).compute
async function getAlgorithmList( async function getAlgorithmList(
publisherTrustedAlgorithms: PublisherTrustedAlgorithm[] publisherTrustedAlgorithms: PublisherTrustedAlgorithm[]

View File

@ -117,7 +117,7 @@ export default function HomePage(): ReactElement {
}) })
const baseParams = { const baseParams = {
chainIds: chainIds, chainIds,
esPaginationOptions: { esPaginationOptions: {
size: 9 size: 9
}, },

View File

@ -21,8 +21,10 @@ export default function NumberUnit({
return ( return (
<div className={styles.unit}> <div className={styles.unit}>
<div className={`${styles.number} ${small && styles.small}`}> <div className={`${styles.number} ${small && styles.small}`}>
{icon && icon} <>
{value} {icon && icon}
{value}
</>
</div> </div>
<span className={styles.label}> <span className={styles.label}>
{label}{' '} {label}{' '}

View File

@ -7,6 +7,7 @@ import styles from './PublishedList.module.css'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import Filters from '../../Search/Filters' import Filters from '../../Search/Filters'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
import { CancelToken } from 'axios'
export default function PublishedList({ export default function PublishedList({
accountId accountId
@ -19,12 +20,19 @@ export default function PublishedList({
const [queryResult, setQueryResult] = useState<PagedAssets>() const [queryResult, setQueryResult] = useState<PagedAssets>()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [page, setPage] = useState<number>(1) const [page, setPage] = useState<number>(1)
const [service, setServiceType] = useState() const [service, setServiceType] = useState<string>()
const [access, setAccsesType] = useState() const [access, setAccessType] = useState<string>()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
const getPublished = useCallback( const getPublished = useCallback(
async (accountId, chainIds, page, service, access, cancelToken) => { async (
accountId: string,
chainIds: number[],
page: number,
service: string,
access: string,
cancelToken: CancelToken
) => {
try { try {
setIsLoading(true) setIsLoading(true)
const result = await getPublishedAssets( const result = await getPublishedAssets(
@ -70,7 +78,7 @@ export default function PublishedList({
serviceType={service} serviceType={service}
setServiceType={setServiceType} setServiceType={setServiceType}
accessType={access} accessType={access}
setAccessType={setAccsesType} setAccessType={setAccessType}
className={styles.filters} className={styles.filters}
/> />
<AssetList <AssetList

View File

@ -134,7 +134,7 @@ export function getSearchQuery(
from: (Number(page) - 1 || 0) * (Number(offset) || 21), from: (Number(page) - 1 || 0) * (Number(offset) || 21),
size: Number(offset) || 21 size: Number(offset) || 21
}, },
sortOptions: { sortBy: sort, sortDirection: sortDirection }, sortOptions: { sortBy: sort, sortDirection },
filters filters
} as BaseQueryParams } as BaseQueryParams