Skip to content

Commit e1724a8

Browse files
authored
feat: translate enums in React Material list with detail
1 parent a672127 commit e1724a8

File tree

6 files changed

+211
-30
lines changed

6 files changed

+211
-30
lines changed

packages/core/src/util/label.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,12 @@ export const computeChildLabel = (
132132
return '';
133133
}
134134

135-
const currentValue = get(childData, childLabelProp, '');
135+
const currentValue = get(childData, childLabelProp);
136+
137+
// in case there is no value, then we can't map it to an enum or oneOf
138+
if (currentValue === undefined) {
139+
return '';
140+
}
136141

137142
// check whether the value is part of a oneOf or enum and needs to be translated
138143
const childSchema = Resolve.schema(

packages/core/src/util/renderer.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
LabelElement,
3232
UISchemaElement,
3333
} from '../models';
34-
import find from 'lodash/find';
3534
import {
3635
getUISchemas,
3736
getAjv,
@@ -53,7 +52,7 @@ import type {
5352
} from '../reducers';
5453
import type { RankedTester } from '../testers';
5554
import { hasShowRule, isInherentlyEnabled, isVisible } from './runtime';
56-
import { createLabelDescriptionFrom } from './label';
55+
import { computeChildLabel, createLabelDescriptionFrom } from './label';
5756
import type { CombinatorKeyword } from './combinators';
5857
import { moveDown, moveUp } from './array';
5958
import type { AnyAction, Dispatch } from './type';
@@ -689,20 +688,17 @@ export const mapStateToMasterListItemProps = (
689688
state: JsonFormsState,
690689
ownProps: OwnPropsOfMasterListItem
691690
): StatePropsOfMasterItem => {
692-
const { schema, path, index } = ownProps;
693-
const firstPrimitiveProp = schema.properties
694-
? find(Object.keys(schema.properties), (propName) => {
695-
const prop = schema.properties[propName];
696-
return (
697-
prop.type === 'string' ||
698-
prop.type === 'number' ||
699-
prop.type === 'integer'
700-
);
701-
})
702-
: undefined;
691+
const { schema, path, uischema, childLabelProp, index } = ownProps;
703692
const childPath = composePaths(path, `${index}`);
704-
const childData = Resolve.data(getData(state), childPath);
705-
const childLabel = firstPrimitiveProp ? childData[firstPrimitiveProp] : '';
693+
const childLabel = computeChildLabel(
694+
getData(state),
695+
childPath,
696+
childLabelProp,
697+
schema,
698+
getSchema(state),
699+
state.jsonforms.i18n.translate,
700+
uischema
701+
);
706702

707703
return {
708704
...ownProps,
@@ -725,6 +721,8 @@ export interface OwnPropsOfMasterListItem {
725721
path: string;
726722
enabled: boolean;
727723
schema: JsonSchema;
724+
uischema: UISchemaElement;
725+
childLabelProp?: string;
728726
handleSelect(index: number): () => void;
729727
removeItem(path: string, value: number): () => void;
730728
translations: ArrayTranslations;

packages/material-renderers/src/additional/ListWithDetailMasterItem.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
ListItemAvatar,
3232
ListItemSecondaryAction,
3333
ListItemText,
34+
Tooltip,
3435
} from '@mui/material';
3536
import { Delete as DeleteIcon } from '@mui/icons-material';
3637
import React from 'react';
@@ -53,13 +54,19 @@ export const ListWithDetailMasterItem = ({
5354
<ListItemText primary={childLabel} />
5455
{enabled && (
5556
<ListItemSecondaryAction>
56-
<IconButton
57-
aria-label={translations.removeAriaLabel}
58-
onClick={removeItem(path, index)}
59-
size='large'
57+
<Tooltip
58+
id='tooltip-remove'
59+
title={translations.removeTooltip}
60+
placement='bottom'
6061
>
61-
<DeleteIcon />
62-
</IconButton>
62+
<IconButton
63+
aria-label={translations.removeAriaLabel}
64+
onClick={removeItem(path, index)}
65+
size='large'
66+
>
67+
<DeleteIcon />
68+
</IconButton>
69+
</Tooltip>
6370
</ListItemSecondaryAction>
6471
)}
6572
</ListItem>

packages/material-renderers/src/additional/MaterialListWithDetailRenderer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,13 @@ export const MaterialListWithDetailRenderer = ({
139139
removeItem={handleRemoveItem}
140140
selected={selectedIndex === index}
141141
key={index}
142+
uischema={foundUISchema}
143+
childLabelProp={appliedUiSchemaOptions.elementLabelProp}
142144
translations={translations}
143145
/>
144146
))
145147
) : (
146-
<p>No data</p>
148+
<p>{translations.noDataMessage}</p>
147149
)}
148150
</List>
149151
</Grid>

packages/material-renderers/test/renderers/MaterialListWithDetailRenderer.test.tsx

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,25 @@
2323
THE SOFTWARE.
2424
*/
2525
import './MatchMediaMock';
26-
import { ControlElement, JsonSchema7 } from '@jsonforms/core';
26+
import {
27+
ArrayTranslationEnum,
28+
ControlElement,
29+
JsonSchema7,
30+
Scoped,
31+
UISchemaElement,
32+
} from '@jsonforms/core';
2733
import * as React from 'react';
2834

29-
import { materialRenderers } from '../../src';
35+
import { ArrayLayoutToolbar, materialRenderers } from '../../src';
3036
import MaterialListWithDetailRenderer, {
3137
materialListWithDetailTester,
3238
} from '../../src/additional/MaterialListWithDetailRenderer';
3339
import Enzyme, { mount, ReactWrapper } from 'enzyme';
3440
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
3541
import { JsonFormsStateProvider } from '@jsonforms/react';
36-
import { ListItem } from '@mui/material';
37-
import { initCore } from './util';
42+
import { ListItem, Typography } from '@mui/material';
43+
import { initCore, testTranslator } from './util';
44+
import { checkTooltip, checkTooltipTranslation } from './tooltipChecker';
3845

3946
Enzyme.configure({ adapter: new Adapter() });
4047

@@ -47,6 +54,23 @@ const data = [
4754
message: 'Yolo',
4855
},
4956
];
57+
58+
const emptyData: any[] = [];
59+
60+
const enumOrOneOfData = [
61+
{
62+
message: 'El Barto was here',
63+
messageType: 'MSG_TYPE_1',
64+
},
65+
{
66+
message: 'El Barto was not here',
67+
messageType: 'MSG_TYPE_2',
68+
},
69+
{
70+
message: 'Yolo',
71+
},
72+
];
73+
5074
const schema: JsonSchema7 = {
5175
type: 'array',
5276
items: {
@@ -69,6 +93,24 @@ const uischema: ControlElement = {
6993
scope: '#',
7094
};
7195

96+
const uischemaListWithDetail: UISchemaElement & Scoped = {
97+
type: 'ListWithDetail',
98+
scope: '#',
99+
};
100+
101+
const enumSchema: JsonSchema7 = {
102+
type: 'array',
103+
items: {
104+
type: 'object',
105+
properties: {
106+
messageType: {
107+
type: 'string',
108+
enum: ['MSG_TYPE_1', 'MSG_TYPE_2'],
109+
},
110+
},
111+
},
112+
};
113+
72114
const nestedSchema: JsonSchema7 = {
73115
type: 'array',
74116
items: {
@@ -346,4 +388,131 @@ describe('Material list with detail renderer', () => {
346388
const lis = wrapper.find(ListItem);
347389
expect(lis).toHaveLength(1);
348390
});
391+
392+
it('should render first simple property', () => {
393+
const core = initCore(schema, uischema, data);
394+
wrapper = mount(
395+
<JsonFormsStateProvider
396+
initState={{ renderers: materialRenderers, core }}
397+
>
398+
<MaterialListWithDetailRenderer schema={schema} uischema={uischema} />
399+
</JsonFormsStateProvider>
400+
);
401+
402+
expect(wrapper.find(ListItem)).toHaveLength(2);
403+
404+
expect(wrapper.find(ListItem).find(Typography).at(0).text()).toBe(
405+
'El Barto was here'
406+
);
407+
expect(wrapper.find(ListItem).find(Typography).at(1).text()).toBe('Yolo');
408+
});
409+
410+
it('should render first simple enum property as translated child label', () => {
411+
const core = initCore(enumSchema, uischema, enumOrOneOfData);
412+
wrapper = mount(
413+
<JsonFormsStateProvider
414+
initState={{ renderers: materialRenderers, core, i18n: testTranslator }}
415+
>
416+
<MaterialListWithDetailRenderer
417+
schema={enumSchema}
418+
uischema={uischema}
419+
/>
420+
</JsonFormsStateProvider>
421+
);
422+
423+
expect(wrapper.find(ListItem)).toHaveLength(3);
424+
425+
expect(wrapper.find(ListItem).find(Typography).at(0).text()).toBe(
426+
'MSG_TYPE_1'
427+
);
428+
expect(wrapper.find(ListItem).find(Typography).at(1).text()).toBe(
429+
'MSG_TYPE_2'
430+
);
431+
expect(wrapper.find(ListItem).find(Typography).at(2).text()).toBe('');
432+
});
433+
434+
it('should have no data message when no translator set', () => {
435+
const core = initCore(schema, uischema, emptyData);
436+
wrapper = mount(
437+
<JsonFormsStateProvider
438+
initState={{ renderers: materialRenderers, core }}
439+
>
440+
<MaterialListWithDetailRenderer schema={schema} uischema={uischema} />
441+
</JsonFormsStateProvider>
442+
);
443+
444+
const noDataLabel = wrapper.find('ul>p').text();
445+
expect(noDataLabel).toBe('No data');
446+
});
447+
448+
it('should have a translation for no data', () => {
449+
const core = initCore(schema, uischema, emptyData);
450+
wrapper = mount(
451+
<JsonFormsStateProvider
452+
initState={{
453+
renderers: materialRenderers,
454+
core,
455+
i18n: { translate: testTranslator },
456+
}}
457+
>
458+
<MaterialListWithDetailRenderer schema={schema} uischema={uischema} />
459+
</JsonFormsStateProvider>
460+
);
461+
462+
const noDataLabel = wrapper.find('ul>p').text();
463+
expect(noDataLabel).toBe('translator.root.noDataMessage');
464+
});
465+
466+
it('should have a tooltip for add button', () => {
467+
wrapper = checkTooltip(
468+
schema,
469+
uischemaListWithDetail,
470+
wrapper,
471+
(wrapper) => wrapper.find(ArrayLayoutToolbar),
472+
ArrayTranslationEnum.addTooltip,
473+
{
474+
id: 'tooltip-add',
475+
},
476+
data
477+
);
478+
});
479+
it('should have a translatable tooltip for add button', () => {
480+
wrapper = checkTooltipTranslation(
481+
schema,
482+
uischemaListWithDetail,
483+
wrapper,
484+
(wrapper) => wrapper.find(ArrayLayoutToolbar),
485+
{
486+
id: 'tooltip-add',
487+
},
488+
data
489+
);
490+
});
491+
492+
it('should have a tooltip for delete button', () => {
493+
wrapper = checkTooltip(
494+
schema,
495+
uischemaListWithDetail,
496+
wrapper,
497+
(wrapper) => wrapper.find('Memo(ListWithDetailMasterItem)').at(0),
498+
ArrayTranslationEnum.removeTooltip,
499+
{
500+
id: 'tooltip-remove',
501+
},
502+
data
503+
);
504+
});
505+
506+
it('should have a translatable tooltip for delete button', () => {
507+
wrapper = checkTooltipTranslation(
508+
schema,
509+
uischemaListWithDetail,
510+
wrapper,
511+
(wrapper) => wrapper.find('Memo(ListWithDetailMasterItem)').at(0),
512+
{
513+
id: 'tooltip-remove',
514+
},
515+
data
516+
);
517+
});
349518
});

packages/material-renderers/test/renderers/tooltipChecker.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { EnzymePropSelector, mount, ReactWrapper } from 'enzyme';
22
import {
33
arrayDefaultTranslations,
44
ArrayTranslationEnum,
5-
ControlElement,
65
JsonSchema,
6+
UISchemaElement,
77
} from '@jsonforms/core';
88
import { JsonForms } from '@jsonforms/react';
99
import { materialRenderers } from '../../src';
@@ -12,7 +12,7 @@ import * as React from 'react';
1212

1313
export const checkTooltip = (
1414
schema: JsonSchema,
15-
uiSchema: ControlElement,
15+
uiSchema: UISchemaElement,
1616
wrapper: ReactWrapper<any, any>,
1717
findTooltipWrapper: (
1818
wrapper: ReactWrapper<any, any>
@@ -42,7 +42,7 @@ export const checkTooltip = (
4242

4343
export const checkTooltipTranslation = (
4444
schema: JsonSchema,
45-
uiSchema: ControlElement,
45+
uiSchema: UISchemaElement,
4646
wrapper: ReactWrapper<any, any>,
4747
findTooltipWrapper: (
4848
wrapper: ReactWrapper<any, any>

0 commit comments

Comments
 (0)