Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions Composer/packages/adaptive-form/src/components/FormRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { isPropertyHidden, resolvePropSchema } from '../utils';
import { SchemaField } from './SchemaField';

export interface FormRowProps extends Omit<FieldProps, 'onChange'> {
onChange: (field: string) => (data: any) => void;
onChange: (data: any) => void;
row: string | [string, string];
}

Expand All @@ -37,6 +37,18 @@ export const getRowProps = (rowProps: FormRowProps, field: string) => {

const newUiOptions = (uiOptions.properties?.[field] as UIOptions) ?? {};

const handleChange = (data: any) => {
const newData = { ...value };

if (typeof data === 'undefined' || (typeof data === 'string' && data.length === 0)) {
delete newData[field];
} else {
newData[field] = data;
}

onChange(newData);
};

return {
id: `${id}.${field}`,
schema: fieldSchema ?? {},
Expand All @@ -46,8 +58,8 @@ export const getRowProps = (rowProps: FormRowProps, field: string) => {
rawErrors: rawErrors?.[field],
required: required.includes(field),
uiOptions: newUiOptions,
value: value && value[field],
onChange: onChange(field),
value: !newUiOptions.additionalField && value ? value[field] : value,
onChange: !newUiOptions.additionalField ? handleChange : onChange,
depth,
definitions,
transparentBorder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import { FormRow, FormRowProps, getRowProps } from '../FormRow';

jest.mock('../SchemaField', () => ({ SchemaField: () => <div data-testid="SchemaField" /> }));

const fieldChangeMock = jest.fn();
const field: FormRowProps = {
onChange: jest.fn().mockReturnValue(fieldChangeMock),
onChange: jest.fn(),
row: '',
definitions: {},
depth: 0,
Expand Down Expand Up @@ -78,7 +77,7 @@ describe('getRowProps', () => {
});

it('binds the onChange to the field', () => {
expect(getRowProps(field, 'single').onChange).toEqual(fieldChangeMock);
expect(field.onChange).toHaveBeenCalledWith('single');
expect(getRowProps(field, 'single').onChange('test'));
expect(field.onChange).toHaveBeenCalledWith({ single: 'test' });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,24 @@ import React from 'react';
import { FieldProps } from '@bfc/extension-client';

import { CollapseField } from '../CollapseField';
import { getFieldSets } from '../../utils';
import { getFieldsets } from '../../utils';

import { ObjectField } from './ObjectField';

export const FieldSets: React.FC<FieldProps<object>> = (props) => {
const Fieldsets: React.FC<FieldProps<object>> = (props) => {
const { schema, uiOptions: baseUiOptions, value } = props;
const { fieldSets: _, ...uiOptions } = baseUiOptions;

const fieldSets = getFieldSets(schema, baseUiOptions, value);
const fieldsets = getFieldsets(schema, baseUiOptions, value);

return (
<React.Fragment>
{fieldSets.map(({ fields, schema, ...rest }, key) => (
<CollapseField key={key} {...rest}>
{fieldsets.map(({ schema, uiOptions, title, defaultExpanded }, key) => (
<CollapseField key={key} defaultExpanded={defaultExpanded} title={title}>
<ObjectField {...props} schema={schema} uiOptions={uiOptions} />
</CollapseField>
))}
</React.Fragment>
);
};

export default FieldSets;
export { Fieldsets };
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,17 @@
import React from 'react';
import { FieldProps } from '@bfc/extension-client';

import { getOrderedProperties } from '../../utils';
import { getOrderedProperties, getSchemaWithAdditionalFields } from '../../utils';
import { FormRow } from '../FormRow';

const ObjectField: React.FC<FieldProps<object>> = function ObjectField(props) {
const { schema, uiOptions, depth, value, label, ...rest } = props;
const { schema: baseSchema, uiOptions, depth, value, label, ...rest } = props;

if (!schema) {
if (!baseSchema) {
return null;
}

const newDepth = depth + 1;

const handleChange = (field: string) => (data: any) => {
const newData = { ...value };

if (typeof data === 'undefined' || (typeof data === 'string' && data.length === 0)) {
delete newData[field];
} else {
newData[field] = data;
}

props.onChange(newData);
};

const schema = getSchemaWithAdditionalFields(baseSchema, uiOptions);
const orderedProperties = getOrderedProperties(schema, uiOptions, value);

return (
Expand All @@ -35,12 +22,11 @@ const ObjectField: React.FC<FieldProps<object>> = function ObjectField(props) {
<FormRow
key={`${props.id}.${typeof row === 'string' ? row : row.join('_')}`}
{...rest}
depth={newDepth}
depth={depth + 1}
row={row}
schema={schema}
uiOptions={uiOptions}
value={value}
onChange={handleChange}
/>
))}
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from 'react';
import { render, fireEvent, act } from '@bfc/test-utils';
import assign from 'lodash/assign';

import { FieldSets } from '../FieldSets';
import { Fieldsets } from '../FieldSets';

import { fieldProps } from './testUtils';

Expand All @@ -19,14 +19,14 @@ const schema = {

function renderSubject(overrides = {}) {
const props = assign({}, fieldProps(), overrides);
return render(<FieldSets {...props} />);
return render(<Fieldsets {...props} />);
}

describe('<FieldSets />', () => {
describe('<Fieldsets />', () => {
it('renders an object with two field sets', async () => {
const onChange = jest.fn();
const uiOptions = {
fieldSets: [
fieldsets: [
{
title: 'set 1',
fields: ['name'],
Expand Down Expand Up @@ -56,4 +56,28 @@ describe('<FieldSets />', () => {

expect(onChange).toHaveBeenLastCalledWith({ city: 'Seattle' });
});

it('renders additional fields', async () => {
const uiOptions = {
properties: {
additionalField: {
additionalField: true,
field: () => <div>Additional Field</div>,
},
},
fieldsets: [
{
title: 'set 1',
fields: ['name'],
},
{
title: 'set 2',
},
],
};

const { findByText } = renderSubject({ schema, uiOptions, value: {} });

await findByText('Additional Field');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jest.mock('../../FormRow', () => ({
<input
onChange={(e) => {
try {
onChange(row)(JSON.parse(e.target.value));
onChange(JSON.parse(e.target.value));
} catch {
onChange(row)(e.target.value);
onChange(e.target.value);
}
}}
/>
Expand Down Expand Up @@ -66,6 +66,6 @@ describe.only('<ObjectField />', () => {
const { container } = renderSubject({ onChange, schema, value });
const input = container.querySelectorAll('input')[0];
fireEvent.change(input, { target: { value: 'new name' } });
expect(onChange).toHaveBeenCalledWith({ name: 'new name', age: 21 });
expect(onChange).toHaveBeenCalledWith('new name');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { JSONSchema7 } from 'json-schema';

import { getFieldSets } from '../getFieldSets';
import { getFieldsets } from '../getFieldsets';

const schema = {
properties: {
Expand All @@ -18,13 +18,13 @@ const schema = {
},
} as JSONSchema7;

describe('getFieldSets', () => {
describe('getFieldsets', () => {
it('should return a single field set containing all the properties', () => {
const uiOptions = {
fieldSets: [{ title: 'set1' }],
const uiOptions: any = {
fieldsets: [{ title: 'set1' }],
};

const result = getFieldSets(schema, uiOptions, {});
const result = getFieldsets(schema, uiOptions, {});

expect(result).toEqual([
expect.objectContaining({
Expand All @@ -46,8 +46,8 @@ describe('getFieldSets', () => {
});

it('should return two sets', () => {
const uiOptions = {
fieldSets: [
const uiOptions: any = {
fieldsets: [
{
title: 'set1',
fields: ['two', 'four', 'six'],
Expand All @@ -59,7 +59,7 @@ describe('getFieldSets', () => {
],
};

const result = getFieldSets(schema, uiOptions, {});
const result = getFieldsets(schema, uiOptions, {});

expect(result).toEqual([
expect.objectContaining({
Expand Down Expand Up @@ -88,33 +88,90 @@ describe('getFieldSets', () => {
]);
});

it('should include additional fields', () => {
const uiOptions: any = {
fieldsets: [
{
title: 'set1',
fields: ['two', 'four', 'six'],
},
{
title: 'set2',
fields: ['*', 'additionalField'],
},
],
properties: {
additionalField: {
additionalField: true,
field: 'field',
},
},
};

const result = getFieldsets(schema, uiOptions, {});

expect(result).toEqual([
expect.objectContaining({
fields: ['two', 'four', 'six'],
title: 'set1',
schema: {
properties: {
two: { type: 'string' },
four: { type: 'object' },
six: { type: 'object' },
},
},
uiOptions: {
order: ['two', 'four', 'six'],
properties: {},
},
}),
expect.objectContaining({
fields: ['one', 'three', 'five', 'seven', 'additionalField'],
title: 'set2',
schema: {
properties: {
one: { type: 'string' },
three: { type: 'number' },
five: { type: 'object' },
seven: { type: 'boolean' },
},
},
uiOptions: {
order: ['one', 'three', 'five', 'seven', 'additionalField'],
properties: { additionalField: { additionalField: true, field: 'field' } },
},
}),
]);
});

it('should throw an error for multiple wildcards', () => {
const uiOptions = {
fieldSets: [{ title: 'set1', fields: ['two', '*', 'six'] }, { title: 'set2' }],
const uiOptions: any = {
fieldsets: [{ title: 'set1', fields: ['two', '*', 'six'] }, { title: 'set2' }],
};

expect(() => getFieldSets(schema, uiOptions, {})).toThrow('multiple wildcards');
expect(() => getFieldsets(schema, uiOptions, {})).toThrow('multiple wildcards');
});

it('should throw an error for missing fields', () => {
const uiOptions = {
fieldSets: [
fieldsets: [
{ title: 'set1', fields: ['two', 'four', 'six'] },
{ title: 'set2', fields: ['one'] },
],
};

expect(() => getFieldSets(schema, uiOptions, {})).toThrow('missing fields');
expect(() => getFieldsets(schema, uiOptions, {})).toThrow('missing fields');
});

it('should throw an error for duplicate fields', () => {
const uiOptions = {
fieldSets: [
fieldsets: [
{ title: 'set1', fields: ['two', 'four', 'six'] },
{ title: 'set2', fields: ['two', '*'] },
],
};

expect(() => getFieldSets(schema, uiOptions, {})).toThrow('duplicate fields');
expect(() => getFieldsets(schema, uiOptions, {})).toThrow('duplicate fields');
});
});
Loading