diff --git a/package-lock.json b/package-lock.json index 0ba165c..85f2917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2595,10 +2595,14 @@ } } }, - "@sheerun/mutationobserver-shim": { - "version": "0.3.3", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", - "integrity": "sha1-VAXujkRO0hLbROeTUfDHClgqriU=" + "@sinonjs/commons": { + "version": "1.7.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@sinonjs/commons/-/commons-1.7.2.tgz", + "integrity": "sha1-UF9Vx04CcrQ/bFLYGUa+1wWPwOI=", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", @@ -2710,23 +2714,21 @@ } }, "@testing-library/dom": { - "version": "6.16.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@testing-library/dom/-/dom-6.16.0.tgz", - "integrity": "sha1-BK2iftdK1MDw2YShJFuymx/ZC6k=", + "version": "7.2.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@testing-library/dom/-/dom-7.2.2.tgz", + "integrity": "sha1-MKsJzKEy/kmyymHM2e14XF8Kb8U=", "requires": { - "@babel/runtime": "^7.8.4", - "@sheerun/mutationobserver-shim": "^0.3.2", - "@types/testing-library__dom": "^6.12.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__dom": "^7.0.0", "aria-query": "^4.0.2", - "dom-accessibility-api": "^0.3.0", - "pretty-format": "^25.1.0", - "wait-for-expect": "^3.0.2" + "dom-accessibility-api": "^0.4.2", + "pretty-format": "^25.1.0" }, "dependencies": { "@jest/types": { - "version": "25.3.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha1-iPlLJ3odAo/XEXvB90RR4PwhMec=", + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha1-TWpHk/e5WZ/DaAh3uFapfbzPKp0=", "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", @@ -2788,11 +2790,11 @@ "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=" }, "pretty-format": { - "version": "25.3.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha1-0KT5iP9KbNNQNC/au7gJrrTUmtU=", + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha1-eHPB13T2gsNLjUi2dDor8qxVeRo=", "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -2825,13 +2827,23 @@ } }, "@testing-library/react": { - "version": "9.5.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@testing-library/react/-/react-9.5.0.tgz", - "integrity": "sha1-cVMWVaeJC2Hnehs5RS++3wRyyl4=", + "version": "10.0.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@testing-library/react/-/react-10.0.4.tgz", + "integrity": "sha1-jg4pnNkazGJtge2Eif3BPfhkwx0=", "requires": { - "@babel/runtime": "^7.8.4", - "@testing-library/dom": "^6.15.0", - "@types/testing-library__react": "^9.1.2" + "@babel/runtime": "^7.9.6", + "@testing-library/dom": "^7.2.2", + "@types/testing-library__react": "^10.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.6", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha1-qRAutcre3z8x0IqezylK94J+op8=", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@testing-library/user-event": { @@ -2999,9 +3011,9 @@ } }, "@types/react-dom": { - "version": "16.9.6", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/react-dom/-/react-dom-16.9.6.tgz", - "integrity": "sha1-nn+D2QVmUhzCCDviJ3xnEtyvdUw=", + "version": "16.9.7", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/react-dom/-/react-dom-16.9.7.tgz", + "integrity": "sha1-YIRNSM4lLXstzPDHu5NxMOJ8DNI=", "requires": { "@types/react": "*" } @@ -3018,17 +3030,93 @@ "integrity": "sha1-CoUdO9lkmPolwzq3J47TvWXwbD4=" }, "@types/testing-library__dom": { - "version": "6.14.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/testing-library__dom/-/testing-library__dom-6.14.0.tgz", - "integrity": "sha1-Gu3oMctO1KOYRI31osVLVKNlZE4=", + "version": "7.0.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/testing-library__dom/-/testing-library__dom-7.0.2.tgz", + "integrity": "sha1-KQb4oNzliwdGxqtgb3hr0G/mlA4=", "requires": { - "pretty-format": "^24.3.0" + "pretty-format": "^25.1.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha1-TWpHk/e5WZ/DaAh3uFapfbzPKp0=", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/yargs/-/yargs-15.0.4.tgz", + "integrity": "sha1-fl0PjKJenVhJ8upEPPfEAt7Ngpk=", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha1-kK51xCTQCNJiTFvynq0xd+v881k=", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ=", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=" + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha1-eHPB13T2gsNLjUi2dDor8qxVeRo=", + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha1-aOMlkd9z4lrRxLSRCKLsUHliv9E=", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@types/testing-library__react": { - "version": "9.1.3", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/testing-library__react/-/testing-library__react-9.1.3.tgz", - "integrity": "sha1-NeymHMbqkjVDeW8WA0iCoWA9cwI=", + "version": "10.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/testing-library__react/-/testing-library__react-10.0.1.tgz", + "integrity": "sha1-krtKAjlL9EQo418dopcO13+ANZM=", "requires": { "@types/react-dom": "*", "@types/testing-library__dom": "*", @@ -3036,9 +3124,9 @@ }, "dependencies": { "@jest/types": { - "version": "25.3.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha1-iPlLJ3odAo/XEXvB90RR4PwhMec=", + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha1-TWpHk/e5WZ/DaAh3uFapfbzPKp0=", "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", @@ -3091,11 +3179,11 @@ "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=" }, "pretty-format": { - "version": "25.3.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha1-0KT5iP9KbNNQNC/au7gJrrTUmtU=", + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha1-eHPB13T2gsNLjUi2dDor8qxVeRo=", "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3147,31 +3235,31 @@ } }, "@typescript-eslint/parser": { - "version": "2.28.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@typescript-eslint/parser/-/parser-2.28.0.tgz", - "integrity": "sha1-u3YShu/SsHFHYcq50O5YR88IA4U=", + "version": "2.30.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@typescript-eslint/parser/-/parser-2.30.0.tgz", + "integrity": "sha1-doHDBab0NBriV59eOnWEbCnu6c4=", "requires": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.28.0", - "@typescript-eslint/typescript-estree": "2.28.0", + "@typescript-eslint/experimental-utils": "2.30.0", + "@typescript-eslint/typescript-estree": "2.30.0", "eslint-visitor-keys": "^1.1.0" }, "dependencies": { "@typescript-eslint/experimental-utils": { - "version": "2.28.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz", - "integrity": "sha1-H9CWHNjvZSJoe0xWJkfabnH4gz0=", + "version": "2.30.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz", + "integrity": "sha1-mEXoaMAfOu1mRyxWHUtrrESAndA=", "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.28.0", + "@typescript-eslint/typescript-estree": "2.30.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/typescript-estree": { - "version": "2.28.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz", - "integrity": "sha1-00lJCZ/4EJLDbcJ1tqHqWAcpugA=", + "version": "2.30.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz", + "integrity": "sha1-G46Ei1UUQnAlX/v+TGMpH492ZhU=", "requires": { "debug": "^4.1.1", "eslint-visitor-keys": "^1.1.0", @@ -5940,6 +6028,12 @@ } } }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha1-OUZhE6ngNhEdAvgkibX9awte0jE=", + "dev": true + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -6202,9 +6296,9 @@ } }, "dom-accessibility-api": { - "version": "0.3.0", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz", - "integrity": "sha1-UR5Zk91nO5fIfqR9ug44kvfgyYM=" + "version": "0.4.3", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz", + "integrity": "sha1-k8qQAusiL9WjQ7bl5rnPWSlBHEw=" }, "dom-converter": { "version": "0.2.0", @@ -9016,6 +9110,12 @@ "isobject": "^3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, "is-promise": { "version": "2.1.0", "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/is-promise/-/is-promise-2.1.0.tgz", @@ -9384,6 +9484,391 @@ } } }, + "jest-environment-jsdom-sixteen": { + "version": "1.0.3", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-1.0.3.tgz", + "integrity": "sha1-4iIij6xTfvFcylrUcLGbR9lpAWU=", + "dev": true, + "requires": { + "@jest/fake-timers": "^25.1.0", + "jest-mock": "^25.1.0", + "jest-util": "^25.1.0", + "jsdom": "^16.2.1" + }, + "dependencies": { + "@jest/fake-timers": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/fake-timers/-/fake-timers-25.5.0.tgz", + "integrity": "sha1-RjUuAFM8AkyQwrwq2fKVn38RQYU=", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "lolex": "^5.0.0" + } + }, + "@jest/types": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha1-TWpHk/e5WZ/DaAh3uFapfbzPKp0=", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/@types/yargs/-/yargs-15.0.4.tgz", + "integrity": "sha1-fl0PjKJenVhJ8upEPPfEAt7Ngpk=", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha1-Rs3Tnw+P8IqHZhm1X1rIptx3C0U=", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-walk": { + "version": "7.1.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha1-NF8N/61cc15zc9L+yaECPmpEuD4=", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha1-kK51xCTQCNJiTFvynq0xd+v881k=", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/braces/-/braces-3.0.2.tgz", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ=", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=", + "dev": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha1-WmbPk9LQtmHYC/akT7ZfXC5OChA=", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha1-/2ZaDdvcMYZLCWR/NBY0Q9kLCFI=", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha1-nxJ29bK0Y/IRTT8sdSUK+MGjb0o=", + "dev": true + } + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha1-FWSFpyljqXD11YIar2Qr7yvy25s=", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha1-+0Su+6eT4VdLCvau0oAdBXUp8wQ=", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha1-rlnIoAsSFUOirMZcBDT1ew/BGv8=", + "dev": true + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha1-Ila94U02MpWMRl68ltxGfKB6Kfs=", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha1-QqbcT9M/ACgRduiyN1nKTk+hhfM=", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", + "dev": true + }, + "jest-message-util": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/jest-message-util/-/jest-message-util-25.5.0.tgz", + "integrity": "sha1-6hHZMgTMeul0VuHYcWJRGFuIgOo=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/jest-mock/-/jest-mock-25.5.0.tgz", + "integrity": "sha1-qRpU2r0U437NYWZda24GNgpVOHo=", + "dev": true, + "requires": { + "@jest/types": "^25.5.0" + } + }, + "jest-util": { + "version": "25.5.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha1-McY7XW6QEnTSZKT+yEkjCqP6NbA=", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + } + }, + "jsdom": { + "version": "16.2.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/jsdom/-/jsdom-16.2.2.tgz", + "integrity": "sha1-dvL3VBZGvrRqk49dxHa4hwW+3ys=", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.0.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha1-QV6WcEazp/HRhSd9hKpYIDcmoT8=", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha1-T8sJmb+fvC/L3SEvbWKbmlbDklk=", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha1-9o5OW6GFKsLK3AD0VV//bCq7YXg=", + "dev": true + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha1-7rq5U/o7dgjb6U5drbFciI+maW0=", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/slash/-/slash-3.0.0.tgz", + "integrity": "sha1-ZTm+hwwWWtvVJAIg2+Nh8bxNRjQ=", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha1-aOMlkd9z4lrRxLSRCKLsUHliv9E=", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha1-nfT1fnOcJpMKAYGEiH9K233Kc7I=", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha1-Ayc1ht7xWVrgj+2zjXczzukdJHk=", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha1-PnEEoFt1FGzGD1ZDgLf2g6zxAgo=", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha1-kRG01+qArNQPUnDWZmIa+ni2lRQ=", + "dev": true + }, + "whatwg-url": { + "version": "8.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/whatwg-url/-/whatwg-url-8.0.0.tgz", + "integrity": "sha1-N/JWy3RjmOGbEHvW74ILSuLRWHE=", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.0", + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha1-rlnIoAsSFUOirMZcBDT1ew/BGv8=", + "dev": true + } + } + }, + "ws": { + "version": "7.2.5", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/ws/-/ws-7.2.5.tgz", + "integrity": "sha1-q7E3DUYmpanNedjeQEqhizRl0Q0=", + "dev": true + } + } + }, "jest-environment-node": { "version": "24.9.0", "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/jest-environment-node/-/jest-environment-node-24.9.0.tgz", @@ -11078,6 +11563,15 @@ "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/loglevel/-/loglevel-1.6.7.tgz", "integrity": "sha1-s+A0IzGIxouIn1uGJBUwb1ZeLFY=" }, + "lolex": { + "version": "5.1.2", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha1-lTaU0JjOfAe8XtbQ5CvGwMbVo2c=", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/loose-envify/-/loose-envify-1.4.0.tgz", @@ -20418,6 +20912,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", + "dev": true + }, "type-fest": { "version": "0.8.1", "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/type-fest/-/type-fest-0.8.1.tgz", @@ -20765,11 +21265,6 @@ "xml-name-validator": "^3.0.0" } }, - "wait-for-expect": { - "version": "3.0.2", - "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/wait-for-expect/-/wait-for-expect-3.0.2.tgz", - "integrity": "sha1-0vFLL3t3jJuCFEEJyPqJzqrapGM=" - }, "walker": { "version": "1.0.7", "resolved": "https://inditex.jfrog.io/inditex/api/npm/node-public/walker/-/walker-1.0.7.tgz", diff --git a/package.json b/package.json index 9a96077..48d2b36 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,8 @@ "@babel/preset-env": "^7.1.0", "@babel/preset-react": "^7.0.0", "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^9.5.0", + "@testing-library/react": "^10.0.4", "@testing-library/user-event": "^7.2.1", - "@typescript-eslint/parser": "^2.28.0", "canvg": "^3.0.6", "codemirror": "^5.52.2", "cypher-codemirror": "^1.1.6", @@ -41,7 +40,8 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "react-scripts test --coverage --watchAll=false", + "test": "react-scripts test --coverage --watchAll=false --env=jest-environment-jsdom-sixteen --silent && codecov", + "test:nocov": "react-scripts test --watchAll=false --env=jest-environment-jsdom-sixteen --silent", "eject": "react-scripts eject", "lint": "eslint . --ext js" }, @@ -72,6 +72,22 @@ "last 1 safari version" ] }, + "jest": { + "collectCoverageFrom": [ + "src/**/**", + "!**/index.js", + "!**/serviceWorker.js", + "!**/**/*.json", + "!**/global/utils/hooks/**", + "!**/global/components/chart/**", + "!**/**/autosuggest.js", + "!**/tests/**", + "!**/assets/**", + "!**/CypherCodeMirror.js", + "!**/cypher/**", + "!**/Download/utils/**" + ] + }, "release": { "plugins": [ "@semantic-release/commit-analyzer", @@ -97,6 +113,8 @@ "@semantic-release/github": "^7.0.5", "@semantic-release/npm": "^7.0.5", "@semantic-release/release-notes-generator": "^9.0.1", + "@typescript-eslint/parser": "^2.30.0", + "jest-environment-jsdom-sixteen": "^1.0.3", "semantic-release": "^17.0.6" } } diff --git a/src/App.js b/src/App.js index 196db8f..1584f55 100644 --- a/src/App.js +++ b/src/App.js @@ -9,11 +9,12 @@ import Sidebar from './components/sidebar/Sidebar'; import { doLogout } from './service/neo.service'; import { getDBSchema } from './service/schema.service'; + import { cls } from './global/utils'; +import { useAsyncDispatch } from './global/utils/hooks/dispatch'; +import actions from './global/utils/store/actions'; import './App.css'; -import actions from './global/utils/store/actions'; -import { useAsyncDispatch } from './global/utils/hooks/dispatch'; function App() { const [cookies, setCookie] = useCookies(["neo4jDash.sess"]); @@ -53,7 +54,7 @@ function App() { const render = () => { if (loading) { - return share + return share } else { if (!user.loggedIn) { return ( @@ -61,7 +62,7 @@ function App() { ) } else { return ( -
+
{menu ? : null} diff --git a/src/App.test.js b/src/App.test.js index a865cb5..5fe7ea7 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,18 +1,110 @@ import React from 'react'; -import { render } from '@testing-library/react'; import App from './App'; -import { setup } from './global/utils/tests/store.mock'; +import { getMockProvider } from './global/utils/tests/store.mock'; +import * as service from './service/neo.service'; +import actions from './global/utils/store/actions'; +import { Cookies } from 'react-cookie'; +import { cleanup } from '@testing-library/react'; +import { Simulate } from 'react-dom/test-utils'; + +jest.mock('./components/comander/Comander.js', () => { + return function MockComander() { + return
+ } +}); + +service.doLogout = jest.fn(); +service.getQuery = jest.fn(); describe('App test suite', () => { - beforeAll(() => { + let rendered; + + beforeEach(() => { + const cookies = new Cookies(); + cookies.set('neo4jDash.sess', { sessionId: 'test' }); + ({ rendered } = getMockProvider(, { user: { loggedIn: false } })); + }) + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("renders App if user is logged in", async () => { + const { findByTestId } = rendered; + + const linkElement = await findByTestId("app"); + expect(linkElement).toBeDefined(); + }); +}); + +describe('App user loggedIn test suite', () => { + let rendered; + let store; + let act; + + beforeEach(() => { + ({ rendered, store, act } = getMockProvider(, { user: { loggedIn: true }})); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + test("renders App if user is logged in", async () => { + const { getByTestId } = rendered; + const appElement = getByTestId('app'); + expect(appElement).toBeDefined(); + }); + + test("trigger logout if user click logout button", async () => { + const { findByTestId } = rendered; + act(() => { + store.dispatch(actions.user.logOut()); + }) + const linkElement = await findByTestId("login"); + expect(linkElement).toBeDefined(); }) +}); + +describe("App user not logged test suite", () => { + let rendered; + + beforeEach(() => { + ({ rendered } = getMockProvider(, { theme: {}, user: { loggedIn: false } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); - test('renders login button if user is not logged in', () => { - const { MockProvider } = setup({ theme: {}, user: { loggedIn: false } }); - const { getByText } = render(); + test("renders login button if user is not logged in", async () => { + const { getByText } = rendered; const linkElement = getByText("Login"); expect(linkElement).toBeDefined(); }); -}) +}); + +describe("App sidebar test suite", () => { + let rendered; + + beforeEach(() => { + ({ rendered } = getMockProvider(, { theme: {}, user: { loggedIn: true } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }) + + test("sidebar render test", async () => { + const { getByTestId, findByTestId } = rendered; + const trigger = getByTestId('menu-trigger'); + Simulate.click(trigger); + const element = await findByTestId('sidebar'); + expect(element).toBeDefined(); + }) +}); diff --git a/src/components/card/Card.js b/src/components/card/Card.js index 3c09e7e..5fdc9e9 100644 --- a/src/components/card/Card.js +++ b/src/components/card/Card.js @@ -49,6 +49,7 @@ function Card(props) { }, [props.query, user.sessionId]) useEffect(() => { + /* istanbul ignore else */ if (query.current !== props.query) { query.current = props.query; setResults(null); @@ -90,18 +91,28 @@ function Card(props) { return (
QUERY -
props.restoreQuery(null, props.query)}> +
props.restoreQuery(null, props.query)}> {props.query}
- + save_alt {expanded ? "unfold_less" : "unfold_more"} - props.deleteQuery(props.query)}> + props.deleteQuery(props.query)}> close {download ? ( @@ -132,6 +145,7 @@ function Card(props) { - (visElement.current = { svgElement, graphElement, type: "graph" }) + assignVisElement={ + /* istanbul ignore next */ + (svgElement, graphElement) => (visElement.current = { svgElement, graphElement, type: "graph" }) } /> ) : ( - + {error ? error : share} )} diff --git a/src/components/card/Card.test.js b/src/components/card/Card.test.js new file mode 100644 index 0000000..9d1ee9d --- /dev/null +++ b/src/components/card/Card.test.js @@ -0,0 +1,223 @@ +import React from "react"; +import { getMockProvider } from "../../global/utils/tests/store.mock"; +import { cleanup, waitFor, act } from "@testing-library/react"; +import { Simulate } from "react-dom/test-utils"; +import Card from "./Card"; +import * as services from './../../service/neo.service'; + +global.fetch = () => ['test']; + +jest.mock('../../global/components/chart/Chart', () => (props) => ( +
+ ev.target.method === "hover" + ? props.itemHovered(ev.target.value) + : ev.target.method === "selected" ? props.itemSelected(ev.target.value) + : ev.target.method === "graph" ? props.graphStyleCallback(ev.target.value) + :props.setSummary(ev.target.value) + } + >
+)); + +jest.mock("./components/Summary/Summary.js", () => (props) => ( +
+)); + +describe('Card component test suite', () => { + let rendered; + + beforeEach(() => { + jest.spyOn(services, "getQuery").mockImplementation(() => "test"); + act(() => + ({ rendered } = getMockProvider(, + { user: { loggedIn: true } })) + ) + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('Card should render', () => { + const { getByTestId } = rendered; + const element = getByTestId('card'); + expect(element).toBeDefined(); + }); + + test('toggleExpand test', () => { + const { getByTestId } = rendered; + const trigger = getByTestId('toggle-expand'); + const element = getByTestId('card'); + Simulate.click(trigger); + expect(element.className.includes('expanded')).toBeTruthy(); + }); + + test('toggleFullscreen test', () => { + const { getByTestId } = rendered; + const trigger = getByTestId('toggle-fullscreen'); + const element = getByTestId('card'); + Simulate.click(trigger); + expect(element.className.includes('fullscreen')).toBeTruthy(); + }); + + test("toggleDownload test", () => { + const { getByTestId } = rendered; + const trigger = getByTestId("toggle-download"); + Simulate.click(trigger); + const element = getByTestId("download"); + expect(element).toBeDefined(); + }); +}); + +describe("Card component with queries test suite", () => { + let spy; + + beforeEach(() => { + spy = jest.spyOn(services, 'getQuery').mockImplementation(() => 'test'); + act(() => + getMockProvider(, { user: { loggedIn: true } }) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("getQuery should have been called", () => { + expect(spy).toHaveBeenCalled(); + }); +}); + +describe("Card chart test suite", () => { + let rendered; + let storeSpy; + + beforeEach(() => { + jest.spyOn(services, "getQuery").mockImplementation(() => "test"); + act(() => + ({ rendered, storeSpy } = getMockProvider(, { user: { loggedIn: true } }, true)) + ) + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("setSelected should be called", async () => { + const { getByTestId } = rendered; + const element = getByTestId('chart'); + const summary = getByTestId('summary') + Simulate.change(element, { target: { method: 'selected', value: 'test' } }); + await waitFor(() => expect(summary[Object.keys(summary)[1]].selected).toEqual("test")); + }); + + test("hovered should be called", async () => { + const { getByTestId } = rendered; + const element = getByTestId("chart"); + const summary = getByTestId("summary"); + Simulate.change(element, { target: { method: "hover", value: { type: 'relationship' } } }); + await waitFor(() => expect(summary[Object.keys(summary)[1]].selected).toEqual({ type: "relationship" })); + }); + + test("hovered with canvas should be called", async () => { + const { getByTestId } = rendered; + const element = getByTestId("chart"); + const summary = getByTestId("summary"); + Simulate.change(element, { target: { method: "hover", value: { type: "canvas" } } }); + await waitFor(() => expect(summary[Object.keys(summary)[1]].selected).toEqual(null)); + }); + + test("hovered not in type should be called", async () => { + const { getByTestId } = rendered; + const element = getByTestId("chart"); + const summary = getByTestId("summary"); + Simulate.change(element, { target: { method: "hover", value: { type: "test" } } }); + await waitFor(() => expect(summary[Object.keys(summary)[1]].selected).toEqual(null)); + }); + + test("setSummary should be called", async () => { + const { getByTestId } = rendered; + const element = getByTestId("chart"); + const summary = getByTestId("summary"); + Simulate.change(element, { target: { method: "summary", value: "test" } }); + await waitFor(() => expect(summary[Object.keys(summary)[1]].summary).toEqual("test")); + }); + + test("graphStyleCallback should be called", async () => { + const { getByTestId } = rendered; + const element = getByTestId("chart"); + Simulate.change(element, { target: { method: "graph", value: { toSheet: () => '' } } }); + expect(storeSpy).toHaveBeenCalled(); + }); +}); + +describe('Card props test suite', () => { + let rendered; + const restore = jest.fn(); + const deleteProp = jest.fn(); + + beforeEach(() => { + jest.spyOn(services, "getQuery").mockImplementation(() => "test"); + act( + () => + ({ rendered } = getMockProvider( + , + { user: { loggedIn: true } } + )) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('restoreQuery test', () => { + const { getByTestId } = rendered; + const trigger = getByTestId('restore-trigger'); + Simulate.click(trigger); + expect(restore).toHaveBeenCalled(); + }); + + test("deleteQuery test", () => { + const { getByTestId } = rendered; + const trigger = getByTestId("delete-query"); + Simulate.click(trigger); + expect(deleteProp).toHaveBeenCalled(); + }); +}); + +describe("Card fetch error test suite", () => { + let rendered; + + beforeEach(() => { + jest.spyOn(services, "getQuery").mockImplementation(() => { + throw new Error('test'); + }); + act( + () => + ({ rendered } = getMockProvider(, { + user: { loggedIn: true }, + })) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("restoreQuery test", () => { + const { getByTestId } = rendered; + const error = getByTestId("error"); + expect(error.textContent).toBe('test: Error: test'); + }); +}); diff --git a/src/components/card/components/Configurator/Configurator.js b/src/components/card/components/Configurator/Configurator.js index 2583e5c..bb53139 100644 --- a/src/components/card/components/Configurator/Configurator.js +++ b/src/components/card/components/Configurator/Configurator.js @@ -22,6 +22,7 @@ function Configurator(props) { }; return (
{ const onClick = () => { updateStyle(props.styleForItem.selector, { - caption: cap[0].startsWith('<') ? cap[0] : `{${cap[0]}}` + caption: + /* istanbul ignore next */ + cap[0].startsWith("<") ? cap[0] : `{${cap[0]}}`, }); }; return (
{ return ( - + Sizes @@ -80,6 +84,7 @@ function Configurator(props) { Properties {renderCaptionSelector(props.properties, styles.property, (prop) => + /* istanbul ignore next */ props.styleForItem.get("caption").includes(prop) ? styles.propertyActive : "" )} @@ -90,7 +95,7 @@ function Configurator(props) { const renderRelConfig = () => { return ( - + Line width @@ -120,6 +125,7 @@ function Configurator(props) { Properties {renderCaptionSelector(props.properties, styles.property, (prop) => + /* istanbul ignore next */ props.styleForItem.get("caption").includes(prop) ? styles.propertyActive : "" )} diff --git a/src/components/card/components/Configurator/Configurator.test.js b/src/components/card/components/Configurator/Configurator.test.js new file mode 100644 index 0000000..b842de2 --- /dev/null +++ b/src/components/card/components/Configurator/Configurator.test.js @@ -0,0 +1,163 @@ +import React from "react"; +import { getMockProvider } from "../../../../global/utils/tests/store.mock"; +import { cleanup, act } from "@testing-library/react"; +import Configurator from "./Configurator"; +import { Simulate } from "react-dom/test-utils"; + +let styleForItem = { get: () => [] }; +let styleForItemWithProps = { + get: (prop) => { + switch(prop) { + case 'diameter': + return '10px'; + case 'color': + return "#ECB5C9"; + case 'shaft-width': + return '1px' + case 'caption': + return []; + default: + break; + } + } +} + +describe('Node Configurator component test suite', () => { + let rendered; + + beforeEach(() => { + act(() => + ({ rendered } = getMockProvider(, + { user: { loggedIn: true } }, true)) + ) + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('Configuratior should render', () => { + const { getByTestId } = rendered; + const element = getByTestId('node-config'); + expect(element).toBeDefined(); + }); +}); + +describe("Rel Configurator component test suite", () => { + let rendered; + + beforeEach(() => { + act( + () => + ({ rendered } = getMockProvider( + , + { user: { loggedIn: true } }, + true + )) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("Configuratior should render", () => { + const { getByTestId } = rendered; + const element = getByTestId("rel-config"); + expect(element).toBeDefined(); + }); +}); + +describe("Node Configurator with props component test suite", () => { + let rendered; + + beforeEach(() => { + act( + () => + ({ rendered } = getMockProvider( + , + { user: { loggedIn: true } }, + true + )) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("Configuratior should render", () => { + const { getByTestId } = rendered; + const element = getByTestId("node-config"); + expect(element).toBeDefined(); + }); +}); + +describe("Rel Configurator component test suite", () => { + let rendered; + let storeSpy; + + beforeEach(() => { + act( + () => + ({ rendered, storeSpy } = getMockProvider( + , + { user: { loggedIn: true } }, true + )) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("Configuratior should render", () => { + const { getByTestId } = rendered; + const element = getByTestId("rel-config"); + expect(element).toBeDefined(); + }); + + test("caption click test", () => { + const { getByTestId } = rendered; + const element = getByTestId("caption-trigger"); + Simulate.click(element); + expect(storeSpy).toHaveBeenCalled(); + }); + + test("prop click test", async () => { + const { findAllByTestId } = rendered; + const element = await findAllByTestId("prop-trigger"); + Simulate.click(element[0]); + expect(storeSpy).toHaveBeenCalled(); + }); +}); + +describe("noType Configurator component test suite", () => { + beforeEach(() => { + act( + () => + getMockProvider( + , + { user: { loggedIn: true } }, + true + ) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("Configuratior should render", () => { + expect(true).toBeTruthy(); + }); +}); diff --git a/src/components/card/components/Download/Download.js b/src/components/card/components/Download/Download.js index b700a11..29931f8 100644 --- a/src/components/card/components/Download/Download.js +++ b/src/components/card/components/Download/Download.js @@ -3,11 +3,11 @@ import { saveAs } from "file-saver"; import { stringifyResultArray, transformResultRecordsToResultArray } from './utils/utils'; import { CSVSerializer } from './utils/csvHelper'; +import { downloadPNGFromSVG, downloadSVG } from './utils/exporting/imageUtils'; import { ColumnLayout } from '../../../../global/layouts'; import { csvFormat, recordToJSONMapper } from '../../../../global/components/chart/utils/cypher-utils'; import styles from './Download.module.css'; -import { downloadPNGFromSVG, downloadSVG } from './utils/exporting/imageUtils'; function Download(props) { @@ -46,17 +46,17 @@ function Download(props) { } return ( - -
+ +
Export to CSV
-
+
Export to JSON
-
+
Export to PNG
-
+
Export to SVG
diff --git a/src/components/card/components/Download/Download.test.js b/src/components/card/components/Download/Download.test.js new file mode 100644 index 0000000..ad012c3 --- /dev/null +++ b/src/components/card/components/Download/Download.test.js @@ -0,0 +1,68 @@ +import React from "react"; +import { getMockProvider } from "../../../../global/utils/tests/store.mock"; +import { cleanup, act } from "@testing-library/react"; +import Download from "./Download"; +import { Simulate } from "react-dom/test-utils"; +import * as imgUtils from "./utils/exporting/imageUtils"; +const fileSaver = require('file-saver'); + +describe('Download component test suite', () => { + let rendered; + const results = { + records: [ + { keys: [ 'test' ], _fields: ['test'], get: () => 'test' } + ] + }; + let spy; + let pngSpy; + let svgSpy + + beforeEach(() => { + spy = jest.spyOn(fileSaver, "saveAs").mockImplementation(() => jest.fn()); + pngSpy = jest.spyOn(imgUtils, 'downloadPNGFromSVG').mockImplementation(() => jest.fn()); + svgSpy = jest.spyOn(imgUtils, "downloadSVG").mockImplementation(() => jest.fn()); + act(() => + ({ rendered } = getMockProvider(, + { user: { loggedIn: true } })) + ) + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('Download should render', () => { + const { getByTestId } = rendered; + const element = getByTestId('download'); + expect(element).toBeDefined(); + }); + + test('DownloadCSV test', () => { + const { getByTestId } = rendered; + const trigger = getByTestId('csv-trigger'); + Simulate.click(trigger); + expect(spy).toHaveBeenCalledWith(new Blob(), 'export.csv'); + }); + + test('DownloadJSON test', () => { + const { getByTestId } = rendered; + const trigger = getByTestId("json-trigger"); + Simulate.click(trigger); + expect(spy).toHaveBeenCalledWith(new Blob(), 'records.json'); + }) + + test("exportPNG test", () => { + const { getByTestId } = rendered; + const trigger = getByTestId("png-trigger"); + Simulate.click(trigger); + expect(pngSpy).toHaveBeenCalled(); + }); + + test("exportSVG test", () => { + const { getByTestId } = rendered; + const trigger = getByTestId("svg-trigger"); + Simulate.click(trigger); + expect(svgSpy).toHaveBeenCalled(); + }) +}); diff --git a/src/components/card/components/Download/utils/utils.js b/src/components/card/components/Download/utils/utils.js index 2a172e0..9ac49a4 100644 --- a/src/components/card/components/Download/utils/utils.js +++ b/src/components/card/components/Download/utils/utils.js @@ -1,5 +1,4 @@ import { stringModifier, extractRecordsToResultArray, flattenGraphItemsInResultArray } from "../../../../../global/components/chart/utils/cypher-utils"; -import { stringifyMod } from "../../../../../global/utils"; import neo4j from "neo4j-driver"; /** @@ -12,7 +11,7 @@ export const stringifyResultArray = (formatter = stringModifier, arr = []) => { return arr.map((col) => { if (!col) return col; return col.map((fVal) => { - return stringifyMod(fVal, formatter); + return _stringifyMod(fVal, formatter); }); }); }; @@ -24,3 +23,67 @@ export const transformResultRecordsToResultArray = (records) => { .map(flattenGraphItemsInResultArray.bind(null, neo4j.types, neo4j.isInt))[0] : undefined; }; + +const _stringifyMod = (value, modFn = null, prettyLevel = false, skipOpeningIndentation = false) => { + prettyLevel = !prettyLevel ? false : prettyLevel === true ? 1 : parseInt(prettyLevel); + const nextPrettyLevel = prettyLevel ? prettyLevel + 1 : false; + const newLine = prettyLevel ? "\n" : ""; + const indentation = prettyLevel && !skipOpeningIndentation ? Array(prettyLevel).join(" ") : ""; + const endIndentation = prettyLevel ? Array(prettyLevel).join(" ") : ""; + const propSpacing = prettyLevel ? " " : ""; + const toString = Object.prototype.toString; + const isArray = + Array.isArray || + function (a) { + return toString.call(a) === "[object Array]"; + }; + const escMap = { + '"': '"', + "\\": "\\", + "\b": "\b", + "\f": "\f", + "\n": "\n", + "\r": "\r", + "\t": "\t", + }; + const escFunc = function (m) { + return escMap[m] || "\\u" + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); + }; + const escRE = /[\\"\u0000-\u001F\u2028\u2029]/g; // eslint-disable-line no-control-regex + if (modFn) { + const modVal = modFn && modFn(value); + if (typeof modVal !== "undefined") return indentation + modVal; + } + if (value == null) return indentation + "null"; + if (typeof value === "number") { + return indentation + (isFinite(value) ? value.toString() : "null"); + } + if (typeof value === "boolean") return indentation + value.toString(); + if (typeof value === "object") { + if (typeof value.toJSON === "function") { + return _stringifyMod(value.toJSON(), modFn, nextPrettyLevel); + } else if (isArray(value)) { + let hasValues = false; + let res = ""; + for (let i = 0; i < value.length; i++) { + hasValues = true; + res += (i ? "," : "") + newLine + _stringifyMod(value[i], modFn, nextPrettyLevel); + } + return indentation + "[" + res + (hasValues ? newLine + endIndentation : "") + "]"; + } else if (toString.call(value) === "[object Object]") { + const tmp = []; + for (const k in value) { + if (value.hasOwnProperty(k)) { + tmp.push( + _stringifyMod(k, modFn, nextPrettyLevel) + + ":" + + propSpacing + + _stringifyMod(value[k], modFn, nextPrettyLevel, true) + ); + } + } + return indentation + "{" + newLine + tmp.join("," + newLine) + newLine + endIndentation + "}"; + } + } + return indentation + '"' + value.toString().replace(escRE, escFunc) + '"'; +}; diff --git a/src/components/card/components/Summary/Summary.js b/src/components/card/components/Summary/Summary.js index 0fbf03d..511e82d 100644 --- a/src/components/card/components/Summary/Summary.js +++ b/src/components/card/components/Summary/Summary.js @@ -35,6 +35,7 @@ function Summary (props) { style.summary, configItem && configItem.label === key ? style.summaryActive : "" )} + data-testid="label-trigger" dist="middle" key={idx} onClick={() => @@ -60,6 +61,7 @@ function Summary (props) { }); return ( + { item.type === 'node' ? item.item.labels.join(' ') : item.item.type } @@ -131,7 +133,7 @@ function Summary (props) { }; return ( - + {label} @@ -141,7 +143,7 @@ function Summary (props) { } return ( - + { renderLabels(props.summary) } { renderRelations(props.summary) } { renderProperties(props.item) } diff --git a/src/components/card/components/Summary/Summary.test.js b/src/components/card/components/Summary/Summary.test.js new file mode 100644 index 0000000..fccb585 --- /dev/null +++ b/src/components/card/components/Summary/Summary.test.js @@ -0,0 +1,128 @@ +import React from "react"; +import { getMockProvider } from "../../../../global/utils/tests/store.mock"; +import { cleanup, act } from "@testing-library/react"; +import { Simulate } from "react-dom/test-utils"; +import Summary from "./Summary"; + +const summary = { + labels: { + node: { properties: { prop: "test" } }, + }, + relTypes: { + relationship: { properties: { prop: "test" } }, + }, +}; +const item = { + item: { labels: ["test"], properties: [ { key: 'test', value: 'test' } ] }, + type: "node", +}; + +describe("Summary wighout config component test suite", () => { + let rendered; + + beforeEach(() => { + act(() => ({ rendered } = getMockProvider(, { user: { loggedIn: true } }, true))); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("component should render", () => { + const { getByTestId } = rendered; + const element = getByTestId("summary"); + expect(element).toBeDefined(); + }); +}); + +describe("Summary canvas item component test suite", () => { + let rendered; + const _item = JSON.parse(JSON.stringify(item)); + _item.type = 'canvas'; + + beforeEach(() => { + act(() => ({ rendered } = getMockProvider(, { user: { loggedIn: true } }, true))); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("component should render", () => { + const { getByTestId } = rendered; + try { + getByTestId("properties"); + } catch(err) { + expect(true).toBeTruthy(); + } + }); +}); + +describe("Summary rel item component test suite", () => { + let rendered; + const _item = JSON.parse(JSON.stringify(item)); + _item.type = "rel"; + + beforeEach(() => { + act(() => ({ rendered } = getMockProvider(, { user: { loggedIn: true } }, true))); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("component should render", () => { + const { getByTestId } = rendered; + const element = getByTestId("properties"); + expect(element).toBeDefined(); + }); +}); + +describe('Summary component test suite', () => { + let rendered; + + beforeEach(() => { + act(() => + ({ rendered } = getMockProvider(, + { user: { loggedIn: true } }, true)) + ) + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('component should render', () => { + const { getByTestId } = rendered; + const element = getByTestId('summary'); + const props = getByTestId('properties'); + expect(element).toBeDefined(); + expect(props).toBeDefined(); + }) + + test('label click test', async () => { + const { getByTestId, findByTestId } = rendered; + const trigger = getByTestId('label-trigger'); + Simulate.click(trigger); + const element = await findByTestId('configurator'); + expect(element).toBeDefined(); + expect(trigger.className.includes("summaryActive")).toBeTruthy(); + Simulate.click(trigger); + expect(trigger.className.includes('summaryActive')).toBeFalsy(); + }); + + test("rel click test", async () => { + const { getByTestId, findByTestId } = rendered; + const trigger = getByTestId("rel-trigger"); + Simulate.click(trigger); + const element = await findByTestId("configurator"); + expect(element).toBeDefined(); + expect(trigger.className.includes("summaryActive")).toBeTruthy(); + Simulate.click(trigger); + expect(trigger.className.includes("summaryActive")).toBeFalsy(); + }); +}); diff --git a/src/components/comander/Comander.codemirror.test.js b/src/components/comander/Comander.codemirror.test.js new file mode 100644 index 0000000..1d17f38 --- /dev/null +++ b/src/components/comander/Comander.codemirror.test.js @@ -0,0 +1,34 @@ +import React, * as react from "react"; +import { getMockProvider } from "../../global/utils/tests/store.mock"; +import { Simulate } from "react-dom/test-utils"; +import Comander from "./Comander"; +import { waitFor, cleanup } from "@testing-library/react"; + +jest.mock("./CypherCodeMirror.js", () => { + const React = require("react"); + return React.forwardRef((props, ref) => ( +
props.onChange(ev.target.value)}>
+ )); +}); + +describe("Codemirror tests suite", () => { + let rendered; + let fn = jest.fn(); + + beforeEach(() => { + jest.spyOn(react, 'useState').mockImplementation(() => ['', fn]); + ({ rendered } = getMockProvider(, { theme: {}, user: { loggedIn: true } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("setQuery test", async () => { + const { getByTestId } = rendered; + const codemirror = getByTestId("codemirror"); + Simulate.change(codemirror, { target: { value: 'test' } }); + await waitFor(() => expect(fn).toHaveBeenCalledWith('test')); + }); +}); diff --git a/src/components/comander/Comander.js b/src/components/comander/Comander.js index 45964a5..719d3cd 100644 --- a/src/components/comander/Comander.js +++ b/src/components/comander/Comander.js @@ -11,8 +11,7 @@ import { useEventListener } from "../../global/utils/hooks/events"; import styles from './Comander.module.css'; - -function Comander(props) { +function Comander() { const [query, setQuery] = useState(""); const [queries, setQueries] = useState([]); const [showStored, setShowStored] = useState(false); @@ -21,7 +20,7 @@ function Comander(props) { const schema = useRef(neo4jSchema); const cm = useRef(null); const storedQueries = useRef([]); - const [theme, fullscreen, dbSchema] = useSelector(state => [state.theme, state.theme.fullscreen, state.dbSchema]); + const [theme, dbSchema] = useSelector(state => [state.theme, state.dbSchema]); useEffect(() => { Object.keys(dbSchema) @@ -77,7 +76,6 @@ function Comander(props) { switch (event.keyCode) { case 40: // ArrowDown setHighlightedSuggestion((hs) => { - console.log((hs === storedQueries.current.length - 1 ? 0 : hs + 1)) return (hs === storedQueries.current.length - 1 ? 0 : hs + 1) }); break; @@ -85,7 +83,7 @@ function Comander(props) { setHighlightedSuggestion((hs) => (hs <= 0 ? storedQueries.current.length : hs) - 1); break; case 13: // Enter - showStored && ! query.length && selectQuery(event, storedQueries.current[highlightedSuggestion]); + showStored && !query.length && selectQuery(event, storedQueries.current[highlightedSuggestion]); (!showStored || query.length) && handlePlay(); break; default: @@ -94,29 +92,37 @@ function Comander(props) { }); return ( - + setQuery(value)} - onParsed={() => null} ref={(el) => setEditor(el)} /> - + play_arrow - + history -
+
Last queries
    {storedQueries.current.map((q, i) => (
  • selectQuery(e, q)} className={highlightedSuggestion === i ? styles.suggestionActive : ""} @@ -136,6 +142,7 @@ function Comander(props) {
jest.fn(); + +jest.mock('cypher-codemirror', () => ({ createCypherEditor: () => ({ + editor: { on: jest.fn(), setValue: jest.fn(), setOption: jest.fn(), lineCount: jest.fn(), setCursor: jest.fn() }, + editorSupport: { setSchema: jest.fn() } + }) +})); + +jest.mock("./../timeline/Timeline.js", () => + (props) => ( +
+ ev.target.method === "delete" ? props.deleteQuery(ev.target.value) : props.selectQuery(null, ev.target.value) + } + >
+ ) +); + +describe('Comander component test suite', () => { + let rendered; + let store; + + beforeEach(() => { + ({ rendered, store } = getMockProvider(, + { theme: {}, user: { loggedIn: true } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('should render component', () => { + const { getByTestId } = rendered; + const element = getByTestId('comander'); + expect(element).toBeDefined(); + }); + + test('should load dbSchema', async () => { + const spy = jest.spyOn(cypher, 'toSchema').mockImplementation(() => []); + store.dispatch(actions.db.setProperties({ labels: 'test' })); + await waitFor(() => expect(spy).toHaveBeenCalled()) + }); + + test('handlePlay test', async () => { + jest.spyOn(utils, 'concatUniqueStrings').mockImplementation(() => [ 'test' ]) + const spy = jest.spyOn(window.localStorage.__proto__, "setItem").mockImplementation(() => jest.fn()); + const { getByTestId } = rendered; + const trigger = getByTestId('play-trigger'); + Simulate.click(trigger); + await waitFor(() => expect(spy).toHaveBeenCalled()); + }); + + test('showStored test', async () => { + const { getByTestId, findByTestId } = rendered; + const trigger = getByTestId('stored-trigger'); + Simulate.click(trigger); + const element = await findByTestId('show-stored'); + expect(element.className.includes('listActive')).toBeTruthy(); + }); +}); + +describe('Comander queries test suite', () => { + let rendered; + let spy = jest.fn(); + + beforeEach(() => { + jest.spyOn(window.localStorage.__proto__, "getItem").mockImplementation(() => ["test"]); + JSON.parse = spy.mockImplementation(() => ["test"]); + ({ rendered } = getMockProvider(, { theme: {}, user: { loggedIn: true } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test("should load localStorage queries", async () => { + await waitFor(() => expect(spy).toHaveBeenCalledWith(["test"])); + }); + + test("selectQuery test", async () => { + const { getByTestId, findByTestId } = rendered; + const trigger = getByTestId("stored-trigger"); + Simulate.click(trigger); + const queryTrigger = await findByTestId("select-query-trigger"); + Simulate.click(queryTrigger); + const element = await findByTestId("show-stored"); + await waitFor(() => expect(element.className.includes("listActive")).toBeFalsy()); + }); + + test("selectQuery from timeline", async () => { + const { getByTestId, findByTestId } = rendered; + const trigger = getByTestId("stored-trigger"); + Simulate.click(trigger); + const timeline = getByTestId("timeline"); + Simulate.change(timeline, { target: { method: "select", value: "test" } }); + const element = await findByTestId("show-stored"); + await waitFor(() => expect(element.className.includes("listActive")).toBeFalsy()); + }); + + test("deleteQuery from timeline", async () => { + jest.spyOn(utils, "concatUniqueStrings").mockImplementation(() => ["test"]); + jest.spyOn(window.localStorage.__proto__, "setItem").mockImplementation(() => jest.fn()); + const { getByTestId } = rendered; + const playTrigger = getByTestId("play-trigger"); + Simulate.click(playTrigger); + const trigger = getByTestId("stored-trigger"); + Simulate.click(trigger); + const timeline = getByTestId("timeline"); + expect(timeline[Object.keys(timeline)[1]].queries).toEqual(['test']); + Simulate.change(timeline, { target: { method: "delete", value: "test" } }); + expect(timeline[Object.keys(timeline)[1]].queries).toEqual([]); + }); +}); + +describe('Comander keycodes test suite', () => { + let rendered; + let act; + + beforeEach(() => { + jest.spyOn(window.localStorage.__proto__, "getItem").mockImplementation(() => ["test"]); + JSON.parse = jest.fn().mockImplementation(() => ["", "test"]); + ({ rendered, act } = getMockProvider(, { theme: {}, user: { loggedIn: true } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('keyDown test', async () => { + const { findAllByTestId } = rendered; + const event = new KeyboardEvent("keydown", { keyCode: 40 }); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + const queries = await findAllByTestId('select-query-trigger'); + expect(queries[0].className.includes('suggestionActive')).toBeTruthy(); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + expect(queries[1].className.includes("suggestionActive")).toBeTruthy(); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + expect(queries[0].className.includes("suggestionActive")).toBeTruthy(); + }); + + test("keyUp test", async () => { + const { findAllByTestId } = rendered; + const event = new KeyboardEvent("keydown", { keyCode: 38 }); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + const queries = await findAllByTestId("select-query-trigger"); + expect(queries[1].className.includes("suggestionActive")).toBeTruthy(); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + expect(queries[0].className.includes("suggestionActive")).toBeTruthy(); + }); + + test("enter test", async () => { + const { findByTestId, getByTestId } = rendered; + const spy = jest.spyOn(window.localStorage.__proto__, "setItem").mockImplementation(() => jest.fn()); + const trigger = getByTestId("stored-trigger"); + let event = new KeyboardEvent("keydown", { keyCode: 40 }); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + Simulate.click(trigger); + event = new KeyboardEvent("keydown", { keyCode: 13 }); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + const element = await findByTestId("show-stored"); + await waitFor(() => expect(element.className.includes("listActive")).toBeFalsy()); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + expect(spy).toHaveBeenCalled(); + }); + + test("default break", async () => { + let event = new KeyboardEvent("keydown", { keyCode: 99 }); + act(() => { + window.dispatchEvent(event); + return undefined; + }); + expect(true).toBeTruthy(); + }) +}) diff --git a/src/components/comander/CypherCodeMirror.js b/src/components/comander/CypherCodeMirror.js index a7d5eb8..099a6b9 100644 --- a/src/components/comander/CypherCodeMirror.js +++ b/src/components/comander/CypherCodeMirror.js @@ -164,6 +164,6 @@ export default class CodeMirror extends React.Component { const setEditorReference = (ref) => { this.editorReference = ref; }; - return
; + return
; } } diff --git a/src/components/header/Header.js b/src/components/header/Header.js index aaf82ad..e5389b0 100644 --- a/src/components/header/Header.js +++ b/src/components/header/Header.js @@ -26,9 +26,13 @@ function Header(props) { } return ( - + - + { menu ? 'menu_open' : 'menu' } @@ -36,7 +40,7 @@ function Header(props) {
Hi, {user.user}!
- + Logout
diff --git a/src/components/header/Header.test.js b/src/components/header/Header.test.js new file mode 100644 index 0000000..3886eaa --- /dev/null +++ b/src/components/header/Header.test.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { getMockProvider } from '../../global/utils/tests/store.mock'; +import { Simulate } from "react-dom/test-utils"; +import { cleanup } from "@testing-library/react"; +import Header from './Header'; + +describe('Header component test suite', () => { + let rendered; + let store; + let toggleMenu = jest.fn() + + beforeEach(() => { + ({ rendered, store } = getMockProvider(
, + { theme: {}, user: { loggedIn: true } })); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + test('should render component', () => { + const { getByTestId } = rendered; + const element = getByTestId('header'); + expect(element).toBeDefined(); + }); + + test('should trigger logout', () => { + const { getByTestId } = rendered; + const logoutEl = getByTestId('logout'); + Simulate.click(logoutEl); + expect(store.getState().user.user).toBe(null); + }); + + test('should toggle menu', () => { + const { getByTestId } = rendered; + const toggleEl = getByTestId('menu-trigger'); + Simulate.click(toggleEl); + expect(toggleMenu).toHaveBeenCalled(); + }) +}) diff --git a/src/components/login/Login.js b/src/components/login/Login.js index 7041e6a..6ed03e5 100644 --- a/src/components/login/Login.js +++ b/src/components/login/Login.js @@ -11,10 +11,11 @@ import { defaultTheme } from '../../global/autosuggest'; import styles from './Login.module.css'; // Teach Autosuggest how to calculate suggestions for any given input value. -const getSuggestions = (value, suggestions) => { +export const getSuggestions = (value, suggestions) => { if (!value) { return [] } + const inputValue = value.trim().toLowerCase(); const inputLength = inputValue.length; @@ -28,20 +29,20 @@ const getSuggestions = (value, suggestions) => { // When suggestion is clicked, Autosuggest needs to populate the input // based on the clicked suggestion. Teach Autosuggest how to calculate the // input value for every given suggestion. -const getSuggestionValue = (suggestion) => suggestion.uri; +export const getSuggestionValue = (suggestion) => suggestion.uri; // Use your imagination to render suggestions. -const renderSuggestion = (suggestion) => {suggestion.uri}; +export const renderSuggestion = (suggestion) => {suggestion.uri}; function Login(props) { const [suggestions, setSuggestions] = useState([]); const [history, setHistory] = useState([]); const [error, setError] = useState(""); - const [uri, setUri] = useState(''); - const [user, setUser] = useState(''); - const [password, setPassword] = useState(''); - const sessionId = useRef(''); + const [uri, setUri] = useState(""); + const [user, setUser] = useState(""); + const [password, setPassword] = useState(""); + const sessionId = useRef(""); useEffect(() => { const savedHistory = localStorage.getItem("neo4jDash.loginHistory"); @@ -54,35 +55,41 @@ function Login(props) { try { const sess = await doLogin(`neo4j://${uri}`, user, password); if (sess && sess.id) { - sessionId.current = { sessionId: sess.id, user: user } - localStorage.setItem("neo4jDash.loginHistory", JSON.stringify( - history.filter(h => h.uri !== uri).concat({ uri })) + sessionId.current = { sessionId: sess.id, user: user }; + /* istanbul ignore next */ + localStorage.setItem( + "neo4jDash.loginHistory", + JSON.stringify(history.filter((h) => h.uri !== uri).concat({ uri })) ); - props.callback(sessionId.current) + props.callback(sessionId.current); } else { setError("Incorrect connection credentials"); } } catch (err) { - setError(err); + setError(err.message); } }; + /* istanbul ignore next */ const uriUpdate = (_, { newValue }) => { setUri(newValue); - } + }; // Autosuggest will call this function every time you need to update suggestions. // You already implemented this logic above, so just use it. + /* istanbul ignore next */ const onSuggestionsFetchRequested = ({ value }) => { setSuggestions(getSuggestions(value, history)); }; // Autosuggest will call this function every time you need to clear suggestions. + /* istanbul ignore next */ const onSuggestionsClearRequested = () => { setSuggestions([]); }; const onLogin = async (event) => { + /* istanbul ignore else */ if (event) { event.preventDefault(); } @@ -90,14 +97,14 @@ function Login(props) { }; return ( - +
share

Use your Neo4j credentials

-
+ setPassword(e.target.value)} /> - {error.length ? {error} : null} - diff --git a/src/components/login/Login.test.js b/src/components/login/Login.test.js new file mode 100644 index 0000000..62fc380 --- /dev/null +++ b/src/components/login/Login.test.js @@ -0,0 +1,119 @@ +import React, * as react from "react"; +import { getMockProvider } from "../../global/utils/tests/store.mock"; +import { Simulate } from "react-dom/test-utils"; +import { cleanup, waitFor } from "@testing-library/react"; +import Login, { getSuggestions, getSuggestionValue, renderSuggestion } from "./Login"; +import * as services from "../../service/neo.service"; + +jest.mock("@adrianiy/react-autosuggest", () => { + return function MockComander() { + return
; + }; +}); + +describe('Login test suite', () => { + let rendered; + const callback = jest.fn(); + + beforeEach(() => { + ({ rendered } = getMockProvider(, { + theme: {}, + user: { loggedIn: true }, + })); + }); + + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }) + + describe('render test', () => { + test('login should render', () => { + const { getByTestId } = rendered; + const element = getByTestId('login'); + expect(element).toBeDefined(); + }) + }); + + describe("login test suite", () => { + let form; + beforeEach(() => { + const { getByTestId } = rendered; + const user = getByTestId("user"); + const pass = getByTestId("password"); + form = getByTestId("form"); + Simulate.change(user, { target: { value: "user" } }); + Simulate.change(pass, { target: { value: "pass" } }); + }); + + test("should do login on form submit", async () => { + jest.spyOn(services, 'doLogin').mockImplementation(() => ({ id: 'test' })); + Simulate.submit(form); + await waitFor(() => expect(callback).toHaveBeenCalledWith({ sessionId: "test", user: "user" })); + }); + + test('should render error', async () => { + const { findByTestId } = rendered; + jest.spyOn(services, 'doLogin').mockImplementation(() => null); + Simulate.submit(form); + const error = await findByTestId('error'); + expect(error).toBeDefined(); + }); + + test('network connection error', async () => { + const { findByTestId } = rendered; + jest.spyOn(services, "doLogin").mockImplementation(() => { + throw new Error('test'); + }); + Simulate.submit(form); + const error = await findByTestId("error"); + expect(error).toBeDefined(); + }) + }); + + describe('saved history cookies test suite', () => { + test('should setHistory when cookies are setted', () => { + const fn = jest.fn(); + jest.spyOn(react, 'useState').mockImplementation(() => [[], fn]); + cleanup(); + jest.spyOn(window.localStorage.__proto__, "getItem").mockImplementation(() => + JSON.stringify({ test: "test" }) + ); + + ({ rendered } = getMockProvider(, { + theme: {}, + user: { loggedIn: true }, + })); + + expect(fn).toHaveBeenCalledWith({ test: 'test' }); + }) + }); +}) + +describe('auxiliar methods test suite', () => { + const suggestions = [{ uri: 'test' }]; + test('getSuggestions test', () => { + const result = getSuggestions('test', suggestions); + expect(result).toEqual([{ uri: 'test' }]) + }); + + test('getSuggestion no length test', () => { + const result = getSuggestions(' ', suggestions); + expect(result).toEqual([]); + }); + + test('getSuggestion no value test', () => { + const result = getSuggestions(null, suggestions); + expect(result).toEqual([]); + }) + + test('getSuggestionValue test', () => { + const result = getSuggestionValue(suggestions[0]); + expect(result).toBe('test'); + }) + + test('renderSuggestions test', () => { + const result = renderSuggestion(suggestions[0]); + expect(result.type).toBe('span'); + }) +}) diff --git a/src/components/sidebar/Sidebar.js b/src/components/sidebar/Sidebar.js index 56e278f..6f745fe 100644 --- a/src/components/sidebar/Sidebar.js +++ b/src/components/sidebar/Sidebar.js @@ -40,7 +40,7 @@ function Sidebar(props) { const renderDbInfo = () => { return ( - +

D.B. Info

Node Labels @@ -69,7 +69,7 @@ function Sidebar(props) { const renderSettings = () => { return ( - +

Theme

AUTO @@ -97,10 +97,22 @@ function Sidebar(props) { } return ( - + - setMenu('settings')}>settings - setMenu('storage')}>storage + setMenu('settings')} + > + settings + + setMenu('storage')} + > + storage + { menu === 'settings' && renderSettings() } { menu === 'storage' && renderDbInfo() } diff --git a/src/components/sidebar/Sidebar.test.js b/src/components/sidebar/Sidebar.test.js new file mode 100644 index 0000000..753a146 --- /dev/null +++ b/src/components/sidebar/Sidebar.test.js @@ -0,0 +1,98 @@ +import React from 'react'; +import { getMockProvider } from '../../global/utils/tests/store.mock'; +import { cleanup } from "@testing-library/react"; +import { Simulate } from "react-dom/test-utils"; +import Sidebar from './Sidebar'; +import actions from '../../global/utils/store/actions'; + +describe('Sidebar test suite', () => { + let rendered; + let store; + + beforeEach(() => { + ({ rendered, store } = getMockProvider(, { + theme: {}, + user: { loggedIn: true }, + })); + }); + + beforeEach(() => { + store.dispatch( + actions.db.setProperties({ + labels: { records: [{ get: () => "test" }] }, + relationshipTypes: { records: [{ get: () => "test" }] }, + propertyKeys: { records: [{ get: () => "test" }] }, + }) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanup(); + }); + + describe('render test suite', () => { + test("Sidebar should render", () => { + const { getByTestId } = rendered; + const element = getByTestId("sidebar"); + expect(element).toBeDefined(); + }); + + test("Sidebar should render settings", () => { + const { getByTestId } = rendered; + const toggler = getByTestId("settings-toggler"); + Simulate.click(toggler); + const element = getByTestId("sidebar-settings"); + expect(element).toBeDefined(); + }); + + test("Sidebar should render storage", () => { + const { getByTestId } = rendered; + const toggler = getByTestId("storage-toggler"); + Simulate.click(toggler); + const element = getByTestId("sidebar-storage"); + expect(element).toBeDefined(); + }); + }); + + describe('theme toggler test suite', () => { + let togglers; + + beforeEach(() => { + const { getAllByTestId } = rendered; + togglers = getAllByTestId('toggler'); + }); + + test('light theme toggler test', () => { + Simulate.click(togglers[1]); + expect(store.getState().theme['_id']).toBe('light'); + Simulate.click(togglers[1]); + expect(store.getState().theme["_id"]).toBe("auto"); + }); + + test("dark theme toggler test", () => { + Simulate.click(togglers[2]); + expect(store.getState().theme["_id"]).toBe("dark"); + }); + + test('auto theme toggler test', () => { + Simulate.click(togglers[0]); + expect(store.getState().theme['_id']).toBe('auto'); + }); + + test("small size theme toggler test", () => { + Simulate.click(togglers[3]); + expect(store.getState().theme["size"]).toBe("small"); + Simulate.click(togglers[3]); + expect(store.getState().theme["size"]).toBe("wide"); + }); + + test("wide size theme toggler test", () => { + Simulate.click(togglers[4]); + expect(store.getState().theme["size"]).toBe("wide"); + Simulate.click(togglers[4]); + expect(store.getState().theme["size"]).toBe("small"); + }); + }) +}); + diff --git a/src/components/timeline/Timeline.js b/src/components/timeline/Timeline.js index 0d2ca7d..09e1b45 100644 --- a/src/components/timeline/Timeline.js +++ b/src/components/timeline/Timeline.js @@ -14,12 +14,13 @@ function Timeline(props) { } }, [props, queries]); + /* istanbul ignore next */ const deleteQuery = (query) => { props.deleteQuery(query); }; return ( - + {queries.length ? ( queries.map((q, idx) => ( diff --git a/src/components/timeline/Timeline.test.js b/src/components/timeline/Timeline.test.js new file mode 100644 index 0000000..328d9f1 --- /dev/null +++ b/src/components/timeline/Timeline.test.js @@ -0,0 +1,47 @@ +import React, * as react from 'react'; +import { getMockProvider } from "../../global/utils/tests/store.mock"; +import { cleanup } from "@testing-library/react"; +import Timeline from './Timeline'; + +jest.mock('./../card/Card.js', () => { + return function MockComander() { + return
; + }; +}) + +describe('Timeline teset suite', () => { + let rendered; + + beforeEach(() => { + ({ rendered } = getMockProvider(, { + theme: {}, + user: { loggedIn: true }, + })); + }); + + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }); + + describe('render test suite', () => { + test('should render timeline', () => { + const { getByTestId } = rendered; + const element = getByTestId('timeline'); + expect(element).toBeDefined(); + }); + }); + + describe('Timeline methods test suite', () => { + test('should set queries', () => { + cleanup(); + const fn = jest.fn(); + jest.spyOn(react, "useState").mockImplementation(() => [[], fn]); + ({ rendered } = getMockProvider(, { + theme: {}, + user: { loggedIn: true }, + })); + expect(fn).toHaveBeenCalledWith(['test']); + }) + }); +}) diff --git a/src/global/components/toggler/Toggler.js b/src/global/components/toggler/Toggler.js index 6f31837..88eeac6 100644 --- a/src/global/components/toggler/Toggler.js +++ b/src/global/components/toggler/Toggler.js @@ -6,11 +6,11 @@ import styles from './Toggler.module.css'; function Toggler(props) { return ( - -
+
) } diff --git a/src/global/components/toggler/Toggler.test.js b/src/global/components/toggler/Toggler.test.js new file mode 100644 index 0000000..4ee20af --- /dev/null +++ b/src/global/components/toggler/Toggler.test.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { render } from "@testing-library/react"; +import Toggler from './Toggler'; +import { Simulate } from 'react-dom/test-utils'; + +describe('Toggler test suite', () => { + test("toggler should render", () => { + const { getByTestId } = render(); + const element = getByTestId("toggler"); + expect(element).toBeDefined(); + }); + + test("toggler should trigger click function", () => { + const handler = jest.fn(); + const { getByTestId } = render(); + const element = getByTestId("toggler"); + Simulate.click(element); + expect(handler).toHaveBeenCalled(); + }) +}) diff --git a/src/global/layout.test.js b/src/global/layout.test.js new file mode 100644 index 0000000..4d00e39 --- /dev/null +++ b/src/global/layout.test.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { render } from "@testing-library/react"; +import { Simulate } from "react-dom/test-utils"; +import { RowLayout, ColumnLayout } from "./layouts"; + +describe('layout test suite', () => { + test('row layout test', () => { + const { getByTestId } = render(); + const element = getByTestId('test'); + expect(element).toBeDefined(); + }); + + test("row layout click test", () => { + const handler = jest.fn(); + const { getByTestId } = render(); + const element = getByTestId("test"); + Simulate.click(element) + expect(handler).toHaveBeenCalled(); + }); + + test("col layout test", () => { + const { getByTestId } = render(); + const element = getByTestId("test"); + expect(element).toBeDefined(); + }); + + test("col layout click test", () => { + const handler = jest.fn(); + const { getByTestId } = render(); + const element = getByTestId("test"); + Simulate.click(element); + expect(handler).toHaveBeenCalled(); + }); +}) diff --git a/src/global/layouts.js b/src/global/layouts.js index 1887913..85cb74d 100644 --- a/src/global/layouts.js +++ b/src/global/layouts.js @@ -3,7 +3,12 @@ import { cls } from "./utils"; export function RowLayout(props) { return ( -
+
{ props.children }
) @@ -11,8 +16,13 @@ export function RowLayout(props) { export function ColumnLayout(props) { return ( -
- { props.children } +
+ {props.children}
- ) + ); } diff --git a/src/global/utils.js b/src/global/utils.js index 3e2c404..5818fe9 100644 --- a/src/global/utils.js +++ b/src/global/utils.js @@ -10,72 +10,9 @@ export const debounce = (fn, time, context = null) => { let pending; return (...args) => { if (pending) clearTimeout(pending); + /* istanbul ignore next */ pending = setTimeout(() => typeof fn === "function" && fn.apply(context, args), parseInt(time)); }; }; -export const stringifyMod = (value, modFn = null, prettyLevel = false, skipOpeningIndentation = false) => { - prettyLevel = !prettyLevel ? false : prettyLevel === true ? 1 : parseInt(prettyLevel); - const nextPrettyLevel = prettyLevel ? prettyLevel + 1 : false; - const newLine = prettyLevel ? "\n" : ""; - const indentation = prettyLevel && !skipOpeningIndentation ? Array(prettyLevel).join(" ") : ""; - const endIndentation = prettyLevel ? Array(prettyLevel).join(" ") : ""; - const propSpacing = prettyLevel ? " " : ""; - const toString = Object.prototype.toString; - const isArray = - Array.isArray || - function (a) { - return toString.call(a) === "[object Array]"; - }; - const escMap = { - '"': '"', - "\\": "\\", - "\b": "\b", - "\f": "\f", - "\n": "\n", - "\r": "\r", - "\t": "\t", - }; - const escFunc = function (m) { - return escMap[m] || "\\u" + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); - }; - const escRE = /[\\"\u0000-\u001F\u2028\u2029]/g; // eslint-disable-line no-control-regex - if (modFn) { - const modVal = modFn && modFn(value); - if (typeof modVal !== "undefined") return indentation + modVal; - } - if (value == null) return indentation + "null"; - if (typeof value === "number") { - return indentation + (isFinite(value) ? value.toString() : "null"); - } - if (typeof value === "boolean") return indentation + value.toString(); - if (typeof value === "object") { - if (typeof value.toJSON === "function") { - return stringifyMod(value.toJSON(), modFn, nextPrettyLevel); - } else if (isArray(value)) { - let hasValues = false; - let res = ""; - for (let i = 0; i < value.length; i++) { - hasValues = true; - res += (i ? "," : "") + newLine + stringifyMod(value[i], modFn, nextPrettyLevel); - } - return indentation + "[" + res + (hasValues ? newLine + endIndentation : "") + "]"; - } else if (toString.call(value) === "[object Object]") { - const tmp = []; - for (const k in value) { - if (value.hasOwnProperty(k)) { - tmp.push( - stringifyMod(k, modFn, nextPrettyLevel) + - ":" + - propSpacing + - stringifyMod(value[k], modFn, nextPrettyLevel, true) - ); - } - } - return indentation + "{" + newLine + tmp.join("," + newLine) + newLine + endIndentation + "}"; - } - } - return indentation + '"' + value.toString().replace(escRE, escFunc) + '"'; -}; - diff --git a/src/global/utils.test.js b/src/global/utils.test.js new file mode 100644 index 0000000..d8b51cc --- /dev/null +++ b/src/global/utils.test.js @@ -0,0 +1,61 @@ +import { cls, optionalToString, concatUniqueStrings, debounce } from "./utils"; + +describe('cls test suite', () => { + test('cls test', () => { + const result = cls('test', 'test2'); + expect(result).toEqual('test test2') + }); +}); + +describe('optionalToString test suite', () => { + test('ok test', () => { + const result = optionalToString(1); + expect(result).toBe('1') + }) + + test('ko test',() => { + const result = optionalToString(null); + expect(result).toBe(null); + + const result2 = optionalToString(() => 'test'); + expect(result2 instanceof String).toBeFalsy(); + }); +}); + +describe('concatenateUniqueStrings test suite', () => { + test('unique test', () => { + const result = concatUniqueStrings('test', ['a', 'b']); + expect(result).toEqual(['test', 'a', 'b']); + }); + + test("unique null test", () => { + const result = concatUniqueStrings('test', ["a", null]); + expect(result).toEqual(["test", "a", null]); + }); + + test("not unique test", () => { + const result = concatUniqueStrings("test", ["a", "test"]); + expect(result).toEqual(["test", "a"]); + }); +}); + +describe('debounce test suite', () => { + test('debouce test', () => { + const debounced = debounce(() => 'test', 300); + const result = debounced(); + setTimeout(() => expect(result).toBe('test'), 301); + }); + + test("debouce ko test", () => { + const debounced = debounce(() => "test", 300); + const result = debounced(); + setTimeout(() => expect(result).toBe(null), 200); + }); + + test('debounce twice test', () => { + const debounced = debounce((text) => text, 300); + let result = debounced('a'); + debounced('test'); + setTimeout(() => expect(result).toBe("test"), 301); + }) +}) diff --git a/src/global/utils/store/store.js b/src/global/utils/store/store.js index 8b8d338..7b6bbf9 100644 --- a/src/global/utils/store/store.js +++ b/src/global/utils/store/store.js @@ -3,5 +3,6 @@ import rootReducer from "./reducers"; export const store = createStore( rootReducer, + /* istanbul ignore next */ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); diff --git a/src/global/utils/store/store.test.js b/src/global/utils/store/store.test.js new file mode 100644 index 0000000..284786e --- /dev/null +++ b/src/global/utils/store/store.test.js @@ -0,0 +1,81 @@ +import actions from "./actions" +import { store } from "./store" + +describe('store test suite', () => { + + afterEach(() => { + jest.restoreAllMocks(); + }) + + describe('db store test suite', () => { + test('setProperties test', () => { + store.dispatch(actions.db.setProperties({ test: 'test' })); + expect(store.getState().dbSchema).toEqual({ test: 'test' }); + }) + }); + + describe('graph store test suite', () => { + test('updateStyle test', () => { + store.dispatch( + actions.graph.updateStyle({ + relationship: { + "text-color-external": 'yellow', + }, + }) + ); + expect(store.getState().graph.rules[1].props['text-color-external']).toBe('yellow'); + }); + + test('updateSelectorStyle test', () => { + store.dispatch(actions.graph.updateSelector('relationship', { 'text-color-external': 'blue' })); + expect(store.getState().graph.rules[2].props["text-color-external"]).toBe("blue"); + }) + }); + + describe('theme store test suite', () => { + test('setAutoTheme test before 20:00', () => { + const mockDate = new Date("2020-04-07T15:20:30Z"); + jest.spyOn(global, "Date").mockImplementation(() => mockDate); + store.dispatch(actions.theme.setAutoTheme()); + expect(store.getState().theme['_id']).toBe('auto'); + expect(store.getState().theme.id).toBe('light'); + }); + + test("setAutoTheme test after 20:00", () => { + const mockDate = new Date("2020-04-07T20:20:30Z"); + jest.spyOn(global, "Date").mockImplementation(() => mockDate); + store.dispatch(actions.theme.setAutoTheme()); + expect(store.getState().theme["_id"]).toBe("auto"); + expect(store.getState().theme.id).toBe("dark"); + }); + + test('setCustomTheme test', () => { + store.dispatch(actions.theme.setCustomTheme('light')); + expect(store.getState().theme['_id']).toBe('light'); + }); + + test('setSize test', () => { + store.dispatch(actions.theme.setSize('large')); + expect(store.getState().theme.size).toBe('large'); + }); + + test('toggleFullscreen test', () => { + store.dispatch(actions.theme.toggleFullScreen()); + expect(store.getState().theme.fullscreen).toBeTruthy(); + }); + }); + + describe('user store test suite', () => { + test('setUser test', () => { + store.dispatch(actions.user.setUser({ test: 'test' })); + expect(store.getState().user.test).toBe('test'); + expect(store.getState().user.loggedIn).toBeTruthy(); + }); + + test("logout test", () => { + store.dispatch(actions.user.logOut()); + expect(store.getState().user.user).toBe(null); + expect(store.getState().user.loggedIn).toBeFalsy(); + }); + }) +}) diff --git a/src/global/utils/tests/store.mock.js b/src/global/utils/tests/store.mock.js index 5a9e4e9..597af63 100644 --- a/src/global/utils/tests/store.mock.js +++ b/src/global/utils/tests/store.mock.js @@ -1,27 +1,23 @@ import React from "react"; import { Provider } from "react-redux"; -import configureStore from "redux-mock-store"; +import { render, act } from "@testing-library/react"; +import rootReducer from "../store/reducers"; +import { createStore } from "redux"; -export const getMockProvider = (partialState) => { - const mockStore = configureStore(); - const store = mockStore({ - theme: {}, - user: { user: null, loggedIn: false } - }); +export const getMockProvider = (children, partialState, spyDispatch = false) => { + const store = createStore(rootReducer, partialState); + let storeSpy; + if (spyDispatch) { + storeSpy = jest.spyOn(store, 'dispatch').mockImplementation(() => jest.fn()); + } + const rendered = render( + {children} + ); return { - MockProvider: ({ children }) => {children}, + rendered, store, - }; -}; - -export const setup = (partialState) => { - const { MockProvider } = getMockProvider(partialState); - - // the other mocking you want to do like a custom hook - - return { - MockProvider, - // you can return all mock instance from here, so you assert then in the tests - }; + act, + storeSpy + } }; diff --git a/src/service/neo.service.js b/src/service/neo.service.js index 9ce6e4d..ab55ba2 100644 --- a/src/service/neo.service.js +++ b/src/service/neo.service.js @@ -1,5 +1,6 @@ import { createRecord } from './utils'; +/* istanbul ignore next */ const SERVICE_URL = process.env.NODE_ENV === 'production' ? "https://neo4j-service.azurewebsites.net/api" : "http://localhost:5000/api"; export const doLogin = async (uri, user, password) => { diff --git a/src/service/neo.service.test.js b/src/service/neo.service.test.js new file mode 100644 index 0000000..0578e39 --- /dev/null +++ b/src/service/neo.service.test.js @@ -0,0 +1,77 @@ +import * as service from './neo.service'; + +describe('neo service doLogin test suite', () => { + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('doLogin ok test', async () => { + jest.spyOn(global, 'fetch').mockImplementation(() => Promise.resolve({ json: () => 'test', ok: true })); + const result = await service.doLogin(); + expect(result).toBe('test'); + }); + + test("doLogin ko test", async () => { + jest.spyOn(global, "fetch").mockImplementation(() => Promise.resolve({ json: () => "test", ok: false })); + const result = await service.doLogin(); + expect(result).toBe(null); + }); +}); + +describe('neo service doLogout test suite', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test("doLogout ok test", async () => { + jest.spyOn(global, "fetch").mockImplementation(() => Promise.resolve({ json: () => "test", ok: true })); + const result = await service.doLogout(); + expect(result).toBe("test"); + }); + + test("doLogin ko test", async () => { + jest.spyOn(global, "fetch").mockImplementation(() => Promise.resolve({ json: () => "test", ok: false })); + const result = await service.doLogout(); + expect(result).toBe(null); + }); +}); + +describe('neo service getQuery test suite', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test("getQuery ok test", async () => { + jest.spyOn(global, "fetch").mockImplementation(() => + Promise.resolve({ + json: () => ({ + records: [ + { + keys: [], + _fields: [ + { segments: [] }, + { properties: { prop: { low: 0, high: 1 } } }, + null, + ], + }, + ], + summary: "test", + }), + ok: true, + }) + ); + const result = await service.getQuery(); + expect(result.summary).toBe("test"); + expect(result.records.length).toBe(1); + }); + + test("getQuery ko test", async () => { + try { + jest.spyOn(global, "fetch").mockImplementation(() => Promise.resolve({ json: () => ({ message: 'test' }), ok: false })); + await service.getQuery(); + } catch (err) { + expect(err).toBe('test'); + } + }); +}) diff --git a/src/service/schema.service.test.js b/src/service/schema.service.test.js new file mode 100644 index 0000000..7dfaaae --- /dev/null +++ b/src/service/schema.service.test.js @@ -0,0 +1,19 @@ +import * as service from './neo.service'; +import { getDBSchema } from './schema.service'; + +describe('schema service test suite', () => { + + beforeEach(() => { + jest.spyOn(service, 'getQuery').mockImplementation(() => 'test'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('getDbSchema test', async () => { + const result = await getDBSchema('sess'); + expect(result instanceof Object).toBeTruthy(); + expect(Object.keys(result).length).toBe(5); + }); +}) diff --git a/src/service/utils.js b/src/service/utils.js index 0562ac0..d49fe3b 100644 --- a/src/service/utils.js +++ b/src/service/utils.js @@ -2,7 +2,7 @@ import Record from "neo4j-driver/lib/record"; import { Path, Node, PathSegment, Relationship } from "neo4j-driver/lib/graph-types"; import Integer from "neo4j-driver/lib/integer"; -const parseProperties = (props) => { +export const parseProperties = (props) => { Object.keys(props).forEach((key) => { if (props[key] instanceof Object) { props[key] = createInteger(props[key]); @@ -11,7 +11,7 @@ const parseProperties = (props) => { return props; }; -const createPath = (path) => { +export const createPath = (path) => { if (path.start) { path.start = createNode(path.start); } @@ -24,13 +24,13 @@ const createPath = (path) => { return new Path(path.start, path.end, path.segments); }; -const createSegments = (segments) => { +export const createSegments = (segments) => { return segments.map((seg) => { return createSegment(seg); }); }; -const createSegment = (seg) => { +export const createSegment = (seg) => { if (seg.start) { seg.start = createNode(seg.start); } @@ -43,7 +43,7 @@ const createSegment = (seg) => { return new PathSegment(seg.start, seg.relationship, seg.end); }; -const createRel = (rel) => { +export const createRel = (rel) => { if (rel.identity) { rel.identity = createInteger(rel.identity); } @@ -59,11 +59,11 @@ const createRel = (rel) => { return new Relationship(rel.identity, rel.start, rel.end, rel.type, rel.properties); }; -const createInteger = (data) => { +export const createInteger = (data) => { return new Integer(data.low, data.high); }; -const createNode = (node) => { +export const createNode = (node) => { if (node.properties) { node.properties = parseProperties(node.properties); } diff --git a/src/service/utils.test.js b/src/service/utils.test.js new file mode 100644 index 0000000..b5149a1 --- /dev/null +++ b/src/service/utils.test.js @@ -0,0 +1,129 @@ +import { parseProperties, createPath, createSegments, createSegment, createRel, createInteger, createNode, createRecord } from './utils'; +import Record from "neo4j-driver/lib/record"; +import { Path, Node, PathSegment, Relationship } from "neo4j-driver/lib/graph-types"; +import Integer from "neo4j-driver/lib/integer"; + +describe('service/utils test suite', () => { + const prop = { low: 0, high: 1 }; + const node = { properties: { prop }, identity: prop} + const relationship = { + identity: prop, + start: node, + end: node, + properties: { prop } + } + const segment = { start: node, end: node, relationship }; + const path = { + start: node, + end: node, + segments: [ segment ], + }; + describe("parseProperties test suite", () => { + test("parseProperties test", () => { + const data = { + test: prop, + noObj: 1 + }; + const result = parseProperties(data); + expect(result.test instanceof Integer).toBeTruthy(); + expect(result.noObj instanceof Integer).toBeFalsy(); + }); + }); + + describe("createPath test suite", () => { + test("createPath test", () => { + const result = createPath(path); + expect(result instanceof Path).toBeTruthy(); + }); + + test("createPath with null Object", () => { + try { + createPath({}); + } catch (err) { + expect(err.message).toBe('Cannot read property \'length\' of undefined') + } + }); + }) + + describe("createSegments test suite", () => { + test("createSegments test", () => { + const data = [ + segment + ]; + const result = createSegments(data); + expect(result.length).toBe(1); + expect(result[0] instanceof PathSegment).toBeTruthy(); + }); + + test("createSegments empty data", () => { + const data = []; + const result = createSegments(data); + expect(result.length).toBe(0); + }) + }); + + describe("createSegment test suite", () => { + test("createSegment test", () => { + const result = createSegment(segment); + expect(result instanceof PathSegment).toBeTruthy(); + }); + + test("createSegment with no data", () => { + const result = createSegment({}); + expect(result instanceof PathSegment).toBeTruthy(); + }) + }); + + describe("createRel test suite", () => { + test("createRel test", () => { + const result = createRel(relationship); + expect(result instanceof Relationship).toBeTruthy(); + }); + + test("createRel with no data", () => { + const result = createRel({}); + expect(result instanceof Relationship).toBeTruthy(); + }) + }); + + describe("createInteger test suite", () => { + test("createInteger test", () => { + const result = createInteger(prop); + expect(result instanceof Integer).toBeTruthy(); + }); + + test("create Integer with no data", () => { + const result = createInteger({}); + expect(result instanceof Integer).toBeTruthy(); + }); + }); + + describe("createNode test suite", () => { + test("createNode test", () => { + const result = createNode(node); + expect(result instanceof Node).toBeTruthy(); + }); + + test("createNode with no data", () => { + const result = createNode({}); + expect(result instanceof Node).toBeTruthy(); + }); + }); + + describe("createRecord test suite", () => { + test("createRecord test", () => { + const result = createRecord({ + keys: [], + _fields: [ + { segments: [segment] }, + {relationship}, + node, + { identity: prop, start: node, end: node }, + { properties: { prop } }, + null + ] + }); + expect(result instanceof Record).toBeTruthy(); + }) + }) +})