diff --git a/.eslintrc b/.eslintrc index 23175c317..296ab9c7d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,7 @@ "react" ], "settings": { + "import/external-module-folders": ["public"], "jsdoc": { "preferredTypes": { "delete": "delete", @@ -37,6 +38,11 @@ "plugin:jest/recommended", "plugin:jsdoc/recommended" ], + "globals": { + "mockHook": "readonly", + "mountHookComponent": "readonly", + "mockWindowLocation": "readonly" + }, "rules": { "arrow-parens": [ "error", @@ -81,6 +87,14 @@ "jsdoc/require-param-description": 0, "jsdoc/require-property-description": 0, "jsdoc/require-returns-description": 0, + "jsdoc/tag-lines": [ + "warn", + "always", + { + "count": 0, + "noEndLines": true + } + ], "max-len": [ "error", { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9437ac2c..4a0030711 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,13 +71,13 @@ Curiosity makes use of the branches `master`, `stage`, `qa`, and `ci`. - `stage` branch is a protected representation of production environments - Adding commits, or a PR, into `stage` should generate a `prod-beta` branch within the deploy repository [curiosity-frontend-build](https://github.com/RedHatInsights/curiosity-frontend-build) - The `prod-beta` branch is manually deployed through coordination with the operations team. -- `qa` branch is a representation of `qa-stable`, and `ci-stable`. +- `qa` branch is a representation of `ci-stable`, `qa-stable` and `stage-stable`. - Adding commits, or a PR, into `ci-stable` should generate `ci-*` and `qa-*` branches within the deploy repository [curiosity-frontend-build](https://github.com/RedHatInsights/curiosity-frontend-build) - - The `ci-*` and `qa-*` branches are automatically deployed within an averaged time for both `https://ci.*.redhat.com` and `https://qa.*.redhat.com` + - The `ci-*` and `qa-*` branches are automatically deployed within an averaged time for `https://ci.*.redhat.com`, `https://qa.*.redhat.com` and `https://*.stage.redhat.com` - In the future, once the API is fully deployed to QA, this will be a representation of `qa-beta` and `qa-stable` -- `ci` branch is a representation of `ci-beta`, and `qa-beta`. +- `ci` branch is a representation of `ci-beta`, `qa-beta` and `stage-beta`. - Adding commits, or a PR, into `ci-beta` should generate `ci-*` and `qa-*` branches within the deploy repository [curiosity-frontend-build](https://github.com/RedHatInsights/curiosity-frontend-build) - - The `ci-*` and `qa-*` branches are automatically deployed within an averaged time for both `https://ci.*.redhat.com` and `https://qa.*.redhat.com` + - The `ci-*` and `qa-*` branches are automatically deployed within an averaged time for `https://ci.*.redhat.com`, `https://qa.*.redhat.com` and `https://*.stage.redhat.com` - In the future, once the API is fully deployed to QA, this will be a representation of `ci-beta` and `ci-stable` #### Branching and Pull Request Workflow diff --git a/package.json b/package.json index 4a534837b..00b048f6d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "!src/index.js", "!src/setupTests.js", "!src/components/app.js", + "!src/components/**/index.js", "!src/common/index.js", "!src/redux/index.js", "!src/redux/store.js", @@ -113,31 +114,30 @@ "test:local": "react-scripts test --env=jsdom --roots=./src" }, "dependencies": { - "@patternfly/patternfly": "4.102.2", - "@patternfly/react-charts": "6.14.17", - "@patternfly/react-core": "4.115.2", - "@patternfly/react-icons": "4.10.2", - "@patternfly/react-styles": "4.10.2", - "@patternfly/react-table": "4.26.7", - "@patternfly/react-tokens": "4.11.3", - "@redhat-cloud-services/frontend-components": "3.1.11", - "@redhat-cloud-services/frontend-components-notifications": "3.1.0", - "@redhat-cloud-services/frontend-components-utilities": "3.1.2", + "@patternfly/patternfly": "4.108.2", + "@patternfly/react-charts": "6.14.29", + "@patternfly/react-core": "4.128.2", + "@patternfly/react-icons": "4.10.11", + "@patternfly/react-styles": "4.10.11", + "@patternfly/react-table": "4.27.24", + "@patternfly/react-tokens": "4.11.12", + "@redhat-cloud-services/frontend-components": "3.2.5", + "@redhat-cloud-services/frontend-components-notifications": "3.2.2", + "@redhat-cloud-services/frontend-components-utilities": "3.2.2", "axios": "^0.21.1", "classnames": "^2.3.1", - "i18next": "^20.2.2", + "i18next": "^20.3.1", "i18next-xhr-backend": "^3.2.2", "js-cookie": "^2.2.1", "locale-code": "^2.0.2", "lodash": "^4.17.21", "lru-cache": "^6.0.0", "moment": "^2.29.1", - "node-sass": "^4.14.1", "numbro": "^2.3.2", "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-i18next": "^11.8.15", + "react-i18next": "^11.10.0", "react-redux": "^7.2.4", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", @@ -148,23 +148,24 @@ "redux-promise-middleware": "^6.1.2", "redux-thunk": "^2.3.0", "reselect": "^4.0.0", - "victory-create-container": "^35.5.1" + "sass": "^1.34.1", + "victory-create-container": "^35.8.5" }, "devDependencies": { "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "apidoc-mock": "^3.0.4", - "cspell": "^5.4.0", + "apidoc-mock": "^4.0.0", + "cspell": "^5.6.1", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.2", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.22.1", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-jest": "^24.3.6", - "eslint-plugin-jsdoc": "^33.3.0", + "eslint-plugin-jsdoc": "^35.1.3", "eslint-plugin-json": "^3.0.0", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^3.4.0", - "eslint-plugin-react": "^7.23.2", + "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^4.2.0", "express": "^4.17.1", "glob": "^7.1.7", diff --git a/src/components/app.js b/src/components/app.js index b6e1af3cd..5d076c148 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -4,7 +4,7 @@ import { NotificationsPortal } from '@redhat-cloud-services/frontend-components- import { connectRouter, reduxActions } from '../redux'; import { helpers } from '../common/helpers'; import { I18n } from './i18n/i18n'; -import { Router } from './router/router'; +import { Router } from './router'; import Authentication from './authentication/authentication'; /** diff --git a/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap b/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap index 18f0ea5b3..16b408c3e 100644 --- a/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap +++ b/src/components/authentication/__tests__/__snapshots__/authentication.test.js.snap @@ -286,7 +286,126 @@ exports[`Authentication Component should return a message on 401 error: 401 erro `; -exports[`Authentication Component should return a redirect on 418 error: 418 error 1`] = `"418 redirect"`; +exports[`Authentication Component should return a redirect on 418 error: 418 error 1`] = ` + +`; exports[`Authentication Component should return a redirect on a specific 403 error and error code: 403 error 1`] = ` `; -exports[`Authentication Component should return a redirect on a specific 403 error and error code: 403 redirect error 1`] = `"403 redirect"`; +exports[`Authentication Component should return a redirect on a specific 403 error and error code: 403 redirect error 1`] = ` + +`; diff --git a/src/components/authentication/__tests__/authentication.test.js b/src/components/authentication/__tests__/authentication.test.js index 3e01ef313..eae540244 100644 --- a/src/components/authentication/__tests__/authentication.test.js +++ b/src/components/authentication/__tests__/authentication.test.js @@ -64,7 +64,7 @@ describe('Authentication Component', () => { ); - expect(component.html()).toMatchSnapshot('418 error'); + expect(component).toMatchSnapshot('418 error'); }); it('should return a redirect on a specific 403 error and error code', () => { @@ -88,7 +88,7 @@ describe('Authentication Component', () => { ); - expect(component.html()).toMatchSnapshot('403 redirect error'); + expect(component).toMatchSnapshot('403 redirect error'); component.setProps({ session: { diff --git a/src/components/authentication/authentication.js b/src/components/authentication/authentication.js index 1609ae647..3205f4c3d 100644 --- a/src/components/authentication/authentication.js +++ b/src/components/authentication/authentication.js @@ -6,7 +6,7 @@ import { NotAuthorized } from '@redhat-cloud-services/frontend-components/NotAut import { connectRouter, reduxActions, reduxSelectors } from '../../redux'; import { rhsmApiTypes } from '../../types'; import { helpers } from '../../common'; -import { Redirect, routerHelpers, routerConfig } from '../router/router'; +import { routerHelpers, Redirect } from '../router'; import MessageView from '../messageView/messageView'; import { translate } from '../i18n/i18n'; @@ -16,7 +16,7 @@ import { translate } from '../i18n/i18n'; * @augments React.Component */ class Authentication extends Component { - appName = routerConfig.appName; + appName = routerHelpers.appName; removeListeners = helpers.noop; @@ -42,7 +42,7 @@ class Authentication extends Component { hideGlobalFilter(); const appNav = onNavigation(event => { - const { routeHref } = routerHelpers.getNavRouteDetail({ id: event.navId }); + const { routeHref } = routerHelpers.getRouteConfig({ id: event.navId }); history.push(routeHref); }); @@ -85,10 +85,7 @@ class Authentication extends Component { (session.errorCodes && session.errorCodes.includes(rhsmApiTypes.RHSM_API_RESPONSE_ERROR_DATA_CODE_TYPES.OPTIN)) || session.status === 418 ) { - if (helpers.TEST_MODE) { - return {session.status} redirect; - } - return ; + return ; } return ( @@ -119,7 +116,7 @@ Authentication.propTypes = { setAppName: PropTypes.func, session: PropTypes.shape({ authorized: PropTypes.shape({ - [routerConfig.appName]: PropTypes.bool + [routerHelpers.appName]: PropTypes.bool }), errorCodes: PropTypes.arrayOf(PropTypes.string), pending: PropTypes.bool, diff --git a/src/components/form/__tests__/__snapshots__/select.test.js.snap b/src/components/form/__tests__/__snapshots__/select.test.js.snap index 500ebe088..ff0ab3dca 100644 --- a/src/components/form/__tests__/__snapshots__/select.test.js.snap +++ b/src/components/form/__tests__/__snapshots__/select.test.js.snap @@ -111,6 +111,7 @@ exports[`Select Component should allow alternate direction and position options: isCreatable={false} isDisabled={false} isGrouped={false} + isInputValuePersisted={false} isOpen={false} isPlain={false} maxHeight={null} @@ -145,6 +146,8 @@ exports[`Select Component should allow alternate direction and position options: isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -165,6 +168,8 @@ exports[`Select Component should allow alternate direction and position options: isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -198,6 +203,7 @@ exports[`Select Component should allow alternate direction and position options: isCreatable={false} isDisabled={false} isGrouped={false} + isInputValuePersisted={false} isOpen={false} isPlain={false} maxHeight={null} @@ -232,6 +238,8 @@ exports[`Select Component should allow alternate direction and position options: isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -252,6 +260,8 @@ exports[`Select Component should allow alternate direction and position options: isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -285,6 +295,7 @@ exports[`Select Component should allow being disabled with missing options: no o isCreatable={false} isDisabled={true} isGrouped={false} + isInputValuePersisted={false} isOpen={false} isPlain={false} maxHeight={null} @@ -331,6 +342,7 @@ exports[`Select Component should allow being disabled with missing options: opti isCreatable={false} isDisabled={true} isGrouped={false} + isInputValuePersisted={false} isOpen={false} isPlain={false} maxHeight={null} @@ -365,6 +377,8 @@ exports[`Select Component should allow being disabled with missing options: opti isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -385,6 +399,8 @@ exports[`Select Component should allow being disabled with missing options: opti isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -405,6 +421,8 @@ exports[`Select Component should allow being disabled with missing options: opti isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -425,6 +443,8 @@ exports[`Select Component should allow being disabled with missing options: opti isChecked={false} isDisabled={false} isFavorite={null} + isLoad={false} + isLoading={false} isNoResultsOption={false} isPlaceholder={false} isSelected={false} @@ -458,6 +478,7 @@ exports[`Select Component should allow being disabled with missing options: opti isCreatable={false} isDisabled={true} isGrouped={false} + isInputValuePersisted={false} isOpen={false} isPlain={false} maxHeight={null} @@ -483,6 +504,28 @@ exports[`Select Component should allow being disabled with missing options: opti /> `; +exports[`Select Component should allow data- props: data- attributes 1`] = ` +Object { + "ariaLabel": "Select option", + "className": "", + "data-dolor-sit": "dolor sit", + "data-lorem": "ipsum", + "direction": "down", + "id": "generatedid-", + "isDisabled": false, + "isToggleText": true, + "maxHeight": null, + "name": null, + "onSelect": [Function], + "options": Array [], + "placeholder": "Select option", + "position": "left", + "selectedOptions": null, + "toggleIcon": null, + "variant": "single", +} +`; + exports[`Select Component should allow plain objects as values, and be able to select options based on values within the object: select when option values are objects 1`] = `
`; -exports[`Select Component should render a expanded select: expanded 1`] = ` +exports[`Select Component should render an expanded select: expanded 1`] = ` ); + expect(component.props()).toMatchSnapshot('data- attributes'); + }); }); diff --git a/src/components/form/select.js b/src/components/form/select.js index b7a48d2a1..8ee309743 100644 --- a/src/components/form/select.js +++ b/src/components/form/select.js @@ -37,6 +37,8 @@ const SelectPosition = DropdownPosition; class Select extends React.Component { state = { isExpanded: false, options: null, selected: null }; + selectField = React.createRef(); + componentDidMount() { const { options } = this.state; @@ -135,13 +137,16 @@ class Select extends React.Component { }); }; + // FixMe: attributes filtered on PF select component. allow data- attributes /** * Format options into a consumable array of objects format. */ formatOptions() { + const { current: domElement = {} } = this.selectField; const { options, selectedOptions, variant } = this.props; + const dataAttributes = Object.entries(this.props).filter(([key]) => /^data-/i.test(key)); const updatedOptions = _isPlainObject(options) - ? Object.keys(options).map(key => ({ ...options[key], title: key, value: options[key] })) + ? Object.entries(options).map(([key, value]) => ({ ...value, title: key, value })) : _cloneDeep(options); const activateOptions = @@ -197,6 +202,10 @@ class Select extends React.Component { updateSelected = updatedOptions.filter(opt => opt.selected === true).map(opt => opt.title); } + if (domElement?.parentRef?.current) { + dataAttributes.forEach(([key, value]) => domElement?.parentRef?.current.setAttribute(key, value)); + } + this.setState({ options: updatedOptions, selected: updateSelected @@ -257,6 +266,7 @@ class Select extends React.Component { isOpen={isExpanded} toggleIcon={toggleIcon} placeholderText={placeholder} + ref={this.selectField} {...pfSelectOptions} > {(options && diff --git a/src/components/graphCard/__tests__/graphCard.test.js b/src/components/graphCard/__tests__/graphCard.test.js index 59df35a8c..e134881ab 100644 --- a/src/components/graphCard/__tests__/graphCard.test.js +++ b/src/components/graphCard/__tests__/graphCard.test.js @@ -179,4 +179,20 @@ describe('GraphCard Component', () => { expect(component).toMatchSnapshot('disabled component'); }); + + it('should allow a custom display for card actions', () => { + const actionDisplay = jest.fn(); + const props = { + query: { + [RHSM_API_QUERY_TYPES.GRANULARITY]: GRANULARITY_TYPES.DAILY, + [RHSM_API_QUERY_TYPES.END_DATE]: '2021-02-24T23:59:59.999Z', + [RHSM_API_QUERY_TYPES.START_DATE]: '2021-01-25T00:00:00.000Z' + }, + productId: 'lorem', + settings: { actionDisplay } + }; + + shallow(); + expect(actionDisplay).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/components/graphCard/graphCard.js b/src/components/graphCard/graphCard.js index 3871a4334..ed2a21511 100644 --- a/src/components/graphCard/graphCard.js +++ b/src/components/graphCard/graphCard.js @@ -142,7 +142,7 @@ class GraphCard extends React.Component { * @returns {Node} */ render() { - const { cardTitle, children, error, graphData, isDisabled, pending, settings } = this.props; + const { cardTitle, children, error, graphData, meta, isDisabled, pending, settings } = this.props; if (isDisabled) { return null; @@ -152,7 +152,7 @@ class GraphCard extends React.Component { // Apply actionDisplay callback, return node if (typeof settings?.actionDisplay === 'function') { - actionDisplay = settings.actionDisplay({ ...graphData }); + actionDisplay = settings.actionDisplay({ data: { ...graphData }, meta: { ...meta } }); } return ( @@ -188,7 +188,8 @@ class GraphCard extends React.Component { * * @type {{productLabel: string, settings: object, productId: string, query: object, pending: boolean, * error: boolean, cardTitle: Node, filterGraphData: Array, getGraphReportsCapacity: Function, - * viewId: string, t: Function, children: Node, graphData: object, isDisabled: boolean}} + * viewId: string, t: Function, children: Node, graphData: object, isDisabled: boolean, + * meta: object}} */ GraphCard.propTypes = { cardTitle: PropTypes.node, @@ -203,6 +204,7 @@ GraphCard.propTypes = { ), getGraphReportsCapacity: PropTypes.func, graphData: PropTypes.object, + meta: PropTypes.object, query: PropTypes.shape({ [RHSM_API_QUERY_TYPES.GRANULARITY]: PropTypes.oneOf([...Object.values(GRANULARITY_TYPES)]).isRequired, [RHSM_API_QUERY_TYPES.START_DATE]: PropTypes.string.isRequired, @@ -222,9 +224,9 @@ GraphCard.propTypes = { /** * Default props. * - * @type {{getGraphReportsCapacity: Function, productLabel: string, settings: object, viewId: string, - * t: translate, children: Node, pending: boolean, graphData: object, isDisabled: boolean, - * error: boolean, cardTitle: Node, filterGraphData: Array}} + * @type {{productLabel: string, settings: object, pending: boolean, error: boolean, cardTitle: Node, + * filterGraphData: Array, getGraphReportsCapacity: Function, viewId: string, t: translate, + * children: Node, graphData: object, isDisabled: boolean, meta: object}} */ GraphCard.defaultProps = { cardTitle: null, @@ -233,6 +235,7 @@ GraphCard.defaultProps = { filterGraphData: [], getGraphReportsCapacity: helpers.noop, graphData: {}, + meta: {}, isDisabled: helpers.UI_DISABLED_GRAPH, pending: false, productLabel: '', diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap index a0c396b3a..4ecf5b3b4 100644 --- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap +++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap @@ -5,6 +5,7 @@ exports[`I18n Component should attempt to perform a component translate: transla exports[`I18n Component should attempt to perform a string replace: translate 1`] = ` Object { "localeKey": "t(lorem.ipsum)", + "multiContext": "t(lorem.ipsum, {\\"context\\":\\"hello_world\\"})", "placeholder": "t(lorem.ipsum, hello world)", } `; @@ -266,11 +267,11 @@ Array [ }, Object { "key": "curiosity-view.title", - "match": "t('curiosity-view.title', { appName: helpers.UI_DISPLAY_NAME, context: product.pathParameter })", + "match": "t('curiosity-view.title', { appName: helpers.UI_DISPLAY_NAME, context: (Array.isArray(product.pathParameter)", }, Object { "key": "curiosity-view.description", - "match": "t('curiosity-view.description', { appName: helpers.UI_DISPLAY_NAME, context: product.productParameter })", + "match": "t('curiosity-view.description', { appName: helpers.UI_DISPLAY_NAME, context: (Array.isArray(product.productParameter)", }, ], }, @@ -315,7 +316,7 @@ Array [ }, Object { "key": "curiosity-graph.card-action-total", - "match": "translate('curiosity-graph.card-action-total', { context: 'coreHours', total: numbro(total)", + "match": "translate('curiosity-graph.card-action-total', { context: 'coreHours', total: numbro(totalCoreHours)", }, Object { "key": "curiosity-inventory.label", @@ -328,7 +329,7 @@ Array [ "keys": Array [ Object { "key": "curiosity-graph.card-action-total", - "match": "translate('curiosity-graph.card-action-total', { context: 'coreHours', total: numbro(total)", + "match": "translate('curiosity-graph.card-action-total', { context: 'coreHours', total: numbro(totalCoreHours)", }, Object { "key": "curiosity-inventory.label", diff --git a/src/components/i18n/__tests__/i18n.test.js b/src/components/i18n/__tests__/i18n.test.js index f5d5cd01f..38117c097 100644 --- a/src/components/i18n/__tests__/i18n.test.js +++ b/src/components/i18n/__tests__/i18n.test.js @@ -1,12 +1,11 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import React from 'react'; -import { act } from 'react-dom/test-utils'; import PropTypes from 'prop-types'; -import { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import _get from 'lodash/get'; -import { I18n, translate, translateComponent } from '../i18n'; import enLocales from '../../../../public/locales/en-US'; +import { I18n, translate, translateComponent } from '../i18n'; /** * Get translation keys. @@ -53,38 +52,25 @@ const getTranslationKeys = ({ files = './src/**/!(*.test|*.spec).@(js|jsx)', lis describe('I18n Component', () => { const getKeys = getTranslationKeys({}); - const loadHookComponent = async callback => { - let component = null; - await act(async () => { - component = callback(); - }); - component?.update(); - return component; - }; - it('should render a basic component', async () => { const props = { locale: 'es' }; - const component = await loadHookComponent(() => - mount( - - lorem ipsum - - ) + const component = await mountHookComponent( + + lorem ipsum + ); expect(component).toMatchSnapshot('basic'); }); it('should pass children', async () => { - const component = await loadHookComponent(() => - mount( - - lorem ipsum - - ) + const component = await mountHookComponent( + + lorem ipsum + ); expect(component.html()).toMatchSnapshot('children'); @@ -121,10 +107,12 @@ describe('I18n Component', () => { it('should attempt to perform a string replace', () => { const localeKey = translate('lorem.ipsum'); const placeholder = translate('lorem.ipsum', 'hello world'); + const multiContext = translate('lorem.ipsum', { context: ['hello', 'world'] }); expect({ localeKey, - placeholder + placeholder, + multiContext }).toMatchSnapshot('translate'); }); diff --git a/src/components/i18n/i18n.js b/src/components/i18n/i18n.js index a4b7d81ba..9b967f9d5 100644 --- a/src/components/i18n/i18n.js +++ b/src/components/i18n/i18n.js @@ -16,19 +16,25 @@ import { helpers } from '../../common/helpers'; * @returns {string|Node} */ const translate = (translateKey, values = null, components) => { + const updatedValues = values; + + if (Array.isArray(updatedValues?.context)) { + updatedValues.context = updatedValues.context.join('_'); + } + if (helpers.TEST_MODE) { - return helpers.noopTranslate(translateKey, values, components); + return helpers.noopTranslate(translateKey, updatedValues, components); } if (components) { return ( - (i18next.store && ) || ( + (i18next.store && ) || ( t({translateKey}) ) ); } - return (i18next.store && i18next.t(translateKey, values)) || `t(${translateKey})`; + return (i18next.store && i18next.t(translateKey, updatedValues)) || `t(${translateKey})`; }; /** diff --git a/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap b/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap index faada656b..515ed00ef 100644 --- a/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap +++ b/src/components/productView/__tests__/__snapshots__/productViewMissing.test.js.snap @@ -1,5 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`ProductViewMissing Component should redirect when there are limited product cards: redirect action 1`] = ` +Array [ + Array [ + Object { + "meta": Object { + "appName": undefined, + "id": "rhel", + "secondaryNav": undefined, + }, + "payload": Promise {}, + "type": "PLATFORM_SET_NAV", + }, + ], +] +`; + exports[`ProductViewMissing Component should render a non-connected component: non-connected 1`] = ` @@ -55,7 +71,7 @@ exports[`ProductViewMissing Component should render a non-connected component: n @@ -91,7 +107,7 @@ exports[`ProductViewMissing Component should render a non-connected component: n @@ -149,7 +165,7 @@ exports[`ProductViewMissing Component should render a predictable set of product > @@ -157,13 +173,13 @@ exports[`ProductViewMissing Component should render a predictable set of product headingLevel="h2" size="lg" > - t(curiosity-view.title, {"appName":"Subscriptions"}) + t(curiosity-view.title, {"appName":"Subscriptions","context":"OpenShift Container Platform"}) - t(curiosity-view.description, {"appName":"Subscriptions","context":"loremIpsum"}) + t(curiosity-view.description, {"appName":"Subscriptions","context":"OpenShift Container Platform"})