Skip to content

Commit

Permalink
Merge pull request #57 from open-formulieren/feature/56-radio-compone…
Browse files Browse the repository at this point in the history
…nt-edit-form

Implement radio component edit form
  • Loading branch information
sergei-maertens authored Nov 21, 2023
2 parents 1ada106 + 11412e2 commit 582a639
Show file tree
Hide file tree
Showing 12 changed files with 786 additions and 0 deletions.
202 changes: 202 additions & 0 deletions src/components/ComponentConfiguration.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1109,3 +1109,205 @@ export const SelectBoxes: Story = {
});
},
};

export const Radio: Story = {
render: Template,
name: 'type: radio',

args: {
component: {
id: 'wqimsadk',
type: 'radio',
key: 'radio',
label: 'A radio field',
openForms: {
dataSrc: 'manual',
translations: {},
},
values: [],
defaultValue: '',
},

builderInfo: {
title: 'Radio',
icon: 'dot-circle-o',
group: 'basic',
weight: 100,
schema: {},
},
},

play: async ({canvasElement, step, args}) => {
const canvas = within(canvasElement);
const editForm = within(canvas.getByTestId('componentEditForm'));
const preview = within(canvas.getByTestId('componentPreview'));

await expect(canvas.getByLabelText('Label')).toHaveValue('A radio field');
await waitFor(async () => {
await expect(canvas.getByLabelText('Property Name')).toHaveValue('aRadioField');
});
await expect(canvas.getByLabelText('Description')).toHaveValue('');
await expect(canvas.getByLabelText('Tooltip')).toHaveValue('');
await expect(canvas.getByLabelText('Show in summary')).toBeChecked();
await expect(canvas.getByLabelText('Show in email')).not.toBeChecked();
await expect(canvas.getByLabelText('Show in PDF')).toBeChecked();

// ensure that changing fields in the edit form properly update the preview

await userEvent.clear(canvas.getByLabelText('Label'));
await userEvent.type(canvas.getByLabelText('Label'), 'Updated preview label');
expect(await preview.findByText('Updated preview label'));

const previewInput = preview.getByRole('radio');
await expect(previewInput).toBeChecked(); // matches '' value

// Ensure that the manually entered key is kept instead of derived from the label,
// even when key/label components are not mounted.
const keyInput = canvas.getByLabelText('Property Name');
// fireEvent is deliberate, as userEvent.clear + userEvent.type briefly makes the field
// not have any value, which triggers the generate-key-from-label behaviour.
fireEvent.change(keyInput, {target: {value: 'customKey'}});
await userEvent.click(canvas.getByRole('tab', {name: 'Basic'}));
await userEvent.clear(canvas.getByLabelText('Label'));
await userEvent.type(canvas.getByLabelText('Label'), 'Other label', {delay: 50});
await expect(canvas.getByLabelText('Property Name')).toHaveDisplayValue('customKey');

await step('Set up manual options', async () => {
// enter some possible options
const firstOptionLabelInput = canvas.getByLabelText('Option label');
expect(firstOptionLabelInput).toHaveDisplayValue('');
await userEvent.type(firstOptionLabelInput, 'Option label 1');
const firstOptionValue = canvas.getByLabelText('Option value');
await waitFor(() => expect(firstOptionValue).toHaveDisplayValue('optionLabel1'));

// add a second option
await userEvent.click(canvas.getByRole('button', {name: 'Add another'}));
const optionLabels = canvas.queryAllByLabelText('Option label');
const optionValues = canvas.queryAllByLabelText('Option value');
expect(optionLabels).toHaveLength(2);
expect(optionValues).toHaveLength(2);
await userEvent.type(optionValues[1], 'manualValue');
await userEvent.type(optionLabels[1], 'Second option');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));
expect(args.onSubmit).toHaveBeenCalledWith({
id: 'wqimsadk',
type: 'radio',
// basic tab
label: 'Other label',
key: 'customKey',
description: '',
tooltip: '',
showInSummary: true,
showInEmail: false,
showInPDF: true,
hidden: false,
clearOnHide: true,
isSensitiveData: false,
openForms: {
dataSrc: 'manual',
translations: {},
},
values: [
{
value: 'optionLabel1',
label: 'Option label 1',
},
{
value: 'manualValue',
label: 'Second option',
openForms: {translations: {}},
},
],
defaultValue: '',
// Advanced tab
conditional: {
show: undefined,
when: '',
eq: '',
},
// Validation tab
validate: {
required: false,
plugins: [],
},
translatedErrors: {
nl: {required: ''},
},
// registration tab
registration: {
attribute: '',
},
});
// @ts-expect-error
args.onSubmit.mockClear();
});

await step('Option labels are translatable', async () => {
await userEvent.click(canvas.getByRole('tab', {name: 'Translations'}));

// check that the option labels are in the translations table
expect(await editForm.findByText('Option label 1')).toBeVisible();
expect(await editForm.findByText('Second option')).toBeVisible();
});

await step('Set up itemsExpression for options', async () => {
await userEvent.click(canvas.getByRole('tab', {name: 'Basic'}));

canvas.getByLabelText('Data source').focus();
await userEvent.keyboard('[ArrowDown]');
await userEvent.click(await canvas.findByText('From variable'));
const itemsExpressionInput = canvas.getByLabelText('Items expression');
await userEvent.clear(itemsExpressionInput);
// { needs to be escaped: https://github.com/testing-library/user-event/issues/584
const expression = '{"var": "someVar"}'.replace(/[{[]/g, '$&$&');
await userEvent.type(itemsExpressionInput, expression);

await expect(editForm.queryByLabelText('Default value')).toBeNull();
await expect(preview.getByRole('radio', {name: /Options from expression:/})).toBeVisible();

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));
expect(args.onSubmit).toHaveBeenCalledWith({
id: 'wqimsadk',
type: 'radio',
// basic tab
label: 'Other label',
key: 'customKey',
description: '',
tooltip: '',
showInSummary: true,
showInEmail: false,
showInPDF: true,
hidden: false,
clearOnHide: true,
isSensitiveData: false,
openForms: {
dataSrc: 'variable',
itemsExpression: {var: 'someVar'},
translations: {},
},
defaultValue: '',
// Advanced tab
conditional: {
show: undefined,
when: '',
eq: '',
},
// Validation tab
validate: {
required: false,
plugins: [],
},
translatedErrors: {
nl: {required: ''},
},
// registration tab
registration: {
attribute: '',
},
});
// @ts-expect-error
args.onSubmit.mockClear();
});
},
};
69 changes: 69 additions & 0 deletions src/components/ComponentPreview.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -684,3 +684,72 @@ export const SelectBoxesVariable: Story = {
},
},
};

export const Radio: Story = {
name: 'Radio manual values',
render: Template,

args: {
component: {
type: 'radio',
id: 'radio',
key: 'radioPreview',
label: 'Radio preview',
description: 'A preview of the radio Formio component',
openForms: {
dataSrc: 'manual',
translations: {},
},
values: [
{
value: 'option1',
label: 'Option 1',
},
{
value: 'option2',
label: 'Option 2',
},
],
},
},

play: async ({canvasElement, args}) => {
const canvas = within(canvasElement);

// check that the user-controlled content is visible
await canvas.findByText('Radio preview');
await canvas.findByText('A preview of the radio Formio component');

// check that the input name is set correctly
const firstOptionInput = canvas.getByLabelText<HTMLInputElement>('Option 1');
// @ts-ignore
await expect(firstOptionInput.getAttribute('name').startsWith(args.component.key)).toBe(true);

// check the toggle state of a checkbox
await expect(firstOptionInput).not.toBeChecked();
// https://github.com/testing-library/user-event/issues/1149 applies to radio and
// checkbox inputs
fireEvent.click(canvas.getByText('Option 1'));
await expect(firstOptionInput).toBeChecked();
},
};

export const RadioVariable: Story = {
name: 'Radio variable for values',
render: Template,

args: {
component: {
type: 'radio',
id: 'radio',
key: 'radioPreview',
label: 'Radio preview',
description: 'A preview of the radio Formio component',
openForms: {
dataSrc: 'variable',
itemsExpression: {var: 'foo'},
translations: {},
},
},
},
};
1 change: 1 addition & 0 deletions src/components/formio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {default as TimeField} from './timefield';
export {default as Panel} from './panel';
export {default as Select} from './select';
export {default as SelectBoxes} from './selectboxes';
export {default as Radio} from './radio';
export {default as NumberField} from './number';
export {default as TextArea} from './textarea';
export * from './datagrid';
Expand Down
87 changes: 87 additions & 0 deletions src/components/formio/radio.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {expect} from '@storybook/jest';
import {Meta, StoryObj} from '@storybook/react';
import {within} from '@storybook/testing-library';

import {withFormik} from '@/sb-decorators';

import Radio from './radio';

export default {
title: 'Formio/Components/Radio',
component: Radio,
decorators: [withFormik],
parameters: {
modal: {noModal: true},
formik: {initialValues: {'my-radio': ''}},
},
args: {
name: 'my-radio',
label: '',
tooltip: '',
options: [
{value: 'a', label: 'A'},
{value: 'b', label: 'B'},
],
},
} as Meta<typeof Radio>;

type Story = StoryObj<typeof Radio>;

export const Default: Story = {
args: {
label: 'Labeled radio',
},
};

export const WithInitialChecked: Story = {
parameters: {
formik: {
initialValues: {
'my-radio': 'b',
},
},
},
args: {
label: 'Labeled radio',
},

play: async ({canvasElement}) => {
const canvas = within(canvasElement);

await expect(canvas.getByLabelText('A')).not.toBeChecked();
await expect(canvas.getByLabelText('B')).toBeChecked();
},
};

export const WithToolTip: Story = {
args: {
label: 'With tooltip',
tooltip: 'Hiya!',
},
};

export const WithDescription: Story = {
args: {
label: 'With description',
description: 'A description',
},
};

export const WithErrors: Story = {
args: {
label: 'With errors',
},

parameters: {
formik: {
initialValues: {'my-radio': ''},
initialErrors: {'my-radio': 'Example error', 'other-field': 'Other error'},
},
},

play: async ({canvasElement}) => {
const canvas = within(canvasElement);
await expect(canvas.queryByText('Other error')).not.toBeInTheDocument();
await expect(canvas.queryByText('Example error')).toBeInTheDocument();
},
};
Loading

0 comments on commit 582a639

Please sign in to comment.