From 449a0f2896cc79dbc17d75ae773e62b2d5e30735 Mon Sep 17 00:00:00 2001 From: Brendon Lopes Date: Mon, 25 Apr 2022 17:26:35 -0300 Subject: [PATCH 1/4] feat: Create header and template for the length slider. Implemented first tests for the Header. Also created the initial reducers configurations for the slider. --- package-lock.json | 275 ++++++++++++++++++++++++++++++++ package.json | 2 + src/App.js | 25 --- src/App.jsx | 14 ++ src/App.test.js | 10 +- src/components/Header.jsx | 10 ++ src/components/LengthSlider.jsx | 24 +++ src/index.js | 6 +- src/slices/lengthSlice.js | 22 +++ src/store/index.js | 10 ++ 10 files changed, 367 insertions(+), 31 deletions(-) delete mode 100644 src/App.js create mode 100644 src/App.jsx create mode 100644 src/components/Header.jsx create mode 100644 src/components/LengthSlider.jsx create mode 100644 src/slices/lengthSlice.js create mode 100644 src/store/index.js diff --git a/package-lock.json b/package-lock.json index 6c5205a..f5053a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,13 @@ "name": "exercise-password-generator", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^1.8.1", "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-redux": "^8.0.1", "react-scripts": "4.0.3", "web-vitals": "^2.1.4" }, @@ -2667,6 +2669,38 @@ "node": ">=10" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz", + "integrity": "sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==", + "dependencies": { + "immer": "^9.0.7", + "redux": "^4.1.2", + "redux-thunk": "^2.4.1", + "reselect": "^4.1.5" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.0-beta" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", + "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", @@ -3242,6 +3276,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", @@ -3318,11 +3361,26 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==" }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, "node_modules/@types/q": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" }, + "node_modules/@types/react": { + "version": "18.0.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.7.tgz", + "integrity": "sha512-CXSXHzTexlX9esf4ReIUJeaemKcmBEvYzxHDUk19c3BCcEGUvUjkeC3jkscPSfSaQ6SPDRNd/zMxi8oc/P1zxA==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -3331,6 +3389,11 @@ "@types/node": "*" } }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -3370,6 +3433,11 @@ "node": ">=0.10.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -6484,6 +6552,11 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, + "node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, "node_modules/cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -9979,6 +10052,19 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -18757,6 +18843,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz", + "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.0.0.tgz", + "integrity": "sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw==" + }, "node_modules/react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -19385,6 +19514,22 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -19583,6 +19728,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "node_modules/reselect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", + "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -22776,6 +22926,14 @@ "node": ">=0.10.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz", + "integrity": "sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0-rc" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -26592,6 +26750,24 @@ } } }, + "@reduxjs/toolkit": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz", + "integrity": "sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==", + "requires": { + "immer": "^9.0.7", + "redux": "^4.1.2", + "redux-thunk": "^2.4.1", + "reselect": "^4.1.5" + }, + "dependencies": { + "immer": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", + "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==" + } + } + }, "@rollup/plugin-node-resolve": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", @@ -26990,6 +27166,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", @@ -27066,11 +27251,26 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==" }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, "@types/q": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" }, + "@types/react": { + "version": "18.0.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.7.tgz", + "integrity": "sha512-CXSXHzTexlX9esf4ReIUJeaemKcmBEvYzxHDUk19c3BCcEGUvUjkeC3jkscPSfSaQ6SPDRNd/zMxi8oc/P1zxA==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -27079,6 +27279,11 @@ "@types/node": "*" } }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -27117,6 +27322,11 @@ } } }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -29553,6 +29763,11 @@ } } }, + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -32218,6 +32433,21 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -38817,6 +39047,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz", + "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.0.0.tgz", + "integrity": "sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw==" + } + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -39216,6 +39466,20 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -39370,6 +39634,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", + "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -41864,6 +42133,12 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-sync-external-store": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz", + "integrity": "sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw==", + "requires": {} + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/package.json b/package.json index 4593872..3280fdd 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,13 @@ "node": "16" }, "dependencies": { + "@reduxjs/toolkit": "^1.8.1", "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-redux": "^8.0.1", "react-scripts": "4.0.3", "web-vitals": "^2.1.4" }, diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 3784575..0000000 --- a/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..30ac58d --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Header from './components/Header'; +import LengthSlider from './components/LengthSlider'; + +function App() { + return ( +
+
+ +
+ ); +} + +export default App; diff --git a/src/App.test.js b/src/App.test.js index 1f03afe..54adf48 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,8 +1,10 @@ import { render, screen } from '@testing-library/react'; import App from './App'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); +describe('Header tests', () => { + it('renders header', () => { + render(); + const headerElement = screen.getByText(/password generator/i); + expect(headerElement).toBeInTheDocument(); + }); }); diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 0000000..e57f927 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,10 @@ +function Header() { + return ( +
+

Password Generator

+
CLICK GENERATE
+
+ ); +} + +export default Header; \ No newline at end of file diff --git a/src/components/LengthSlider.jsx b/src/components/LengthSlider.jsx new file mode 100644 index 0000000..0b79eff --- /dev/null +++ b/src/components/LengthSlider.jsx @@ -0,0 +1,24 @@ + + +function LengthSlider() { + const handleChange = ({ target: { value } }) => { + console.log(value) + } + + return ( +
+

LENGTH: 4

+
+ +
+
+ ); +} + +export default LengthSlider; \ No newline at end of file diff --git a/src/index.js b/src/index.js index ef2edf8..bd1b607 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,13 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import store from './store'; +import { Provider } from 'react-redux'; ReactDOM.render( - + - , + , document.getElementById('root') ); diff --git a/src/slices/lengthSlice.js b/src/slices/lengthSlice.js new file mode 100644 index 0000000..5aadcaf --- /dev/null +++ b/src/slices/lengthSlice.js @@ -0,0 +1,22 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + value: 4, +}; + +export const lengthSlice = createSlice({ + name: 'length', + initialState, + reducers: { + increment: (state) => { + state.value += 1; + }, + decrement: (state) => { + state.value -= 1; + }, + }, +}); + +export const { increment, decrement } = lengthSlice.actions; + +export default lengthSlice.reducer; diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..95a28d5 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; +import lengthReducer from '../slices/lengthSlice'; + +const store = configureStore({ + reducer: { + length: lengthReducer, + }, +}); + +export default store; From 47b47a515c064228b51a86332c3ec99bee075e44 Mon Sep 17 00:00:00 2001 From: Brendon Lopes Date: Sun, 1 May 2022 21:43:26 -0300 Subject: [PATCH 2/4] Gerador funcionando como esperado --- .eslintrc.json | 9 +++- src/App.jsx | 4 ++ src/App.test.js | 10 ----- src/__tests__/Header.test.js | 17 ++++++++ src/__tests__/LengthSlider.test.js | 21 ++++++++++ src/components/GeneratePasswordButton.jsx | 27 ++++++++++++ src/components/LengthSlider.jsx | 20 ++++++--- src/components/Settings.jsx | 50 +++++++++++++++++++++++ src/components/SettingsToggle.jsx | 14 +++++++ src/functions/generateRandomIndex.js | 6 +++ src/functions/getRandomCharacter.js | 10 +++++ src/functions/passwordGenerator.js | 34 +++++++++++++++ src/helpers/test-utils.jsx | 28 +++++++++++++ src/slices/lengthSlice.js | 9 ++-- src/slices/settingsSlice.js | 33 +++++++++++++++ src/store/index.js | 2 + 16 files changed, 270 insertions(+), 24 deletions(-) delete mode 100644 src/App.test.js create mode 100644 src/__tests__/Header.test.js create mode 100644 src/__tests__/LengthSlider.test.js create mode 100644 src/components/GeneratePasswordButton.jsx create mode 100644 src/components/Settings.jsx create mode 100644 src/components/SettingsToggle.jsx create mode 100644 src/functions/generateRandomIndex.js create mode 100644 src/functions/getRandomCharacter.js create mode 100644 src/functions/passwordGenerator.js create mode 100644 src/helpers/test-utils.jsx create mode 100644 src/slices/settingsSlice.js diff --git a/.eslintrc.json b/.eslintrc.json index cf533ce..f4bdbb2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,6 +13,11 @@ }, "plugins": [ "react", - "sonarjs" - ] + "sonarjs", + "react-hooks" + ], + "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } } \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 30ac58d..f6c6273 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,16 @@ import React from 'react'; import Header from './components/Header'; +import Settings from './components/Settings'; import LengthSlider from './components/LengthSlider'; +import GeneratePasswordButton from './components/GeneratePasswordButton'; function App() { return (
+ +
); } diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 54adf48..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,10 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -describe('Header tests', () => { - it('renders header', () => { - render(); - const headerElement = screen.getByText(/password generator/i); - expect(headerElement).toBeInTheDocument(); - }); -}); diff --git a/src/__tests__/Header.test.js b/src/__tests__/Header.test.js new file mode 100644 index 0000000..d2c03ee --- /dev/null +++ b/src/__tests__/Header.test.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { render, screen } from '../helpers/test-utils'; +import App from '../App'; + +describe('Header tests', () => { + it('renders header', () => { + render(); + const headerElement = screen.getByText(/password generator/i); + expect(headerElement).toBeInTheDocument(); + }); + + it('renders placeholder text', () => { + render(); + const placeholderText = screen.getByText(/click generate/i); + expect(placeholderText).toBeInTheDocument(); + }); +}); diff --git a/src/__tests__/LengthSlider.test.js b/src/__tests__/LengthSlider.test.js new file mode 100644 index 0000000..82da2af --- /dev/null +++ b/src/__tests__/LengthSlider.test.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { render, screen, fireEvent } from '../helpers/test-utils'; +import App from '../App'; + +describe('Length slider tests', () => { + it('renders length slider', () => { + render(); + const lengthSliderElement = screen.getByLabelText(/length: 4/i); + expect(lengthSliderElement).toBeInTheDocument(); + }); + + it('changes the "length" value correctly after changing the slider value', () => { + render(); + const newValue = '5'; + const lengthSliderElement = screen.getByLabelText(/length: 4/i); + fireEvent.change(lengthSliderElement, { target: { value: newValue } }); + expect(lengthSliderElement).toHaveValue(newValue); + const labelWithNewValue = screen.getByText(/length: 5/i); + expect(labelWithNewValue).toBeInTheDocument(); + }); +}); diff --git a/src/components/GeneratePasswordButton.jsx b/src/components/GeneratePasswordButton.jsx new file mode 100644 index 0000000..c05fdff --- /dev/null +++ b/src/components/GeneratePasswordButton.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import passwordGenerator from '../functions/passwordGenerator'; + +function GeneratePasswordButton() { + const { value } = useSelector((state) => state.length); + const { settings } = useSelector((state) => state); + + const handleClick = () => { + try { + passwordGenerator(value, settings); + } catch (error) { + alert('Escolha pelo menos uma opção'); + } + } + + return ( + + ); +} + +export default GeneratePasswordButton; \ No newline at end of file diff --git a/src/components/LengthSlider.jsx b/src/components/LengthSlider.jsx index 0b79eff..25e785b 100644 --- a/src/components/LengthSlider.jsx +++ b/src/components/LengthSlider.jsx @@ -1,22 +1,30 @@ - +import { useSelector, useDispatch } from 'react-redux'; +import * as actionCreators from '../slices/lengthSlice'; +import { bindActionCreators } from '@reduxjs/toolkit' function LengthSlider() { + const value = useSelector((state) => state.length.value); + const dispatch = useDispatch(); + + const { changeValue } = bindActionCreators(actionCreators, dispatch); + const handleChange = ({ target: { value } }) => { - console.log(value) + changeValue(value); } return (
-

LENGTH: 4

-
+
+
); } diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx new file mode 100644 index 0000000..ad8d422 --- /dev/null +++ b/src/components/Settings.jsx @@ -0,0 +1,50 @@ +import { useSelector, useDispatch } from 'react-redux'; +import SettingsToggle from './SettingsToggle'; +import * as actionCreators from '../slices/settingsSlice'; +import { bindActionCreators } from '@reduxjs/toolkit' + +function Settings() { + const { + upperCase, lowerCase, numbers, symbols + } = useSelector((state) => state.settings); + + const dispatch = useDispatch(); + + const { + toggleUpperCase, + toggleLowerCase, + toggleNumbers, + toggleSymbols, + } = bindActionCreators(actionCreators, dispatch); + + return ( +
+

SETTINGS

+ +
+ +
+ +
+ +
+ ); +} + +export default Settings; \ No newline at end of file diff --git a/src/components/SettingsToggle.jsx b/src/components/SettingsToggle.jsx new file mode 100644 index 0000000..84ba9fa --- /dev/null +++ b/src/components/SettingsToggle.jsx @@ -0,0 +1,14 @@ +function SettingsToggle({ labelText, checked, toggle }) { + return ( + + ); +} + +export default SettingsToggle; \ No newline at end of file diff --git a/src/functions/generateRandomIndex.js b/src/functions/generateRandomIndex.js new file mode 100644 index 0000000..499c826 --- /dev/null +++ b/src/functions/generateRandomIndex.js @@ -0,0 +1,6 @@ +const generateRandomIndex = (max) => { + const randomNumber = Math.floor(Math.random() * (max + 1)); + return randomNumber; +}; + +export default generateRandomIndex; diff --git a/src/functions/getRandomCharacter.js b/src/functions/getRandomCharacter.js new file mode 100644 index 0000000..9eea1ef --- /dev/null +++ b/src/functions/getRandomCharacter.js @@ -0,0 +1,10 @@ +import generateRandomIndex from './generateRandomIndex'; + +const getRandomCharacter = (characters) => { + const charactersArray = characters.split(''); + const randomIndex = generateRandomIndex(charactersArray.length - 1); + const randomCharacter = charactersArray[randomIndex]; + return randomCharacter; +}; + +export default getRandomCharacter; diff --git a/src/functions/passwordGenerator.js b/src/functions/passwordGenerator.js new file mode 100644 index 0000000..ecfb493 --- /dev/null +++ b/src/functions/passwordGenerator.js @@ -0,0 +1,34 @@ +import getRandomCharacter from './getRandomCharacter'; +import generateRandomIndex from './generateRandomIndex'; + +const characters = { + 0: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 1: 'abcdefghijklmnopqrstuvwxyz', + 2: '0123456789', + 3: '!@#$%^&*()_+-=[]{}|;\':",./<>?', +}; + +const passwordGenerator = (value, settings) => { + const { upperCase, lowerCase, numbers, symbols } = settings; + const settingsArray = [upperCase, lowerCase, numbers, symbols]; + + const selectedOptions = settingsArray.reduce((acc, curr, index) => { + if (curr) acc.push(index); + return acc; + }, []); + + let password = ''; + + for (let i = 0; i < value; i += 1) { + const randomIndex = generateRandomIndex(selectedOptions.length - 1); + const index = selectedOptions[randomIndex]; + const newCharacter = getRandomCharacter(characters[index]); + password += newCharacter; + } + + console.log('value: ', value, upperCase, lowerCase, numbers, symbols); + console.log('password: ', password); + console.log('selectedOptions: ', selectedOptions); +}; + +export default passwordGenerator; diff --git a/src/helpers/test-utils.jsx b/src/helpers/test-utils.jsx new file mode 100644 index 0000000..630bce5 --- /dev/null +++ b/src/helpers/test-utils.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { render as rtlRender } from '@testing-library/react'; +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; +import lengthReducer from '../slices/lengthSlice'; +import settingsReducer from '../slices/settingsSlice'; + +const render = ( + ui, + { + preloadedState, + store = configureStore( + { reducer: { length: lengthReducer, settings: settingsReducer }, preloadedState }, + ), + ...renderOptions + } = {}, +) => { + // eslint-disable-next-line react/prop-types + const Wrapper = ({ children }) => ( + {children} + ); + return rtlRender(ui, { wrapper: Wrapper, ...renderOptions }); +}; + +// re-export everything +export * from '@testing-library/react'; +// override render method +export { render }; diff --git a/src/slices/lengthSlice.js b/src/slices/lengthSlice.js index 5aadcaf..f8b0375 100644 --- a/src/slices/lengthSlice.js +++ b/src/slices/lengthSlice.js @@ -8,15 +8,12 @@ export const lengthSlice = createSlice({ name: 'length', initialState, reducers: { - increment: (state) => { - state.value += 1; - }, - decrement: (state) => { - state.value -= 1; + changeValue: (state, { payload }) => { + state.value = payload; }, }, }); -export const { increment, decrement } = lengthSlice.actions; +export const { changeValue } = lengthSlice.actions; export default lengthSlice.reducer; diff --git a/src/slices/settingsSlice.js b/src/slices/settingsSlice.js new file mode 100644 index 0000000..27e2293 --- /dev/null +++ b/src/slices/settingsSlice.js @@ -0,0 +1,33 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + upperCase: true, + lowerCase: true, + numbers: false, + symbols: false, +}; + +export const settingsSlice = createSlice({ + name: 'settings', + initialState, + reducers: { + toggleUpperCase: (state) => { + state.upperCase = !state.upperCase; + }, + toggleLowerCase: (state) => { + state.lowerCase = !state.lowerCase; + }, + toggleNumbers: (state) => { + state.numbers = !state.numbers; + }, + toggleSymbols: (state) => { + state.symbols = !state.symbols; + }, + }, +}); + +export const { + toggleUpperCase, toggleLowerCase, toggleNumbers, toggleSymbols, +} = settingsSlice.actions; + +export default settingsSlice.reducer; diff --git a/src/store/index.js b/src/store/index.js index 95a28d5..2ad5841 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,9 +1,11 @@ import { configureStore } from '@reduxjs/toolkit'; import lengthReducer from '../slices/lengthSlice'; +import settingsReducer from '../slices/settingsSlice'; const store = configureStore({ reducer: { length: lengthReducer, + settings: settingsReducer, }, }); From b84f24cabd8495923015e643201024cce82f834b Mon Sep 17 00:00:00 2001 From: Brendon Lopes Date: Sun, 1 May 2022 22:50:21 -0300 Subject: [PATCH 3/4] =?UTF-8?q?senha=20adicionada=20na=20tela=20ao=20clica?= =?UTF-8?q?r=20no=20bot=C3=A3o,=20tamb=C3=A9m=20=C3=A9=20adicionada=20ao?= =?UTF-8?q?=20local=20storage=20a=20ultima=20senha=20gerada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/GeneratePasswordButton.jsx | 21 ++++++++++++++++++--- src/components/Header.jsx | 6 +++++- src/functions/passwordGenerator.js | 7 ++----- src/slices/passwordSlice.js | 19 +++++++++++++++++++ src/store/index.js | 2 ++ 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 src/slices/passwordSlice.js diff --git a/src/components/GeneratePasswordButton.jsx b/src/components/GeneratePasswordButton.jsx index c05fdff..a4fe9fb 100644 --- a/src/components/GeneratePasswordButton.jsx +++ b/src/components/GeneratePasswordButton.jsx @@ -1,19 +1,34 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; +import React, { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import * as actionCreators from '../slices/passwordSlice'; +import { bindActionCreators } from '@reduxjs/toolkit'; import passwordGenerator from '../functions/passwordGenerator'; function GeneratePasswordButton() { const { value } = useSelector((state) => state.length); const { settings } = useSelector((state) => state); + const dispatch = useDispatch(); + + const { setPassword } = bindActionCreators(actionCreators, dispatch); + const handleClick = () => { try { - passwordGenerator(value, settings); + const generatedPassword = passwordGenerator(value, settings); + setPassword(generatedPassword); + localStorage.setItem('lastPassword', JSON.stringify(generatedPassword)); } catch (error) { alert('Escolha pelo menos uma opção'); } } + useEffect(() => { + const lastPassword = JSON.parse(localStorage.getItem('lastPassword')); + if (lastPassword) { + setPassword(lastPassword); + } + }, []) + return (