diff --git a/package-lock.json b/package-lock.json index 803790740..b2d9e6b48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,54 +152,6 @@ "z-schema": "^4.2.3" } }, - "@babel/cli": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.17.tgz", - "integrity": "sha512-R9dQbDshWvAp3x5XjANsfthqka+ToEdDUonKgtALNgzQxgiUg2Xa4ZwKIcE84wnoiobIJFXF+TCM4ylJvUuW5w==", - "requires": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "lodash": "^4.17.19", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -272,6 +224,7 @@ "version": "7.12.10", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", + "dev": true, "requires": { "@babel/types": "^7.12.10" } @@ -639,6 +592,7 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", @@ -756,6 +710,7 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -1032,6 +987,7 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -1067,6 +1023,7 @@ "version": "7.12.12", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.12.tgz", "integrity": "sha512-JDWGuzGNWscYcq8oJVCtSE61a5+XAOos+V0HrxnDieUus4UMnBEosDnY1VJqU5iZ4pA04QY7l0+JvHL1hZEfsw==", + "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.12.10", "@babel/helper-module-imports": "^7.12.5", @@ -1112,41 +1069,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-runtime": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.17.tgz", - "integrity": "sha512-s+kIJxnaTj+E9Q3XxQZ5jOo+xcogSe3V78/iFQ5RmoT0jROdpcdxhfGdq/VLqW1hFSzw6VjqN8aQqTaAMixWsw==", - "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "semver": "^5.5.1" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", - "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==" - }, - "@babel/types": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.17.tgz", - "integrity": "sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - } - } - }, "@babel/plugin-transform-shorthand-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", @@ -1312,90 +1234,6 @@ "@babel/plugin-transform-react-pure-annotations": "^7.12.1" } }, - "@babel/register": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.12.13.tgz", - "integrity": "sha512-fnCeRXj970S9seY+973oPALQg61TRvAaW0nRDe1f4ytKqM3fZgsNXewTZWmqZedg74LFIRpg/11dsrPZZvYs2g==", - "requires": { - "find-cache-dir": "^2.0.0", - "lodash": "^4.17.19", - "make-dir": "^2.1.0", - "pirates": "^4.0.0", - "source-map-support": "^0.5.16" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "^3.0.0" - } - } - } - }, "@babel/runtime": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", @@ -1404,15 +1242,6 @@ "regenerator-runtime": "^0.13.4" } }, - "@babel/runtime-corejs2": { - "version": "7.12.18", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.12.18.tgz", - "integrity": "sha512-1wlWco+J/wbzdblk2oxThE7PhN8hZDK9fFnEG77Z1TJ+1dwt7UJ5soH5JPqrBTazlVTXTIwCcFGXsH/7m9WdYQ==", - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.4" - } - }, "@babel/runtime-corejs3": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", @@ -5072,198 +4901,6 @@ "glob-to-regexp": "^0.3.0" } }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", - "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -6964,7 +6601,8 @@ "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true }, "async-foreach": { "version": "0.1.3", @@ -7285,6 +6923,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, "optional": true }, "bindings": { @@ -7835,6 +7474,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", + "dev": true, "optional": true, "requires": { "anymatch": "~3.1.1", @@ -8179,7 +7819,8 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true }, "compare-func": { "version": "2.0.0", @@ -11867,11 +11508,6 @@ "minipass": "^2.6.0" } }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" - }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -13694,6 +13330,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "optional": true, "requires": { "binary-extensions": "^2.0.0" @@ -17545,7 +17182,8 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true }, "path-exists": { "version": "3.0.0", @@ -17886,7 +17524,8 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "progress": { "version": "2.0.3", @@ -18538,6 +18177,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -18551,7 +18191,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, @@ -18571,6 +18212,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, "optional": true, "requires": { "picomatch": "^2.2.1" @@ -19132,14 +18774,6 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -20558,6 +20192,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -20565,7 +20200,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, @@ -21864,7 +21500,8 @@ "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true }, "uri-js": { "version": "4.4.0", @@ -21932,7 +21569,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util-promisify": { "version": "2.1.0", diff --git a/packages/api-explorer/__tests__/__snapshots__/index.test.jsx.snap b/packages/api-explorer/__tests__/__snapshots__/index.test.jsx.snap index 53549b400..7f67e939f 100644 --- a/packages/api-explorer/__tests__/__snapshots__/index.test.jsx.snap +++ b/packages/api-explorer/__tests__/__snapshots__/index.test.jsx.snap @@ -4,7 +4,7 @@ exports[`DocAsync should do OAS dereferencing 1`] = ` "

oneOf request with a nested allOf

 
patchhttps://httpbin.org/pets
curl --request PATCH \\\\ --url https://httpbin.org/pets \\\\ --header 'Content-Type: application/json' \\\\ - --data '{\\"pet_type\\":\\"Cat\\"}'
Try the API to see Results

Body Params

string
object
boolean
int32

Response

Updated

allOf with a readOnly prop

 
puthttps://httpbin.org/pets
curl --request PUT \\\\ + --data '{\\"pet_type\\":\\"Cat\\"}'
Try the API to see Results

Body Params

string
object
boolean
int32

Response

Updated

allOf with a readOnly prop

 
puthttps://httpbin.org/pets
curl --request PUT \\\\ --url https://httpbin.org/pets \\\\ - --header 'Content-Type: application/json'
Try the API to see Results

Body Params

object
_self
string
string
string
object
source
string
string
string
int32

Response

successful operation

" + --header 'Content-Type: application/json'
Try the API to see Results

Body Params

object
_self
string
string
string
object
source
string
string
string
int32

Response

successful operation

" `; diff --git a/packages/api-explorer/package-lock.json b/packages/api-explorer/package-lock.json index 7eea89286..177ccc2e9 100644 --- a/packages/api-explorer/package-lock.json +++ b/packages/api-explorer/package-lock.json @@ -2935,17 +2935,6 @@ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -10271,15 +10260,6 @@ "safe-buffer": "^5.0.1" } }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -12713,16 +12693,6 @@ } } }, - "webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, "webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", @@ -12785,12 +12755,6 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/packages/api-explorer/package.json b/packages/api-explorer/package.json index 93d888ce3..777881998 100644 --- a/packages/api-explorer/package.json +++ b/packages/api-explorer/package.json @@ -11,7 +11,7 @@ "directory": "packages/api-explorer" }, "scripts": { - "build": "webpack --config webpack.prod.js", + "build": "webpack --config webpack.config.js", "lint": "eslint . --ext js --ext jsx", "pretest": "npm run lint && npm run prettier", "prettier": "prettier --list-different --write \"./**/**.{js,jsx}\"", @@ -61,8 +61,7 @@ "react-dom": "^16.14.0", "terser-webpack-plugin": "^4.1.0", "webpack": "^4.41.0", - "webpack-cli": "^3.3.9", - "webpack-merge": "^5.1.3" + "webpack-cli": "^3.3.9" }, "prettier": "@readme/eslint-config/prettier" } diff --git a/packages/api-explorer/src/form-components/MultiSchemaField.jsx b/packages/api-explorer/src/form-components/MultiSchemaField.jsx index 99dda71ed..8444408db 100644 --- a/packages/api-explorer/src/form-components/MultiSchemaField.jsx +++ b/packages/api-explorer/src/form-components/MultiSchemaField.jsx @@ -227,7 +227,6 @@ class MultiSchemaField extends Component { const { baseType, disabled, - errorSchema, formData, idPrefix, idSchema, @@ -274,7 +273,6 @@ class MultiSchemaField extends Component {
{children} - {errors &&
{errors}
} {help &&
{help}
}
); } function CustomTemplate(props) { - const { id, classNames, label, help, required, description, errors, children, schema, onKeyChange } = props; + const { id, classNames, label, help, required, description, children, schema, onKeyChange } = props; const EditLabel = ( onKeyChange(event.target.value)} type="text" /> @@ -92,7 +91,6 @@ function CustomTemplate(props) { {description &&
{description}
} {children &&
{children}
} - {errors &&
{errors}
} {help &&
{help}
} ); @@ -172,7 +170,6 @@ CustomTemplate.propTypes = { children: PropTypes.node.isRequired, classNames: PropTypes.string.isRequired, description: PropTypes.node.isRequired, - errors: PropTypes.node.isRequired, help: PropTypes.node.isRequired, id: PropTypes.node.isRequired, label: PropTypes.string, @@ -188,7 +185,6 @@ CustomTemplate.defaultProps = { CustomTemplateShell.propTypes = { children: PropTypes.node.isRequired, classNames: PropTypes.string.isRequired, - errors: PropTypes.node.isRequired, help: PropTypes.node.isRequired, }; diff --git a/packages/api-explorer/webpack.config.js b/packages/api-explorer/webpack.config.js index 4dc901bb6..23b98c2b7 100644 --- a/packages/api-explorer/webpack.config.js +++ b/packages/api-explorer/webpack.config.js @@ -1,24 +1,33 @@ const path = require('path'); +const webpack = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); module.exports = { entry: ['whatwg-fetch', './src/index.jsx'], - externals: { - '@readme/markdown': '@readme/markdown', - '@readme/variable': '@readme/variable', - react: { - root: 'React', - commonjs: 'react', - commonjs2: 'react', - amd: 'react', - }, - 'react-dom': { - root: 'ReactDOM', - commonjs2: 'react-dom', - commonjs: 'react-dom', - amd: 'react-dom', - umd: 'react-dom', + externals: [ + '@readme/markdown', + '@readme/variable', + { + react: { + root: 'React', + commonjs: 'react', + commonjs2: 'react', + amd: 'react', + }, + 'react-dom': { + root: 'ReactDOM', + commonjs2: 'react-dom', + commonjs: 'react-dom', + amd: 'react-dom', + umd: 'react-dom', + }, }, - }, + + // `@readme/ui` loads this in for the search components but unfortunately we aren't able to exclude this from being + // compiled into our dist, despite not using that component. If we treat it as a Webpack external it will thankfully + // be ignored. + 'react-instantsearch-dom', + ], module: { rules: [ { @@ -45,11 +54,20 @@ module.exports = { }, ], }, + optimization: { + minimize: true, + minimizer: [new TerserPlugin()], + }, output: { path: path.resolve(__dirname, 'dist'), filename: 'index.js', libraryTarget: 'commonjs2', }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production'), + }), + ], resolve: { extensions: ['.js', '.json', '.jsx'], }, diff --git a/packages/api-explorer/webpack.prod.js b/packages/api-explorer/webpack.prod.js deleted file mode 100644 index 8e4f3a470..000000000 --- a/packages/api-explorer/webpack.prod.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true }] */ -const webpack = require('webpack'); -const { merge } = require('webpack-merge'); -const TerserPlugin = require('terser-webpack-plugin'); - -const common = require('./webpack.config'); - -module.exports = merge(common, { - optimization: { - minimize: true, - minimizer: [new TerserPlugin()], - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production'), - }), - ], -}); diff --git a/packages/oas-form/.babelrc b/packages/oas-form/.babelrc deleted file mode 100644 index 0d11e9ccb..000000000 --- a/packages/oas-form/.babelrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "presets": [ - "@babel/preset-react", - "@babel/preset-env" - ], - "plugins": [ - "@babel/plugin-proposal-object-rest-spread", - "@babel/plugin-proposal-class-properties", - [ - "@babel/plugin-transform-runtime", - { - "corejs": 2 - } - ] - ], - "env": { - "development": { - "plugins": [ - "@babel/plugin-transform-react-jsx" - ] - } - } -} diff --git a/packages/oas-form/__tests__/ArrayField_test.js b/packages/oas-form/__tests__/ArrayField_test.js index 38625d918..2df78b1a0 100644 --- a/packages/oas-form/__tests__/ArrayField_test.js +++ b/packages/oas-form/__tests__/ArrayField_test.js @@ -2,7 +2,7 @@ import React from 'react'; import { Simulate } from 'react-dom/test-utils'; -import { createFormComponent, submitForm } from './test_utils'; +import { createFormComponent } from './test_utils'; const ArrayKeyDataAttr = 'data-rjsf-itemkey'; const ExposedArrayKeyTemplate = function (props) { @@ -34,8 +34,8 @@ const ExposedArrayKeyTemplate = function (props) { }; describe('ArrayField', () => { - const CustomComponent = props => { - return
{props.rawErrors}
; + const CustomComponent = () => { + return
; }; describe('Unsupported array schema', () => { @@ -137,27 +137,6 @@ describe('ArrayField', () => { expect(node.querySelector('#custom')).not.toBeNull(); }); - it('should pass rawErrors down to custom array field templates', () => { - const schema = { - type: 'array', - title: 'my list', - description: 'my description', - items: { type: 'string' }, - minItems: 2, - }; - - const { node } = createFormComponent({ - schema, - ArrayFieldTemplate: CustomComponent, - formData: [1], - liveValidate: true, - }); - - const matches = node.querySelectorAll('#custom'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('should NOT have fewer than 2 items'); - }); - it('should contain no field in the list by default', () => { const { node } = createFormComponent({ schema }); @@ -437,79 +416,6 @@ describe('ArrayField', () => { expect(dropBtn).toBeNull(); }); - it('should force revalidation when a field is removed', () => { - // refs #195 - const { node } = createFormComponent({ - schema: { - ...schema, - items: { ...schema.items, minLength: 4 }, - }, - formData: ['foo', 'bar!'], - }); - - try { - Simulate.submit(node); - } catch (e) { - // Silencing error thrown as failure is expected here - } - - expect(node.querySelectorAll('.has-error .error-detail')).toHaveLength(1); - - const dropBtns = node.querySelectorAll('.array-item-remove'); - - Simulate.click(dropBtns[0]); - - expect(node.querySelectorAll('.has-error .error-detail')).toHaveLength(0); - }); - - it('should handle cleared field values in the array', () => { - const schema = { - type: 'array', - items: { type: 'integer' }, - }; - const formData = [1, 2, 3]; - const { node, onChange, onError } = createFormComponent({ - liveValidate: true, - schema, - formData, - }); - - Simulate.change(node.querySelector('#root_1'), { - target: { value: '' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { 1: { __errors: ['should be integer'] } }, - errors: [ - { - message: 'should be integer', - name: 'type', - params: { type: 'integer' }, - property: '[1]', - schemaPath: '#/items/type', - stack: '[1] should be integer', - }, - ], - formData: [1, null, 3], - }) - ); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should be integer', - name: 'type', - params: { type: 'integer' }, - property: '[1]', - schemaPath: '#/items/type', - stack: '[1] should be integer', - }, - ]) - ); - }); - it('should render the input widgets with the expected ids', () => { const { node } = createFormComponent({ schema, @@ -657,65 +563,6 @@ describe('ArrayField', () => { expect(inputs[3].value).toBe('Unknown'); }); - it('should not add minItems extra formData entries when schema item is a multiselect', () => { - const schema = { - type: 'object', - properties: { - multipleChoicesList: { - type: 'array', - minItems: 3, - uniqueItems: true, - items: { - type: 'string', - enum: ['Aramis', 'Athos', 'Porthos', "d'Artagnan"], - }, - }, - }, - }; - const uiSchema = { - multipleChoicesList: { - 'ui:widget': 'checkboxes', - }, - }; - let form = createFormComponent({ - schema, - uiSchema, - formData: {}, - liveValidate: true, - noValidate: true, - }); - submitForm(form.node); - - expect(form.onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { multipleChoicesList: [] }, - }), - expect.anything() - ); - - form = createFormComponent({ - schema, - uiSchema, - formData: {}, - liveValidate: true, - noValidate: false, - }); - submitForm(form.node); - - expect(form.onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT have fewer than 3 items', - name: 'minItems', - params: { limit: 3 }, - property: '.multipleChoicesList', - schemaPath: '#/properties/multipleChoicesList/minItems', - stack: '.multipleChoicesList should NOT have fewer than 3 items', - }, - ]) - ); - }); - it('should honor given formData, even when it does not meet ths minItems-requirement', () => { const complexSchema = { type: 'object', @@ -859,21 +706,6 @@ describe('ArrayField', () => { expect(node.querySelector('select').id).toBe('root'); }); - - it('should pass rawErrors down to custom widgets', () => { - const { node } = createFormComponent({ - schema, - widgets: { - SelectWidget: CustomComponent, - }, - formData: ['foo', 'foo'], - liveValidate: true, - }); - - const matches = node.querySelectorAll('#custom'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('should NOT have duplicate items (items ## 1 and 0 are identical)'); - }); }); describe('CheckboxesWidget', () => { @@ -944,33 +776,6 @@ describe('ArrayField', () => { expect(node.querySelectorAll('.checkbox-inline')).toHaveLength(3); }); - - it('should pass rawErrors down to custom widgets', () => { - const schema = { - type: 'array', - title: 'My field', - items: { - enum: ['foo', 'bar', 'fuzz'], - type: 'string', - }, - minItems: 3, - uniqueItems: true, - }; - - const { node } = createFormComponent({ - schema, - widgets: { - CheckboxesWidget: CustomComponent, - }, - uiSchema, - formData: [], - liveValidate: true, - }); - - const matches = node.querySelectorAll('#custom'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('should NOT have fewer than 3 items'); - }); }); }); @@ -1051,31 +856,6 @@ describe('ArrayField', () => { expect(node.querySelector('input[type=file]').id).toBe('root'); }); - - it('should pass rawErrors down to custom widgets', () => { - const schema = { - type: 'array', - title: 'My field', - items: { - type: 'string', - format: 'data-url', - }, - minItems: 5, - }; - - const { node } = createFormComponent({ - schema, - widgets: { - FileWidget: CustomComponent, - }, - formData: [], - liveValidate: true, - }); - - const matches = node.querySelectorAll('#custom'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('should NOT have fewer than 5 items'); - }); }); describe('Nested lists', () => { @@ -1110,44 +890,6 @@ describe('ArrayField', () => { expect(node.querySelectorAll('fieldset fieldset')).toHaveLength(1); }); - - it('should pass rawErrors down to every level of custom widgets', () => { - const CustomItem = props =>
{props.children}
; - const CustomTemplate = props => { - return ( -
- {props.items && props.items.map((p, i) => )} -
{props.rawErrors && props.rawErrors.join(', ')}
-
- ); - }; - - const schema = { - type: 'array', - title: 'A list of arrays', - items: { - type: 'array', - title: 'A list of numbers', - items: { - type: 'number', - }, - minItems: 3, - }, - minItems: 2, - }; - - const { node } = createFormComponent({ - schema, - ArrayFieldTemplate: CustomTemplate, - formData: [[]], - liveValidate: true, - }); - - const matches = node.querySelectorAll('#custom-error'); - expect(matches).toHaveLength(2); - expect(matches[0]).toHaveTextContent('should NOT have fewer than 3 items'); - expect(matches[1]).toHaveTextContent('should NOT have fewer than 2 items'); - }); }); describe('Fixed items lists', () => { diff --git a/packages/oas-form/__tests__/BooleanField_test.js b/packages/oas-form/__tests__/BooleanField_test.js index 500f8c685..fb757b497 100644 --- a/packages/oas-form/__tests__/BooleanField_test.js +++ b/packages/oas-form/__tests__/BooleanField_test.js @@ -199,7 +199,6 @@ describe('BooleanField', () => { it('formData should default to undefined', () => { const { node, onSubmit } = createFormComponent({ schema: { type: 'boolean' }, - noValidate: true, }); submitForm(node); expect(onSubmit).toHaveBeenLastCalledWith( diff --git a/packages/oas-form/__tests__/Form_test.js b/packages/oas-form/__tests__/Form_test.js index 585428ad8..f7679d6fc 100644 --- a/packages/oas-form/__tests__/Form_test.js +++ b/packages/oas-form/__tests__/Form_test.js @@ -1,4 +1,3 @@ -/* eslint-disable global-require */ /* eslint-disable react/no-find-dom-node */ /* eslint-disable max-classes-per-file */ import React from 'react'; @@ -10,2032 +9,206 @@ import { createRef } from 'create-react-ref'; import Form from '../src'; import { createComponent, createFormComponent, setProps, submitForm } from './test_utils'; -const formExtraPropsList = [ - [{ omitExtraData: false }], - [{ omitExtraData: true }], - [{ omitExtraData: true, liveOmit: true }], -]; +let createFormComponentFn; -describe.each(formExtraPropsList)('Form with props: `%s`', formExtraProps => { - let createFormComponentFn; - - beforeAll(() => { - createFormComponentFn = props => createFormComponent({ ...props, ...formExtraProps }); - }); - - describe('Empty schema', () => { - it('should render a form tag', () => { - const { node } = createFormComponentFn({ schema: {} }); - - expect(node.tagName).toBe('FORM'); - }); - - it('should render a submit button', () => { - const { node } = createFormComponent({ schema: {} }); - - expect(node.querySelectorAll('button[type=submit]')).toHaveLength(1); - }); - - it('should render children buttons', () => { - const props = { schema: {} }; - const comp = renderIntoDocument( -
- - -
- ); - const node = findDOMNode(comp); - expect(node.querySelectorAll('button[type=submit]')).toHaveLength(2); - }); - - it("should render errors if schema isn't object", () => { - const props = { - schema: { - type: 'object', - title: 'object', - properties: { - firstName: 'some mame', - address: { - $ref: '#/definitions/address', - }, - }, - definitions: { - address: { - street: 'some street', - }, - }, - }, - }; - const comp = renderIntoDocument( -
- -
- ); - const node = findDOMNode(comp); - expect(node.querySelector('.unsupported-field')).toHaveTextContent('Unknown field type undefined'); - }); - }); - - describe('on component creation', () => { - let onChangeProp; - let formData; - let schema; - - function createComponent() { - renderIntoDocument( -
- - -
- ); - } - - beforeEach(() => { - onChangeProp = jest.fn(); - schema = { - type: 'object', - title: 'root object', - required: ['count'], - properties: { - count: { - type: 'number', - default: 789, - }, - }, - }; - }); - - describe('when props.formData does not equal the default values', () => { - beforeEach(() => { - formData = { - foo: 123, - }; - createComponent(); - }); - - it('should call props.onChange with current state', () => { - expect(onChangeProp).toHaveBeenCalledTimes(1); - expect(onChangeProp).toHaveBeenCalledWith({ - formData: { ...formData, count: 789 }, - schema, - errorSchema: {}, - errors: [], - edit: true, - uiSchema: {}, - idSchema: { $id: 'root', count: { $id: 'root_count' } }, - additionalMetaSchemas: undefined, - }); - }); - }); - - describe('when props.formData equals the default values', () => { - beforeEach(() => { - formData = { - count: 789, - }; - createComponent(); - }); - - it('should not call props.onChange', () => { - expect(onChangeProp).not.toHaveBeenCalled(); - }); - }); - }); - - describe('Option idPrefix', function () { - it('should change the rendered ids', function () { - const schema = { - type: 'object', - title: 'root object', - required: ['foo'], - properties: { - count: { - type: 'number', - }, - }, - }; - const comp = renderIntoDocument(
); - const node = findDOMNode(comp); - const inputs = node.querySelectorAll('input'); - const ids = []; - for (let i = 0, len = inputs.length; i < len; i++) { - const input = inputs[i]; - ids.push(input.getAttribute('id')); - } - expect(ids).toStrictEqual(['rjsf_count']); - expect(node.querySelector('fieldset').id).toBe('rjsf'); - }); - }); - - describe('Changing idPrefix', function () { - it('should work with simple example', function () { - const schema = { - type: 'object', - title: 'root object', - required: ['foo'], - properties: { - count: { - type: 'number', - }, - }, - }; - const comp = renderIntoDocument(); - const node = findDOMNode(comp); - const inputs = node.querySelectorAll('input'); - const ids = []; - for (let i = 0, len = inputs.length; i < len; i++) { - const input = inputs[i]; - ids.push(input.getAttribute('id')); - } - expect(ids).toStrictEqual(['rjsf_count']); - expect(node.querySelector('fieldset').id).toBe('rjsf'); - }); - - it('should work with oneOf', function () { - const schema = { - $schema: 'http://json-schema.org/draft-06/schema#', - type: 'object', - properties: { - connector: { - type: 'string', - enum: ['aws', 'gcp'], - title: 'Provider', - default: 'aws', - }, - }, - dependencies: { - connector: { - oneOf: [ - { - type: 'object', - properties: { - connector: { - type: 'string', - enum: ['aws'], - }, - key_aws: { - type: 'string', - }, - }, - }, - { - type: 'object', - properties: { - connector: { - type: 'string', - enum: ['gcp'], - }, - key_gcp: { - type: 'string', - }, - }, - }, - ], - }, - }, - }; - - const comp = renderIntoDocument(); - const node = findDOMNode(comp); - const inputs = node.querySelectorAll('input'); - const ids = []; - for (let i = 0, len = inputs.length; i < len; i++) { - const input = inputs[i]; - ids.push(input.getAttribute('id')); - } - expect(ids).toStrictEqual(['rjsf_key_aws']); - }); - }); - - describe('Custom field template', () => { - const schema = { - type: 'object', - title: 'root object', - required: ['foo'], - properties: { - foo: { - type: 'string', - description: 'this is description', - minLength: 32, - }, - }, - }; - - const uiSchema = { - foo: { - 'ui:help': 'this is help', - }, - }; - - const formData = { foo: 'invalid' }; - - function FieldTemplate(props) { - const { - id, - classNames, - label, - help, - rawHelp, - required, - description, - rawDescription, - errors, - rawErrors, - children, - } = props; - return ( -
- - {description} - {children} - {errors} - {help} - {`${rawHelp} rendered from the raw format`} - {`${rawDescription} rendered from the raw format`} - {rawErrors ? ( -
    - {rawErrors.map((error, i) => ( -
  • - {error} -
  • - ))} -
- ) : null} -
- ); - } - - let node; - - beforeEach(() => { - node = createFormComponent({ - schema, - uiSchema, - formData, - FieldTemplate, - liveValidate: true, - }).node; - }); - - it('should use the provided field template', () => { - expect(node.querySelector('.my-template')).not.toBeNull(); - }); - - it('should use the provided template for labels', () => { - expect(node.querySelector('.my-template > label')).toHaveTextContent('root object'); - expect(node.querySelector('.my-template .field-string > label')).toHaveTextContent('foo*'); - }); - - it('should pass description as the provided React element', () => { - expect(node.querySelector('#root_foo__description')).toHaveTextContent('this is description'); - }); - - it('should pass rawDescription as a string', () => { - expect(node.querySelector('.raw-description')).toHaveTextContent( - 'this is description rendered from the raw format' - ); - }); - - it('should pass errors as the provided React component', () => { - expect(node.querySelectorAll('.error-detail li')).toHaveLength(1); - }); - - it('should pass rawErrors as an array of strings', () => { - expect(node.querySelectorAll('.raw-error')).toHaveLength(1); - }); - - it('should pass help as a the provided React element', () => { - expect(node.querySelector('.help-block')).toHaveTextContent('this is help'); - }); - - it('should pass rawHelp as a string', () => { - expect(node.querySelector('.raw-help')).toHaveTextContent('this is help rendered from the raw format'); - }); - }); - - describe('Schema definitions', () => { - it('should use a single schema definition reference', () => { - const schema = { - definitions: { - testdef: { type: 'string' }, - }, - $ref: '#/definitions/testdef', - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); - }); - - it('should recursively handle refer multiple schema definition references', () => { - const schema = { - definitions: { - testdef: { type: 'string' }, - }, - type: 'object', - properties: { - foo: { $ref: '#/definitions/testdef' }, - bar: { $ref: '#/definitions/testdef' }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(2); - }); - - it('should handle deeply referenced schema definitions', () => { - const schema = { - definitions: { - testdef: { type: 'string' }, - }, - type: 'object', - properties: { - foo: { - type: 'object', - properties: { - bar: { $ref: '#/definitions/testdef' }, - }, - }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); - }); - - it('should handle references to deep schema definitions', () => { - const schema = { - definitions: { - testdef: { - type: 'object', - properties: { - bar: { type: 'string' }, - }, - }, - }, - type: 'object', - properties: { - foo: { $ref: '#/definitions/testdef/properties/bar' }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); - }); - - it('should handle referenced definitions for array items', () => { - const schema = { - definitions: { - testdef: { type: 'string' }, - }, - type: 'object', - properties: { - foo: { - type: 'array', - items: { $ref: '#/definitions/testdef' }, - }, - }, - }; - - const { node } = createFormComponent({ - schema, - formData: { - foo: ['blah'], - }, - }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); - }); - - it('should raise for non-existent definitions referenced', () => { - const schema = { - type: 'object', - properties: { - foo: { $ref: '#/definitions/nonexistent' }, - }, - }; - - expect(() => createFormComponent({ schema })).toThrow(/#\/definitions\/nonexistent/); - }); - - it('should propagate referenced definition defaults', () => { - const schema = { - definitions: { - testdef: { type: 'string', default: 'hello' }, - }, - $ref: '#/definitions/testdef', - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelector('input[type=text]').value).toBe('hello'); - }); - - it('should propagate nested referenced definition defaults', () => { - const schema = { - definitions: { - testdef: { type: 'string', default: 'hello' }, - }, - type: 'object', - properties: { - foo: { $ref: '#/definitions/testdef' }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelector('input[type=text]').value).toBe('hello'); - }); - - it('should propagate referenced definition defaults for array items', () => { - const schema = { - definitions: { - testdef: { type: 'string', default: 'hello' }, - }, - type: 'array', - items: { - $ref: '#/definitions/testdef', - }, - }; - - const { node } = createFormComponent({ schema }); - - Simulate.click(node.querySelector('.array-item-add button')); - - expect(node.querySelector('input[type=text]').value).toBe('hello'); - }); - - it('should propagate referenced definition defaults in objects with additionalProperties', () => { - const schema = { - definitions: { - testdef: { type: 'string' }, - }, - type: 'object', - additionalProperties: { - $ref: '#/definitions/testdef', - }, - }; - - const { node } = createFormComponent({ schema }); - - Simulate.click(node.querySelector('.btn-add')); - - expect(node.querySelector('input[type=text]').value).toBe('newKey'); - }); - - it('should propagate referenced definition defaults in objects with additionalProperties that have a type present', () => { - // Though `additionalProperties` has a `type` present here, it also has a `$ref` so that - // referenced schema should override it. - const schema = { - definitions: { - testdef: { type: 'number' }, - }, - type: 'object', - additionalProperties: { - type: 'string', - $ref: '#/definitions/testdef', - }, - }; - - const { node } = createFormComponent({ schema }); - - Simulate.click(node.querySelector('.btn-add')); - - expect(node.querySelector('input[type=number]').value).toBe('0'); - }); - - it('should follow recursive references', () => { - const schema = { - definitions: { - bar: { $ref: '#/definitions/qux' }, - qux: { type: 'string' }, - }, - type: 'object', - required: ['foo'], - properties: { - foo: { $ref: '#/definitions/bar' }, - }, - }; - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); - }); - - it('should follow multiple recursive references', () => { - const schema = { - definitions: { - bar: { $ref: '#/definitions/bar2' }, - bar2: { $ref: '#/definitions/qux' }, - qux: { type: 'string' }, - }, - type: 'object', - required: ['foo'], - properties: { - foo: { $ref: '#/definitions/bar' }, - }, - }; - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); - }); - - it('should priorize definition over schema type property', () => { - // Refs bug #140 - const schema = { - type: 'object', - properties: { - name: { type: 'string' }, - childObj: { - type: 'object', - $ref: '#/definitions/childObj', - }, - }, - definitions: { - childObj: { - type: 'object', - properties: { - otherName: { type: 'string' }, - }, - }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('input[type=text]')).toHaveLength(2); - }); - - it('should priorize local properties over definition ones', () => { - // Refs bug #140 - const schema = { - type: 'object', - properties: { - foo: { - title: 'custom title', - $ref: '#/definitions/objectDef', - }, - }, - definitions: { - objectDef: { - type: 'object', - title: 'definition title', - properties: { - field: { type: 'string' }, - }, - }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelector('legend')).toHaveTextContent('custom title'); - }); - - it('should propagate and handle a resolved schema definition', () => { - const schema = { - definitions: { - enumDef: { type: 'string', enum: ['a', 'b'] }, - }, - type: 'object', - properties: { - name: { $ref: '#/definitions/enumDef' }, - }, - }; - - const { node } = createFormComponent({ schema }); - - expect(node.querySelectorAll('option')).toHaveLength(3); - }); - }); - - describe('Default value handling on clear', () => { - const schema = { - type: 'string', - default: 'foo', - }; - - it('should not set default when a text field is cleared', () => { - const { node } = createFormComponent({ schema, formData: 'bar' }); - - Simulate.change(node.querySelector('input'), { - target: { value: '' }, - }); - - expect(node.querySelector('input').value).toBe(''); - }); - }); - - describe('Defaults array items default propagation', () => { - const schema = { - type: 'object', - title: 'lvl 1 obj', - properties: { - object: { - type: 'object', - title: 'lvl 2 obj', - properties: { - array: { - type: 'array', - items: { - type: 'object', - title: 'lvl 3 obj', - properties: { - bool: { - type: 'boolean', - default: true, - }, - }, - }, - }, - }, - }, - }, - }; - - it('should propagate deeply nested defaults to submit handler', () => { - const { node, onSubmit } = createFormComponent({ schema }); - - Simulate.click(node.querySelector('.array-item-add button')); - Simulate.submit(node); - - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { - object: { - array: [ - { - bool: true, - }, - ], - }, - }, - }), - expect.anything() - ); - }); - }); - - describe('Submit handler', () => { - it('should call provided submit handler with form state', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }; - const formData = { - foo: 'bar', - }; - const onSubmit = jest.fn(); - const event = { type: 'submit' }; - const { node } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - Simulate.submit(node, event); - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ formData, schema }), - expect.objectContaining(event) - ); - }); - - it('should not call provided submit handler on validation errors', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - minLength: 1, - }, - }, - }; - const formData = { - foo: '', - }; - const onSubmit = jest.fn(); - const onError = jest.fn(); - const { node } = createFormComponent({ - schema, - formData, - onSubmit, - onError, - }); - - Simulate.submit(node); - - expect(onSubmit).not.toHaveBeenCalled(); - }); - }); - - describe('Change handler', () => { - it('should call provided change handler on form state change', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - }, - }, - }; - const formData = { - foo: '', - }; - const onChange = jest.fn(); - const { node } = createFormComponent({ - schema, - formData, - onChange, - }); - - Simulate.change(node.querySelector('[type=text]'), { - target: { value: 'new' }, - }); - - expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ - formData: { - foo: 'new', - }, - }) - ); - }); - }); - - describe('Blur handler', () => { - it('should call provided blur handler on form input blur event', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - }, - }, - }; - const formData = { - foo: '', - }; - const onBlur = jest.fn(); - const { node } = createFormComponent({ schema, formData, onBlur }); - - const input = node.querySelector('[type=text]'); - Simulate.blur(input, { - target: { value: 'new' }, - }); - - expect(onBlur).toHaveBeenCalledWith(input.id, 'new'); - }); - }); - - describe('Focus handler', () => { - it('should call provided focus handler on form input focus event', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - }, - }, - }; - const formData = { - foo: '', - }; - const onFocus = jest.fn(); - const { node } = createFormComponent({ schema, formData, onFocus }); - - const input = node.querySelector('[type=text]'); - Simulate.focus(input, { - target: { value: 'new' }, - }); - - expect(onFocus).toHaveBeenCalledWith(input.id, 'new'); - }); - }); - - describe('Error handler', () => { - it('should call provided error handler on validation errors', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - minLength: 1, - }, - }, - }; - const formData = { - foo: '', - }; - const onError = jest.fn(); - const { node } = createFormComponent({ schema, formData, onError }); - - Simulate.submit(node); - - expect(onError).toHaveBeenCalledTimes(1); - }); - }); - - describe('Schema and external formData updates', () => { - let comp; - let onChangeProp; - let formProps; - - beforeEach(() => { - onChangeProp = jest.fn(); - formProps = { - schema: { - type: 'string', - default: 'foobar', - }, - formData: 'some value', - onChange: onChangeProp, - }; - comp = createFormComponent(formProps).comp; - }); - - describe('when the form data is set to null', () => { - beforeEach(() => - setProps(comp, { - ...formProps, - formData: null, - }) - ); - - it('should call onChange', () => { - expect(onChangeProp).toHaveBeenCalledTimes(1); - expect(onChangeProp).toHaveBeenCalledWith({ - additionalMetaSchemas: undefined, - edit: true, - errorSchema: {}, - errors: [], - formData: 'foobar', - idSchema: { $id: 'root' }, - schema: formProps.schema, - uiSchema: {}, - }); - }); - }); - - describe('when the schema default is changed but formData is not changed', () => { - const newSchema = { - type: 'string', - default: 'the new default', - }; - - beforeEach(() => - setProps(comp, { - ...formProps, - schema: newSchema, - formData: 'some value', - }) - ); - - it('should not call onChange', () => { - expect(onChangeProp).not.toHaveBeenCalled(); - }); - }); - - describe('when the schema default is changed and formData is changed', () => { - const newSchema = { - type: 'string', - default: 'the new default', - }; - - beforeEach(() => - setProps(comp, { - ...formProps, - schema: newSchema, - formData: 'something else', - }) - ); - - it('should not call onChange', () => { - expect(onChangeProp).not.toHaveBeenCalled(); - }); - }); - - describe('when the schema default is changed and formData is nulled', () => { - const newSchema = { - type: 'string', - default: 'the new default', - }; - - beforeEach(() => - setProps(comp, { - ...formProps, - schema: newSchema, - formData: null, - }) - ); - - it('should call onChange', () => { - expect(onChangeProp).toHaveBeenCalledTimes(1); - expect(onChangeProp).toHaveBeenCalledWith( - expect.objectContaining({ - schema: newSchema, - formData: 'the new default', - }) - ); - }); - }); - - describe('when the onChange prop sets formData to a falsey value', () => { - class TestForm extends React.Component { - constructor() { - super(); - - this.state = { - formData: {}, - }; - } - - onChange = () => { - this.setState({ formData: this.props.falseyValue }); - }; - - render() { - const schema = { - type: 'object', - properties: { - value: { - type: 'string', - }, - }, - }; - return ; - } - } - - it.each([[0], [false], [null], [undefined], [NaN]])( - 'should not crash due to "Maximum call stack size exceeded..." with `%s`"', - falsyValue => { - // It is expected that this will throw an error due to non-matching propTypes, - // so the error message needs to be inspected - expect(() => { - createComponent(TestForm, { falsyValue }); - }).not.toThrow('Maximum call stack size exceeded'); - } - ); - }); - }); - - describe('External formData updates', () => { - describe('root level', () => { - const formProps = { - schema: { type: 'string' }, - liveValidate: true, - }; - - it('should call submit handler with new formData prop value', () => { - const { comp, node, onSubmit } = createFormComponent(formProps); - - setProps(comp, { - ...formProps, - onSubmit, - formData: 'yo', - }); - submitForm(node); - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: 'yo', - }), - expect.anything() - ); - }); - - it('should validate formData when the schema is updated', () => { - const { comp, node, onError } = createFormComponent(formProps); - - setProps(comp, { - ...formProps, - onError, - formData: 'yo', - schema: { type: 'number' }, - }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should be number', - name: 'type', - params: { type: 'number' }, - property: '', - schemaPath: '#/type', - stack: 'should be number', - }, - ]) - ); - }); - }); - - describe('object level', () => { - it('should call submit handler with new formData prop value', () => { - const formProps = { - schema: { type: 'object', properties: { foo: { type: 'string' } } }, - }; - const { comp, onSubmit, node } = createFormComponent(formProps); - - setProps(comp, { - ...formProps, - onSubmit, - formData: { foo: 'yo' }, - }); - - submitForm(node); - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { foo: 'yo' }, - }), - expect.anything() - ); - }); - }); - - describe('array level', () => { - it('should call submit handler with new formData prop value', () => { - const schema = { - type: 'array', - items: { - type: 'string', - }, - }; - const { comp, node, onSubmit } = createFormComponent({ schema }); - - setProps(comp, { schema, onSubmit, formData: ['yo'] }); - - submitForm(node); - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: ['yo'], - }), - expect.anything() - ); - }); - }); - }); - - describe('Internal formData updates', () => { - it('root', () => { - const formProps = { - schema: { type: 'string' }, - liveValidate: true, - }; - const { node, onChange } = createFormComponent(formProps); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'yo' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: 'yo', - }) - ); - }); - - it('object', () => { - const { node, onChange } = createFormComponent({ - schema: { - type: 'object', - properties: { - foo: { - type: 'string', - }, - }, - }, - }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'yo' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { foo: 'yo' }, - }) - ); - }); - - it('array of strings', () => { - const schema = { - type: 'array', - items: { - type: 'string', - }, - }; - const { node, onChange } = createFormComponent({ schema }); - - Simulate.click(node.querySelector('.array-item-add button')); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'yo' }, - }); - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: ['yo'], - }) - ); - }); - - it('array of objects', () => { - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - }, - }, - }; - const { node, onChange } = createFormComponent({ schema }); - - Simulate.click(node.querySelector('.array-item-add button')); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'yo' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: [{ name: 'yo' }], - }) - ); - }); - - it('dependency with array of objects', () => { - const schema = { - definitions: {}, - type: 'object', - properties: { - show: { - type: 'boolean', - }, - }, - dependencies: { - show: { - oneOf: [ - { - properties: { - show: { - const: true, - }, - participants: { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - }, - }, - }, - }, - ], - }, - }, - }; - const { node, onChange } = createFormComponent({ schema }); - - Simulate.change(node.querySelector('[type=checkbox]'), { - target: { checked: true }, - }); - - Simulate.click(node.querySelector('.array-item-add button')); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'yo' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { - show: true, - participants: [{ name: 'yo' }], - }, - }) - ); - }); - }); - - describe('Error contextualization', () => { - describe('on form state updated', () => { - const schema = { - type: 'string', - minLength: 8, - }; - - describe('Lazy validation', () => { - it('should not update the errorSchema when the formData changes', () => { - const { node, onChange } = createFormComponent({ schema }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.not.objectContaining({ - errorSchema: undefined, - }) - ); - }); - - it('should not denote an error in the field', () => { - const { node } = createFormComponent({ schema }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - - expect(node.querySelectorAll('.field-error')).toHaveLength(0); - }); - - it("should clean contextualized errors up when they're fixed", () => { - const altSchema = { - type: 'object', - properties: { - field1: { type: 'string', minLength: 8 }, - field2: { type: 'string', minLength: 8 }, - }, - }; - const { node } = createFormComponent({ - schema: altSchema, - formData: { - field1: 'short', - field2: 'short', - }, - }); - - Simulate.submit(node); - - // Fix the first field - Simulate.change(node.querySelectorAll('input[type=text]')[0], { - target: { value: 'fixed error' }, - }); - Simulate.submit(node); - - expect(node.querySelectorAll('.field-error')).toHaveLength(1); - - // Fix the second field - Simulate.change(node.querySelectorAll('input[type=text]')[1], { - target: { value: 'fixed error too' }, - }); - Simulate.submit(node); - - // No error remaining, shouldn't throw. - Simulate.submit(node); - - expect(node.querySelectorAll('.field-error')).toHaveLength(0); - }); - }); - - describe('Live validation', () => { - it('should update the errorSchema when the formData changes', () => { - const { node, onChange } = createFormComponent({ - schema, - liveValidate: true, - }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { - __errors: ['should NOT be shorter than 8 characters'], - }, - }) - ); - }); - - it('should denote the new error in the field', () => { - const { node } = createFormComponent({ - schema, - liveValidate: true, - }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - - expect(node.querySelectorAll('.field-error')).toHaveLength(1); - expect(node.querySelector('.field-string .error-detail')).toHaveTextContent( - 'should NOT be shorter than 8 characters' - ); - }); - }); - - describe('Disable validation onChange event', () => { - it('should not update errorSchema when the formData changes', () => { - const { node, onChange } = createFormComponent({ - schema, - noValidate: true, - liveValidate: true, - }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.not.objectContaining({ - errorSchema: undefined, - }) - ); - }); - }); - - describe('Disable validation onSubmit event', () => { - it('should not update errorSchema when the formData changes', () => { - const { node, onSubmit } = createFormComponent({ - schema, - noValidate: true, - }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - Simulate.submit(node); - - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: {}, - }), - expect.anything() - ); - }); - }); - }); - - describe('on form submitted', () => { - const schema = { - type: 'string', - minLength: 8, - }; - - it('should call the onError handler', () => { - const onError = jest.fn(); - const { node } = createFormComponent({ schema, onError }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - Simulate.submit(node); - - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('should NOT be shorter than 8 characters'), - }), - ]) - ); - }); - - it('should reset errors and errorSchema state to initial state after correction and resubmission', () => { - const { node, onError } = createFormComponent({ - schema, - }); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'short' }, - }); - Simulate.submit(node); - - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 8 characters', - name: 'minLength', - params: { limit: 8 }, - property: '', - schemaPath: '#/minLength', - stack: 'should NOT be shorter than 8 characters', - }, - ]) - ); - expect(onError).toHaveBeenCalledTimes(1); - onError.mockClear(); - - Simulate.change(node.querySelector('input[type=text]'), { - target: { value: 'long enough' }, - }); - Simulate.submit(node); - expect(onError).not.toHaveBeenCalled(); - }); - }); - - describe('root level', () => { - const formProps = { - liveValidate: true, - schema: { - type: 'string', - minLength: 8, - }, - formData: 'short', - }; - - it('should reflect the contextualized error in state', () => { - const { node, onError } = createFormComponent(formProps); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 8 characters', - name: 'minLength', - params: { limit: 8 }, - property: '', - schemaPath: '#/minLength', - stack: 'should NOT be shorter than 8 characters', - }, - ]) - ); - }); - - it('should denote the error in the field', () => { - const { node } = createFormComponent(formProps); - - expect(node.querySelectorAll('.field-error')).toHaveLength(1); - expect(node.querySelector('.field-string .error-detail')).toHaveTextContent( - 'should NOT be shorter than 8 characters' - ); - }); - }); - - describe('root level with multiple errors', () => { - const formProps = { - liveValidate: true, - schema: { - type: 'string', - minLength: 8, - pattern: 'd+', - }, - formData: 'short', - }; - - it('should reflect the contextualized error in state', () => { - const { node, onError } = createFormComponent(formProps); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 8 characters', - name: 'minLength', - params: { limit: 8 }, - property: '', - schemaPath: '#/minLength', - stack: 'should NOT be shorter than 8 characters', - }, - { - message: 'should match pattern "d+"', - name: 'pattern', - params: { pattern: 'd+' }, - property: '', - schemaPath: '#/pattern', - stack: 'should match pattern "d+"', - }, - ]) - ); - }); - - it('should denote the error in the field', () => { - const { node } = createFormComponent(formProps); - - const liNodes = node.querySelectorAll('.field-string .error-detail li'); - const errors = [].map.call(liNodes, li => li.textContent); - - expect(errors).toStrictEqual(['should NOT be shorter than 8 characters', 'should match pattern "d+"']); - }); - }); - - describe('nested field level', () => { - const schema = { - type: 'object', - properties: { - level1: { - type: 'object', - properties: { - level2: { - type: 'string', - minLength: 8, - }, - }, - }, - }, - }; - - const formProps = { - schema, - liveValidate: true, - formData: { - level1: { - level2: 'short', - }, - }, - }; - - it('should reflect the contextualized error in state', () => { - const { node, onError } = createFormComponent(formProps); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 8 characters', - name: 'minLength', - params: { limit: 8 }, - property: '.level1.level2', - schemaPath: '#/properties/level1/properties/level2/minLength', - stack: '.level1.level2 should NOT be shorter than 8 characters', - }, - ]) - ); - }); - - it('should denote the error in the field', () => { - const { node } = createFormComponent(formProps); - const errorDetail = node.querySelector('.field-object .field-string .error-detail'); - - expect(node.querySelectorAll('.field-error')).toHaveLength(1); - expect(errorDetail).toHaveTextContent('should NOT be shorter than 8 characters'); - }); - }); - - describe('array indices', () => { - const schema = { - type: 'array', - items: { - type: 'string', - minLength: 4, - }, - }; - - const formProps = { - schema, - liveValidate: true, - formData: ['good', 'bad', 'good'], - }; - - it('should contextualize the error for array indices', () => { - const { node, onError } = createFormComponent(formProps); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 4 characters', - name: 'minLength', - params: { limit: 4 }, - property: '[1]', - schemaPath: '#/items/minLength', - stack: '[1] should NOT be shorter than 4 characters', - }, - ]) - ); - }); - - it('should denote the error in the item field in error', () => { - const { node } = createFormComponent(formProps); - const fieldNodes = node.querySelectorAll('.field-string'); - - const liNodes = fieldNodes[1].querySelectorAll('.field-string .error-detail li'); - const errors = [].map.call(liNodes, li => li.textContent); - - expect(fieldNodes[1]).toHaveClass('field-error'); - expect(errors).toStrictEqual(['should NOT be shorter than 4 characters']); - }); - - it('should not denote errors on non impacted fields', () => { - const { node } = createFormComponent(formProps); - const fieldNodes = node.querySelectorAll('.field-string'); - - expect(fieldNodes[0]).not.toHaveClass('field-error'); - expect(fieldNodes[2]).not.toHaveClass('field-error'); - }); - }); - - describe('nested array indices', () => { - const schema = { - type: 'object', - properties: { - level1: { - type: 'array', - items: { - type: 'string', - minLength: 4, - }, - }, - }, - }; - - const formProps = { schema, liveValidate: true }; - - it('should contextualize the error for nested array indices', () => { - const { node, onError } = createFormComponent({ - ...formProps, - formData: { - level1: ['good', 'bad', 'good', 'bad'], - }, - }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 4 characters', - name: 'minLength', - params: { limit: 4 }, - property: '.level1[1]', - schemaPath: '#/properties/level1/items/minLength', - stack: '.level1[1] should NOT be shorter than 4 characters', - }, - { - message: 'should NOT be shorter than 4 characters', - name: 'minLength', - params: { limit: 4 }, - property: '.level1[3]', - schemaPath: '#/properties/level1/items/minLength', - stack: '.level1[3] should NOT be shorter than 4 characters', - }, - ]) - ); - }); - - it('should denote the error in the nested item field in error', () => { - const { node } = createFormComponent({ - ...formProps, - formData: { - level1: ['good', 'bad', 'good'], - }, - }); - - const liNodes = node.querySelectorAll('.field-string .error-detail li'); - const errors = [].map.call(liNodes, li => li.textContent); - - expect(errors).toStrictEqual(['should NOT be shorter than 4 characters']); - }); - }); - - describe('nested arrays', () => { - const schema = { - type: 'object', - properties: { - outer: { - type: 'array', - items: { - type: 'array', - items: { - type: 'string', - minLength: 4, - }, - }, - }, - }, - }; - - const formData = { - outer: [ - ['good', 'bad'], - ['bad', 'good'], - ], - }; - - const formProps = { schema, formData, liveValidate: true }; - - it('should contextualize the error for nested array indices', () => { - const { node, onError } = createFormComponent(formProps); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 4 characters', - name: 'minLength', - params: { limit: 4 }, - property: '.outer[0][1]', - schemaPath: '#/properties/outer/items/items/minLength', - stack: '.outer[0][1] should NOT be shorter than 4 characters', - }, - { - message: 'should NOT be shorter than 4 characters', - name: 'minLength', - params: { limit: 4 }, - property: '.outer[1][0]', - schemaPath: '#/properties/outer/items/items/minLength', - stack: '.outer[1][0] should NOT be shorter than 4 characters', - }, - ]) - ); - }); - - it('should denote the error in the nested item field in error', () => { - const { node } = createFormComponent(formProps); - const fields = node.querySelectorAll('.field-string'); - const errors = [].map.call(fields, field => { - const li = field.querySelector('.error-detail li'); - return li && li.textContent; - }); - - expect(errors).toStrictEqual([ - null, - 'should NOT be shorter than 4 characters', - 'should NOT be shorter than 4 characters', - null, - ]); - }); - }); - - describe('array nested items', () => { - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - foo: { - type: 'string', - minLength: 4, - }, - }, - }, - }; - - const formProps = { - schema, - liveValidate: true, - formData: [{ foo: 'good' }, { foo: 'bad' }, { foo: 'good' }], - }; +beforeAll(() => { + createFormComponentFn = props => createFormComponent({ ...props }); +}); - it('should contextualize the error for array nested items', () => { - const { node, onError } = createFormComponent(formProps); +describe('Empty schema', () => { + it('should render a form tag', () => { + const { node } = createFormComponentFn({ schema: {} }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 4 characters', - name: 'minLength', - params: { limit: 4 }, - property: '[1].foo', - schemaPath: '#/items/properties/foo/minLength', - stack: '[1].foo should NOT be shorter than 4 characters', - }, - ]) - ); - }); + expect(node.tagName).toBe('FORM'); + }); - it('should denote the error in the array nested item', () => { - const { node } = createFormComponent(formProps); - const fieldNodes = node.querySelectorAll('.field-string'); + it('should render a submit button', () => { + const { node } = createFormComponent({ schema: {} }); - const liNodes = fieldNodes[1].querySelectorAll('.field-string .error-detail li'); - const errors = [].map.call(liNodes, li => li.textContent); + expect(node.querySelectorAll('button[type=submit]')).toHaveLength(1); + }); - expect(fieldNodes[1]).toHaveClass('field-error'); - expect(errors).toStrictEqual(['should NOT be shorter than 4 characters']); - }); - }); + it('should render children buttons', () => { + const props = { schema: {} }; + const comp = renderIntoDocument( + + + +
+ ); + const node = findDOMNode(comp); + expect(node.querySelectorAll('button[type=submit]')).toHaveLength(2); + }); - describe('schema dependencies', () => { - const schema = { + it("should render an UnsupportedField error if schema isn't object", () => { + const props = { + schema: { type: 'object', + title: 'object', properties: { - branch: { - type: 'number', - enum: [1, 2, 3], - default: 1, + firstName: 'some mame', + address: { + $ref: '#/definitions/address', }, }, - required: ['branch'], - dependencies: { - branch: { - oneOf: [ - { - properties: { - branch: { - enum: [1], - }, - field1: { - type: 'number', - }, - }, - required: ['field1'], - }, - { - properties: { - branch: { - enum: [2], - }, - field1: { - type: 'number', - }, - field2: { - type: 'number', - }, - }, - required: ['field1', 'field2'], - }, - ], + definitions: { + address: { + street: 'some street', }, }, - }; + }, + }; + const comp = renderIntoDocument( +
+ +
+ ); + const node = findDOMNode(comp); + expect(node.querySelector('.unsupported-field')).toHaveTextContent('Unknown field type undefined'); + }); +}); - it('should only show error for property in selected branch', () => { - const { node, onChange } = createFormComponent({ - schema, - liveValidate: true, - }); +describe('on component creation', () => { + let onChangeProp; + let formData; + let schema; + + function createComponent() { + renderIntoDocument( +
+ + +
+ ); + } + + beforeEach(() => { + onChangeProp = jest.fn(); + schema = { + type: 'object', + title: 'root object', + required: ['count'], + properties: { + count: { + type: 'number', + default: 789, + }, + }, + }; + }); - Simulate.change(node.querySelector('input[type=number]'), { - target: { value: 'not a number' }, - }); + describe('when props.formData does not equal the default values', () => { + beforeEach(() => { + formData = { + foo: 123, + }; + createComponent(); + }); - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { field1: { __errors: ['should be number'] } }, - }) - ); + it('should call props.onChange with current state', () => { + expect(onChangeProp).toHaveBeenCalledTimes(1); + expect(onChangeProp).toHaveBeenCalledWith({ + formData: { ...formData, count: 789 }, + schema, + edit: true, + uiSchema: {}, + idSchema: { $id: 'root', count: { $id: 'root_count' } }, }); + }); + }); - it('should only show errors for properties in selected branch', () => { - const { node, onChange } = createFormComponent({ - schema, - liveValidate: true, - formData: { branch: 2 }, - }); - - Simulate.change(node.querySelector('input[type=number]'), { - target: { value: 'not a number' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { - field1: { - __errors: ['should be number'], - }, - field2: { - __errors: ['is a required property'], - }, - }, - }) - ); - }); + describe('when props.formData equals the default values', () => { + beforeEach(() => { + formData = { + count: 789, + }; + createComponent(); + }); - it('should not show any errors when branch is empty', () => { - const { node, onChange } = createFormComponent({ - schema, - liveValidate: true, - formData: { branch: 3 }, - }); - - Simulate.change(node.querySelector('select'), { - target: { value: 3 }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: {}, - }) - ); - }); + it('should not call props.onChange', () => { + expect(onChangeProp).not.toHaveBeenCalled(); }); }); +}); - describe('Schema and formData updates', () => { - // https://github.com/mozilla-services/react-jsonschema-form/issues/231 +describe('Option idPrefix', function () { + it('should change the rendered ids', function () { const schema = { type: 'object', + title: 'root object', + required: ['foo'], properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, + count: { + type: 'number', + }, }, }; + const comp = renderIntoDocument(
); + const node = findDOMNode(comp); + const inputs = node.querySelectorAll('input'); + const ids = []; + for (let i = 0, len = inputs.length; i < len; i++) { + const input = inputs[i]; + ids.push(input.getAttribute('id')); + } + expect(ids).toStrictEqual(['rjsf_count']); + expect(node.querySelector('fieldset').id).toBe('rjsf'); + }); +}); - it('should replace state when props remove formData keys', () => { - const formData = { foo: 'foo', bar: 'bar' }; - const { comp, node, onChange } = createFormComponent({ - schema, - formData, - }); - - setProps(comp, { - onChange, - schema: { - type: 'object', - properties: { - bar: { type: 'string' }, - }, - }, - formData: { bar: 'bar' }, - }); - - Simulate.change(node.querySelector('#root_bar'), { - target: { value: 'baz' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { bar: 'baz' }, - }) - ); - }); - - it('should replace state when props change formData keys', () => { - const formData = { foo: 'foo', bar: 'bar' }; - const { comp, node, onChange } = createFormComponent({ - schema, - formData, - }); - - setProps(comp, { - onChange, - schema: { - type: 'object', - properties: { - foo: { type: 'string' }, - baz: { type: 'string' }, - }, +describe('Changing idPrefix', function () { + it('should work with simple example', function () { + const schema = { + type: 'object', + title: 'root object', + required: ['foo'], + properties: { + count: { + type: 'number', }, - formData: { foo: 'foo', baz: 'bar' }, - }); - - Simulate.change(node.querySelector('#root_baz'), { - target: { value: 'baz' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - formData: { foo: 'foo', baz: 'baz' }, - }) - ); - }); + }, + }; + const comp = renderIntoDocument(); + const node = findDOMNode(comp); + const inputs = node.querySelectorAll('input'); + const ids = []; + for (let i = 0, len = inputs.length; i < len; i++) { + const input = inputs[i]; + ids.push(input.getAttribute('id')); + } + expect(ids).toStrictEqual(['rjsf_count']); + expect(node.querySelector('fieldset').id).toBe('rjsf'); }); - describe('idSchema updates based on formData', () => { + it('should work with oneOf', function () { const schema = { + $schema: 'http://json-schema.org/draft-06/schema#', type: 'object', properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - }, - dependencies: { - a: { + connector: { oneOf: [ { + type: 'object', properties: { - a: { enum: ['int'] }, + connector: { + type: 'string', + enum: ['aws'], + }, + key_aws: { + type: 'string', + }, }, }, { + type: 'object', properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, + connector: { + type: 'string', + enum: ['gcp'], + }, + key_gcp: { + type: 'string', + }, }, }, ], @@ -2043,472 +216,490 @@ describe.each(formExtraPropsList)('Form with props: `%s`', formExtraProps => { }, }; - it('should not update idSchema for a falsey value', () => { - const formData = { a: 'int' }; - const { comp, node, onSubmit } = createFormComponent({ - schema, - formData, - }); + const comp = renderIntoDocument(); + const node = findDOMNode(comp); + const inputs = node.querySelectorAll('input'); + const ids = []; + for (let i = 0, len = inputs.length; i < len; i++) { + const input = inputs[i]; + ids.push(input.getAttribute('id')); + } + expect(ids).toStrictEqual(['rjsf_key_aws']); + }); +}); - setProps(comp, { - onSubmit, - schema: { +describe('Custom field template', () => { + const schema = { + type: 'object', + title: 'root object', + required: ['foo'], + properties: { + foo: { + type: 'string', + description: 'this is description', + minLength: 32, + }, + }, + }; + + const uiSchema = { + foo: { + 'ui:help': 'this is help', + }, + }; + + const formData = { foo: 'invalid' }; + + function FieldTemplate(props) { + const { id, classNames, label, help, rawHelp, required, description, rawDescription, children } = props; + return ( +
+ + {description} + {children} + {help} + {`${rawHelp} rendered from the raw format`} + {`${rawDescription} rendered from the raw format`} +
+ ); + } + + let node; + + beforeEach(() => { + node = createFormComponent({ + schema, + uiSchema, + formData, + FieldTemplate, + }).node; + }); + + it('should use the provided field template', () => { + expect(node.querySelector('.my-template')).not.toBeNull(); + }); + + it('should use the provided template for labels', () => { + expect(node.querySelector('.my-template > label')).toHaveTextContent('root object'); + expect(node.querySelector('.my-template .field-string > label')).toHaveTextContent('foo*'); + }); + + it('should pass description as the provided React element', () => { + expect(node.querySelector('#root_foo__description')).toHaveTextContent('this is description'); + }); + + it('should pass rawDescription as a string', () => { + expect(node.querySelector('.raw-description')).toHaveTextContent( + 'this is description rendered from the raw format' + ); + }); + + it('should pass help as a the provided React element', () => { + expect(node.querySelector('.help-block')).toHaveTextContent('this is help'); + }); + + it('should pass rawHelp as a string', () => { + expect(node.querySelector('.raw-help')).toHaveTextContent('this is help rendered from the raw format'); + }); +}); + +describe('Schema definitions', () => { + it('should use a single schema definition reference', () => { + const schema = { + definitions: { + testdef: { type: 'string' }, + }, + $ref: '#/definitions/testdef', + }; + + const { node } = createFormComponent({ schema }); + + expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); + }); + + it('should recursively handle refer multiple schema definition references', () => { + const schema = { + definitions: { + testdef: { type: 'string' }, + }, + type: 'object', + properties: { + foo: { $ref: '#/definitions/testdef' }, + bar: { $ref: '#/definitions/testdef' }, + }, + }; + + const { node } = createFormComponent({ schema }); + + expect(node.querySelectorAll('input[type=text]')).toHaveLength(2); + }); + + it('should handle deeply referenced schema definitions', () => { + const schema = { + definitions: { + testdef: { type: 'string' }, + }, + type: 'object', + properties: { + foo: { type: 'object', properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - }, - dependencies: { - a: { - oneOf: [ - { - properties: { - a: { enum: ['int'] }, - }, - }, - { - properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, - }, - }, - ], - }, + bar: { $ref: '#/definitions/testdef' }, }, }, - formData: { a: 'int' }, - }); + }, + }; - submitForm(node); - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - idSchema: { $id: 'root', a: { $id: 'root_a' } }, - }), - expect.anything() - ); - }); + const { node } = createFormComponent({ schema }); - it('should update idSchema based on truthy value', () => { - const formData = { - a: 'int', - }; - const { comp, node, onSubmit } = createFormComponent({ - schema, - formData, - }); - setProps(comp, { - onSubmit, - schema: { + expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); + }); + + it('should handle references to deep schema definitions', () => { + const schema = { + definitions: { + testdef: { type: 'object', properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - }, - dependencies: { - a: { - oneOf: [ - { - properties: { - a: { enum: ['int'] }, - }, - }, - { - properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, - }, - }, - ], - }, + bar: { type: 'string' }, }, }, - formData: { a: 'bool' }, - }); - submitForm(node); - expect(onSubmit).toHaveBeenLastCalledWith( - expect.objectContaining({ - idSchema: { - $id: 'root', - a: { $id: 'root_a' }, - b: { $id: 'root_b' }, - }, - }), - expect.anything() - ); + }, + type: 'object', + properties: { + foo: { $ref: '#/definitions/testdef/properties/bar' }, + }, + }; + + const { node } = createFormComponent({ schema }); + + expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); + }); + + it('should handle referenced definitions for array items', () => { + const schema = { + definitions: { + testdef: { type: 'string' }, + }, + type: 'object', + properties: { + foo: { + type: 'array', + items: { $ref: '#/definitions/testdef' }, + }, + }, + }; + + const { node } = createFormComponent({ + schema, + formData: { + foo: ['blah'], + }, }); + + expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); }); - describe('Form disable prop', () => { + it('should raise for non-existent definitions referenced', () => { const schema = { type: 'object', properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, + foo: { $ref: '#/definitions/nonexistent' }, }, }; - const formData = { foo: 'foo', bar: 'bar' }; - it('should enable all items', () => { - const { node } = createFormComponent({ schema, formData }); + expect(() => createFormComponent({ schema })).toThrow(/#\/definitions\/nonexistent/); + }); - expect(node.querySelectorAll('input:disabled')).toHaveLength(0); - }); + it('should propagate referenced definition defaults', () => { + const schema = { + definitions: { + testdef: { type: 'string', default: 'hello' }, + }, + $ref: '#/definitions/testdef', + }; - it('should disable all items', () => { - const { node } = createFormComponent({ - schema, - formData, - disabled: true, - }); + const { node } = createFormComponent({ schema }); - expect(node.querySelectorAll('input:disabled')).toHaveLength(2); - }); + expect(node.querySelector('input[type=text]').value).toBe('hello'); }); - describe('Attributes', () => { - const formProps = { - schema: {}, - id: 'test-form', - className: 'test-class other-class', - name: 'testName', - method: 'post', - target: '_blank', - action: '/users/list', - autoComplete: 'off', - enctype: 'multipart/form-data', - acceptcharset: 'ISO-8859-1', - noHtml5Validate: true, + it('should propagate nested referenced definition defaults', () => { + const schema = { + definitions: { + testdef: { type: 'string', default: 'hello' }, + }, + type: 'object', + properties: { + foo: { $ref: '#/definitions/testdef' }, + }, }; - let node; + const { node } = createFormComponent({ schema }); - beforeEach(() => { - node = createFormComponent(formProps).node; - }); + expect(node.querySelector('input[type=text]').value).toBe('hello'); + }); - it('should set attr id of form', () => { - expect(node).toHaveAttribute('id', formProps.id); - }); + it('should propagate referenced definition defaults for array items', () => { + const schema = { + definitions: { + testdef: { type: 'string', default: 'hello' }, + }, + type: 'array', + items: { + $ref: '#/definitions/testdef', + }, + }; - it('should set attr class of form', () => { - expect(node).toHaveAttribute('class', formProps.className); - }); + const { node } = createFormComponent({ schema }); - it('should set attr name of form', () => { - expect(node).toHaveAttribute('name', formProps.name); - }); + Simulate.click(node.querySelector('.array-item-add button')); + + expect(node.querySelector('input[type=text]').value).toBe('hello'); + }); + + it('should propagate referenced definition defaults in objects with additionalProperties', () => { + const schema = { + definitions: { + testdef: { type: 'string' }, + }, + type: 'object', + additionalProperties: { + $ref: '#/definitions/testdef', + }, + }; - it('should set attr method of form', () => { - expect(node).toHaveAttribute('method', formProps.method); - }); + const { node } = createFormComponent({ schema }); - it('should set attr target of form', () => { - expect(node).toHaveAttribute('target', formProps.target); - }); + Simulate.click(node.querySelector('.btn-add')); - it('should set attr action of form', () => { - expect(node).toHaveAttribute('action', formProps.action); - }); + expect(node.querySelector('input[type=text]').value).toBe('newKey'); + }); - it('should set attr autocomplete of form', () => { - expect(node).toHaveAttribute('autocomplete', formProps.autoComplete); - }); + it('should propagate referenced definition defaults in objects with additionalProperties that have a type present', () => { + // Though `additionalProperties` has a `type` present here, it also has a `$ref` so that + // referenced schema should override it. + const schema = { + definitions: { + testdef: { type: 'number' }, + }, + type: 'object', + additionalProperties: { + type: 'string', + $ref: '#/definitions/testdef', + }, + }; - it('should set attr enctype of form', () => { - expect(node).toHaveAttribute('enctype', formProps.enctype); - }); + const { node } = createFormComponent({ schema }); - it('should set attr acceptcharset of form', () => { - expect(node).toHaveAttribute('accept-charset', formProps.acceptcharset); - }); + Simulate.click(node.querySelector('.btn-add')); - it('should set attr novalidate of form', () => { - expect(node.getAttribute('novalidate')).not.toBeNull(); - }); + expect(node.querySelector('input[type=number]').value).toBe('0'); }); - describe('Deprecated autocomplete attribute', () => { - it('should set attr autocomplete of form', () => { - const formProps = { - schema: {}, - autocomplete: 'off', - }; - const node = createFormComponent(formProps).node; - expect(node).toHaveAttribute('autocomplete', formProps.autocomplete); - }); + it('should follow recursive references', () => { + const schema = { + definitions: { + bar: { $ref: '#/definitions/qux' }, + qux: { type: 'string' }, + }, + type: 'object', + required: ['foo'], + properties: { + foo: { $ref: '#/definitions/bar' }, + }, + }; + const { node } = createFormComponent({ schema }); - it('should log deprecation warning when it is used', () => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); - createFormComponent({ - schema: {}, - autocomplete: 'off', - }); - expect(console.warn).toHaveBeenCalledWith( - expect.stringMatching(/Using autocomplete property of Form is deprecated/) - ); - }); + expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); + }); - it('should use autoComplete value if both autocomplete and autoComplete are used', () => { - const formProps = { - schema: {}, - autocomplete: 'off', - autoComplete: 'on', - }; - const node = createFormComponent(formProps).node; - expect(node).toHaveAttribute('autocomplete', formProps.autoComplete); - }); + it('should follow multiple recursive references', () => { + const schema = { + definitions: { + bar: { $ref: '#/definitions/bar2' }, + bar2: { $ref: '#/definitions/qux' }, + qux: { type: 'string' }, + }, + type: 'object', + required: ['foo'], + properties: { + foo: { $ref: '#/definitions/bar' }, + }, + }; + const { node } = createFormComponent({ schema }); + + expect(node.querySelectorAll('input[type=text]')).toHaveLength(1); }); - describe('Custom format updates', () => { - it('should update custom formats when customFormats is changed', () => { - const formProps = { - liveValidate: true, - formData: { - areaCode: '123455', + it('should priorize definition over schema type property', () => { + // Refs bug #140 + const schema = { + type: 'object', + properties: { + name: { type: 'string' }, + childObj: { + type: 'object', + $ref: '#/definitions/childObj', }, - schema: { + }, + definitions: { + childObj: { type: 'object', properties: { - areaCode: { - type: 'string', - format: 'area-code', - }, - }, - }, - uiSchema: { - areaCode: { - 'ui:widget': 'area-code', + otherName: { type: 'string' }, }, }, - widgets: { - 'area-code': () =>
, - }, - }; - - const { comp, node, onError } = createFormComponent(formProps); - - submitForm(node); - expect(onError).not.toHaveBeenCalled(); + }, + }; - setProps(comp, { - ...formProps, - onError, - customFormats: { - 'area-code': /^\d{3}$/, - }, - }); + const { node } = createFormComponent({ schema }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should match format "area-code"', - name: 'format', - params: { format: 'area-code' }, - property: '.areaCode', - schemaPath: '#/properties/areaCode/format', - stack: '.areaCode should match format "area-code"', - }, - ]) - ); - }); + expect(node.querySelectorAll('input[type=text]')).toHaveLength(2); }); - describe('Meta schema updates', () => { - it('Should update allowed meta schemas when additionalMetaSchemas is changed', () => { - const formProps = { - liveValidate: true, - schema: { - $schema: 'http://json-schema.org/draft-04/schema#', - type: 'string', - minLength: 8, - pattern: 'd+', + it('should priorize local properties over definition ones', () => { + // Refs bug #140 + const schema = { + type: 'object', + properties: { + foo: { + title: 'custom title', + $ref: '#/definitions/objectDef', }, - formData: 'short', - additionalMetaSchemas: [], - }; - - const { comp, node, onError } = createFormComponent(formProps); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - stack: 'no schema with key or ref "http://json-schema.org/draft-04/schema#"', - }, - ]) - ); - - setProps(comp, { - ...formProps, - onError, - additionalMetaSchemas: [require('ajv/lib/refs/json-schema-draft-04.json')], - }); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 8 characters', - name: 'minLength', - params: { limit: 8 }, - property: '', - schemaPath: '#/minLength', - stack: 'should NOT be shorter than 8 characters', - }, - { - message: 'should match pattern "d+"', - name: 'pattern', - params: { pattern: 'd+' }, - property: '', - schemaPath: '#/pattern', - stack: 'should match pattern "d+"', + }, + definitions: { + objectDef: { + type: 'object', + title: 'definition title', + properties: { + field: { type: 'string' }, }, - ]) - ); + }, + }, + }; - setProps(comp, { ...formProps, onError }); + const { node } = createFormComponent({ schema }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - stack: 'no schema with key or ref "http://json-schema.org/draft-04/schema#"', - }, - ]) - ); - }); + expect(node.querySelector('legend')).toHaveTextContent('custom title'); }); - describe('Changing the tagName', () => { - it('should render the component using the custom tag name', () => { - const tagName = 'span'; - const { node } = createFormComponent({ schema: {}, tagName }); - expect(node.tagName).toBe(tagName.toUpperCase()); - }); + it('should propagate and handle a resolved schema definition', () => { + const schema = { + definitions: { + enumDef: { type: 'string', enum: ['a', 'b'] }, + }, + type: 'object', + properties: { + name: { $ref: '#/definitions/enumDef' }, + }, + }; - it('should render the component using a ComponentType', () => { - const Component = props =>
; - const { node } = createFormComponent({ schema: {}, tagName: Component }); - expect(node.id).toBe('test'); - }); + const { node } = createFormComponent({ schema }); + + expect(node.querySelectorAll('option')).toHaveLength(3); }); +}); - describe('Nested forms', () => { - it('should call provided submit handler with form state', () => { - const innerOnSubmit = jest.fn(); - const outerOnSubmit = jest.fn(); - let innerRef; - - class ArrayTemplateWithForm extends React.Component { - constructor(props) { - super(props); - innerRef = createRef(); - } - - render() { - const innerFormProps = { - schema: {}, - onSubmit: innerOnSubmit, - }; - - return ( - -
- - - -
-
- ); - } - } +describe('Default value handling on clear', () => { + const schema = { + type: 'string', + default: 'foo', + }; - const outerFormProps = { - schema: { - type: 'array', - title: 'my list', - description: 'my description', - items: { type: 'string' }, - }, - formData: ['foo', 'bar'], - ArrayFieldTemplate: ArrayTemplateWithForm, - onSubmit: outerOnSubmit, - }; - createFormComponent(outerFormProps); - const arrayForm = innerRef.current.querySelector('form'); - const arraySubmit = arrayForm.querySelector('.array-form-submit'); + it('should not set default when a text field is cleared', () => { + const { node } = createFormComponent({ schema, formData: 'bar' }); - arraySubmit.click(); - expect(innerOnSubmit).toHaveBeenCalledTimes(1); - expect(outerOnSubmit).not.toHaveBeenCalled(); + Simulate.change(node.querySelector('input'), { + target: { value: '' }, }); + + expect(node.querySelector('input').value).toBe(''); }); +}); - describe('Dependencies', () => { - it('should not give a validation error by duplicating enum values in dependencies', () => { - const schema = { - title: 'A registration form', - description: 'A simple form example.', +describe('Defaults array items default propagation', () => { + const schema = { + type: 'object', + title: 'lvl 1 obj', + properties: { + object: { type: 'object', + title: 'lvl 2 obj', properties: { - type1: { - type: 'string', - title: 'Type 1', - enum: ['FOO', 'BAR', 'BAZ'], - }, - type2: { - type: 'string', - title: 'Type 2', - enum: ['GREEN', 'BLUE', 'RED'], - }, - }, - dependencies: { - type1: { - properties: { - type1: { - enum: ['FOO'], - }, - type2: { - enum: ['GREEN'], + array: { + type: 'array', + items: { + type: 'object', + title: 'lvl 3 obj', + properties: { + bool: { + type: 'boolean', + default: true, + }, }, }, }, }, - }; - const formData = { - type1: 'FOO', - }; - const { node, onError } = createFormComponent({ schema, formData }); - Simulate.submit(node); - expect(onError).not.toHaveBeenCalled(); - }); + }, + }, + }; - it('should show dependency defaults for uncontrolled components', () => { - const schema = { - type: 'object', - properties: { - firstName: { type: 'string' }, - }, - dependencies: { - firstName: { - properties: { - lastName: { type: 'string', default: 'Norris' }, - }, + it('should propagate deeply nested defaults to submit handler', () => { + const { node, onSubmit } = createFormComponent({ schema }); + + Simulate.click(node.querySelector('.array-item-add button')); + Simulate.submit(node); + + expect(onSubmit).toHaveBeenLastCalledWith( + expect.objectContaining({ + formData: { + object: { + array: [ + { + bool: true, + }, + ], }, }, - }; - const { node } = createFormComponent({ schema }); + }), + expect.anything() + ); + }); +}); - Simulate.change(node.querySelector('#root_firstName'), { - target: { value: 'Chuck' }, - }); - expect(node.querySelector('#root_lastName').value).toBe('Norris'); +describe('Submit handler', () => { + it('should call provided submit handler with form state', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + }, + }; + const formData = { + foo: 'bar', + }; + const onSubmit = jest.fn(); + const event = { type: 'submit' }; + const { node } = createFormComponent({ + schema, + formData, + onSubmit, }); + + Simulate.submit(node, event); + expect(onSubmit).toHaveBeenLastCalledWith( + expect.objectContaining({ formData, schema }), + expect.objectContaining(event) + ); }); }); -describe('Form omitExtraData and liveOmit', () => { - it('should call getUsedFormData when the omitExtraData prop is true and liveOmit is true', () => { +describe('Change handler', () => { + it('should call provided change handler on form state change', () => { const schema = { type: 'object', properties: { @@ -2518,31 +709,31 @@ describe('Form omitExtraData and liveOmit', () => { }, }; const formData = { - foo: 'bar', + foo: '', }; const onChange = jest.fn(); - const omitExtraData = true; - const liveOmit = true; - const { node, comp } = createFormComponent({ + const { node } = createFormComponent({ schema, formData, onChange, - omitExtraData, - liveOmit, }); - jest.spyOn(comp, 'getUsedFormData').mockImplementation(() => ({ - foo: '', - })); - Simulate.change(node.querySelector('[type=text]'), { target: { value: 'new' }, }); - expect(comp.getUsedFormData).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + formData: { + foo: 'new', + }, + }) + ); }); +}); - it('should not call getUsedFormData when the omitExtraData prop is true and liveOmit is unspecified', () => { +describe('Blur handler', () => { + it('should call provided blur handler on form input blur event', () => { const schema = { type: 'object', properties: { @@ -2552,473 +743,561 @@ describe('Form omitExtraData and liveOmit', () => { }, }; const formData = { - foo: 'bar', - }; - const onChange = jest.fn(); - const omitExtraData = true; - const { node, comp } = createFormComponent({ - schema, - formData, - onChange, - omitExtraData, - }); - - jest.spyOn(comp, 'getUsedFormData').mockImplementation(() => ({ foo: '', - })); + }; + const onBlur = jest.fn(); + const { node } = createFormComponent({ schema, formData, onBlur }); - Simulate.change(node.querySelector('[type=text]'), { + const input = node.querySelector('[type=text]'); + Simulate.blur(input, { target: { value: 'new' }, }); - expect(comp.getUsedFormData).not.toHaveBeenCalled(); + expect(onBlur).toHaveBeenCalledWith(input.id, 'new'); }); +}); - describe('getUsedFormData', () => { - it('should call getUsedFormData when the omitExtraData prop is true', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - }, +describe('Focus handler', () => { + it('should call provided focus handler on form input focus event', () => { + const schema = { + type: 'object', + properties: { + foo: { + type: 'string', }, - }; - const formData = { - foo: '', - }; - const onSubmit = jest.fn(); - const onError = jest.fn(); - const omitExtraData = true; - const { comp, node } = createFormComponent({ - schema, - formData, - onSubmit, - onError, - omitExtraData, - }); - - jest.spyOn(comp, 'getUsedFormData').mockImplementation(() => ({ - foo: '', - })); - - Simulate.submit(node); + }, + }; + const formData = { + foo: '', + }; + const onFocus = jest.fn(); + const { node } = createFormComponent({ schema, formData, onFocus }); - expect(comp.getUsedFormData).toHaveBeenCalledTimes(1); + const input = node.querySelector('[type=text]'); + Simulate.focus(input, { + target: { value: 'new' }, }); - it('should just return the single input form value', () => { - const schema = { - title: 'A single-field form', - type: 'string', - }; - const formData = 'foo'; - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); + expect(onFocus).toHaveBeenCalledWith(input.id, 'new'); + }); +}); - const result = comp.getUsedFormData(formData, []); - expect(result).toBe('foo'); - }); +describe('Schema and external formData updates', () => { + let comp; + let onChangeProp; + let formProps; - it('should return the root level array', () => { - const schema = { - type: 'array', - items: { - type: 'string', - }, - }; - const formData = []; - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); + beforeEach(() => { + onChangeProp = jest.fn(); + formProps = { + schema: { + type: 'string', + default: 'foobar', + }, + formData: 'some value', + onChange: onChangeProp, + }; + comp = createFormComponent(formProps).comp; + }); - const result = comp.getUsedFormData(formData, []); - expect(result).toStrictEqual([]); - }); + describe('when the form data is set to null', () => { + beforeEach(() => + setProps(comp, { + ...formProps, + formData: null, + }) + ); - it('should call getUsedFormData with data from fields in event', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }; - const formData = { - foo: 'bar', - }; - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, + it('should call onChange', () => { + expect(onChangeProp).toHaveBeenCalledTimes(1); + expect(onChangeProp).toHaveBeenCalledWith({ + edit: true, + formData: 'foobar', + idSchema: { $id: 'root' }, + schema: formProps.schema, + uiSchema: {}, }); - - const result = comp.getUsedFormData(formData, ['foo']); - expect(result).toStrictEqual({ foo: 'bar' }); }); + }); - it('unused form values should be omitted', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - baz: { type: 'string' }, - list: { - type: 'array', - items: { - type: 'object', - properties: { - title: { type: 'string' }, - details: { type: 'string' }, - }, - }, - }, - }, - }; + describe('when the schema default is changed but formData is not changed', () => { + const newSchema = { + type: 'string', + default: 'the new default', + }; - const formData = { - foo: 'bar', - baz: 'buzz', - list: [ - { title: 'title0', details: 'details0' }, - { title: 'title1', details: 'details1' }, - ], - }; - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); + beforeEach(() => + setProps(comp, { + ...formProps, + schema: newSchema, + formData: 'some value', + }) + ); - const result = comp.getUsedFormData(formData, ['foo', 'list.0.title', 'list.1.details']); - expect(result).toStrictEqual({ - foo: 'bar', - list: [{ title: 'title0' }, { details: 'details1' }], - }); + it('should not call onChange', () => { + expect(onChangeProp).not.toHaveBeenCalled(); }); }); - describe('getFieldNames()', () => { - it('should return an empty array for a single input form', () => { - const schema = { - type: 'string', - }; + describe('when the schema default is changed and formData is changed', () => { + const newSchema = { + type: 'string', + default: 'the new default', + }; + + beforeEach(() => + setProps(comp, { + ...formProps, + schema: newSchema, + formData: 'something else', + }) + ); - const formData = 'foo'; + it('should not call onChange', () => { + expect(onChangeProp).not.toHaveBeenCalled(); + }); + }); - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); + describe('when the schema default is changed and formData is nulled', () => { + const newSchema = { + type: 'string', + default: 'the new default', + }; - const pathSchema = { - $name: '', - }; + beforeEach(() => + setProps(comp, { + ...formProps, + schema: newSchema, + formData: null, + }) + ); - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames).toStrictEqual([]); + it('should call onChange', () => { + expect(onChangeProp).toHaveBeenCalledTimes(1); + expect(onChangeProp).toHaveBeenCalledWith( + expect.objectContaining({ + schema: newSchema, + formData: 'the new default', + }) + ); }); + }); - it('should get field names from pathSchema', () => { - const schema = {}; + describe('when the onChange prop sets formData to a falsey value', () => { + class TestForm extends React.Component { + constructor() { + super(); - const formData = { - extra: { - foo: 'bar', - }, - level1: { - level2: 'test', - anotherThing: { - anotherThingNested: 'abc', - extra: 'asdf', - anotherThingNested2: 0, - }, - }, - level1a: 1.23, - }; + this.state = { + formData: {}, + }; + } - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); + onChange = () => { + this.setState({ formData: this.props.falseyValue }); + }; - const pathSchema = { - $name: '', - level1: { - $name: 'level1', - level2: { $name: 'level1.level2' }, - anotherThing: { - $name: 'level1.anotherThing', - anotherThingNested: { - $name: 'level1.anotherThing.anotherThingNested', - }, - anotherThingNested2: { - $name: 'level1.anotherThing.anotherThingNested2', + render() { + const schema = { + type: 'object', + properties: { + value: { + type: 'string', }, }, - }, - level1a: { - $name: 'level1a', - }, - }; + }; + return
; + } + } + + it.each([[0], [false], [null], [undefined], [NaN]])( + 'should not crash due to "Maximum call stack size exceeded..." with `%s`"', + falsyValue => { + // It is expected that this will throw an error due to non-matching propTypes, + // so the error message needs to be inspected + expect(() => { + createComponent(TestForm, { falsyValue }); + }).not.toThrow('Maximum call stack size exceeded'); + } + ); + }); +}); + +describe('External formData updates', () => { + describe('root level', () => { + const formProps = { + schema: { type: 'string' }, + }; - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames.sort()).toStrictEqual( - [ - 'level1a', - 'level1.level2', - 'level1.anotherThing.anotherThingNested', - 'level1.anotherThing.anotherThingNested2', - ].sort() + it('should call submit handler with new formData prop value', () => { + const { comp, node, onSubmit } = createFormComponent(formProps); + + setProps(comp, { + ...formProps, + onSubmit, + formData: 'yo', + }); + submitForm(node); + expect(onSubmit).toHaveBeenLastCalledWith( + expect.objectContaining({ + formData: 'yo', + }), + expect.anything() ); }); + }); - it('should get field names from pathSchema with array', () => { - const schema = {}; - - const formData = { - address_list: [ - { - street_address: '21, Jump Street', - city: 'Babel', - state: 'Neverland', - }, - { - street_address: '1234 Schema Rd.', - city: 'New York', - state: 'Arizona', - }, - ], + describe('object level', () => { + it('should call submit handler with new formData prop value', () => { + const formProps = { + schema: { type: 'object', properties: { foo: { type: 'string' } } }, }; + const { comp, onSubmit, node } = createFormComponent(formProps); - const onSubmit = jest.fn(); - const { comp } = createFormComponent({ - schema, - formData, + setProps(comp, { + ...formProps, onSubmit, + formData: { foo: 'yo' }, }); - const pathSchema = { - $name: '', - address_list: { - 0: { - $name: 'address_list.0', - city: { - $name: 'address_list.0.city', - }, - state: { - $name: 'address_list.0.state', - }, - street_address: { - $name: 'address_list.0.street_address', - }, - }, - 1: { - $name: 'address_list.1', - city: { - $name: 'address_list.1.city', - }, - state: { - $name: 'address_list.1.state', - }, - street_address: { - $name: 'address_list.1.street_address', - }, - }, + submitForm(node); + expect(onSubmit).toHaveBeenLastCalledWith( + expect.objectContaining({ + formData: { foo: 'yo' }, + }), + expect.anything() + ); + }); + }); + + describe('array level', () => { + it('should call submit handler with new formData prop value', () => { + const schema = { + type: 'array', + items: { + type: 'string', }, }; + const { comp, node, onSubmit } = createFormComponent({ schema }); + + setProps(comp, { schema, onSubmit, formData: ['yo'] }); - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames.sort()).toStrictEqual( - [ - 'address_list.0.city', - 'address_list.0.state', - 'address_list.0.street_address', - 'address_list.1.city', - 'address_list.1.state', - 'address_list.1.street_address', - ].sort() + submitForm(node); + expect(onSubmit).toHaveBeenLastCalledWith( + expect.objectContaining({ + formData: ['yo'], + }), + expect.anything() ); }); }); +}); - it('should not omit data on change with omitExtraData=false and liveOmit=false', () => { - const omitExtraData = false; - const liveOmit = false; - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, +describe('Internal formData updates', () => { + it('root', () => { + const formProps = { + schema: { type: 'string' }, }; - const formData = { foo: 'foo', baz: 'baz' }; - const { node, onChange } = createFormComponent({ - schema, - formData, - omitExtraData, - liveOmit, - }); + const { node, onChange } = createFormComponent(formProps); - Simulate.change(node.querySelector('#root_foo'), { - target: { value: 'foobar' }, + Simulate.change(node.querySelector('input[type=text]'), { + target: { value: 'yo' }, }); expect(onChange).toHaveBeenLastCalledWith( expect.objectContaining({ - formData: { foo: 'foobar', baz: 'baz' }, + formData: 'yo', }) ); }); - it('should not omit data on change with omitExtraData=true and liveOmit=false', () => { - const omitExtraData = true; - const liveOmit = false; - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }; - const formData = { foo: 'foo', baz: 'baz' }; + it('object', () => { const { node, onChange } = createFormComponent({ - schema, - formData, - omitExtraData, - liveOmit, + schema: { + type: 'object', + properties: { + foo: { + type: 'string', + }, + }, + }, }); - Simulate.change(node.querySelector('#root_foo'), { - target: { value: 'foobar' }, + Simulate.change(node.querySelector('input[type=text]'), { + target: { value: 'yo' }, }); expect(onChange).toHaveBeenLastCalledWith( expect.objectContaining({ - formData: { foo: 'foobar', baz: 'baz' }, + formData: { foo: 'yo' }, }) ); }); - it('should not omit data on change with omitExtraData=false and liveOmit=true', () => { - const omitExtraData = false; - const liveOmit = true; + it('array of strings', () => { const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, + type: 'array', + items: { + type: 'string', }, }; - const formData = { foo: 'foo', baz: 'baz' }; - const { node, onChange } = createFormComponent({ - schema, - formData, - omitExtraData, - liveOmit, - }); + const { node, onChange } = createFormComponent({ schema }); - Simulate.change(node.querySelector('#root_foo'), { - target: { value: 'foobar' }, - }); + Simulate.click(node.querySelector('.array-item-add button')); + Simulate.change(node.querySelector('input[type=text]'), { + target: { value: 'yo' }, + }); expect(onChange).toHaveBeenLastCalledWith( expect.objectContaining({ - formData: { foo: 'foobar', baz: 'baz' }, + formData: ['yo'], }) ); }); - it('should omit data on change with omitExtraData=true and liveOmit=true', () => { - const omitExtraData = true; - const liveOmit = true; + it('array of objects', () => { const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + }, }, }; - const formData = { foo: 'foo', baz: 'baz' }; - const { node, onChange } = createFormComponent({ - schema, - formData, - omitExtraData, - liveOmit, - }); + const { node, onChange } = createFormComponent({ schema }); + + Simulate.click(node.querySelector('.array-item-add button')); - Simulate.change(node.querySelector('#root_foo'), { - target: { value: 'foobar' }, + Simulate.change(node.querySelector('input[type=text]'), { + target: { value: 'yo' }, }); expect(onChange).toHaveBeenLastCalledWith( expect.objectContaining({ - formData: { foo: 'foobar' }, + formData: [{ name: 'yo' }], }) ); }); +}); - describe('Async errors', () => { - it('should render the async errors', () => { - const schema = { +describe('Schema and formData updates', () => { + // https://github.com/mozilla-services/react-jsonschema-form/issues/231 + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { type: 'string' }, + }, + }; + + it('should replace state when props remove formData keys', () => { + const formData = { foo: 'foo', bar: 'bar' }; + const { comp, node, onChange } = createFormComponent({ + schema, + formData, + }); + + setProps(comp, { + onChange, + schema: { type: 'object', properties: { - foo: { type: 'string' }, - candy: { - type: 'object', - properties: { - bar: { type: 'string' }, - }, - }, + bar: { type: 'string' }, }, - }; + }, + formData: { bar: 'bar' }, + }); - const extraErrors = { - foo: { - __errors: ['some error that got added as a prop'], - }, - candy: { - bar: { - __errors: ['some other error that got added as a prop'], - }, - }, - }; + Simulate.change(node.querySelector('#root_bar'), { + target: { value: 'baz' }, + }); - const { node } = createFormComponent({ schema, extraErrors }); + expect(onChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + formData: { bar: 'baz' }, + }) + ); + }); - expect(node.querySelectorAll('.error-detail li')).toHaveLength(2); + it('should replace state when props change formData keys', () => { + const formData = { foo: 'foo', bar: 'bar' }; + const { comp, node, onChange } = createFormComponent({ + schema, + formData, }); - it('should not block form submission', () => { - const onSubmit = jest.fn(); - const schema = { + setProps(comp, { + onChange, + schema: { type: 'object', properties: { foo: { type: 'string' }, + baz: { type: 'string' }, }, - }; + }, + formData: { foo: 'foo', baz: 'bar' }, + }); - const extraErrors = { - foo: { - __errors: ['some error that got added as a prop'], - }, - }; + Simulate.change(node.querySelector('#root_baz'), { + target: { value: 'baz' }, + }); + + expect(onChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + formData: { foo: 'foo', baz: 'baz' }, + }) + ); + }); +}); + +describe('Form disable prop', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { type: 'string' }, + }, + }; + const formData = { foo: 'foo', bar: 'bar' }; - const { node } = createFormComponent({ schema, extraErrors, onSubmit }); - Simulate.submit(node); - expect(onSubmit).toHaveBeenCalledTimes(1); + it('should enable all items', () => { + const { node } = createFormComponent({ schema, formData }); + + expect(node.querySelectorAll('input:disabled')).toHaveLength(0); + }); + + it('should disable all items', () => { + const { node } = createFormComponent({ + schema, + formData, + disabled: true, }); + + expect(node.querySelectorAll('input:disabled')).toHaveLength(2); + }); +}); + +describe('Attributes', () => { + const formProps = { + schema: {}, + id: 'test-form', + className: 'test-class other-class', + name: 'testName', + method: 'post', + target: '_blank', + action: '/users/list', + autoComplete: 'off', + enctype: 'multipart/form-data', + acceptcharset: 'ISO-8859-1', + }; + + let node; + + beforeEach(() => { + node = createFormComponent(formProps).node; + }); + + it('should set attr id of form', () => { + expect(node).toHaveAttribute('id', formProps.id); + }); + + it('should set attr class of form', () => { + expect(node).toHaveAttribute('class', formProps.className); + }); + + it('should set attr name of form', () => { + expect(node).toHaveAttribute('name', formProps.name); + }); + + it('should set attr method of form', () => { + expect(node).toHaveAttribute('method', formProps.method); + }); + + it('should set attr target of form', () => { + expect(node).toHaveAttribute('target', formProps.target); + }); + + it('should set attr action of form', () => { + expect(node).toHaveAttribute('action', formProps.action); + }); + + it('should set attr autocomplete of form', () => { + expect(node).toHaveAttribute('autocomplete', formProps.autoComplete); + }); + + it('should set attr enctype of form', () => { + expect(node).toHaveAttribute('enctype', formProps.enctype); + }); + + it('should set attr acceptcharset of form', () => { + expect(node).toHaveAttribute('accept-charset', formProps.acceptcharset); + }); +}); + +describe('Changing the tagName', () => { + it('should render the component using the custom tag name', () => { + const tagName = 'span'; + const { node } = createFormComponent({ schema: {}, tagName }); + expect(node.tagName).toBe(tagName.toUpperCase()); + }); + + it('should render the component using a ComponentType', () => { + const Component = props =>
; + const { node } = createFormComponent({ schema: {}, tagName: Component }); + expect(node.id).toBe('test'); + }); +}); + +describe('Nested forms', () => { + it('should call provided submit handler with form state', () => { + const innerOnSubmit = jest.fn(); + const outerOnSubmit = jest.fn(); + let innerRef; + + class ArrayTemplateWithForm extends React.Component { + constructor(props) { + super(props); + innerRef = createRef(); + } + + render() { + const innerFormProps = { + schema: {}, + onSubmit: innerOnSubmit, + }; + + return ( + +
+ + + +
+
+ ); + } + } + + const outerFormProps = { + schema: { + type: 'array', + title: 'my list', + description: 'my description', + items: { type: 'string' }, + }, + formData: ['foo', 'bar'], + ArrayFieldTemplate: ArrayTemplateWithForm, + onSubmit: outerOnSubmit, + }; + createFormComponent(outerFormProps); + const arrayForm = innerRef.current.querySelector('form'); + const arraySubmit = arrayForm.querySelector('.array-form-submit'); + + arraySubmit.click(); + expect(innerOnSubmit).toHaveBeenCalledTimes(1); + expect(outerOnSubmit).not.toHaveBeenCalled(); }); }); diff --git a/packages/oas-form/__tests__/NullField_test.js b/packages/oas-form/__tests__/NullField_test.js index d37c5c8e6..b3372824c 100644 --- a/packages/oas-form/__tests__/NullField_test.js +++ b/packages/oas-form/__tests__/NullField_test.js @@ -40,7 +40,6 @@ describe('NullField', () => { type: 'null', }, formData: 3, - noValidate: true, }); submitForm(node); diff --git a/packages/oas-form/__tests__/NumberField_test.js b/packages/oas-form/__tests__/NumberField_test.js index eb32ba1b3..38a7271d5 100644 --- a/packages/oas-form/__tests__/NumberField_test.js +++ b/packages/oas-form/__tests__/NumberField_test.js @@ -69,7 +69,6 @@ describe('NumberField', () => { const { node, onSubmit } = createFormComponent({ schema: { type: 'number' }, uiSchema, - noValidate: true, }); submitForm(node); @@ -346,7 +345,6 @@ describe('NumberField', () => { enum: [1, 2], default: 1, }, - noValidate: true, }); expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ formData: 1 })); diff --git a/packages/oas-form/__tests__/ObjectField_test.js b/packages/oas-form/__tests__/ObjectField_test.js index 1ab907594..3c175cfc1 100644 --- a/packages/oas-form/__tests__/ObjectField_test.js +++ b/packages/oas-form/__tests__/ObjectField_test.js @@ -1,7 +1,7 @@ import React from 'react'; import { Simulate } from 'react-dom/test-utils'; -import { createFormComponent, submitForm } from './test_utils'; +import { createFormComponent } from './test_utils'; describe('ObjectField', () => { describe('schema', () => { @@ -164,150 +164,6 @@ describe('ObjectField', () => { }); }); - describe('fields ordering', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - baz: { type: 'string' }, - qux: { type: 'string' }, - }, - }; - - it('should use provided order', () => { - const { node } = createFormComponent({ - schema, - uiSchema: { - 'ui:order': ['baz', 'qux', 'bar', 'foo'], - }, - }); - const labels = [].map.call(node.querySelectorAll('.field > label'), l => l.textContent); - - expect(labels).toStrictEqual(['baz', 'qux', 'bar', 'foo']); - }); - - it('should insert unordered properties at wildcard position', () => { - const { node } = createFormComponent({ - schema, - uiSchema: { - 'ui:order': ['baz', '*', 'foo'], - }, - }); - const labels = [].map.call(node.querySelectorAll('.field > label'), l => l.textContent); - - expect(labels).toStrictEqual(['baz', 'bar', 'qux', 'foo']); - }); - - it('should use provided order also if order list contains extraneous properties', () => { - const { node } = createFormComponent({ - schema, - uiSchema: { - 'ui:order': ['baz', 'qux', 'bar', 'wut?', 'foo', 'huh?'], - }, - }); - - const labels = [].map.call(node.querySelectorAll('.field > label'), l => l.textContent); - - expect(labels).toStrictEqual(['baz', 'qux', 'bar', 'foo']); - }); - - it('should throw when order list misses an existing property', () => { - const { node } = createFormComponent({ - schema, - uiSchema: { - 'ui:order': ['baz', 'bar'], - }, - }); - - expect(node.querySelector('.config-error')).toHaveTextContent(/does not contain properties 'foo', 'qux'/); - }); - - it('should throw when more than one wildcard is present', () => { - const { node } = createFormComponent({ - schema, - uiSchema: { - 'ui:order': ['baz', '*', 'bar', '*'], - }, - }); - - expect(node.querySelector('.config-error')).toHaveTextContent(/contains more than one wildcard/); - }); - - it('should order referenced schema definitions', () => { - const refSchema = { - definitions: { - testdef: { type: 'string' }, - }, - type: 'object', - properties: { - foo: { $ref: '#/definitions/testdef' }, - bar: { $ref: '#/definitions/testdef' }, - }, - }; - - const { node } = createFormComponent({ - schema: refSchema, - uiSchema: { - 'ui:order': ['bar', 'foo'], - }, - }); - const labels = [].map.call(node.querySelectorAll('.field > label'), l => l.textContent); - - expect(labels).toStrictEqual(['bar', 'foo']); - }); - - it('should order referenced object schema definition properties', () => { - const refSchema = { - definitions: { - testdef: { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }, - }, - type: 'object', - properties: { - root: { $ref: '#/definitions/testdef' }, - }, - }; - - const { node } = createFormComponent({ - schema: refSchema, - uiSchema: { - root: { - 'ui:order': ['bar', 'foo'], - }, - }, - }); - const labels = [].map.call(node.querySelectorAll('.field > label'), l => l.textContent); - - expect(labels).toStrictEqual(['bar', 'foo']); - }); - - it('should render the widget with the expected id', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }; - - const { node } = createFormComponent({ - schema, - uiSchema: { - 'ui:order': ['bar', 'foo'], - }, - }); - - const ids = [].map.call(node.querySelectorAll('input[type=text]'), node => node.id); - expect(ids).toStrictEqual(['root_bar', 'root_foo']); - }); - }); - describe('Title', () => { const TitleField = props =>
; @@ -384,53 +240,6 @@ describe('ObjectField', () => { expect(labels[1]).toHaveTextContent('CustomName'); }); - it('should not throw validation errors if additionalProperties is undefined', () => { - const undefinedAPSchema = { - ...schema, - properties: { second: { type: 'string' } }, - }; - delete undefinedAPSchema.additionalProperties; - const { node, onSubmit, onError } = createFormComponent({ - schema: undefinedAPSchema, - formData: { nonschema: 1 }, - }); - - submitForm(node); - expect(onSubmit).toHaveBeenCalledWith( - expect.objectContaining({ - formData: { nonschema: 1 }, - }), - expect.anything() - ); - - expect(onError).not.toHaveBeenCalled(); - }); - - it('should throw a validation error if additionalProperties is false', () => { - const { node, onSubmit, onError } = createFormComponent({ - schema: { - ...schema, - additionalProperties: false, - properties: { second: { type: 'string' } }, - }, - formData: { nonschema: 1 }, - }); - submitForm(node); - expect(onSubmit).not.toHaveBeenCalled(); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'is an invalid additional property', - name: 'additionalProperties', - params: { additionalProperty: 'nonschema' }, - property: "['nonschema']", - schemaPath: '#/additionalProperties', - stack: "['nonschema'] is an invalid additional property", - }, - ]) - ); - }); - it('should still obey properties if additionalProperties is defined', () => { const { node } = createFormComponent({ schema: { diff --git a/packages/oas-form/__tests__/SchemaField_test.js b/packages/oas-form/__tests__/SchemaField_test.js index 4d4413c7f..6b1d3d633 100644 --- a/packages/oas-form/__tests__/SchemaField_test.js +++ b/packages/oas-form/__tests__/SchemaField_test.js @@ -1,5 +1,4 @@ import React from 'react'; -import { Simulate } from 'react-dom/test-utils'; import SchemaField from '../src/components/fields/SchemaField'; import TitleField from '../src/components/fields/TitleField'; @@ -314,72 +313,4 @@ describe('SchemaField', () => { expect(node.querySelector('#custom')).toHaveTextContent('A Foo field'); }); }); - - describe('errors', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }; - - const uiSchema = { - 'ui:field': props => { - const { uiSchema, ...fieldProps } = props; - return ; - }, - }; - - function validate(formData, errors) { - errors.addError('container'); - errors.foo.addError('test'); - return errors; - } - - it('should render its own errors', () => { - const { node } = createFormComponent({ - schema, - uiSchema, - validate, - }); - Simulate.submit(node); - - const matches = node.querySelectorAll('form > .form-group > div > .error-detail .text-danger'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('container'); - }); - - it('should pass errors to child component', () => { - const { node } = createFormComponent({ - schema, - uiSchema, - validate, - }); - Simulate.submit(node); - - const matches = node.querySelectorAll('form .form-group .form-group .text-danger'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('test'); - }); - - describe('Custom error rendering', () => { - const customStringWidget = props => { - return
{props.rawErrors}
; - }; - - it('should pass rawErrors down to custom widgets', () => { - const { node } = createFormComponent({ - schema, - uiSchema, - validate, - widgets: { BaseInput: customStringWidget }, - }); - Simulate.submit(node); - - const matches = node.querySelectorAll('.custom-text-widget'); - expect(matches).toHaveLength(1); - expect(matches[0]).toHaveTextContent('test'); - }); - }); - }); }); diff --git a/packages/oas-form/__tests__/StringField_test.js b/packages/oas-form/__tests__/StringField_test.js index 2d32b95c2..2873d2a51 100644 --- a/packages/oas-form/__tests__/StringField_test.js +++ b/packages/oas-form/__tests__/StringField_test.js @@ -98,7 +98,6 @@ describe('StringField', () => { it('should default submit value to undefined', () => { const { node, onSubmit } = createFormComponent({ schema: { type: 'string' }, - noValidate: true, }); submitForm(node); @@ -671,36 +670,6 @@ describe('StringField', () => { expect(node.querySelector('[type=datetime-local]').id).toBe('root'); }); - it('should reject an invalid entered datetime', () => { - const { node, onChange } = createFormComponent({ - schema: { - type: 'string', - format: 'date-time', - }, - liveValidate: true, - }); - - Simulate.change(node.querySelector('[type=datetime-local]'), { - target: { value: 'invalid' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { __errors: ['should be string'] }, - errors: [ - { - message: 'should be string', - name: 'type', - params: { type: 'string' }, - property: '', - schemaPath: '#/type', - stack: 'should be string', - }, - ], - }) - ); - }); - it('should render customized DateTimeWidget', () => { const { node } = createFormComponent({ schema: { @@ -754,7 +723,6 @@ describe('StringField', () => { default: datetime, }, uiSchema, - noValidate: true, }); submitForm(node); expect(onSubmit).toHaveBeenLastCalledWith( @@ -792,7 +760,6 @@ describe('StringField', () => { format: 'date', }, formData: datetime, - noValidate: true, }); submitForm(node); expect(onSubmit).toHaveBeenLastCalledWith( @@ -816,21 +783,18 @@ describe('StringField', () => { }); it('should accept a valid entered date', () => { - const { node, onError, onChange } = createFormComponent({ + const { node, onChange } = createFormComponent({ schema: { type: 'string', format: 'date', }, uiSchema, - liveValidate: true, }); Simulate.change(node.querySelector('[type=date]'), { target: { value: '2012-12-12' }, }); - expect(onError).not.toHaveBeenCalled(); - expect(onChange).toHaveBeenLastCalledWith( expect.objectContaining({ formData: '2012-12-12', @@ -838,37 +802,6 @@ describe('StringField', () => { ); }); - it('should reject an invalid entered date', () => { - const { node, onChange } = createFormComponent({ - schema: { - type: 'string', - format: 'date', - }, - uiSchema, - liveValidate: true, - }); - - Simulate.change(node.querySelector('[type=date]'), { - target: { value: 'invalid' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { __errors: ['should match format "date"'] }, - errors: [ - { - message: 'should match format "date"', - name: 'format', - params: { format: 'date' }, - property: '', - schemaPath: '#/format', - stack: 'should match format "date"', - }, - ], - }) - ); - }); - it('should render customized DateWidget', () => { const { node } = createFormComponent({ schema: { @@ -1301,20 +1234,6 @@ describe('StringField', () => { ]); }); - it('should accept a valid date', () => { - const { onError } = createFormComponent({ - schema: { - type: 'string', - format: 'date', - }, - uiSchema, - liveValidate: true, - formData: '2012-12-12', - }); - - expect(onError).not.toHaveBeenCalled(); - }); - it('should throw on invalid date', () => { expect(() => { createFormComponent({ @@ -1323,7 +1242,6 @@ describe('StringField', () => { format: 'date', }, uiSchema, - liveValidate: true, formData: '2012-1212', }); }).toThrow('Unable to parse date 2012-1212'); @@ -1500,36 +1418,6 @@ describe('StringField', () => { expect(node.querySelector('[type=email]').id).toBe('root'); }); - it('should reject an invalid entered email', () => { - const { node, onChange } = createFormComponent({ - schema: { - type: 'string', - format: 'email', - }, - liveValidate: true, - }); - - Simulate.change(node.querySelector('[type=email]'), { - target: { value: 'invalid' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { __errors: ['should match format "email"'] }, - errors: [ - { - message: 'should match format "email"', - name: 'format', - params: { format: 'email' }, - property: '', - schemaPath: '#/format', - stack: 'should match format "email"', - }, - ], - }) - ); - }); - it('should render customized EmailWidget', () => { const { node } = createFormComponent({ schema: { @@ -1648,36 +1536,6 @@ describe('StringField', () => { expect(node.querySelector('[type=url]').id).toBe('root'); }); - it('should reject an invalid entered url', () => { - const { node, onChange } = createFormComponent({ - schema: { - type: 'string', - format: 'uri', - }, - liveValidate: true, - }); - - Simulate.change(node.querySelector('[type=url]'), { - target: { value: 'invalid' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { __errors: ['should match format "uri"'] }, - errors: [ - { - message: 'should match format "uri"', - name: 'format', - params: { format: 'uri' }, - property: '', - schemaPath: '#/format', - stack: 'should match format "uri"', - }, - ], - }) - ); - }); - it('should render customized URLWidget', () => { const { node } = createFormComponent({ schema: { @@ -1766,37 +1624,6 @@ describe('StringField', () => { expect(node.querySelector('[type=color]').id).toBe('root'); }); - it('should reject an invalid entered color', () => { - const { node, onChange } = createFormComponent({ - schema: { - type: 'string', - format: 'color', - }, - uiSchema, - liveValidate: true, - }); - - Simulate.change(node.querySelector('[type=color]'), { - target: { value: 'invalid' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { __errors: ['should match format "color"'] }, - errors: [ - { - message: 'should match format "color"', - name: 'format', - params: { format: 'color' }, - property: '', - schemaPath: '#/format', - stack: 'should match format "color"', - }, - ], - }) - ); - }); - it('should render customized ColorWidget', () => { const { node } = createFormComponent({ schema: { diff --git a/packages/oas-form/__tests__/anyOf_test.js b/packages/oas-form/__tests__/anyOf_test.js index 17fc33311..309aebd6a 100644 --- a/packages/oas-form/__tests__/anyOf_test.js +++ b/packages/oas-form/__tests__/anyOf_test.js @@ -357,33 +357,6 @@ describe('anyOf', () => { expect(node.querySelectorAll('#custom-anyof-field')).toHaveLength(1); }); - it('should select the correct field when the form is rendered from existing data', () => { - const schema = { - type: 'object', - properties: { - userId: { - anyOf: [ - { - type: 'number', - }, - { - type: 'string', - }, - ], - }, - }, - }; - - const { node } = createFormComponent({ - schema, - formData: { - userId: 'foobarbaz', - }, - }); - - expect(node.querySelector('select').value).toBe('1'); - }); - it.skip('should select the correct field when the formData property is updated', () => { const schema = { type: 'object', diff --git a/packages/oas-form/__tests__/oneOf_test.js b/packages/oas-form/__tests__/oneOf_test.js index 929185764..f4ac08201 100644 --- a/packages/oas-form/__tests__/oneOf_test.js +++ b/packages/oas-form/__tests__/oneOf_test.js @@ -351,33 +351,6 @@ describe('oneOf', () => { expect(node.querySelectorAll('#custom-oneof-field')).toHaveLength(1); }); - it('should select the correct field when the form is rendered from existing data', () => { - const schema = { - type: 'object', - properties: { - userId: { - oneOf: [ - { - type: 'number', - }, - { - type: 'string', - }, - ], - }, - }, - }; - - const { node } = createFormComponent({ - schema, - formData: { - userId: 'foobarbaz', - }, - }); - - expect(node.querySelector('select').value).toBe('1'); - }); - it.skip('should select the correct field when the formData property is updated', () => { const schema = { type: 'object', diff --git a/packages/oas-form/__tests__/test_utils.js b/packages/oas-form/__tests__/test_utils.js index b191738c2..3594268c5 100644 --- a/packages/oas-form/__tests__/test_utils.js +++ b/packages/oas-form/__tests__/test_utils.js @@ -9,11 +9,11 @@ import Form from '../src'; export function createComponent(Component, props) { const onChange = jest.fn(); - const onError = jest.fn(); const onSubmit = jest.fn(); - const comp = renderIntoDocument(); + const comp = renderIntoDocument(); const node = findDOMNode(comp); - return { comp, node, onChange, onError, onSubmit }; + + return { comp, node, onChange, onSubmit }; } export function createFormComponent(props) { diff --git a/packages/oas-form/__tests__/utils_test.js b/packages/oas-form/__tests__/utils_test.js index d780eb0b2..f368debec 100644 --- a/packages/oas-form/__tests__/utils_test.js +++ b/packages/oas-form/__tests__/utils_test.js @@ -3,7 +3,6 @@ import React from 'react'; import { ADDITIONAL_PROPERTY_FLAG, asNumber, - orderProperties, dataURItoBlob, deepEquals, getDefaultFormState, @@ -22,9 +21,7 @@ import { shouldRender, toDateString, toIdSchema, - toPathSchema, guessType, - mergeSchemas, } from '../src/utils'; describe('utils', () => { @@ -675,41 +672,6 @@ describe('utils', () => { }, }); }); - - it('should populate defaults for oneOf + dependencies', () => { - const schema = { - oneOf: [ - { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - }, - ], - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: 'string', - }, - grade: { - default: 'A', - }, - }, - }, - ], - }, - }, - }; - expect(getDefaultFormState(schema, { name: 'Name' })).toStrictEqual({ - name: 'Name', - grade: 'A', - }); - }); }); describe('defaults with anyOf', () => { @@ -755,362 +717,6 @@ describe('utils', () => { }, }); }); - - it('should populate defaults for anyOf + dependencies', () => { - const schema = { - anyOf: [ - { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - }, - ], - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: 'string', - }, - grade: { - type: 'string', - default: 'A', - }, - }, - }, - ], - }, - }, - }; - expect(getDefaultFormState(schema, { name: 'Name' })).toStrictEqual({ - name: 'Name', - grade: 'A', - }); - }); - }); - - describe('with dependencies', () => { - it('should populate defaults for dependencies', () => { - const schema = { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: 'string', - }, - grade: { - type: 'string', - default: 'A', - }, - }, - }, - ], - }, - }, - }; - expect(getDefaultFormState(schema, { name: 'Name' })).toStrictEqual({ - name: 'Name', - grade: 'A', - }); - }); - - it('should populate defaults for nested dependencies', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: 'string', - }, - grade: { - type: 'string', - default: 'A', - }, - }, - }, - ], - }, - }, - }, - }, - }; - expect(getDefaultFormState(schema, { foo: { name: 'Name' } })).toStrictEqual({ - foo: { - name: 'Name', - grade: 'A', - }, - }); - }); - - it('should populate defaults for nested dependencies in arrays', () => { - const schema = { - type: 'array', - items: { - properties: { - foo: { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: 'string', - }, - grade: { - type: 'string', - default: 'A', - }, - }, - }, - ], - }, - }, - }, - }, - }, - }; - expect(getDefaultFormState(schema, [{ foo: { name: 'Name' } }])).toStrictEqual([ - { - foo: { - name: 'Name', - grade: 'A', - }, - }, - ]); - }); - - it('should populate defaults for nested dependencies in arrays when matching enum values in oneOf', () => { - const schema = { - type: 'array', - items: { - properties: { - foo: { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - enum: ['first'], - }, - grade: { - type: 'string', - default: 'A', - }, - }, - }, - { - properties: { - name: { - enum: ['second'], - }, - grade: { - type: 'string', - default: 'B', - }, - }, - }, - ], - }, - }, - }, - }, - }, - }; - expect( - getDefaultFormState(schema, [ - { foo: { name: 'first' } }, - { foo: { name: 'second' } }, - { foo: { name: 'third' } }, - ]) - ).toStrictEqual([ - { - foo: { - name: 'first', - grade: 'A', - }, - }, - { - foo: { - name: 'second', - grade: 'B', - }, - }, - { - foo: { - name: 'third', - }, - }, - ]); - }); - - it('should populate defaults for nested oneOf + dependencies', () => { - const schema = { - type: 'object', - properties: { - foo: { - oneOf: [ - { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - }, - ], - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: 'string', - }, - grade: { - type: 'string', - default: 'A', - }, - }, - }, - ], - }, - }, - }, - }, - }; - expect(getDefaultFormState(schema, { foo: { name: 'Name' } })).toStrictEqual({ - foo: { - name: 'Name', - grade: 'A', - }, - }); - }); - - it('should populate defaults for nested dependencies when formData passed to computeDefaults is undefined', () => { - const schema = { - type: 'object', - properties: { - can_1: { - type: 'object', - properties: { - phy: { - title: 'Physical', - description: 'XYZ', - type: 'object', - properties: { - bit_rate_cfg_mode: { - title: 'Sub title', - description: 'XYZ', - type: 'integer', - default: 0, - }, - }, - dependencies: { - bit_rate_cfg_mode: { - oneOf: [ - { - properties: { - bit_rate_cfg_mode: { - enum: [0], - }, - }, - }, - ], - }, - }, - }, - }, - }, - }, - }; - expect(getDefaultFormState(schema, undefined)).toStrictEqual({ - can_1: { - phy: { - bit_rate_cfg_mode: 0, - }, - }, - }); - }); - - it('should not crash for defaults for nested dependencies when formData passed to computeDefaults is null', () => { - const schema = { - type: 'object', - properties: { - can_1: { - type: 'object', - properties: { - phy: { - title: 'Physical', - description: 'XYZ', - type: 'object', - properties: { - bit_rate_cfg_mode: { - title: 'Sub title', - description: 'XYZ', - type: 'integer', - default: 0, - }, - }, - dependencies: { - bit_rate_cfg_mode: { - oneOf: [ - { - properties: { - bit_rate_cfg_mode: { - enum: [0], - }, - }, - }, - ], - }, - }, - }, - }, - }, - }, - }; - expect(getDefaultFormState(schema, { can_1: { phy: null } })).toStrictEqual({ - can_1: { - phy: null, - }, - }); - }); }); describe('with schema keys not defined in the formData', () => { @@ -1166,32 +772,6 @@ describe('utils', () => { }); }); - describe('orderProperties()', () => { - it('should remove from order elements that are not in properties', () => { - const properties = ['foo', 'baz']; - const order = ['foo', 'bar', 'baz', 'qux']; - expect(orderProperties(properties, order)).toStrictEqual(['foo', 'baz']); - }); - - it('should order properties according to the order', () => { - const properties = ['bar', 'foo']; - const order = ['foo', 'bar']; - expect(orderProperties(properties, order)).toStrictEqual(['foo', 'bar']); - }); - - it('should replace * with properties that are absent in order', () => { - const properties = ['foo', 'bar', 'baz']; - const order = ['*', 'foo']; - expect(orderProperties(properties, order)).toStrictEqual(['bar', 'baz', 'foo']); - }); - - it('should handle more complex ordering case correctly', () => { - const properties = ['foo', 'baz', 'qux', 'bar']; - const order = ['quux', 'foo', '*', 'corge', 'baz']; - expect(orderProperties(properties, order)).toStrictEqual(['foo', 'qux', 'bar', 'baz']); - }); - }); - describe('isConstant', () => { it('should return false when neither enum nor const is defined', () => { const schema = {}; @@ -1483,121 +1063,6 @@ describe('utils', () => { }); }); - describe('mergeSchemas()', () => { - it("shouldn't mutate the provided objects", () => { - const obj1 = { a: 1 }; - mergeSchemas(obj1, { b: 2 }); - expect(obj1).toStrictEqual({ a: 1 }); - }); - - it('should merge two one-level deep objects', () => { - expect(mergeSchemas({ a: 1 }, { b: 2 })).toStrictEqual({ a: 1, b: 2 }); - }); - - it('should override the first object with the values from the second', () => { - expect(mergeSchemas({ a: 1 }, { a: 2 })).toStrictEqual({ a: 2 }); - }); - - it('should override non-existing values of the first object with the values from the second', () => { - expect(mergeSchemas({ a: { b: undefined } }, { a: { b: { c: 1 } } })).toStrictEqual({ a: { b: { c: 1 } } }); - }); - - it('should recursively merge deeply nested objects', () => { - const obj1 = { - a: 1, - b: { - c: 3, - d: [1, 2, 3], - e: { f: { g: 1 } }, - }, - c: 2, - }; - const obj2 = { - a: 1, - b: { - d: [3, 2, 1], - e: { f: { h: 2 } }, - g: 1, - }, - c: 3, - }; - const expected = { - a: 1, - b: { - c: 3, - d: [3, 2, 1], - e: { f: { g: 1, h: 2 } }, - g: 1, - }, - c: 3, - }; - expect(mergeSchemas(obj1, obj2)).toStrictEqual(expected); - }); - - it('should recursively merge File objects', () => { - const file = new File(['test'], 'test.txt'); - const obj1 = { - a: {}, - }; - const obj2 = { - a: file, - }; - expect(mergeSchemas(obj1, obj2).a).toBeInstanceOf(File); - }); - - describe('arrays', () => { - it('should not concat arrays', () => { - const obj1 = { a: [1] }; - const obj2 = { a: [2] }; - - expect(mergeSchemas(obj1, obj2)).toStrictEqual({ a: [2] }); - }); - - it("should concat arrays under 'required' keyword", () => { - const obj1 = { type: 'object', required: [1] }; - const obj2 = { type: 'object', required: [2] }; - - expect(mergeSchemas(obj1, obj2)).toStrictEqual({ - type: 'object', - required: [1, 2], - }); - }); - - it("should concat arrays under 'required' keyword when one of the schemas is an object type", () => { - const obj1 = { type: 'object', required: [1] }; - const obj2 = { required: [2] }; - - expect(mergeSchemas(obj1, obj2)).toStrictEqual({ - type: 'object', - required: [1, 2], - }); - }); - - it("should concat nested arrays under 'required' keyword", () => { - const obj1 = { a: { type: 'object', required: [1] } }; - const obj2 = { a: { type: 'object', required: [2] } }; - - expect(mergeSchemas(obj1, obj2)).toStrictEqual({ - a: { type: 'object', required: [1, 2] }, - }); - }); - - it("should not include duplicate values when concatting arrays under 'required' keyword", () => { - const obj1 = { type: 'object', required: [1] }; - const obj2 = { type: 'object', required: [1] }; - - expect(mergeSchemas(obj1, obj2)).toStrictEqual({ type: 'object', required: [1] }); - }); - - it("should not concat arrays under 'required' keyword that are not under an object type", () => { - const obj1 = { required: [1] }; - const obj2 = { required: [2] }; - - expect(mergeSchemas(obj1, obj2)).toStrictEqual({ required: [2] }); - }); - }); - }); - describe('retrieveSchema()', () => { it("should 'resolve' a schema which contains definitions", () => { const schema = { $ref: '#/definitions/address' }; @@ -1731,558 +1196,18 @@ describe('utils', () => { it('should priorize local definitions over foreign ones', () => { const schema = { - $ref: '#/definitions/address', - title: 'foo', - }; - const address = { - type: 'string', - title: 'bar', - }; - const definitions = { address }; - - expect(retrieveSchema(schema, { definitions })).toStrictEqual({ - ...address, - title: 'foo', - }); - }); - - describe('property dependencies', () => { - describe('false condition', () => { - it('should not add required properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['a'], - dependencies: { - a: ['b'], - }, - }; - const definitions = {}; - const formData = {}; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['a'], - }); - }); - }); - - describe('true condition', () => { - describe('when required is not defined', () => { - it('should define required properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - dependencies: { - a: ['b'], - }, - }; - const definitions = {}; - const formData = { a: '1' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['b'], - }); - }); - }); - - describe('when required is defined', () => { - it('should concat required properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['a'], - dependencies: { - a: ['b'], - }, - }; - const definitions = {}; - const formData = { a: '1' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['a', 'b'], - }); - }); - }); - }); - }); - - describe('schema dependencies', () => { - describe('conditional', () => { - describe('false condition', () => { - it('should not modify properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - }, - dependencies: { - a: { - properties: { - b: { type: 'integer' }, - }, - }, - }, - }; - const definitions = {}; - const formData = {}; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - }, - }); - }); - }); - - describe('true condition', () => { - it('should add properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - }, - dependencies: { - a: { - properties: { - b: { type: 'integer' }, - }, - }, - }, - }; - const definitions = {}; - const formData = { a: '1' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - }); - }); - - it('should concat required properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['a'], - dependencies: { - a: { - properties: { - a: { type: 'string' }, - }, - required: ['b'], - }, - }, - }; - const definitions = {}; - const formData = { a: '1' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - required: ['a', 'b'], - }); - }); - - it("should not concat enum properties, but should concat 'required' properties", () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string', enum: ['FOO', 'BAR', 'BAZ'] }, - b: { type: 'string', enum: ['GREEN', 'BLUE', 'RED'] }, - }, - required: ['a'], - dependencies: { - a: { - properties: { - a: { enum: ['FOO'] }, - b: { enum: ['BLUE'] }, - }, - required: ['a', 'b'], - }, - }, - }; - const definitions = {}; - const formData = { a: 'FOO' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string', enum: ['FOO'] }, - b: { type: 'string', enum: ['BLUE'] }, - }, - required: ['a', 'b'], - }); - }); - }); - - describe('with $ref in dependency', () => { - it('should retrieve referenced schema', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - }, - dependencies: { - a: { - $ref: '#/definitions/needsB', - }, - }, - }; - const definitions = { - needsB: { - properties: { - b: { type: 'integer' }, - }, - }, - }; - const formData = { a: '1' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'integer' }, - }, - }); - }); - }); - - describe('with $ref in oneOf', () => { - it('should retrieve referenced schemas', () => { - const schema = { - type: 'object', - properties: { - a: { enum: ['typeA', 'typeB'] }, - }, - dependencies: { - a: { - oneOf: [{ $ref: '#/definitions/needsA' }, { $ref: '#/definitions/needsB' }], - }, - }, - }; - const definitions = { - needsA: { - properties: { - a: { enum: ['typeA'] }, - b: { type: 'number' }, - }, - }, - needsB: { - properties: { - a: { enum: ['typeB'] }, - c: { type: 'boolean' }, - }, - }, - }; - const formData = { a: 'typeB' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { enum: ['typeA', 'typeB'] }, - c: { type: 'boolean' }, - }, - }); - }); - }); - }); - - describe('dynamic', () => { - describe('false condition', () => { - it('should not modify properties', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string' }, - }, - dependencies: { - a: { - oneOf: [ - { - properties: { - a: { enum: ['int'] }, - b: { type: 'integer' }, - }, - }, - { - properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, - }, - }, - ], - }, - }, - }; - const definitions = {}; - const formData = {}; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string' }, - }, - }); - }); - }); - - describe('true condition', () => { - it("should add 'first' properties given 'first' data", () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - }, - dependencies: { - a: { - oneOf: [ - { - properties: { - a: { enum: ['int'] }, - b: { type: 'integer' }, - }, - }, - { - properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, - }, - }, - ], - }, - }, - }; - const definitions = {}; - const formData = { a: 'int' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - b: { type: 'integer' }, - }, - }); - }); - - it("should add 'second' properties given 'second' data", () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - }, - dependencies: { - a: { - oneOf: [ - { - properties: { - a: { enum: ['int'] }, - b: { type: 'integer' }, - }, - }, - { - properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, - }, - }, - ], - }, - }, - }; - const definitions = {}; - const formData = { a: 'bool' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - b: { type: 'boolean' }, - }, - }); - }); - - describe('showing/hiding nested dependencies', () => { - const schema = { - type: 'object', - dependencies: { - employee_accounts: { - oneOf: [ - { - properties: { - employee_accounts: { - const: true, - }, - update_absences: { - title: 'Update Absences', - type: 'string', - oneOf: [ - { - title: 'Both', - const: 'BOTH', - }, - ], - }, - }, - }, - ], - }, - update_absences: { - oneOf: [ - { - properties: { - permitted_extension: { - title: 'Permitted Extension', - type: 'integer', - }, - update_absences: { - const: 'BOTH', - }, - }, - }, - { - properties: { - permitted_extension: { - title: 'Permitted Extension', - type: 'integer', - }, - update_absences: { - const: 'MEDICAL_ONLY', - }, - }, - }, - { - properties: { - permitted_extension: { - title: 'Permitted Extension', - type: 'integer', - }, - update_absences: { - const: 'NON_MEDICAL_ONLY', - }, - }, - }, - ], - }, - }, - properties: { - employee_accounts: { - type: 'boolean', - title: 'Employee Accounts', - }, - }, - }; - const definitions = {}; - - it('should not include nested dependencies that should be hidden', () => { - const formData = { - employee_accounts: false, - update_absences: 'BOTH', - }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - employee_accounts: { - type: 'boolean', - title: 'Employee Accounts', - }, - }, - }); - }); - - it('should include nested dependencies that should not be hidden', () => { - const formData = { - employee_accounts: true, - update_absences: 'BOTH', - }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - employee_accounts: { - type: 'boolean', - title: 'Employee Accounts', - }, - permitted_extension: { - title: 'Permitted Extension', - type: 'integer', - }, - update_absences: { - title: 'Update Absences', - type: 'string', - oneOf: [ - { - title: 'Both', - const: 'BOTH', - }, - ], - }, - }, - }); - }); - }); - }); - - describe('with $ref in dependency', () => { - it('should retrieve the referenced schema', () => { - const schema = { - type: 'object', - properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - }, - dependencies: { - a: { - $ref: '#/definitions/typedInput', - }, - }, - }; - const definitions = { - typedInput: { - oneOf: [ - { - properties: { - a: { enum: ['int'] }, - b: { type: 'integer' }, - }, - }, - { - properties: { - a: { enum: ['bool'] }, - b: { type: 'boolean' }, - }, - }, - ], - }, - }; - const formData = { a: 'bool' }; - expect(retrieveSchema(schema, { definitions }, formData)).toStrictEqual({ - type: 'object', - properties: { - a: { type: 'string', enum: ['int', 'bool'] }, - b: { type: 'boolean' }, - }, - }); - }); - }); + $ref: '#/definitions/address', + title: 'foo', + }; + const address = { + type: 'string', + title: 'bar', + }; + const definitions = { address }; + + expect(retrieveSchema(schema, { definitions })).toStrictEqual({ + ...address, + title: 'foo', }); }); @@ -2524,774 +1449,113 @@ describe('utils', () => { type: 'string', }, }, - required: ['id'], - }, - }, - }; - expect(toIdSchema(schema)).toStrictEqual({ - $id: 'root', - metadata: { - $id: 'root_metadata', - id: { $id: 'root_metadata_id' }, - }, - }); - }); - - it('should return an idSchema for array item objects', () => { - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }, - }; - - expect(toIdSchema(schema)).toStrictEqual({ - $id: 'root', - foo: { $id: 'root_foo' }, - }); - }); - - it('should retrieve referenced schema definitions', () => { - const schema = { - definitions: { - testdef: { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }, - }, - $ref: '#/definitions/testdef', - }; - - expect(toIdSchema(schema, undefined, schema)).toStrictEqual({ - $id: 'root', - foo: { $id: 'root_foo' }, - bar: { $id: 'root_bar' }, - }); - }); - - it('should return an idSchema for property dependencies', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - dependencies: { - foo: { - properties: { - bar: { type: 'string' }, - }, - }, - }, - }; - const formData = { - foo: 'test', - }; - - expect(toIdSchema(schema, undefined, schema, formData)).toStrictEqual({ - $id: 'root', - foo: { $id: 'root_foo' }, - bar: { $id: 'root_bar' }, - }); - }); - - it('should return an idSchema for nested property dependencies', () => { - const schema = { - type: 'object', - properties: { - obj: { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - dependencies: { - foo: { - properties: { - bar: { type: 'string' }, - }, - }, - }, - }, - }, - }; - const formData = { - obj: { - foo: 'test', - }, - }; - - expect(toIdSchema(schema, undefined, schema, formData)).toStrictEqual({ - $id: 'root', - obj: { - $id: 'root_obj', - foo: { $id: 'root_obj_foo' }, - bar: { $id: 'root_obj_bar' }, - }, - }); - }); - - it('should return an idSchema for unmet property dependencies', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - dependencies: { - foo: { - properties: { - bar: { type: 'string' }, - }, - }, - }, - }; - - const formData = {}; - - expect(toIdSchema(schema, undefined, schema, formData)).toStrictEqual({ - $id: 'root', - foo: { $id: 'root_foo' }, - }); - }); - - it('should handle idPrefix parameter', () => { - const schema = { - definitions: { - testdef: { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }, - }, - $ref: '#/definitions/testdef', - }; - - expect(toIdSchema(schema, undefined, schema, {}, 'rjsf')).toStrictEqual({ - $id: 'rjsf', - foo: { $id: 'rjsf_foo' }, - bar: { $id: 'rjsf_bar' }, - }); - }); - - it('should handle null form data for object schemas', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }; - const formData = null; - const result = toIdSchema(schema, null, {}, formData, 'rjsf'); - - expect(result).toStrictEqual({ - $id: 'rjsf', - foo: { $id: 'rjsf_foo' }, - bar: { $id: 'rjsf_bar' }, - }); - }); - - it('should handle circular referencing', () => { - const treeSchema = { - properties: {}, - }; - treeSchema.properties.tree = treeSchema; - const rootSchema = { - definitions: {}, - properties: { - tree: treeSchema, - }, - type: 'object', - }; - - const result = toIdSchema(treeSchema, null, rootSchema); - - expect(result).toStrictEqual({ - $id: 'root', - }); - }); - }); - - describe('toPathSchema', () => { - it('should return a pathSchema for root field', () => { - const schema = { type: 'string' }; - - expect(toPathSchema(schema)).toStrictEqual({ $name: '' }); - }); - - it('should return a pathSchema for nested objects', () => { - const schema = { - type: 'object', - properties: { - level1: { - type: 'object', - properties: { - level2: { type: 'string' }, - }, - }, - }, - }; - - expect(toPathSchema(schema)).toStrictEqual({ - $name: '', - level1: { - $name: 'level1', - level2: { $name: 'level1.level2' }, - }, - }); - }); - - it('should return a pathSchema for a schema with dependencies', () => { - const schema = { - type: 'object', - properties: { - list: { - title: 'list', - type: 'array', - items: { - type: 'object', - properties: { - a: { type: 'string' }, - b: { type: 'string' }, - }, - dependencies: { - b: { - properties: { - c: { - type: 'string', - }, - }, - }, - }, - }, - }, - }, - }; - - const formData = { - list: [ - { - a: 'a1', - b: 'b1', - c: 'c1', - }, - { - a: 'a2', - }, - { - a: 'a2', - c: 'c2', - }, - ], - }; - - expect(toPathSchema(schema, '', schema, formData)).toStrictEqual({ - $name: '', - list: { - $name: 'list', - 0: { - $name: 'list.0', - a: { - $name: 'list.0.a', - }, - b: { - $name: 'list.0.b', - }, - c: { - $name: 'list.0.c', - }, - }, - 1: { - $name: 'list.1', - a: { - $name: 'list.1.a', - }, - b: { - $name: 'list.1.b', - }, - }, - 2: { - $name: 'list.2', - a: { - $name: 'list.2.a', - }, - b: { - $name: 'list.2.b', - }, - }, - }, - }); - }); - - it('should return a pathSchema for a schema with references', () => { - const schema = { - definitions: { - address: { - type: 'object', - properties: { - street_address: { - type: 'string', - }, - city: { - type: 'string', - }, - state: { - type: 'string', - }, - }, - required: ['street_address', 'city', 'state'], - }, - }, - type: 'object', - properties: { - billing_address: { - title: 'Billing address', - $ref: '#/definitions/address', + required: ['id'], }, }, }; - - const formData = { - billing_address: { - street_address: '21, Jump Street', - city: 'Babel', - state: 'Neverland', + expect(toIdSchema(schema)).toStrictEqual({ + $id: 'root', + metadata: { + $id: 'root_metadata', + id: { $id: 'root_metadata_id' }, }, - }; + }); + }); - expect(toPathSchema(schema, '', schema, formData)).toStrictEqual({ - $name: '', - billing_address: { - $name: 'billing_address', - city: { - $name: 'billing_address.city', - }, - state: { - $name: 'billing_address.state', - }, - street_address: { - $name: 'billing_address.street_address', + it('should return an idSchema for array item objects', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + foo: { type: 'string' }, }, }, + }; + + expect(toIdSchema(schema)).toStrictEqual({ + $id: 'root', + foo: { $id: 'root_foo' }, }); }); - it('should return a pathSchema for a schema with references in an array item', () => { + it('should retrieve referenced schema definitions', () => { const schema = { definitions: { - address: { + testdef: { type: 'object', properties: { - street_address: { - type: 'string', - }, - city: { - type: 'string', - }, - state: { - type: 'string', - }, - }, - required: ['street_address', 'city', 'state'], - }, - }, - type: 'object', - properties: { - address_list: { - title: 'Address list', - type: 'array', - items: { - $ref: '#/definitions/address', + foo: { type: 'string' }, + bar: { type: 'string' }, }, }, }, + $ref: '#/definitions/testdef', }; - const formData = { - address_list: [ - { - street_address: '21, Jump Street', - city: 'Babel', - state: 'Neverland', - }, - { - street_address: '1234 Schema Rd.', - city: 'New York', - state: 'Arizona', - }, - ], - }; - - expect(toPathSchema(schema, '', schema, formData)).toStrictEqual({ - $name: '', - address_list: { - $name: 'address_list', - 0: { - $name: 'address_list.0', - city: { - $name: 'address_list.0.city', - }, - state: { - $name: 'address_list.0.state', - }, - street_address: { - $name: 'address_list.0.street_address', - }, - }, - 1: { - $name: 'address_list.1', - city: { - $name: 'address_list.1.city', - }, - state: { - $name: 'address_list.1.state', - }, - street_address: { - $name: 'address_list.1.street_address', - }, - }, - }, + expect(toIdSchema(schema, undefined, schema)).toStrictEqual({ + $id: 'root', + foo: { $id: 'root_foo' }, + bar: { $id: 'root_bar' }, }); }); - it('should return an pathSchema with different types of arrays', () => { + it('should handle idPrefix parameter', () => { const schema = { definitions: { - Thing: { + testdef: { type: 'object', properties: { - name: { - type: 'string', - default: 'Default name', - }, + foo: { type: 'string' }, + bar: { type: 'string' }, }, }, }, + $ref: '#/definitions/testdef', + }; + + expect(toIdSchema(schema, undefined, schema, {}, 'rjsf')).toStrictEqual({ + $id: 'rjsf', + foo: { $id: 'rjsf_foo' }, + bar: { $id: 'rjsf_bar' }, + }); + }); + + it('should handle null form data for object schemas', () => { + const schema = { type: 'object', properties: { - listOfStrings: { - type: 'array', - title: 'A list of strings', - items: { - type: 'string', - default: 'bazinga', - }, - }, - multipleChoicesList: { - type: 'array', - title: 'A multiple choices list', - items: { - type: 'string', - enum: ['foo', 'bar', 'fuzz', 'qux'], - }, - uniqueItems: true, - }, - fixedItemsList: { - type: 'array', - title: 'A list of fixed items', - items: [ - { - title: 'A string value', - type: 'string', - default: 'lorem ipsum', - }, - { - title: 'a boolean value', - type: 'boolean', - }, - ], - additionalItems: { - title: 'Additional item', - type: 'number', - }, - }, - minItemsList: { - type: 'array', - title: 'A list with a minimal number of items', - minItems: 3, - items: { - $ref: '#/definitions/Thing', - }, - }, - defaultsAndMinItems: { - type: 'array', - title: 'List and item level defaults', - minItems: 5, - default: ['carp', 'trout', 'bream'], - items: { - type: 'string', - default: 'unidentified', - }, - }, - nestedList: { - type: 'array', - title: 'Nested list', - items: { - type: 'array', - title: 'Inner list', - items: { - type: 'string', - default: 'lorem ipsum', - }, - }, - }, - listOfObjects: { - type: 'array', - title: 'List of objects', - items: { - type: 'object', - title: 'Object in list', - properties: { - name: { - type: 'string', - default: 'Default name', - }, - id: { - type: 'number', - default: 'an id', - }, - }, - }, - }, - unorderable: { - title: 'Unorderable items', - type: 'array', - items: { - type: 'string', - default: 'lorem ipsum', - }, - }, - unremovable: { - title: 'Unremovable items', - type: 'array', - items: { - type: 'string', - default: 'lorem ipsum', - }, - }, - noToolbar: { - title: 'No add, remove and order buttons', - type: 'array', - items: { - type: 'string', - default: 'lorem ipsum', - }, - }, - fixedNoToolbar: { - title: 'Fixed array without buttons', - type: 'array', - items: [ - { - title: 'A number', - type: 'number', - default: 42, - }, - { - title: 'A boolean', - type: 'boolean', - default: false, - }, - ], - additionalItems: { - title: 'A string', - type: 'string', - default: 'lorem ipsum', - }, - }, + foo: { type: 'string' }, + bar: { type: 'string' }, }, }; + const formData = null; + const result = toIdSchema(schema, null, {}, formData, 'rjsf'); - const formData = { - listOfStrings: ['foo', 'bar'], - multipleChoicesList: ['foo', 'bar'], - fixedItemsList: ['Some text', true, 123], - minItemsList: [ - { - name: 'Default name', - }, - { - name: 'Default name', - }, - { - name: 'Default name', - }, - ], - defaultsAndMinItems: ['carp', 'trout', 'bream', 'unidentified', 'unidentified'], - nestedList: [['lorem', 'ipsum'], ['dolor']], - listOfObjects: [{ name: 'name1', id: 123 }, { name: 'name2', id: 1234 }, { id: 12345 }], - unorderable: ['one', 'two'], - unremovable: ['one', 'two'], - noToolbar: ['one', 'two'], - fixedNoToolbar: [42, true, 'additional item one', 'additional item two'], - }; + expect(result).toStrictEqual({ + $id: 'rjsf', + foo: { $id: 'rjsf_foo' }, + bar: { $id: 'rjsf_bar' }, + }); + }); - expect(toPathSchema(schema, '', schema, formData)).toStrictEqual({ - $name: '', - defaultsAndMinItems: { - $name: 'defaultsAndMinItems', - 0: { - $name: 'defaultsAndMinItems.0', - }, - 1: { - $name: 'defaultsAndMinItems.1', - }, - 2: { - $name: 'defaultsAndMinItems.2', - }, - 3: { - $name: 'defaultsAndMinItems.3', - }, - 4: { - $name: 'defaultsAndMinItems.4', - }, - }, - fixedItemsList: { - $name: 'fixedItemsList', - 0: { - $name: 'fixedItemsList.0', - }, - 1: { - $name: 'fixedItemsList.1', - }, - 2: { - $name: 'fixedItemsList.2', - }, - }, - fixedNoToolbar: { - $name: 'fixedNoToolbar', - 0: { - $name: 'fixedNoToolbar.0', - }, - 1: { - $name: 'fixedNoToolbar.1', - }, - 2: { - $name: 'fixedNoToolbar.2', - }, - 3: { - $name: 'fixedNoToolbar.3', - }, - }, - listOfObjects: { - $name: 'listOfObjects', - 0: { - $name: 'listOfObjects.0', - id: { - $name: 'listOfObjects.0.id', - }, - name: { - $name: 'listOfObjects.0.name', - }, - }, - 1: { - $name: 'listOfObjects.1', - id: { - $name: 'listOfObjects.1.id', - }, - name: { - $name: 'listOfObjects.1.name', - }, - }, - 2: { - $name: 'listOfObjects.2', - id: { - $name: 'listOfObjects.2.id', - }, - name: { - $name: 'listOfObjects.2.name', - }, - }, - }, - listOfStrings: { - $name: 'listOfStrings', - 0: { - $name: 'listOfStrings.0', - }, - 1: { - $name: 'listOfStrings.1', - }, - }, - minItemsList: { - $name: 'minItemsList', - 0: { - $name: 'minItemsList.0', - name: { - $name: 'minItemsList.0.name', - }, - }, - 1: { - $name: 'minItemsList.1', - name: { - $name: 'minItemsList.1.name', - }, - }, - 2: { - $name: 'minItemsList.2', - name: { - $name: 'minItemsList.2.name', - }, - }, - }, - multipleChoicesList: { - $name: 'multipleChoicesList', - 0: { - $name: 'multipleChoicesList.0', - }, - 1: { - $name: 'multipleChoicesList.1', - }, - }, - nestedList: { - $name: 'nestedList', - 0: { - $name: 'nestedList.0', - 0: { - $name: 'nestedList.0.0', - }, - 1: { - $name: 'nestedList.0.1', - }, - }, - 1: { - $name: 'nestedList.1', - 0: { - $name: 'nestedList.1.0', - }, - }, - }, - noToolbar: { - $name: 'noToolbar', - 0: { - $name: 'noToolbar.0', - }, - 1: { - $name: 'noToolbar.1', - }, - }, - unorderable: { - $name: 'unorderable', - 0: { - $name: 'unorderable.0', - }, - 1: { - $name: 'unorderable.1', - }, - }, - unremovable: { - $name: 'unremovable', - 0: { - $name: 'unremovable.0', - }, - 1: { - $name: 'unremovable.1', - }, + it('should handle circular referencing', () => { + const treeSchema = { + properties: {}, + }; + treeSchema.properties.tree = treeSchema; + const rootSchema = { + definitions: {}, + properties: { + tree: treeSchema, }, + type: 'object', + }; + + const result = toIdSchema(treeSchema, null, rootSchema); + + expect(result).toStrictEqual({ + $id: 'root', }); }); }); @@ -3504,16 +1768,6 @@ describe('utils', () => { const Widget = props =>
; expect(getWidget(schema, Widget)({})).toStrictEqual(); }); - - it('should not fail on forwarded ref component', () => { - const Widget = React.forwardRef((props, ref) =>
); - expect(getWidget(schema, Widget)({})).toStrictEqual(); - }); - - it.skip('should not fail on memo component', () => { - const Widget = React.memo(props =>
); - expect(getWidget(schema, Widget)({})).toStrictEqual(); - }); }); }); diff --git a/packages/oas-form/__tests__/validate_test.js b/packages/oas-form/__tests__/validate_test.js deleted file mode 100644 index 97780daa4..000000000 --- a/packages/oas-form/__tests__/validate_test.js +++ /dev/null @@ -1,795 +0,0 @@ -/* eslint-disable global-require */ -import React from 'react'; -import { Simulate } from 'react-dom/test-utils'; - -import validateFormData, { isValid, toErrorList } from '../src/validate'; -import { createFormComponent, submitForm } from './test_utils'; - -describe('Validation', () => { - describe('validate.isValid()', () => { - it('should return true if the data is valid against the schema', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }; - - expect(isValid(schema, { foo: 'bar' })).toBe(true); - }); - - it('should return false if the data is not valid against the schema', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }; - - expect(isValid(schema, { foo: 12345 })).toBe(false); - }); - - it('should return false if the schema is invalid', () => { - const schema = 'foobarbaz'; - - expect(isValid(schema, { foo: 'bar' })).toBe(false); - }); - }); - - describe('validate.validateFormData()', () => { - describe('No custom validate function', () => { - const illFormedKey = 'bar.\'"[]()=+*&^%$#@!'; - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - [illFormedKey]: { type: 'string' }, - }, - }; - - let errors; - let errorSchema; - - beforeEach(() => { - const result = validateFormData({ foo: 42, [illFormedKey]: 41 }, schema); - errors = result.errors; - errorSchema = result.errorSchema; - }); - - it('should return an error list', () => { - expect(errors).toHaveLength(2); - expect(errors[0].message).toBe('should be string'); - expect(errors[1].message).toBe('should be string'); - }); - - it('should return an errorSchema', () => { - expect(errorSchema.foo.__errors).toHaveLength(1); - expect(errorSchema.foo.__errors[0]).toBe('should be string'); - expect(errorSchema[illFormedKey].__errors).toHaveLength(1); - expect(errorSchema[illFormedKey].__errors[0]).toBe('should be string'); - }); - }); - - describe('Validating multipleOf with a float', () => { - const schema = { - type: 'object', - properties: { - price: { - title: 'Price per task ($)', - type: 'number', - multipleOf: 0.01, - minimum: 0, - }, - }, - }; - - let errors; - - beforeEach(() => { - const result = validateFormData({ price: 0.14 }, schema); - errors = result.errors; - }); - - it('should not return an error', () => { - expect(errors).toHaveLength(0); - }); - }); - - describe('validating using custom meta schema', () => { - const schema = { - $ref: '#/definitions/Dataset', - $schema: 'http://json-schema.org/draft-04/schema#', - definitions: { - Dataset: { - properties: { - datasetId: { - pattern: '\\d+', - type: 'string', - }, - }, - required: ['datasetId'], - type: 'object', - }, - }, - }; - const metaSchemaDraft4 = require('ajv/lib/refs/json-schema-draft-04.json'); - const metaSchemaDraft6 = require('ajv/lib/refs/json-schema-draft-06.json'); - - it('should return a validation error about meta schema when meta schema is not defined', () => { - const errors = validateFormData({ datasetId: 'some kind of text' }, schema); - const errMessage = 'no schema with key or ref "http://json-schema.org/draft-04/schema#"'; - expect(errors.errors[0].stack).toBe(errMessage); - expect(errors.errors).toStrictEqual([ - { - stack: errMessage, - }, - ]); - expect(errors.errorSchema).toStrictEqual({ - $schema: { __errors: [errMessage] }, - }); - }); - - it('should return a validation error about formData', () => { - const errors = validateFormData({ datasetId: 'some kind of text' }, schema, null, null, [metaSchemaDraft4]); - expect(errors.errors).toHaveLength(1); - expect(errors.errors[0].stack).toBe('.datasetId should match pattern "\\d+"'); - }); - - it('should return a validation error about formData, when used with multiple meta schemas', () => { - const errors = validateFormData({ datasetId: 'some kind of text' }, schema, null, null, [ - metaSchemaDraft4, - metaSchemaDraft6, - ]); - expect(errors.errors).toHaveLength(1); - expect(errors.errors[0].stack).toBe('.datasetId should match pattern "\\d+"'); - }); - }); - - describe('validating using custom string formats', () => { - const schema = { - type: 'object', - properties: { - phone: { - type: 'string', - format: 'phone-us', - }, - }, - }; - - it('should not return a validation error if unknown string format is used', () => { - const result = validateFormData({ phone: '800.555.2368' }, schema); - expect(result.errors).toHaveLength(0); - }); - - it('should return a validation error about formData', () => { - const result = validateFormData({ phone: '800.555.2368' }, schema, null, null, null, { - 'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/, - }); - - expect(result.errors).toHaveLength(1); - expect(result.errors[0].stack).toBe('.phone should match format "phone-us"'); - }); - - it('prop updates with new custom formats are accepted', () => { - const result = validateFormData( - { phone: 'abc' }, - { - type: 'object', - properties: { - phone: { - type: 'string', - format: 'area-code', - }, - }, - }, - null, - null, - null, - { 'area-code': /\d{3}/ } - ); - - expect(result.errors).toHaveLength(1); - expect(result.errors[0].stack).toBe('.phone should match format "area-code"'); - }); - }); - - describe('Custom validate function', () => { - let errors; - let errorSchema; - - const schema = { - type: 'object', - required: ['pass1', 'pass2'], - properties: { - pass1: { type: 'string' }, - pass2: { type: 'string' }, - }, - }; - - beforeEach(() => { - const validate = (formData, errors) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2.addError("passwords don't match."); - } - return errors; - }; - const formData = { pass1: 'a', pass2: 'b' }; - const result = validateFormData(formData, schema, validate); - errors = result.errors; - errorSchema = result.errorSchema; - }); - - it('should return an error list', () => { - expect(errors).toHaveLength(1); - expect(errors[0].stack).toBe("pass2: passwords don't match."); - }); - - it('should return an errorSchema', () => { - expect(errorSchema.pass2.__errors).toHaveLength(1); - expect(errorSchema.pass2.__errors[0]).toBe("passwords don't match."); - }); - }); - - describe('Data-Url validation', () => { - const schema = { - type: 'object', - properties: { - dataUrlWithName: { type: 'string', format: 'data-url' }, - dataUrlWithoutName: { type: 'string', format: 'data-url' }, - }, - }; - - it('Data-Url with name is accepted', () => { - const formData = { - dataUrlWithName: 'data:text/plain;name=file1.txt;base64,x=', - }; - const result = validateFormData(formData, schema); - expect(result.errors).toHaveLength(0); - }); - - it('Data-Url without name is accepted', () => { - const formData = { - dataUrlWithoutName: 'data:text/plain;base64,x=', - }; - const result = validateFormData(formData, schema); - expect(result.errors).toHaveLength(0); - }); - }); - - describe('Invalid schema', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - required: 'invalid_type_non_array', - }, - }, - }; - - let errors; - let errorSchema; - - beforeEach(() => { - const result = validateFormData({ foo: 42 }, schema); - errors = result.errors; - errorSchema = result.errorSchema; - }); - - it('should return an error list', () => { - expect(errors).toHaveLength(1); - expect(errors[0].name).toBe('type'); - expect(errors[0].property).toBe(".properties['foo'].required"); - expect(errors[0].schemaPath).toBe('#/definitions/stringArray/type'); // TODO: This schema path is wrong due to a bug in ajv; change this test when https://github.com/epoberezkin/ajv/issues/512 is fixed. - expect(errors[0].message).toBe('should be array'); - }); - - it('should return an errorSchema', () => { - expect(errorSchema.properties.foo.required.__errors).toHaveLength(1); - expect(errorSchema.properties.foo.required.__errors[0]).toBe('should be array'); - }); - }); - }); - - describe('toErrorList()', () => { - it('should convert an errorSchema into a flat list', () => { - expect( - toErrorList({ - __errors: ['err1', 'err2'], - a: { - b: { - __errors: ['err3', 'err4'], - }, - }, - c: { - __errors: ['err5'], - }, - }) - ).toStrictEqual([ - { stack: 'root: err1' }, - { stack: 'root: err2' }, - { stack: 'b: err3' }, - { stack: 'b: err4' }, - { stack: 'c: err5' }, - ]); - }); - }); - - describe('transformErrors', () => { - const illFormedKey = 'bar.\'"[]()=+*&^%$#@!'; - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - [illFormedKey]: { type: 'string' }, - }, - }; - const newErrorMessage = 'Better error message'; - const transformErrors = errors => { - return [{ ...errors[0], message: newErrorMessage }]; - }; - - let errors; - - beforeEach(() => { - const result = validateFormData({ foo: 42, [illFormedKey]: 41 }, schema, undefined, transformErrors); - errors = result.errors; - }); - - it('should use transformErrors function', () => { - expect(errors).not.toHaveLength(0); - expect(errors[0].message).toBe(newErrorMessage); - }); - }); - - describe('Form integration', () => { - describe('JSONSchema validation', () => { - describe('Required fields', () => { - const schema = { - type: 'object', - required: ['foo'], - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }; - - let onError; - let node; - - beforeEach(() => { - const compInfo = createFormComponent({ - schema, - formData: { - foo: undefined, - }, - }); - onError = compInfo.onError; - node = compInfo.node; - submitForm(node); - }); - - it('should trigger onError call', () => { - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'is a required property', - name: 'required', - params: { missingProperty: 'foo' }, - property: '.foo', - schemaPath: '#/required', - stack: '.foo is a required property', - }, - ]) - ); - }); - - it('should render errors', () => { - expect(node.querySelectorAll('.errors li')).toHaveLength(1); - expect(node.querySelector('.errors li')).toHaveTextContent('.foo is a required property'); - }); - }); - - describe('Min length', () => { - const schema = { - type: 'object', - required: ['foo'], - properties: { - foo: { - type: 'string', - minLength: 10, - }, - }, - }; - - let node; - let onError; - - beforeEach(() => { - onError = jest.fn(); - const compInfo = createFormComponent({ - schema, - formData: { - foo: '123456789', - }, - onError, - }); - node = compInfo.node; - - submitForm(node); - }); - - it('should render errors', () => { - expect(node.querySelectorAll('.errors li')).toHaveLength(1); - expect(node.querySelector('.errors li')).toHaveTextContent('.foo should NOT be shorter than 10 characters'); - }); - - it('should trigger the onError handler', () => { - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should NOT be shorter than 10 characters', - name: 'minLength', - params: { limit: 10 }, - property: '.foo', - schemaPath: '#/properties/foo/minLength', - stack: '.foo should NOT be shorter than 10 characters', - }, - ]) - ); - }); - }); - }); - - describe('Custom Form validation', () => { - it('should validate a simple string value', () => { - const schema = { type: 'string' }; - const formData = 'a'; - - function validate(formData, errors) { - if (formData !== 'hello') { - errors.addError('Invalid'); - } - return errors; - } - - const { onError, node } = createFormComponent({ - schema, - validate, - formData, - }); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith(expect.arrayContaining([{ stack: 'root: Invalid' }])); - }); - - it('should live validate a simple string value when liveValidate is set to true', () => { - const schema = { type: 'string' }; - const formData = 'a'; - - function validate(formData, errors) { - if (formData !== 'hello') { - errors.addError('Invalid'); - } - return errors; - } - - const { onChange, node } = createFormComponent({ - schema, - validate, - formData, - liveValidate: true, - }); - Simulate.change(node.querySelector('input'), { - target: { value: '1234' }, - }); - - expect(onChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - errorSchema: { __errors: ['Invalid'] }, - errors: [{ stack: 'root: Invalid' }], - formData: '1234', - }) - ); - }); - - it('should submit form on valid data', () => { - const schema = { type: 'string' }; - const formData = 'hello'; - const onSubmit = jest.fn(); - - function validate(formData, errors) { - if (formData !== 'hello') { - errors.addError('Invalid'); - } - return errors; - } - - const { node } = createFormComponent({ - schema, - formData, - validate, - onSubmit, - }); - - submitForm(node); - - expect(onSubmit).toHaveBeenCalled(); - }); - - it('should prevent form submission on invalid data', () => { - const schema = { type: 'string' }; - const formData = 'a'; - const onSubmit = jest.fn(); - const onError = jest.fn(); - - function validate(formData, errors) { - if (formData !== 'hello') { - errors.addError('Invalid'); - } - return errors; - } - - const { node } = createFormComponent({ - schema, - formData, - validate, - onSubmit, - onError, - }); - - submitForm(node); - - expect(onSubmit).not.toHaveBeenCalled(); - expect(onError).toHaveBeenCalled(); - }); - - it('should validate a simple object', () => { - const schema = { - type: 'object', - properties: { - pass1: { type: 'string', minLength: 3 }, - pass2: { type: 'string', minLength: 3 }, - }, - }; - - const formData = { pass1: 'aaa', pass2: 'b' }; - - function validate(formData, errors) { - const { pass1, pass2 } = formData; - if (pass1 !== pass2) { - errors.pass2.addError("Passwords don't match"); - } - return errors; - } - - const { node, onError } = createFormComponent({ - schema, - validate, - formData, - }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { stack: 'pass2: should NOT be shorter than 3 characters' }, - { stack: "pass2: Passwords don't match" }, - ]) - ); - }); - - it('should validate an array of object', () => { - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - pass1: { type: 'string' }, - pass2: { type: 'string' }, - }, - }, - }; - - const formData = [ - { pass1: 'a', pass2: 'b' }, - { pass1: 'a', pass2: 'a' }, - ]; - - function validate(formData, errors) { - formData.forEach(({ pass1, pass2 }, i) => { - if (pass1 !== pass2) { - errors[i].pass2.addError("Passwords don't match"); - } - }); - return errors; - } - - const { node, onError } = createFormComponent({ - schema, - validate, - formData, - }); - - submitForm(node); - expect(onError).toHaveBeenLastCalledWith(expect.arrayContaining([{ stack: "pass2: Passwords don't match" }])); - }); - - it('should validate a simple array', () => { - const schema = { - type: 'array', - items: { - type: 'string', - }, - }; - - const formData = ['aaa', 'bbb', 'ccc']; - - function validate(formData, errors) { - if (formData.indexOf('bbb') !== -1) { - errors.addError('Forbidden value: bbb'); - } - return errors; - } - - const { node, onError } = createFormComponent({ - schema, - validate, - formData, - }); - submitForm(node); - expect(onError).toHaveBeenLastCalledWith(expect.arrayContaining([{ stack: 'root: Forbidden value: bbb' }])); - }); - }); - - describe('showErrorList prop validation', () => { - describe('Required fields', () => { - const schema = { - type: 'object', - required: ['foo'], - properties: { - foo: { type: 'string' }, - bar: { type: 'string' }, - }, - }; - - let node; - let onError; - - beforeEach(() => { - const compInfo = createFormComponent({ - schema, - formData: { - foo: undefined, - }, - showErrorList: false, - }); - node = compInfo.node; - onError = compInfo.onError; - - submitForm(node); - }); - - it('should not render error list if showErrorList prop true', () => { - expect(node.querySelectorAll('.errors li')).toHaveLength(0); - }); - - it('should trigger onError call', () => { - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'is a required property', - name: 'required', - params: { missingProperty: 'foo' }, - property: '.foo', - schemaPath: '#/required', - stack: '.foo is a required property', - }, - ]) - ); - }); - }); - }); - - describe('Custom ErrorList', () => { - const schema = { - type: 'string', - minLength: 1, - }; - - const uiSchema = { - foo: 'bar', - }; - - const formData = 0; - - const CustomErrorList = ({ errors, errorSchema, schema, uiSchema, formContext: { className } }) => ( -
-
{errors.length} custom
-
{errorSchema.__errors[0]}
-
{schema.type}
-
{uiSchema.foo}
-
-
- ); - - it('should use CustomErrorList', () => { - const { node } = createFormComponent({ - schema, - uiSchema, - liveValidate: true, - formData, - ErrorList: CustomErrorList, - formContext: { className: 'foo' }, - }); - expect(node.querySelectorAll('.CustomErrorList')).toHaveLength(1); - expect(node.querySelector('.CustomErrorList')).toHaveTextContent('1 custom'); - expect(node.querySelectorAll('.ErrorSchema')).toHaveLength(1); - expect(node.querySelector('.ErrorSchema')).toHaveTextContent('should be string'); - expect(node.querySelectorAll('.Schema')).toHaveLength(1); - expect(node.querySelector('.Schema')).toHaveTextContent('string'); - expect(node.querySelectorAll('.UiSchema')).toHaveLength(1); - expect(node.querySelector('.UiSchema')).toHaveTextContent('bar'); - expect(node.querySelectorAll('.foo')).toHaveLength(1); - }); - }); - - describe('Custom meta schema', () => { - let onError; - let node; - const formData = { - datasetId: 'no err', - }; - - const schema = { - $ref: '#/definitions/Dataset', - $schema: 'http://json-schema.org/draft-04/schema#', - definitions: { - Dataset: { - properties: { - datasetId: { - pattern: '\\d+', - type: 'string', - }, - }, - required: ['datasetId'], - type: 'object', - }, - }, - }; - - beforeEach(() => { - const withMetaSchema = createFormComponent({ - schema, - formData, - liveValidate: true, - additionalMetaSchemas: [require('ajv/lib/refs/json-schema-draft-04.json')], - }); - node = withMetaSchema.node; - onError = withMetaSchema.onError; - submitForm(node); - }); - - it('should be used to validate schema', () => { - expect(node.querySelectorAll('.errors li')).toHaveLength(1); - expect(onError).toHaveBeenLastCalledWith( - expect.arrayContaining([ - { - message: 'should match pattern "\\d+"', - name: 'pattern', - params: { pattern: '\\d+' }, - property: '.datasetId', - schemaPath: '#/properties/datasetId/pattern', - stack: '.datasetId should match pattern "\\d+"', - }, - ]) - ); - onError.mockClear(); - - Simulate.change(node.querySelector('input'), { - target: { value: '1234' }, - }); - expect(node.querySelectorAll('.errors li')).toHaveLength(0); - expect(onError).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/packages/oas-form/__tests__/withTheme_test.js b/packages/oas-form/__tests__/withTheme_test.js deleted file mode 100644 index 0d67e4c2f..000000000 --- a/packages/oas-form/__tests__/withTheme_test.js +++ /dev/null @@ -1,242 +0,0 @@ -import React, { Component, createRef } from 'react'; - -import { withTheme } from '../src'; -import { createComponent } from './test_utils'; - -const WrapperClassComponent = (...args) => { - return class extends Component { - render() { - const Cmp = withTheme(...args); - return ; - } - }; -}; - -describe('withTheme', () => { - describe('With fields', () => { - it('should use the withTheme field', () => { - const fields = { - StringField() { - return
; - }, - }; - const schema = { - type: 'object', - properties: { - fieldA: { - type: 'string', - }, - fieldB: { - type: 'string', - }, - }, - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ fields }), { - schema, - uiSchema, - }); - expect(node.querySelectorAll('.string-field')).toHaveLength(2); - }); - - it('should use withTheme field and the user defined field', () => { - const themeFields = { - StringField() { - return
; - }, - }; - const userFields = { - NumberField() { - return
; - }, - }; - const schema = { - type: 'object', - properties: { - fieldA: { - type: 'string', - }, - fieldB: { - type: 'number', - }, - }, - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ fields: themeFields }), { - schema, - uiSchema, - fields: userFields, - }); - expect(node.querySelectorAll('.string-field')).toHaveLength(1); - expect(node.querySelectorAll('.number-field')).toHaveLength(1); - }); - - it('should use only the user defined field', () => { - const themeFields = { - StringField() { - return
; - }, - }; - const userFields = { - StringField() { - return
; - }, - }; - const schema = { - type: 'object', - properties: { - fieldA: { - type: 'string', - }, - fieldB: { - type: 'string', - }, - }, - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ fields: themeFields }), { - schema, - uiSchema, - fields: userFields, - }); - expect(node.querySelectorAll('.string-field')).toHaveLength(0); - expect(node.querySelectorAll('.form-control')).toHaveLength(2); - }); - }); - - describe('With widgets', () => { - it('should use the withTheme widget', () => { - const widgets = { - TextWidget: () =>
, - }; - const schema = { - type: 'string', - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ widgets }), { - schema, - uiSchema, - }); - expect(node.querySelectorAll('#test')).toHaveLength(1); - }); - - it('should use the withTheme widget as well as user defined widget', () => { - const themeWidgets = { - TextWidget: () =>
, - }; - const userWidgets = { - DateWidget: () =>
, - }; - const schema = { - type: 'object', - properties: { - fieldA: { - type: 'string', - }, - fieldB: { - format: 'date', - type: 'string', - }, - }, - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ widgets: themeWidgets }), { - schema, - uiSchema, - widgets: userWidgets, - }); - expect(node.querySelectorAll('#test-theme-widget')).toHaveLength(1); - expect(node.querySelectorAll('#test-user-widget')).toHaveLength(1); - }); - - it('should use only the user defined widget', () => { - const themeWidgets = { - TextWidget: () =>
, - }; - const userWidgets = { - TextWidget: () =>
, - }; - const schema = { - type: 'object', - properties: { - fieldA: { - type: 'string', - }, - }, - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ widgets: themeWidgets }), { - schema, - uiSchema, - widgets: userWidgets, - }); - expect(node.querySelectorAll('#test-theme-widget')).toHaveLength(0); - expect(node.querySelectorAll('#test-user-widget')).toHaveLength(1); - }); - }); - - describe('With templates', () => { - it('should use the withTheme template', () => { - const themeTemplates = { - FieldTemplate() { - return
; - }, - }; - const schema = { - type: 'object', - properties: { - fieldA: { - type: 'string', - }, - fieldB: { - type: 'string', - }, - }, - }; - const uiSchema = {}; - const { node } = createComponent(WrapperClassComponent({ ...themeTemplates }), { - schema, - uiSchema, - }); - expect(node.querySelectorAll('.with-theme-field-template')).toHaveLength(1); - }); - - it('should use only the user defined template', () => { - const themeTemplates = { - FieldTemplate() { - return
; - }, - }; - const userTemplates = { - FieldTemplate() { - return
; - }, - }; - - const schema = { - type: 'object', - properties: { foo: { type: 'string' }, bar: { type: 'string' } }, - }; - const { node } = createComponent(WrapperClassComponent({ ...themeTemplates }), { - schema, - ...userTemplates, - }); - expect(node.querySelectorAll('.with-theme-field-template')).toHaveLength(0); - expect(node.querySelectorAll('.user-field-template')).toHaveLength(1); - }); - - it('should forward the ref', () => { - const ref = createRef(); - const schema = {}; - const uiSchema = {}; - - createComponent(withTheme({}), { - schema, - uiSchema, - ref, - }); - - expect(ref.current.submit).not.toBeUndefined(); - }); - }); -}); diff --git a/packages/oas-form/package-lock.json b/packages/oas-form/package-lock.json index 124cebddc..737a97bfc 100644 --- a/packages/oas-form/package-lock.json +++ b/packages/oas-form/package-lock.json @@ -352,48 +352,6 @@ } } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz", - "integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.4", - "@babel/helper-split-export-declaration": "^7.4.4" - }, - "dependencies": { - "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4" - } - }, - "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, "@babel/helper-create-regexp-features-plugin": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", @@ -566,26 +524,6 @@ } } }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, "@babel/helper-hoist-variables": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", @@ -620,15 +558,6 @@ } } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", - "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, "@babel/helper-module-imports": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", @@ -801,15 +730,6 @@ } } }, - "@babel/helper-optimise-call-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, "@babel/helper-plugin-utils": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", @@ -852,103 +772,6 @@ } } }, - "@babel/helper-replace-supers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", - "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" - }, - "dependencies": { - "@babel/generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", - "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4", - "jsesc": "^2.5.1", - "lodash": "^4.17.11", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4" - } - }, - "@babel/parser": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", - "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", - "dev": true - }, - "@babel/traverse": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", - "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.4.5", - "@babel/types": "^7.4.4", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" - } - }, - "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "@babel/helper-simple-access": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", @@ -1367,16 +1190,6 @@ } } }, - "@babel/plugin-proposal-class-properties": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz", - "integrity": "sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, "@babel/plugin-proposal-dynamic-import": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", @@ -2944,6 +2757,112 @@ } } }, + "@babel/plugin-transform-react-display-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz", + "integrity": "sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.16.tgz", + "integrity": "sha512-dNu0vAbIk8OkqJfGtYF6ADk6jagoyAl+Ks5aoltbAlfoKv8d6yooi3j+kObeSQaCj9PgN6KMZPB90wWyek5TmQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", + "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-module-imports": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", + "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.16.tgz", + "integrity": "sha512-GOp5SkMC4zhHwLbOSYhF+WpIZSf5bGzaKQTT9jWkemJRDM/CE6FtPydXjEYO3pHcna2Zjvg4mQ1lfjOR/4jsaQ==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.12.16" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", + "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } + } + }, "@babel/plugin-transform-regenerator": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", @@ -3433,6 +3352,27 @@ } } }, + "@babel/preset-react": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.13.tgz", + "integrity": "sha512-TYM0V9z6Abb6dj1K7i5NrEhA13oS5ujUYQYDfqIBXYHOc2c2VkFgc+q9kyssIyUfy4/hEwqrgSlJ/Qgv8zJLsA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-transform-react-display-name": "^7.12.13", + "@babel/plugin-transform-react-jsx": "^7.12.13", + "@babel/plugin-transform-react-jsx-development": "^7.12.12", + "@babel/plugin-transform-react-pure-annotations": "^7.12.1" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } + } + }, "@babel/runtime": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", @@ -3442,15 +3382,14 @@ "regenerator-runtime": "^0.13.4" } }, - "@babel/template": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", - "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "@babel/runtime-corejs3": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz", + "integrity": "sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.2.2", - "@babel/types": "^7.2.2" + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" } }, "@babel/traverse": { @@ -4321,19 +4260,10 @@ } } }, - "@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -4452,6 +4382,110 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "@typescript-eslint/experimental-utils": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz", + "integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.15.1", + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/typescript-estree": "4.15.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz", + "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/visitor-keys": "4.15.1" + } + }, + "@typescript-eslint/types": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz", + "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", + "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/visitor-keys": "4.15.1", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz", + "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz", @@ -4687,6 +4721,24 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, "alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", @@ -4870,6 +4922,18 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, + "axe-core": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.2.tgz", + "integrity": "sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg==", + "dev": true + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, "babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", @@ -7337,6 +7401,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -12272,6 +12342,12 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "queue-microtask": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", + "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==", + "dev": true + }, "randombytes": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", @@ -12689,6 +12765,15 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -13772,11 +13857,40 @@ } } }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } }, "tslib": { "version": "1.9.3", @@ -13841,6 +13955,12 @@ "is-typedarray": "^1.0.0" } }, + "ua-parser-js": { + "version": "0.7.24", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.24.tgz", + "integrity": "sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw==", + "dev": true + }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", diff --git a/packages/oas-form/package.json b/packages/oas-form/package.json index a734abdb4..18a686a0c 100644 --- a/packages/oas-form/package.json +++ b/packages/oas-form/package.json @@ -18,9 +18,7 @@ "test": "__tests__" }, "scripts": { - "build": "npm run dist", - "build:lib": "rimraf lib && NODE_ENV=production babel -d lib/ src/", - "build:dist": "rimraf dist && NODE_ENV=production webpack --config webpack.config.dist.js", + "build": "NODE_ENV=production webpack", "dist": "npm run build:lib && npm run build:dist", "lint": "eslint . --ext js --ext jsx", "prettier": "prettier --list-different --write \"./**/**.{js,jsx}\"", @@ -30,12 +28,8 @@ "test": "jest --coverage" }, "dependencies": { - "@babel/runtime-corejs2": "^7.4.5", - "ajv": "^6.7.0", - "core-js": "^2.5.7", "json-schema-merge-allof": "^0.6.0", "jsonpointer": "^4.0.1", - "lodash": "^4.17.15", "nanoid": "^3.1.20", "prop-types": "^15.7.2" }, @@ -44,15 +38,9 @@ "react-is": "16.x" }, "devDependencies": { - "@babel/cli": "^7.4.4", "@babel/core": "^7.4.5", - "@babel/plugin-proposal-class-properties": "^7.4.4", - "@babel/plugin-proposal-object-rest-spread": "^7.4.4", - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@babel/plugin-transform-runtime": "^7.4.4", "@babel/preset-env": "^7.4.5", "@babel/preset-react": "^7.0.0", - "@babel/register": "^7.4.4", "@readme/eslint-config": "^4.0.0", "@testing-library/jest-dom": "^5.11.9", "babel-eslint": "^10.1.0", @@ -65,9 +53,7 @@ "prettier": "^2.1.2", "react": "^16.14.0", "react-dom": "^16.14.0", - "react-is": "^16.13.1", "react-portal": "^4.2.0", - "rimraf": "^2.5.4", "style-loader": "^0.13.1", "webpack": "^4.20.2", "webpack-cli": "^3.1.2" diff --git a/packages/oas-form/src/components/ErrorList.js b/packages/oas-form/src/components/ErrorList.js deleted file mode 100644 index 1e3567222..000000000 --- a/packages/oas-form/src/components/ErrorList.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -export default function ErrorList(props) { - const { errors } = props; - return ( -
-
-

Errors

-
-
    - {errors.map((error, i) => { - return ( -
  • - {error.stack} -
  • - ); - })} -
-
- ); -} diff --git a/packages/oas-form/src/components/Form.js b/packages/oas-form/src/components/Form.js index 3563b1b10..c3097605b 100644 --- a/packages/oas-form/src/components/Form.js +++ b/packages/oas-form/src/components/Form.js @@ -1,10 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import _pick from 'lodash/pick'; -import _get from 'lodash/get'; -import _isEmpty from 'lodash/isEmpty'; -import DefaultErrorList from './ErrorList'; import { getDefaultFormState, retrieveSchema, @@ -12,20 +8,12 @@ import { toIdSchema, getDefaultRegistry, deepEquals, - toPathSchema, isObject, - mergeObjects, } from '../utils'; -import validateFormData, { toErrorList } from '../validate'; export default class Form extends Component { static defaultProps = { disabled: false, - ErrorList: DefaultErrorList, - liveValidate: false, - noHtml5Validate: false, - noValidate: false, - omitExtraData: false, uiSchema: {}, }; @@ -51,27 +39,12 @@ export default class Form extends Component { } getStateFromProps(props, inputFormData) { - const state = this.state || {}; const schema = 'schema' in props ? props.schema : this.props.schema; const uiSchema = 'uiSchema' in props ? props.uiSchema : this.props.uiSchema; const edit = typeof inputFormData !== 'undefined'; - const liveValidate = props.liveValidate || this.props.liveValidate; - const mustValidate = edit && !props.noValidate && liveValidate; const rootSchema = schema; const formData = getDefaultFormState(schema, inputFormData, rootSchema); const retrievedSchema = retrieveSchema(schema, rootSchema, formData); - const customFormats = props.customFormats; - const additionalMetaSchemas = props.additionalMetaSchemas; - let { errors, errorSchema } = mustValidate - ? this.validate(formData, schema, additionalMetaSchemas, customFormats) - : { - errors: state.errors || [], - errorSchema: state.errorSchema || {}, - }; - if (props.extraErrors) { - errorSchema = mergeObjects(errorSchema, props.extraErrors); - errors = toErrorList(errorSchema); - } const idSchema = toIdSchema(retrievedSchema, uiSchema['ui:rootFieldId'], rootSchema, formData, props.idPrefix); return { schema, @@ -79,9 +52,6 @@ export default class Form extends Component { idSchema, formData, edit, - errors, - errorSchema, - additionalMetaSchemas, }; } @@ -89,112 +59,14 @@ export default class Form extends Component { return shouldRender(this, nextProps, nextState); } - validate( - formData, - schema = this.props.schema, - additionalMetaSchemas = this.props.additionalMetaSchemas, - customFormats = this.props.customFormats - ) { - const { validate, transformErrors } = this.props; - const { rootSchema } = this.getRegistry(); - const resolvedSchema = retrieveSchema(schema, rootSchema, formData); - return validateFormData(formData, resolvedSchema, validate, transformErrors, additionalMetaSchemas, customFormats); - } - - renderErrors() { - const { errors, errorSchema, schema, uiSchema } = this.state; - const { ErrorList, showErrorList, formContext } = this.props; - - if (errors.length && showErrorList !== false) { - return ( - - ); - } - return null; - } - - getUsedFormData = (formData, fields) => { - // for the case of a single input form - if (fields.length === 0 && typeof formData !== 'object') { - return formData; - } - - const data = _pick(formData, fields); - if (Array.isArray(formData)) { - return Object.keys(data).map(key => data[key]); - } - - return data; - }; - - getFieldNames = (pathSchema, formData) => { - const getAllPaths = (_obj, acc = [], paths = ['']) => { - Object.keys(_obj).forEach(key => { - if (typeof _obj[key] === 'object') { - const newPaths = paths.map(path => `${path}.${key}`); - getAllPaths(_obj[key], acc, newPaths); - } else if (key === '$name' && _obj[key] !== '') { - paths.forEach(path => { - path = path.replace(/^\./, ''); - const formValue = _get(formData, path); - // adds path to fieldNames if it points to a value - // or an empty object/array - if (typeof formValue !== 'object' || _isEmpty(formValue)) { - acc.push(path); - } - }); - } - }); - return acc; - }; - - return getAllPaths(pathSchema); - }; - - onChange = (formData, newErrorSchema) => { + onChange = formData => { if (isObject(formData) || Array.isArray(formData)) { const newState = this.getStateFromProps(this.props, formData); formData = newState.formData; } - const mustValidate = !this.props.noValidate && this.props.liveValidate; - let state = { formData }; - let newFormData = formData; - if (this.props.omitExtraData === true && this.props.liveOmit === true) { - const retrievedSchema = retrieveSchema(this.state.schema, this.state.schema, formData); - const pathSchema = toPathSchema(retrievedSchema, '', this.state.schema, formData); + const state = { formData }; - const fieldNames = this.getFieldNames(pathSchema, formData); - - newFormData = this.getUsedFormData(formData, fieldNames); - state = { - formData: newFormData, - }; - } - - if (mustValidate) { - let { errors, errorSchema } = this.validate(newFormData); - if (this.props.extraErrors) { - errorSchema = mergeObjects(errorSchema, this.props.extraErrors); - errors = toErrorList(errorSchema); - } - state = { formData: newFormData, errors, errorSchema }; - } else if (!this.props.noValidate && newErrorSchema) { - const errorSchema = this.props.extraErrors - ? mergeObjects(newErrorSchema, this.props.extraErrors) - : newErrorSchema; - state = { - formData: newFormData, - errorSchema, - errors: toErrorList(errorSchema), - }; - } this.setState(state, () => this.props.onChange && this.props.onChange(state)); }; @@ -217,46 +89,9 @@ export default class Form extends Component { } event.persist(); - let newFormData = this.state.formData; - - if (this.props.omitExtraData === true) { - const retrievedSchema = retrieveSchema(this.state.schema, this.state.schema, newFormData); - const pathSchema = toPathSchema(retrievedSchema, '', this.state.schema, newFormData); + const newFormData = this.state.formData; - const fieldNames = this.getFieldNames(pathSchema, newFormData); - - newFormData = this.getUsedFormData(newFormData, fieldNames); - } - - if (!this.props.noValidate) { - let { errors, errorSchema } = this.validate(newFormData); - if (Object.keys(errors).length > 0) { - if (this.props.extraErrors) { - errorSchema = mergeObjects(errorSchema, this.props.extraErrors); - errors = toErrorList(errorSchema); - } - this.setState({ errors, errorSchema }, () => { - if (this.props.onError) { - this.props.onError(errors); - } else { - console.error('Form validation failed', errors); - } - }); - return; - } - } - - let errorSchema; - let errors; - if (this.props.extraErrors) { - errorSchema = this.props.extraErrors; - errors = toErrorList(errorSchema); - } else { - errorSchema = {}; - errors = []; - } - - this.setState({ formData: newFormData, errors, errorSchema }, () => { + this.setState({ formData: newFormData }, () => { if (this.props.onSubmit) { this.props.onSubmit({ ...this.state, formData: newFormData, status: 'submitted' }, event); } @@ -300,23 +135,17 @@ export default class Form extends Component { method, target, action, - autocomplete: deprecatedAutocomplete, - autoComplete: currentAutoComplete, + autoComplete, enctype, acceptcharset, - noHtml5Validate, disabled, formContext, } = this.props; - const { schema, uiSchema, formData, errorSchema, idSchema } = this.state; + const { schema, uiSchema, formData, idSchema } = this.state; const registry = this.getRegistry(); const _SchemaField = registry.fields.SchemaField; const FormTag = tagName || 'form'; - if (deprecatedAutocomplete) { - console.warn('Using autocomplete property of Form is deprecated, use autoComplete instead.'); - } - const autoComplete = currentAutoComplete || deprecatedAutocomplete; return ( - {this.renderErrors()} <_SchemaField disabled={disabled} - errorSchema={errorSchema} formContext={formContext} formData={formData} idPrefix={idPrefix} @@ -366,37 +192,24 @@ if (process.env.NODE_ENV !== 'production') { Form.propTypes = { acceptcharset: PropTypes.string, action: PropTypes.string, - additionalMetaSchemas: PropTypes.arrayOf(PropTypes.object), ArrayFieldTemplate: PropTypes.elementType, - autocomplete: PropTypes.string, autoComplete: PropTypes.string, className: PropTypes.string, - customFormats: PropTypes.object, enctype: PropTypes.string, - ErrorList: PropTypes.func, - extraErrors: PropTypes.object, fields: PropTypes.objectOf(PropTypes.elementType), FieldTemplate: PropTypes.elementType, formContext: PropTypes.object, formData: PropTypes.any, id: PropTypes.string, - liveValidate: PropTypes.bool, method: PropTypes.string, name: PropTypes.string, - noHtml5Validate: PropTypes.bool, - noValidate: PropTypes.bool, ObjectFieldTemplate: PropTypes.elementType, - omitExtraData: PropTypes.bool, onChange: PropTypes.func, - onError: PropTypes.func, onSubmit: PropTypes.func, schema: PropTypes.object.isRequired, - showErrorList: PropTypes.bool, tagName: PropTypes.elementType, target: PropTypes.string, - transformErrors: PropTypes.func, uiSchema: PropTypes.object, - validate: PropTypes.func, widgets: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])), }; } diff --git a/packages/oas-form/src/components/fields/ArrayField.js b/packages/oas-form/src/components/fields/ArrayField.js index 905af8857..92c23abd9 100644 --- a/packages/oas-form/src/components/fields/ArrayField.js +++ b/packages/oas-form/src/components/fields/ArrayField.js @@ -1,7 +1,6 @@ import AddButton from '../AddButton'; import IconButton from '../IconButton'; import React, { Component } from 'react'; -import includes from 'core-js/library/fn/array/includes'; import * as types from '../../types'; import { @@ -205,7 +204,7 @@ class ArrayField extends Component { if (Array.isArray(itemSchema.type)) { // While we don't yet support composite/nullable jsonschema types, it's // future-proof to check for requirement against these. - return !includes(itemSchema.type, 'null'); + return !itemSchema.type.includes('null'); } // All non-null array item types are inherently required by design return itemSchema.type !== 'null'; @@ -284,34 +283,20 @@ class ArrayField extends Component { } const { onChange } = this.props; const { keyedFormData } = this.state; - // refs #195: revalidate to ensure properly reindexing errors - let newErrorSchema; - if (this.props.errorSchema) { - newErrorSchema = {}; - const errorSchema = this.props.errorSchema; - for (let i in errorSchema) { - // eslint-disable-next-line radix - i = parseInt(i); - if (i < index) { - newErrorSchema[i] = errorSchema[i]; - } else if (i > index) { - newErrorSchema[i - 1] = errorSchema[i]; - } - } - } + const newKeyedFormData = keyedFormData.filter((_, i) => i !== index); this.setState( { keyedFormData: newKeyedFormData, updatedKeyedFormData: true, }, - () => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema) + () => onChange(keyedToPlainFormData(newKeyedFormData)) ); }; }; onChangeForIndex = index => { - return (value, errorSchema) => { + return value => { const { formData, onChange } = this.props; const newFormData = formData.map((item, i) => { // We need to treat undefined items as nulls to have validation. @@ -319,14 +304,7 @@ class ArrayField extends Component { const jsonValue = typeof value === 'undefined' ? null : value; return index === i ? jsonValue : item; }); - onChange( - newFormData, - errorSchema && - this.props.errorSchema && { - ...this.props.errorSchema, - [index]: errorSchema, - } - ); + onChange(newFormData); }; }; @@ -359,7 +337,6 @@ class ArrayField extends Component { const { schema, uiSchema, - errorSchema, idSchema, name, required, @@ -370,7 +347,6 @@ class ArrayField extends Component { onBlur, onFocus, idPrefix, - rawErrors, } = this.props; const title = schema.title === undefined ? name : schema.title; const { ArrayFieldTemplate, rootSchema, fields, formContext } = registry; @@ -382,7 +358,6 @@ class ArrayField extends Component { items: this.state.keyedFormData.map((keyedItem, index) => { const { key, item } = keyedItem; const itemSchema = retrieveSchema(schema.items, rootSchema, item); - const itemErrorSchema = errorSchema ? errorSchema[index] : undefined; const itemIdPrefix = `${idSchema.$id}_${index}`; const itemIdSchema = toIdSchema(itemSchema, itemIdPrefix, rootSchema, item, idPrefix); return this.renderArrayFieldItem({ @@ -390,7 +365,6 @@ class ArrayField extends Component { index, itemSchema, itemIdSchema, - itemErrorSchema, itemData: item, itemUiSchema: uiSchema.items, autofocus: autofocus && index === 0, @@ -411,7 +385,6 @@ class ArrayField extends Component { TitleField, formContext, formData, - rawErrors, registry, }; @@ -435,7 +408,6 @@ class ArrayField extends Component { onBlur, onFocus, registry = getDefaultRegistry(), - rawErrors, } = this.props; const items = this.props.formData; const { widgets, rootSchema, formContext } = registry; @@ -459,7 +431,6 @@ class ArrayField extends Component { onFocus={onFocus} options={options} placeholder={placeholder} - rawErrors={rawErrors} readonly={readonly} registry={registry} required={required} @@ -481,7 +452,6 @@ class ArrayField extends Component { onBlur, onFocus, registry = getDefaultRegistry(), - rawErrors, } = this.props; const title = schema.title || name; const items = this.props.formData; @@ -499,7 +469,6 @@ class ArrayField extends Component { onChange={this.onSelectChange} onFocus={onFocus} options={options} - rawErrors={rawErrors} readonly={readonly} schema={schema} title={title} @@ -513,7 +482,6 @@ class ArrayField extends Component { schema, uiSchema, formData, - errorSchema, idPrefix, idSchema, name, @@ -524,7 +492,6 @@ class ArrayField extends Component { registry = getDefaultRegistry(), onBlur, onFocus, - rawErrors, } = this.props; const title = schema.title || name; let items = this.props.formData; @@ -559,7 +526,6 @@ class ArrayField extends Component { : Array.isArray(uiSchema.items) // eslint-disable-line unicorn/no-nested-ternary ? uiSchema.items[index] : uiSchema.items || {}; - const itemErrorSchema = errorSchema ? errorSchema[index] : undefined; return this.renderArrayFieldItem({ key, @@ -569,7 +535,6 @@ class ArrayField extends Component { itemData: item, itemUiSchema, itemIdSchema, - itemErrorSchema, autofocus: autofocus && index === 0, onBlur, onFocus, @@ -583,7 +548,6 @@ class ArrayField extends Component { title, TitleField, formContext, - rawErrors, }; // Check if a custom template template was passed in @@ -593,19 +557,7 @@ class ArrayField extends Component { renderArrayFieldItem(props) { let { itemSchema } = props; - const { - key, - index, - canRemove = true, - itemData, - itemUiSchema, - itemIdSchema, - itemErrorSchema, - autofocus, - onBlur, - onFocus, - rawErrors, - } = props; + const { key, index, canRemove = true, itemData, itemUiSchema, itemIdSchema, autofocus, onBlur, onFocus } = props; const { disabled, readonly, uiSchema, registry = getDefaultRegistry() } = this.props; const { fields: { SchemaField }, @@ -632,14 +584,12 @@ class ArrayField extends Component { { const selectedOption = parseInt(option, 10); const { formData, onChange, options, registry } = this.props; @@ -90,7 +50,6 @@ class AnyOfField extends Component { const { baseType, disabled, - errorSchema, formData, idPrefix, idSchema, @@ -141,7 +100,6 @@ class AnyOfField extends Component { {option !== null && ( <_SchemaField disabled={disabled} - errorSchema={errorSchema} formData={formData} idPrefix={idPrefix} idSchema={idSchema} @@ -160,7 +118,6 @@ class AnyOfField extends Component { AnyOfField.defaultProps = { disabled: false, - errorSchema: {}, idSchema: {}, uiSchema: {}, }; @@ -168,7 +125,6 @@ AnyOfField.defaultProps = { if (process.env.NODE_ENV !== 'production') { AnyOfField.propTypes = { baseType: PropTypes.string, - errorSchema: PropTypes.object, formData: PropTypes.any, idSchema: PropTypes.object, options: PropTypes.arrayOf(PropTypes.object).isRequired, diff --git a/packages/oas-form/src/components/fields/ObjectField.js b/packages/oas-form/src/components/fields/ObjectField.js index fd80b46b4..de009dc39 100644 --- a/packages/oas-form/src/components/fields/ObjectField.js +++ b/packages/oas-form/src/components/fields/ObjectField.js @@ -2,13 +2,7 @@ import AddButton from '../AddButton'; import React, { Component } from 'react'; import * as types from '../../types'; -import { - orderProperties, - retrieveSchema, - getDefaultRegistry, - getUiOptions, - ADDITIONAL_PROPERTY_FLAG, -} from '../../utils'; +import { retrieveSchema, getDefaultRegistry, getUiOptions, ADDITIONAL_PROPERTY_FLAG } from '../../utils'; function DefaultObjectFieldTemplate(props) { const canExpand = function canExpand() { @@ -61,7 +55,6 @@ function DefaultObjectFieldTemplate(props) { class ObjectField extends Component { static defaultProps = { disabled: false, - errorSchema: {}, formData: {}, idSchema: {}, readonly: false, @@ -80,7 +73,7 @@ class ObjectField extends Component { } onPropertyChange = (name, addedByAdditionalProperties = false) => { - return (value, errorSchema) => { + return value => { if (!value && addedByAdditionalProperties) { // Don't set value = undefined for fields added by // additionalProperties. Doing so removes them from the @@ -92,14 +85,7 @@ class ObjectField extends Component { value = ''; } const newFormData = { ...this.props.formData, [name]: value }; - this.props.onChange( - newFormData, - errorSchema && - this.props.errorSchema && { - ...this.props.errorSchema, - [name]: errorSchema, - } - ); + this.props.onChange(newFormData); }; }; @@ -123,7 +109,7 @@ class ObjectField extends Component { }; onKeyChange = oldValue => { - return (value, errorSchema) => { + return value => { if (oldValue === value) { return; } @@ -139,14 +125,7 @@ class ObjectField extends Component { this.setState({ wasPropertyKeyModified: true }); - this.props.onChange( - renamedObj, - errorSchema && - this.props.errorSchema && { - ...this.props.errorSchema, - [value]: errorSchema, - } - ); + this.props.onChange(renamedObj); }; }; @@ -194,7 +173,6 @@ class ObjectField extends Component { const { uiSchema, formData, - errorSchema, idSchema, name, required, @@ -219,21 +197,7 @@ class ObjectField extends Component { } const description = uiSchema['ui:description'] || schema.description; - let orderedProperties; - try { - const properties = Object.keys(schema.properties || {}); - orderedProperties = orderProperties(properties, uiSchema['ui:order']); - } catch (err) { - return ( -
-

- Invalid {name || 'root'} object field configuration: - {err.message}. -

-
{JSON.stringify(schema)}
-
- ); - } + const properties = Object.keys(schema.properties || {}); const Template = uiSchema['ui:ObjectFieldTemplate'] || registry.ObjectFieldTemplate || DefaultObjectFieldTemplate; @@ -242,14 +206,13 @@ class ObjectField extends Component { description, TitleField, DescriptionField, - properties: orderedProperties.map(prop => { + properties: properties.map(prop => { const addedByAdditionalProperties = schema.properties[prop].hasOwnProperty(ADDITIONAL_PROPERTY_FLAG); return { content: ( {help}
; } -function ErrorList(props) { - const { errors = [] } = props; - if (errors.length === 0) { - return null; - } - - return ( -
-
    - {errors - .filter(elem => !!elem) - .map((error, index) => { - return ( -
  • - {error} -
  • - ); - })} -
-
- ); -} function DefaultTemplate(props) { - const { id, label, children, errors, help, description, hidden, required, displayLabel } = props; + const { id, label, children, help, description, hidden, required, displayLabel } = props; if (hidden) { return
{children}
; } @@ -124,7 +102,6 @@ function DefaultTemplate(props) { {displayLabel &&