Compare commits

...

14 Commits

Author SHA1 Message Date
dc1b459c56 Adds tests for unauthenticated app
All checks were successful
gitea-deepak/gog_frontend/pipeline/head This commit looks good
2021-01-31 18:30:16 -06:00
8a088b718a Adds some snaps and gets set to add router 2021-01-31 16:30:36 -06:00
35777252d0 Adds user service to get people's names and things
All checks were successful
gitea-deepak/gog_frontend/pipeline/head This commit looks good
2021-01-24 20:02:38 -06:00
a4b4cddb36 Adds snapshot tests for authenticated app and unauthenticated app components, which now logout properly
All checks were successful
gitea-deepak/gog_frontend/pipeline/head This commit looks good
2021-01-24 19:10:56 -06:00
aa89bf9ca6 fmt changes 2021-01-24 14:54:55 -06:00
ec673515b8 Adds command to update jest snapshots
All checks were successful
gitea-deepak/gog_frontend/pipeline/head This commit looks good
2021-01-24 13:15:44 -06:00
93eea91ae6 Adds test for register.jsx and snapshot 2021-01-24 13:15:32 -06:00
55a8b26a7d Adds login tests
All checks were successful
gitea-deepak/gog_frontend/pipeline/head This commit looks good
2021-01-23 19:34:23 -06:00
4810a6ed74 Adds testing-library eslint plugin 2021-01-23 18:56:26 -06:00
6da0c2b84a Adds snapshot and begins to add login component tests
All checks were successful
gitea-deepak/gog_frontend/pipeline/head This commit looks good
2021-01-23 18:53:53 -06:00
ea710c3845 Adds services directory and env var flag- 2021-01-18 17:57:44 -06:00
3998d43041 Adds jest env var setup
Some checks failed
gitea-deepak/gog_frontend/pipeline/head There was a failure building this commit
2021-01-18 17:56:22 -06:00
d680d16a76 Adds more info to jest coverage 2021-01-18 17:55:56 -06:00
b78929b566 initial commit for auth context stuff
Some checks failed
gitea-deepak/gog_frontend/pipeline/head There was a failure building this commit
2021-01-18 10:27:45 -06:00
38 changed files with 1150 additions and 232 deletions

View File

@@ -9,6 +9,7 @@ extends:
- plugin:jest/style
- prettier
- prettier/standard
- plugin:react-hooks/recommended
parserOptions:
ecmaFeatures:
jsx: true
@@ -17,6 +18,7 @@ parserOptions:
plugins:
- react
- jest
- testing-library
rules:
no-tabs: 0
indent:

6
do.sh
View File

@@ -11,7 +11,7 @@ build() {
run() {
echo "I am ${FUNCNAME[0]}ning"
npx webpack serve
API_ROOT=http://localhost:3000/api npx webpack serve
}
fmt() {
@@ -26,6 +26,10 @@ _jest() {
npx jest --ci --coverage
}
_jestUpdateSnaps() {
npx jest -u
}
test() {
echo "I am ${FUNCNAME[0]}ing"
_lint && _jest

1
jest/setEnvVars.js Normal file
View File

@@ -0,0 +1 @@
process.env.API_ROOT = "http://localhost:8080/";

463
package-lock.json generated
View File

@@ -1100,8 +1100,17 @@
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@babel/runtime-corejs3": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz",
"integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==",
"dev": true,
"requires": {
"core-js-pure": "^3.0.0",
"regenerator-runtime": "^0.13.4"
}
},
@@ -1919,6 +1928,165 @@
"@sinonjs/commons": "^1.7.0"
}
},
"@testing-library/dom": {
"version": "7.29.4",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz",
"integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^4.2.0",
"aria-query": "^4.2.2",
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.4",
"lz-string": "^1.4.4",
"pretty-format": "^26.6.2"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"@testing-library/jest-dom": {
"version": "5.11.9",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.9.tgz",
"integrity": "sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.9.2",
"@types/testing-library__jest-dom": "^5.9.1",
"aria-query": "^4.2.2",
"chalk": "^3.0.0",
"css": "^3.0.0",
"css.escape": "^1.5.1",
"lodash": "^4.17.15",
"redent": "^3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"@testing-library/react": {
"version": "11.2.3",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.3.tgz",
"integrity": "sha512-BirBUGPkTW28ULuCwIbYo0y2+0aavHczBT6N9r3LrsswEW3pg25l1wgoE7I8QBIy1upXWkwKpYdWY7NYYP0Bxw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.5",
"@testing-library/dom": "^7.28.1"
}
},
"@testing-library/user-event": {
"version": "12.6.2",
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.2.tgz",
"integrity": "sha512-4OsiTSo2vbQm+eOnm1un8b9i2Re4mn+D7d7ET6HXtzYKY7vPe3O01iYKRmSW9vS5mNrQcCLwvRhVq1gWs5YGKA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.5"
}
},
"@types/aria-query": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==",
"dev": true
},
"@types/babel__core": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
@@ -2029,6 +2197,16 @@
"@types/istanbul-lib-report": "*"
}
},
"@types/jest": {
"version": "26.0.20",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz",
"integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==",
"dev": true,
"requires": {
"jest-diff": "^26.0.0",
"pretty-format": "^26.0.0"
}
},
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
@@ -2071,6 +2249,15 @@
"integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==",
"dev": true
},
"@types/testing-library__jest-dom": {
"version": "5.9.5",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz",
"integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==",
"dev": true,
"requires": {
"@types/jest": "*"
}
},
"@types/yargs": {
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
@@ -2552,6 +2739,16 @@
"sprintf-js": "~1.0.2"
}
},
"aria-query": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
"integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.10.2",
"@babel/runtime-corejs3": "^7.10.2"
}
},
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -3574,6 +3771,12 @@
}
}
},
"core-js-pure": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz",
"integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -3591,6 +3794,35 @@
"which": "^2.0.1"
}
},
"css": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
"dev": true,
"requires": {
"inherits": "^2.0.4",
"source-map": "^0.6.1",
"source-map-resolve": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-resolve": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
"dev": true,
"requires": {
"atob": "^2.1.2",
"decode-uri-component": "^0.2.0"
}
}
}
},
"css-loader": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.0.1.tgz",
@@ -3653,6 +3885,12 @@
}
}
},
"css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
"dev": true
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -4001,6 +4239,12 @@
"esutils": "^2.0.2"
}
},
"dom-accessibility-api": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz",
"integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==",
"dev": true
},
"dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
@@ -4590,6 +4834,97 @@
}
}
},
"eslint-plugin-react-hooks": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
"integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
"dev": true
},
"eslint-plugin-testing-library": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz",
"integrity": "sha512-nQIFe2muIFv2oR2zIuXE4vTbcFNx8hZKRzgHZqJg8rfopIWwoTwtlbCCNELT/jXzVe1uZF68ALGYoDXjLczKiQ==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "^3.10.1"
},
"dependencies": {
"@typescript-eslint/experimental-utils": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz",
"integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/types": "3.10.1",
"@typescript-eslint/typescript-estree": "3.10.1",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/types": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz",
"integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz",
"integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==",
"dev": true,
"requires": {
"@typescript-eslint/types": "3.10.1",
"@typescript-eslint/visitor-keys": "3.10.1",
"debug": "^4.1.1",
"glob": "^7.1.6",
"is-glob": "^4.0.1",
"lodash": "^4.17.15",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/visitor-keys": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz",
"integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -5501,6 +5836,19 @@
}
}
},
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"requires": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^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",
@@ -5676,6 +6024,12 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
@@ -8100,6 +8454,12 @@
"yallist": "^4.0.0"
}
},
"lz-string": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
"dev": true
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -8230,6 +8590,21 @@
"dom-walk": "^0.1.0"
}
},
"min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true
},
"mini-create-react-context": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
"requires": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -9256,6 +9631,52 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-router": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
"requires": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"requires": {
"isarray": "0.0.1"
}
}
}
},
"react-router-dom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
"requires": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
}
},
"react-shallow-renderer": {
"version": "16.14.1",
"resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz",
@@ -9393,6 +9814,16 @@
"resolve": "^1.9.0"
}
},
"redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"requires": {
"indent-string": "^4.0.0",
"strip-indent": "^3.0.0"
}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -9411,8 +9842,7 @@
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
},
"regenerator-transform": {
"version": "0.14.5",
@@ -9631,6 +10061,11 @@
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true
},
"resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -10610,6 +11045,15 @@
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true
},
"strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"requires": {
"min-indent": "^1.0.0"
}
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -10877,6 +11321,16 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"tiny-invariant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
@@ -11231,6 +11685,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -10,6 +10,9 @@
"name": "Deepak Mallubhotla"
},
"jest": {
"setupFiles": [
"<rootDir>/jest/setEnvVars.js"
],
"reporters": [
"default",
"jest-junit"
@@ -17,7 +20,13 @@
"coverageReporters": [
"text",
"cobertura"
]
],
"collectCoverage": true,
"collectCoverageFrom": [
"<rootDir>/src/**/*.jsx",
"!<rootDir>/src/**/*.test.jsx"
],
"coverageProvider": "babel"
},
"private": "true",
"license": "ISC",
@@ -26,6 +35,9 @@
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.3",
"@testing-library/user-event": "^12.6.2",
"babel-loader": "^8.2.2",
"css-loader": "^5.0.1",
"eslint": "^7.17.0",
@@ -36,6 +48,8 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.10.1",
"jest": "^26.6.3",
"jest-junit": "^12.0.0",
"prettier": "2.2.1",
@@ -49,6 +63,10 @@
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-hot-loader": "^4.13.0"
}
"react-hot-loader": "^4.13.0",
"react-router-dom": "^5.2.0"
},
"browserslist": [
"since 2017-06"
]
}

View File

@@ -1,19 +1,19 @@
import React from "react";
import { hot } from "react-hot-loader";
import "./App.css";
import AllPlansComponent from "./components/AllPlansComponent.jsx";
import { useAuth } from "./context/AuthContext";
import UnauthenticatedApp from "./components/UnauthenticatedApp";
import AuthenticatedApp from "./components/AuthenticatedApp";
class App extends React.Component {
render() {
return (
<div className="App">
<h1> Hello, World! </h1>
<div className="mainContent">
<AllPlansComponent />
</div>
</div>
);
}
function App() {
const { user } = useAuth();
console.log(user);
return (
<div className="App">
<h1>Frontend</h1>
{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
</div>
);
}
export default hot(module)(App);

View File

@@ -1,30 +0,0 @@
.actionID,
.actionDescription,
.actionChunks {
margin-left: 1rem;
margin-right: 1rem;
}
.actionCompleted {
flex: 1;
}
.actionChunks {
flex: 4;
}
.actionID {
flex: 4;
}
.actionDescription {
flex: 8;
}
.actionWrapper {
align-items: flex-end;
display: flex;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
padding-top: 1rem;
}

View File

@@ -1,29 +0,0 @@
import React from "react";
import { action } from "../types";
import "./Action.css";
class Action extends React.Component {
render() {
const action = this.props.action;
const completed = "completed_on" in action;
console.log([action.completed_on, completed]);
return (
<div className="actionWrapper">
<div className="actionCompleted">{completed ? "X" : " "}</div>
<div className="actionID">
ID: {action.action_id} | {action.plan_id}
</div>
<div className="actionDescription">{action.action_description}</div>
<div className="actionChunks">
{action.completed_chunks}/{action.estimated_chunks}
</div>
</div>
);
}
}
Action.propTypes = {
action: action,
};
export default Action;

View File

@@ -1,3 +0,0 @@
test("trying it out", () => {
expect(true).toEqual(true);
});

View File

@@ -1,28 +0,0 @@
import React from "react";
import Action from "./Action.jsx";
import PropTypes from "prop-types";
import { action } from "../types";
class ActionsContainer extends React.Component {
render() {
return (
<div className="ActionsListWrapper">
<ul className="ActionsList">
{this.props.actions.map((action) => {
return (
<li className="Action" key={action.action_id}>
<Action action={action} />
</li>
);
})}
</ul>
</div>
);
}
}
ActionsContainer.propTypes = {
actions: PropTypes.arrayOf(action),
};
export default ActionsContainer;

View File

@@ -1,40 +0,0 @@
import React from "react";
import PlanList from "./PlanList.jsx";
class AllPlansComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
plans: [],
};
}
getPlans() {
fetch("http://localhost:3000/api/plans", {
headers: {
Accept: "application/json",
},
})
.then((response) => response.json())
.then((data) => {
this.setState({
plans: data,
});
})
.catch((error) => console.error(error));
}
componentDidMount() {
this.getPlans();
}
render() {
return (
<div className="AllPlans">
<PlanList plans={this.state.plans} />
</div>
);
}
}
export default AllPlansComponent;

View File

@@ -0,0 +1,23 @@
import React from "react";
import renderer from "react-test-renderer";
import AuthenticatedApp from "./AuthenticatedApp";
import { AuthContext } from "../context/AuthContext";
test("AuthenticatedApp Snapshot", () => {
const appRender = renderer.create(
<AuthContext.Provider
value={{
login: jest.fn(),
register: jest.fn(),
logout: jest.fn(),
user: { display_name: "Ted" },
}}
>
<AuthenticatedApp />
</AuthContext.Provider>
);
const tree = appRender.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@@ -0,0 +1,17 @@
import React from "react";
import { useAuth } from "../context/AuthContext";
function AuthenticatedApp() {
const { logout, user } = useAuth();
return (
<div>
<p>Howdy partner. Your name looks like it&apos;s {user.display_name}.</p>
<button type="button" onClick={logout}>
Logout
</button>
</div>
);
}
export default AuthenticatedApp;

View File

@@ -0,0 +1,13 @@
import React from "react";
import renderer from "react-test-renderer";
import Login from "./Login";
test("Login Snapshot", () => {
const loginFunc = jest.fn();
const login = renderer.create(<Login login={loginFunc} />);
const tree = login.toJSON();
expect(tree).toMatchSnapshot();
});

44
src/components/Login.jsx Normal file
View File

@@ -0,0 +1,44 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
export default function Login({ login }) {
const [username, setUserName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async (e) => {
e.preventDefault();
login(username, password);
};
return (
<div className="login-wrapper">
<h1>Please Log In</h1>
<form onSubmit={handleSubmit}>
<label>
<p>Username</p>
<input
id="username"
type="text"
onChange={(e) => setUserName(e.target.value)}
/>
</label>
<label>
<p>Password</p>
<input
id="password"
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
);
}
Login.propTypes = {
login: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,21 @@
import React from "react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Login from "./Login";
test("Login inputs", () => {
const loginFunc = jest.fn();
const login = render(<Login login={loginFunc} />);
const usernameInput = login.getByRole("textbox", { name: /Username/i });
const passwordInput = login.getByLabelText("Password");
const buttonInput = login.getByRole("button", { name: /Submit/i });
const username = "here's a username";
const pw = "here's a password";
userEvent.type(usernameInput, username);
userEvent.type(passwordInput, pw);
userEvent.click(buttonInput);
expect(loginFunc).toHaveBeenCalledWith(username, pw);
});

View File

@@ -1,54 +0,0 @@
import React from "react";
import ActionsContainer from "./ActionsContainer.jsx";
import { plan } from "../types";
class Plan extends React.Component {
constructor(props) {
super(props);
this.state = {
actions: [],
};
}
getActions() {
fetch(
"http://localhost:3000/api/actions?" +
new URLSearchParams({
plan_id: this.props.plan.plan_id,
}),
{
headers: {
Accept: "application/json",
},
}
)
.then((response) => response.json())
.then((data) => {
this.setState({
actions: data,
});
})
.catch((error) => console.error(error));
}
componentDidMount() {
this.getActions();
}
render() {
return (
<div className="PlanActionsWrapper">
<div>
<h3>Plan for {this.props.plan.plan_date}</h3>
</div>
<ActionsContainer actions={this.state.actions.slice()} />
</div>
);
}
}
Plan.propTypes = {
plan: plan,
};
export default Plan;

View File

@@ -1,28 +0,0 @@
import React from "react";
import Plan from "./Plan.jsx";
import { plan } from "../types";
import PropTypes from "prop-types";
class PlanList extends React.Component {
render() {
return (
<div className="PlanListWrapper">
<ul className="PlanList">
{this.props.plans.map((plan) => {
return (
<li className="Plan" key={plan.plan_id}>
<Plan plan={plan} />
</li>
);
})}
</ul>
</div>
);
}
}
PlanList.propTypes = {
plans: PropTypes.arrayOf(plan),
};
export default PlanList;

View File

@@ -0,0 +1,13 @@
import React from "react";
import renderer from "react-test-renderer";
import Register from "./Register";
test("Register Snapshot", () => {
const registerFunc = jest.fn();
const register = renderer.create(<Register register={registerFunc} />);
const tree = register.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@@ -0,0 +1,53 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
export default function Register({ register }) {
const [username, setUserName] = useState();
const [displayName, setDisplayName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async (e) => {
e.preventDefault();
register(username, displayName, password);
};
return (
<div className="register-wrapper">
<h1>Enter your data to register.</h1>
<form onSubmit={handleSubmit}>
<label>
<p>Username</p>
<input
id="username"
type="text"
onChange={(e) => setUserName(e.target.value)}
/>
</label>
<label>
<p>Name</p>
<input
id="displayName"
type="text"
onChange={(e) => setDisplayName(e.target.value)}
/>
</label>
<label>
<p>Password</p>
<input
id="password"
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<div>
<button type="submit">Sign me up</button>
</div>
</form>
</div>
);
}
Register.propTypes = {
register: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,24 @@
import React from "react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Register from "./Register";
test("Register inputs", () => {
const registerFunc = jest.fn();
const register = render(<Register register={registerFunc} />);
const usernameInput = register.getByRole("textbox", { name: /Username/ });
const displayNameInput = register.getByRole("textbox", { name: /Name/ });
const passwordInput = register.getByLabelText("Password");
const buttonInput = register.getByRole("button", { name: /Sign me up/i });
const username = "here's a username";
const displayName = "testing this is a display name for a person";
const pw = "here's a password";
userEvent.type(usernameInput, username);
userEvent.type(displayNameInput, displayName);
userEvent.type(passwordInput, pw);
userEvent.click(buttonInput);
expect(registerFunc).toHaveBeenCalledWith(username, displayName, pw);
});

View File

@@ -0,0 +1,18 @@
import React from "react";
import renderer from "react-test-renderer";
import UnauthenticatedApp from "./UnauthenticatedApp";
import { AuthContext } from "../context/AuthContext";
test("UnauthenticatedApp Snapshot", () => {
const appRender = renderer.create(
<AuthContext.Provider
value={{ login: jest.fn(), register: jest.fn(), logout: jest.fn() }}
>
<UnauthenticatedApp />
</AuthContext.Provider>
);
const tree = appRender.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@@ -0,0 +1,39 @@
import React, { useState } from "react";
import { useAuth } from "../context/AuthContext";
import Login from "./Login";
import Register from "./Register";
function UnauthenticatedApp() {
const { login, register } = useAuth();
const [showRegister, setShowRegister] = useState(false);
const form = showRegister ? (
<Register register={register} />
) : (
<Login login={login} />
);
return (
<div className="UnauthenticatedApp">
<div className="loginRegisterButtonsWrapper">
<button
type="submit"
onClick={() => setShowRegister(false)}
className="login"
>
Login
</button>
<button
type="submit"
onClick={() => setShowRegister(true)}
className="register"
>
Register
</button>
</div>
{form}
</div>
);
}
export default UnauthenticatedApp;

View File

@@ -0,0 +1,28 @@
import React from "react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import UnauthenticatedApp from "./UnauthenticatedApp";
import '@testing-library/jest-dom/extend-expect';
import { AuthContext } from "../context/AuthContext";
test("Login inputs", () => {
const loginFunc = jest.fn();
const registerFunc = jest.fn();
const app = render(
<AuthContext.Provider
value={{ login: loginFunc, register: registerFunc, logout: jest.fn() }}
>
<UnauthenticatedApp />
</AuthContext.Provider>
);
const chooseLoginButton = app.getByRole("button", { name: /Login/i });
const chooseRegisterButton = app.getByRole("button", { name: /Register/i });
userEvent.click(chooseLoginButton);
expect(app.getByRole('heading')).toHaveTextContent('Please Log In')
userEvent.click(chooseRegisterButton);
expect(app.getByRole('heading')).toHaveTextContent('Enter your data to register.')
});

View File

@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AuthenticatedApp Snapshot 1`] = `
<div>
<p>
Howdy partner. Your name looks like it's
Ted
.
</p>
<button
onClick={[MockFunction]}
type="button"
>
Logout
</button>
</div>
`;

View File

@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Login Snapshot 1`] = `
<div
className="login-wrapper"
>
<h1>
Please Log In
</h1>
<form
onSubmit={[Function]}
>
<label>
<p>
Username
</p>
<input
id="username"
onChange={[Function]}
type="text"
/>
</label>
<label>
<p>
Password
</p>
<input
id="password"
onChange={[Function]}
type="password"
/>
</label>
<div>
<button
type="submit"
>
Submit
</button>
</div>
</form>
</div>
`;

View File

@@ -0,0 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Register Snapshot 1`] = `
<div
className="register-wrapper"
>
<h1>
Enter your data to register.
</h1>
<form
onSubmit={[Function]}
>
<label>
<p>
Username
</p>
<input
id="username"
onChange={[Function]}
type="text"
/>
</label>
<label>
<p>
Name
</p>
<input
id="displayName"
onChange={[Function]}
type="text"
/>
</label>
<label>
<p>
Password
</p>
<input
id="password"
onChange={[Function]}
type="password"
/>
</label>
<div>
<button
type="submit"
>
Sign me up
</button>
</div>
</form>
</div>
`;

View File

@@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UnauthenticatedApp Snapshot 1`] = `
<div
className="UnauthenticatedApp"
>
<div
className="loginRegisterButtonsWrapper"
>
<button
className="login"
onClick={[Function]}
type="submit"
>
Login
</button>
<button
className="register"
onClick={[Function]}
type="submit"
>
Register
</button>
</div>
<div
className="login-wrapper"
>
<h1>
Please Log In
</h1>
<form
onSubmit={[Function]}
>
<label>
<p>
Username
</p>
<input
id="username"
onChange={[Function]}
type="text"
/>
</label>
<label>
<p>
Password
</p>
<input
id="password"
onChange={[Function]}
type="password"
/>
</label>
<div>
<button
type="submit"
>
Submit
</button>
</div>
</form>
</div>
</div>
`;

View File

@@ -0,0 +1,19 @@
import React from "react";
import { NavLink } from "react-router-dom";
function Header() {
return (
<nav>
<NavLink exact activeClassName="active" to="/">
Home
</NavLink>
<NavLink activeClassName="active" to="/users">
Users
</NavLink>
<NavLink activeClassName="active" to="/contact">
Contact
</NavLink>
</nav>
);
}
export default Header;

View File

View File

@@ -0,0 +1,62 @@
import React, { useState } from "react";
import { register, login } from "../services/auth-service";
import { getUserInfo } from "../services/user-service";
const localStorageKey = "__auth_token__";
const AuthContext = React.createContext();
const AuthProvider = (props) => {
const [user, setUser] = useState(null);
const setUserWithToken = (token) => {
if (user !== null) {
// Already set don't re-set.
return;
}
getUserInfo(token).then((user) => {
setUser(user);
});
};
const tok = window.localStorage.getItem(localStorageKey);
if (tok !== null) {
setUserWithToken(tok);
}
const wrappedLogin = (username, password) => {
login(username, password).then((token) => {
window.localStorage.setItem(localStorageKey, token);
setUserWithToken(token);
});
};
const wrappedRegister = register;
const wrappedLogout = () => {
window.localStorage.removeItem(localStorageKey);
setUser(null);
};
return (
<AuthContext.Provider
value={{
login: wrappedLogin,
register: wrappedRegister,
logout: wrappedLogout,
user,
}}
{...props}
/>
);
};
function useAuth() {
const context = React.useContext(AuthContext);
if (context === undefined) {
throw new Error(`useAuth must be used within a AuthProvider`);
}
return context;
}
export { AuthProvider, AuthContext, useAuth };

13
src/context/index.jsx Normal file
View File

@@ -0,0 +1,13 @@
import React from "react";
import { AuthProvider } from "./AuthContext";
import PropTypes from "prop-types";
function AppProviders({ children }) {
return <AuthProvider>{children}</AuthProvider>;
}
export default AppProviders;
AppProviders.propTypes = {
children: PropTypes.node,
};

View File

@@ -1,4 +1,11 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.jsx";
ReactDOM.render(<App />, document.getElementById("root"));
import AppProviders from "./context";
ReactDOM.render(
<AppProviders>
<App />
</AppProviders>,
document.getElementById("root")
);

View File

@@ -0,0 +1,48 @@
import { API_ROOT } from "./config";
export const register = (username, displayName, password) => {
const url = API_ROOT + "auth/register";
const body = {
username: username,
display_name: displayName,
password: password,
};
return fetch(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
};
export const login = (username, password) => {
const url = API_ROOT + "auth/tokens";
const body = {
username: username,
password: password,
};
return fetch(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.then((data) => {
if (data.token) {
return data.token;
}
throw Error("where's my token");
});
};

1
src/services/config.jsx Normal file
View File

@@ -0,0 +1 @@
export const API_ROOT = process.env.API_ROOT;

View File

@@ -0,0 +1,5 @@
import { API_ROOT } from "./config";
test("testing config api root", () => {
expect(API_ROOT).toEqual("http://localhost:8080/");
});

View File

@@ -0,0 +1,18 @@
import { API_ROOT } from "./config";
export const getUserInfo = (token) => {
const url = API_ROOT + "me";
return fetch(url, {
method: "GET",
headers: {
Authorization: "Bearer " + token,
Accept: "application/json",
},
}).then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
});
};

View File

@@ -36,5 +36,10 @@ module.exports = {
},
},
},
plugins: [new webpack.HotModuleReplacementPlugin()],
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.EnvironmentPlugin({
API_ROOT: "http://localhost:8080/"
})
],
};