From 7d1484ada0e3deee8da92e474215a3bc9676baa5 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 7 Jun 2020 21:44:25 +1000 Subject: [PATCH 01/11] Update package-lock --- package-lock.json | 438 +++++++++++++++++++++++++--------------------- 1 file changed, 234 insertions(+), 204 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3426ad7..b0564854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1429,21 +1429,21 @@ "dev": true }, "@graphql-tools/code-file-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.0.7.tgz", - "integrity": "sha512-KLmOaWiNxi49z5962Xi3/VNYcSkA4M0L3RJJ3p+uTEifGIHyTUbADjRrygXko7cfULGzyOPP5L2Bn6leYGg4vw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.0.8.tgz", + "integrity": "sha512-SsSZluxk6jMa3zPjLWzo+umbTFyyTNNapp0rwiFJsWwb4J+1EUAd3NBlhjxjzP2YfZaD9ijuFRU5H0HthfRKCA==", "dev": true, "requires": { - "@graphql-tools/graphql-tag-pluck": "6.0.7", - "@graphql-tools/utils": "6.0.7", - "fs-extra": "9.0.0", + "@graphql-tools/graphql-tag-pluck": "6.0.8", + "@graphql-tools/utils": "6.0.8", + "fs-extra": "9.0.1", "tslib": "~2.0.0" }, "dependencies": { "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -1477,13 +1477,13 @@ } }, "@graphql-tools/delegate": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.7.tgz", - "integrity": "sha512-s2JLxpDD0AXif4yvSdy9W18UEutliO4YzrAOzNL8B1BS6kiIT7BSZv0Eyu7XTuL2fIg4Ln8z7WZclj2bYiK8mw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.8.tgz", + "integrity": "sha512-XhvH30RpGLjft3cYhB99XJK8kDSaYwpXOiIp51mQzRber6ws/4gNsOQlcpMfmIz+y5WBciKLnzJa8IV3kA3J2A==", "dev": true, "requires": { - "@graphql-tools/schema": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/schema": "6.0.8", + "@graphql-tools/utils": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -1496,24 +1496,24 @@ } }, "@graphql-tools/git-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-6.0.7.tgz", - "integrity": "sha512-FdbW07zV4sbruoapSfP/bCw6SB8eOXKtO6UO7JMBnITwmznzf6MEH/4Tsr5p2TIg2mZPMvehrF/L128HKDG+8w==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-6.0.8.tgz", + "integrity": "sha512-CAYKbKb/gr5xaEbElI7Mg5iu761CfE8F3ewslH5P+P5pmhCSkFjtJ506ztGowPJ00zP44qZy2c+L0W58KolpWQ==", "dev": true, "requires": { - "@graphql-tools/graphql-tag-pluck": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/graphql-tag-pluck": "6.0.8", + "@graphql-tools/utils": "6.0.8", "simple-git": "2.5.0" } }, "@graphql-tools/github-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-6.0.7.tgz", - "integrity": "sha512-DY2vImbM+vzRzQ1RRMVwJQ1TT8ywPUoX2aLHZAMyav49LOtzVEjKKyup4ELvgfTlnEceHRUldVbB+9GMZivOGw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-6.0.8.tgz", + "integrity": "sha512-NHH3/d+2V8eecm4XLAgqKr39qTPzA5CL1l5lACwmECapHLT3/cpHiy29IZmKjEZkZz8dovuHgNcB9AU27zyFag==", "dev": true, "requires": { - "@graphql-tools/graphql-tag-pluck": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/graphql-tag-pluck": "6.0.8", + "@graphql-tools/utils": "6.0.8", "cross-fetch": "3.0.4" }, "dependencies": { @@ -1536,21 +1536,21 @@ } }, "@graphql-tools/graphql-file-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.0.7.tgz", - "integrity": "sha512-YmaKPZ7fgXA0T+K1yeQ633UnDiS7TPGWbAs5eBAMm9OYN0aCcJi7VCf/Adz/OSOCbus68+eiq4wRZR96XtMkRw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.0.8.tgz", + "integrity": "sha512-3rvp662U9oPfFMmcSSQ1yKFlLS4ZZqDjVqkSjX/oW1BeFUYQAOOgB0UYmQ1Hcj0HrHMDEUDvjLuj1FT3egQr3g==", "dev": true, "requires": { - "@graphql-tools/import": "6.0.7", - "@graphql-tools/utils": "6.0.7", - "fs-extra": "9.0.0", + "@graphql-tools/import": "6.0.8", + "@graphql-tools/utils": "6.0.8", + "fs-extra": "9.0.1", "tslib": "~2.0.0" }, "dependencies": { "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -1584,32 +1584,32 @@ } }, "@graphql-tools/graphql-tag-pluck": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.0.7.tgz", - "integrity": "sha512-R7crmiZWStDyJ86I52v1EVAtnvJGzI0ujifG2BI2a6DMiN9G/jY1mDxl4w887Zf8qccT2NsBzzm1HlKgWlUjfQ==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.0.8.tgz", + "integrity": "sha512-rqFGWsvQfgnzmWu7/k7or9HgATOx3YKE7te/VO7TaOZP7K+U372Dc1eKHXu3yBzfp1HeDjQK1OYHXhD4Gcq6kQ==", "dev": true, "requires": { "@babel/parser": "7.10.2", "@babel/traverse": "7.10.1", "@babel/types": "7.10.2", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/utils": "6.0.8", "vue-template-compiler": "^2.6.11" } }, "@graphql-tools/import": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.0.7.tgz", - "integrity": "sha512-+um2IVjgUOGhuUAVmwiaUZhyodMXLs7IMmsfcHrZqVg7pf4diuyfNF7C+MlRkivWGk3nD48cFCQ7w8bz8XiSsA==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.0.8.tgz", + "integrity": "sha512-miJytXyRy8oSJLCln/p5G8GpCDkeco4bHtMUO+d5i2WzxtOkbKnQzTRoZ3P1Hy6EmLLyVwY+6/TEAS6DYxUJfA==", "dev": true, "requires": { - "fs-extra": "9.0.0", + "fs-extra": "9.0.1", "resolve-from": "5.0.0" }, "dependencies": { "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -1643,20 +1643,20 @@ } }, "@graphql-tools/json-file-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.0.7.tgz", - "integrity": "sha512-dHOLffhP3HXKhq2+U22/WRmhqt4r4Mprn+m/kMmNK4qNj+lm+R+I/kCPV6G0KsMLNnjJ4+lA3VVxTZeiz6FJpA==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.0.8.tgz", + "integrity": "sha512-nWMT9yWAQ0JoayA92em+az3Hdn21g6hx4YgXeoxi0O0wVxBJZ2Gds/5YnCzViAFgwBn3sP5EzEUuSkZiD2uZpA==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", - "fs-extra": "9.0.0", + "@graphql-tools/utils": "6.0.8", + "fs-extra": "9.0.1", "tslib": "~2.0.0" }, "dependencies": { "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -1690,12 +1690,12 @@ } }, "@graphql-tools/links": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/links/-/links-6.0.7.tgz", - "integrity": "sha512-yHYQ8v58GoI+PxTc4vgRqfeNmF0+emlIuURE45QPcLKvGLP+wpY6hW66p7CXWaKbkmQcGRBbEivc4vmXw4NnHw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/links/-/links-6.0.8.tgz", + "integrity": "sha512-Sqkvozo46cmQ18W9cq8B9VRmNVsNunlkZzb8hWKD22hqyDncEuqRsap2G+TnwXfUPxLWKwiGfpovomaa2pNBFQ==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/utils": "6.0.8", "apollo-link": "1.2.14", "apollo-upload-client": "13.0.0", "cross-fetch": "3.0.4", @@ -1739,13 +1739,13 @@ } }, "@graphql-tools/load": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.0.7.tgz", - "integrity": "sha512-SflEux607epsd6NtLUOb7l9SSlXqyvm/eW8FjrZuz7QrPWzbCQp6IkS5mx3fyy2qZZiecU1m2jH6jrZbXVabWA==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.0.8.tgz", + "integrity": "sha512-4qNLsNiH34zXAk5VkLvrowkGa2gYESuCqxPTSnQaL/cwtZcoGOZ3E7OjZQj+2I/+qtLHOZxplVy3zTnBElkJZA==", "dev": true, "requires": { - "@graphql-tools/merge": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/merge": "6.0.8", + "@graphql-tools/utils": "6.0.8", "globby": "11.0.1", "import-from": "3.0.0", "is-glob": "4.0.1", @@ -1799,20 +1799,20 @@ } }, "@graphql-tools/load-files": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/load-files/-/load-files-6.0.7.tgz", - "integrity": "sha512-KMSgrMB50LicUhLRWAAW5kjUVD/liyrFxy5uzTK2hglPizCrfn9FcxIrubCs7NhHt9exbVZu/pTPg9WDIai1/g==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/load-files/-/load-files-6.0.8.tgz", + "integrity": "sha512-C0cG/XE2T2A8tBeUa3OchqY2cQ0vf7UqoHHMFKVpADdFRqSmWdayzfIEMYCenAqiAL8owSElCbSTCV3676B2AA==", "dev": true, "requires": { - "fs-extra": "9.0.0", + "fs-extra": "9.0.1", "globby": "11.0.1", "unixify": "1.0.0" }, "dependencies": { "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -1860,13 +1860,13 @@ } }, "@graphql-tools/merge": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.7.tgz", - "integrity": "sha512-K7efEQbq8l69srWYxNfWRwQjTZOTGxx2K2WLIunLbrQU6IxBXLobmW8LwMYtMK34HowVEkW+NKy4l8FxJmQX9w==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.8.tgz", + "integrity": "sha512-CxMON945bQUDZ8WivP/1Bt7V1PgjpEqHQc/8UYeBXkUrUC/CKDhD270I13v/6njgImagdBHGgwS9r6cs785eWg==", "dev": true, "requires": { - "@graphql-tools/schema": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/schema": "6.0.8", + "@graphql-tools/utils": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -1879,13 +1879,13 @@ } }, "@graphql-tools/mock": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-6.0.7.tgz", - "integrity": "sha512-/jvFSohE9pXQEPgohGT/werrCLXf4yyoMozQow+xPyKlTOfdxMcOt96RQW8KoIxzp2o6wpFoXo2yv0eNpwb12A==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-6.0.8.tgz", + "integrity": "sha512-arlOgQ5z0DySnQsEq1XThAC7z/B7yUri+r4nyI+mCz1CXcOK4tohVid6LI84Ujgjw6bDOL2xhFHQSAmSDsjfhQ==", "dev": true, "requires": { - "@graphql-tools/schema": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/schema": "6.0.8", + "@graphql-tools/utils": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -1898,12 +1898,12 @@ } }, "@graphql-tools/module-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/module-loader/-/module-loader-6.0.7.tgz", - "integrity": "sha512-UAwv2vGYCotj8WoPvZu9mw3jSuSrN71JVWsdNkZ04wJ8wyL7Dya9sB5QvnLoQyrS5JYSUCGN4n8pq96/Vbm3oQ==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/module-loader/-/module-loader-6.0.8.tgz", + "integrity": "sha512-BeMjGuZwUnWjT/1Oh8EojEkiZ+Z6990Via9WnUFfpbtspePMIFdp8Zt49d4TnnQvTLWhQPE+nqITkIxDrTbCKQ==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/utils": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -1916,32 +1916,32 @@ } }, "@graphql-tools/relay-operation-optimizer": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.0.7.tgz", - "integrity": "sha512-PbnrIMyNmMSXbjAUJAGA5WwvAPDJXzeuWHtS7Zb5VgDZGyOBwzgksRTE+OpUKv088So11mZ3dBQQpddAfsJNWg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.0.8.tgz", + "integrity": "sha512-ZP+HX0mLdkaYIsZtH904WJtOYEFPxhy+vCsMAXRDsIE+9H4SPrslKvqhldLl5lpGnG/49r+Yu3M06nXaf3jsgg==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/utils": "6.0.8", "relay-compiler": "9.1.0" } }, "@graphql-tools/resolvers-composition": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/resolvers-composition/-/resolvers-composition-6.0.7.tgz", - "integrity": "sha512-KNzy8s4eyCdIKdpVHT74yqvB0HmKsNHLuL0yj7kQXOTpWLRm/PBbtRaKeGo0SjnoEGOWXTxjjy+mnEkZZjM/9g==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/resolvers-composition/-/resolvers-composition-6.0.8.tgz", + "integrity": "sha512-1mA7Z5uV3XUeUtXECZneiYUTzfPhtIvTaiTdemZUs+Vf8F0zYROBxn87AmosH9QLjauGVftdfOzgTlWiMXIhVA==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/utils": "6.0.8", "lodash": "4.17.15" } }, "@graphql-tools/schema": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.7.tgz", - "integrity": "sha512-10BptaXzNh6idEfmx75Y+hmxbtwvG+yB47bAC4JrOL+vy4PZ9wUvCn2IXfHdwXUx4CnU4qvMS8tosUdgYyJ4ag==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.8.tgz", + "integrity": "sha512-DCypfqU1UJTgFGRTgoSTbrfYVZy3ZjRlNn8HYOjCB2Og2nUsl+WxBrWRmCD+X+cK3qhH+gYZor0SlDxyOMDq9w==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/utils": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -1954,15 +1954,15 @@ } }, "@graphql-tools/stitch": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.7.tgz", - "integrity": "sha512-qYDpo5F/wbiSU697tpzmn1CdUmNTn36spDfWjhg/NBFl0ieE48mAsaEnRsLI9JtevlRJEja2gLNmGJV2jfOriw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.8.tgz", + "integrity": "sha512-Tr7AjTdPM4rQepYC1UBn31KsGprLzbFJdgb6bC1Ua/j1PlbHdNOtfwF2LlrkvTgdDTJnRC8jbeG85ZzTGyjqvw==", "dev": true, "requires": { - "@graphql-tools/delegate": "6.0.7", - "@graphql-tools/schema": "6.0.7", - "@graphql-tools/utils": "6.0.7", - "@graphql-tools/wrap": "6.0.7", + "@graphql-tools/delegate": "6.0.8", + "@graphql-tools/schema": "6.0.8", + "@graphql-tools/utils": "6.0.8", + "@graphql-tools/wrap": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -1975,13 +1975,14 @@ } }, "@graphql-tools/url-loader": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.0.7.tgz", - "integrity": "sha512-qgChWEKTNHbnAJ8Je19gtOZ65xFqjNQstV40eo6loQ+3kRX67yRpzfKEaHTJMoW7F0o6SjPtEJAVXasMkleaoQ==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.0.8.tgz", + "integrity": "sha512-iQtC8lz6ef9j3wtXPtrE3gZtE4DciX9q64tHx+/60UMyBj6zw0kL+z0OpbaBowzNiherahgqSmpDsM2s8jEgAA==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.7", - "@graphql-tools/wrap": "6.0.7", + "@graphql-tools/delegate": "6.0.8", + "@graphql-tools/utils": "6.0.8", + "@graphql-tools/wrap": "6.0.8", "@types/websocket": "1.0.0", "cross-fetch": "3.0.4", "subscriptions-transport-ws": "0.9.16", @@ -2015,9 +2016,9 @@ } }, "@graphql-tools/utils": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.7.tgz", - "integrity": "sha512-ij/ohAg/PBWx8JHKNQO7PMuABDUGICFbpaJHCe1OB5q4lGmpwlJXI1jJ6JjAKN8+FX0fc1TPh7zY/ScJ8/dXgg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.8.tgz", + "integrity": "sha512-1Idtq70pocuivrFUV2E+7o3ikyEUvES9i+YzQcNRoC04J29NWIiqJawjaWyLUt3z3/xdb6BEDpCwEy2Spu0CFQ==", "dev": true, "requires": { "camel-case": "4.1.1" @@ -2036,14 +2037,14 @@ } }, "@graphql-tools/wrap": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.7.tgz", - "integrity": "sha512-2azhUV4xkIE5BoNuR9Gf3YX7pZq7gNusC+bvRB6P88ROpgULZF1deoraXYaFm9BkpJCdH950b4UwsYSD9luy3g==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.8.tgz", + "integrity": "sha512-vv8BZiiOyZjORHzMOMIhZRIBEGcc3o/OnmVlgPudYvDMgILfXxMSlXVx3LIx7LHqzbrDfgP4KWfcKwuIcezTSA==", "dev": true, "requires": { - "@graphql-tools/delegate": "6.0.7", - "@graphql-tools/schema": "6.0.7", - "@graphql-tools/utils": "6.0.7", + "@graphql-tools/delegate": "6.0.8", + "@graphql-tools/schema": "6.0.8", + "@graphql-tools/utils": "6.0.8", "tslib": "~2.0.0" }, "dependencies": { @@ -4176,12 +4177,11 @@ "dev": true }, "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", "dev": true, "requires": { - "@types/events": "*", "@types/minimatch": "*", "@types/node": "*" } @@ -7232,9 +7232,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001077", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001077.tgz", - "integrity": "sha512-AEzsGvjBJL0lby/87W96PyEvwN0GsYvk5LHsglLg9tW37K4BqvAvoSCdWIE13OZQ8afupqZ73+oL/1LkedN8hA==", + "version": "1.0.30001078", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001078.tgz", + "integrity": "sha512-sF12qXe9VMm32IEf/+NDvmTpwJaaU7N1igpiH2FdI4DyABJSsOqG3ZAcFvszLkoLoo1y6VJLMYivukUAxaMASw==", "dev": true }, "capitalize": { @@ -8523,6 +8523,12 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=", + "dev": true + }, "cssnano": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", @@ -11219,9 +11225,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.460", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.460.tgz", - "integrity": "sha512-9nOPN0KoGUim2cDV2I1JWoWnxfC9o8z0ictsPnpNPhJD8NVZVW8DDacyrmIobwgY6Xaxn0TgVuUYXXmov8mPGg==", + "version": "1.3.464", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.464.tgz", + "integrity": "sha512-Oo+0+CN9d2z6FToQW6Hwvi9ez09Y/usKwr0tsDsyg43a871zVJCi1nR0v03djLbRNcaCKjtrnVf2XJhTxEpPCg==", "dev": true }, "elliptic": { @@ -11692,22 +11698,6 @@ "ms": "^2.1.1" } }, - "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", - "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", - "dev": true - }, "espree": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", @@ -13333,9 +13323,9 @@ "dev": true }, "gatsby": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.23.0.tgz", - "integrity": "sha512-+iQ/3i1N28/C+5LRduL06CMsRxmKX5PcjgZwboM53ZrvI+bu0LeSqVLPgbCnIJQGnAYtSnxArm1xJa6EBvol4A==", + "version": "2.23.1", + "resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.23.1.tgz", + "integrity": "sha512-PFl8uTeA9D1HolCOSbaFSYTf6nG0DcSghe3fXrYLD4daov4IYGzM6+eGQkVNFwBbFEZod/7QzA4r1Rx6awkZDw==", "dev": true, "requires": { "@babel/code-frame": "^7.10.1", @@ -13400,8 +13390,8 @@ "flat": "^4.1.0", "fs-exists-cached": "1.0.0", "fs-extra": "^8.1.0", - "gatsby-admin": "^0.1.64", - "gatsby-cli": "^2.12.43", + "gatsby-admin": "^0.1.65", + "gatsby-cli": "^2.12.44", "gatsby-core-utils": "^1.3.4", "gatsby-graphiql-explorer": "^0.4.4", "gatsby-link": "^2.4.5", @@ -13679,9 +13669,9 @@ "optional": true }, "gatsby-cli": { - "version": "2.12.43", - "resolved": "https://registry.npmjs.org/gatsby-cli/-/gatsby-cli-2.12.43.tgz", - "integrity": "sha512-pYWUPXgWoXMrGBNyVshpaT0/n3AfSHP52r21H3MUHEx/zhusNYCbePJTJS49LozfJ/o/XCc+kRu6JHbd4Sf2MQ==", + "version": "2.12.44", + "resolved": "https://registry.npmjs.org/gatsby-cli/-/gatsby-cli-2.12.44.tgz", + "integrity": "sha512-7cGZHSt/YFL6q68Cst7qnthYMckzP8zlNTkKQH24hm/vMHUusvl2vPONJL4XbyLZwnQ3tts44B2eMGK4Qoa5Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.1", @@ -13700,7 +13690,7 @@ "fs-exists-cached": "^1.0.0", "fs-extra": "^8.1.0", "gatsby-core-utils": "^1.3.4", - "gatsby-recipes": "^0.1.37", + "gatsby-recipes": "^0.1.38", "gatsby-telemetry": "^1.3.10", "hosted-git-info": "^3.0.4", "ink": "^2.7.1", @@ -13959,9 +13949,9 @@ } }, "gatsby-admin": { - "version": "0.1.64", - "resolved": "https://registry.npmjs.org/gatsby-admin/-/gatsby-admin-0.1.64.tgz", - "integrity": "sha512-yEFOOyTM8lBLlhk2uP0/XpwhfZx1IiOTX6JzXIPmJIulnPm4u8fDZNAnUluy9K2xqLE951dJZLYOTlx/7i4gWQ==", + "version": "0.1.65", + "resolved": "https://registry.npmjs.org/gatsby-admin/-/gatsby-admin-0.1.65.tgz", + "integrity": "sha512-9pwJA4kSMAfQ3xOC/xAGIRY4va515R49EsoWza63UR9vusbrjBOUdqyzCgZhqyhp47sUGr/urrfsbWttqoVOBw==", "dev": true, "requires": { "@emotion/core": "^10.0.28", @@ -13970,12 +13960,13 @@ "@typescript-eslint/parser": "^2.28.0", "csstype": "^2.6.10", "formik": "^2.1.4", - "gatsby": "^2.23.0", + "gatsby": "^2.23.1", "gatsby-interface": "0.0.167", "gatsby-plugin-typescript": "^2.4.4", "gatsby-source-graphql": "^2.5.3", "react": "^16.12.0", "react-dom": "^16.12.0", + "react-helmet": "^6.0.0", "react-icons": "^3.10.0", "strict-ui": "^0.1.3", "subscriptions-transport-ws": "^0.9.16", @@ -14480,9 +14471,9 @@ } }, "gatsby-recipes": { - "version": "0.1.37", - "resolved": "https://registry.npmjs.org/gatsby-recipes/-/gatsby-recipes-0.1.37.tgz", - "integrity": "sha512-70C4zeEu8xdTKFlOkXqMmkmDN9+23wjKKErcOR8JhkKr1j5hOgnY7ieX+hHh1ud9AvaKKHlQGGvDNArX35E8tg==", + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/gatsby-recipes/-/gatsby-recipes-0.1.38.tgz", + "integrity": "sha512-Mly2n47VL0XfiAcK07+ZQvdOAFyYl3TH8cIApOpkJZCMSE0/Zmaz4rpD0W33ZXe2yIDnlVJkQPNDjAdYHN8ePA==", "dev": true, "requires": { "@babel/core": "^7.10.2", @@ -14618,32 +14609,32 @@ } }, "graphql-tools": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-6.0.7.tgz", - "integrity": "sha512-aiZ0N0nW65CDvANXJ6WwhI286n/yeRVcvEr9Hhlo+gzJbDt3iMHoo1Rplf2YycJ8KZdddrHGGSDh5ZPm4LvjkA==", - "dev": true, - "requires": { - "@graphql-tools/code-file-loader": "6.0.7", - "@graphql-tools/delegate": "6.0.7", - "@graphql-tools/git-loader": "6.0.7", - "@graphql-tools/github-loader": "6.0.7", - "@graphql-tools/graphql-file-loader": "6.0.7", - "@graphql-tools/graphql-tag-pluck": "6.0.7", - "@graphql-tools/import": "6.0.7", - "@graphql-tools/json-file-loader": "6.0.7", - "@graphql-tools/links": "6.0.7", - "@graphql-tools/load": "6.0.7", - "@graphql-tools/load-files": "6.0.7", - "@graphql-tools/merge": "6.0.7", - "@graphql-tools/mock": "6.0.7", - "@graphql-tools/module-loader": "6.0.7", - "@graphql-tools/relay-operation-optimizer": "6.0.7", - "@graphql-tools/resolvers-composition": "6.0.7", - "@graphql-tools/schema": "6.0.7", - "@graphql-tools/stitch": "6.0.7", - "@graphql-tools/url-loader": "6.0.7", - "@graphql-tools/utils": "6.0.7", - "@graphql-tools/wrap": "6.0.7" + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-6.0.8.tgz", + "integrity": "sha512-/zfzAN5Ifux4oMzIkEK6EdYdVDzYu0a9rxCVk+GhpodnsLXmkg+J/SnisPlrCwYiT6QRRMpmQX3Cce3dRViI6w==", + "dev": true, + "requires": { + "@graphql-tools/code-file-loader": "6.0.8", + "@graphql-tools/delegate": "6.0.8", + "@graphql-tools/git-loader": "6.0.8", + "@graphql-tools/github-loader": "6.0.8", + "@graphql-tools/graphql-file-loader": "6.0.8", + "@graphql-tools/graphql-tag-pluck": "6.0.8", + "@graphql-tools/import": "6.0.8", + "@graphql-tools/json-file-loader": "6.0.8", + "@graphql-tools/links": "6.0.8", + "@graphql-tools/load": "6.0.8", + "@graphql-tools/load-files": "6.0.8", + "@graphql-tools/merge": "6.0.8", + "@graphql-tools/mock": "6.0.8", + "@graphql-tools/module-loader": "6.0.8", + "@graphql-tools/relay-operation-optimizer": "6.0.8", + "@graphql-tools/resolvers-composition": "6.0.8", + "@graphql-tools/schema": "6.0.8", + "@graphql-tools/stitch": "6.0.8", + "@graphql-tools/url-loader": "6.0.8", + "@graphql-tools/utils": "6.0.8", + "@graphql-tools/wrap": "6.0.8" } }, "is-binary-path": { @@ -15549,18 +15540,21 @@ } }, "graphql-playground-html": { - "version": "1.6.20", - "resolved": "https://registry.npmjs.org/graphql-playground-html/-/graphql-playground-html-1.6.20.tgz", - "integrity": "sha512-RkC18un0a1YEm0PoTMGgFQh7kIA6mtp3dUun+6coWtuMLczoNNij6V0DPHEj5kWi8u0qIrSKgSx5kh4pxcCX6g==", - "dev": true + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/graphql-playground-html/-/graphql-playground-html-1.6.22.tgz", + "integrity": "sha512-pqDJ8N8tI1LFORLZTDC+gByuqiBfbirjgMKprFxRbCmFylnIRj2AyUQUhTPQkm08n8pWj8w5T0wWCVtbE323ag==", + "dev": true, + "requires": { + "xss": "^1.0.6" + } }, "graphql-playground-middleware-express": { - "version": "1.7.15", - "resolved": "https://registry.npmjs.org/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.15.tgz", - "integrity": "sha512-Q7bjD1SMT5fiXMgUqstNzkYk9+csbuu5K7uOga9tJlA8x9gOVsSmmIfLi0tjPOrPd4m8icPnKncR73oNA22d5g==", + "version": "1.7.16", + "resolved": "https://registry.npmjs.org/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.16.tgz", + "integrity": "sha512-i06hkhvBacKJGkqVjWkwHt0Ov0YkYa6Lsjx8ThTybrUUxuV0fjaVdVxyUotC06olwW1ZQGCmlV5AnB8LJ8z3Dw==", "dev": true, "requires": { - "graphql-playground-html": "^1.6.19" + "graphql-playground-html": "1.6.22" } }, "graphql-request": { @@ -25820,9 +25814,9 @@ "dev": true }, "query-string": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.12.1.tgz", - "integrity": "sha512-OHj+zzfRMyj3rmo/6G8a5Ifvw3AleL/EbcHMD27YA31Q+cO5lfmQxECkImuNVjcskLcvBRVHNAB3w6udMs1eAA==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.0.tgz", + "integrity": "sha512-KJe8p8EUcixhPCp4cJoTYVfmgKHjnAB/Pq3fiqlmyNHvpHnOL5U4YE7iI2PYivGHp4HFocWz300906BAQX0H7g==", "dev": true, "requires": { "decode-uri-component": "^0.2.0", @@ -26394,6 +26388,18 @@ "integrity": "sha512-gWTtpOoi8Mgxayj0iWLL3SXRu2jW4eW4oim6B/FycaOH9HHIsCTPulC9u7CZ2BwY0CtqA+v8FsINp6qPuaN6qQ==", "dev": true }, + "react-helmet": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.0.0.tgz", + "integrity": "sha512-My6S4sa0uHN/IuVUn0HFmasW5xj9clTkB9qmMngscVycQ5vVG51Qp44BEvLJ4lixupTwDlU9qX1/sCrMN4AEPg==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^2.0.4", + "react-side-effect": "^2.1.0" + } + }, "react-helmet-async": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.0.6.tgz", @@ -26601,6 +26607,12 @@ "resize-observer-polyfill": "^1.5.1" } }, + "react-side-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz", + "integrity": "sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==", + "dev": true + }, "react-simple-code-editor": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.10.0.tgz", @@ -28062,9 +28074,9 @@ } }, "serve": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/serve/-/serve-11.3.1.tgz", - "integrity": "sha512-+tcx5eybTZT0scsp1PCb7HYjzBSfRF9fQIwyEU8ZYLioVuhHwywRYBBTF5WYlTXvC62eumK2bloDXAd7+9blGQ==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-11.3.2.tgz", + "integrity": "sha512-yKWQfI3xbj/f7X1lTBg91fXBP0FqjJ4TEi+ilES5yzH0iKJpN5LjNb1YzIfQg9Rqn4ECUS2SOf2+Kmepogoa5w==", "dev": true, "requires": { "@zeit/schemas": "2.6.0", @@ -28074,7 +28086,7 @@ "chalk": "2.4.1", "clipboardy": "1.2.3", "compression": "1.7.3", - "serve-handler": "6.1.2", + "serve-handler": "6.1.3", "update-check": "1.5.2" }, "dependencies": { @@ -28279,9 +28291,9 @@ } }, "serve-handler": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.2.tgz", - "integrity": "sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", + "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", "dev": true, "requires": { "bytes": "3.0.0", @@ -31248,9 +31260,9 @@ "dev": true }, "use-callback-ref": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.3.tgz", - "integrity": "sha512-DPBPh1i2adCZoIArRlTuKRy7yue7QogtEnfv0AKrWsY+GA+4EKe37zhRDouNnyWMoNQFYZZRF+2dLHsWE4YvJA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.4.tgz", + "integrity": "sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ==", "dev": true }, "use-sidecar": { @@ -32696,6 +32708,24 @@ "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", "dev": true }, + "xss": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.6.tgz", + "integrity": "sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==", + "dev": true, + "requires": { + "commander": "^2.9.0", + "cssfilter": "0.0.10" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, "xstate": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.10.0.tgz", From 09350625b367a2c1487f0df084358502ebfff238 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 7 Jun 2020 22:29:36 +1000 Subject: [PATCH 02/11] Refactor react-test-renderer into native renderer --- src/{ => core}/asyncUtils.js | 4 +- src/{ => core}/cleanup.js | 9 +++ src/{ => core}/flush-microtasks.js | 0 src/core/index.js | 91 +++++++++++++++++++++++++ src/index.js | 11 +-- src/native/index.js | 5 ++ src/native/pure.js | 34 ++++++++++ src/pure.js | 103 +---------------------------- 8 files changed, 142 insertions(+), 115 deletions(-) rename src/{ => core}/asyncUtils.js (96%) rename src/{ => core}/cleanup.js (62%) rename src/{ => core}/flush-microtasks.js (100%) create mode 100644 src/core/index.js create mode 100644 src/native/index.js create mode 100644 src/native/pure.js diff --git a/src/asyncUtils.js b/src/core/asyncUtils.js similarity index 96% rename from src/asyncUtils.js rename to src/core/asyncUtils.js index c3cf7ab9..ab673d76 100644 --- a/src/asyncUtils.js +++ b/src/core/asyncUtils.js @@ -1,12 +1,10 @@ -import { act } from 'react-test-renderer' - function createTimeoutError(utilName, { timeout }) { const timeoutError = new Error(`Timed out in ${utilName} after ${timeout}ms.`) timeoutError.timeout = true return timeoutError } -function asyncUtils(addResolver) { +function asyncUtils(act, addResolver) { let nextUpdatePromise = null const waitForNextUpdate = async (options = {}) => { diff --git a/src/cleanup.js b/src/core/cleanup.js similarity index 62% rename from src/cleanup.js rename to src/core/cleanup.js index c240b5e1..2ed4a75a 100644 --- a/src/cleanup.js +++ b/src/core/cleanup.js @@ -16,4 +16,13 @@ function removeCleanup(callback) { cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback) } +cleanup.autoRegister = function () { + // Automatically registers cleanup in supported testing frameworks + if (typeof afterEach === 'function' && !process.env.RHTL_SKIP_AUTO_CLEANUP) { + afterEach(async () => { + await cleanup() + }) + } +} + export { cleanup, addCleanup, removeCleanup } diff --git a/src/flush-microtasks.js b/src/core/flush-microtasks.js similarity index 100% rename from src/flush-microtasks.js rename to src/core/flush-microtasks.js diff --git a/src/core/index.js b/src/core/index.js new file mode 100644 index 00000000..5a1ba885 --- /dev/null +++ b/src/core/index.js @@ -0,0 +1,91 @@ +import React from 'react' +import asyncUtils from './asyncUtils' +import { cleanup, addCleanup, removeCleanup } from './cleanup' + +function TestHook({ callback, hookProps, onError, children }) { + try { + children(callback(hookProps)) + } catch (err) { + if (err.then) { + throw err + } else { + onError(err) + } + } + return null +} + +function resultContainer() { + let value = null + let error = null + const resolvers = [] + + const result = { + get current() { + if (error) { + throw error + } + return value + }, + get error() { + return error + } + } + + const updateResult = (val, err) => { + value = val + error = err + resolvers.splice(0, resolvers.length).forEach((resolve) => resolve()) + } + + return { + result, + addResolver: (resolver) => { + resolvers.push(resolver) + }, + setValue: (val) => updateResult(val), + setError: (err) => updateResult(undefined, err) + } +} + +function createRenderHook(createRenderer) { + return function renderHook(callback, { initialProps, wrapper } = {}) { + const { result, setValue, setError, addResolver } = resultContainer() + const hookProps = { current: initialProps } + + const wrapUiIfNeeded = (innerElement) => + wrapper ? React.createElement(wrapper, hookProps.current, innerElement) : innerElement + + const toRender = () => + wrapUiIfNeeded( + + {setValue} + + ) + + let { render, rerender, unmount, act } = createRenderer() + + render(toRender()) + + function rerenderHook(newProps = hookProps.current) { + hookProps.current = newProps + rerender(toRender()) + } + + function unmountHook() { + removeCleanup(unmountHook) + unmount() + } + + addCleanup(unmountHook) + + return { + result, + rerender: rerenderHook, + unmount: unmountHook, + ...asyncUtils(act, addResolver) + } + } +} + +export { createRenderHook, cleanup } diff --git a/src/index.js b/src/index.js index c1abc074..585816ce 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1 @@ -import { cleanup } from './pure' - -// Automatically registers cleanup in supported testing frameworks -if (typeof afterEach === 'function' && !process.env.RHTL_SKIP_AUTO_CLEANUP) { - afterEach(async () => { - await cleanup() - }) -} - -export * from './pure' +export * from './native' diff --git a/src/native/index.js b/src/native/index.js new file mode 100644 index 00000000..4a47e48a --- /dev/null +++ b/src/native/index.js @@ -0,0 +1,5 @@ +import { cleanup } from '../core' + +cleanup.autoRegister() + +export * from './pure' diff --git a/src/native/pure.js b/src/native/pure.js new file mode 100644 index 00000000..8d891fc4 --- /dev/null +++ b/src/native/pure.js @@ -0,0 +1,34 @@ +import React, { Suspense } from 'react' +import { act, create } from 'react-test-renderer' +import { createRenderHook, cleanup } from '../core' + +function Fallback() { + return null +} + +function createRenderer() { + let container + + return { + render(component) { + act(() => { + container = create(}>{component}) + }) + }, + rerender(component) { + act(() => { + container.update(}>{component}) + }) + }, + unmount() { + act(() => { + container.unmount() + }) + }, + act + } +} + +const renderHook = createRenderHook(createRenderer) + +export { renderHook, act, cleanup } diff --git a/src/pure.js b/src/pure.js index 3b4a475d..a4384083 100644 --- a/src/pure.js +++ b/src/pure.js @@ -1,102 +1 @@ -import React, { Suspense } from 'react' -import { act, create } from 'react-test-renderer' -import asyncUtils from './asyncUtils' -import { cleanup, addCleanup, removeCleanup } from './cleanup' - -function TestHook({ callback, hookProps, onError, children }) { - try { - children(callback(hookProps)) - } catch (err) { - if (err.then) { - throw err - } else { - onError(err) - } - } - return null -} - -function Fallback() { - return null -} - -function resultContainer() { - let value = null - let error = null - const resolvers = [] - - const result = { - get current() { - if (error) { - throw error - } - return value - }, - get error() { - return error - } - } - - const updateResult = (val, err) => { - value = val - error = err - resolvers.splice(0, resolvers.length).forEach((resolve) => resolve()) - } - - return { - result, - addResolver: (resolver) => { - resolvers.push(resolver) - }, - setValue: (val) => updateResult(val), - setError: (err) => updateResult(undefined, err) - } -} - -function renderHook(callback, { initialProps, wrapper } = {}) { - const { result, setValue, setError, addResolver } = resultContainer() - const hookProps = { current: initialProps } - - const wrapUiIfNeeded = (innerElement) => - wrapper ? React.createElement(wrapper, hookProps.current, innerElement) : innerElement - - const toRender = () => - wrapUiIfNeeded( - }> - - {setValue} - - - ) - - let testRenderer - act(() => { - testRenderer = create(toRender()) - }) - const { unmount, update } = testRenderer - - function rerenderHook(newProps = hookProps.current) { - hookProps.current = newProps - act(() => { - update(toRender()) - }) - } - - function unmountHook() { - act(() => { - removeCleanup(unmountHook) - unmount() - }) - } - - addCleanup(unmountHook) - - return { - result, - rerender: rerenderHook, - unmount: unmountHook, - ...asyncUtils(addResolver) - } -} - -export { renderHook, cleanup, act } +export * from './native/pure' From ab6d14fff13e11b19ccf1668bd4395ffbcb6ae06 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 7 Jun 2020 22:48:26 +1000 Subject: [PATCH 03/11] Implement dom renderer --- package.json | 10 + src/dom/index.js | 5 + src/dom/pure.js | 37 +++ test/{ => dom}/asyncHook.test.js | 2 +- test/{ => dom}/autoCleanup.disabled.test.js | 2 +- .../{ => dom}/autoCleanup.noAfterEach.test.js | 2 +- test/{ => dom}/autoCleanup.test.js | 2 +- test/{ => dom}/cleanup.test.js | 2 +- test/{ => dom}/customHook.test.js | 2 +- test/{ => dom}/errorHook.test.js | 2 +- test/{ => dom}/suspenseHook.test.js | 2 +- test/{ => dom}/useContext.test.js | 2 +- test/{ => dom}/useEffect.test.js | 2 +- test/{ => dom}/useMemo.test.js | 2 +- test/{ => dom}/useReducer.test.js | 2 +- test/{ => dom}/useRef.test.js | 2 +- test/{ => dom}/useState.test.js | 2 +- test/native/asyncHook.test.js | 217 ++++++++++++++++++ test/native/autoCleanup.disabled.test.js | 28 +++ test/native/autoCleanup.noAfterEach.test.js | 28 +++ test/native/autoCleanup.test.js | 24 ++ test/native/cleanup.test.js | 41 ++++ test/native/customHook.test.js | 29 +++ test/native/errorHook.test.js | 155 +++++++++++++ test/native/suspenseHook.test.js | 49 ++++ test/native/useContext.test.js | 63 +++++ test/native/useEffect.test.js | 62 +++++ test/native/useMemo.test.js | 64 ++++++ test/native/useReducer.test.js | 19 ++ test/native/useRef.test.js | 27 +++ test/native/useState.test.js | 24 ++ 31 files changed, 896 insertions(+), 14 deletions(-) create mode 100644 src/dom/index.js create mode 100644 src/dom/pure.js rename test/{ => dom}/asyncHook.test.js (99%) rename test/{ => dom}/autoCleanup.disabled.test.js (92%) rename test/{ => dom}/autoCleanup.noAfterEach.test.js (92%) rename test/{ => dom}/autoCleanup.test.js (93%) rename test/{ => dom}/cleanup.test.js (94%) rename test/{ => dom}/customHook.test.js (94%) rename test/{ => dom}/errorHook.test.js (99%) rename test/{ => dom}/suspenseHook.test.js (97%) rename test/{ => dom}/useContext.test.js (97%) rename test/{ => dom}/useEffect.test.js (97%) rename test/{ => dom}/useMemo.test.js (97%) rename test/{ => dom}/useReducer.test.js (91%) rename test/{ => dom}/useRef.test.js (94%) rename test/{ => dom}/useState.test.js (92%) create mode 100644 test/native/asyncHook.test.js create mode 100644 test/native/autoCleanup.disabled.test.js create mode 100644 test/native/autoCleanup.noAfterEach.test.js create mode 100644 test/native/autoCleanup.test.js create mode 100644 test/native/cleanup.test.js create mode 100644 test/native/customHook.test.js create mode 100644 test/native/errorHook.test.js create mode 100644 test/native/suspenseHook.test.js create mode 100644 test/native/useContext.test.js create mode 100644 test/native/useEffect.test.js create mode 100644 test/native/useMemo.test.js create mode 100644 test/native/useReducer.test.js create mode 100644 test/native/useRef.test.js create mode 100644 test/native/useState.test.js diff --git a/package.json b/package.json index 596d44b6..c8c68003 100644 --- a/package.json +++ b/package.json @@ -61,12 +61,22 @@ "prettier-eslint": "11.0.0", "prettier-eslint-cli": "5.0.0", "react": "16.13.1", + "react-dom": "^16.13.1", "react-test-renderer": "16.13.1" }, "peerDependencies": { "react": ">=16.9.0", + "react-dom": ">=16.9.0", "react-test-renderer": ">=16.9.0" }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + }, "jest": { "collectCoverage": true, "coverageDirectory": "./coverage/", diff --git a/src/dom/index.js b/src/dom/index.js new file mode 100644 index 00000000..4a47e48a --- /dev/null +++ b/src/dom/index.js @@ -0,0 +1,5 @@ +import { cleanup } from '../core' + +cleanup.autoRegister() + +export * from './pure' diff --git a/src/dom/pure.js b/src/dom/pure.js new file mode 100644 index 00000000..63e34cf9 --- /dev/null +++ b/src/dom/pure.js @@ -0,0 +1,37 @@ +import React, { Suspense } from 'react' +import ReactDOM from 'react-dom' +import { act } from 'react-dom/test-utils' +import { createRenderHook, cleanup } from '../core' + +function Fallback() { + return null +} + +function createRenderer() { + const container = document.createElement('div') + + return { + render(component) { + document.body.appendChild(container) + act(() => { + ReactDOM.render(}>{component}, container) + }) + }, + rerender(component) { + act(() => { + ReactDOM.render(}>{component}, container) + }) + }, + unmount() { + act(() => { + ReactDOM.unmountComponentAtNode(container) + document.body.removeChild(container) + }) + }, + act + } +} + +const renderHook = createRenderHook(createRenderer) + +export { renderHook, act, cleanup } diff --git a/test/asyncHook.test.js b/test/dom/asyncHook.test.js similarity index 99% rename from test/asyncHook.test.js rename to test/dom/asyncHook.test.js index 61a58cb6..20fe9153 100644 --- a/test/asyncHook.test.js +++ b/test/dom/asyncHook.test.js @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('async hook tests', () => { const useSequence = (...values) => { diff --git a/test/autoCleanup.disabled.test.js b/test/dom/autoCleanup.disabled.test.js similarity index 92% rename from test/autoCleanup.disabled.test.js rename to test/dom/autoCleanup.disabled.test.js index 7da342d5..533a0f7f 100644 --- a/test/autoCleanup.disabled.test.js +++ b/test/dom/autoCleanup.disabled.test.js @@ -8,7 +8,7 @@ describe('skip auto cleanup (disabled) tests', () => { beforeAll(() => { process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' - renderHook = require('src').renderHook + renderHook = require('src/dom').renderHook }) test('first', () => { diff --git a/test/autoCleanup.noAfterEach.test.js b/test/dom/autoCleanup.noAfterEach.test.js similarity index 92% rename from test/autoCleanup.noAfterEach.test.js rename to test/dom/autoCleanup.noAfterEach.test.js index c1f51eea..7dc1a237 100644 --- a/test/autoCleanup.noAfterEach.test.js +++ b/test/dom/autoCleanup.noAfterEach.test.js @@ -8,7 +8,7 @@ describe('skip auto cleanup (no afterEach) tests', () => { beforeAll(() => { afterEach = false - renderHook = require('src').renderHook + renderHook = require('src/dom').renderHook }) test('first', () => { diff --git a/test/autoCleanup.test.js b/test/dom/autoCleanup.test.js similarity index 93% rename from test/autoCleanup.test.js rename to test/dom/autoCleanup.test.js index d644fe79..fcb599a2 100644 --- a/test/autoCleanup.test.js +++ b/test/dom/autoCleanup.test.js @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' // This verifies that by importing RHTL in an // environment which supports afterEach (like Jest) diff --git a/test/cleanup.test.js b/test/dom/cleanup.test.js similarity index 94% rename from test/cleanup.test.js rename to test/dom/cleanup.test.js index a8c3bbba..9dbabb1b 100644 --- a/test/cleanup.test.js +++ b/test/dom/cleanup.test.js @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { renderHook, cleanup } from 'src' +import { renderHook, cleanup } from 'src/dom' describe('cleanup tests', () => { test('should flush effects on cleanup', async () => { diff --git a/test/customHook.test.js b/test/dom/customHook.test.js similarity index 94% rename from test/customHook.test.js rename to test/dom/customHook.test.js index 72dd1bac..df156f7c 100644 --- a/test/customHook.test.js +++ b/test/dom/customHook.test.js @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react' -import { renderHook, act } from 'src' +import { renderHook, act } from 'src/dom' describe('custom hook tests', () => { function useCounter() { diff --git a/test/errorHook.test.js b/test/dom/errorHook.test.js similarity index 99% rename from test/errorHook.test.js rename to test/dom/errorHook.test.js index dbfb21fa..0bb57bdc 100644 --- a/test/errorHook.test.js +++ b/test/dom/errorHook.test.js @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('error hook tests', () => { function useError(throwError) { diff --git a/test/suspenseHook.test.js b/test/dom/suspenseHook.test.js similarity index 97% rename from test/suspenseHook.test.js rename to test/dom/suspenseHook.test.js index f7ece119..2cbdf4f0 100644 --- a/test/suspenseHook.test.js +++ b/test/dom/suspenseHook.test.js @@ -1,4 +1,4 @@ -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('suspense hook tests', () => { const cache = {} diff --git a/test/useContext.test.js b/test/dom/useContext.test.js similarity index 97% rename from test/useContext.test.js rename to test/dom/useContext.test.js index 2c22caca..50cef65f 100644 --- a/test/useContext.test.js +++ b/test/dom/useContext.test.js @@ -1,5 +1,5 @@ import React, { createContext, useContext } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('useContext tests', () => { test('should get default value from context', () => { diff --git a/test/useEffect.test.js b/test/dom/useEffect.test.js similarity index 97% rename from test/useEffect.test.js rename to test/dom/useEffect.test.js index d5c96d2f..1b609ff3 100644 --- a/test/useEffect.test.js +++ b/test/dom/useEffect.test.js @@ -1,5 +1,5 @@ import { useEffect, useLayoutEffect } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('useEffect tests', () => { test('should handle useEffect hook', () => { diff --git a/test/useMemo.test.js b/test/dom/useMemo.test.js similarity index 97% rename from test/useMemo.test.js rename to test/dom/useMemo.test.js index 3abeb855..080d8356 100644 --- a/test/useMemo.test.js +++ b/test/dom/useMemo.test.js @@ -1,5 +1,5 @@ import { useMemo, useCallback } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('useCallback tests', () => { test('should handle useMemo hook', () => { diff --git a/test/useReducer.test.js b/test/dom/useReducer.test.js similarity index 91% rename from test/useReducer.test.js rename to test/dom/useReducer.test.js index 092d585d..237cc8d1 100644 --- a/test/useReducer.test.js +++ b/test/dom/useReducer.test.js @@ -1,5 +1,5 @@ import { useReducer } from 'react' -import { renderHook, act } from 'src' +import { renderHook, act } from 'src/dom' describe('useReducer tests', () => { test('should handle useReducer hook', () => { diff --git a/test/useRef.test.js b/test/dom/useRef.test.js similarity index 94% rename from test/useRef.test.js rename to test/dom/useRef.test.js index 63dd241d..37878417 100644 --- a/test/useRef.test.js +++ b/test/dom/useRef.test.js @@ -1,5 +1,5 @@ import { useRef, useImperativeHandle } from 'react' -import { renderHook } from 'src' +import { renderHook } from 'src/dom' describe('useHook tests', () => { test('should handle useRef hook', () => { diff --git a/test/useState.test.js b/test/dom/useState.test.js similarity index 92% rename from test/useState.test.js rename to test/dom/useState.test.js index ebb943f8..d3e909f0 100644 --- a/test/useState.test.js +++ b/test/dom/useState.test.js @@ -1,5 +1,5 @@ import { useState } from 'react' -import { renderHook, act } from 'src' +import { renderHook, act } from 'src/dom' describe('useState tests', () => { test('should use setState value', () => { diff --git a/test/native/asyncHook.test.js b/test/native/asyncHook.test.js new file mode 100644 index 00000000..1a9f298c --- /dev/null +++ b/test/native/asyncHook.test.js @@ -0,0 +1,217 @@ +import { useState, useRef, useEffect } from 'react' +import { renderHook } from 'src/native' + +describe('async hook tests', () => { + const useSequence = (...values) => { + const [first, ...otherValues] = values + const [value, setValue] = useState(first) + const index = useRef(0) + + useEffect(() => { + const interval = setInterval(() => { + setValue(otherValues[index.current++]) + if (index.current === otherValues.length) { + clearInterval(interval) + } + }, 50) + return () => { + clearInterval(interval) + } + }, [...values]) + + return value + } + + test('should wait for next update', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + await waitForNextUpdate() + + expect(result.current).toBe('second') + }) + + test('should wait for multiple updates', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + await waitForNextUpdate() + + expect(result.current).toBe('second') + + await waitForNextUpdate() + + expect(result.current).toBe('third') + }) + + test('should resolve all when updating', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + await Promise.all([waitForNextUpdate(), waitForNextUpdate(), waitForNextUpdate()]) + + expect(result.current).toBe('second') + }) + + test('should reject if timeout exceeded when waiting for next update', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( + Error('Timed out in waitForNextUpdate after 10ms.') + ) + }) + + test('should wait for expectation to pass', async () => { + const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + let complete = false + await wait(() => { + expect(result.current).toBe('third') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should not hang if expectation is already passing', async () => { + const { result, wait } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + let complete = false + await wait(() => { + expect(result.current).toBe('first') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should reject if callback throws error', async () => { + const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + await expect( + wait( + () => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current === 'third' + }, + { + suppressErrors: false + } + ) + ).rejects.toThrow(Error('Something Unexpected')) + }) + + test('should reject if callback immediately throws error', async () => { + const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + await expect( + wait( + () => { + throw new Error('Something Unexpected') + }, + { + suppressErrors: false + } + ) + ).rejects.toThrow(Error('Something Unexpected')) + }) + + test('should wait for truthy value', async () => { + const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + await wait(() => result.current === 'third') + + expect(result.current).toBe('third') + }) + + test('should reject if timeout exceeded when waiting for expectation to pass', async () => { + const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + await expect( + wait( + () => { + expect(result.current).toBe('third') + }, + { timeout: 75 } + ) + ).rejects.toThrow(Error('Timed out in wait after 75ms.')) + }) + + test('should wait for value to change', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + await waitForValueToChange(() => result.current === 'third') + + expect(result.current).toBe('third') + }) + + test('should reject if timeout exceeded when waiting for value to change', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + await expect( + waitForValueToChange(() => result.current === 'third', { + timeout: 75 + }) + ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) + }) + + test('should reject if selector throws error', async () => { + const { result, waitForValueToChange } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + await expect( + waitForValueToChange(() => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current + }) + ).rejects.toThrow(Error('Something Unexpected')) + }) + + test('should not reject if selector throws error and suppress errors option is enabled', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + await waitForValueToChange( + () => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current === 'third' + }, + { suppressErrors: true } + ) + + expect(result.current).toBe('third') + }) +}) diff --git a/test/native/autoCleanup.disabled.test.js b/test/native/autoCleanup.disabled.test.js new file mode 100644 index 00000000..a3498f3b --- /dev/null +++ b/test/native/autoCleanup.disabled.test.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set +// then we DON'T auto-wire up the afterEach for folks +describe('skip auto cleanup (disabled) tests', () => { + let cleanupCalled = false + let renderHook + + beforeAll(() => { + process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' + renderHook = require('src/native').renderHook + }) + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(false) + }) +}) diff --git a/test/native/autoCleanup.noAfterEach.test.js b/test/native/autoCleanup.noAfterEach.test.js new file mode 100644 index 00000000..363e02c9 --- /dev/null +++ b/test/native/autoCleanup.noAfterEach.test.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set +// then we DON'T auto-wire up the afterEach for folks +describe('skip auto cleanup (no afterEach) tests', () => { + let cleanupCalled = false + let renderHook + + beforeAll(() => { + afterEach = false + renderHook = require('src/native').renderHook + }) + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(false) + }) +}) diff --git a/test/native/autoCleanup.test.js b/test/native/autoCleanup.test.js new file mode 100644 index 00000000..8511b966 --- /dev/null +++ b/test/native/autoCleanup.test.js @@ -0,0 +1,24 @@ +import { useEffect } from 'react' +import { renderHook } from 'src/native' + +// This verifies that by importing RHTL in an +// environment which supports afterEach (like Jest) +// we'll get automatic cleanup between tests. +describe('auto cleanup tests', () => { + let cleanupCalled = false + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(true) + }) +}) diff --git a/test/native/cleanup.test.js b/test/native/cleanup.test.js new file mode 100644 index 00000000..42481f42 --- /dev/null +++ b/test/native/cleanup.test.js @@ -0,0 +1,41 @@ +import { useEffect } from 'react' +import { renderHook, cleanup } from 'src/native' + +describe('cleanup tests', () => { + test('should flush effects on cleanup', async () => { + let cleanupCalled = false + + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + + renderHook(() => hookWithCleanup()) + + await cleanup() + + expect(cleanupCalled).toBe(true) + }) + + test('should cleanup all rendered hooks', async () => { + let cleanupCalled = [] + const hookWithCleanup = (id) => { + useEffect(() => { + return () => { + cleanupCalled[id] = true + } + }) + } + + renderHook(() => hookWithCleanup(1)) + renderHook(() => hookWithCleanup(2)) + + await cleanup() + + expect(cleanupCalled[1]).toBe(true) + expect(cleanupCalled[2]).toBe(true) + }) +}) diff --git a/test/native/customHook.test.js b/test/native/customHook.test.js new file mode 100644 index 00000000..ddf2106f --- /dev/null +++ b/test/native/customHook.test.js @@ -0,0 +1,29 @@ +import { useState, useCallback } from 'react' +import { renderHook, act } from 'src/native' + +describe('custom hook tests', () => { + function useCounter() { + const [count, setCount] = useState(0) + + const increment = useCallback(() => setCount(count + 1), [count]) + const decrement = useCallback(() => setCount(count - 1), [count]) + + return { count, increment, decrement } + } + + test('should increment counter', () => { + const { result } = renderHook(() => useCounter()) + + act(() => result.current.increment()) + + expect(result.current.count).toBe(1) + }) + + test('should decrement counter', () => { + const { result } = renderHook(() => useCounter()) + + act(() => result.current.decrement()) + + expect(result.current.count).toBe(-1) + }) +}) diff --git a/test/native/errorHook.test.js b/test/native/errorHook.test.js new file mode 100644 index 00000000..ce2378ee --- /dev/null +++ b/test/native/errorHook.test.js @@ -0,0 +1,155 @@ +import { useState, useEffect } from 'react' +import { renderHook } from 'src/native' + +describe('error hook tests', () => { + function useError(throwError) { + if (throwError) { + throw new Error('expected') + } + return true + } + + function useAsyncError(throwError) { + const [value, setValue] = useState() + useEffect(() => { + const timeout = setTimeout(() => setValue(throwError), 100) + return () => clearTimeout(timeout) + }, [throwError]) + return useError(value) + } + + function useEffectError(throwError) { + useEffect(() => { + useError(throwError) + }, []) + return true + } + + describe('synchronous', () => { + test('should raise error', () => { + const { result } = renderHook(() => useError(true)) + + expect(() => { + expect(result.current).not.toBe(undefined) + }).toThrow(Error('expected')) + }) + + test('should capture error', () => { + const { result } = renderHook(() => useError(true)) + + expect(result.error).toEqual(Error('expected')) + }) + + test('should not capture error', () => { + const { result } = renderHook(() => useError(false)) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + + test('should reset error', () => { + const { result, rerender } = renderHook((throwError) => useError(throwError), { + initialProps: true + }) + + expect(result.error).not.toBe(undefined) + + rerender(false) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + }) + + describe('asynchronous', () => { + test('should raise async error', async () => { + const { result, waitForNextUpdate } = renderHook(() => useAsyncError(true)) + + await waitForNextUpdate() + + expect(() => { + expect(result.current).not.toBe(undefined) + }).toThrow(Error('expected')) + }) + + test('should capture async error', async () => { + const { result, waitForNextUpdate } = renderHook(() => useAsyncError(true)) + + await waitForNextUpdate() + + expect(result.error).toEqual(Error('expected')) + }) + + test('should not capture async error', async () => { + const { result, waitForNextUpdate } = renderHook(() => useAsyncError(false)) + + await waitForNextUpdate() + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + + test('should reset async error', async () => { + const { result, waitForNextUpdate, rerender } = renderHook( + (throwError) => useAsyncError(throwError), + { + initialProps: true + } + ) + + await waitForNextUpdate() + + expect(result.error).not.toBe(undefined) + + rerender(false) + + await waitForNextUpdate() + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + }) + + /* + These tests capture error cases that are not currently being caught successfully. + Refer to https://github.com/testing-library/react-hooks-testing-library/issues/308 + for more details. + */ + describe.skip('effect', () => { + test('should raise effect error', () => { + const { result } = renderHook(() => useEffectError(true)) + + expect(() => { + expect(result.current).not.toBe(undefined) + }).toThrow(Error('expected')) + }) + + test('should capture effect error', () => { + const { result } = renderHook(() => useEffectError(true)) + expect(result.error).toEqual(Error('expected')) + }) + + test('should not capture effect error', () => { + const { result } = renderHook(() => useEffectError(false)) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + + test('should reset effect error', () => { + const { result, waitForNextUpdate, rerender } = renderHook( + (throwError) => useEffectError(throwError), + { + initialProps: true + } + ) + + expect(result.error).not.toBe(undefined) + + rerender(false) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + }) +}) diff --git a/test/native/suspenseHook.test.js b/test/native/suspenseHook.test.js new file mode 100644 index 00000000..25059b50 --- /dev/null +++ b/test/native/suspenseHook.test.js @@ -0,0 +1,49 @@ +import { renderHook } from 'src/native' + +describe('suspense hook tests', () => { + const cache = {} + const fetchName = (isSuccessful) => { + if (!cache.value) { + cache.value = new Promise((resolve, reject) => { + setTimeout(() => { + if (isSuccessful) { + resolve('Bob') + } else { + reject(new Error('Failed to fetch name')) + } + }, 50) + }) + .then((value) => (cache.value = value)) + .catch((e) => (cache.value = e)) + } + return cache.value + } + + const useFetchName = (isSuccessful = true) => { + const name = fetchName(isSuccessful) + if (typeof name.then === 'function' || name instanceof Error) { + throw name + } + return name + } + + beforeEach(() => { + delete cache.value + }) + + test('should allow rendering to be suspended', async () => { + const { result, waitForNextUpdate } = renderHook(() => useFetchName(true)) + + await waitForNextUpdate() + + expect(result.current).toBe('Bob') + }) + + test('should set error if suspense promise rejects', async () => { + const { result, waitForNextUpdate } = renderHook(() => useFetchName(false)) + + await waitForNextUpdate() + + expect(result.error).toEqual(new Error('Failed to fetch name')) + }) +}) diff --git a/test/native/useContext.test.js b/test/native/useContext.test.js new file mode 100644 index 00000000..119dfe4e --- /dev/null +++ b/test/native/useContext.test.js @@ -0,0 +1,63 @@ +import React, { createContext, useContext } from 'react' +import { renderHook } from 'src/native' + +describe('useContext tests', () => { + test('should get default value from context', () => { + const TestContext = createContext('foo') + + const { result } = renderHook(() => useContext(TestContext)) + + const value = result.current + + expect(value).toBe('foo') + }) + + test('should get value from context provider', () => { + const TestContext = createContext('foo') + + const wrapper = ({ children }) => ( + {children} + ) + + const { result } = renderHook(() => useContext(TestContext), { wrapper }) + + expect(result.current).toBe('bar') + }) + + test('should update mutated value in context', () => { + const TestContext = createContext('foo') + + const value = { current: 'bar' } + + const wrapper = ({ children }) => ( + {children} + ) + + const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper }) + + value.current = 'baz' + + rerender() + + expect(result.current).toBe('baz') + }) + + test('should update value in context when props are updated', () => { + const TestContext = createContext('foo') + + const wrapper = ({ current, children }) => ( + {children} + ) + + const { result, rerender } = renderHook(() => useContext(TestContext), { + wrapper, + initialProps: { + current: 'bar' + } + }) + + rerender({ current: 'baz' }) + + expect(result.current).toBe('baz') + }) +}) diff --git a/test/native/useEffect.test.js b/test/native/useEffect.test.js new file mode 100644 index 00000000..5714b146 --- /dev/null +++ b/test/native/useEffect.test.js @@ -0,0 +1,62 @@ +import { useEffect, useLayoutEffect } from 'react' +import { renderHook } from 'src/native' + +describe('useEffect tests', () => { + test('should handle useEffect hook', () => { + const sideEffect = { [1]: false, [2]: false } + + const { rerender, unmount } = renderHook( + ({ id }) => { + useEffect(() => { + sideEffect[id] = true + return () => { + sideEffect[id] = false + } + }, [id]) + }, + { initialProps: { id: 1 } } + ) + + expect(sideEffect[1]).toBe(true) + expect(sideEffect[2]).toBe(false) + + rerender({ id: 2 }) + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(true) + + unmount() + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(false) + }) + + test('should handle useLayoutEffect hook', () => { + const sideEffect = { [1]: false, [2]: false } + + const { rerender, unmount } = renderHook( + ({ id }) => { + useLayoutEffect(() => { + sideEffect[id] = true + return () => { + sideEffect[id] = false + } + }, [id]) + }, + { initialProps: { id: 1 } } + ) + + expect(sideEffect[1]).toBe(true) + expect(sideEffect[2]).toBe(false) + + rerender({ id: 2 }) + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(true) + + unmount() + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(false) + }) +}) diff --git a/test/native/useMemo.test.js b/test/native/useMemo.test.js new file mode 100644 index 00000000..ce043ea5 --- /dev/null +++ b/test/native/useMemo.test.js @@ -0,0 +1,64 @@ +import { useMemo, useCallback } from 'react' +import { renderHook } from 'src/native' + +describe('useCallback tests', () => { + test('should handle useMemo hook', () => { + const { result, rerender } = renderHook(({ value }) => useMemo(() => ({ value }), [value]), { + initialProps: { value: 1 } + }) + + const value1 = result.current + + expect(value1).toEqual({ value: 1 }) + + rerender() + + const value2 = result.current + + expect(value2).toEqual({ value: 1 }) + + expect(value2).toBe(value1) + + rerender({ value: 2 }) + + const value3 = result.current + + expect(value3).toEqual({ value: 2 }) + + expect(value3).not.toBe(value1) + }) + + test('should handle useCallback hook', () => { + const { result, rerender } = renderHook( + ({ value }) => { + const callback = () => ({ value }) + return useCallback(callback, [value]) + }, + { initialProps: { value: 1 } } + ) + + const callback1 = result.current + + const calbackValue1 = callback1() + + expect(calbackValue1).toEqual({ value: 1 }) + + const callback2 = result.current + + const calbackValue2 = callback2() + + expect(calbackValue2).toEqual({ value: 1 }) + + expect(callback2).toBe(callback1) + + rerender({ value: 2 }) + + const callback3 = result.current + + const calbackValue3 = callback3() + + expect(calbackValue3).toEqual({ value: 2 }) + + expect(callback3).not.toBe(callback1) + }) +}) diff --git a/test/native/useReducer.test.js b/test/native/useReducer.test.js new file mode 100644 index 00000000..a5dfa0ae --- /dev/null +++ b/test/native/useReducer.test.js @@ -0,0 +1,19 @@ +import { useReducer } from 'react' +import { renderHook, act } from 'src/native' + +describe('useReducer tests', () => { + test('should handle useReducer hook', () => { + const reducer = (state, action) => (action.type === 'inc' ? state + 1 : state) + const { result } = renderHook(() => useReducer(reducer, 0)) + + const [initialState, dispatch] = result.current + + expect(initialState).toBe(0) + + act(() => dispatch({ type: 'inc' })) + + const [state] = result.current + + expect(state).toBe(1) + }) +}) diff --git a/test/native/useRef.test.js b/test/native/useRef.test.js new file mode 100644 index 00000000..9955dfda --- /dev/null +++ b/test/native/useRef.test.js @@ -0,0 +1,27 @@ +import { useRef, useImperativeHandle } from 'react' +import { renderHook } from 'src/native' + +describe('useHook tests', () => { + test('should handle useRef hook', () => { + const { result } = renderHook(() => useRef()) + + const refContainer = result.current + + expect(Object.keys(refContainer)).toEqual(['current']) + expect(refContainer.current).toBeUndefined() + }) + + test('should handle useImperativeHandle hook', () => { + const { result } = renderHook(() => { + const ref = useRef() + useImperativeHandle(ref, () => ({ + fakeImperativeMethod: () => true + })) + return ref + }) + + const refContainer = result.current + + expect(refContainer.current.fakeImperativeMethod()).toBe(true) + }) +}) diff --git a/test/native/useState.test.js b/test/native/useState.test.js new file mode 100644 index 00000000..52086598 --- /dev/null +++ b/test/native/useState.test.js @@ -0,0 +1,24 @@ +import { useState } from 'react' +import { renderHook, act } from 'src/native' + +describe('useState tests', () => { + test('should use setState value', () => { + const { result } = renderHook(() => useState('foo')) + + const [value] = result.current + + expect(value).toBe('foo') + }) + + test('should update setState value using setter', () => { + const { result } = renderHook(() => useState('foo')) + + const [_, setValue] = result.current + + act(() => setValue('bar')) + + const [value] = result.current + + expect(value).toBe('bar') + }) +}) From 3b92a721ddbfefa31f7ca108245977a0baf4d964 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 21:53:58 +1000 Subject: [PATCH 04/11] Implement server renderer --- src/core/index.js | 5 +- src/server/index.js | 5 + src/server/pure.js | 62 +++++ test/server/asyncHook.test.js | 277 ++++++++++++++++++++ test/server/autoCleanup.disabled.test.js | 28 ++ test/server/autoCleanup.noAfterEach.test.js | 28 ++ test/server/autoCleanup.test.js | 32 +++ test/server/cleanup.test.js | 67 +++++ test/server/customHook.test.js | 33 +++ test/server/errorHook.test.js | 171 ++++++++++++ test/server/useContext.test.js | 45 ++++ test/server/useEffect.test.js | 38 +++ test/server/useMemo.test.js | 87 ++++++ test/server/useReducer.test.js | 21 ++ test/server/useRef.test.js | 29 ++ test/server/useState.test.js | 39 +++ 16 files changed, 965 insertions(+), 2 deletions(-) create mode 100644 src/server/index.js create mode 100644 src/server/pure.js create mode 100644 test/server/asyncHook.test.js create mode 100644 test/server/autoCleanup.disabled.test.js create mode 100644 test/server/autoCleanup.noAfterEach.test.js create mode 100644 test/server/autoCleanup.test.js create mode 100644 test/server/cleanup.test.js create mode 100644 test/server/customHook.test.js create mode 100644 test/server/errorHook.test.js create mode 100644 test/server/useContext.test.js create mode 100644 test/server/useEffect.test.js create mode 100644 test/server/useMemo.test.js create mode 100644 test/server/useReducer.test.js create mode 100644 test/server/useRef.test.js create mode 100644 test/server/useState.test.js diff --git a/src/core/index.js b/src/core/index.js index 5a1ba885..1fc136ff 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -63,7 +63,7 @@ function createRenderHook(createRenderer) { ) - let { render, rerender, unmount, act } = createRenderer() + let { render, rerender, unmount, act, ...rendererUtils } = createRenderer() render(toRender()) @@ -83,7 +83,8 @@ function createRenderHook(createRenderer) { result, rerender: rerenderHook, unmount: unmountHook, - ...asyncUtils(act, addResolver) + ...asyncUtils(act, addResolver), + ...rendererUtils } } } diff --git a/src/server/index.js b/src/server/index.js new file mode 100644 index 00000000..4a47e48a --- /dev/null +++ b/src/server/index.js @@ -0,0 +1,5 @@ +import { cleanup } from '../core' + +cleanup.autoRegister() + +export * from './pure' diff --git a/src/server/pure.js b/src/server/pure.js new file mode 100644 index 00000000..10e95e00 --- /dev/null +++ b/src/server/pure.js @@ -0,0 +1,62 @@ +import ReactDOMServer from 'react-dom/server' +import ReactDOM from 'react-dom' +import { act as baseAct } from 'react-dom/test-utils' +import { createRenderHook, cleanup } from '../core' + +let act + +function createRenderer() { + const container = document.createElement('div') + let hydrated = false + let ssrComponent + + act = function act(...args) { + if (!hydrated) { + throw new Error('You must hydrate the component before you can act') + } + return baseAct(...args) + } + + return { + render(component) { + ssrComponent = component + baseAct(() => { + const serverOutput = ReactDOMServer.renderToString(component) + container.innerHTML = serverOutput + }) + }, + hydrate() { + if (hydrated) { + throw new Error('The component can only be hydrated once') + } + if (!hydrated) { + document.body.appendChild(container) + baseAct(() => { + ReactDOM.hydrate(ssrComponent, container) + }) + hydrated = true + } + }, + rerender(component) { + if (!hydrated) { + throw new Error('You must hydrate the component before you can rerender') + } + baseAct(() => { + ReactDOM.render(component, container) + }) + }, + unmount() { + if (hydrated) { + baseAct(() => { + ReactDOM.unmountComponentAtNode(container) + document.body.removeChild(container) + }) + } + }, + act + } +} + +const renderHook = createRenderHook(createRenderer) + +export { renderHook, act, cleanup } diff --git a/test/server/asyncHook.test.js b/test/server/asyncHook.test.js new file mode 100644 index 00000000..a8d4a7e9 --- /dev/null +++ b/test/server/asyncHook.test.js @@ -0,0 +1,277 @@ +import { useState, useRef, useEffect } from 'react' +import { renderHook } from 'src/server' + +describe('async hook tests', () => { + const useSequence = (...values) => { + const [first, ...otherValues] = values + const [value, setValue] = useState(first) + const index = useRef(0) + + useEffect(() => { + const interval = setInterval(() => { + setValue(otherValues[index.current++]) + if (index.current === otherValues.length) { + clearInterval(interval) + } + }, 50) + return () => { + clearInterval(interval) + } + }, [...values]) + + return value + } + + test('should wait for next update', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await waitForNextUpdate() + + expect(result.current).toBe('second') + }) + + test('should wait for multiple updates', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await waitForNextUpdate() + + expect(result.current).toBe('second') + + await waitForNextUpdate() + + expect(result.current).toBe('third') + }) + + test('should resolve all when updating', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await Promise.all([waitForNextUpdate(), waitForNextUpdate(), waitForNextUpdate()]) + + expect(result.current).toBe('second') + }) + + test('should reject if timeout exceeded when waiting for next update', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( + Error('Timed out in waitForNextUpdate after 10ms.') + ) + }) + + test('should wait for expectation to pass', async () => { + const { result, hydrate, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + let complete = false + await wait(() => { + expect(result.current).toBe('third') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should not hang if expectation is already passing', async () => { + const { result, hydrate, wait } = renderHook(() => useSequence('first', 'second')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + let complete = false + await wait(() => { + expect(result.current).toBe('first') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should reject if callback throws error', async () => { + const { result, hydrate, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await expect( + wait( + () => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current === 'third' + }, + { + suppressErrors: false + } + ) + ).rejects.toThrow(Error('Something Unexpected')) + }) + + test('should reject if callback immediately throws error', async () => { + const { result, hydrate, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await expect( + wait( + () => { + throw new Error('Something Unexpected') + }, + { + suppressErrors: false + } + ) + ).rejects.toThrow(Error('Something Unexpected')) + }) + + test('should wait for truthy value', async () => { + const { result, hydrate, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await wait(() => result.current === 'third') + + expect(result.current).toBe('third') + }) + + test('should reject if timeout exceeded when waiting for expectation to pass', async () => { + const { result, hydrate, wait } = renderHook(() => useSequence('first', 'second', 'third')) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await expect( + wait( + () => { + expect(result.current).toBe('third') + }, + { timeout: 75 } + ) + ).rejects.toThrow(Error('Timed out in wait after 75ms.')) + }) + + test('should wait for value to change', async () => { + const { result, hydrate, waitForValueToChange } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await waitForValueToChange(() => result.current === 'third') + + expect(result.current).toBe('third') + }) + + test('should reject if timeout exceeded when waiting for value to change', async () => { + const { result, hydrate, waitForValueToChange } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await expect( + waitForValueToChange(() => result.current === 'third', { + timeout: 75 + }) + ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) + }) + + test('should reject if selector throws error', async () => { + const { result, hydrate, waitForValueToChange } = renderHook(() => + useSequence('first', 'second') + ) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await expect( + waitForValueToChange(() => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current + }) + ).rejects.toThrow(Error('Something Unexpected')) + }) + + test('should not reject if selector throws error and suppress errors option is enabled', async () => { + const { result, hydrate, waitForValueToChange } = renderHook(() => + useSequence('first', 'second', 'third') + ) + + expect(result.current).toBe('first') + + hydrate() + + expect(result.current).toBe('first') + + await waitForValueToChange( + () => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current === 'third' + }, + { suppressErrors: true } + ) + + expect(result.current).toBe('third') + }) +}) diff --git a/test/server/autoCleanup.disabled.test.js b/test/server/autoCleanup.disabled.test.js new file mode 100644 index 00000000..d94e643c --- /dev/null +++ b/test/server/autoCleanup.disabled.test.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set +// then we DON'T auto-wire up the afterEach for folks +describe('skip auto cleanup (disabled) tests', () => { + let cleanupCalled = false + let renderHook + + beforeAll(() => { + process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' + renderHook = require('src/server').renderHook + }) + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(false) + }) +}) diff --git a/test/server/autoCleanup.noAfterEach.test.js b/test/server/autoCleanup.noAfterEach.test.js new file mode 100644 index 00000000..d4f30a80 --- /dev/null +++ b/test/server/autoCleanup.noAfterEach.test.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set +// then we DON'T auto-wire up the afterEach for folks +describe('skip auto cleanup (no afterEach) tests', () => { + let cleanupCalled = false + let renderHook + + beforeAll(() => { + afterEach = false + renderHook = require('src/server').renderHook + }) + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(false) + }) +}) diff --git a/test/server/autoCleanup.test.js b/test/server/autoCleanup.test.js new file mode 100644 index 00000000..67428422 --- /dev/null +++ b/test/server/autoCleanup.test.js @@ -0,0 +1,32 @@ +import { useEffect } from 'react' +import { renderHook } from 'src/server' + +// This verifies that by importing RHTL in an +// environment which supports afterEach (like Jest) +// we'll get automatic cleanup between tests. +describe('auto cleanup tests', () => { + const cleanups = { + ssr: false, + hydrated: false + } + + test('first', () => { + const hookWithCleanup = (name) => { + useEffect(() => { + return () => { + cleanups[name] = true + } + }) + } + + renderHook(() => hookWithCleanup('ssr')) + + const { hydrate } = renderHook(() => hookWithCleanup('hydrated')) + hydrate() + }) + + test('second', () => { + expect(cleanups.ssr).toBe(false) + expect(cleanups.hydrated).toBe(true) + }) +}) diff --git a/test/server/cleanup.test.js b/test/server/cleanup.test.js new file mode 100644 index 00000000..fb7f6d7d --- /dev/null +++ b/test/server/cleanup.test.js @@ -0,0 +1,67 @@ +import { useEffect } from 'react' +import { renderHook, cleanup } from 'src/server' + +describe('cleanup tests', () => { + test('should flush effects on cleanup', async () => { + let cleanupCalled = false + + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + + const { hydrate } = renderHook(() => hookWithCleanup()) + + hydrate() + + await cleanup() + + expect(cleanupCalled).toBe(true) + }) + + test('should cleanup all rendered hooks', async () => { + let cleanupCalled = [] + const hookWithCleanup = (id) => { + useEffect(() => { + return () => { + cleanupCalled[id] = true + } + }) + } + + const { hydrate: hydrate1 } = renderHook(() => hookWithCleanup(1)) + const { hydrate: hydrate2 } = renderHook(() => hookWithCleanup(2)) + + hydrate1() + hydrate2() + + await cleanup() + + expect(cleanupCalled[1]).toBe(true) + expect(cleanupCalled[2]).toBe(true) + }) + + test('should only cleanup hydrated hooks', async () => { + let cleanupCalled = [false, false] + const hookWithCleanup = (id) => { + useEffect(() => { + return () => { + cleanupCalled[id] = true + } + }) + } + + renderHook(() => hookWithCleanup(1)) + const { hydrate } = renderHook(() => hookWithCleanup(2)) + + hydrate() + + await cleanup() + + expect(cleanupCalled[1]).toBe(false) + expect(cleanupCalled[2]).toBe(true) + }) +}) diff --git a/test/server/customHook.test.js b/test/server/customHook.test.js new file mode 100644 index 00000000..b699b7d8 --- /dev/null +++ b/test/server/customHook.test.js @@ -0,0 +1,33 @@ +import { useState, useCallback } from 'react' +import { renderHook, act } from 'src/server' + +describe('custom hook tests', () => { + function useCounter() { + const [count, setCount] = useState(0) + + const increment = useCallback(() => setCount(count + 1), [count]) + const decrement = useCallback(() => setCount(count - 1), [count]) + + return { count, increment, decrement } + } + + test('should increment counter', () => { + const { result, hydrate } = renderHook(() => useCounter()) + + hydrate() + + act(() => result.current.increment()) + + expect(result.current.count).toBe(1) + }) + + test('should decrement counter', () => { + const { result, hydrate } = renderHook(() => useCounter()) + + hydrate() + + act(() => result.current.decrement()) + + expect(result.current.count).toBe(-1) + }) +}) diff --git a/test/server/errorHook.test.js b/test/server/errorHook.test.js new file mode 100644 index 00000000..cd88d5fd --- /dev/null +++ b/test/server/errorHook.test.js @@ -0,0 +1,171 @@ +import { useState, useEffect } from 'react' +import { renderHook } from 'src/server' + +describe('error hook tests', () => { + function useError(throwError) { + if (throwError) { + throw new Error('expected') + } + return true + } + + function useAsyncError(throwError) { + const [value, setValue] = useState() + useEffect(() => { + const timeout = setTimeout(() => setValue(throwError), 100) + return () => clearTimeout(timeout) + }, [throwError]) + return useError(value) + } + + function useEffectError(throwError) { + useEffect(() => { + useError(throwError) + }, []) + return true + } + + describe('synchronous', () => { + test('should raise error', () => { + const { result } = renderHook(() => useError(true)) + + expect(() => { + expect(result.current).not.toBe(undefined) + }).toThrow(Error('expected')) + }) + + test('should capture error', () => { + const { result } = renderHook(() => useError(true)) + + expect(result.error).toEqual(Error('expected')) + }) + + test('should not capture error', () => { + const { result } = renderHook(() => useError(false)) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + + test('should reset error', () => { + const { result, hydrate, rerender } = renderHook((throwError) => useError(throwError), { + initialProps: true + }) + + expect(result.error).not.toBe(undefined) + + hydrate() + + rerender(false) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + }) + + describe('asynchronous', () => { + test('should raise async error', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncError(true)) + + hydrate() + + await waitForNextUpdate() + + expect(() => { + expect(result.current).not.toBe(undefined) + }).toThrow(Error('expected')) + }) + + test('should capture async error', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncError(true)) + + hydrate() + + await waitForNextUpdate() + + expect(result.error).toEqual(Error('expected')) + }) + + test('should not capture async error', async () => { + const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncError(false)) + + hydrate() + + await waitForNextUpdate() + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + + test('should reset async error', async () => { + const { result, hydrate, waitForNextUpdate, rerender } = renderHook( + (throwError) => useAsyncError(throwError), + { + initialProps: true + } + ) + + hydrate() + + await waitForNextUpdate() + + expect(result.error).not.toBe(undefined) + + rerender(false) + + await waitForNextUpdate() + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + }) + + /* + These tests capture error cases that are not currently being caught successfully. + Refer to https://github.com/testing-library/react-hooks-testing-library/issues/308 + for more details. + */ + describe.skip('effect', () => { + test('should raise effect error', () => { + const { result, hydrate } = renderHook(() => useEffectError(true)) + + hydrate() + + expect(() => { + expect(result.current).not.toBe(undefined) + }).toThrow(Error('expected')) + }) + + test('should capture effect error', () => { + const { result, hydrate } = renderHook(() => useEffectError(true)) + + hydrate() + + expect(result.error).toEqual(Error('expected')) + }) + + test('should not capture effect error', () => { + const { result, hydrate } = renderHook(() => useEffectError(false)) + + hydrate() + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + + test('should reset effect error', () => { + const { result, hydrate, rerender } = renderHook((throwError) => useEffectError(throwError), { + initialProps: true + }) + + hydrate() + + expect(result.error).not.toBe(undefined) + + rerender(false) + + expect(result.current).not.toBe(undefined) + expect(result.error).toBe(undefined) + }) + }) +}) diff --git a/test/server/useContext.test.js b/test/server/useContext.test.js new file mode 100644 index 00000000..2934d7f0 --- /dev/null +++ b/test/server/useContext.test.js @@ -0,0 +1,45 @@ +import React, { createContext, useContext } from 'react' +import { renderHook } from 'src/server' + +describe('useContext tests', () => { + test('should get default value from context', () => { + const TestContext = createContext('foo') + + const { result } = renderHook(() => useContext(TestContext)) + + const value = result.current + + expect(value).toBe('foo') + }) + + test('should get value from context provider', () => { + const TestContext = createContext('foo') + + const wrapper = ({ children }) => ( + {children} + ) + + const { result } = renderHook(() => useContext(TestContext), { wrapper }) + + expect(result.current).toBe('bar') + }) + + test('should update value in context when props are updated', () => { + const TestContext = createContext('foo') + + const wrapper = ({ contextValue, children }) => ( + {children} + ) + + const { result, hydrate, rerender } = renderHook(() => useContext(TestContext), { + wrapper, + initialProps: { contextValue: 'bar' } + }) + + hydrate() + + rerender({ contextValue: 'baz' }) + + expect(result.current).toBe('baz') + }) +}) diff --git a/test/server/useEffect.test.js b/test/server/useEffect.test.js new file mode 100644 index 00000000..ff2387cd --- /dev/null +++ b/test/server/useEffect.test.js @@ -0,0 +1,38 @@ +import { useEffect, useLayoutEffect } from 'react' +import { renderHook } from 'src/server' + +describe('useEffect tests', () => { + test('should handle useEffect hook', () => { + const sideEffect = { [1]: false, [2]: false } + + const { hydrate, rerender, unmount } = renderHook( + ({ id }) => { + useEffect(() => { + sideEffect[id] = true + return () => { + sideEffect[id] = false + } + }, [id]) + }, + { initialProps: { id: 1 } } + ) + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(false) + + hydrate() + + expect(sideEffect[1]).toBe(true) + expect(sideEffect[2]).toBe(false) + + rerender({ id: 2 }) + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(true) + + unmount() + + expect(sideEffect[1]).toBe(false) + expect(sideEffect[2]).toBe(false) + }) +}) diff --git a/test/server/useMemo.test.js b/test/server/useMemo.test.js new file mode 100644 index 00000000..19bc1960 --- /dev/null +++ b/test/server/useMemo.test.js @@ -0,0 +1,87 @@ +import { useMemo, useCallback } from 'react' +import { renderHook } from 'src/server' + +describe('useCallback tests', () => { + test('should handle useMemo hook', () => { + const { result, hydrate, rerender } = renderHook( + ({ value }) => useMemo(() => ({ value }), [value]), + { + initialProps: { value: 1 } + } + ) + + const value1 = result.current + + expect(value1).toEqual({ value: 1 }) + + hydrate() + + const value2 = result.current + + expect(value2).toEqual({ value: 1 }) + + expect(value2).not.toBe(value1) + + rerender() + + const value3 = result.current + + expect(value3).toEqual({ value: 1 }) + + expect(value3).toBe(value2) + + rerender({ value: 2 }) + + const value4 = result.current + + expect(value4).toEqual({ value: 2 }) + + expect(value4).not.toBe(value2) + }) + + test('should handle useCallback hook', () => { + const { result, hydrate, rerender } = renderHook( + ({ value }) => { + const callback = () => ({ value }) + return useCallback(callback, [value]) + }, + { initialProps: { value: 1 } } + ) + + const callback1 = result.current + + const calbackValue1 = callback1() + + expect(calbackValue1).toEqual({ value: 1 }) + + hydrate() + + const callback2 = result.current + + const calbackValue2 = callback2() + + expect(calbackValue2).toEqual({ value: 1 }) + + expect(callback2).not.toBe(callback1) + + rerender() + + const callback3 = result.current + + const calbackValue3 = callback3() + + expect(calbackValue3).toEqual({ value: 1 }) + + expect(callback3).toBe(callback2) + + rerender({ value: 2 }) + + const callback4 = result.current + + const calbackValue4 = callback4() + + expect(calbackValue4).toEqual({ value: 2 }) + + expect(callback4).not.toBe(callback2) + }) +}) diff --git a/test/server/useReducer.test.js b/test/server/useReducer.test.js new file mode 100644 index 00000000..ea3724ab --- /dev/null +++ b/test/server/useReducer.test.js @@ -0,0 +1,21 @@ +import { useReducer } from 'react' +import { renderHook, act } from 'src/server' + +describe('useReducer tests', () => { + test('should handle useReducer hook', () => { + const reducer = (state, action) => (action.type === 'inc' ? state + 1 : state) + + const { result, hydrate } = renderHook(() => { + const [state, dispatch] = useReducer(reducer, 0) + return { state, dispatch } + }) + + hydrate() + + expect(result.current.state).toBe(0) + + act(() => result.current.dispatch({ type: 'inc' })) + + expect(result.current.state).toBe(1) + }) +}) diff --git a/test/server/useRef.test.js b/test/server/useRef.test.js new file mode 100644 index 00000000..1327e604 --- /dev/null +++ b/test/server/useRef.test.js @@ -0,0 +1,29 @@ +import { useRef, useImperativeHandle } from 'react' +import { renderHook } from 'src/server' + +describe('useHook tests', () => { + test('should handle useRef hook', () => { + const { result } = renderHook(() => useRef('foo')) + + const refContainer = result.current + + expect(Object.keys(refContainer)).toEqual(['current']) + expect(refContainer.current).toBe('foo') + }) + + test('should handle useImperativeHandle hook', () => { + const { result, hydrate } = renderHook(() => { + const ref = useRef() + useImperativeHandle(ref, () => ({ + fakeImperativeMethod: () => true + })) + return ref + }) + + expect(result.current.current).toBeUndefined() + + hydrate() + + expect(result.current.current.fakeImperativeMethod()).toBe(true) + }) +}) diff --git a/test/server/useState.test.js b/test/server/useState.test.js new file mode 100644 index 00000000..48af8903 --- /dev/null +++ b/test/server/useState.test.js @@ -0,0 +1,39 @@ +import { useState } from 'react' +import { renderHook, act } from 'src/server' + +describe('useState tests', () => { + test('should use state value', () => { + const { result } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) + + expect(result.current.value).toBe('foo') + }) + + test('should retain state value after hydration', () => { + const { result, hydrate } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) + + hydrate() + + expect(result.current.value).toBe('foo') + }) + + test('should update state value using setter', () => { + const { result, hydrate } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) + + hydrate() + + act(() => { + result.current.setValue('bar') + }) + + expect(result.current.value).toBe('bar') + }) +}) From be5deefa47060f7feb5c4d8dbbdddf2489522af4 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 21:55:18 +1000 Subject: [PATCH 05/11] Clean up tests with changes from server tests --- test/dom/errorHook.test.js | 9 +++------ test/dom/useContext.test.js | 28 ++++------------------------ test/dom/useReducer.test.js | 15 +++++++-------- test/dom/useState.test.js | 26 +++++++++++++------------- test/native/errorHook.test.js | 9 +++------ test/native/useContext.test.js | 28 ++++------------------------ test/native/useReducer.test.js | 15 +++++++-------- test/native/useState.test.js | 26 +++++++++++++------------- 8 files changed, 54 insertions(+), 102 deletions(-) diff --git a/test/dom/errorHook.test.js b/test/dom/errorHook.test.js index 0bb57bdc..c2cbe49a 100644 --- a/test/dom/errorHook.test.js +++ b/test/dom/errorHook.test.js @@ -137,12 +137,9 @@ describe('error hook tests', () => { }) test('should reset effect error', () => { - const { result, waitForNextUpdate, rerender } = renderHook( - (throwError) => useEffectError(throwError), - { - initialProps: true - } - ) + const { result, rerender } = renderHook((throwError) => useEffectError(throwError), { + initialProps: true + }) expect(result.error).not.toBe(undefined) diff --git a/test/dom/useContext.test.js b/test/dom/useContext.test.js index 50cef65f..1ffd7aad 100644 --- a/test/dom/useContext.test.js +++ b/test/dom/useContext.test.js @@ -24,39 +24,19 @@ describe('useContext tests', () => { expect(result.current).toBe('bar') }) - test('should update mutated value in context', () => { - const TestContext = createContext('foo') - - const value = { current: 'bar' } - - const wrapper = ({ children }) => ( - {children} - ) - - const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper }) - - value.current = 'baz' - - rerender() - - expect(result.current).toBe('baz') - }) - test('should update value in context when props are updated', () => { const TestContext = createContext('foo') - const wrapper = ({ current, children }) => ( - {children} + const wrapper = ({ contextValue, children }) => ( + {children} ) const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper, - initialProps: { - current: 'bar' - } + initialProps: { contextValue: 'bar' } }) - rerender({ current: 'baz' }) + rerender({ contextValue: 'baz' }) expect(result.current).toBe('baz') }) diff --git a/test/dom/useReducer.test.js b/test/dom/useReducer.test.js index 237cc8d1..99929026 100644 --- a/test/dom/useReducer.test.js +++ b/test/dom/useReducer.test.js @@ -4,16 +4,15 @@ import { renderHook, act } from 'src/dom' describe('useReducer tests', () => { test('should handle useReducer hook', () => { const reducer = (state, action) => (action.type === 'inc' ? state + 1 : state) - const { result } = renderHook(() => useReducer(reducer, 0)) + const { result } = renderHook(() => { + const [state, dispatch] = useReducer(reducer, 0) + return { state, dispatch } + }) - const [initialState, dispatch] = result.current + expect(result.current.state).toBe(0) - expect(initialState).toBe(0) + act(() => result.current.dispatch({ type: 'inc' })) - act(() => dispatch({ type: 'inc' })) - - const [state] = result.current - - expect(state).toBe(1) + expect(result.current.state).toBe(1) }) }) diff --git a/test/dom/useState.test.js b/test/dom/useState.test.js index d3e909f0..ae61d0bc 100644 --- a/test/dom/useState.test.js +++ b/test/dom/useState.test.js @@ -2,23 +2,23 @@ import { useState } from 'react' import { renderHook, act } from 'src/dom' describe('useState tests', () => { - test('should use setState value', () => { - const { result } = renderHook(() => useState('foo')) + test('should use state value', () => { + const { result } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) - const [value] = result.current - - expect(value).toBe('foo') + expect(result.current.value).toBe('foo') }) - test('should update setState value using setter', () => { - const { result } = renderHook(() => useState('foo')) - - const [_, setValue] = result.current - - act(() => setValue('bar')) + test('should update state value using setter', () => { + const { result } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) - const [value] = result.current + act(() => result.current.setValue('bar')) - expect(value).toBe('bar') + expect(result.current.value).toBe('bar') }) }) diff --git a/test/native/errorHook.test.js b/test/native/errorHook.test.js index ce2378ee..0937a5e4 100644 --- a/test/native/errorHook.test.js +++ b/test/native/errorHook.test.js @@ -137,12 +137,9 @@ describe('error hook tests', () => { }) test('should reset effect error', () => { - const { result, waitForNextUpdate, rerender } = renderHook( - (throwError) => useEffectError(throwError), - { - initialProps: true - } - ) + const { result, rerender } = renderHook((throwError) => useEffectError(throwError), { + initialProps: true + }) expect(result.error).not.toBe(undefined) diff --git a/test/native/useContext.test.js b/test/native/useContext.test.js index 119dfe4e..3ecf1e7f 100644 --- a/test/native/useContext.test.js +++ b/test/native/useContext.test.js @@ -24,39 +24,19 @@ describe('useContext tests', () => { expect(result.current).toBe('bar') }) - test('should update mutated value in context', () => { - const TestContext = createContext('foo') - - const value = { current: 'bar' } - - const wrapper = ({ children }) => ( - {children} - ) - - const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper }) - - value.current = 'baz' - - rerender() - - expect(result.current).toBe('baz') - }) - test('should update value in context when props are updated', () => { const TestContext = createContext('foo') - const wrapper = ({ current, children }) => ( - {children} + const wrapper = ({ contextValue, children }) => ( + {children} ) const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper, - initialProps: { - current: 'bar' - } + initialProps: { contextValue: 'bar' } }) - rerender({ current: 'baz' }) + rerender({ contextValue: 'baz' }) expect(result.current).toBe('baz') }) diff --git a/test/native/useReducer.test.js b/test/native/useReducer.test.js index a5dfa0ae..305ae656 100644 --- a/test/native/useReducer.test.js +++ b/test/native/useReducer.test.js @@ -4,16 +4,15 @@ import { renderHook, act } from 'src/native' describe('useReducer tests', () => { test('should handle useReducer hook', () => { const reducer = (state, action) => (action.type === 'inc' ? state + 1 : state) - const { result } = renderHook(() => useReducer(reducer, 0)) + const { result } = renderHook(() => { + const [state, dispatch] = useReducer(reducer, 0) + return { state, dispatch } + }) - const [initialState, dispatch] = result.current + expect(result.current.state).toBe(0) - expect(initialState).toBe(0) + act(() => result.current.dispatch({ type: 'inc' })) - act(() => dispatch({ type: 'inc' })) - - const [state] = result.current - - expect(state).toBe(1) + expect(result.current.state).toBe(1) }) }) diff --git a/test/native/useState.test.js b/test/native/useState.test.js index 52086598..b59e64cd 100644 --- a/test/native/useState.test.js +++ b/test/native/useState.test.js @@ -2,23 +2,23 @@ import { useState } from 'react' import { renderHook, act } from 'src/native' describe('useState tests', () => { - test('should use setState value', () => { - const { result } = renderHook(() => useState('foo')) + test('should use state value', () => { + const { result } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) - const [value] = result.current - - expect(value).toBe('foo') + expect(result.current.value).toBe('foo') }) - test('should update setState value using setter', () => { - const { result } = renderHook(() => useState('foo')) - - const [_, setValue] = result.current - - act(() => setValue('bar')) + test('should update state value using setter', () => { + const { result } = renderHook(() => { + const [value, setValue] = useState('foo') + return { value, setValue } + }) - const [value] = result.current + act(() => result.current.setValue('bar')) - expect(value).toBe('bar') + expect(result.current.value).toBe('bar') }) }) From d2f410bc81ab278e13f427e3fad436b7396a0907 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 21:55:50 +1000 Subject: [PATCH 06/11] Don't remove container until after act is complete --- src/dom/pure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/pure.js b/src/dom/pure.js index 63e34cf9..a7ae06ff 100644 --- a/src/dom/pure.js +++ b/src/dom/pure.js @@ -25,8 +25,8 @@ function createRenderer() { unmount() { act(() => { ReactDOM.unmountComponentAtNode(container) - document.body.removeChild(container) }) + document.body.removeChild(container) }, act } From 0b747ac1added9086512118e04d8613d519870cf Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 22:02:52 +1000 Subject: [PATCH 07/11] Add root level imports for each renderer --- dom/index.js | 2 ++ dom/pure.js | 2 ++ native/index.js | 2 ++ native/pure.js | 2 ++ package.json | 3 +++ server/index.js | 2 ++ server/pure.js | 2 ++ 7 files changed, 15 insertions(+) create mode 100644 dom/index.js create mode 100644 dom/pure.js create mode 100644 native/index.js create mode 100644 native/pure.js create mode 100644 server/index.js create mode 100644 server/pure.js diff --git a/dom/index.js b/dom/index.js new file mode 100644 index 00000000..5b8693bf --- /dev/null +++ b/dom/index.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/dom' +module.exports = require('../lib/dom') diff --git a/dom/pure.js b/dom/pure.js new file mode 100644 index 00000000..c6e171cc --- /dev/null +++ b/dom/pure.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/dom/pure' +module.exports = require('../lib/dom/pure') diff --git a/native/index.js b/native/index.js new file mode 100644 index 00000000..bc2242a6 --- /dev/null +++ b/native/index.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/native' +module.exports = require('../lib/native') diff --git a/native/pure.js b/native/pure.js new file mode 100644 index 00000000..10dc143f --- /dev/null +++ b/native/pure.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/native/pure' +module.exports = require('../lib/native/pure') diff --git a/package.json b/package.json index c8c68003..75d656ed 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ "files": [ "lib", "src", + "dom", + "native", + "server", "pure.js", "dont-cleanup-after-each.js" ], diff --git a/server/index.js b/server/index.js new file mode 100644 index 00000000..21b80f69 --- /dev/null +++ b/server/index.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/server' +module.exports = require('../lib/server') diff --git a/server/pure.js b/server/pure.js new file mode 100644 index 00000000..a64c1790 --- /dev/null +++ b/server/pure.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/server/pure' +module.exports = require('../lib/server/pure') From 29acc54661a3b8ff86f4efd864ccb76e42966826 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 22:50:25 +1000 Subject: [PATCH 08/11] Auto-detect renderer when specific renderer is provided in import --- src/index.js | 6 +++++- src/pure.js | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 585816ce..00a60d57 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,5 @@ -export * from './native' +import { cleanup } from './core' + +cleanup.autoRegister() + +export * from './pure' diff --git a/src/pure.js b/src/pure.js index a4384083..bdda0c5e 100644 --- a/src/pure.js +++ b/src/pure.js @@ -1 +1,22 @@ -export * from './native/pure' +function hasDependency(name) { + try { + require(name) + return true + } catch { + return false + } +} + +const autoDetectableRenderers = [ + { required: 'react-dom', renderer: './dom/pure' }, + { required: 'react-test-renderer', renderer: './native/pure' } +] + +const validRenderers = autoDetectableRenderers.filter(({ required }) => hasDependency(required)) + +if (validRenderers.length === 0) { + const options = autoDetectableRenderers.map(({ required }) => ` - ${required}`).join('\n') + throw new Error(`Could not auto-detect a React renderer. Options are:\n${options}`) +} + +module.exports = require(validRenderers[0].renderer) From 9c0ae68da703ac4eb8e1733c66049eb3ccfde6a0 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 22:56:02 +1000 Subject: [PATCH 09/11] Auto register the exported cleanup instead of importing from core --- src/dom/index.js | 4 ++-- src/index.js | 4 ++-- src/native/index.js | 4 ++-- src/server/index.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/dom/index.js b/src/dom/index.js index 4a47e48a..d18e042c 100644 --- a/src/dom/index.js +++ b/src/dom/index.js @@ -1,5 +1,5 @@ -import { cleanup } from '../core' +import { renderHook, act, cleanup } from './pure' cleanup.autoRegister() -export * from './pure' +export { renderHook, act, cleanup } diff --git a/src/index.js b/src/index.js index 00a60d57..d18e042c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ -import { cleanup } from './core' +import { renderHook, act, cleanup } from './pure' cleanup.autoRegister() -export * from './pure' +export { renderHook, act, cleanup } diff --git a/src/native/index.js b/src/native/index.js index 4a47e48a..d18e042c 100644 --- a/src/native/index.js +++ b/src/native/index.js @@ -1,5 +1,5 @@ -import { cleanup } from '../core' +import { renderHook, act, cleanup } from './pure' cleanup.autoRegister() -export * from './pure' +export { renderHook, act, cleanup } diff --git a/src/server/index.js b/src/server/index.js index 4a47e48a..d18e042c 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,5 +1,5 @@ -import { cleanup } from '../core' +import { renderHook, act, cleanup } from './pure' cleanup.autoRegister() -export * from './pure' +export { renderHook, act, cleanup } From 98212170a17d595bb821d0f54b03f6692a85ebc0 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 8 Jun 2020 22:57:00 +1000 Subject: [PATCH 10/11] Renamed async-utils for consistency --- src/core/{asyncUtils.js => async-utils.js} | 0 src/core/index.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/core/{asyncUtils.js => async-utils.js} (100%) diff --git a/src/core/asyncUtils.js b/src/core/async-utils.js similarity index 100% rename from src/core/asyncUtils.js rename to src/core/async-utils.js diff --git a/src/core/index.js b/src/core/index.js index 1fc136ff..4645be71 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -1,5 +1,5 @@ import React from 'react' -import asyncUtils from './asyncUtils' +import asyncUtils from './async-utils' import { cleanup, addCleanup, removeCleanup } from './cleanup' function TestHook({ callback, hookProps, onError, children }) { From 78c55150000bdadb007c6ef9352dab2f46bdb3eb Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Mon, 13 Jul 2020 23:26:27 +1000 Subject: [PATCH 11/11] Refactor to remove React dependency from core logic --- custom/index.js | 2 ++ custom/pure.js | 2 ++ package.json | 3 +++ src/core/index.js | 35 +++++++++++++++++------------------ src/custom/index.js | 5 +++++ src/custom/pure.js | 8 ++++++++ src/dom/pure.js | 17 ++++++++++++----- src/native/pure.js | 17 +++++++++++------ src/pure.js | 2 +- src/server/pure.js | 24 ++++++++++++++++-------- test/dom/errorHook.test.js | 20 +++++++++----------- test/native/errorHook.test.js | 20 +++++++++----------- test/server/errorHook.test.js | 23 +++++++++++------------ 13 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 custom/index.js create mode 100644 custom/pure.js create mode 100644 src/custom/index.js create mode 100644 src/custom/pure.js diff --git a/custom/index.js b/custom/index.js new file mode 100644 index 00000000..11d50e45 --- /dev/null +++ b/custom/index.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/custom' +module.exports = require('../lib/custom') diff --git a/custom/pure.js b/custom/pure.js new file mode 100644 index 00000000..c98aee61 --- /dev/null +++ b/custom/pure.js @@ -0,0 +1,2 @@ +// makes it so people can import from '@testing-library/react-hooks/custom/pure' +module.exports = require('../lib/custom/pure') diff --git a/package.json b/package.json index 88b3ad5b..3a714e06 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,9 @@ "react-test-renderer": ">=16.9.0" }, "peerDependenciesMeta": { + "react": { + "optional": true + }, "react-dom": { "optional": true }, diff --git a/src/core/index.js b/src/core/index.js index 4645be71..114e5448 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -1,15 +1,14 @@ -import React from 'react' import asyncUtils from './async-utils' import { cleanup, addCleanup, removeCleanup } from './cleanup' -function TestHook({ callback, hookProps, onError, children }) { +function TestHook({ callback, setValue, setError, ...props }) { try { - children(callback(hookProps)) + setValue(callback(props)) } catch (err) { if (err.then) { throw err } else { - onError(err) + setError(err) } } return null @@ -48,28 +47,28 @@ function resultContainer() { } } +function defaultWrapper({ children }) { + return children +} + function createRenderHook(createRenderer) { - return function renderHook(callback, { initialProps, wrapper } = {}) { + return function renderHook(callback, { initialProps, wrapper = defaultWrapper } = {}) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } + const props = { callback, setValue, setError } + const options = { initialProps, wrapper } - const wrapUiIfNeeded = (innerElement) => - wrapper ? React.createElement(wrapper, hookProps.current, innerElement) : innerElement - - const toRender = () => - wrapUiIfNeeded( - - {setValue} - - ) - - let { render, rerender, unmount, act, ...rendererUtils } = createRenderer() + const { render, rerender, unmount, act, ...rendererUtils } = createRenderer( + TestHook, + props, + options + ) - render(toRender()) + render(hookProps.current) function rerenderHook(newProps = hookProps.current) { hookProps.current = newProps - rerender(toRender()) + rerender(hookProps.current) } function unmountHook() { diff --git a/src/custom/index.js b/src/custom/index.js new file mode 100644 index 00000000..ca4f465a --- /dev/null +++ b/src/custom/index.js @@ -0,0 +1,5 @@ +import { createCustomRenderer, cleanup } from './pure' + +cleanup.autoRegister() + +export { createCustomRenderer, cleanup } diff --git a/src/custom/pure.js b/src/custom/pure.js new file mode 100644 index 00000000..d6010705 --- /dev/null +++ b/src/custom/pure.js @@ -0,0 +1,8 @@ +import { createRenderHook, cleanup } from '../core' + +function createCustomRenderer(createRenderer) { + const renderHook = createRenderHook(createRenderer) + return { renderHook } +} + +export { createCustomRenderer, cleanup } diff --git a/src/dom/pure.js b/src/dom/pure.js index a7ae06ff..9d6689d4 100644 --- a/src/dom/pure.js +++ b/src/dom/pure.js @@ -7,19 +7,26 @@ function Fallback() { return null } -function createRenderer() { +function createRenderer(TestHook, testHookProps, { wrapper: Wrapper }) { const container = document.createElement('div') + const toRender = (props) => + console.log(props) || ( + + + + ) + return { - render(component) { + render(props) { document.body.appendChild(container) act(() => { - ReactDOM.render(}>{component}, container) + ReactDOM.render(}>{toRender(props)}, container) }) }, - rerender(component) { + rerender(props) { act(() => { - ReactDOM.render(}>{component}, container) + ReactDOM.render(}>{toRender(props)}, container) }) }, unmount() { diff --git a/src/native/pure.js b/src/native/pure.js index 8d891fc4..c57cad5d 100644 --- a/src/native/pure.js +++ b/src/native/pure.js @@ -5,19 +5,24 @@ import { createRenderHook, cleanup } from '../core' function Fallback() { return null } - -function createRenderer() { +function createRenderer(TestHook, testHookProps, { wrapper: Wrapper }) { let container + const toRender = (props) => ( + + + + ) + return { - render(component) { + render(props) { act(() => { - container = create(}>{component}) + container = create(}>{toRender(props)}) }) }, - rerender(component) { + rerender(props) { act(() => { - container.update(}>{component}) + container.update(}>{toRender(props)}) }) }, unmount() { diff --git a/src/pure.js b/src/pure.js index bdda0c5e..34450820 100644 --- a/src/pure.js +++ b/src/pure.js @@ -15,7 +15,7 @@ const autoDetectableRenderers = [ const validRenderers = autoDetectableRenderers.filter(({ required }) => hasDependency(required)) if (validRenderers.length === 0) { - const options = autoDetectableRenderers.map(({ required }) => ` - ${required}`).join('\n') + const options = autoDetectableRenderers.map(({ option }) => ` - ${option}`).join('\n') throw new Error(`Could not auto-detect a React renderer. Options are:\n${options}`) } diff --git a/src/server/pure.js b/src/server/pure.js index 10e95e00..07fed150 100644 --- a/src/server/pure.js +++ b/src/server/pure.js @@ -1,3 +1,4 @@ +import React from 'react' import ReactDOMServer from 'react-dom/server' import ReactDOM from 'react-dom' import { act as baseAct } from 'react-dom/test-utils' @@ -5,10 +6,17 @@ import { createRenderHook, cleanup } from '../core' let act -function createRenderer() { +function createRenderer(TestHook, testHookProps, { wrapper: Wrapper }) { const container = document.createElement('div') + + const toRender = (props) => ( + + + + ) + + let renderProps let hydrated = false - let ssrComponent act = function act(...args) { if (!hydrated) { @@ -18,10 +26,10 @@ function createRenderer() { } return { - render(component) { - ssrComponent = component + render(props) { + renderProps = props baseAct(() => { - const serverOutput = ReactDOMServer.renderToString(component) + const serverOutput = ReactDOMServer.renderToString(toRender(props)) container.innerHTML = serverOutput }) }, @@ -32,17 +40,17 @@ function createRenderer() { if (!hydrated) { document.body.appendChild(container) baseAct(() => { - ReactDOM.hydrate(ssrComponent, container) + ReactDOM.hydrate(toRender(renderProps), container) }) hydrated = true } }, - rerender(component) { + rerender(props) { if (!hydrated) { throw new Error('You must hydrate the component before you can rerender') } baseAct(() => { - ReactDOM.render(component, container) + ReactDOM.render(toRender(props), container) }) }, unmount() { diff --git a/test/dom/errorHook.test.js b/test/dom/errorHook.test.js index c2cbe49a..e7ced74b 100644 --- a/test/dom/errorHook.test.js +++ b/test/dom/errorHook.test.js @@ -48,13 +48,13 @@ describe('error hook tests', () => { }) test('should reset error', () => { - const { result, rerender } = renderHook((throwError) => useError(throwError), { - initialProps: true + const { result, rerender } = renderHook(({ throwError }) => useError(throwError), { + initialProps: { throwError: true } }) expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) expect(result.current).not.toBe(undefined) expect(result.error).toBe(undefined) @@ -91,17 +91,15 @@ describe('error hook tests', () => { test('should reset async error', async () => { const { result, waitForNextUpdate, rerender } = renderHook( - (throwError) => useAsyncError(throwError), - { - initialProps: true - } + ({ throwError }) => useAsyncError(throwError), + { initialProps: { throwError: true } } ) await waitForNextUpdate() expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) await waitForNextUpdate() @@ -137,13 +135,13 @@ describe('error hook tests', () => { }) test('should reset effect error', () => { - const { result, rerender } = renderHook((throwError) => useEffectError(throwError), { - initialProps: true + const { result, rerender } = renderHook(({ throwError }) => useEffectError(throwError), { + initialProps: { throwError: true } }) expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) expect(result.current).not.toBe(undefined) expect(result.error).toBe(undefined) diff --git a/test/native/errorHook.test.js b/test/native/errorHook.test.js index 0937a5e4..a465a8e2 100644 --- a/test/native/errorHook.test.js +++ b/test/native/errorHook.test.js @@ -48,13 +48,13 @@ describe('error hook tests', () => { }) test('should reset error', () => { - const { result, rerender } = renderHook((throwError) => useError(throwError), { - initialProps: true + const { result, rerender } = renderHook(({ throwError }) => useError(throwError), { + initialProps: { throwError: true } }) expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) expect(result.current).not.toBe(undefined) expect(result.error).toBe(undefined) @@ -91,17 +91,15 @@ describe('error hook tests', () => { test('should reset async error', async () => { const { result, waitForNextUpdate, rerender } = renderHook( - (throwError) => useAsyncError(throwError), - { - initialProps: true - } + ({ throwError }) => useAsyncError(throwError), + { initialProps: { throwError: true } } ) await waitForNextUpdate() expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) await waitForNextUpdate() @@ -137,13 +135,13 @@ describe('error hook tests', () => { }) test('should reset effect error', () => { - const { result, rerender } = renderHook((throwError) => useEffectError(throwError), { - initialProps: true + const { result, rerender } = renderHook(({ throwError }) => useEffectError(throwError), { + initialProps: { throwError: true } }) expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) expect(result.current).not.toBe(undefined) expect(result.error).toBe(undefined) diff --git a/test/server/errorHook.test.js b/test/server/errorHook.test.js index cd88d5fd..616a17c4 100644 --- a/test/server/errorHook.test.js +++ b/test/server/errorHook.test.js @@ -48,15 +48,15 @@ describe('error hook tests', () => { }) test('should reset error', () => { - const { result, hydrate, rerender } = renderHook((throwError) => useError(throwError), { - initialProps: true + const { result, hydrate, rerender } = renderHook(({ throwError }) => useError(throwError), { + initialProps: { throwError: true } }) expect(result.error).not.toBe(undefined) hydrate() - rerender(false) + rerender({ throwError: false }) expect(result.current).not.toBe(undefined) expect(result.error).toBe(undefined) @@ -99,10 +99,8 @@ describe('error hook tests', () => { test('should reset async error', async () => { const { result, hydrate, waitForNextUpdate, rerender } = renderHook( - (throwError) => useAsyncError(throwError), - { - initialProps: true - } + ({ throwError }) => useAsyncError(throwError), + { initialProps: { throwError: true } } ) hydrate() @@ -111,7 +109,7 @@ describe('error hook tests', () => { expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) await waitForNextUpdate() @@ -154,15 +152,16 @@ describe('error hook tests', () => { }) test('should reset effect error', () => { - const { result, hydrate, rerender } = renderHook((throwError) => useEffectError(throwError), { - initialProps: true - }) + const { result, hydrate, rerender } = renderHook( + ({ throwError }) => useEffectError(throwError), + { initialProps: { throwError: true } } + ) hydrate() expect(result.error).not.toBe(undefined) - rerender(false) + rerender({ throwError: false }) expect(result.current).not.toBe(undefined) expect(result.error).toBe(undefined)