From b26131d4887b422c6df0beb88f42d7b131ac6aad Mon Sep 17 00:00:00 2001 From: Diana Catalina Olarte Date: Tue, 8 Nov 2022 12:12:08 -0500 Subject: [PATCH 1/2] feat: allow runtime configuration --- .env | 2 + .env.development | 2 + package-lock.json | 62 +++++++++++++++++++++----- package.json | 3 +- src/App.jsx | 2 + src/__snapshots__/App.test.jsx.snap | 2 + src/components/Head/Head.jsx | 23 ++++++++++ src/components/Head/index.jsx | 1 + src/components/Head/messages.js | 11 +++++ src/components/Head/test/Head.test.jsx | 17 +++++++ 10 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/components/Head/Head.jsx create mode 100644 src/components/Head/index.jsx create mode 100644 src/components/Head/messages.js create mode 100644 src/components/Head/test/Head.test.jsx diff --git a/.env b/.env index d57909ef..1c57b441 100644 --- a/.env +++ b/.env @@ -30,3 +30,5 @@ ENTERPRISE_MARKETING_URL='' ENTERPRISE_MARKETING_UTM_SOURCE='' ENTERPRISE_MARKETING_UTM_CAMPAIGN='' ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='' +APP_ID='' +MFE_CONFIG_API_URL='' diff --git a/.env.development b/.env.development index 2aa9d4de..44fb5656 100644 --- a/.env.development +++ b/.env.development @@ -36,3 +36,5 @@ 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' +APP_ID='' +MFE_CONFIG_API_URL='' diff --git a/package-lock.json b/package-lock.json index 11c2bd57..430c02e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@edx/brand": "npm:@edx/brand-edx.org@^2.0.3", "@edx/frontend-component-footer": "^11.1.1", "@edx/frontend-component-header": "^3.1.1", - "@edx/frontend-platform": "^2.4.0", + "@edx/frontend-platform": "^2.5.1", "@edx/paragon": "^19.9.0", "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", @@ -40,6 +40,7 @@ "query-string": "7.0.1", "react": "^16.14.0", "react-dom": "^16.14.0", + "react-helmet": "^6.1.0", "react-intl": "^5.20.9", "react-pdf": "^5.5.0", "react-redux": "^7.2.4", @@ -3447,13 +3448,13 @@ } }, "node_modules/@edx/frontend-platform": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-2.4.0.tgz", - "integrity": "sha512-i5z6cg+A+OLHgpvqPzU2jd7VM2soHsOAb8Jk6t4ddAtN1gLDad/pFhty3+aBEPIsnTuGDmrtUl5Yl/hP8Muc+w==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-2.6.2.tgz", + "integrity": "sha512-h+gYLkPYw41krGiSGs59o2jaq/g3Yk6ay/3rBq0y1/KM6eeaq/F7o14YOhfTRLTpld9Hg+MPKzfOuHyqQN2TEw==", "dependencies": { "@cospired/i18n-iso-languages": "2.2.0", - "@formatjs/intl-pluralrules": "^4.3.3", - "@formatjs/intl-relativetimeformat": "^10.0.1", + "@formatjs/intl-pluralrules": "4.3.3", + "@formatjs/intl-relativetimeformat": "10.0.1", "axios": "0.26.1", "axios-cache-adapter": "2.7.3", "form-urlencoded": "4.1.4", @@ -27485,6 +27486,20 @@ } } }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-intl": { "version": "5.25.1", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz", @@ -27825,6 +27840,14 @@ "isarray": "0.0.1" } }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -36031,13 +36054,13 @@ } }, "@edx/frontend-platform": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-2.4.0.tgz", - "integrity": "sha512-i5z6cg+A+OLHgpvqPzU2jd7VM2soHsOAb8Jk6t4ddAtN1gLDad/pFhty3+aBEPIsnTuGDmrtUl5Yl/hP8Muc+w==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-2.6.2.tgz", + "integrity": "sha512-h+gYLkPYw41krGiSGs59o2jaq/g3Yk6ay/3rBq0y1/KM6eeaq/F7o14YOhfTRLTpld9Hg+MPKzfOuHyqQN2TEw==", "requires": { "@cospired/i18n-iso-languages": "2.2.0", - "@formatjs/intl-pluralrules": "^4.3.3", - "@formatjs/intl-relativetimeformat": "^10.0.1", + "@formatjs/intl-pluralrules": "4.3.3", + "@formatjs/intl-relativetimeformat": "10.0.1", "axios": "0.26.1", "axios-cache-adapter": "2.7.3", "form-urlencoded": "4.1.4", @@ -54222,6 +54245,17 @@ "use-sidecar": "^1.1.2" } }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, "react-intl": { "version": "5.25.1", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz", @@ -54490,6 +54524,12 @@ } } }, + "react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "requires": {} + }, "react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", diff --git a/package.json b/package.json index 0293e900..2f40f454 100755 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@edx/brand": "npm:@edx/brand-edx.org@^2.0.3", "@edx/frontend-component-footer": "^11.1.1", "@edx/frontend-component-header": "^3.1.1", - "@edx/frontend-platform": "^2.4.0", + "@edx/frontend-platform": "^2.5.1", "@edx/paragon": "^19.9.0", "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", @@ -57,6 +57,7 @@ "query-string": "7.0.1", "react": "^16.14.0", "react-dom": "^16.14.0", + "react-helmet": "^6.1.0", "react-intl": "^5.20.9", "react-pdf": "^5.5.0", "react-redux": "^7.2.4", diff --git a/src/App.jsx b/src/App.jsx index b70e7b31..f91c6b51 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,10 +13,12 @@ import CTA from 'containers/CTA'; import ListView from 'containers/ListView'; import './App.scss'; +import Head from './components/Head'; export const App = ({ courseMetadata, isEnabled }) => (
+
+
+
+ + {intl.formatMessage(messages.PageTitle, { siteName: getConfig().SITE_NAME })} + + + + ); +} + +Head.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(Head); diff --git a/src/components/Head/index.jsx b/src/components/Head/index.jsx new file mode 100644 index 00000000..2d123e35 --- /dev/null +++ b/src/components/Head/index.jsx @@ -0,0 +1 @@ +export { default } from './Head'; diff --git a/src/components/Head/messages.js b/src/components/Head/messages.js new file mode 100644 index 00000000..24ae5842 --- /dev/null +++ b/src/components/Head/messages.js @@ -0,0 +1,11 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + PageTitle: { + id: 'PageTitle', + defaultMessage: 'ORA staff grading | {siteName}', + description: 'Title tag', + }, +}); + +export default messages; diff --git a/src/components/Head/test/Head.test.jsx b/src/components/Head/test/Head.test.jsx new file mode 100644 index 00000000..e6c30647 --- /dev/null +++ b/src/components/Head/test/Head.test.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { Helmet } from 'react-helmet'; +import { getConfig } from '@edx/frontend-platform'; +import { mount } from 'enzyme'; +import Head from '../Head'; + +describe('Head', () => { + const props = {}; + it('should match render title tag and favicon with the site configuration values', () => { + mount(); + const helmet = Helmet.peek(); + expect(helmet.title).toEqual(`ORA staff grading | ${getConfig().SITE_NAME}`); + expect(helmet.linkTags[0].rel).toEqual('shortcut icon'); + expect(helmet.linkTags[0].href).toEqual(getConfig().FAVICON_URL); + }); +}); From c0f2445ab5822a33ef0a4e427b7235d23786c9b0 Mon Sep 17 00:00:00 2001 From: Diana Catalina Olarte Date: Sun, 20 Nov 2022 12:19:41 +1100 Subject: [PATCH 2/2] test: organize Head test --- src/App.test.jsx | 1 + src/__snapshots__/App.test.jsx.snap | 4 +-- src/components/Head/Head.jsx | 23 ----------------- .../Head/__snapshots__/index.test.jsx.snap | 14 +++++++++++ src/components/Head/index.jsx | 21 +++++++++++++++- src/components/Head/index.test.jsx | 25 +++++++++++++++++++ src/components/Head/test/Head.test.jsx | 17 ------------- src/setupTest.js | 6 +++++ 8 files changed, 68 insertions(+), 43 deletions(-) delete mode 100644 src/components/Head/Head.jsx create mode 100644 src/components/Head/__snapshots__/index.test.jsx.snap create mode 100644 src/components/Head/index.test.jsx delete mode 100644 src/components/Head/test/Head.test.jsx diff --git a/src/App.test.jsx b/src/App.test.jsx index 7bc36ad6..37c553b4 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -25,6 +25,7 @@ jest.mock('@edx/frontend-component-footer', () => 'Footer'); jest.mock('containers/DemoWarning', () => 'DemoWarning'); jest.mock('containers/CTA', () => 'CTA'); jest.mock('containers/ListView', () => 'ListView'); +jest.mock('components/Head', () => 'Head'); const logo = 'fakeLogo.png'; let el; diff --git a/src/__snapshots__/App.test.jsx.snap b/src/__snapshots__/App.test.jsx.snap index 98e4642f..198fafd1 100644 --- a/src/__snapshots__/App.test.jsx.snap +++ b/src/__snapshots__/App.test.jsx.snap @@ -3,7 +3,7 @@ exports[`App router component snapshot: disabled (show demo warning) 1`] = `
- +
- +
- - {intl.formatMessage(messages.PageTitle, { siteName: getConfig().SITE_NAME })} - - - - ); -} - -Head.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(Head); diff --git a/src/components/Head/__snapshots__/index.test.jsx.snap b/src/components/Head/__snapshots__/index.test.jsx.snap new file mode 100644 index 00000000..ceb42a5f --- /dev/null +++ b/src/components/Head/__snapshots__/index.test.jsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Head snapshot 1`] = ` + + + ORA staff grading | site-name + + + +`; diff --git a/src/components/Head/index.jsx b/src/components/Head/index.jsx index 2d123e35..ba3e78fc 100644 --- a/src/components/Head/index.jsx +++ b/src/components/Head/index.jsx @@ -1 +1,20 @@ -export { default } from './Head'; +import React from 'react'; +import { Helmet } from 'react-helmet'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { getConfig } from '@edx/frontend-platform'; + +import messages from './messages'; + +function Head() { + const { formatMessage } = useIntl(); + return ( + + + {formatMessage(messages.PageTitle, { siteName: getConfig().SITE_NAME })} + + + + ); +} + +export default Head; diff --git a/src/components/Head/index.test.jsx b/src/components/Head/index.test.jsx new file mode 100644 index 00000000..8d7036c2 --- /dev/null +++ b/src/components/Head/index.test.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { getConfig } from '@edx/frontend-platform'; +import { shallow } from 'enzyme'; +import Head from '.'; + +jest.mock('react-helmet', () => ({ + Helmet: 'Helmet', +})); + +jest.mock('@edx/frontend-platform', () => ({ + getConfig: () => ({ + SITE_NAME: 'site-name', + FAVICON_URL: 'favicon-url', + }), +})); + +describe('Head', () => { + it('snapshot', () => { + const el = shallow(); + expect(el).toMatchSnapshot(); + + expect(el.find('title').text()).toContain(getConfig().SITE_NAME); + expect(el.find('link').prop('href')).toEqual(getConfig().FAVICON_URL); + }); +}); diff --git a/src/components/Head/test/Head.test.jsx b/src/components/Head/test/Head.test.jsx deleted file mode 100644 index e6c30647..00000000 --- a/src/components/Head/test/Head.test.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { Helmet } from 'react-helmet'; -import { getConfig } from '@edx/frontend-platform'; -import { mount } from 'enzyme'; -import Head from '../Head'; - -describe('Head', () => { - const props = {}; - it('should match render title tag and favicon with the site configuration values', () => { - mount(); - const helmet = Helmet.peek(); - expect(helmet.title).toEqual(`ORA staff grading | ${getConfig().SITE_NAME}`); - expect(helmet.linkTags[0].rel).toEqual('shortcut icon'); - expect(helmet.linkTags[0].href).toEqual(getConfig().FAVICON_URL); - }); -}); diff --git a/src/setupTest.js b/src/setupTest.js index 7f16c8e7..914910bd 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -18,11 +18,17 @@ jest.mock('react', () => ({ jest.mock('@edx/frontend-platform/i18n', () => { const i18n = jest.requireActual('@edx/frontend-platform/i18n'); const PropTypes = jest.requireActual('prop-types'); + const { formatMessage } = jest.requireActual('./testUtils'); + const formatDate = jest.fn(date => new Date(date).toLocaleDateString()).mockName('useIntl.formatDate'); return { ...i18n, intlShape: PropTypes.shape({ formatMessage: PropTypes.func, }), + useIntl: () => ({ + formatMessage, + formatDate, + }), defineMessages: m => m, FormattedMessage: () => 'FormattedMessage', };