@@ -23,7 +23,7 @@ import {
2323import validator from '@rjsf/validator-ajv8' ;
2424import { render , screen , within } from '@testing-library/react' ;
2525import userEvent from '@testing-library/user-event' ;
26- import { get , has , omit , pick } from 'lodash' ;
26+ import { get , has , isEmpty , omit , pick } from 'lodash' ;
2727
2828import LayoutGridField , {
2929 GridType ,
@@ -593,6 +593,44 @@ const arraySchema: RJSFSchema = {
593593const outerArraySchema = arraySchema ?. properties ?. example as RJSFSchema ;
594594const innerArraySchema = outerArraySchema ?. items as RJSFSchema ;
595595
596+ const nestedSchema : RJSFSchema = {
597+ type : 'object' ,
598+ properties : {
599+ listOfStrings : {
600+ type : 'array' ,
601+ title : 'A list of strings' ,
602+ items : {
603+ type : 'string' ,
604+ default : 'bazinga' ,
605+ } ,
606+ } ,
607+ mapOfStrings : {
608+ type : 'object' ,
609+ title : 'A map of strings' ,
610+ additionalProperties : {
611+ type : 'string' ,
612+ default : 'bazinga' ,
613+ } ,
614+ } ,
615+ } ,
616+ } ;
617+
618+ const nestedUiSchema : UiSchema = {
619+ 'ui:field' : 'LayoutGridField' ,
620+ 'ui:layoutGrid' : {
621+ 'ui:row' : {
622+ children : [
623+ {
624+ 'ui:columns' : {
625+ span : 6 ,
626+ children : [ 'listOfStrings' , 'mapOfStrings' ] ,
627+ } ,
628+ } ,
629+ ] ,
630+ } ,
631+ } ,
632+ } ;
633+
596634const ERRORS = [ 'error' ] ;
597635const EXTRA_ERROR = new ErrorSchemaBuilder ( ) . addErrors ( ERRORS ) . ErrorSchema ;
598636const DEFAULT_ID = 'test-id' ;
@@ -672,6 +710,7 @@ const gridFormSchemaRegistry = getTestRegistry(GRID_FORM_SCHEMA, REGISTRY_FIELDS
672710const sampleSchemaRegistry = getTestRegistry ( SAMPLE_SCHEMA , REGISTRY_FIELDS , { } , { } , REGISTRY_FORM_CONTEXT ) ;
673711const readonlySchemaRegistry = getTestRegistry ( readonlySchema , REGISTRY_FIELDS , { } , { } , REGISTRY_FORM_CONTEXT ) ;
674712const arraySchemaRegistry = getTestRegistry ( arraySchema , REGISTRY_FIELDS , { } , { } , REGISTRY_FORM_CONTEXT ) ;
713+ const nestedSchemaRegistry = getTestRegistry ( nestedSchema , REGISTRY_FIELDS , { } , { } , REGISTRY_FORM_CONTEXT ) ;
675714const GRID_FORM_ID_SCHEMA = gridFormSchemaRegistry . schemaUtils . toIdSchema ( GRID_FORM_SCHEMA ) ;
676715const SAMPLE_SCHEMA_ID_SCHEMA = sampleSchemaRegistry . schemaUtils . toIdSchema ( SAMPLE_SCHEMA ) ;
677716const READONLY_ID_SCHEMA = readonlySchemaRegistry . schemaUtils . toIdSchema ( readonlySchema ) ;
@@ -713,6 +752,10 @@ function getExpectedPropsForField(
713752 required = result ?. required ?. includes ( name ) || false ;
714753 return schema1 ;
715754 } , props . schema ) ;
755+ // Null out nested properties that can show up when additionalProperties is specified
756+ if ( ! isEmpty ( schema ?. properties ) ) {
757+ schema . properties = { } ;
758+ }
716759 // Get the readonly options from the schema, if any
717760 const readonly = get ( schema , 'readOnly' ) ;
718761 // Get the options from the schema's oneOf, if any
@@ -1501,6 +1544,60 @@ describe('LayoutGridField', () => {
15011544 await userEvent . tab ( ) ;
15021545 expect ( props . onBlur ) . toHaveBeenCalledWith ( fieldId , 'foo' ) ;
15031546 } ) ;
1547+ test ( 'renderField via name explicit layoutGridSchema, nested array' , async ( ) => {
1548+ const fieldName = 'listOfStrings' ;
1549+ const props = getProps ( {
1550+ schema : nestedSchema ,
1551+ uiSchema : nestedUiSchema ,
1552+ layoutGridSchema : fieldName ,
1553+ idSeparator : '.' ,
1554+ registry : nestedSchemaRegistry ,
1555+ } ) ;
1556+ const fieldId = get ( props . idSchema , [ fieldName , ID_KEY ] ) ;
1557+ render ( < LayoutGridField { ...props } /> ) ;
1558+ // Renders a field
1559+ const field = screen . getByTestId ( LayoutGridField . TEST_IDS . field ) ;
1560+ expect ( field ) . toHaveTextContent ( stringifyProps ( getExpectedPropsForField ( props , fieldName ) ) ) ;
1561+ // Test onChange, onFocus, onBlur
1562+ const input = within ( field ) . getByRole ( 'textbox' ) ;
1563+ // Click on the input to cause the focus
1564+ await userEvent . click ( input ) ;
1565+ expect ( props . onFocus ) . toHaveBeenCalledWith ( fieldId , '' ) ;
1566+ // Type to trigger the onChange
1567+ await userEvent . type ( input , 'foo' ) ;
1568+ // Due to the selection of schema type = `array` the path is appended to the fieldName, duplicating it
1569+ expect ( props . onChange ) . toHaveBeenCalledWith ( 'foo' , [ fieldName , fieldName ] , props . errorSchema , fieldId ) ;
1570+ // Tab out of the input field to cause the blur
1571+ await userEvent . tab ( ) ;
1572+ expect ( props . onBlur ) . toHaveBeenCalledWith ( fieldId , 'foo' ) ;
1573+ } ) ;
1574+ test ( 'renderField via name explicit layoutGridSchema, nested object' , async ( ) => {
1575+ const fieldName = 'mapOfStrings' ;
1576+ const props = getProps ( {
1577+ schema : nestedSchema ,
1578+ uiSchema : nestedUiSchema ,
1579+ layoutGridSchema : fieldName ,
1580+ idSeparator : '.' ,
1581+ registry : nestedSchemaRegistry ,
1582+ } ) ;
1583+ const fieldId = get ( props . idSchema , [ fieldName , ID_KEY ] ) ;
1584+ render ( < LayoutGridField { ...props } /> ) ;
1585+ // Renders a field
1586+ const field = screen . getByTestId ( LayoutGridField . TEST_IDS . field ) ;
1587+ expect ( field ) . toHaveTextContent ( stringifyProps ( getExpectedPropsForField ( props , fieldName ) ) ) ;
1588+ // Test onChange, onFocus, onBlur
1589+ const input = within ( field ) . getByRole ( 'textbox' ) ;
1590+ // Click on the input to cause the focus
1591+ await userEvent . click ( input ) ;
1592+ expect ( props . onFocus ) . toHaveBeenCalledWith ( fieldId , '' ) ;
1593+ // Type to trigger the onChange
1594+ await userEvent . type ( input , 'foo' ) ;
1595+ // Due to the selection of schema type = `object` the path is appended to the fieldName, duplicating it
1596+ expect ( props . onChange ) . toHaveBeenCalledWith ( 'foo' , [ fieldName , fieldName ] , props . errorSchema , fieldId ) ;
1597+ // Tab out of the input field to cause the blur
1598+ await userEvent . tab ( ) ;
1599+ expect ( props . onBlur ) . toHaveBeenCalledWith ( fieldId , 'foo' ) ;
1600+ } ) ;
15041601 test ( 'renderField via object explicit layoutGridSchema, otherProps' , ( ) => {
15051602 const fieldName = 'employment' ;
15061603 const globalUiOptions = { propToApplyToAllFields : 'foobar' } ;
0 commit comments