From dacc6333ae8904a554db9cdf6e865c4175733b09 Mon Sep 17 00:00:00 2001 From: Leangseu Kim Date: Wed, 15 Sep 2021 15:17:40 -0400 Subject: [PATCH] commit before updating test updated some test --- .env.development | 2 +- .env.test | 38 ++ package-lock.json | 420 ++++++++++++++++++ package.json | 2 + src/App.jsx | 24 +- src/App.scss | 57 +++ src/course-header/AnonymousUserMenu.jsx | 34 ++ .../AnonymousUserMenu.messages.js | 31 ++ .../AuthenticatedUserDropdown.jsx | 53 +++ .../AuthenticatedUserDropdown.messages.js | 53 +++ src/course-header/Header.jsx | 81 ++++ src/course-header/Header.test.jsx | 29 ++ src/course-header/index.js | 1 + src/course-header/messages.js | 36 ++ src/index.jsx | 30 +- src/index.test.jsx | 2 +- src/setupTest.js | 89 +++- 17 files changed, 960 insertions(+), 22 deletions(-) create mode 100644 .env.test create mode 100644 src/course-header/AnonymousUserMenu.jsx create mode 100644 src/course-header/AnonymousUserMenu.messages.js create mode 100644 src/course-header/AuthenticatedUserDropdown.jsx create mode 100644 src/course-header/AuthenticatedUserDropdown.messages.js create mode 100644 src/course-header/Header.jsx create mode 100644 src/course-header/Header.test.jsx create mode 100644 src/course-header/index.js create mode 100644 src/course-header/messages.js diff --git a/.env.development b/.env.development index 7973d4b..d6c5a55 100644 --- a/.env.development +++ b/.env.development @@ -3,7 +3,7 @@ PORT=8181 BASE_URL='localhost:8181' LMS_BASE_URL='http://localhost:18000' LOGIN_URL='http://localhost:18000/login' -LOGOUT_URL='http://localhost:18000/login' +LOGOUT_URL='http://localhost:18000/logout' LOGO_URL=https://edx-cdn.org/v3/default/logo.svg LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..9222c9c --- /dev/null +++ b/.env.test @@ -0,0 +1,38 @@ +NODE_ENV='test' +PORT=8181 +BASE_URL='localhost:8181' +LMS_BASE_URL='http://localhost:18000' +LOGIN_URL='http://localhost:18000/login' +LOGOUT_URL='http://localhost:18000/logout' +LOGO_URL=https://edx-cdn.org/v3/default/logo.svg +LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg +LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg +LOGO_POWERED_BY_OPEN_EDX_URL_SVG=https://edx-cdn.org/v3/stage/open-edx-tag.svg +FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico +CSRF_TOKEN_API_PATH='/csrf/api/v1/token' +REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh' +ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' +USER_INFO_COOKIE_NAME='edx-user-info' +SITE_NAME=localhost +DATA_API_BASE_URL='http://localhost:8000' +// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe +LMS_CLIENT_ID='login-service-client-id' +SEGMENT_KEY='' +FEATURE_FLAGS={} +MARKETING_SITE_BASE_URL='http://localhost:18000' +SUPPORT_URL='http://localhost:18000/support' +CONTACT_URL='http://localhost:18000/contact' +OPEN_SOURCE_URL='http://localhost:18000/openedx' +TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service' +PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy' +FACEBOOK_URL='https://www.facebook.com' +TWITTER_URL='https://twitter.com' +YOU_TUBE_URL='https://www.youtube.com' +LINKED_IN_URL='https://www.linkedin.com' +REDDIT_URL='https://www.reddit.com' +APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/' +GOOGLE_PLAY_URL='https://play.google.com/store' +ENTERPRISE_MARKETING_URL='http://example.com' +ENTERPRISE_MARKETING_UTM_SOURCE='example.com' +ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral' +ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer' diff --git a/package-lock.json b/package-lock.json index 519765a..5f0cade 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3630,6 +3630,225 @@ "defer-to-connect": "^1.0.1" } }, + "@testing-library/dom": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.5.0.tgz", + "integrity": "sha512-O0fmHFaPlqaYCpa/cBL0cvroMridb9vZsMLacgIqrlxj+fd+bGF8UfAgwsLCHRF84KLBafWlm9CuOvxeNTlodw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^4.2.2", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.6", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "@jest/types": { + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", + "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz", + "integrity": "sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==", + "dev": true, + "requires": { + "@jest/types": "^27.1.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz", + "integrity": "sha512-dfB7HVIgTNCxH22M1+KU6viG5of2ldoA5ly8Ar8xkezKHKXjRvznCdbMbqjYGgO2xjRbwnR+rR8MLUIqF3kKbQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^4.2.2", + "chalk": "^3.0.0", + "css": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.0.tgz", + "integrity": "sha512-Ge3Ht3qXE82Yv9lyPpQ7ZWgzo/HgOcHu569Y4ZGWcZME38iOFiOg87qnu6hTEa8jTJVL7zYovnvD3GE2nsNIoQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3642,6 +3861,12 @@ "integrity": "sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==", "dev": true }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "@types/babel__core": { "version": "7.1.16", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", @@ -3803,6 +4028,145 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", + "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "dev": true, + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", + "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.0.tgz", + "integrity": "sha512-QSO9WC6btFYWtRJ3Hac0sRrkspf7B01mGrrQEiCW6TobtViJ9RWL0EmOs/WnBsZDsI/Y2IoSHZA2x6offu0sYw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.0" + } + }, + "jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true + }, + "pretty-format": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz", + "integrity": "sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==", + "dev": true, + "requires": { + "@jest/types": "^27.1.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -3926,6 +4290,15 @@ "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", "dev": true }, + "@types/testing-library__jest-dom": { + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.1.tgz", + "integrity": "sha512-Gk9vaXfbzc5zCXI9eYE9BI5BNHEp4D3FWjgqBE/ePGYElLAP+KvxBcsdkwfIVvezs605oiyd/VrpiHe3Oeg+Aw==", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, "@types/uglify-js": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", @@ -7001,6 +7374,35 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + } + } + }, "css-color-names": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", @@ -7080,6 +7482,12 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==" }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -7758,6 +8166,12 @@ "esutils": "^2.0.2" } }, + "dom-accessibility-api": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.7.tgz", + "integrity": "sha512-ml3lJIq9YjUfM9TUnEPvEYWFSwivwIGBPKpewX7tii7fwCazA8yCioGdqQcNsItPpfFvSJ3VIdMQPj60LJhcQA==", + "dev": true + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -16316,6 +16730,12 @@ "yallist": "^4.0.0" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", + "dev": true + }, "mailto-link": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-1.0.0.tgz", diff --git a/package.json b/package.json index 6805f1c..d248f4b 100755 --- a/package.json +++ b/package.json @@ -67,6 +67,8 @@ }, "devDependencies": { "@edx/frontend-build": "8.0.4", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^12.1.0", "axios": "0.21.1", "axios-mock-adapter": "^1.20.0", "codecov": "^3.8.3", diff --git a/src/App.jsx b/src/App.jsx index 451e022..736bc35 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,31 +1,37 @@ import React from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { IntlProvider } from 'react-intl'; import Footer from '@edx/frontend-component-footer'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { routePath } from 'data/constants/app'; import store from 'data/store'; import ListView from 'containers/ListView'; -//import messages from './i18n'; +import messages from './i18n'; import './App.scss'; +import { AppProvider, ErrorPage, PageRoute } from '@edx/frontend-platform/react'; +import Header from './course-header/Header'; + + const App = () => ( - // - + +
+
-
-
-
- //
+ + ); export default App; diff --git a/src/App.scss b/src/App.scss index eaa95a0..7dfe894 100755 --- a/src/App.scss +++ b/src/App.scss @@ -11,6 +11,63 @@ $input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4. @import "~@edx/frontend-component-footer/dist/_footer"; +#root { + display: flex; + flex-direction: column; + min-height: 100vh; + + main { + flex-grow: 1; + } + + header { + flex: 0 0 auto; + + .logo { + display: block; + box-sizing: content-box; + position: relative; + top: 0.1em; + height: 1.75rem; + margin-right: 1rem; + img { + display: block; + height: 100%; + } + } + } + + footer { + flex: 0; + } +} + +.course-header { + min-width: 0; + border-bottom: 1px solid black; + + .course-title-lockup { + min-width: 0; + + span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-bottom: 0.1rem; + } + } + + .user-dropdown { + .btn { + height: 3rem; + @media (max-width: -1 + map-get($grid-breakpoints, "sm")) { + padding: 0 0.5rem; + } + } + } +} + + #paragon-portal-root { .pgn__modal-layer { .pgn__modal-close-container { diff --git a/src/course-header/AnonymousUserMenu.jsx b/src/course-header/AnonymousUserMenu.jsx new file mode 100644 index 0000000..72156cd --- /dev/null +++ b/src/course-header/AnonymousUserMenu.jsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { getConfig } from '@edx/frontend-platform'; +import { getLoginRedirectUrl } from '@edx/frontend-platform/auth'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { Button } from '@edx/paragon'; + +import message from './AnonymousUserMenu.messages'; + +function AnonymousUserMenu({ intl }) { + return ( +
+ + +
+ ); +} + +AnonymousUserMenu.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(AnonymousUserMenu); diff --git a/src/course-header/AnonymousUserMenu.messages.js b/src/course-header/AnonymousUserMenu.messages.js new file mode 100644 index 0000000..ad435a0 --- /dev/null +++ b/src/course-header/AnonymousUserMenu.messages.js @@ -0,0 +1,31 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + close: { + id: 'general.altText.close', + defaultMessage: 'Close', + description: 'Text used as an aria-label to describe closing or dismissing a component', + }, + registerLowercase: { + id: 'learning.logistration.register', // ID left for historical purposes + defaultMessage: 'register', + description: 'Text in a link, prompting the user to create an account. Used in "learning.logistration.alert"', + }, + registerSentenceCase: { + id: 'general.register.sentenceCase', + defaultMessage: 'Register', + description: 'Text in a button, prompting the user to register.', + }, + signInLowercase: { + id: 'learning.logistration.login', // ID left for historical purposes + defaultMessage: 'sign in', + description: 'Text in a link, prompting the user to log in. Used in "learning.logistration.alert"', + }, + signInSentenceCase: { + id: 'general.signIn.sentenceCase', + defaultMessage: 'Sign in', + description: 'Text in a button, prompting the user to log in.', + }, +}); + +export default messages; diff --git a/src/course-header/AuthenticatedUserDropdown.jsx b/src/course-header/AuthenticatedUserDropdown.jsx new file mode 100644 index 0000000..c9b1704 --- /dev/null +++ b/src/course-header/AuthenticatedUserDropdown.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +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 messages from './messages'; + +function AuthenticatedUserDropdown({ intl, username }) { + let dashboardMenuItem = ( + + {intl.formatMessage(messages.dashboard)} + + ); + return ( + <> + {intl.formatMessage(messages.help)} + + + + + {username} + + + + {dashboardMenuItem} + + {intl.formatMessage(messages.profile)} + + + {intl.formatMessage(messages.account)} + + + {intl.formatMessage(messages.signOut)} + + + + + ); +} + +AuthenticatedUserDropdown.propTypes = { + intl: intlShape.isRequired, + username: PropTypes.string.isRequired, +}; + +AuthenticatedUserDropdown.defaultProps = {}; + +export default injectIntl(AuthenticatedUserDropdown); diff --git a/src/course-header/AuthenticatedUserDropdown.messages.js b/src/course-header/AuthenticatedUserDropdown.messages.js new file mode 100644 index 0000000..c9b1704 --- /dev/null +++ b/src/course-header/AuthenticatedUserDropdown.messages.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +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 messages from './messages'; + +function AuthenticatedUserDropdown({ intl, username }) { + let dashboardMenuItem = ( + + {intl.formatMessage(messages.dashboard)} + + ); + return ( + <> + {intl.formatMessage(messages.help)} + + + + + {username} + + + + {dashboardMenuItem} + + {intl.formatMessage(messages.profile)} + + + {intl.formatMessage(messages.account)} + + + {intl.formatMessage(messages.signOut)} + + + + + ); +} + +AuthenticatedUserDropdown.propTypes = { + intl: intlShape.isRequired, + username: PropTypes.string.isRequired, +}; + +AuthenticatedUserDropdown.defaultProps = {}; + +export default injectIntl(AuthenticatedUserDropdown); diff --git a/src/course-header/Header.jsx b/src/course-header/Header.jsx new file mode 100644 index 0000000..7dac9b7 --- /dev/null +++ b/src/course-header/Header.jsx @@ -0,0 +1,81 @@ +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 } from '@edx/frontend-platform/react'; + +import AnonymousUserMenu from './AnonymousUserMenu'; +import AuthenticatedUserDropdown from './AuthenticatedUserDropdown'; +import messages from './messages'; + +function LinkedLogo({ + href, + src, + alt, + ...attributes +}) { + return ( + + {alt} + + ); +} + +LinkedLogo.propTypes = { + href: PropTypes.string.isRequired, + src: PropTypes.string.isRequired, + alt: PropTypes.string.isRequired, +}; + +function Header({ + courseOrg, courseNumber, courseTitle, intl, showUserDropdown, +}) { + const { authenticatedUser } = useContext(AppContext); + + let headerLogo = ( + + ); + + return ( +
+ {intl.formatMessage(messages.skipNavLink)} +
+ {headerLogo} +
+ {courseOrg} {courseNumber} + {courseTitle} +
+ {showUserDropdown && authenticatedUser && ( + + )} + {showUserDropdown && !authenticatedUser && ( + + )} +
+
+ ); +} + +Header.propTypes = { + courseOrg: PropTypes.string, + courseNumber: PropTypes.string, + courseTitle: PropTypes.string, + intl: intlShape.isRequired, + showUserDropdown: PropTypes.bool, +}; + +Header.defaultProps = { + courseOrg: null, + courseNumber: null, + courseTitle: null, + showUserDropdown: true, +}; + +export default injectIntl(Header); diff --git a/src/course-header/Header.test.jsx b/src/course-header/Header.test.jsx new file mode 100644 index 0000000..2889aa7 --- /dev/null +++ b/src/course-header/Header.test.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { + authenticatedUser, initializeMockApp, render, screen, +} from '../setupTest'; +import { Header } from './index'; + +describe('Header', () => { + beforeAll(async () => { + // We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`. + await initializeMockApp(); + }); + + it('displays user button', () => { + render(
); + expect(screen.getByRole('button')).toHaveTextContent(authenticatedUser.username); + }); + + it('displays course data', () => { + const courseData = { + courseOrg: 'course-org', + courseNumber: 'course-number', + courseTitle: 'course-title', + }; + render(
); + + expect(screen.getByText(`${courseData.courseOrg} ${courseData.courseNumber}`)).toBeInTheDocument(); + expect(screen.getByText(courseData.courseTitle)).toBeInTheDocument(); + }); +}); diff --git a/src/course-header/index.js b/src/course-header/index.js new file mode 100644 index 0000000..5653319 --- /dev/null +++ b/src/course-header/index.js @@ -0,0 +1 @@ +export { default as Header } from './Header'; diff --git a/src/course-header/messages.js b/src/course-header/messages.js new file mode 100644 index 0000000..0750159 --- /dev/null +++ b/src/course-header/messages.js @@ -0,0 +1,36 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + dashboard: { + id: 'header.menu.dashboard.label', + defaultMessage: 'Dashboard', + description: 'The text for the user menu Dashboard navigation link.', + }, + help: { + id: 'header.help.label', + defaultMessage: 'Help', + description: 'The text for the link to the Help Center', + }, + profile: { + id: 'header.menu.profile.label', + defaultMessage: 'Profile', + description: 'The text for the user menu Profile navigation link.', + }, + account: { + id: 'header.menu.account.label', + defaultMessage: 'Account', + description: 'The text for the user menu Account navigation link.', + }, + skipNavLink: { + id: 'header.navigation.skipNavLink', + defaultMessage: 'Skip to main content.', + description: 'A link used by screen readers to allow users to skip to the main content of the page.', + }, + signOut: { + id: 'header.menu.signOut.label', + defaultMessage: 'Sign Out', + description: 'The label for the user menu Sign Out action.', + }, +}); + +export default messages; diff --git a/src/index.jsx b/src/index.jsx index 2a73d1c..61fa3be 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -4,9 +4,9 @@ import 'regenerator-runtime/runtime'; import React from 'react'; import ReactDOM from 'react-dom'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; import { APP_READY, + APP_INIT_ERROR, initialize, subscribe, } from '@edx/frontend-platform'; @@ -29,9 +29,25 @@ initialize({ }); */ -ReactDOM.render( - - - , - document.getElementById('root'), -); +// ReactDOM.render( +// +// +// , +// document.getElementById('root'), +// ); + +subscribe(APP_READY, () => { + ReactDOM.render(, document.getElementById('root')); +}); + +subscribe(APP_INIT_ERROR, (error) => { + ReactDOM.render(, document.getElementById('root')); +}); + +initialize({ + messages: [ + appMessages, + footerMessages, + ], + requireAuthenticatedUser: true, +}); \ No newline at end of file diff --git a/src/index.test.jsx b/src/index.test.jsx index b4fdf99..d6212de 100644 --- a/src/index.test.jsx +++ b/src/index.test.jsx @@ -23,7 +23,7 @@ jest.mock('@edx/frontend-platform', () => ({ jest.mock('@edx/frontend-component-footer', () => ({ messages: ['some', 'messages'], })); -jest.mock('./App', () => 'App'); +jest.mock('./App', () => () => (
App
)); describe('app registry', () => { let getElement; diff --git a/src/setupTest.js b/src/setupTest.js index 99a30d7..5b06656 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -1,13 +1,23 @@ /* eslint-disable import/no-extraneous-dependencies */ +import '@testing-library/jest-dom'; +import '@testing-library/jest-dom/extend-expect'; import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; -Enzyme.configure({ adapter: new Adapter() }); +import AppProvider from '@edx/frontend-platform/react/AppProvider'; +import { IntlProvider } from 'react-intl'; +import { render as rtlRender } from '@testing-library/react'; +import PropTypes from 'prop-types'; -// These configuration values are usually set in webpack's EnvironmentPlugin however -// Jest does not use webpack so we need to set these so for testing -process.env.LMS_BASE_URL = 'http://localhost:18000'; +import { getConfig, mergeConfig } from '@edx/frontend-platform'; +import { configure as configureAuth, MockAuthService } from '@edx/frontend-platform/auth'; +import { configure as configureI18n } from '@edx/frontend-platform/i18n'; + +import appMessages from './i18n'; +import { messages as footerMessages } from '@edx/frontend-component-footer'; + +Enzyme.configure({ adapter: new Adapter() }); jest.mock('@edx/frontend-platform/i18n', () => { const i18n = jest.requireActual('@edx/frontend-platform/i18n'); @@ -21,3 +31,74 @@ jest.mock('@edx/frontend-platform/i18n', () => { FormattedMessage: () => 'FormattedMessage', }; }); + +export const authenticatedUser = { + userId: 'abc123', + username: 'Mock User', + roles: [], + administrator: false, +}; + +export function initializeMockApp() { + mergeConfig({ + CONTACT_URL: process.env.CONTACT_URL || null, + INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null, + STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null, + TWITTER_URL: process.env.TWITTER_URL || null, + authenticatedUser: { + userId: 'abc123', + username: 'Mock User', + roles: [], + administrator: false, + }, + SUPPORT_URL_ID_VERIFICATION: 'http://example.com', + }); + + const authService = configureAuth(MockAuthService, { + config: getConfig() + }); + + // i18n doesn't have a service class to return. + configureI18n({ + config: getConfig(), + messages: [appMessages, footerMessages], + requireAuthenticatedUser: true, + }); + + return { authService }; +} + + + +function render( + ui, + { + store = null, + ...renderOptions + } = {}, +) { + function Wrapper({ children }) { + return ( + // eslint-disable-next-line react/jsx-filename-extension + + + {children} + + + ); + } + + Wrapper.propTypes = { + children: PropTypes.node.isRequired, + }; + + return rtlRender(ui, { wrapper: Wrapper, ...renderOptions }); +} + +// Re-export everything. +export * from '@testing-library/react'; + +// Override `render` method. +export { + render, +};