Compare commits
8 Commits
Ali-Abbas/
...
v4.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
678b95f60d | ||
|
|
599f513624 | ||
|
|
e8ce471f6f | ||
|
|
4e727748af | ||
|
|
79c5bbff68 | ||
|
|
a95b600a00 | ||
|
|
464e952a1e | ||
|
|
018ca18a4e |
@@ -4,8 +4,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { initialize, getConfig, subscribe, APP_READY } from '@edx/frontend-platform';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
// import Header from '@edx/frontend-component-header';
|
||||
import { LearningHeader as Header } from '@edx/frontend-component-header';
|
||||
import Header from '@edx/frontend-component-header';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
||||
1432
package-lock.json
generated
1432
package-lock.json
generated
@@ -9,29 +9,22 @@
|
||||
"version": "1.0.0-semantically-released",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@edx/paragon": "20.45.5",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||
"@edx/paragon": "20.46.2",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@reduxjs/toolkit": "1.9.5",
|
||||
"axios-mock-adapter": "1.21.5",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"classnames": "2.3.2",
|
||||
"lodash": "4.17.21",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-transition-group": "4.4.5",
|
||||
"rosie": "2.1.0",
|
||||
"timeago.js": "4.0.2"
|
||||
"react-transition-group": "4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@edx/frontend-build": "12.9.3",
|
||||
"@edx/frontend-platform": "4.6.0",
|
||||
"@edx/frontend-build": "12.9.4",
|
||||
"@edx/frontend-platform": "5.0.0",
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@testing-library/dom": "9.3.1",
|
||||
"@testing-library/jest-dom": "5.17.0",
|
||||
@@ -45,13 +38,13 @@
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-redux": "7.2.9",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-router-dom": "6.14.2",
|
||||
"react-test-renderer": "17.0.2",
|
||||
"redux": "4.2.1",
|
||||
"redux-saga": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-platform": "^4.0.0",
|
||||
"@edx/frontend-platform": "^4.0.0 || ^5.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^16.9.0 || ^17.0.0",
|
||||
"react-dom": "^16.9.0 || ^17.0.0"
|
||||
@@ -2171,9 +2164,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build": {
|
||||
"version": "12.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-build/-/frontend-build-12.9.3.tgz",
|
||||
"integrity": "sha512-6qKUMR5dCLMTIZ9a+ae2fSbSCgySD3Uw8H9+Pe6SDcRDgXp/C41NstxNO0zEggrbtEjCBEyR68FXZia9M/HnZQ==",
|
||||
"version": "12.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-build/-/frontend-build-12.9.4.tgz",
|
||||
"integrity": "sha512-q2ac5JqJX8SwUsCU2v16RlJHs2FG5yJ4m3+WraAjpZT2+TL/zLnlbRfETp6MuL+xDz6q8G9qvfFCOYT7mGq/WQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/cli": "7.22.5",
|
||||
@@ -2832,40 +2825,6 @@
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-each": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz",
|
||||
"integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"chalk": "^4.0.0",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"jest-util": "^26.6.2",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-environment-jsdom": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz",
|
||||
"integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jsdom": "^16.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-environment-node": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz",
|
||||
@@ -2919,35 +2878,6 @@
|
||||
"fsevents": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-jasmine2": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz",
|
||||
"integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.1.0",
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/source-map": "^26.6.2",
|
||||
"@jest/test-result": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"co": "^4.6.0",
|
||||
"expect": "^26.6.2",
|
||||
"is-generator-fn": "^2.0.0",
|
||||
"jest-each": "^26.6.2",
|
||||
"jest-matcher-utils": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-runtime": "^26.6.3",
|
||||
"jest-snapshot": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"pretty-format": "^26.6.2",
|
||||
"throat": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-leak-detector": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz",
|
||||
@@ -3123,19 +3053,6 @@
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-serializer": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz",
|
||||
"integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"graceful-fs": "^4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/jest-snapshot": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz",
|
||||
@@ -3355,12 +3272,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/throat": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
|
||||
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@edx/frontend-build/node_modules/v8-to-istanbul": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz",
|
||||
@@ -3455,9 +3366,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-platform": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-4.6.0.tgz",
|
||||
"integrity": "sha512-NZ1I3BgUZl7bqvDwSnnL+LxqZOdOUGZU55KiwvknqiKU8RS5Lx9tc4arp+NcX1u58xy/Xbinv+mriSO6PPxQNQ==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-5.0.0.tgz",
|
||||
"integrity": "sha512-DD9/B4rnC3BKPiWlbEFF1JIYFbWC6vUBKTyN8sf4khi4DNhhWhsobk+iNeCWNzF9UgCPRbniIqesdV1F9NXNZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cospired/i18n-iso-languages": "4.1.0",
|
||||
@@ -3485,13 +3396,13 @@
|
||||
"transifex-utils.js": "i18n/scripts/transifex-utils.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-build": ">= 8.1.0",
|
||||
"@edx/frontend-build": ">= 8.1.0 || ^12.9.0-alpha.1",
|
||||
"@edx/paragon": ">= 10.0.0 < 21.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.9.0 || ^17.0.0",
|
||||
"react-dom": "^16.9.0 || ^17.0.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-router-dom": "^6.0.0",
|
||||
"redux": "^4.0.4"
|
||||
}
|
||||
},
|
||||
@@ -3505,9 +3416,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/paragon": {
|
||||
"version": "20.45.5",
|
||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.45.5.tgz",
|
||||
"integrity": "sha512-7GsGPKyxtjFo3Xnj+uQ4vx/Khz7S6srHe8MqcsYCMx2mJ8fulPN2JFm84m+0o1CSwHaL469wBPONI4KCa+vfrA==",
|
||||
"version": "20.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.46.2.tgz",
|
||||
"integrity": "sha512-px+KS/BV1CbiMKgfVgUofyjJi4CHUCUOLRukJbT66VPPqWP4Xon5Rns6uohoratPXMg2kNN46v2L8wIwqKQ4Lw==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
||||
@@ -3853,57 +3764,57 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
|
||||
"integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz",
|
||||
"integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz",
|
||||
"integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
||||
"@fortawesome/fontawesome-common-types": "6.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-brands-svg-icons": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz",
|
||||
"integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz",
|
||||
"integrity": "sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
||||
"@fortawesome/fontawesome-common-types": "6.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-regular-svg-icons": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz",
|
||||
"integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz",
|
||||
"integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
||||
"@fortawesome/fontawesome-common-types": "6.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz",
|
||||
"integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz",
|
||||
"integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
||||
"@fortawesome/fontawesome-common-types": "6.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -5559,27 +5470,13 @@
|
||||
"integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
|
||||
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.21",
|
||||
"redux": "^4.2.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"reselect": "^4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18",
|
||||
"react-redux": "^7.2.1 || ^8.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
|
||||
"integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/context": {
|
||||
@@ -6570,7 +6467,7 @@
|
||||
"version": "7.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
|
||||
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
@@ -12782,6 +12679,7 @@
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
@@ -14444,6 +14342,218 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz",
|
||||
"integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jsdom": "^16.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/@jest/environment": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz",
|
||||
"integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz",
|
||||
"integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
"@types/node": "*",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/@sinonjs/commons": {
|
||||
"version": "1.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
||||
"integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/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,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/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,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/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
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/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,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/jest-message-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz",
|
||||
"integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/stack-utils": "^2.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"micromatch": "^4.0.2",
|
||||
"pretty-format": "^26.6.2",
|
||||
"slash": "^3.0.0",
|
||||
"stack-utils": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/jest-mock": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz",
|
||||
"integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/jest-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"micromatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/pretty-format": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
|
||||
"integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"ansi-regex": "^5.0.0",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/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,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node": {
|
||||
"version": "29.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz",
|
||||
@@ -14687,6 +14797,916 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz",
|
||||
"integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.1.0",
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/source-map": "^26.6.2",
|
||||
"@jest/test-result": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"co": "^4.6.0",
|
||||
"expect": "^26.6.2",
|
||||
"is-generator-fn": "^2.0.0",
|
||||
"jest-each": "^26.6.2",
|
||||
"jest-matcher-utils": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-runtime": "^26.6.3",
|
||||
"jest-snapshot": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"pretty-format": "^26.6.2",
|
||||
"throat": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/console": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz",
|
||||
"integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/environment": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz",
|
||||
"integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/fake-timers": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz",
|
||||
"integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
"@types/node": "*",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/globals": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz",
|
||||
"integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"expect": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/source-map": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz",
|
||||
"integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"callsites": "^3.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/test-result": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz",
|
||||
"integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/console": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"collect-v8-coverage": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/test-sequencer": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz",
|
||||
"integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/test-result": "^26.6.2",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-haste-map": "^26.6.2",
|
||||
"jest-runner": "^26.6.3",
|
||||
"jest-runtime": "^26.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/transform": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz",
|
||||
"integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.1.0",
|
||||
"@jest/types": "^26.6.2",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"convert-source-map": "^1.4.0",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-haste-map": "^26.6.2",
|
||||
"jest-regex-util": "^26.0.0",
|
||||
"jest-util": "^26.6.2",
|
||||
"micromatch": "^4.0.2",
|
||||
"pirates": "^4.0.1",
|
||||
"slash": "^3.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"write-file-atomic": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@sinonjs/commons": {
|
||||
"version": "1.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
||||
"integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@sinonjs/fake-timers": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/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,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/babel-jest": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz",
|
||||
"integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/transform": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/babel__core": "^7.1.7",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-preset-jest": "^26.6.2",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/babel-plugin-jest-hoist": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz",
|
||||
"integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.3.3",
|
||||
"@babel/types": "^7.3.3",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/babel__traverse": "^7.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/babel-preset-jest": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz",
|
||||
"integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"babel-plugin-jest-hoist": "^26.6.2",
|
||||
"babel-preset-current-node-syntax": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/cjs-module-lexer": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz",
|
||||
"integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/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,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/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
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/diff-sequences": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
|
||||
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/emittery": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz",
|
||||
"integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/expect": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz",
|
||||
"integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"jest-matcher-utils": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-regex-util": "^26.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/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,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-config": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz",
|
||||
"integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.1.0",
|
||||
"@jest/test-sequencer": "^26.6.3",
|
||||
"@jest/types": "^26.6.2",
|
||||
"babel-jest": "^26.6.3",
|
||||
"chalk": "^4.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"glob": "^7.1.1",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-environment-jsdom": "^26.6.2",
|
||||
"jest-environment-node": "^26.6.2",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"jest-jasmine2": "^26.6.3",
|
||||
"jest-regex-util": "^26.0.0",
|
||||
"jest-resolve": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jest-validate": "^26.6.2",
|
||||
"micromatch": "^4.0.2",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ts-node": ">=9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ts-node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-diff": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz",
|
||||
"integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^26.6.2",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-docblock": {
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz",
|
||||
"integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"detect-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-each": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz",
|
||||
"integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"chalk": "^4.0.0",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"jest-util": "^26.6.2",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-environment-node": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz",
|
||||
"integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-get-type": {
|
||||
"version": "26.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
|
||||
"integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-haste-map": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz",
|
||||
"integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/graceful-fs": "^4.1.2",
|
||||
"@types/node": "*",
|
||||
"anymatch": "^3.0.3",
|
||||
"fb-watchman": "^2.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-regex-util": "^26.0.0",
|
||||
"jest-serializer": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jest-worker": "^26.6.2",
|
||||
"micromatch": "^4.0.2",
|
||||
"sane": "^4.0.3",
|
||||
"walker": "^1.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-leak-detector": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz",
|
||||
"integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jest-get-type": "^26.3.0",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-matcher-utils": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz",
|
||||
"integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^26.6.2",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-message-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz",
|
||||
"integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/stack-utils": "^2.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"micromatch": "^4.0.2",
|
||||
"pretty-format": "^26.6.2",
|
||||
"slash": "^3.0.0",
|
||||
"stack-utils": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-mock": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz",
|
||||
"integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-regex-util": {
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
|
||||
"integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-resolve": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz",
|
||||
"integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-pnp-resolver": "^1.2.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"read-pkg-up": "^7.0.1",
|
||||
"resolve": "^1.18.1",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-runner": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz",
|
||||
"integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/console": "^26.6.2",
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/test-result": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"emittery": "^0.7.1",
|
||||
"exit": "^0.1.2",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-config": "^26.6.3",
|
||||
"jest-docblock": "^26.0.0",
|
||||
"jest-haste-map": "^26.6.2",
|
||||
"jest-leak-detector": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-resolve": "^26.6.2",
|
||||
"jest-runtime": "^26.6.3",
|
||||
"jest-util": "^26.6.2",
|
||||
"jest-worker": "^26.6.2",
|
||||
"source-map-support": "^0.5.6",
|
||||
"throat": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-runtime": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz",
|
||||
"integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/console": "^26.6.2",
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/globals": "^26.6.2",
|
||||
"@jest/source-map": "^26.6.2",
|
||||
"@jest/test-result": "^26.6.2",
|
||||
"@jest/transform": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"cjs-module-lexer": "^0.6.0",
|
||||
"collect-v8-coverage": "^1.0.0",
|
||||
"exit": "^0.1.2",
|
||||
"glob": "^7.1.3",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-config": "^26.6.3",
|
||||
"jest-haste-map": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-regex-util": "^26.0.0",
|
||||
"jest-resolve": "^26.6.2",
|
||||
"jest-snapshot": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jest-validate": "^26.6.2",
|
||||
"slash": "^3.0.0",
|
||||
"strip-bom": "^4.0.0",
|
||||
"yargs": "^15.4.1"
|
||||
},
|
||||
"bin": {
|
||||
"jest-runtime": "bin/jest-runtime.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-snapshot": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz",
|
||||
"integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.0.0",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/babel__traverse": "^7.0.4",
|
||||
"@types/prettier": "^2.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"expect": "^26.6.2",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-diff": "^26.6.2",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"jest-haste-map": "^26.6.2",
|
||||
"jest-matcher-utils": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-resolve": "^26.6.2",
|
||||
"natural-compare": "^1.4.0",
|
||||
"pretty-format": "^26.6.2",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"micromatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-validate": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz",
|
||||
"integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"camelcase": "^6.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"leven": "^3.1.0",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-worker": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
|
||||
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/pretty-format": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
|
||||
"integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"ansi-regex": "^5.0.0",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/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,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/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,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/yargs-parser/node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-leak-detector": {
|
||||
"version": "29.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz",
|
||||
@@ -15520,6 +16540,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-serializer": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz",
|
||||
"integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"graceful-fs": "^4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-snapshot": {
|
||||
"version": "29.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz",
|
||||
@@ -16540,7 +17573,8 @@
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
@@ -17806,15 +18840,6 @@
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/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==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
@@ -19422,7 +20447,7 @@
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
@@ -19479,7 +20504,7 @@
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
@@ -19572,49 +20597,37 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
|
||||
"integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
|
||||
"integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"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"
|
||||
"@remix-run/router": "1.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
|
||||
"integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==",
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
|
||||
"integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.3.4",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"@remix-run/router": "1.7.2",
|
||||
"react-router": "6.14.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router/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==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-shallow-renderer": {
|
||||
"version": "16.15.0",
|
||||
"resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz",
|
||||
@@ -19874,6 +20887,7 @@
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
@@ -19887,14 +20901,6 @@
|
||||
"@redux-saga/core": "^1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||
"peerDependencies": {
|
||||
"redux": "^4"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
@@ -20164,11 +21170,6 @@
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
@@ -20294,14 +21295,6 @@
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/rosie": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rosie/-/rosie-2.1.0.tgz",
|
||||
"integrity": "sha512-Dbzdc+prLXZuB/suRptDnBUY29SdGvND3bLg6cll8n7PNqzuyCxSlRfrkn8PqjS9n4QVsiM7RCvxCkKAkTQRjA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/rst-selector-parser": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz",
|
||||
@@ -22399,17 +23392,18 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/throat": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
|
||||
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/thunky": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
|
||||
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/timeago.js": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/timeago.js/-/timeago.js-4.0.2.tgz",
|
||||
"integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w=="
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
||||
|
||||
27
package.json
27
package.json
@@ -35,8 +35,8 @@
|
||||
"devDependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@edx/frontend-build": "12.9.3",
|
||||
"@edx/frontend-platform": "4.6.0",
|
||||
"@edx/frontend-build": "12.9.4",
|
||||
"@edx/frontend-platform": "5.0.0",
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@testing-library/dom": "9.3.1",
|
||||
"@testing-library/jest-dom": "5.17.0",
|
||||
@@ -50,32 +50,25 @@
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-redux": "7.2.9",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-router-dom": "6.14.2",
|
||||
"react-test-renderer": "17.0.2",
|
||||
"redux": "4.2.1",
|
||||
"redux-saga": "1.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/paragon": "20.45.5",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||
"@edx/paragon": "20.46.2",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@reduxjs/toolkit": "1.9.5",
|
||||
"axios-mock-adapter": "1.21.5",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"classnames": "2.3.2",
|
||||
"lodash": "4.17.21",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-transition-group": "4.4.5",
|
||||
"rosie": "2.1.0",
|
||||
"timeago.js": "4.0.2"
|
||||
"react-transition-group": "4.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-platform": "^4.0.0",
|
||||
"@edx/frontend-platform": "^4.0.0 || ^5.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^16.9.0 || ^17.0.0",
|
||||
"react-dom": "^16.9.0 || ^17.0.0"
|
||||
|
||||
@@ -2,38 +2,24 @@
|
||||
import React from 'react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import store from './store';
|
||||
|
||||
import Header from './index';
|
||||
|
||||
const HeaderComponent = ({ width, contextValue }) => (
|
||||
<ResponsiveContext.Provider value={width}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<AppProvider store={store}>
|
||||
<AppContext.Provider
|
||||
value={contextValue}
|
||||
>
|
||||
<Header />
|
||||
</AppContext.Provider>
|
||||
</AppProvider>
|
||||
<AppContext.Provider
|
||||
value={contextValue}
|
||||
>
|
||||
<Header />
|
||||
</AppContext.Provider>
|
||||
</IntlProvider>
|
||||
</ResponsiveContext.Provider>
|
||||
);
|
||||
|
||||
describe('<Header />', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: '123abc',
|
||||
username: 'testuser',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
it('renders correctly for anonymous desktop', () => {
|
||||
const contextValue = {
|
||||
authenticatedUser: null,
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from '@edx/paragon';
|
||||
import * as timeago from 'timeago.js';
|
||||
import { getIconByType } from './utils';
|
||||
import { markNotificationsAsRead } from './data/thunks';
|
||||
import messages from './messages';
|
||||
import timeLocale from '../common/time-locale';
|
||||
|
||||
const NotificationRowItem = ({
|
||||
id, type, contentUrl, content, courseName, createdAt, lastRead,
|
||||
}) => {
|
||||
timeago.register('time-locale', timeLocale);
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleMarkAsRead = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const { icon: iconComponent, class: iconClass } = getIconByType(type);
|
||||
|
||||
return (
|
||||
<a
|
||||
target="_blank"
|
||||
className="d-flex mb-2 align-items-center text-decoration-none"
|
||||
href={contentUrl}
|
||||
onClick={handleMarkAsRead}
|
||||
data-testid={`notification-${id}`}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon
|
||||
src={iconComponent}
|
||||
className={`${iconClass} mr-4 notification-icon`}
|
||||
data-testid={`notification-icon-${id}`}
|
||||
/>
|
||||
<div className="d-flex w-100" data-testid="notification-contents">
|
||||
<div className="d-flex align-items-center w-100">
|
||||
<div className="py-10px w-100 px-0 cursor-pointer">
|
||||
<span
|
||||
className="line-height-24 text-gray-700 mb-2 notification-item-content overflow-hidden content"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
data-testid={`notification-content-${id}`}
|
||||
/>
|
||||
<div className="py-0 d-flex">
|
||||
<span className="font-size-12 text-gray-500 line-height-20">
|
||||
<span data-testid={`notification-course-${id}`}>{courseName}
|
||||
</span>
|
||||
<span className="text-light-700 px-1.5">{intl.formatMessage(messages.fullStop)}</span>
|
||||
<span data-testid={`notification-created-date-${id}`}> {timeago.format(createdAt, 'time-locale')}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!lastRead && (
|
||||
<div className="d-flex py-1.5 px-1.5 ml-2 cursor-pointer">
|
||||
<span className="bg-brand-500 rounded unread" data-testid={`unread-notification-${id}`} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
NotificationRowItem.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
contentUrl: PropTypes.string.isRequired,
|
||||
content: PropTypes.node.isRequired,
|
||||
courseName: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
lastRead: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(NotificationRowItem);
|
||||
@@ -1,96 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Button, Spinner } from '@edx/paragon';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import messages from './messages';
|
||||
import NotificationRowItem from './NotificationRowItem';
|
||||
import { markAllNotificationsAsRead } from './data/thunks';
|
||||
import {
|
||||
selectNotificationsByIds, selectPaginationData, selectSelectedAppName, selectNotificationStatus,
|
||||
} from './data/selectors';
|
||||
import { splitNotificationsByTime } from './utils';
|
||||
import { updatePaginationRequest, RequestStatus } from './data/slice';
|
||||
|
||||
const NotificationSections = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const selectedAppName = useSelector(selectSelectedAppName());
|
||||
const notificationRequestStatus = useSelector(selectNotificationStatus());
|
||||
const notifications = useSelector(selectNotificationsByIds(selectedAppName));
|
||||
const { hasMorePages } = useSelector(selectPaginationData());
|
||||
const { today = [], earlier = [] } = useMemo(
|
||||
() => splitNotificationsByTime(notifications),
|
||||
[notifications],
|
||||
);
|
||||
|
||||
const handleMarkAllAsRead = useCallback(() => {
|
||||
dispatch(markAllNotificationsAsRead(selectedAppName));
|
||||
}, [dispatch, selectedAppName]);
|
||||
|
||||
const updatePagination = useCallback(() => {
|
||||
dispatch(updatePaginationRequest());
|
||||
}, [dispatch]);
|
||||
|
||||
const renderNotificationSection = (section, items) => {
|
||||
if (isEmpty(items)) { return null; }
|
||||
|
||||
return (
|
||||
<div className="pb-2">
|
||||
<div className="d-flex justify-content-between align-items-center py-10px mb-2">
|
||||
<span className="text-gray-500 line-height-10">
|
||||
{section === 'today' && intl.formatMessage(messages.notificationTodayHeading)}
|
||||
{section === 'earlier' && intl.formatMessage(messages.notificationEarlierHeading)}
|
||||
</span>
|
||||
{notifications?.length > 0 && (section === 'earlier' ? today.length === 0 : true) && (
|
||||
<Button
|
||||
variant="link"
|
||||
className="text-info-500 font-size-14 line-height-10 text-decoration-none p-0 border-0"
|
||||
onClick={handleMarkAllAsRead}
|
||||
data-testid="mark-all-read"
|
||||
>
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{items.map((notification) => (
|
||||
<NotificationRowItem
|
||||
key={notification.id}
|
||||
id={notification.id}
|
||||
type={notification.type}
|
||||
contentUrl={notification.contentUrl}
|
||||
content={notification.content}
|
||||
courseName={notification.contentContext?.courseName || ''}
|
||||
createdAt={notification.createdAt}
|
||||
lastRead={notification.lastRead}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 px-4" data-testid="notification-tray-section">
|
||||
{renderNotificationSection('today', today)}
|
||||
{renderNotificationSection('earlier', earlier)}
|
||||
{hasMorePages && notificationRequestStatus === RequestStatus.IN_PROGRESS ? (
|
||||
<div className="d-flex justify-content-center p-4">
|
||||
<Spinner animation="border" variant="primary" size="lg" />
|
||||
</div>
|
||||
) : (hasMorePages && notificationRequestStatus === RequestStatus.SUCCESSFUL
|
||||
&& (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-100 bg-primary-500"
|
||||
onClick={updatePagination}
|
||||
data-testid="load-more-notifications"
|
||||
>
|
||||
{intl.formatMessage(messages.loadMoreNotifications)}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NotificationSections);
|
||||
@@ -1,53 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Tab, Tabs } from '@edx/paragon';
|
||||
import NotificationSections from './NotificationSections';
|
||||
import { fetchNotificationList, markNotificationsAsSeen } from './data/thunks';
|
||||
import {
|
||||
selectNotificationTabs, selectNotificationTabsCount, selectPaginationData, selectSelectedAppName,
|
||||
} from './data/selectors';
|
||||
import { updateAppNameRequest } from './data/slice';
|
||||
|
||||
const NotificationTabs = () => {
|
||||
const dispatch = useDispatch();
|
||||
const selectedAppName = useSelector(selectSelectedAppName());
|
||||
const notificationUnseenCounts = useSelector(selectNotificationTabsCount());
|
||||
const notificationTabs = useSelector(selectNotificationTabs());
|
||||
const { currentPage } = useSelector(selectPaginationData());
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchNotificationList({ appName: selectedAppName, page: currentPage }));
|
||||
if (selectedAppName) { dispatch(markNotificationsAsSeen(selectedAppName)); }
|
||||
}, [currentPage, selectedAppName]);
|
||||
|
||||
const handleActiveTab = useCallback((appName) => {
|
||||
dispatch(updateAppNameRequest({ appName }));
|
||||
}, []);
|
||||
|
||||
const tabArray = useMemo(() => notificationTabs?.map((appName) => (
|
||||
<Tab
|
||||
key={appName}
|
||||
eventKey={appName}
|
||||
title={appName}
|
||||
notification={notificationUnseenCounts[appName]}
|
||||
tabClassName="pt-0 pb-10px px-2.5 d-flex border-top-0 mb-0 align-items-center line-height-24 text-capitalize"
|
||||
data-testid={`notification-tab-${appName}`}
|
||||
>
|
||||
{appName === selectedAppName && (<NotificationSections />)}
|
||||
</Tab>
|
||||
)), [notificationUnseenCounts, selectedAppName, notificationTabs]);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
variant="tabs"
|
||||
defaultActiveKey={selectedAppName}
|
||||
onSelect={handleActiveTab}
|
||||
className="px-2.5 text-primary-500"
|
||||
>
|
||||
{tabArray}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NotificationTabs);
|
||||
@@ -1 +0,0 @@
|
||||
import './notifications.factory';
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
Factory.define('notificationsCount')
|
||||
.attr('count', 45)
|
||||
.attr('countByAppName', {
|
||||
reminders: 10,
|
||||
discussion: 20,
|
||||
grades: 10,
|
||||
authoring: 5,
|
||||
})
|
||||
.attr('showNotificationsTray', true);
|
||||
|
||||
Factory.define('notification')
|
||||
.sequence('id')
|
||||
.attr('type', 'post')
|
||||
.sequence('content', ['id'], (idx, notificationId) => `<p><strong>User ${idx}</strong> posts <strong>Hello and welcome to SC0x
|
||||
${notificationId}!</strong></p>`)
|
||||
.attr('course_name', 'Supply Chain Analytics')
|
||||
.sequence('content_url', (idx) => `https://example.com/${idx}`)
|
||||
.attr('last_read', null)
|
||||
.attr('last_seen', null)
|
||||
.sequence('created_at', ['createdDate'], (idx, date) => date);
|
||||
|
||||
Factory.define('notificationsList')
|
||||
.attr('next', null)
|
||||
.attr('previous', null)
|
||||
.attr('count', null, 2)
|
||||
.attr('num_pages', null, 1)
|
||||
.attr('current_page', null, 1)
|
||||
.attr('start', null, 0)
|
||||
.attr('results', ['results'], (results) => results || Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() }));
|
||||
@@ -1,39 +0,0 @@
|
||||
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
export const getNotificationsCountApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/count/`;
|
||||
export const getNotificationsListApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/`;
|
||||
export const markNotificationsSeenApiUrl = (appName) => `${getConfig().LMS_BASE_URL}/api/notifications/mark-seen/${appName}/`;
|
||||
export const markNotificationAsReadApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/read/`;
|
||||
|
||||
export async function getNotificationsList(appName, page) {
|
||||
const params = snakeCaseObject({ appName, page });
|
||||
const { data } = await getAuthenticatedHttpClient().get(getNotificationsListApiUrl(), { params });
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getNotificationCounts() {
|
||||
const { data } = await getAuthenticatedHttpClient().get(getNotificationsCountApiUrl());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function markNotificationSeen(appName) {
|
||||
const { data } = await getAuthenticatedHttpClient().put(`${markNotificationsSeenApiUrl(appName)}`);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function markAllNotificationRead(appName) {
|
||||
const params = snakeCaseObject({ appName });
|
||||
const { data } = await getAuthenticatedHttpClient().patch(markNotificationAsReadApiUrl(), params);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function markNotificationRead(notificationId) {
|
||||
const params = snakeCaseObject({ notificationId });
|
||||
const { data } = await getAuthenticatedHttpClient().patch(markNotificationAsReadApiUrl(), params);
|
||||
|
||||
return { data, id: notificationId };
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { initializeMockApp } from '@edx/frontend-platform/testing';
|
||||
|
||||
import {
|
||||
getNotificationsListApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl,
|
||||
getNotificationCounts, getNotificationsList, markNotificationSeen, markAllNotificationRead, markNotificationRead,
|
||||
} from './api';
|
||||
|
||||
import './__factories__';
|
||||
|
||||
const notificationCountsApiUrl = getNotificationsCountApiUrl();
|
||||
const notificationsApiUrl = getNotificationsListApiUrl();
|
||||
const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussion');
|
||||
const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl();
|
||||
|
||||
let axiosMock = null;
|
||||
|
||||
describe('Notifications API', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: '123abc',
|
||||
username: 'testuser',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
Factory.resetAll();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.reset();
|
||||
});
|
||||
|
||||
it('Successfully get notification counts for different tabs.', async () => {
|
||||
axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount')));
|
||||
|
||||
const { count, countByAppName } = await getNotificationCounts();
|
||||
|
||||
expect(count).toEqual(45);
|
||||
expect(countByAppName.reminders).toEqual(10);
|
||||
expect(countByAppName.discussion).toEqual(20);
|
||||
expect(countByAppName.grades).toEqual(10);
|
||||
expect(countByAppName.authoring).toEqual(5);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, message: 'Failed to get notification counts.' },
|
||||
{ statusCode: 403, message: 'Denied to get notification counts.' },
|
||||
])('%s for notification counts API.', async ({ statusCode, message }) => {
|
||||
axiosMock.onGet(notificationCountsApiUrl).reply(statusCode, { message });
|
||||
try {
|
||||
await getNotificationCounts();
|
||||
} catch (error) {
|
||||
expect(error.response.status).toEqual(statusCode);
|
||||
expect(error.response.data.message).toEqual(message);
|
||||
}
|
||||
});
|
||||
|
||||
it('Successfully get notifications.', async () => {
|
||||
axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.build('notificationsList')));
|
||||
|
||||
const notifications = await getNotificationsList('discussion', 1);
|
||||
|
||||
expect(notifications.results).toHaveLength(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, message: 'Failed to get notifications.' },
|
||||
{ statusCode: 403, message: 'Denied to get notifications.' },
|
||||
])('%s for notification API.', async ({ statusCode, message }) => {
|
||||
axiosMock.onGet(notificationsApiUrl).reply(statusCode, { message });
|
||||
try {
|
||||
await getNotificationsList('discussion', 1);
|
||||
} catch (error) {
|
||||
expect(error.response.status).toEqual(statusCode);
|
||||
expect(error.response.data.message).toEqual(message);
|
||||
}
|
||||
});
|
||||
|
||||
it('Successfully marked all notifications as seen for selected app.', async () => {
|
||||
axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(200, { message: 'Notifications marked seen.' });
|
||||
|
||||
const { message } = await markNotificationSeen('discussion');
|
||||
|
||||
expect(message).toEqual('Notifications marked seen.');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, message: 'Failed to mark all notifications as seen for selected app.' },
|
||||
{ statusCode: 403, message: 'Denied to mark all notifications as seen for selected app.' },
|
||||
])('%s for notification mark as seen API.', async ({ statusCode, message }) => {
|
||||
axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(statusCode, { message });
|
||||
try {
|
||||
await markNotificationSeen('discussion');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toEqual(statusCode);
|
||||
expect(error.response.data.message).toEqual(message);
|
||||
}
|
||||
});
|
||||
|
||||
it('Successfully marked all notifications as read for selected app.', async () => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' });
|
||||
|
||||
const { message } = await markAllNotificationRead('discussion');
|
||||
|
||||
expect(message).toEqual('Notifications marked read.');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, message: 'Failed to mark all notifications as read for selected app.' },
|
||||
{ statusCode: 403, message: 'Denied to mark all notifications as read for selected app.' },
|
||||
])('%s for notification mark all as read API.', async ({ statusCode, message }) => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(statusCode, { message });
|
||||
try {
|
||||
await markAllNotificationRead('discussion');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toEqual(statusCode);
|
||||
expect(error.response.data.message).toEqual(message);
|
||||
}
|
||||
});
|
||||
|
||||
it('Successfully marked notification as read.', async () => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notification marked read.' });
|
||||
|
||||
const { data } = await markNotificationRead(1);
|
||||
|
||||
expect(data.message).toEqual('Notification marked read.');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, message: 'Failed to mark notification as read.' },
|
||||
{ statusCode: 403, message: 'Denied to mark notification as read.' },
|
||||
])('%s for notification mark as read API.', async ({ statusCode, message }) => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(statusCode, { message });
|
||||
try {
|
||||
await markAllNotificationRead(1);
|
||||
} catch (error) {
|
||||
expect(error.response.status).toEqual(statusCode);
|
||||
expect(error.response.data.message).toEqual(message);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||
|
||||
export function useIsOnMediumScreen() {
|
||||
const windowSize = useWindowSize();
|
||||
return breakpoints.large.maxWidth > windowSize.width && windowSize.width >= breakpoints.medium.minWidth;
|
||||
}
|
||||
|
||||
export function useIsOnLargeScreen() {
|
||||
const windowSize = useWindowSize();
|
||||
return windowSize.width >= breakpoints.extraLarge.minWidth;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './slice';
|
||||
@@ -1,134 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "post",
|
||||
"content": "<p><b>SCM_Lead</b> posts <b>Hello and welcome to SC0x!</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:46:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "help",
|
||||
"content": "<p><b>MITx_Learner</b> asked <b>What grade does a student need to get in order to pass the course and earn a certificate?</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "post",
|
||||
"content": "<p><b>SCM_Lead</b> posts <b>Hello and welcome to SC0x!</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:46:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "respond",
|
||||
"content": "<p><b>MITx_Learner</b> responded <b>Can't find linear regression in section 3 review</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented on <b>MITx_Expert's</b> response on a post your following <b>Can't find linear regression in section 3 review</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "question",
|
||||
"content": "<p><b>MITx_Learner</b> commented <b>Examples of quadratic equations in supply chains</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "answer",
|
||||
"content": "<p><b>MITx_Expert</b> answered <b>Examples of quadratic equations in supply chains</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-05T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented <b>Examples of quadratic equations in supply chains</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented on <b>MITx_Expert's</b>what grade does a student need to get in order to pass the course and earn a certificate?</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented on your response in <b>Convexity of f(x)=1/x , x>1</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "answer",
|
||||
"content": "<p><b>SCM_Lead’s</b> response has been marked as answer in your post <b>Quiz in section 3 - Please explain the F-Significance value</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "endorsed",
|
||||
"content": "<p>Your response has been endorsed in <b>Quiz in section 3 - Please explain the F-Significance value</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "reported",
|
||||
"content": "<p><b>MITx Learner’s</b> post has been reported <b>“Here are the exam answers. Question 1 - CSA stands for Compliance Safety Ac...”</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { initializeMockApp } from '@edx/frontend-platform/testing';
|
||||
|
||||
import { initializeStore } from '../../store';
|
||||
import executeThunk from '../../test-utils';
|
||||
import mockNotificationsResponse from '../test-utils';
|
||||
import {
|
||||
getNotificationsListApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl,
|
||||
} from './api';
|
||||
import {
|
||||
fetchAppsNotificationCount, fetchNotificationList, markNotificationsAsRead, markAllNotificationsAsRead,
|
||||
resetNotificationState, markNotificationsAsSeen,
|
||||
} from './thunks';
|
||||
|
||||
import './__factories__';
|
||||
|
||||
const notificationCountsApiUrl = getNotificationsCountApiUrl();
|
||||
const notificationsListApiUrl = getNotificationsListApiUrl();
|
||||
const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl();
|
||||
const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussion');
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
|
||||
describe('Notification Redux', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: '123abc',
|
||||
username: 'testuser',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
Factory.resetAll();
|
||||
store = initializeStore();
|
||||
|
||||
({ store, axiosMock } = await mockNotificationsResponse());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.reset();
|
||||
});
|
||||
|
||||
it('Successfully loaded initial notification states in the redux.', async () => {
|
||||
executeThunk(resetNotificationState(), store.dispatch, store.getState);
|
||||
|
||||
const { notifications } = store.getState();
|
||||
|
||||
expect(notifications.notificationStatus).toEqual('idle');
|
||||
expect(notifications.appName).toEqual('discussion');
|
||||
expect(notifications.appsId).toHaveLength(0);
|
||||
expect(notifications.apps).toEqual({});
|
||||
expect(notifications.notifications).toEqual({});
|
||||
expect(notifications.tabsCount).toEqual({});
|
||||
expect(notifications.showNotificationsTray).toEqual(false);
|
||||
expect(notifications.pagination).toEqual({});
|
||||
});
|
||||
|
||||
it('Successfully loaded notifications list in the redux.', async () => {
|
||||
const { notifications: { notifications } } = store.getState();
|
||||
expect(Object.keys(notifications)).toHaveLength(10);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, status: 'failed' },
|
||||
{ statusCode: 403, status: 'denied' },
|
||||
])('%s to load notifications list in the redux.', async ({ statusCode, status }) => {
|
||||
axiosMock.onGet(notificationsListApiUrl).reply(statusCode);
|
||||
await executeThunk(fetchNotificationList({ page: 1 }), store.dispatch, store.getState);
|
||||
|
||||
const { notifications: { notificationStatus } } = store.getState();
|
||||
|
||||
expect(notificationStatus).toEqual(status);
|
||||
});
|
||||
|
||||
it('Successfully loaded notification counts in the redux.', async () => {
|
||||
const { notifications: { tabsCount } } = store.getState();
|
||||
|
||||
expect(tabsCount.count).toEqual(25);
|
||||
expect(tabsCount.reminders).toEqual(10);
|
||||
expect(tabsCount.discussion).toEqual(0);
|
||||
expect(tabsCount.grades).toEqual(10);
|
||||
expect(tabsCount.authoring).toEqual(5);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, status: 'failed' },
|
||||
{ statusCode: 403, status: 'denied' },
|
||||
])('%s to load notification counts in the redux.', async ({ statusCode, status }) => {
|
||||
axiosMock.onGet(notificationCountsApiUrl).reply(statusCode);
|
||||
await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState);
|
||||
|
||||
const { notifications: { notificationStatus } } = store.getState();
|
||||
|
||||
expect(notificationStatus).toEqual(status);
|
||||
});
|
||||
|
||||
it('Successfully marked all notifications as seen for selected app.', async () => {
|
||||
axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(200);
|
||||
await executeThunk(markNotificationsAsSeen('discussion'), store.dispatch, store.getState);
|
||||
|
||||
expect(store.getState().notifications.notificationStatus).toEqual('successful');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, status: 'failed' },
|
||||
{ statusCode: 403, status: 'denied' },
|
||||
])('%s to mark all notifications as seen for selected app.', async ({ statusCode, status }) => {
|
||||
axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(statusCode);
|
||||
await executeThunk(markNotificationsAsSeen('discussion'), store.dispatch, store.getState);
|
||||
|
||||
const { notifications: { notificationStatus } } = store.getState();
|
||||
|
||||
expect(notificationStatus).toEqual(status);
|
||||
});
|
||||
|
||||
it('Successfully marked all notifications as read for selected app in the redux.', async () => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200);
|
||||
await executeThunk(markAllNotificationsAsRead('discussion'), store.dispatch, store.getState);
|
||||
|
||||
const { notifications: { notificationStatus, notifications } } = store.getState();
|
||||
const firstNotification = Object.values(notifications)[0];
|
||||
|
||||
expect(notificationStatus).toEqual('successful');
|
||||
expect(firstNotification.lastRead).not.toBeNull();
|
||||
});
|
||||
|
||||
it('Successfully marked notification as read in the redux.', async () => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200);
|
||||
await executeThunk(markNotificationsAsRead(1), store.dispatch, store.getState);
|
||||
|
||||
const { notifications: { notificationStatus, notifications } } = store.getState();
|
||||
const firstNotification = Object.values(notifications)[0];
|
||||
|
||||
expect(notificationStatus).toEqual('successful');
|
||||
expect(firstNotification.lastRead).not.toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ statusCode: 404, status: 'failed' },
|
||||
{ statusCode: 403, status: 'denied' },
|
||||
])('%s to marked notification as read in the redux.', async ({ statusCode, status }) => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(statusCode);
|
||||
await executeThunk(markNotificationsAsRead(1), store.dispatch, store.getState);
|
||||
|
||||
const { notifications: { notificationStatus } } = store.getState();
|
||||
|
||||
expect(notificationStatus).toEqual(status);
|
||||
});
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { initializeMockApp } from '@edx/frontend-platform/testing';
|
||||
|
||||
import { initializeStore } from '../../store';
|
||||
import mockNotificationsResponse from '../test-utils';
|
||||
import {
|
||||
selectNotifications,
|
||||
selectNotificationsByIds,
|
||||
selectNotificationStatus,
|
||||
selectNotificationTabs,
|
||||
selectNotificationTabsCount,
|
||||
selectPaginationData,
|
||||
selectSelectedAppName,
|
||||
selectSelectedAppNotificationIds,
|
||||
selectShowNotificationTray,
|
||||
} from './selectors';
|
||||
|
||||
import './__factories__';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
|
||||
describe('Notification Selectors', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: '123abc',
|
||||
username: 'testuser',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
Factory.resetAll();
|
||||
store = initializeStore();
|
||||
|
||||
({ store, axiosMock } = await mockNotificationsResponse());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.reset();
|
||||
});
|
||||
|
||||
it('Should return notification status.', async () => {
|
||||
const state = store.getState();
|
||||
const status = selectNotificationStatus()(state);
|
||||
|
||||
expect(status).toEqual('successful');
|
||||
});
|
||||
|
||||
it('Should return notification tabs count.', async () => {
|
||||
const state = store.getState();
|
||||
const tabsCount = selectNotificationTabsCount()(state);
|
||||
|
||||
expect(tabsCount.count).toEqual(25);
|
||||
expect(tabsCount.reminders).toEqual(10);
|
||||
expect(tabsCount.discussion).toEqual(0);
|
||||
expect(tabsCount.grades).toEqual(10);
|
||||
expect(tabsCount.authoring).toEqual(5);
|
||||
});
|
||||
|
||||
it('Should return notification tabs.', async () => {
|
||||
const state = store.getState();
|
||||
const tabs = selectNotificationTabs()(state);
|
||||
|
||||
expect(tabs).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('Should return selected app notification ids.', async () => {
|
||||
const state = store.getState();
|
||||
const notificationIds = selectSelectedAppNotificationIds('discussion')(state);
|
||||
|
||||
expect(notificationIds).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('Should return show notification tray status.', async () => {
|
||||
const state = store.getState();
|
||||
const showNotificationTrayStatus = selectShowNotificationTray()(state);
|
||||
|
||||
expect(showNotificationTrayStatus).toEqual(true);
|
||||
});
|
||||
|
||||
it('Should return notifications.', async () => {
|
||||
const state = store.getState();
|
||||
const notifications = selectNotifications()(state);
|
||||
|
||||
expect(Object.keys(notifications)).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('Should return notifications from Ids.', async () => {
|
||||
const state = store.getState();
|
||||
const notifications = selectNotificationsByIds('discussion')(state);
|
||||
|
||||
expect(notifications).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('Should return selected app name.', async () => {
|
||||
const state = store.getState();
|
||||
const appName = selectSelectedAppName()(state);
|
||||
|
||||
expect(appName).toEqual('discussion');
|
||||
});
|
||||
|
||||
it('Should return pagination data.', async () => {
|
||||
const state = store.getState();
|
||||
const paginationData = selectPaginationData()(state);
|
||||
|
||||
expect(paginationData.currentPage).toEqual(1);
|
||||
expect(paginationData.numPages).toEqual(2);
|
||||
expect(paginationData.hasMorePages).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
|
||||
export const selectNotificationStatus = () => state => state.notifications.notificationStatus;
|
||||
|
||||
export const selectNotificationTabsCount = () => state => state.notifications.tabsCount;
|
||||
|
||||
export const selectNotificationTabs = () => state => state.notifications.appsId;
|
||||
|
||||
export const selectSelectedAppNotificationIds = (appName) => state => state.notifications.apps[appName] ?? [];
|
||||
|
||||
export const selectShowNotificationTray = () => state => state.notifications.showNotificationsTray;
|
||||
|
||||
export const selectNotifications = () => state => state.notifications.notifications;
|
||||
|
||||
export const selectNotificationsByIds = (appName) => createSelector(
|
||||
selectNotifications(),
|
||||
selectSelectedAppNotificationIds(appName),
|
||||
(notifications, notificationIds) => notificationIds.map((notificationId) => notifications[notificationId]) || [],
|
||||
);
|
||||
|
||||
export const selectSelectedAppName = () => state => state.notifications.appName;
|
||||
|
||||
export const selectPaginationData = () => state => state.notifications.pagination;
|
||||
@@ -1,147 +0,0 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const RequestStatus = {
|
||||
IDLE: 'idle',
|
||||
IN_PROGRESS: 'in-progress',
|
||||
SUCCESSFUL: 'successful',
|
||||
FAILED: 'failed',
|
||||
DENIED: 'denied',
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
notificationStatus: RequestStatus.IDLE,
|
||||
appName: 'discussion',
|
||||
appsId: [],
|
||||
apps: {},
|
||||
notifications: {},
|
||||
tabsCount: {},
|
||||
showNotificationsTray: false,
|
||||
pagination: {},
|
||||
};
|
||||
const slice = createSlice({
|
||||
name: 'notifications',
|
||||
initialState,
|
||||
reducers: {
|
||||
fetchNotificationDenied: (state) => {
|
||||
state.notificationStatus = RequestStatus.DENIED;
|
||||
},
|
||||
fetchNotificationFailure: (state) => {
|
||||
state.notificationStatus = RequestStatus.FAILED;
|
||||
},
|
||||
fetchNotificationRequest: (state) => {
|
||||
state.notificationStatus = RequestStatus.IN_PROGRESS;
|
||||
},
|
||||
fetchNotificationSuccess: (state, { payload }) => {
|
||||
const {
|
||||
newNotificationIds, notificationsKeyValuePair, pagination,
|
||||
} = payload;
|
||||
const existingNotificationIds = state.apps[state.appName];
|
||||
state.apps[state.appName] = Array.from(new Set([...existingNotificationIds, ...newNotificationIds]));
|
||||
state.notifications = { ...state.notifications, ...notificationsKeyValuePair };
|
||||
state.tabsCount.count -= state.tabsCount[state.appName];
|
||||
state.tabsCount[state.appName] = 0;
|
||||
state.notificationStatus = RequestStatus.SUCCESSFUL;
|
||||
state.pagination = pagination;
|
||||
},
|
||||
fetchNotificationsCountDenied: (state) => {
|
||||
state.notificationStatus = RequestStatus.DENIED;
|
||||
},
|
||||
fetchNotificationsCountFailure: (state) => {
|
||||
state.notificationStatus = RequestStatus.FAILED;
|
||||
},
|
||||
fetchNotificationsCountRequest: (state) => {
|
||||
state.notificationStatus = RequestStatus.IN_PROGRESS;
|
||||
},
|
||||
fetchNotificationsCountSuccess: (state, { payload }) => {
|
||||
const {
|
||||
countByAppName, appIds, apps, count, showNotificationsTray,
|
||||
} = payload;
|
||||
state.tabsCount = { count, ...countByAppName };
|
||||
state.appsId = appIds;
|
||||
state.apps = apps;
|
||||
state.showNotificationsTray = showNotificationsTray;
|
||||
state.notificationStatus = RequestStatus.SUCCESSFUL;
|
||||
},
|
||||
markNotificationsAsSeenRequest: (state) => {
|
||||
state.notificationStatus = RequestStatus.IN_PROGRESS;
|
||||
},
|
||||
markNotificationsAsSeenSuccess: (state) => {
|
||||
state.notificationStatus = RequestStatus.SUCCESSFUL;
|
||||
},
|
||||
markNotificationsAsSeenDenied: (state) => {
|
||||
state.notificationStatus = RequestStatus.DENIED;
|
||||
},
|
||||
markNotificationsAsSeenFailure: (state) => {
|
||||
state.notificationStatus = RequestStatus.FAILED;
|
||||
},
|
||||
markAllNotificationsAsReadRequest: (state) => {
|
||||
state.notificationStatus = RequestStatus.IN_PROGRESS;
|
||||
},
|
||||
markAllNotificationsAsReadSuccess: (state) => {
|
||||
const updatedNotifications = Object.fromEntries(
|
||||
Object.entries(state.notifications).map(([key, notification]) => [
|
||||
key, { ...notification, lastRead: new Date().toISOString() },
|
||||
]),
|
||||
);
|
||||
state.notifications = updatedNotifications;
|
||||
state.notificationStatus = RequestStatus.SUCCESSFUL;
|
||||
},
|
||||
markAllNotificationsAsReadDenied: (state) => {
|
||||
state.notificationStatus = RequestStatus.DENIED;
|
||||
},
|
||||
markAllNotificationsAsReadFailure: (state) => {
|
||||
state.notificationStatus = RequestStatus.FAILED;
|
||||
},
|
||||
markNotificationsAsReadRequest: (state) => {
|
||||
state.notificationStatus = RequestStatus.IN_PROGRESS;
|
||||
},
|
||||
markNotificationsAsReadSuccess: (state, { payload }) => {
|
||||
const date = new Date().toISOString();
|
||||
state.notifications[payload.id] = { ...state.notifications[payload.id], lastRead: date };
|
||||
state.notificationStatus = RequestStatus.SUCCESSFUL;
|
||||
},
|
||||
markNotificationsAsReadDenied: (state) => {
|
||||
state.notificationStatus = RequestStatus.DENIED;
|
||||
},
|
||||
markNotificationsAsReadFailure: (state) => {
|
||||
state.notificationStatus = RequestStatus.FAILED;
|
||||
},
|
||||
resetNotificationStateRequest: () => initialState,
|
||||
updateAppNameRequest: (state, { payload }) => {
|
||||
state.appName = payload.appName;
|
||||
state.pagination.currentPage = 1;
|
||||
},
|
||||
updatePaginationRequest: (state) => {
|
||||
state.pagination.currentPage += 1;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
fetchNotificationDenied,
|
||||
fetchNotificationFailure,
|
||||
fetchNotificationRequest,
|
||||
fetchNotificationSuccess,
|
||||
fetchNotificationsCountDenied,
|
||||
fetchNotificationsCountFailure,
|
||||
fetchNotificationsCountRequest,
|
||||
fetchNotificationsCountSuccess,
|
||||
markNotificationsAsSeenRequest,
|
||||
markNotificationsAsSeenSuccess,
|
||||
markNotificationsAsSeenFailure,
|
||||
markNotificationsAsSeenDenied,
|
||||
markAllNotificationsAsReadDenied,
|
||||
markAllNotificationsAsReadRequest,
|
||||
markAllNotificationsAsReadSuccess,
|
||||
markAllNotificationsAsReadFailure,
|
||||
markNotificationsAsReadDenied,
|
||||
markNotificationsAsReadRequest,
|
||||
markNotificationsAsReadSuccess,
|
||||
markNotificationsAsReadFailure,
|
||||
resetNotificationStateRequest,
|
||||
updateAppNameRequest,
|
||||
updatePaginationRequest,
|
||||
} = slice.actions;
|
||||
|
||||
export const notificationsReducer = slice.reducer;
|
||||
@@ -1,135 +0,0 @@
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
import {
|
||||
fetchNotificationSuccess,
|
||||
fetchNotificationRequest,
|
||||
fetchNotificationFailure,
|
||||
fetchNotificationDenied,
|
||||
fetchNotificationsCountFailure,
|
||||
fetchNotificationsCountRequest,
|
||||
fetchNotificationsCountSuccess,
|
||||
fetchNotificationsCountDenied,
|
||||
markNotificationsAsSeenRequest,
|
||||
markNotificationsAsSeenSuccess,
|
||||
markNotificationsAsSeenFailure,
|
||||
markNotificationsAsSeenDenied,
|
||||
markNotificationsAsReadDenied,
|
||||
resetNotificationStateRequest,
|
||||
markAllNotificationsAsReadRequest,
|
||||
markAllNotificationsAsReadSuccess,
|
||||
markAllNotificationsAsReadFailure,
|
||||
markAllNotificationsAsReadDenied,
|
||||
markNotificationsAsReadRequest,
|
||||
markNotificationsAsReadSuccess,
|
||||
markNotificationsAsReadFailure,
|
||||
} from './slice';
|
||||
import {
|
||||
getNotificationsList, getNotificationCounts, markNotificationSeen, markAllNotificationRead, markNotificationRead,
|
||||
} from './api';
|
||||
import { getHttpErrorStatus } from '../utils';
|
||||
|
||||
const normalizeNotificationCounts = ({ countByAppName, count, showNotificationsTray }) => {
|
||||
const appIds = Object.keys(countByAppName);
|
||||
const apps = appIds.reduce((acc, appId) => { acc[appId] = []; return acc; }, {});
|
||||
return {
|
||||
countByAppName, appIds, apps, count, showNotificationsTray,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeNotifications = (data) => {
|
||||
const newNotificationIds = data.results.map(notification => notification.id.toString());
|
||||
const notificationsKeyValuePair = data.results.reduce((acc, obj) => { acc[obj.id] = obj; return acc; }, {});
|
||||
const pagination = {
|
||||
numPages: data.numPages,
|
||||
currentPage: data.currentPage,
|
||||
hasMorePages: !!data.next,
|
||||
};
|
||||
return {
|
||||
newNotificationIds, notificationsKeyValuePair, pagination,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchNotificationList = ({ appName, page }) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(fetchNotificationRequest({ appName }));
|
||||
const data = await getNotificationsList(appName, page);
|
||||
const normalisedData = normalizeNotifications((camelCaseObject(data)));
|
||||
dispatch(fetchNotificationSuccess({ ...normalisedData }));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(fetchNotificationDenied(appName));
|
||||
} else {
|
||||
dispatch(fetchNotificationFailure(appName));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchAppsNotificationCount = () => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(fetchNotificationsCountRequest());
|
||||
const data = await getNotificationCounts();
|
||||
const normalisedData = normalizeNotificationCounts((camelCaseObject(data)));
|
||||
dispatch(fetchNotificationsCountSuccess({ ...normalisedData }));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(fetchNotificationsCountDenied());
|
||||
} else {
|
||||
dispatch(fetchNotificationsCountFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const markAllNotificationsAsRead = (appName) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(markAllNotificationsAsReadRequest({ appName }));
|
||||
const data = await markAllNotificationRead(appName);
|
||||
dispatch(markAllNotificationsAsReadSuccess(camelCaseObject(data)));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(markAllNotificationsAsReadDenied());
|
||||
} else {
|
||||
dispatch(markAllNotificationsAsReadFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const markNotificationsAsRead = (notificationId) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(markNotificationsAsReadRequest({ notificationId }));
|
||||
const data = await markNotificationRead(notificationId);
|
||||
dispatch(markNotificationsAsReadSuccess(camelCaseObject(data)));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(markNotificationsAsReadDenied());
|
||||
} else {
|
||||
dispatch(markNotificationsAsReadFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const markNotificationsAsSeen = (appName) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(markNotificationsAsSeenRequest({ appName }));
|
||||
const data = await markNotificationSeen(appName);
|
||||
dispatch(markNotificationsAsSeenSuccess(camelCaseObject(data)));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(markNotificationsAsSeenDenied());
|
||||
} else {
|
||||
dispatch(markNotificationsAsSeenFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const resetNotificationState = () => (
|
||||
async (dispatch) => { dispatch(resetNotificationStateRequest()); }
|
||||
);
|
||||
@@ -1,103 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, {
|
||||
useCallback, useEffect, useRef, useState,
|
||||
} from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Badge, Icon, IconButton, OverlayTrigger, Popover,
|
||||
} from '@edx/paragon';
|
||||
import { NotificationsNone, Settings } from '@edx/paragon/icons';
|
||||
import { selectNotificationTabsCount } from './data/selectors';
|
||||
import { resetNotificationState } from './data/thunks';
|
||||
import { useIsOnLargeScreen, useIsOnMediumScreen } from './data/hook';
|
||||
import NotificationTabs from './NotificationTabs';
|
||||
import messages from './messages';
|
||||
|
||||
const Notifications = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const popoverRef = useRef(null);
|
||||
const buttonRef = useRef(null);
|
||||
const [enableNotificationTray, setEnableNotificationTray] = useState(false);
|
||||
const notificationCounts = useSelector(selectNotificationTabsCount());
|
||||
const isOnMediumScreen = useIsOnMediumScreen();
|
||||
const isOnLargeScreen = useIsOnLargeScreen();
|
||||
|
||||
const hideNotificationTray = useCallback(() => {
|
||||
setEnableNotificationTray(prevState => !prevState);
|
||||
}, []);
|
||||
|
||||
const handleClickOutsideNotificationTray = useCallback((event) => {
|
||||
if (!popoverRef.current?.contains(event.target) && !buttonRef.current?.contains(event.target)) {
|
||||
setEnableNotificationTray(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutsideNotificationTray);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutsideNotificationTray);
|
||||
dispatch(resetNotificationState());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
key="bottom"
|
||||
placement="bottom"
|
||||
id="notificationTray"
|
||||
show={enableNotificationTray}
|
||||
overlay={(
|
||||
<Popover
|
||||
id="notificationTray"
|
||||
data-testid="notification-tray"
|
||||
className={classNames('overflow-auto rounded-0 border-0', {
|
||||
'w-100': !isOnMediumScreen && !isOnLargeScreen,
|
||||
'medium-screen': isOnMediumScreen,
|
||||
'large-screen': isOnLargeScreen,
|
||||
})}
|
||||
>
|
||||
<div ref={popoverRef}>
|
||||
<Popover.Title as="h2" className="d-flex justify-content-between p-0 m-4 border-0 text-primary-500 font-size-18 line-height-24">
|
||||
{intl.formatMessage(messages.notificationTitle)}
|
||||
<Icon src={Settings} className="icon-size-20" data-testid="setting-icon" />
|
||||
</Popover.Title>
|
||||
<Popover.Content className="notification-content p-0">
|
||||
<NotificationTabs />
|
||||
</Popover.Content>
|
||||
</div>
|
||||
</Popover>
|
||||
)}
|
||||
>
|
||||
<div ref={buttonRef}>
|
||||
<IconButton
|
||||
isActive={enableNotificationTray}
|
||||
alt="notification bell icon"
|
||||
onClick={hideNotificationTray}
|
||||
src={NotificationsNone}
|
||||
iconAs={Icon}
|
||||
variant="light"
|
||||
iconClassNames="text-primary-500"
|
||||
className="ml-4 mr-1 my-3 notification-button"
|
||||
data-testid="notification-bell-icon"
|
||||
/>
|
||||
{notificationCounts?.count > 0 && (
|
||||
<Badge
|
||||
pill
|
||||
variant="danger"
|
||||
className="font-weight-normal px-1 notification-badge"
|
||||
data-testid="notification-count"
|
||||
>
|
||||
{notificationCounts.count}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
@@ -1,109 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
act, fireEvent, render, screen, waitFor,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import AuthenticatedUserDropdown from '../learning-header/AuthenticatedUserDropdown';
|
||||
import { initializeStore } from '../store';
|
||||
import executeThunk from '../test-utils';
|
||||
import { getNotificationsCountApiUrl } from './data/api';
|
||||
import { fetchAppsNotificationCount } from './data/thunks';
|
||||
|
||||
import './data/__factories__';
|
||||
|
||||
const notificationCountsApiUrl = getNotificationsCountApiUrl();
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
|
||||
function renderComponent() {
|
||||
render(
|
||||
<ResponsiveContext.Provider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<AppProvider store={store}>
|
||||
<AppContext.Provider>
|
||||
<AuthenticatedUserDropdown />
|
||||
</AppContext.Provider>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
</ResponsiveContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('Notification test cases.', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: '123abc',
|
||||
username: 'testuser',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
Factory.resetAll();
|
||||
store = initializeStore();
|
||||
});
|
||||
|
||||
async function setupMockNotificationCountResponse(count = 45, showNotificationsTray = true) {
|
||||
axiosMock.onGet(notificationCountsApiUrl)
|
||||
.reply(200, (Factory.build('notificationsCount', { count, showNotificationsTray })));
|
||||
await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState);
|
||||
}
|
||||
|
||||
it('Successfully showed bell icon and unseen count on it if unseen count is greater then 0.', async () => {
|
||||
await setupMockNotificationCountResponse();
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
const notificationCount = screen.queryByTestId('notification-count');
|
||||
|
||||
expect(bellIcon).toBeInTheDocument();
|
||||
expect(notificationCount).toBeInTheDocument();
|
||||
expect(screen.queryByText(45)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully showed bell icon and hide unseen count tag when unseen count is zero.', async () => {
|
||||
await setupMockNotificationCountResponse(0);
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
const notificationCount = screen.queryByTestId('notification-count');
|
||||
|
||||
expect(bellIcon).toBeInTheDocument();
|
||||
expect(notificationCount).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully hides bell icon when showNotificationsTray is false.', async () => {
|
||||
await setupMockNotificationCountResponse(45, false);
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
|
||||
expect(bellIcon).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully viewed setting icon and show/hide notification tray by clicking on the bell icon .', async () => {
|
||||
await setupMockNotificationCountResponse();
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
expect(screen.queryByTestId('notification-tray')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('setting-icon')).toBeInTheDocument();
|
||||
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
await waitFor(() => expect(screen.queryByTestId('notification-tray')).not.toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
notificationTitle: {
|
||||
id: 'notification.title',
|
||||
defaultMessage: 'Notifications',
|
||||
description: 'Notifications',
|
||||
},
|
||||
notificationTodayHeading: {
|
||||
id: 'notification.today.heading',
|
||||
defaultMessage: 'Last 24 hours',
|
||||
description: 'Today Notifications',
|
||||
},
|
||||
notificationEarlierHeading: {
|
||||
id: 'notification.earlier.heading',
|
||||
defaultMessage: 'Earlier',
|
||||
description: 'Earlier Notifications',
|
||||
},
|
||||
notificationMarkAsRead: {
|
||||
id: 'notification.mark.as.read',
|
||||
defaultMessage: 'Mark all as read',
|
||||
description: 'Mark all Notifications as read',
|
||||
},
|
||||
fullStop: {
|
||||
id: 'notification.fullStop',
|
||||
defaultMessage: '•',
|
||||
description: 'Fullstop shown to users to indicate who edited a post.',
|
||||
},
|
||||
loadMoreNotifications: {
|
||||
id: 'notification.load.more.notifications',
|
||||
defaultMessage: 'Load more notifications',
|
||||
description: 'Load more button to load more notifications',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,87 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
act, fireEvent, render, screen,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import AuthenticatedUserDropdown from '../learning-header/AuthenticatedUserDropdown';
|
||||
import { initializeStore } from '../store';
|
||||
import { markNotificationAsReadApiUrl } from './data/api';
|
||||
import mockNotificationsResponse from './test-utils';
|
||||
|
||||
import './data/__factories__';
|
||||
|
||||
const markedNotificationAsReadApiUrl = markNotificationAsReadApiUrl();
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
|
||||
function renderComponent() {
|
||||
render(
|
||||
<ResponsiveContext.Provider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<AppProvider store={store}>
|
||||
<AppContext.Provider>
|
||||
<AuthenticatedUserDropdown />
|
||||
</AppContext.Provider>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
</ResponsiveContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('Notification row item test cases.', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
Factory.resetAll();
|
||||
store = initializeStore();
|
||||
|
||||
({ store, axiosMock } = await mockNotificationsResponse());
|
||||
});
|
||||
|
||||
it(
|
||||
'Successfully viewed notification icon, notification context, unread , course name and notification time.',
|
||||
async () => {
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
|
||||
expect(screen.queryByTestId('notification-icon-1')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('notification-content-1')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('notification-course-1')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('notification-created-date-1')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('unread-notification-1')).toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
|
||||
it('Successfully marked notification as read.', async () => {
|
||||
axiosMock.onPatch(markedNotificationAsReadApiUrl).reply(200, { message: 'Notification marked read.' });
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
|
||||
const notification = screen.queryByTestId('notification-1');
|
||||
await act(async () => { fireEvent.click(notification); });
|
||||
|
||||
expect(screen.queryByTestId('unread-notification-1')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
act, fireEvent, render, screen, within,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import AuthenticatedUserDropdown from '../learning-header/AuthenticatedUserDropdown';
|
||||
import { initializeStore } from '../store';
|
||||
import { markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, getNotificationsListApiUrl } from './data/api';
|
||||
import mockNotificationsResponse from './test-utils';
|
||||
import { markNotificationsAsSeen, fetchNotificationList } from './data/thunks';
|
||||
import executeThunk from '../test-utils';
|
||||
import './data/__factories__';
|
||||
|
||||
const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl();
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
|
||||
function renderComponent() {
|
||||
render(
|
||||
<ResponsiveContext.Provider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<AppProvider store={store}>
|
||||
<AppContext.Provider>
|
||||
<AuthenticatedUserDropdown />
|
||||
</AppContext.Provider>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
</ResponsiveContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('Notification sections test cases.', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
Factory.resetAll();
|
||||
store = initializeStore();
|
||||
|
||||
({ store, axiosMock } = await mockNotificationsResponse());
|
||||
});
|
||||
|
||||
it('Successfully viewed last 24 hours and earlier section along with mark all as read label.', async () => {
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
const notificationTraySection = screen.queryByTestId('notification-tray-section');
|
||||
|
||||
expect(within(notificationTraySection).queryByText('Last 24 hours')).toBeInTheDocument();
|
||||
expect(within(notificationTraySection).queryByText('Earlier')).toBeInTheDocument();
|
||||
expect(within(notificationTraySection).queryByText('Mark all as read')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully marked all notifications as read, removing the unread status.', async () => {
|
||||
axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' });
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
const markAllReadButton = screen.queryByTestId('mark-all-read');
|
||||
|
||||
expect(screen.queryByTestId('unread-notification-1')).toBeInTheDocument();
|
||||
await act(async () => { fireEvent.click(markAllReadButton); });
|
||||
|
||||
expect(screen.queryByTestId('unread-notification-1')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully load more notifications by clicking on load more notification button.', async () => {
|
||||
axiosMock.onPut(markNotificationsSeenApiUrl('discussion')).reply(200);
|
||||
await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch, store.getState);
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
|
||||
expect(screen.queryAllByTestId('notification-contents')).toHaveLength(10);
|
||||
const loadMoreButton = screen.queryByTestId('load-more-notifications');
|
||||
|
||||
axiosMock.onGet(getNotificationsListApiUrl()).reply(
|
||||
200,
|
||||
(Factory.build('notificationsList', { num_pages: 2, current_page: 2 })),
|
||||
);
|
||||
await executeThunk(fetchNotificationList({ appName: 'discussion', page: 2 }), store.dispatch, store.getState);
|
||||
|
||||
await act(async () => { fireEvent.click(loadMoreButton); });
|
||||
expect(screen.queryAllByTestId('notification-contents')).toHaveLength(12);
|
||||
});
|
||||
});
|
||||
@@ -1,91 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
act, fireEvent, render, screen, within,
|
||||
} from '@testing-library/react';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import AuthenticatedUserDropdown from '../learning-header/AuthenticatedUserDropdown';
|
||||
import { initializeStore } from '../store';
|
||||
import mockNotificationsResponse from './test-utils';
|
||||
|
||||
import './data/__factories__';
|
||||
|
||||
let store;
|
||||
|
||||
function renderComponent() {
|
||||
render(
|
||||
<ResponsiveContext.Provider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<AppProvider store={store}>
|
||||
<AppContext.Provider>
|
||||
<AuthenticatedUserDropdown />
|
||||
</AppContext.Provider>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
</ResponsiveContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('Notification Tabs test cases.', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: '123abc',
|
||||
username: 'testuser',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
Factory.resetAll();
|
||||
store = initializeStore();
|
||||
|
||||
({ store } = await mockNotificationsResponse());
|
||||
});
|
||||
|
||||
it('Notification tabs displayed with default discussion tab selected and no unseen counts.', async () => {
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
|
||||
const tabs = screen.queryAllByRole('tab');
|
||||
const selectedTab = tabs.find(tab => tab.getAttribute('aria-selected') === 'true');
|
||||
|
||||
expect(tabs.length).toEqual(5);
|
||||
expect(within(selectedTab).queryByText('discussion')).toBeInTheDocument();
|
||||
expect(within(selectedTab).queryByRole('status')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully showed unseen counts for unselected tabs.', async () => {
|
||||
renderComponent();
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
|
||||
const tabs = screen.getAllByRole('tab');
|
||||
|
||||
expect(within(tabs[0]).queryByRole('status')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Successfully selected reminder tab.', async () => {
|
||||
renderComponent();
|
||||
|
||||
const bellIcon = screen.queryByTestId('notification-bell-icon');
|
||||
await act(async () => { fireEvent.click(bellIcon); });
|
||||
const notificationTab = screen.getAllByRole('tab');
|
||||
|
||||
await act(async () => { fireEvent.click(notificationTab[0], { dataset: { rbEventKey: 'reminders' } }); });
|
||||
|
||||
const tabs = screen.queryAllByRole('tab');
|
||||
const selectedTab = tabs.find(tab => tab.getAttribute('aria-selected') === 'true');
|
||||
|
||||
expect(within(selectedTab).queryByText('reminders')).toBeInTheDocument();
|
||||
expect(within(selectedTab).queryByRole('status')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { initializeStore } from '../store';
|
||||
import executeThunk from '../test-utils';
|
||||
import { getNotificationsListApiUrl, getNotificationsCountApiUrl } from './data/api';
|
||||
import { fetchAppsNotificationCount, fetchNotificationList } from './data/thunks';
|
||||
|
||||
import './data/__factories__';
|
||||
|
||||
const notificationCountsApiUrl = getNotificationsCountApiUrl();
|
||||
const notificationsApiUrl = getNotificationsListApiUrl();
|
||||
|
||||
export default async function mockNotificationsResponse() {
|
||||
const store = initializeStore();
|
||||
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
const notifications = (Factory.buildList('notification', 8, null, { createdDate: new Date().toISOString() }).concat(
|
||||
Factory.buildList('notification', 2, null, { createdDate: '2023-06-01T00:46:11.979531Z' }),
|
||||
));
|
||||
axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount')));
|
||||
axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.build('notificationsList', {
|
||||
results: notifications,
|
||||
num_pages: 2,
|
||||
next: `${notificationsApiUrl}?app_name=discussion&page=2`,
|
||||
})));
|
||||
|
||||
await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState);
|
||||
await executeThunk(fetchNotificationList({ appName: 'discussion', page: 1 }), store.dispatch, store.getState);
|
||||
return { store, axiosMock };
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import {
|
||||
CheckCircle, HelpOutline, QuestionAnswerOutline, Verified, Report, EditOutline, ThumbUpOutline, PostOutline,
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
/**
|
||||
* Get HTTP Error status from generic error.
|
||||
* @param error Generic caught error.
|
||||
* @returns {number|null}
|
||||
*/
|
||||
export const getHttpErrorStatus = error => error?.customAttributes?.httpErrorStatus ?? error?.response?.status;
|
||||
|
||||
export const splitNotificationsByTime = (notificationList) => {
|
||||
let splittedData = [];
|
||||
if (notificationList.length > 0) {
|
||||
const currentTime = Date.now();
|
||||
const twentyFourHoursAgo = currentTime - (24 * 60 * 60 * 1000);
|
||||
|
||||
splittedData = notificationList.reduce(
|
||||
(result, notification) => {
|
||||
if (notification) {
|
||||
const objectTime = new Date(notification.createdAt).getTime();
|
||||
if (objectTime >= twentyFourHoursAgo && objectTime <= currentTime) {
|
||||
result.today.push(notification);
|
||||
} else {
|
||||
result.earlier.push(notification);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{ today: [], earlier: [] },
|
||||
);
|
||||
}
|
||||
const { today, earlier } = splittedData;
|
||||
return { today, earlier };
|
||||
};
|
||||
|
||||
export const getIconByType = (type) => {
|
||||
const iconMap = {
|
||||
post: { icon: PostOutline, class: 'text-primary-500' },
|
||||
help: { icon: HelpOutline, class: 'text-primary-500' },
|
||||
respond: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
comment: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
question: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
answer: { icon: CheckCircle, class: 'text-success' },
|
||||
endorsed: { icon: Verified, class: 'text-primary-500' },
|
||||
reported: { icon: Report, class: 'text-danger-500' },
|
||||
postLiked: { icon: ThumbUpOutline, class: 'text-primary-500' },
|
||||
commentLiked: { icon: ThumbUpOutline, class: 'text-primary-500' },
|
||||
edited: { icon: EditOutline, class: 'text-primary-500' },
|
||||
};
|
||||
return iconMap[type] || { icon: PostOutline, class: 'text-primary-500' };
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
export default function timeLocale(number, index) {
|
||||
return [
|
||||
['just now', 'right now'],
|
||||
['%ss', 'in %s seconds'],
|
||||
['1m', 'in 1 minute'],
|
||||
['%sm', 'in %s minutes'],
|
||||
['1h', 'in 1 hour'],
|
||||
['%sh', 'in %s hours'],
|
||||
['1d', 'in 1 day'],
|
||||
['%sd', 'in %s days'],
|
||||
['1w', 'in 1 week'],
|
||||
['%sw', 'in %s weeks'],
|
||||
['4w', 'in 1 month'],
|
||||
[`${number * 4}w`, 'in %s months'],
|
||||
['1y', 'in 1 year'],
|
||||
['%sy', 'in %s years'],
|
||||
][index];
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "الحساب",
|
||||
"header.menu.orderHistory.label": "سجل الطلبيات",
|
||||
"header.navigation.skipNavLink": "التخطي إلى المحتوى الرئيسي",
|
||||
"header.menu.signOut.label": "تسجيل الخروج",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "تسجيل الخروج"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Cuenta",
|
||||
"header.menu.orderHistory.label": "Historial de órdenes",
|
||||
"header.navigation.skipNavLink": "Dirígete al contenido principal.",
|
||||
"header.menu.signOut.label": "Cerrar sesión",
|
||||
"notification.title": "Notificaciones",
|
||||
"notification.today.heading": "Últimas 24 horas",
|
||||
"notification.earlier.heading": "Más temprano",
|
||||
"notification.mark.as.read": "Marcar todo como leído",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Cargar más notificaciones"
|
||||
"header.menu.signOut.label": "Cerrar sesión"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Compte",
|
||||
"header.menu.orderHistory.label": "Historique des commandes",
|
||||
"header.navigation.skipNavLink": "Passer au contenu principal",
|
||||
"header.menu.signOut.label": "Se déconnecter",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Se déconnecter"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Compte",
|
||||
"header.menu.orderHistory.label": "Historique des commandes",
|
||||
"header.navigation.skipNavLink": "Passer au contenu principal.",
|
||||
"header.menu.signOut.label": "Se déconnecter",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Dernières 24 heures",
|
||||
"notification.earlier.heading": "Plus tôt",
|
||||
"notification.mark.as.read": "tout marquer comme lu",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Charger plus de notifications"
|
||||
"header.menu.signOut.label": "Se déconnecter"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Перейти до головного змісту.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
@@ -29,11 +29,5 @@
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"notification.title": "Notifications",
|
||||
"notification.today.heading": "Last 24 hours",
|
||||
"notification.earlier.heading": "Earlier",
|
||||
"notification.mark.as.read": "Mark all as read",
|
||||
"notification.fullStop": "•",
|
||||
"notification.load.more.notifications": "Load more notifications"
|
||||
"header.menu.signOut.label": "Sign Out"
|
||||
}
|
||||
137
src/index.scss
137
src/index.scss
@@ -1,10 +1,7 @@
|
||||
$spacer: 1rem;
|
||||
$blue: #007db8;
|
||||
$white: #fff;
|
||||
@import "@edx/brand/paragon/fonts.scss";
|
||||
@import "@edx/brand/paragon/variables.scss";
|
||||
@import "@edx/paragon/scss/core/core.scss";
|
||||
@import "@edx/brand/paragon/overrides.scss";
|
||||
|
||||
@import './Menu/menu.scss';
|
||||
|
||||
.dropdown-item a {
|
||||
@@ -121,135 +118,3 @@ $white: #fff;
|
||||
border-radius: $rounded-pill;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
strong {
|
||||
color: #00262B !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.font-size-18 {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
.font-size-12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.font-size-14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.py-10px {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.pb-10px {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.line-height-24 {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.line-height-20 {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.line-height-10 {
|
||||
line-height: 10px !important;
|
||||
}
|
||||
|
||||
.icon-size-20 {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.notification-icon{
|
||||
height: 23.33px !important;
|
||||
width: 23.33px !important;
|
||||
}
|
||||
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
margin-top: 18px;
|
||||
margin-left: -21px;
|
||||
border: 2px solid #FFFFFF;
|
||||
font-size: 9px !important;
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-height: calc(100% - 68px);
|
||||
min-height: 1220px;
|
||||
filter: none;
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15), 0px 2px 8px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&.medium-screen {
|
||||
min-width: 24.313rem;
|
||||
}
|
||||
|
||||
&.large-screen {
|
||||
min-width: 34.313rem;
|
||||
}
|
||||
|
||||
.dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expandable {
|
||||
position: relative !important;
|
||||
margin-left: 4px;
|
||||
padding: 2px 5px;
|
||||
border-radius: 10rem;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
font-size: 14px;
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 12px !important;
|
||||
|
||||
div {
|
||||
min-height: 6px !important;
|
||||
min-width: 6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
.notification-item-content {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
p {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
b {
|
||||
color: #00262B;
|
||||
}
|
||||
}
|
||||
|
||||
.unread {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
@@ -6,26 +6,10 @@ import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import Notifications from '../Notifications';
|
||||
import { selectShowNotificationTray, selectNotificationStatus } from '../Notifications/data/selectors';
|
||||
import { fetchAppsNotificationCount } from '../Notifications/data/thunks';
|
||||
import { RequestStatus } from '../Notifications/data/slice';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthenticatedUserDropdown = ({ intl, username }) => {
|
||||
const showNotificationsTray = useSelector(selectShowNotificationTray());
|
||||
const notificationStatus = useSelector(selectNotificationStatus());
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (notificationStatus === RequestStatus.IDLE) {
|
||||
dispatch(fetchAppsNotificationCount());
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const dashboardMenuItem = (
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
|
||||
{intl.formatMessage(messages.dashboard)}
|
||||
@@ -35,7 +19,6 @@ const AuthenticatedUserDropdown = ({ intl, username }) => {
|
||||
return (
|
||||
<>
|
||||
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
|
||||
{showNotificationsTray && <Notifications />}
|
||||
<Dropdown className="user-dropdown ml-3">
|
||||
<Dropdown.Toggle variant="outline-primary">
|
||||
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
|
||||
|
||||
@@ -2,12 +2,11 @@ import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import AnonymousUserMenu from './AnonymousUserMenu';
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
import messages from './messages';
|
||||
import store from '../store';
|
||||
|
||||
const LinkedLogo = ({
|
||||
href,
|
||||
@@ -41,26 +40,24 @@ const LearningHeader = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<AppProvider store={store}>
|
||||
<header className="learning-header">
|
||||
<a className="sr-only sr-only-focusable" href="#main-content">{intl.formatMessage(messages.skipNavLink)}</a>
|
||||
<div className="container-xl py-2 d-flex align-items-center">
|
||||
{headerLogo}
|
||||
<div className="flex-grow-1 course-title-lockup" style={{ lineHeight: 1 }}>
|
||||
<span className="d-block small m-0">{courseOrg} {courseNumber}</span>
|
||||
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
|
||||
</div>
|
||||
{showUserDropdown && authenticatedUser && (
|
||||
<AuthenticatedUserDropdown
|
||||
username={authenticatedUser.username}
|
||||
/>
|
||||
)}
|
||||
{showUserDropdown && !authenticatedUser && (
|
||||
<AnonymousUserMenu />
|
||||
)}
|
||||
<header className="learning-header">
|
||||
<a className="sr-only sr-only-focusable" href="#main-content">{intl.formatMessage(messages.skipNavLink)}</a>
|
||||
<div className="container-xl py-2 d-flex align-items-center">
|
||||
{headerLogo}
|
||||
<div className="flex-grow-1 course-title-lockup" style={{ lineHeight: 1 }}>
|
||||
<span className="d-block small m-0">{courseOrg} {courseNumber}</span>
|
||||
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
|
||||
</div>
|
||||
</header>
|
||||
</AppProvider>
|
||||
{showUserDropdown && authenticatedUser && (
|
||||
<AuthenticatedUserDropdown
|
||||
username={authenticatedUser.username}
|
||||
/>
|
||||
)}
|
||||
{showUserDropdown && !authenticatedUser && (
|
||||
<AnonymousUserMenu />
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
16
src/store.js
16
src/store.js
@@ -1,16 +0,0 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import { notificationsReducer } from './Notifications/data';
|
||||
|
||||
export function initializeStore(preloadedState = undefined) {
|
||||
return configureStore({
|
||||
reducer: {
|
||||
notifications: notificationsReducer,
|
||||
},
|
||||
preloadedState,
|
||||
});
|
||||
}
|
||||
|
||||
const store = initializeStore();
|
||||
|
||||
export default store;
|
||||
Reference in New Issue
Block a user