diff --git a/packages/core/src/util/label.ts b/packages/core/src/util/label.ts index 64cd4ef2c..80e9fd8ef 100644 --- a/packages/core/src/util/label.ts +++ b/packages/core/src/util/label.ts @@ -132,7 +132,12 @@ export const computeChildLabel = ( return ''; } - const currentValue = get(childData, childLabelProp, ''); + const currentValue = get(childData, childLabelProp); + + // in case there is no value, then we can't map it to an enum or oneOf + if (currentValue === undefined) { + return ''; + } // check whether the value is part of a oneOf or enum and needs to be translated const childSchema = Resolve.schema( diff --git a/packages/core/src/util/renderer.ts b/packages/core/src/util/renderer.ts index 004e75a8a..33346064a 100644 --- a/packages/core/src/util/renderer.ts +++ b/packages/core/src/util/renderer.ts @@ -31,7 +31,6 @@ import { LabelElement, UISchemaElement, } from '../models'; -import find from 'lodash/find'; import { getUISchemas, getAjv, @@ -53,7 +52,7 @@ import type { } from '../reducers'; import type { RankedTester } from '../testers'; import { hasShowRule, isInherentlyEnabled, isVisible } from './runtime'; -import { createLabelDescriptionFrom } from './label'; +import { computeChildLabel, createLabelDescriptionFrom } from './label'; import type { CombinatorKeyword } from './combinators'; import { moveDown, moveUp } from './array'; import type { AnyAction, Dispatch } from './type'; @@ -689,20 +688,17 @@ export const mapStateToMasterListItemProps = ( state: JsonFormsState, ownProps: OwnPropsOfMasterListItem ): StatePropsOfMasterItem => { - const { schema, path, index } = ownProps; - const firstPrimitiveProp = schema.properties - ? find(Object.keys(schema.properties), (propName) => { - const prop = schema.properties[propName]; - return ( - prop.type === 'string' || - prop.type === 'number' || - prop.type === 'integer' - ); - }) - : undefined; + const { schema, path, uischema, childLabelProp, index } = ownProps; const childPath = composePaths(path, `${index}`); - const childData = Resolve.data(getData(state), childPath); - const childLabel = firstPrimitiveProp ? childData[firstPrimitiveProp] : ''; + const childLabel = computeChildLabel( + getData(state), + childPath, + childLabelProp, + schema, + getSchema(state), + state.jsonforms.i18n.translate, + uischema + ); return { ...ownProps, @@ -725,6 +721,8 @@ export interface OwnPropsOfMasterListItem { path: string; enabled: boolean; schema: JsonSchema; + uischema: UISchemaElement; + childLabelProp?: string; handleSelect(index: number): () => void; removeItem(path: string, value: number): () => void; translations: ArrayTranslations; diff --git a/packages/material-renderers/src/additional/ListWithDetailMasterItem.tsx b/packages/material-renderers/src/additional/ListWithDetailMasterItem.tsx index 23b46689b..40458f16d 100644 --- a/packages/material-renderers/src/additional/ListWithDetailMasterItem.tsx +++ b/packages/material-renderers/src/additional/ListWithDetailMasterItem.tsx @@ -31,6 +31,7 @@ import { ListItemAvatar, ListItemSecondaryAction, ListItemText, + Tooltip, } from '@mui/material'; import { Delete as DeleteIcon } from '@mui/icons-material'; import React from 'react'; @@ -53,13 +54,19 @@ export const ListWithDetailMasterItem = ({ {enabled && ( - - - + + + + )} diff --git a/packages/material-renderers/src/additional/MaterialListWithDetailRenderer.tsx b/packages/material-renderers/src/additional/MaterialListWithDetailRenderer.tsx index 4f526e91d..9c4f61ae5 100644 --- a/packages/material-renderers/src/additional/MaterialListWithDetailRenderer.tsx +++ b/packages/material-renderers/src/additional/MaterialListWithDetailRenderer.tsx @@ -139,11 +139,13 @@ export const MaterialListWithDetailRenderer = ({ removeItem={handleRemoveItem} selected={selectedIndex === index} key={index} + uischema={foundUISchema} + childLabelProp={appliedUiSchemaOptions.elementLabelProp} translations={translations} /> )) ) : ( -

No data

+

{translations.noDataMessage}

)} diff --git a/packages/material-renderers/test/renderers/MaterialListWithDetailRenderer.test.tsx b/packages/material-renderers/test/renderers/MaterialListWithDetailRenderer.test.tsx index f55dad99d..ca57afe32 100644 --- a/packages/material-renderers/test/renderers/MaterialListWithDetailRenderer.test.tsx +++ b/packages/material-renderers/test/renderers/MaterialListWithDetailRenderer.test.tsx @@ -23,18 +23,25 @@ THE SOFTWARE. */ import './MatchMediaMock'; -import { ControlElement, JsonSchema7 } from '@jsonforms/core'; +import { + ArrayTranslationEnum, + ControlElement, + JsonSchema7, + Scoped, + UISchemaElement, +} from '@jsonforms/core'; import * as React from 'react'; -import { materialRenderers } from '../../src'; +import { ArrayLayoutToolbar, materialRenderers } from '../../src'; import MaterialListWithDetailRenderer, { materialListWithDetailTester, } from '../../src/additional/MaterialListWithDetailRenderer'; import Enzyme, { mount, ReactWrapper } from 'enzyme'; import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import { JsonFormsStateProvider } from '@jsonforms/react'; -import { ListItem } from '@mui/material'; -import { initCore } from './util'; +import { ListItem, Typography } from '@mui/material'; +import { initCore, testTranslator } from './util'; +import { checkTooltip, checkTooltipTranslation } from './tooltipChecker'; Enzyme.configure({ adapter: new Adapter() }); @@ -47,6 +54,23 @@ const data = [ message: 'Yolo', }, ]; + +const emptyData: any[] = []; + +const enumOrOneOfData = [ + { + message: 'El Barto was here', + messageType: 'MSG_TYPE_1', + }, + { + message: 'El Barto was not here', + messageType: 'MSG_TYPE_2', + }, + { + message: 'Yolo', + }, +]; + const schema: JsonSchema7 = { type: 'array', items: { @@ -69,6 +93,24 @@ const uischema: ControlElement = { scope: '#', }; +const uischemaListWithDetail: UISchemaElement & Scoped = { + type: 'ListWithDetail', + scope: '#', +}; + +const enumSchema: JsonSchema7 = { + type: 'array', + items: { + type: 'object', + properties: { + messageType: { + type: 'string', + enum: ['MSG_TYPE_1', 'MSG_TYPE_2'], + }, + }, + }, +}; + const nestedSchema: JsonSchema7 = { type: 'array', items: { @@ -346,4 +388,131 @@ describe('Material list with detail renderer', () => { const lis = wrapper.find(ListItem); expect(lis).toHaveLength(1); }); + + it('should render first simple property', () => { + const core = initCore(schema, uischema, data); + wrapper = mount( + + + + ); + + expect(wrapper.find(ListItem)).toHaveLength(2); + + expect(wrapper.find(ListItem).find(Typography).at(0).text()).toBe( + 'El Barto was here' + ); + expect(wrapper.find(ListItem).find(Typography).at(1).text()).toBe('Yolo'); + }); + + it('should render first simple enum property as translated child label', () => { + const core = initCore(enumSchema, uischema, enumOrOneOfData); + wrapper = mount( + + + + ); + + expect(wrapper.find(ListItem)).toHaveLength(3); + + expect(wrapper.find(ListItem).find(Typography).at(0).text()).toBe( + 'MSG_TYPE_1' + ); + expect(wrapper.find(ListItem).find(Typography).at(1).text()).toBe( + 'MSG_TYPE_2' + ); + expect(wrapper.find(ListItem).find(Typography).at(2).text()).toBe(''); + }); + + it('should have no data message when no translator set', () => { + const core = initCore(schema, uischema, emptyData); + wrapper = mount( + + + + ); + + const noDataLabel = wrapper.find('ul>p').text(); + expect(noDataLabel).toBe('No data'); + }); + + it('should have a translation for no data', () => { + const core = initCore(schema, uischema, emptyData); + wrapper = mount( + + + + ); + + const noDataLabel = wrapper.find('ul>p').text(); + expect(noDataLabel).toBe('translator.root.noDataMessage'); + }); + + it('should have a tooltip for add button', () => { + wrapper = checkTooltip( + schema, + uischemaListWithDetail, + wrapper, + (wrapper) => wrapper.find(ArrayLayoutToolbar), + ArrayTranslationEnum.addTooltip, + { + id: 'tooltip-add', + }, + data + ); + }); + it('should have a translatable tooltip for add button', () => { + wrapper = checkTooltipTranslation( + schema, + uischemaListWithDetail, + wrapper, + (wrapper) => wrapper.find(ArrayLayoutToolbar), + { + id: 'tooltip-add', + }, + data + ); + }); + + it('should have a tooltip for delete button', () => { + wrapper = checkTooltip( + schema, + uischemaListWithDetail, + wrapper, + (wrapper) => wrapper.find('Memo(ListWithDetailMasterItem)').at(0), + ArrayTranslationEnum.removeTooltip, + { + id: 'tooltip-remove', + }, + data + ); + }); + + it('should have a translatable tooltip for delete button', () => { + wrapper = checkTooltipTranslation( + schema, + uischemaListWithDetail, + wrapper, + (wrapper) => wrapper.find('Memo(ListWithDetailMasterItem)').at(0), + { + id: 'tooltip-remove', + }, + data + ); + }); }); diff --git a/packages/material-renderers/test/renderers/tooltipChecker.tsx b/packages/material-renderers/test/renderers/tooltipChecker.tsx index 7754133d7..19c660cc3 100644 --- a/packages/material-renderers/test/renderers/tooltipChecker.tsx +++ b/packages/material-renderers/test/renderers/tooltipChecker.tsx @@ -2,8 +2,8 @@ import { EnzymePropSelector, mount, ReactWrapper } from 'enzyme'; import { arrayDefaultTranslations, ArrayTranslationEnum, - ControlElement, JsonSchema, + UISchemaElement, } from '@jsonforms/core'; import { JsonForms } from '@jsonforms/react'; import { materialRenderers } from '../../src'; @@ -12,7 +12,7 @@ import * as React from 'react'; export const checkTooltip = ( schema: JsonSchema, - uiSchema: ControlElement, + uiSchema: UISchemaElement, wrapper: ReactWrapper, findTooltipWrapper: ( wrapper: ReactWrapper @@ -42,7 +42,7 @@ export const checkTooltip = ( export const checkTooltipTranslation = ( schema: JsonSchema, - uiSchema: ControlElement, + uiSchema: UISchemaElement, wrapper: ReactWrapper, findTooltipWrapper: ( wrapper: ReactWrapper