diff --git a/.circleci/config.yml b/.circleci/config.yml index 8c5fba9..a6ff13b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,31 +1,25 @@ version: 2 aliases: + - &repo_path + ~/ui-theme - &defaults - working_directory: ~/ui-theme - - &node11_executor + working_directory: *repo_path + - &node_latest_executor docker: - - image: circleci/node:11.10.1 - - &node8_executor + - image: circleci/node:latest + - &node_lts_executor docker: - - image: circleci/node:8 - - &node10_executor - docker: - - image: circleci/node:10 + - image: circleci/node:lts - &default_executor - <<: *node10_executor - - &repo_key - repo-{{ .Branch }}-{{ .Revision }} - - &coverage_key - coverage-{{ .Branch }}-{{ .Revision }} - - &base_config_key - base-config-{{ .Branch }}-{{ .Revision }} + <<: *node_latest_executor - &yarn_cache_key yarn-sha-{{ checksum "yarn.lock" }} + - &coverage_key + coverage-{{ .Branch }}-{{ .Revision }} - &restore_repo - restore_cache: - keys: - - *repo_key + attach_workspace: + at: *repo_path - &ignore_non_dev_branches filters: tags: @@ -36,7 +30,7 @@ aliases: - &execute_on_release filters: tags: - only: /(v)?[0-9]+(\.[0-9]+)*/ + only: /v?[0-9]+(\.[0-9]+)*([-+\.][a-zA-Z0-9]+)*/ branches: ignore: - /.*/ @@ -48,21 +42,11 @@ jobs: steps: - *restore_repo - checkout - - restore_cache: - key: *base_config_key - - run: - name: 'Base environment setup' - command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - - save_cache: - key: *base_config_key - paths: - - ~/.npmrc - - ~/.gitconfig - restore_cache: key: *yarn_cache_key - run: - name: Install Js dependencies - command: yarn install --no-progress --registry https://registry.verdaccio.org --no-lockfile + name: Install dependencies + command: yarn install --frozen-lockfile - run: name: Build project command: yarn run build @@ -72,10 +56,20 @@ jobs: - ~/.yarn - ~/.cache/yarn - node_modules - - save_cache: - key: *repo_key + - persist_to_workspace: + root: *repo_path paths: - - ~/ui-theme + - ./* + + lint: + <<: *defaults + <<: *default_executor + steps: + - *restore_repo + - run: + name: Lint code + command: yarn lint + test_bundlesize: <<: *defaults <<: *default_executor @@ -85,37 +79,28 @@ jobs: name: Test BundleSize command: yarn test:size - test_node11: + test_node_latest: <<: *defaults - <<: *node11_executor + <<: *node_latest_executor steps: - *restore_repo - run: - name: Test with Node 11 + name: Test with Node (Latest) command: yarn test - - test_node8: - <<: *defaults - <<: *node8_executor - steps: - - *restore_repo - - run: - name: Test with Node 8 - command: yarn test - - test_node10: - <<: *defaults - <<: *node10_executor - steps: - - *restore_repo - - run: - name: Test with Node 10 - command: yarn run test - save_cache: key: *coverage_key paths: - coverage + test_node_lts: + <<: *defaults + <<: *node_lts_executor + steps: + - *restore_repo + - run: + name: Test with Node (LTS) + command: yarn test + coverage: <<: *defaults <<: *default_executor @@ -140,8 +125,9 @@ jobs: <<: *default_executor steps: - *restore_repo - - restore_cache: - key: *base_config_key + - run: + name: 'Setup publish credentials' + command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - run: name: Publish command: yarn publish @@ -152,29 +138,31 @@ workflows: jobs: - prepare: <<: *ignore_non_dev_branches - - test_node11: - requires: - - prepare - <<: *ignore_non_dev_branches - - test_node8: - requires: - - prepare - <<: *ignore_non_dev_branches - - test_node10: + - lint: requires: - prepare <<: *ignore_non_dev_branches - test_bundlesize: requires: - - test_node11 - - test_node8 - - test_node10 + - prepare + <<: *ignore_non_dev_branches + - test_node_latest: + requires: + - prepare + <<: *ignore_non_dev_branches + - test_node_lts: + requires: + - prepare <<: *ignore_non_dev_branches - coverage: requires: - - test_bundlesize + - test_node_latest <<: *ignore_non_dev_branches - publish_package: requires: + - lint + - test_bundlesize + - test_node_latest + - test_node_lts - coverage <<: *execute_on_release diff --git a/.eslintrc b/.eslintrc index 6ee2367..a4a9a7a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,7 +13,8 @@ "prettier", "verdaccio", "jsx-a11y", - "codeceptjs" + "codeceptjs", + "react-hooks" ], "settings": { "react": { @@ -107,6 +108,8 @@ 2, "always" ], + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", "verdaccio/jsx-no-style": ["warn"], "verdaccio/jsx-spread": ["warn"], "jest/expect-expect": 0, diff --git a/.github/main.workflow b/.github/main.workflow deleted file mode 100644 index 2986720..0000000 --- a/.github/main.workflow +++ /dev/null @@ -1,67 +0,0 @@ -################################################ -# Workflow for a github release when a tag is -# pushed -################################################ -workflow "github release" { - resolves = [ - "release.github", - "release.lint", - ] - on = "push" -} - -action "release.filter" { - uses = "actions/bin/filter@master" - args = "tag v*" -} - -action "release.install" { - uses = "docker://node:10" - needs = ["release.filter"] - args = "yarn install" -} - -action "release.build" { - uses = "docker://node:10" - needs = ["release.install"] - args = "yarn run build" -} - -action "release.lint" { - uses = "docker://node:10" - needs = ["release.install"] - args = "yarn run lint" -} - -action "release.test" { - uses = "docker://node:10" - needs = ["release.build"] - args = "yarn run test" -} - -action "release.auth" { - needs = ["release.test"] - uses = "actions/bin/filter@master" - args = ["actor", "octocat", "torvalds"] -} - -action "release.npm.publish" { - needs = ["release.auth"] - uses = "docker://node:10" - args = "sh scripts/publish.sh" - secrets = [ - "REGISTRY_AUTH_TOKEN", - ] - env = { - REGISTRY_URL = "registry.npmjs.org" - } -} - -action "release.github" { - needs = ["release.npm.publish"] - uses = "docker://node:10" - args = "sh scripts/github-release.sh" - secrets = [ - "GITHUB_TOKEN", - ] -} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/main.yml similarity index 50% rename from .github/workflows/nodejs.yml rename to .github/workflows/main.yml index 21164cb..1639c53 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/main.yml @@ -1,28 +1,28 @@ -name: Node CI +name: CI -on: [push] +on: push jobs: - ci: - name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} - runs-on: ${{ matrix.os }} + build_test_lint: + name: Node ${{ matrix.node_version }} and ${{ matrix.os }} + strategy: + fail-fast: false matrix: - node_version: [8, 10, 12] + node_version: [10, 12] os: [ubuntu-latest, windows-latest, macOS-latest] + runs-on: ${{ matrix.os }} + steps: - uses: actions/checkout@v1 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: version: ${{ matrix.node_version }} - - - name: Use Yarn 1.17.2 - run: | - npm install -g yarn@1.17.2 - - name: yarn build - run: | - yarn install - yarn lint - yarn build + - name: Install + run: yarn install --frozen-lockfile + - name: Build + run: yarn build + - name: Lint + run: yarn lint diff --git a/.prettierrc b/.prettierrc index 556a741..9ecd3bd 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { + "endOfLine": "auto", "useTabs": false, "printWidth": 160, "tabWidth": 2, diff --git a/.yarnrc b/.yarnrc index 235587f..fdd705c 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,2 +1 @@ save-prefix "" -registry "http://registry.npmjs.org/" diff --git a/CHANGELOG.md b/CHANGELOG.md index e8564bd..7dd1856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,31 @@ -# Change Log +# Changelog All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.3.0](https://github.com/verdaccio/ui/compare/v0.2.4...v0.3.0) (2019-09-01) + + +### Features + +* add browser features to browse by version ([#125](https://github.com/verdaccio/ui/issues/125)) ([1904179](https://github.com/verdaccio/ui/commit/1904179)) + +### [0.2.4](https://github.com/verdaccio/ui/compare/v0.2.3...v0.2.4) (2019-08-31) + + +### Features + +* update material-ui@4.x ([#123](https://github.com/verdaccio/ui/issues/123)) ([67d7188](https://github.com/verdaccio/ui/commit/67d7188)) + +### [0.2.3](https://github.com/verdaccio/ui/compare/v0.2.2...v0.2.3) (2019-08-25) + + +### Bug Fixes + +* missing headers on search endpoint with token ([#121](https://github.com/verdaccio/ui/issues/121)) ([ac58730](https://github.com/verdaccio/ui/commit/ac58730)) +* refactoring version page / fix issue not found page [#100](https://github.com/verdaccio/ui/issues/100) ([#117](https://github.com/verdaccio/ui/issues/117)) ([97e8448](https://github.com/verdaccio/ui/commit/97e8448)) +* remove ToReplaceByVerdaccio [#108](https://github.com/verdaccio/ui/issues/108) ([#122](https://github.com/verdaccio/ui/issues/122)) ([5a9bd60](https://github.com/verdaccio/ui/commit/5a9bd60)) +* **api:** correctly handle responses with missing content-type header ([2049022](https://github.com/verdaccio/ui/commit/2049022)) + ## [0.2.2](https://github.com/verdaccio/ui/compare/v0.2.1...v0.2.2) (2019-07-29) diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index e1b910b..0000000 --- a/jest.config.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = { - name: 'verdaccio-ui-jest', - verbose: true, - collectCoverage: true, - testEnvironment: 'jest-environment-jsdom-global', - moduleFileExtensions: ['js', 'ts', 'tsx'], - testURL: 'http://localhost', - rootDir: '..', - setupFiles: ['/test/setup.js'], - transformIgnorePatterns: ['/node_modules/(?!react-syntax-highlighter)'], - modulePathIgnorePatterns: ['/coverage', '/scripts', '/.circleci', '/tools', '/build', '/.vscode/'], - snapshotSerializers: ['enzyme-to-json/serializer', 'jest-emotion'], - moduleNameMapper: { - '\\.(s?css)$': '/node_modules/identity-obj-proxy', - 'github-markdown-css': '/node_modules/identity-obj-proxy', - '\\.(png)$': '/node_modules/identity-obj-proxy', - '\\.(svg)$': '/test/unit/empty.ts', - }, -}; - -// module.exports = { -// name: 'verdaccio-unit-jest', -// verbose: true, -// collectCoverage: true, -// testEnvironment: 'jest-environment-jsdom-global', -// testURL: 'http://localhost', -// testRegex: '../src/components/CopyToClipBoard/CopyToClipBoard.test.tsx', -// setupFiles: ['./setup.ts'], -// // Some unit tests rely on data folders that look like packages. This confuses jest-hast-map -// // when it tries to scan for package.json files. -// modulePathIgnorePatterns: ['/coverage', '/scripts', '/.circleci', '/tools', '/build', '/.vscode/'], -// // testPathIgnorePatterns: ['__snapshots__', '/build'], -// snapshotSerializers: ['enzyme-to-json/serializer', 'jest-emotion'], -// // coveragePathIgnorePatterns: ['node_modules', 'fixtures', '/src/api/debug', '/test'], -// // transformIgnorePatterns: ['/node_modules/(?!react-syntax-highlighter)'], -// }; diff --git a/jest/jest.config.js b/jest/jest.config.js index c925e59..1658072 100644 --- a/jest/jest.config.js +++ b/jest/jest.config.js @@ -3,6 +3,7 @@ const { defaults } = require('jest-config'); module.exports = { name: 'verdaccio-ui-jest', verbose: true, + automock: false, collectCoverage: true, testEnvironment: 'jest-environment-jsdom-global', moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], diff --git a/jest/setup.ts b/jest/setup.ts index 87297ee..8d54e3d 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -5,6 +5,7 @@ import 'raf/polyfill'; import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; +import { GlobalWithFetchMock } from 'jest-fetch-mock'; // @ts-ignore : Only a void function can be called with the 'new' keyword configure({ adapter: new Adapter() }); @@ -14,6 +15,10 @@ global.__APP_VERSION__ = '1.0.0'; // @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'. global.__VERDACCIO_BASENAME_UI_OPTIONS = {}; +const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock; +customGlobal.fetch = require('jest-fetch-mock'); +customGlobal.fetchMock = customGlobal.fetch; + // mocking few DOM methods // @ts-ignore : Property 'document' does not exist on type 'Global'. if (global.document) { diff --git a/package.json b/package.json index ee15580..01a2bc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@verdaccio/ui-theme", - "version": "0.2.2", + "version": "0.3.0", "description": "Verdaccio User Interface", "author": { "name": "Verdaccio Core Team" @@ -13,26 +13,28 @@ "devDependencies": { "@commitlint/cli": "8.1.0", "@commitlint/config-conventional": "8.1.0", - "@material-ui/core": "3.9.3", - "@material-ui/icons": "3.0.2", + "@material-ui/core": "4.3.3", + "@material-ui/icons": "4.2.1", + "@material-ui/styles": "4.3.3", "@octokit/rest": "16.28.7", + "@testing-library/react": "9.1.3", "@types/enzyme": "3.10.3", - "@types/lodash": "4.14.136", - "@types/material-ui": "0.21.6", - "@types/node": "12.7.1", - "@types/react": "16.9.1", - "@types/react-dom": "16.8.5", - "@types/react-router-dom": "4.3.4", - "@types/validator": "10.11.2", + "@types/jest": "24.0.18", + "@types/lodash": "4.14.138", + "@types/node": "12.7.3", + "@types/react": "16.9.2", + "@types/react-dom": "16.9.0", + "@types/react-router-dom": "4.3.5", + "@types/validator": "10.11.3", "@verdaccio/babel-preset": "2.0.0", "@verdaccio/eslint-config": "2.0.0", - "@verdaccio/types": "8.0.0-next.2", + "@verdaccio/types": "8.0.0", "autosuggest-highlight": "3.1.1", "babel-loader": "8.0.6", "bundlesize": "0.18.0", "codeceptjs": "2.2.1", "codecov": "3.5.0", - "concurrently": "4.1.1", + "concurrently": "4.1.2", "cross-env": "5.2.0", "css-loader": "3.2.0", "date-fns": "1.30.1", @@ -45,22 +47,25 @@ "eslint-plugin-jsx-a11y": "6.2.3", "eslint-plugin-prettier": "3.1.0", "eslint-plugin-react": "7.14.3", + "eslint-plugin-react-hooks": "2.0.1", "eslint-plugin-verdaccio": "2.0.0", "file-loader": "4.2.0", "friendly-errors-webpack-plugin": "1.7.0", "get-stdin": "6.0.0", "github-markdown-css": "3.0.1", "html-webpack-plugin": "3.2.0", - "husky": "3.0.3", + "husky": "3.0.4", "identity-obj-proxy": "3.0.0", "in-publish": "2.0.0", - "jest": "24.8.0", + "jest": "24.9.0", "jest-emotion": "10.0.14", - "jest-environment-jsdom": "24.8.0", + "jest-environment-jsdom": "24.9.0", "jest-environment-jsdom-global": "1.2.0", - "jest-environment-node": "24.8.0", + "jest-environment-node": "24.9.0", + "jest-fetch-mock": "2.1.2", "js-base64": "2.5.1", "js-yaml": "3.13.1", + "lint-staged": "8.2.1", "localstorage-memory": "1.0.3", "mini-css-extract-plugin": "0.8.0", "node-mocks-http": "1.7.6", @@ -74,7 +79,7 @@ "react-autosuggest": "9.4.3", "react-dom": "16.9.0", "react-emotion": "9.2.12", - "react-hot-loader": "4.12.10", + "react-hot-loader": "4.12.11", "react-router": "5.0.1", "react-router-dom": "5.0.1", "resolve-url-loader": "3.1.0", @@ -93,15 +98,15 @@ "uglifyjs-webpack-plugin": "2.2.0", "url-loader": "2.1.0", "validator": "11.1.0", - "verdaccio": "4.2.1", - "verdaccio-auth-memory": "1.1.5", - "verdaccio-memory": "2.0.0", - "webpack": "4.39.1", + "verdaccio": "4.2.2", + "verdaccio-auth-memory": "8.0.0", + "verdaccio-memory": "8.0.0", + "webpack": "4.39.3", "webpack-bundle-analyzer": "3.4.1", "webpack-bundle-size-analyzer": "3.0.0", - "webpack-cli": "3.3.6", + "webpack-cli": "3.3.7", "webpack-dev-server": "3.8.0", - "webpack-merge": "4.2.1", + "webpack-merge": "4.2.2", "whatwg-fetch": "3.0.0", "xss": "1.0.6" }, @@ -139,7 +144,7 @@ "scripts": { "type-check": "tsc --noEmit", "type-check:watch": "npm run type-check -- --watch", - "release": "standard-version -a -s", + "release": "standard-version -a", "test:clean": "npx jest --clearCache", "test:acceptance": "codeceptjs run --steps", "test:acceptance:server": "concurrently --kill-others \"npm run verdaccio:server\" \"npm run test:acceptance\"", @@ -147,7 +152,7 @@ "test:size": "bundlesize", "lint": "npm run lint:js && npm run lint:css", "lint:js": "npm run type-check && eslint . --ext .js,.ts,.tsx", - "lint:css": "stylelint 'src/**/styles.ts'", + "lint:css": "stylelint \"src/**/styles.ts\"", "coverage:publish": "codecov", "pre:webpack": "rimraf static/*", "prepublish": "in-publish && npm run build || not-in-publish", @@ -164,9 +169,25 @@ }, "husky": { "hooks": { - "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" + "pre-commit": "lint-staged", + "commit-msg": "commitlint -e $GIT_PARAMS" } }, + "lint-staged": { + "relative": true, + "linters": { + "*.{js,tsx,ts}": [ + "eslint", + "prettier --write" + ], + "*": [ + "git add" + ] + }, + "ignore": [ + "*.json" + ] + }, "license": "MIT", "commitlint": { "extends": [ diff --git a/partials/storage/jquery/package.json b/partials/storage/jquery/package.json index 1f76451..e01aea4 100644 --- a/partials/storage/jquery/package.json +++ b/partials/storage/jquery/package.json @@ -4579,6 +4579,70 @@ "tarball": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz" }, "maintainers": [ + { + "name": "dmethvin", + "email": "dave.methvin@gmail.com" + }, + { + "name": "mgol", + "email": "m.goleb@gmail.com" + }, + { + "name": "scott.gonzalez", + "email": "scott.gonzalez@gmail.com" + }, + { + "name": "timmywil", + "email": "4timmywil@gmail.com" + }, + { + "name": "dmethvin", + "email": "dave.methvin@gmail.com" + }, + { + "name": "mgol", + "email": "m.goleb@gmail.com" + }, + { + "name": "scott.gonzalez", + "email": "scott.gonzalez@gmail.com" + }, + { + "name": "timmywil", + "email": "4timmywil@gmail.com" + }, + { + "name": "dmethvin", + "email": "dave.methvin@gmail.com" + }, + { + "name": "mgol", + "email": "m.goleb@gmail.com" + }, + { + "name": "scott.gonzalez", + "email": "scott.gonzalez@gmail.com" + }, + { + "name": "timmywil", + "email": "4timmywil@gmail.com" + }, + { + "name": "dmethvin", + "email": "dave.methvin@gmail.com" + }, + { + "name": "mgol", + "email": "m.goleb@gmail.com" + }, + { + "name": "scott.gonzalez", + "email": "scott.gonzalez@gmail.com" + }, + { + "name": "timmywil", + "email": "4timmywil@gmail.com" + }, { "name": "dmethvin", "email": "dave.methvin@gmail.com" @@ -4916,4 +4980,4 @@ }, "_rev": "60-fed4915c27b9c1e6", "readme": "# jQuery\n\n> jQuery is a fast, small, and feature-rich JavaScript library.\n\nFor information on how to get started and how to use jQuery, please see [jQuery's documentation](http://api.jquery.com/).\nFor source files and issues, please visit the [jQuery repo](https://github.com/jquery/jquery).\n\nIf upgrading, please see the [blog post for 3.3.1](https://blog.jquery.com/2017/03/20/jquery-3.3.1-now-available/). This includes notable differences from the previous version and a more readable changelog.\n\n## Including jQuery\n\nBelow are some of the most common ways to include jQuery.\n\n### Browser\n\n#### Script tag\n\n```html\n\n```\n\n#### Babel\n\n[Babel](http://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively.\n\n```js\nimport $ from \"jquery\";\n```\n\n#### Browserify/Webpack\n\nThere are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documention. In the script, including jQuery will usually look like this...\n\n```js\nvar $ = require(\"jquery\");\n```\n\n#### AMD (Asynchronous Module Definition)\n\nAMD is a module format built for the browser. For more information, we recommend [require.js' documentation](http://requirejs.org/docs/whyamd.html).\n\n```js\ndefine([\"jquery\"], function($) {\n\n});\n```\n\n### Node\n\nTo include jQuery in [Node](nodejs.org), first install with npm.\n\n```sh\nnpm install jquery\n```\n\nFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/tmpvar/jsdom). This can be useful for testing purposes.\n\n```js\nrequire(\"jsdom\").env(\"\", function(err, window) {\n\tif (err) {\n\t\tconsole.error(err);\n\t\treturn;\n\t}\n\n\tvar $ = require(\"jquery\")(window);\n});\n```" -} \ No newline at end of file +} diff --git a/partials/storage/vue/package.json b/partials/storage/vue/package.json index 2a15b3b..a3a3573 100644 --- a/partials/storage/vue/package.json +++ b/partials/storage/vue/package.json @@ -25163,7 +25163,12 @@ "registry": "npmjs" } }, - "_attachments": {}, - "_rev": "17-ad64c6287118d7af", - "readme": "

\"Vue

\n\n

\n \"Build\n \"Coverage\n \"Downloads\"\n \"Version\"\n \"License\"\n \"Chat\"\n
\n \"Sauce\n

\n\n

Supporting Vue.js

\n\nVue.js is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/vuejs/vue/blob/dev/BACKERS.md). If you'd like to join them, please consider:\n\n- [Become a backer or sponsor on Patreon](https://www.patreon.com/evanyou).\n- [Become a backer or sponsor on Open Collective](https://opencollective.com/vuejs).\n- [One-time donation via PayPal or crypto-currencies.](https://vuejs.org/support-vuejs/#One-time-Donations)\n\n#### What's the difference between Patreon and OpenCollective?\n\nFunds donated via Patreon go directly to support Evan You's full-time work on Vue.js. Funds donated via OpenCollective are managed with transparent expenses and will be used for compensating work and expenses for core team members or sponsoring community events. Your name/logo will receive proper recognition and exposure by donating on either platform.\n\n

Special Sponsors

\n\n\n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n
\n\n\n

Sponsors via Patreon

\n\n

Platinum

\n\n\n\n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n
\n\n\n

Gold

\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n\n\n

Sponsors via Open Collective

\n\n

Platinum

\n\n\n\n\n

Gold

\n\n\n\n\n\n\n\n---\n\n## Introduction\n\nVue (pronounced `/vjuː/`, like view) is a **progressive framework** for building user interfaces. It is designed from the ground up to be incrementally adoptable, and can easily scale between a library and a framework depending on different use cases. It consists of an approachable core library that focuses on the view layer only, and an ecosystem of supporting libraries that helps you tackle complexity in large Single-Page Applications.\n\n#### Browser Compatibility\n\nVue.js supports all browsers that are [ES5-compliant](http://kangax.github.io/compat-table/es5/) (IE8 and below are not supported).\n\n## Ecosystem\n\n| Project | Status | Description |\n|---------|--------|-------------|\n| [vue-router] | [![vue-router-status]][vue-router-package] | Single-page application routing |\n| [vuex] | [![vuex-status]][vuex-package] | Large-scale state management |\n| [vue-cli] | [![vue-cli-status]][vue-cli-package] | Project scaffolding |\n| [vue-loader] | [![vue-loader-status]][vue-loader-package] | Single File Component (`*.vue` file) loader for webpack |\n| [vue-server-renderer] | [![vue-server-renderer-status]][vue-server-renderer-package] | Server-side rendering support |\n| [vue-class-component] | [![vue-class-component-status]][vue-class-component-package] | TypeScript decorator for a class-based API |\n| [vue-rx] | [![vue-rx-status]][vue-rx-package] | RxJS integration |\n| [vue-devtools] | [![vue-devtools-status]][vue-devtools-package] | Browser DevTools extension |\n\n[vue-router]: https://github.com/vuejs/vue-router\n[vuex]: https://github.com/vuejs/vuex\n[vue-cli]: https://github.com/vuejs/vue-cli\n[vue-loader]: https://github.com/vuejs/vue-loader\n[vue-server-renderer]: https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer\n[vue-class-component]: https://github.com/vuejs/vue-class-component\n[vue-rx]: https://github.com/vuejs/vue-rx\n[vue-devtools]: https://github.com/vuejs/vue-devtools\n\n[vue-router-status]: https://img.shields.io/npm/v/vue-router.svg\n[vuex-status]: https://img.shields.io/npm/v/vuex.svg\n[vue-cli-status]: https://img.shields.io/npm/v/vue-cli.svg\n[vue-loader-status]: https://img.shields.io/npm/v/vue-loader.svg\n[vue-server-renderer-status]: https://img.shields.io/npm/v/vue-server-renderer.svg\n[vue-class-component-status]: https://img.shields.io/npm/v/vue-class-component.svg\n[vue-rx-status]: https://img.shields.io/npm/v/vue-rx.svg\n[vue-devtools-status]: https://img.shields.io/chrome-web-store/v/nhdogjmejiglipccpnnnanhbledajbpd.svg\n\n[vue-router-package]: https://npmjs.com/package/vue-router\n[vuex-package]: https://npmjs.com/package/vuex\n[vue-cli-package]: https://npmjs.com/package/vue-cli\n[vue-loader-package]: https://npmjs.com/package/vue-loader\n[vue-server-renderer-package]: https://npmjs.com/package/vue-server-renderer\n[vue-class-component-package]: https://npmjs.com/package/vue-class-component\n[vue-rx-package]: https://npmjs.com/package/vue-rx\n[vue-devtools-package]: https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd\n\n## Documentation\n\nTo check out [live examples](https://vuejs.org/v2/examples/) and docs, visit [vuejs.org](https://vuejs.org).\n\n## Questions\n\nFor questions and support please use the [the official forum](http://forum.vuejs.org) or [community chat](https://chat.vuejs.org/). The issue list of this repo is **exclusively** for bug reports and feature requests.\n\n## Issues\n\nPlease make sure to read the [Issue Reporting Checklist](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines may be closed immediately.\n\n## Changelog\n\nDetailed changes for each release are documented in the [release notes](https://github.com/vuejs/vue/releases).\n\n## Stay In Touch\n\n- [Twitter](https://twitter.com/vuejs)\n- [Blog](https://medium.com/the-vue-point)\n- [Job Board](https://vuejobs.com/?ref=vuejs)\n\n## Contribution\n\nPlease make sure to read the [Contributing Guide](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md) before making a pull request. If you have a Vue-related project/component/tool, add it with a pull request to [this curated list](https://github.com/vuejs/awesome-vue)!\n\nThank you to all the people who already contributed to Vue!\n\n\n\n\n## License\n\n[MIT](http://opensource.org/licenses/MIT)\n\nCopyright (c) 2013-present, Yuxi (Evan) You" + "_attachments": { + "vue-2.5.16.tgz": { + "shasum": "07edb75e8412aaeed871ebafa99f4672584a0085" + } + }, + "_rev": "18-11555c26367fc5c7", + "readme": "

\"Vue

\n\n

\n \"Build\n \"Coverage\n \"Downloads\"\n \"Version\"\n \"License\"\n \"Chat\"\n
\n \"Sauce\n

\n\n

Supporting Vue.js

\n\nVue.js is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/vuejs/vue/blob/dev/BACKERS.md). If you'd like to join them, please consider:\n\n- [Become a backer or sponsor on Patreon](https://www.patreon.com/evanyou).\n- [Become a backer or sponsor on Open Collective](https://opencollective.com/vuejs).\n- [One-time donation via PayPal or crypto-currencies.](https://vuejs.org/support-vuejs/#One-time-Donations)\n\n#### What's the difference between Patreon and OpenCollective?\n\nFunds donated via Patreon go directly to support Evan You's full-time work on Vue.js. Funds donated via OpenCollective are managed with transparent expenses and will be used for compensating work and expenses for core team members or sponsoring community events. Your name/logo will receive proper recognition and exposure by donating on either platform.\n\n

Special Sponsors

\n\n\n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n
\n\n\n

Sponsors via Patreon

\n\n

Platinum

\n\n\n\n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n
\n\n\n

Gold

\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n\n\n

Sponsors via Open Collective

\n\n

Platinum

\n\n\n\n\n

Gold

\n\n\n\n\n\n\n\n---\n\n## Introduction\n\nVue (pronounced `/vjuː/`, like view) is a **progressive framework** for building user interfaces. It is designed from the ground up to be incrementally adoptable, and can easily scale between a library and a framework depending on different use cases. It consists of an approachable core library that focuses on the view layer only, and an ecosystem of supporting libraries that helps you tackle complexity in large Single-Page Applications.\n\n#### Browser Compatibility\n\nVue.js supports all browsers that are [ES5-compliant](http://kangax.github.io/compat-table/es5/) (IE8 and below are not supported).\n\n## Ecosystem\n\n| Project | Status | Description |\n|---------|--------|-------------|\n| [vue-router] | [![vue-router-status]][vue-router-package] | Single-page application routing |\n| [vuex] | [![vuex-status]][vuex-package] | Large-scale state management |\n| [vue-cli] | [![vue-cli-status]][vue-cli-package] | Project scaffolding |\n| [vue-loader] | [![vue-loader-status]][vue-loader-package] | Single File Component (`*.vue` file) loader for webpack |\n| [vue-server-renderer] | [![vue-server-renderer-status]][vue-server-renderer-package] | Server-side rendering support |\n| [vue-class-component] | [![vue-class-component-status]][vue-class-component-package] | TypeScript decorator for a class-based API |\n| [vue-rx] | [![vue-rx-status]][vue-rx-package] | RxJS integration |\n| [vue-devtools] | [![vue-devtools-status]][vue-devtools-package] | Browser DevTools extension |\n\n[vue-router]: https://github.com/vuejs/vue-router\n[vuex]: https://github.com/vuejs/vuex\n[vue-cli]: https://github.com/vuejs/vue-cli\n[vue-loader]: https://github.com/vuejs/vue-loader\n[vue-server-renderer]: https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer\n[vue-class-component]: https://github.com/vuejs/vue-class-component\n[vue-rx]: https://github.com/vuejs/vue-rx\n[vue-devtools]: https://github.com/vuejs/vue-devtools\n\n[vue-router-status]: https://img.shields.io/npm/v/vue-router.svg\n[vuex-status]: https://img.shields.io/npm/v/vuex.svg\n[vue-cli-status]: https://img.shields.io/npm/v/vue-cli.svg\n[vue-loader-status]: https://img.shields.io/npm/v/vue-loader.svg\n[vue-server-renderer-status]: https://img.shields.io/npm/v/vue-server-renderer.svg\n[vue-class-component-status]: https://img.shields.io/npm/v/vue-class-component.svg\n[vue-rx-status]: https://img.shields.io/npm/v/vue-rx.svg\n[vue-devtools-status]: https://img.shields.io/chrome-web-store/v/nhdogjmejiglipccpnnnanhbledajbpd.svg\n\n[vue-router-package]: https://npmjs.com/package/vue-router\n[vuex-package]: https://npmjs.com/package/vuex\n[vue-cli-package]: https://npmjs.com/package/vue-cli\n[vue-loader-package]: https://npmjs.com/package/vue-loader\n[vue-server-renderer-package]: https://npmjs.com/package/vue-server-renderer\n[vue-class-component-package]: https://npmjs.com/package/vue-class-component\n[vue-rx-package]: https://npmjs.com/package/vue-rx\n[vue-devtools-package]: https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd\n\n## Documentation\n\nTo check out [live examples](https://vuejs.org/v2/examples/) and docs, visit [vuejs.org](https://vuejs.org).\n\n## Questions\n\nFor questions and support please use the [the official forum](http://forum.vuejs.org) or [community chat](https://chat.vuejs.org/). The issue list of this repo is **exclusively** for bug reports and feature requests.\n\n## Issues\n\nPlease make sure to read the [Issue Reporting Checklist](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines may be closed immediately.\n\n## Changelog\n\nDetailed changes for each release are documented in the [release notes](https://github.com/vuejs/vue/releases).\n\n## Stay In Touch\n\n- [Twitter](https://twitter.com/vuejs)\n- [Blog](https://medium.com/the-vue-point)\n- [Job Board](https://vuejobs.com/?ref=vuejs)\n\n## Contribution\n\nPlease make sure to read the [Contributing Guide](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md) before making a pull request. If you have a Vue-related project/component/tool, add it with a pull request to [this curated list](https://github.com/vuejs/awesome-vue)!\n\nThank you to all the people who already contributed to Vue!\n\n\n\n\n## License\n\n[MIT](http://opensource.org/licenses/MIT)\n\nCopyright (c) 2013-present, Yuxi (Evan) You", + "_id": "vue" } \ No newline at end of file diff --git a/src/App/AppError.tsx b/src/App/AppError.tsx new file mode 100644 index 0000000..1583fee --- /dev/null +++ b/src/App/AppError.tsx @@ -0,0 +1,38 @@ +import React, { Component } from 'react'; + +export interface ErrorProps { + children: any; +} + +export interface ErrorAppState { + hasError: boolean; + error: any; + info: any; +} + +export default class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null, info: null }; + } + + componentDidCatch(error, info) { + this.setState({ hasError: true, error, info }); + } + + render() { + const { hasError, error, info } = this.state; + const { children } = this.props; + + if (hasError) { + return ( + <> +

{'Something went wrong.'}

+

{`error: ${error}`}

+

{`info: ${JSON.stringify(info)}`}

+ + ); + } + return children; + } +} diff --git a/src/components/ActionBar/ActionBar.test.tsx b/src/components/ActionBar/ActionBar.test.tsx index e227f45..fac80e0 100644 --- a/src/components/ActionBar/ActionBar.test.tsx +++ b/src/components/ActionBar/ActionBar.test.tsx @@ -1,69 +1,58 @@ import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import { ActionBar } from './ActionBar'; + +const mockPackageMeta = jest.fn(() => ({ + latest: { + homepage: 'https://verdaccio.tld', + bugs: { + url: 'https://verdaccio.tld/bugs', + }, + dist: { + tarball: 'https://verdaccio.tld/download', + }, + }, +})); + +jest.mock('../../pages/Version', () => ({ + DetailContextConsumer: component => { + return component.children({ packageMeta: mockPackageMeta() }); + }, +})); describe(' component', () => { beforeEach(() => { jest.resetModules(); + jest.resetAllMocks(); }); test('should render the component in default state', () => { - const packageMeta = { - latest: { - homepage: 'https://verdaccio.tld', - bugs: { - url: 'https://verdaccio.tld/bugs', - }, - dist: { - tarball: 'https://verdaccio.tld/download', - }, - }, - }; - - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); - - const ActionBar = require('./ActionBar').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); test('when there is no action bar data', () => { - const packageMeta = { + // @ts-ignore + mockPackageMeta.mockImplementation(() => ({ latest: {}, - }; - - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, })); - const ActionBar = require('./ActionBar').default; - const wrapper = shallow(); + const wrapper = mount(); // FIXME: this only renders the DetailContextConsumer, thus // the wrapper will be always empty expect(wrapper.html()).toEqual(''); }); test('when there is a button to download a tarball', () => { - const packageMeta = { + // @ts-ignore + mockPackageMeta.mockImplementation(() => ({ latest: { dist: { tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz', }, }, - }; - - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, })); - const ActionBar = require('./ActionBar').default; const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); diff --git a/src/components/ActionBar/ActionBar.tsx b/src/components/ActionBar/ActionBar.tsx index 51b05f8..ea02107 100644 --- a/src/components/ActionBar/ActionBar.tsx +++ b/src/components/ActionBar/ActionBar.tsx @@ -6,7 +6,7 @@ import HomeIcon from '@material-ui/icons/Home'; import List from '@material-ui/core/List'; import Tooltip from '@material-ui/core/Tooltip'; -import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; +import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/Version'; import { Fab, ActionListItem } from './styles'; import { isURL, extractFileName, downloadFile } from '../../utils/url'; import api from '../../utils/api'; @@ -49,6 +49,12 @@ class ActionBar extends Component { return ( {context => { + const { packageMeta } = context; + + if (!packageMeta) { + return null; + } + return this.renderActionBar(context as VersionPageConsumerProps); }} @@ -65,12 +71,18 @@ class ActionBar extends Component { private renderActionBar = ({ packageMeta }) => { // @ts-ignore - const { latest: { bugs: { url: issue } = {}, homepage, dist: { tarball } = {} } = {} } = packageMeta; + const { latest } = packageMeta; + + if (!latest) { + return null; + } + + const { homepage, bugs, dist } = latest; const actionsMap = { homepage, - issue, - tarball, + issue: bugs ? bugs.url : null, + tarball: dist ? dist.tarball : null, }; const renderList = Object.keys(actionsMap).reduce((component: React.ReactElement[], value, key) => { @@ -108,7 +120,9 @@ class ActionBar extends Component { if (renderList.length > 0) { return ( - {renderList} + + {renderList} + ); } @@ -117,4 +131,4 @@ class ActionBar extends Component { }; } -export default ActionBar; +export { ActionBar }; diff --git a/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap b/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap index 902784d..3da4e83 100644 --- a/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap +++ b/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` component should render the component in default state 1`] = `"
"`; +exports[` component should render the component in default state 1`] = `""`; -exports[` component when there is a button to download a tarball 1`] = `"
"`; +exports[` component when there is a button to download a tarball 1`] = `"
"`; diff --git a/src/components/Author/Author.test.tsx b/src/components/Author/Author.test.tsx index 02308b0..69f1394 100644 --- a/src/components/Author/Author.test.tsx +++ b/src/components/Author/Author.test.tsx @@ -1,9 +1,28 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import Authors from './Author'; + +const mockPackageMeta = jest.fn(() => ({ + latest: { + homepage: 'https://verdaccio.tld', + bugs: { + url: 'https://verdaccio.tld/bugs', + }, + dist: { + tarball: 'https://verdaccio.tld/download', + }, + }, +})); + +jest.mock('../../pages/Version', () => ({ + DetailContextConsumer: component => { + return component.children({ packageMeta: mockPackageMeta() }); + }, +})); describe(' component', () => { beforeEach(() => { - jest.resetModules(); + jest.resetAllMocks(); }); test('should render the component in default state', () => { @@ -20,14 +39,10 @@ describe(' component', () => { }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); - const Author = require('./Author').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); @@ -39,14 +54,10 @@ describe(' component', () => { }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); - const Author = require('./Author').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toEqual(''); }); @@ -63,14 +74,10 @@ describe(' component', () => { }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); - const Author = require('./Author').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); }); diff --git a/src/components/Author/Author.tsx b/src/components/Author/Author.tsx index e39d739..4a64bbe 100644 --- a/src/components/Author/Author.tsx +++ b/src/components/Author/Author.tsx @@ -2,10 +2,9 @@ import React, { Component, ReactNode, ReactElement } from 'react'; import Avatar from '@material-ui/core/Avatar'; import List from '@material-ui/core/List'; -import ListItemText from '@material-ui/core/ListItemText'; -import { DetailContextConsumer } from '../../pages/version/Version'; -import { Heading, AuthorListItem } from './styles'; +import { DetailContextConsumer } from '../../pages/Version'; +import { Heading, AuthorListItem, AuthorListItemText } from './styles'; import { isEmail } from '../../utils/url'; class Authors extends Component { @@ -13,7 +12,13 @@ class Authors extends Component { return ( {context => { - return context && context.packageMeta && this.renderAuthor(context.packageMeta); + const { packageMeta } = context; + + if (!packageMeta) { + return null; + } + + return this.renderAuthor(packageMeta); }} ); @@ -31,8 +36,8 @@ class Authors extends Component { ); } - public renderAuthor = packageMeta => { - const { author, name: packageName, version } = packageMeta.latest; + public renderAuthor = ({ latest }) => { + const { author, name: packageName, version } = latest; if (!author) { return null; @@ -40,10 +45,10 @@ class Authors extends Component { const avatarComponent = ; return ( - {'Author'}}> - + {'Author'}}> + {this.renderLinkForMail(author.email, avatarComponent, packageName, version)} - + ); diff --git a/src/components/Author/__snapshots__/Author.test.tsx.snap b/src/components/Author/__snapshots__/Author.test.tsx.snap index 2838593..bb8e3bd 100644 --- a/src/components/Author/__snapshots__/Author.test.tsx.snap +++ b/src/components/Author/__snapshots__/Author.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` component should render the component in default state 1`] = `"

    Author

  • \\"verdaccio
    verdaccio user
"`; +exports[` component should render the component in default state 1`] = `"
    Author
    \\"verdaccio
    verdaccio user
"`; -exports[` component should render the component when there is no author email 1`] = `"

    Author

  • \\"verdaccio
    verdaccio user
"`; +exports[` component should render the component when there is no author email 1`] = `"
    Author
    \\"verdaccio
    verdaccio user
"`; diff --git a/src/components/Author/styles.ts b/src/components/Author/styles.ts index b6b8d6c..16d99e7 100644 --- a/src/components/Author/styles.ts +++ b/src/components/Author/styles.ts @@ -2,6 +2,7 @@ import styled from 'react-emotion'; import ListItem from '@material-ui/core/ListItem'; import Typography from '@material-ui/core/Typography'; import { fontWeight } from '../../utils/styles/sizes'; +import ListItemText from '@material-ui/core/ListItemText'; export const Heading = styled(Typography)({ '&&': { @@ -11,7 +12,16 @@ export const Heading = styled(Typography)({ }); export const AuthorListItem = styled(ListItem)({ '&&': { - paddingLeft: 0, - paddingRight: 0, + padding: 0, + }, + '&&:hover': { + backgroundColor: 'transparent', + }, +}); + +export const AuthorListItemText = styled(ListItemText)({ + '&&': { + padding: '0 10px', + margin: 0, }, }); diff --git a/src/components/AvatarTooltip/AvatarTooltip.tsx b/src/components/AvatarTooltip/AvatarTooltip.tsx new file mode 100644 index 0000000..e2244b7 --- /dev/null +++ b/src/components/AvatarTooltip/AvatarTooltip.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; + +import Avatar from '@material-ui/core/Avatar'; +import Tooltip from '@material-ui/core/Tooltip'; +import { isEmail } from '../../utils/url'; + +export interface AvatarDeveloper { + name: string; + packageName: string; + version: string; + avatar: string; + email: string; +} + +const AvatarTooltip: FC = ({ name, packageName, version, avatar, email }) => { + const avatarComponent = ; + function renderLinkForMail(email, avatarComponent, packageName, version): JSX.Element { + if (!email || isEmail(email) === false) { + return avatarComponent; + } + + return ( + + {avatarComponent} + + ); + } + + return {renderLinkForMail(email, avatarComponent, packageName, version)}; +}; + +export { AvatarTooltip }; diff --git a/src/components/AvatarTooltip/index.ts b/src/components/AvatarTooltip/index.ts new file mode 100644 index 0000000..746d62c --- /dev/null +++ b/src/components/AvatarTooltip/index.ts @@ -0,0 +1,4 @@ +import { AvatarTooltip } from './AvatarTooltip'; + +export { AvatarTooltip }; +export default AvatarTooltip; diff --git a/src/components/CopyToClipBoard/CopyToClipBoard.test.tsx b/src/components/CopyToClipBoard/CopyToClipBoard.test.tsx index 341852d..a6a3bb4 100644 --- a/src/components/CopyToClipBoard/CopyToClipBoard.test.tsx +++ b/src/components/CopyToClipBoard/CopyToClipBoard.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import CopyToClipBoard from './CopyToClipBoard'; import { CopyIcon } from './styles'; @@ -8,7 +8,7 @@ describe(' component', () => { let wrapper; beforeEach(() => { - wrapper = shallow(); + wrapper = mount(); }); test('render the component', () => { diff --git a/src/components/CopyToClipBoard/__snapshots__/CopyToClipBoard.test.tsx.snap b/src/components/CopyToClipBoard/__snapshots__/CopyToClipBoard.test.tsx.snap index 7be567e..23f310a 100644 --- a/src/components/CopyToClipBoard/__snapshots__/CopyToClipBoard.test.tsx.snap +++ b/src/components/CopyToClipBoard/__snapshots__/CopyToClipBoard.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` component render the component 1`] = `"
copy text
"`; +exports[` component render the component 1`] = `"
copy text
"`; diff --git a/src/components/CopyToClipBoard/styles.ts b/src/components/CopyToClipBoard/styles.ts index deb75fa..9ee5f65 100644 --- a/src/components/CopyToClipBoard/styles.ts +++ b/src/components/CopyToClipBoard/styles.ts @@ -19,8 +19,4 @@ export const ClipBoardCopyText = styled('span')({ }, }); -export const CopyIcon = styled(IconButton)({ - '&&': { - margin: '0 0 0 10px', - }, -}); +export const CopyIcon = styled(IconButton)({}); diff --git a/src/components/Dependencies/Dependencies.tsx b/src/components/Dependencies/Dependencies.tsx index d546c53..34e452c 100644 --- a/src/components/Dependencies/Dependencies.tsx +++ b/src/components/Dependencies/Dependencies.tsx @@ -2,7 +2,7 @@ import React, { Component, Fragment, ReactElement } from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import CardContent from '@material-ui/core/CardContent'; -import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; +import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/Version'; import { CardWrap, Heading, Tags, Tag } from './styles'; import NoItems from '../NoItems'; @@ -32,7 +32,7 @@ class DepDetail extends Component { public render(): ReactElement { const { name, version } = this.state; const tagText = `${name}@${version}`; - return ; + return ; } private handleOnClick = () => { @@ -57,7 +57,7 @@ class DependencyBlock extends Component<{ title: string; dependencies: [] }> { return ( - {`${title} (${deps.length})`} + {`${title} (${deps.length})`} {this.renderTags(deps, enableLoading)} diff --git a/src/components/DetailContainer/DetailContainer.tsx b/src/components/DetailContainer/DetailContainer.tsx index e7e30fa..61f3a6d 100644 --- a/src/components/DetailContainer/DetailContainer.tsx +++ b/src/components/DetailContainer/DetailContainer.tsx @@ -1,6 +1,6 @@ import React, { Component, ReactElement, Fragment } from 'react'; -import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; +import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/Version'; import Readme from '../Readme'; import Versions from '../Versions'; import { preventXSS } from '../../utils/sec-utils'; @@ -14,6 +14,11 @@ interface DetailContainerState { tabPosition: number; } +export const README_LABEL = 'Readme'; +export const DEPS_LABEL = 'Dependencies'; +export const VERSION_LABEL = 'Versions'; +export const UPLINKS_LABEL = 'Uplinks'; + class DetailContainer

extends Component { public state = { tabPosition: 0, @@ -37,10 +42,10 @@ class DetailContainer

extends Component { private renderListTabs(tabPosition: number): React.ReactElement { return ( - - - - + + + + ); } diff --git a/src/components/DetailSidebar/DetailSidebar.tsx b/src/components/DetailSidebar/DetailSidebar.tsx index 068b060..66777ea 100644 --- a/src/components/DetailSidebar/DetailSidebar.tsx +++ b/src/components/DetailSidebar/DetailSidebar.tsx @@ -1,10 +1,10 @@ -import React, { Component, ReactElement } from 'react'; +import React, { ReactElement } from 'react'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import List from '@material-ui/core/List'; -import ActionBar from '../ActionBar/ActionBar'; +import { ActionBar } from '../ActionBar/ActionBar'; import Author from '../Author'; import Developers from '../Developers'; import Dist from '../Dist/Dist'; @@ -12,76 +12,64 @@ import Engine from '../Engines/Engines'; import Install from '../Install'; import Repository from '../Repository/Repository'; -import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; +import { DetailContext } from '../../pages/Version'; import { TitleListItem, TitleListItemText } from './styles'; -class DetailSidebar extends Component { - public render(): ReactElement { - return {context => this.renderSideBar(context as VersionPageConsumerProps)}; - } +const renderLatestDescription = (description, version, isLatest: boolean = true) => { + return ( + +

{description}
+ {version ? {`${isLatest ? 'Latest v' : 'v'}${version}`} : null} + + ); +}; - private renderSideBar = ({ packageName, packageMeta }): ReactElement => { - return ( -
- - - {this.renderTitle(packageName, packageMeta)} - {this.renderActionBar()} - {this.renderCopyCLI()} - {this.renderRepository()} - {this.renderEngine()} - {this.renderDist()} - {this.renderAuthor()} - {this.renderMaintainers()} - {this.renderContributors()} - - -
- ); - }; +const renderCopyCLI = () => ; +const renderMaintainers = () => ; +const renderContributors = () => ; +const renderRepository = () => ; +const renderAuthor = () => ; +const renderEngine = () => ; +const renderDist = () => ; +const renderActionBar = () => ; +const renderTitle = (packageName, packageVersion, packageMeta) => { + const version = packageVersion ? packageVersion : packageMeta.latest.version; + const isLatest = typeof packageVersion === 'undefined'; - private renderTitle = (packageName, packageMeta) => { - return ( - - - {packageName}} secondary={packageMeta.latest.description} /> - - - ); - }; + return ( + + + {packageName}} secondary={renderLatestDescription(packageMeta.latest.description, version, isLatest)} /> + + + ); +}; - private renderCopyCLI = () => { - return ; - }; - - private renderMaintainers = () => { - return ; - }; - - private renderContributors = () => { - return ; - }; - - private renderRepository = () => { - return ; - }; - - private renderAuthor = () => { - return ; - }; - - private renderEngine = () => { - return ; - }; - - private renderDist = () => { - return ; - }; - - private renderActionBar = () => { - return ; - }; +function renderSideBar(packageName, packageVersion, packageMeta): ReactElement { + return ( +
+ + + {renderTitle(packageName, packageVersion, packageMeta)} + {renderActionBar()} + {renderCopyCLI()} + {renderRepository()} + {renderEngine()} + {renderDist()} + {renderAuthor()} + {renderMaintainers()} + {renderContributors()} + + +
+ ); } +const DetailSidebar = () => { + const { packageName, packageMeta, packageVersion } = React.useContext(DetailContext); + + return renderSideBar(packageName, packageVersion, packageMeta); +}; + export default DetailSidebar; diff --git a/src/components/Developers/Developers.test.tsx b/src/components/Developers/Developers.test.tsx new file mode 100644 index 0000000..4d1b9bb --- /dev/null +++ b/src/components/Developers/Developers.test.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Developers, { DevelopersType } from './Developers'; +import { Fab } from './styles'; +import { DetailContextProvider } from '../../pages/Version'; + +describe('test Developers', () => { + const packageMeta = { + latest: { + packageName: 'foo', + version: '1.0.0', + maintainers: [ + { + name: 'dmethvin', + email: 'dave.methvin@gmail.com', + }, + { + name: 'mgol', + email: 'm.goleb@gmail.com', + }, + ], + contributors: [ + { + name: 'dmethvin', + email: 'dave.methvin@gmail.com', + }, + { + name: 'mgol', + email: 'm.goleb@gmail.com', + }, + ], + }, + }; + + test('should render the component with no items', () => { + const type: DevelopersType = 'maintainers'; + const packageMeta = { + latest: {}, + }; + const wrapper = mount( + // @ts-ignore + + + + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('should render the component for maintainers with items', () => { + const type: DevelopersType = 'maintainers'; + const wrapper = mount( + // @ts-ignore + + + + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('should render the component for contributors with items', () => { + const type: DevelopersType = 'contributors'; + const wrapper = mount( + // @ts-ignore + + + + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('should test onClick the component avatar', () => { + const type: DevelopersType = 'contributors'; + const packageMeta = { + latest: { + packageName: 'foo', + version: '1.0.0', + contributors: [ + { + name: 'dmethvin', + email: 'dave.methvin@gmail.com', + }, + { + name: 'dmethvin2', + email: 'dave2.methvin@gmail.com', + }, + ], + }, + }; + + const wrapper = mount( + // @ts-ignore + + + + ); + + const item2 = wrapper.find(Fab); + // TODO: I am not sure here how to verify the method inside the component was called. + item2.simulate('click'); + }); +}); diff --git a/src/components/Developers/Developers.tsx b/src/components/Developers/Developers.tsx index 68d87ca..9490139 100644 --- a/src/components/Developers/Developers.tsx +++ b/src/components/Developers/Developers.tsx @@ -1,79 +1,59 @@ -import React, { Component } from 'react'; - -import Avatar from '@material-ui/core/Avatar'; +import React, { FC, Fragment } from 'react'; import Add from '@material-ui/icons/Add'; -import Tooltip from '@material-ui/core/Tooltip'; -import { DetailContextConsumer } from '../../pages/version/Version'; +import { DetailContext } from '../../pages/Version'; +import { AvatarTooltip } from '../AvatarTooltip'; import { Details, Heading, Content, Fab } from './styles'; -import { isEmail } from '../../utils/url'; + +export type DevelopersType = 'contributors' | 'maintainers'; interface Props { - type: 'contributors' | 'maintainers'; -} -interface State { - visibleDevs: number; + type: DevelopersType; + visibleMax?: number; } -class Developers extends Component { - public state = { - visibleDevs: 6, +export const VISIBLE_MAX = 6; + +const Developers: FC = ({ type, visibleMax }) => { + const [visibleDevs, setVisibleDevs] = React.useState(visibleMax || VISIBLE_MAX); + const { packageMeta } = React.useContext(DetailContext); + + const handleLoadMore = () => { + setVisibleDevs(visibleDevs + VISIBLE_MAX); }; - public render(): JSX.Element { - return ( - - {({ packageMeta }) => { - const { type } = this.props; - const developerType = packageMeta && packageMeta.latest[type]; - if (!developerType || developerType.length === 0) return null; - return this.renderDevelopers(developerType, packageMeta); - }} - - ); - } + const renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => { + const { name: packageName, version } = packageMeta.latest; - public handleLoadMore = () => { - this.setState(prev => ({ visibleDevs: prev.visibleDevs + 6 })); + return ; }; - private renderDevelopers = (developers, packageMeta) => { - const { type } = this.props; - const { visibleDevs } = this.state; + const renderDevelopers = (developers, packageMeta) => { + const listVisibleDevelopers = developers.slice(0, visibleDevs); + return ( - <> - {type} + + {type} - {developers.slice(0, visibleDevs).map(developer => ( -
{this.renderDeveloperDetails(developer, packageMeta)}
+ {listVisibleDevelopers.map(developer => ( +
{renderDeveloperDetails(developer, packageMeta)}
))} {visibleDevs < developers.length && ( - + )}
- +
); }; - private renderLinkForMail(email, avatarComponent, packageName, version): JSX.Element { - if (!email || isEmail(email) === false) { - return avatarComponent; - } - return ( - - {avatarComponent} - - ); + const developerList = packageMeta && packageMeta.latest[type]; + if (!developerList || developerList.length === 0) { + return null; } - private renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => { - const { name: packageName, version } = packageMeta.latest; - - const avatarComponent = ; - return {this.renderLinkForMail(email, avatarComponent, packageName, version)}; - }; -} + return renderDevelopers(developerList, packageMeta); +}; export default Developers; diff --git a/src/components/Developers/__snapshots__/Developers.test.tsx.snap b/src/components/Developers/__snapshots__/Developers.test.tsx.snap new file mode 100644 index 0000000..a821481 --- /dev/null +++ b/src/components/Developers/__snapshots__/Developers.test.tsx.snap @@ -0,0 +1,487 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`test Developers should render the component for contributors with items 1`] = ` + + + + +
+ contributors +
+
+
+
+ +
+ + + + + + + + +
+ + + + +
+ + } + className="MuiTooltip-popper" + id={null} + open={false} + placement="bottom" + transition={true} + /> + + + + + + + + + + + + + +
+ + + + +
+ + } + className="MuiTooltip-popper" + id={null} + open={false} + placement="bottom" + transition={true} + /> + + + + + +
+ + +`; + +exports[`test Developers should render the component for maintainers with items 1`] = ` + + + + +
+ maintainers +
+
+
+
+ +
+ + + + + + + + +
+ + + + +
+ + } + className="MuiTooltip-popper" + id={null} + open={false} + placement="bottom" + transition={true} + /> + + + + + + + + + + + + + +
+ + + + +
+ + } + className="MuiTooltip-popper" + id={null} + open={false} + placement="bottom" + transition={true} + /> + + + + + +
+ + +`; + +exports[`test Developers should render the component with no items 1`] = ` + +`; diff --git a/src/components/Developers/styles.ts b/src/components/Developers/styles.ts index aa2fc23..10eaaff 100644 --- a/src/components/Developers/styles.ts +++ b/src/components/Developers/styles.ts @@ -1,6 +1,7 @@ import styled from 'react-emotion'; import Typography from '@material-ui/core/Typography'; import { default as MuiFab } from '@material-ui/core/Fab'; + import colors from '../../utils/styles/colors'; import { fontWeight } from '../../utils/styles/sizes'; diff --git a/src/components/Dist/Dist.test.tsx b/src/components/Dist/Dist.test.tsx index 5a3a10b..3ae7d4e 100644 --- a/src/components/Dist/Dist.test.tsx +++ b/src/components/Dist/Dist.test.tsx @@ -1,5 +1,24 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import Dist from './Dist'; + +const mockPackageMeta = jest.fn(() => ({ + latest: { + homepage: 'https://verdaccio.tld', + bugs: { + url: 'https://verdaccio.tld/bugs', + }, + dist: { + tarball: 'https://verdaccio.tld/download', + }, + }, +})); + +jest.mock('../../pages/Version', () => ({ + DetailContextConsumer: component => { + return component.children({ packageMeta: mockPackageMeta() }); + }, +})); describe(' component', () => { beforeEach(() => { @@ -18,14 +37,11 @@ describe(' component', () => { license: '', }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); - const Dist = require('./Dist').default; - const wrapper = shallow(); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); + + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); @@ -41,14 +57,11 @@ describe(' component', () => { license: 'MIT', }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); - const Dist = require('./Dist').default; - const wrapper = shallow(); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); + + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); @@ -67,14 +80,11 @@ describe(' component', () => { }, }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); - const Dist = require('./Dist').default; - const wrapper = shallow(); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); + + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); }); diff --git a/src/components/Dist/Dist.tsx b/src/components/Dist/Dist.tsx index 2edcfee..4901170 100644 --- a/src/components/Dist/Dist.tsx +++ b/src/components/Dist/Dist.tsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import List from '@material-ui/core/List'; -import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; +import { VersionPageConsumerProps, DetailContextConsumer } from '../../pages/Version'; import { Heading, DistListItem, DistChips } from './styles'; import fileSizeSI from '../../utils/file-size'; import { PackageMetaInterface } from 'types/packageMeta'; @@ -46,8 +46,8 @@ class Dist extends Component { const { dist, license } = packageMeta && packageMeta.latest; return ( - {'Latest Distribution'}}> - {this.renderChips(dist, license)} + {'Latest Distribution'}}> + {this.renderChips(dist, license)} ); }; diff --git a/src/components/Dist/__snapshots__/Dist.test.tsx.snap b/src/components/Dist/__snapshots__/Dist.test.tsx.snap index 15ebd6c..8aa3173 100644 --- a/src/components/Dist/__snapshots__/Dist.test.tsx.snap +++ b/src/components/Dist/__snapshots__/Dist.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` component should render the component in default state 1`] = `"

    Latest Distribution

  • file count: 7
    size: 10.00 Bytes
"`; +exports[` component should render the component in default state 1`] = `"
    Latest Distribution
    file count: 7
    size: 10.00 Bytes
"`; -exports[` component should render the component with license as object 1`] = `"

    Latest Distribution

  • file count: 7
    size: 10.00 Bytes
    license: MIT
"`; +exports[` component should render the component with license as object 1`] = `"
    Latest Distribution
    file count: 7
    size: 10.00 Bytes
    license: MIT
"`; -exports[` component should render the component with license as string 1`] = `"

    Latest Distribution

  • file count: 7
    size: 10.00 Bytes
    license: MIT
"`; +exports[` component should render the component with license as string 1`] = `"
    Latest Distribution
    file count: 7
    size: 10.00 Bytes
    license: MIT
"`; diff --git a/src/components/Engines/Engines.test.tsx b/src/components/Engines/Engines.test.tsx index 3339b2e..4756588 100644 --- a/src/components/Engines/Engines.test.tsx +++ b/src/components/Engines/Engines.test.tsx @@ -1,12 +1,31 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import Engine from './Engines'; jest.mock('./img/node.png', () => ''); jest.mock('../Install/img/npm.svg', () => ''); +const mockPackageMeta = jest.fn(() => ({ + latest: { + homepage: 'https://verdaccio.tld', + bugs: { + url: 'https://verdaccio.tld/bugs', + }, + dist: { + tarball: 'https://verdaccio.tld/download', + }, + }, +})); + +jest.mock('../../pages/Version', () => ({ + DetailContextConsumer: component => { + return component.children({ packageMeta: mockPackageMeta() }); + }, +})); + describe(' component', () => { beforeEach(() => { - jest.resetModules(); + jest.resetAllMocks(); }); test('should render the component in default state', () => { @@ -19,14 +38,10 @@ describe(' component', () => { }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); - const Engines = require('./Engines').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toMatchSnapshot(); }); @@ -35,14 +50,10 @@ describe(' component', () => { latest: {}, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); - const Engines = require('./Engines').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toEqual(''); }); @@ -53,14 +64,10 @@ describe(' component', () => { }, }; - jest.doMock('../../pages/version/Version', () => ({ - DetailContextConsumer: component => { - return component.children({ packageMeta }); - }, - })); + // @ts-ignore + mockPackageMeta.mockImplementation(() => packageMeta); - const Engines = require('./Engines').default; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.html()).toEqual(''); }); }); diff --git a/src/components/Engines/Engines.tsx b/src/components/Engines/Engines.tsx index 39f9ff1..875bf2f 100644 --- a/src/components/Engines/Engines.tsx +++ b/src/components/Engines/Engines.tsx @@ -5,7 +5,7 @@ import Grid from '@material-ui/core/Grid'; import List from '@material-ui/core/List'; import ListItemText from '@material-ui/core/ListItemText'; -import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; +import { VersionPageConsumerProps, DetailContextConsumer } from '../../pages/Version'; import { Heading, EngineListItem } from './styles'; // @ts-ignore import node from './img/node.png'; @@ -60,8 +60,8 @@ class Engine extends Component { private renderListItems = (heading, text) => { return ( - {text.split('-').join(' ')}}> - + {text.split('-').join(' ')}}> + {ICONS[text]} diff --git a/src/components/Engines/__snapshots__/Engines.test.tsx.snap b/src/components/Engines/__snapshots__/Engines.test.tsx.snap index f3ec735..ec651b4 100644 --- a/src/components/Engines/__snapshots__/Engines.test.tsx.snap +++ b/src/components/Engines/__snapshots__/Engines.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` component should render the component in default state 1`] = `"

    node JS

  • >= 0.1.98

    NPM version

  • >3
"`; +exports[` component should render the component in default state 1`] = `"
    node JS
    >= 0.1.98
    NPM version
    >3
"`; diff --git a/src/components/Footer/__snapshots__/Footer.test.tsx.snap b/src/components/Footer/__snapshots__/Footer.test.tsx.snap index 7320406..aa1ccfc 100644 --- a/src/components/Footer/__snapshots__/Footer.test.tsx.snap +++ b/src/components/Footer/__snapshots__/Footer.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`