Compare commits

...

10 Commits

Author SHA1 Message Date
mubbsharanwar
6162174be8 chore: upgrade frontend-component-header version
upgrade frontend-component-header version to enable show/hide username from header based on the flag

VAN-1804
2024-02-06 20:40:57 +05:00
alangsto
55e2332b00 feat: remove audit access to learning assistant chat (#1280)
* feat: remove audit access to la

* feat: remove audit access to la
2024-01-29 17:08:17 -05:00
Omar Al-Ithawi
25d746b7c8 feat: tutor-mfe compatiblilty for atlas pull (#1279)
- install atlas
 - remove `--filter` to pull all languages by default
 - use ATLAS_OPTIONS to allow custom `--filter`
 - include frontend-lib-special-exams in `atlas pull`
2024-01-29 12:40:36 -05:00
alangsto
e790e2636f feat: upgrade learning assistant to latest version (#1278) 2024-01-24 15:08:30 -05:00
alangsto
c428222125 Revert "feat: update learning-assistant version" (#1277) 2024-01-24 10:17:21 -05:00
alangsto
58365ba18e feat: update learning-assistant version (#1276) 2024-01-24 09:33:38 -05:00
Troy Sankey
ab87167052 fix: ignore failed outline API call when learner has no access
If the learner doesn't even have access to the course (e.g. because the
course starts in the future), don't worry about a 404 fetching the
course outline since we're not planning to use it anyway.

ENT-8078
2024-01-22 12:49:08 -08:00
sundasnoreen12
4928f505bd test: added test cases for new sidebar (#1267)
* test: added test cases for new sidebar

* test: added factory for verified user

* refactor: updated description for notification widget
2024-01-11 15:54:25 +05:00
alangsto
59a68afa5d feat: gate chat visibility by course end date (#1270) 2024-01-10 09:10:15 -05:00
Zachary Hancock
486faa5744 chore: update exams lib (#1268) 2024-01-08 13:56:01 -05:00
16 changed files with 685 additions and 101 deletions

View File

@@ -53,13 +53,15 @@ pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
&& atlas pull $(ATLAS_OPTIONS) \
translations/frontend-platform/src/i18n/messages:frontend-platform \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-lib-special-exams/src/i18n/messages:frontend-lib-special-exams \
translations/frontend-app-learning/src/i18n/messages:frontend-app-learning
$(intl_imports) paragon frontend-component-header frontend-component-footer frontend-app-learning
$(intl_imports) frontend-platform paragon frontend-component-header frontend-component-footer frontend-lib-special-exams frontend-app-learning
endif
# This target is used by Travis.

421
package-lock.json generated
View File

@@ -19,16 +19,17 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "12.2.1",
"@edx/frontend-component-header": "4.6.0",
"@edx/frontend-lib-learning-assistant": "^1.19.0",
"@edx/frontend-lib-special-exams": "2.26.0",
"@edx/frontend-lib-learning-assistant": "^1.20.1",
"@edx/frontend-lib-special-exams": "2.27.0",
"@edx/frontend-platform": "5.5.2",
"@edx/paragon": "20.46.0",
"@edx/openedx-atlas": "^0.6.0",
"@edx/react-unit-test-utils": "npm:@edx/react-unit-test-utils@1.7.0",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.1.4",
"@openedx/paragon": "22.0.0",
"@popperjs/core": "2.11.8",
"@reduxjs/toolkit": "1.8.1",
"classnames": "2.3.2",
@@ -1971,6 +1972,23 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs3": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.9.tgz",
"integrity": "sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==",
"dependencies": {
"core-js-pure": "^3.30.2",
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
@@ -3460,9 +3478,9 @@
}
},
"node_modules/@edx/frontend-lib-learning-assistant": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-1.19.0.tgz",
"integrity": "sha512-wBdeQladtvXmS3RA/LOXVNpmEwtG5zAYtu7E7Dh9N8sN2p12ptWCEEdPa92ViK6d5bmqo2NA9ZhnZeEmqs+oRg==",
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-1.20.1.tgz",
"integrity": "sha512-/JUSfs4CZwZj2Bg0BqlMGIC6BrDu4PufUSFnrgusL4ZD/N8DR3UyX/AQZml7S9O/ci4OY3YU7V4VDM8X00+6Lg==",
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
@@ -3539,9 +3557,9 @@
}
},
"node_modules/@edx/frontend-lib-special-exams": {
"version": "2.26.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-2.26.0.tgz",
"integrity": "sha512-havK1JHJT6cLTK6P/qw5Kl0bqMQJTcVdk0P1humHqtuUGr7gn/b9/4ADni9X7grMAXBwK8oYzMVk8nrrE3owXg==",
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-2.27.0.tgz",
"integrity": "sha512-osKuKq1+RfoLBKeDabmUf82bRP+xQu3USGodhNi4Mt0jX9FWJdZsN0CulhFW+mGFManz8dzKyXnO1AXDX1/I8g==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.11.2",
@@ -3700,6 +3718,14 @@
"@newrelic/publish-sourcemap": "^5.0.1"
}
},
"node_modules/@edx/openedx-atlas": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.6.0.tgz",
"integrity": "sha512-wZO7hA4VJ/bXjaQNNR7KXGYyTCNs1mBJd3HwQK2EmOwFZYFNX6nzSAm9S7HCfi/kb1PCRpmp3wJt+v/Eu9BEQg==",
"bin": {
"atlas": "atlas"
}
},
"node_modules/@edx/paragon": {
"version": "20.46.0",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.46.0.tgz",
@@ -5570,6 +5596,225 @@
"node": ">= 8"
}
},
"node_modules/@openedx/paragon": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.0.0.tgz",
"integrity": "sha512-2tD5SEu6kNf2Llop/FylqTI87mlG0jaeAhGiX57bABcHZ/cDParCxX/unPiui6LCfdrDhghjVBKD3U2+Qn6Wag==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.1.18",
"@popperjs/core": "^2.11.4",
"bootstrap": "^4.6.2",
"chalk": "^4.1.2",
"child_process": "^1.0.2",
"classnames": "^2.3.1",
"email-prop-type": "^3.0.0",
"file-selector": "^0.6.0",
"font-awesome": "^4.7.0",
"glob": "^8.0.3",
"inquirer": "^8.2.5",
"lodash.uniqby": "^4.7.0",
"mailto-link": "^2.0.0",
"prop-types": "^15.8.1",
"react-bootstrap": "^1.6.5",
"react-colorful": "^5.6.1",
"react-dropzone": "^14.2.1",
"react-focus-on": "^3.5.4",
"react-imask": "^7.1.3",
"react-loading-skeleton": "^3.1.0",
"react-popper": "^2.2.5",
"react-proptype-conditional-require": "^1.0.4",
"react-responsive": "^8.2.0",
"react-table": "^7.7.0",
"react-transition-group": "^4.4.2",
"tabbable": "^5.3.3",
"uncontrollable": "^7.2.1",
"uuid": "^9.0.0"
},
"bin": {
"paragon": "bin/paragon-scripts.js"
},
"peerDependencies": {
"react": "^16.8.6 || ^17.0.0",
"react-dom": "^16.8.6 || ^17.0.0",
"react-intl": "^5.25.1 || ^6.4.0"
}
},
"node_modules/@openedx/paragon/node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz",
"integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@openedx/paragon/node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz",
"integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@openedx/paragon/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@openedx/paragon/node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dependencies": {
"restore-cursor": "^3.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@openedx/paragon/node_modules/cli-width": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
"engines": {
"node": ">= 10"
}
},
"node_modules/@openedx/paragon/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/@openedx/paragon/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@openedx/paragon/node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dependencies": {
"escape-string-regexp": "^1.0.5"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@openedx/paragon/node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@openedx/paragon/node_modules/inquirer": {
"version": "8.2.6",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz",
"integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==",
"dependencies": {
"ansi-escapes": "^4.2.1",
"chalk": "^4.1.1",
"cli-cursor": "^3.1.0",
"cli-width": "^3.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.21",
"mute-stream": "0.0.8",
"ora": "^5.4.1",
"run-async": "^2.4.0",
"rxjs": "^7.5.5",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0",
"through": "^2.3.6",
"wrap-ansi": "^6.0.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@openedx/paragon/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/@openedx/paragon/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@openedx/paragon/node_modules/mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"node_modules/@openedx/paragon/node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@openedx/paragon/node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@openedx/paragon/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@optimizely/js-sdk-logging": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz",
@@ -8341,8 +8586,7 @@
"node_modules/chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"node_modules/check-types": {
"version": "7.3.0",
@@ -8465,6 +8709,11 @@
"node": ">= 6"
}
},
"node_modules/child_process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
"integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g=="
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -8684,6 +8933,17 @@
"node": ">=4"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-width": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
@@ -8750,6 +9010,14 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -9633,6 +9901,17 @@
"node": ">= 10"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"dependencies": {
"clone": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -11234,7 +11513,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"dependencies": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
@@ -12839,6 +13117,17 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/imask": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/imask/-/imask-7.3.0.tgz",
"integrity": "sha512-TG+/rfb62JaQDM2KVrzEHMb4lv2srbsby7vHndXhqgQFB1MgPIXl60VQUfly/Xv5iWfA9ytB+rfQ+skUgINw7A==",
"dependencies": {
"@babel/runtime-corejs3": "^7.23.6"
},
"engines": {
"npm": ">=4.0.0"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -13480,6 +13769,14 @@
"node": ">=0.10.0"
}
},
"node_modules/is-interactive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-invalid-path": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz",
@@ -13722,6 +14019,17 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-valid-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz",
@@ -17220,6 +17528,21 @@
"resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
"integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww=="
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dependencies": {
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/logform": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz",
@@ -18739,11 +19062,55 @@
"node": ">= 0.8.0"
}
},
"node_modules/ora": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
"integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
"dependencies": {
"bl": "^4.1.0",
"chalk": "^4.1.0",
"cli-cursor": "^3.1.0",
"cli-spinners": "^2.5.0",
"is-interactive": "^1.0.0",
"is-unicode-supported": "^0.1.0",
"log-symbols": "^4.1.0",
"strip-ansi": "^6.0.0",
"wcwidth": "^1.0.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora/node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dependencies": {
"restore-cursor": "^3.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ora/node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -20504,6 +20871,21 @@
"react": ">=16.3.0"
}
},
"node_modules/react-imask": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.3.0.tgz",
"integrity": "sha512-AHoQUeXil6PfqDzJHN08hO2liWxNDRJosNUa2XSqliFY2tXGL/3Elm0msupDNAyNPItAnyF9G5FGFoCfiCn+AQ==",
"dependencies": {
"imask": "^7.3.0",
"prop-types": "^15.8.1"
},
"engines": {
"npm": ">=4.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/react-intl": {
"version": "5.25.1",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz",
@@ -21408,7 +21790,6 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
"integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -23604,8 +23985,7 @@
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
"node_modules/thunky": {
"version": "1.1.0",
@@ -23636,7 +24016,6 @@
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"dependencies": {
"os-tmpdir": "~1.0.2"
},
@@ -24560,6 +24939,14 @@
"minimalistic-assert": "^1.0.0"
}
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"dependencies": {
"defaults": "^1.0.3"
}
},
"node_modules/webidl-conversions": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",

View File

@@ -32,10 +32,11 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "12.2.1",
"@edx/frontend-component-header": "4.6.0",
"@edx/frontend-lib-special-exams": "2.26.0",
"@edx/frontend-lib-learning-assistant": "^1.19.0",
"@edx/frontend-lib-special-exams": "2.27.0",
"@edx/frontend-lib-learning-assistant": "^1.20.1",
"@edx/frontend-platform": "5.5.2",
"@edx/paragon": "20.46.0",
"@edx/openedx-atlas": "^0.6.0",
"@openedx/paragon": "22.0.0",
"@edx/react-unit-test-utils": "npm:@edx/react-unit-test-utils@1.7.0",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-brands-svg-icons": "5.15.4",

View File

@@ -55,6 +55,29 @@ describe('Data layer integration tests', () => {
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('should result in fetch failed if course metadata call errored', async () => {
const datesTabData = Factory.build('datesTabData');
const datesUrl = `${datesBaseUrl}/${courseId}`;
axiosMock.onGet(courseMetadataUrl).networkError();
axiosMock.onGet(datesUrl).reply(200, datesTabData);
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('should result in fetch failed if course metadata call errored', async () => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(`${datesBaseUrl}/${courseId}`).networkError();
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('Should fetch, normalize, and save metadata', async () => {
const datesTabData = Factory.build('datesTabData');
@@ -78,18 +101,14 @@ describe('Data layer integration tests', () => {
});
it.each([401, 403, 404])(
'should result in fetch denied for expected errors and failed for all others',
'should result in fetch denied if course access is denied, regardless of dates API status',
async (errorStatus) => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata);
axiosMock.onGet(`${datesBaseUrl}/${courseId}`).reply(errorStatus, {});
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
let expectedState = 'failed';
if (errorStatus === 401 || errorStatus === 403) {
expectedState = 'denied';
}
expect(store.getState().courseHome.courseStatus).toEqual(expectedState);
expect(store.getState().courseHome.courseStatus).toEqual('denied');
},
);
});
@@ -129,18 +148,14 @@ describe('Data layer integration tests', () => {
});
it.each([401, 403, 404])(
'should result in fetch denied for expected errors and failed for all others',
'should result in fetch denied if course access is denied, regardless of outline API status',
async (errorStatus) => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata);
axiosMock.onGet(outlineUrl).reply(errorStatus, {});
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
let expectedState = 'failed';
if (errorStatus === 403) {
expectedState = 'denied';
}
expect(store.getState().courseHome.courseStatus).toEqual(expectedState);
expect(store.getState().courseHome.courseStatus).toEqual('denied');
},
);
});

View File

@@ -38,28 +38,41 @@ export function fetchTab(courseId, tab, getTabData, targetUserId) {
return async (dispatch) => {
dispatch(fetchTabRequest({ courseId }));
try {
const courseHomeCourseMetadata = await getCourseHomeCourseMetadata(courseId, 'outline');
dispatch(addModel({
modelType: 'courseHomeMeta',
model: {
id: courseId,
...courseHomeCourseMetadata,
},
}));
const tabDataResult = getTabData && await getTabData(courseId, targetUserId);
if (tabDataResult) {
const promisesToFulfill = [getCourseHomeCourseMetadata(courseId, 'outline')];
if (getTabData) {
promisesToFulfill.push(getTabData(courseId, targetUserId));
}
const [
courseHomeCourseMetadataResult,
tabDataResult,
] = await Promise.allSettled(promisesToFulfill);
if (courseHomeCourseMetadataResult.status === 'fulfilled') {
dispatch(addModel({
modelType: 'courseHomeMeta',
model: {
id: courseId,
...courseHomeCourseMetadataResult.value,
},
}));
}
if (tabDataResult?.status === 'fulfilled') {
dispatch(addModel({
modelType: tab,
model: {
id: courseId,
...tabDataResult,
...tabDataResult.value,
},
}));
}
// Disable the access-denied path for now - it caused a regression
if (!courseHomeCourseMetadata.courseAccess.hasAccess) {
if (courseHomeCourseMetadataResult.status === 'rejected') {
throw courseHomeCourseMetadataResult.reason;
} else if (!courseHomeCourseMetadataResult.value.courseAccess.hasAccess) {
// If the learner does not have access to the course, short cut to dispatch to a denied response regardless of
// the tabDataResult.
dispatch(fetchTabDenied({ courseId }));
} else if (tabDataResult || !getTabData) {
} else if (tabDataResult?.status === 'rejected') {
throw tabDataResult.reason;
} else {
dispatch(fetchTabSuccess({
courseId,
targetUserId,

View File

@@ -91,6 +91,7 @@ const Course = ({
courseId={courseId}
contentToolsEnabled={course.showCalculator || course.notes.enabled}
unitId={unitId}
endDate={course.end ? course.end : ''}
/>
{enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers /> }
</>

View File

@@ -1,18 +1,16 @@
import React from 'react';
import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MockAdapter from 'axios-mock-adapter';
import { breakpoints } from '@edx/paragon';
import {
act, fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
} from '../../setupTest';
import { buildTopicsFromUnits } from '../data/__factories__/discussionTopics.factory';
import { handleNextSectionCelebration } from './celebration';
import * as celebrationUtils from './celebration/utils';
import { handleNextSectionCelebration } from './celebration';
import Course from './Course';
import { executeThunk } from '../../utils';
import * as thunks from '../data/thunks';
import setupDiscussionSidebar from './test-utils';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-lib-special-exams/dist/data/thunks.js', () => ({
@@ -51,26 +49,6 @@ describe('Course', () => {
setItemSpy.mockRestore();
});
const setupDiscussionSidebar = async () => {
const courseHomeMetadata = Factory.build('courseHomeMetadata', { verified_mode: null });
const testStore = await initializeTestStore({ provider: 'openedx', courseHomeMetadata });
const state = testStore.getState();
const { courseware: { courseId } } = state;
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/${courseId}`).reply(200, { provider: 'openedx' });
const topicsResponse = buildTopicsFromUnits(state.models.units);
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v2/course_topics/${courseId}`)
.reply(200, topicsResponse);
await executeThunk(thunks.getCourseDiscussionTopics(courseId), testStore.dispatch);
const [firstUnitId] = Object.keys(state.models.units);
mockData.unitId = firstUnitId;
const [firstSequenceId] = Object.keys(state.models.sequences);
mockData.sequenceId = firstSequenceId;
await render(<Course {...mockData} />, { store: testStore, wrapWithRouter: true });
};
it('loads learning sequence', async () => {
render(<Course {...mockData} />, { wrapWithRouter: true });
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
@@ -183,7 +161,7 @@ describe('Course', () => {
});
it('handles click to open/close notification tray', async () => {
render(<Course {...mockData} />, { wrapWithRouter: true });
await setupDiscussionSidebar();
const notificationShowButton = await screen.findByRole('button', { name: /Show notification tray/i });
expect(screen.queryByRole('region', { name: /notification tray/i })).toHaveClass('d-none');
fireEvent.click(notificationShowButton);

View File

@@ -11,6 +11,7 @@ const Chat = ({
courseId,
contentToolsEnabled,
unitId,
endDate,
}) => {
const VERIFIED_MODES = [
'professional',
@@ -23,22 +24,23 @@ const Chat = ({
'paid-bootcamp',
];
const AUDIT_MODES = [
'audit',
'honor',
'unpaid-executive-education',
'unpaid-bootcamp',
];
const isEnrolled = (
const hasVerifiedEnrollment = (
enrollmentMode !== null
&& enrollmentMode !== undefined
&& [...VERIFIED_MODES, ...AUDIT_MODES].some(mode => mode === enrollmentMode)
&& [...VERIFIED_MODES].some(mode => mode === enrollmentMode)
);
const endDatePassed = () => {
const date = new Date();
const utcDate = date.toISOString();
return endDate ? utcDate > endDate : false; // evaluate if end date has passed only if course has end date
};
const shouldDisplayChat = (
enabled
&& (isEnrolled || isStaff) // display only to enrolled or staff
&& (hasVerifiedEnrollment || isStaff) // display only to verified learners or staff
&& !endDatePassed()
);
return (
@@ -59,6 +61,7 @@ Chat.propTypes = {
courseId: PropTypes.string.isRequired,
contentToolsEnabled: PropTypes.bool.isRequired,
unitId: PropTypes.string.isRequired,
endDate: PropTypes.string.isRequired,
};
Chat.defaultProps = {

View File

@@ -16,9 +16,10 @@ let enabledTestCases = [];
let disabledTestCases = [];
const enabledModes = [
'professional', 'verified', 'no-id-professional', 'credit', 'masters', 'executive-education',
'paid-executive-education', 'paid-bootcamp', 'audit', 'honor', 'unpaid-executive-education', 'unpaid-bootcamp',
'paid-executive-education', 'paid-bootcamp',
];
const disabledModes = [null, undefined, 'xyz'];
const disabledModes = [null, undefined, 'xyz', 'audit', 'honor', 'unpaid-executive-education', 'unpaid-bootcamp'];
const currentTime = new Date();
describe('Chat', () => {
// Generate test cases.
@@ -44,6 +45,7 @@ describe('Chat', () => {
enabled
courseId={courseId}
contentToolsEnabled={false}
endDate={new Date(currentTime.getTime() + 10 * 60000).toISOString()}
/>
</BrowserRouter>,
{ store },
@@ -77,6 +79,7 @@ describe('Chat', () => {
enabled
courseId={courseId}
contentToolsEnabled={false}
endDate={new Date(currentTime.getTime() + 10 * 60000).toISOString()}
/>
</BrowserRouter>,
{ store },
@@ -138,6 +141,7 @@ describe('Chat', () => {
enabled={test.enabled}
courseId={courseId}
contentToolsEnabled={false}
endDate={new Date(currentTime.getTime() + 10 * 60000).toISOString()}
/>
</BrowserRouter>,
{ store },
@@ -152,4 +156,54 @@ describe('Chat', () => {
},
);
});
it('if course end date has passed, component should not be visible', async () => {
const store = configureStore({
reducer: {
learningAssistant: learningAssistantReducer,
},
});
render(
<BrowserRouter>
<Chat
enrollmentMode="verified"
isStaff
enabled
courseId={courseId}
contentToolsEnabled={false}
endDate={new Date(currentTime.getTime() - 10 * 60000).toISOString()}
/>
</BrowserRouter>,
{ store },
);
const chat = screen.queryByTestId('toggle-button');
expect(chat).not.toBeInTheDocument();
});
it('if course has no end date, component should be visible', async () => {
const store = configureStore({
reducer: {
learningAssistant: learningAssistantReducer,
},
});
render(
<BrowserRouter>
<Chat
enrollmentMode="verified"
isStaff
enabled
courseId={courseId}
contentToolsEnabled={false}
endDate={null}
/>
</BrowserRouter>,
{ store },
);
const chat = screen.queryByTestId('toggle-button');
expect(chat).toBeInTheDocument();
});
});

View File

@@ -91,7 +91,7 @@ const SidebarBase = ({
};
SidebarBase.propTypes = {
title: PropTypes.string.isRequired,
title: PropTypes.string,
ariaLabel: PropTypes.string.isRequired,
sidebarId: PropTypes.string.isRequired,
className: PropTypes.string,
@@ -103,6 +103,7 @@ SidebarBase.propTypes = {
};
SidebarBase.defaultProps = {
title: '',
width: '50rem',
allowFullHeight: false,
showTitleBar: true,

View File

@@ -12,6 +12,7 @@ import { executeThunk } from '../../../../../../utils';
import { buildTopicsFromUnits } from '../../../../../data/__factories__/discussionTopics.factory';
import { getCourseDiscussionTopics } from '../../../../../data/thunks';
import SidebarContext from '../../../SidebarContext';
import DiscussionsNotificationsSidebar from '../DiscussionsNotificationsSidebar';
import DiscussionsWidget from './DiscussionsWidget';
initializeMockApp();
@@ -51,24 +52,29 @@ describe('DiscussionsWidget', () => {
await executeThunk(getCourseDiscussionTopics(courseId), store.dispatch);
});
function renderWithProvider(testData = {}) {
function renderWithProvider(Component, testData = {}) {
const { container } = render(
<SidebarContext.Provider value={{ ...mockData, ...testData }}>
<DiscussionsWidget />
<Component />
</SidebarContext.Provider>,
);
return container;
}
it('should show up if unit discussions associated with it', async () => {
renderWithProvider();
renderWithProvider(DiscussionsWidget);
expect(screen.queryByTitle('Discussions')).toBeInTheDocument();
expect(screen.queryByTitle('Discussions'))
.toHaveAttribute('src', `http://localhost:2002/${courseId}/category/${unitId}?inContextSidebar`);
});
it('should show nothing if unit has no discussions associated with it', async () => {
renderWithProvider({ isDiscussionbarAvailable: false });
renderWithProvider(DiscussionsWidget, { isDiscussionbarAvailable: false });
expect(screen.queryByTitle('Discussions')).not.toBeInTheDocument();
});
it('should display the Back to course button on small screens.', async () => {
renderWithProvider(DiscussionsNotificationsSidebar, { shouldDisplayFullScreen: true });
expect(screen.queryByText('Back to course')).toBeInTheDocument();
});
});

View File

@@ -38,7 +38,7 @@ const NotificationsWidget = () => {
if (hideNotificationbar || !isNotificationbarAvailable) { return null; }
return (
<div className="border border-light-400 rounded-sm">
<div className="border border-light-400 rounded-sm" data-testid="notification-widget">
<UpgradeNotification
offer={offer}
verifiedMode={verifiedMode}

View File

@@ -4,16 +4,19 @@ import React from 'react';
import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { mergeConfig, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { breakpoints } from '@edx/paragon';
import { initializeMockApp, render, screen } from '../../../../../../setupTest';
import {
initializeMockApp, render, screen, within, act, fireEvent, waitFor,
} from '../../../../../../setupTest';
import initializeStore from '../../../../../../store';
import { appendBrowserTimezoneToUrl, executeThunk } from '../../../../../../utils';
import { fetchCourse } from '../../../../../data';
import SidebarContext from '../../../SidebarContext';
import NotificationsWidget from './NotificationsWidget';
import setupDiscussionSidebar from '../../../../test-utils';
initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
@@ -22,7 +25,6 @@ describe('NotificationsWidget', () => {
let axiosMock;
let store;
const ID = 'NEWSIDEBAR';
const defaultMetadata = Factory.build('courseMetadata');
const courseId = defaultMetadata.id;
let courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/course/${defaultMetadata.id}`;
@@ -47,6 +49,35 @@ describe('NotificationsWidget', () => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(courseMetadataUrl).reply(200, defaultMetadata);
axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata);
mergeConfig({ ENABLE_NEW_SIDEBAR: 'true' }, 'Custom app config');
});
it('successfully Open/Hide sidebar tray.', async () => {
const userVerifiedMode = Factory.build('verifiedMode');
await setupDiscussionSidebar(userVerifiedMode);
const sidebarButton = await screen.getByRole('button', { name: /Show sidebar tray/i });
await act(async () => {
fireEvent.click(sidebarButton);
});
await waitFor(async () => {
expect(screen.queryByTestId('sidebar-DISCUSSIONS_NOTIFICATIONS')).toBeInTheDocument();
expect(screen.queryByTestId('notification-widget')).toBeInTheDocument();
expect(screen.queryByTitle('Discussions')).toBeInTheDocument();
});
await act(async () => {
fireEvent.click(sidebarButton);
});
await waitFor(async () => {
expect(screen.queryByTestId('sidebar-DISCUSSIONS_NOTIFICATIONS')).not.toBeInTheDocument();
expect(screen.queryByTestId('notification-widget')).not.toBeInTheDocument();
expect(screen.queryByTitle('Discussions')).not.toBeInTheDocument();
});
});
it('renders upgrade card', async () => {
@@ -90,6 +121,41 @@ describe('NotificationsWidget', () => {
.toBeInTheDocument();
});
it.each([
{
description: 'close the notification widget.',
enabledInContext: true,
testId:
'notification-widget',
},
{
description: 'close the sidebar when the notification widget is closed, and the discussion widget is unavailable.',
enabledInContext: false,
testId: 'sidebar-DISCUSSIONS_NOTIFICATIONS',
},
])('successfully %s', async ({ enabledInContext, testId }) => {
const userVerifiedMode = Factory.build('verifiedMode');
await setupDiscussionSidebar(userVerifiedMode, enabledInContext);
const sidebarButton = screen.getByRole('button', { name: /Show sidebar tray/i });
await act(async () => {
fireEvent.click(sidebarButton);
});
const notificationWidget = await waitFor(() => screen.getByTestId('notification-widget'));
const closeNotificationButton = within(notificationWidget).getByRole('button', { name: /Close/i });
await act(async () => {
fireEvent.click(closeNotificationButton);
});
await waitFor(() => {
expect(screen.queryByTestId(testId)).not.toBeInTheDocument();
});
});
it('marks notification as seen 3 seconds later', async () => {
jest.useFakeTimers();
const onNotificationSeen = jest.fn();

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MockAdapter from 'axios-mock-adapter';
import { breakpoints } from '@edx/paragon';
import { initializeTestStore, render } from '../../setupTest';
import { buildTopicsFromUnits } from '../data/__factories__/discussionTopics.factory';
import { executeThunk } from '../../utils';
import * as thunks from '../data/thunks';
import Course from './Course';
const mockData = {
nextSequenceHandler: () => {},
previousSequenceHandler: () => {},
unitNavigationHandler: () => {},
};
const setupDiscussionSidebar = async (verifiedMode = null, enabledInContext = true) => {
const store = await initializeTestStore();
const { courseware, models } = store.getState();
const { courseId, sequenceId } = courseware;
Object.assign(mockData, {
courseId,
sequenceId,
unitId: Object.values(models.units)[0].id,
});
global.innerWidth = breakpoints.extraLarge.minWidth;
const courseHomeMetadata = Factory.build('courseHomeMetadata', { verified_mode: verifiedMode });
const testStore = await initializeTestStore({ provider: 'openedx', courseHomeMetadata });
const state = testStore.getState();
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/${courseId}`).reply(200, { provider: 'openedx' });
const topicsResponse = buildTopicsFromUnits(state.models.units, enabledInContext);
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v2/course_topics/${courseId}`)
.reply(200, topicsResponse);
await executeThunk(thunks.getCourseDiscussionTopics(courseId), testStore.dispatch);
const [firstUnitId] = Object.keys(state.models.units);
mockData.unitId = firstUnitId;
const [firstSequenceId] = Object.keys(state.models.sequences);
mockData.sequenceId = firstSequenceId;
const wrapper = await render(<Course {...mockData} />, { store: testStore, wrapWithRouter: true });
return wrapper;
};
export default setupDiscussionSidebar;

View File

@@ -1,6 +1,13 @@
/* eslint-disable import/prefer-default-export */
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
Factory.define('verifiedMode')
.attr('currency', 'USD')
.attr('currencySymbol', '$')
.attr('price', '$149')
.attr('sku', '8CF08E5')
.attr('upgradeUrl', 'http://localhost:18130/basket/add/?sku=8CF08E5');
Factory.define('discussionTopic')
.option('topicPrefix', null, '')
.option('courseId', null, 'course-v1:edX+DemoX+Demo_Course')
@@ -11,13 +18,14 @@ Factory.define('discussionTopic')
['id', 'courseId'],
(idx, id, courseId) => `block-v1:${courseId.replace('course-v1:', '')}+type@vertical+block@${id}`,
)
.attr('enabled_in_context', null, true)
.attr('enabled_in_context', ['enabled_in_context'], (enabledInContext) => Boolean(enabledInContext))
.attr('thread_counts', [], {
discussion: 0,
question: 0,
});
// Given a pre-build units state, build topics from it.
export function buildTopicsFromUnits(units) {
return Object.values(units).map(unit => Factory.build('discussionTopic', { usage_key: unit.id }));
export function buildTopicsFromUnits(units, enabledInContext = true) {
return Object.values(units).map(unit => Factory.build('discussionTopic', { usage_key: unit.id, enabled_in_context: enabledInContext }));
}

View File

@@ -4,7 +4,7 @@
@import "~@edx/brand/paragon/overrides";
@import "~@edx/frontend-component-footer/dist/footer";
@import "~@edx/frontend-component-header/dist/index";
@import "@edx/frontend-component-header/dist/index";
#root {