feat(form): readOnly flag (#166)

* feat: add readOnly flag on form

* fix: resolve PR coments

* fix: add flag disabled to fields that dont have effect on readonly attr
This commit is contained in:
Allan Siqueira 2022-10-31 13:27:43 -03:00 committed by GitHub
parent 640e945959
commit f502e6ca24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 2334 additions and 5635 deletions

7810
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -14,9 +14,10 @@ export interface Props {
control: FormControl; control: FormControl;
showValidationHints: boolean; showValidationHints: boolean;
showErrors?: boolean; // feature flag for showing validation errors showErrors?: boolean; // feature flag for showing validation errors
readOnly?: boolean;
} }
const { control, showValidationHints, showErrors = false } = Astro.props; const { control, showValidationHints, showErrors = false, readOnly = false } = Astro.props;
const hasErrors: boolean | null = !!control.errors?.length; const hasErrors: boolean | null = !!control.errors?.length;
--- ---
@ -25,12 +26,12 @@ const hasErrors: boolean | null = !!control.errors?.length;
<Label control={control} showValidationHints={showValidationHints}> <Label control={control} showValidationHints={showValidationHints}>
{ {
control.type === 'radio' ? ( control.type === 'radio' ? (
<RadioGroup control={control as Radio} /> <RadioGroup control={control as Radio} readOnly={readOnly} />
) : ) :
control.type === 'dropdown' ? ( control.type === 'dropdown' ? (
<DropdownControl control={control as Dropdown} /> <DropdownControl control={control as Dropdown} readOnly={readOnly} />
) : ( ) : (
<Input control={control} /> <Input control={control} readOnly={readOnly} />
) )
} }
{showErrors && <Errors errors={control.errors} />} {showErrors && <Errors errors={control.errors} />}

View file

@ -5,16 +5,17 @@ import Field from './Field.astro';
export interface Props { export interface Props {
group: FormGroup; group: FormGroup;
showValidationHints: boolean; showValidationHints: boolean;
readOnly?: boolean;
} }
const { group, showValidationHints } = Astro.props; const { group, showValidationHints, readOnly = false } = Astro.props;
--- ---
<fieldset id={group.name} name={group.name}> <fieldset id={group.name} name={group.name}>
{group.name && <legend>{group.name}</legend>} {group.name && <legend>{group.name}</legend>}
{ {
group?.controls?.map((control) => ( group?.controls?.map((control) => (
<Field showValidationHints={showValidationHints} control={control} /> <Field showValidationHints={showValidationHints} control={control} readOnly={readOnly} />
)) ))
} }
</fieldset> </fieldset>

View file

@ -9,9 +9,10 @@ export interface Props {
submitControl?: Submit; submitControl?: Submit;
theme?: 'light' | 'dark'; theme?: 'light' | 'dark';
showValidationHints?: boolean; showValidationHints?: boolean;
readOnly?: boolean;
} }
const { submitControl, formGroups = [], theme, showValidationHints = false } = Astro.props; const { submitControl, formGroups = [], theme, showValidationHints = false, readOnly = false } = Astro.props;
const formTheme = theme ?? 'light'; const formTheme = theme ?? 'light';
const formName = Array.isArray(formGroups) ? null : formGroups?.name || null; const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
@ -26,10 +27,10 @@ const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
{ {
Array.isArray(formGroups) Array.isArray(formGroups)
? formGroups?.map((group) => ( ? formGroups?.map((group) => (
<FieldSet showValidationHints={showValidationHints} group={group} /> <FieldSet showValidationHints={showValidationHints} group={group} readOnly={readOnly}/>
)) ))
: formGroups?.controls.map((control) => ( : formGroups?.controls.map((control) => (
<Field showValidationHints={showValidationHints} control={control} /> <Field showValidationHints={showValidationHints} control={control} readOnly={readOnly}/>
)) ))
} }
{ {

View file

@ -6,9 +6,10 @@ import type { Dropdown, ControlOption } from '@astro-reactive/common';
export interface Props { export interface Props {
control: Dropdown; control: Dropdown;
readOnly?: boolean;
} }
const { control } = Astro.props; const { control, readOnly } = Astro.props;
const options = control.options.map((option: string | ControlOption) => { const options = control.options.map((option: string | ControlOption) => {
if (typeof option === 'string') { if (typeof option === 'string') {
@ -21,7 +22,11 @@ const options = control.options.map((option: string | ControlOption) => {
}); });
--- ---
<select name={control.name} id={control.name}> <select
name={control.name}
id={control.name}
disabled={readOnly || null}
>
{ {
control?.placeholder && ( control?.placeholder && (
<option value="" disabled selected={!control?.value}> <option value="" disabled selected={!control?.value}>

View file

@ -7,9 +7,10 @@ import type { FormControl } from '../../core/form-control';
export interface Props { export interface Props {
control: FormControl; control: FormControl;
readOnly?: boolean;
} }
const { control } = Astro.props; const { control, readOnly } = Astro.props;
const { validators = [] } = control; const { validators = [] } = control;
@ -37,5 +38,7 @@ const validatorAttributes: Record<string, string> = validators?.reduce((prev, va
data-label={control.label} data-label={control.label}
data-label-position={control.labelPosition} data-label-position={control.labelPosition}
data-validator-haserrors={hasErrors.toString()} data-validator-haserrors={hasErrors.toString()}
readonly={readOnly || null}
disabled={(readOnly || null) && control.type === 'checkbox'}
{...validatorAttributes} {...validatorAttributes}
/> />

View file

@ -6,9 +6,10 @@ import type { Radio, ControlOption } from '@astro-reactive/common';
export interface Props { export interface Props {
control: Radio; control: Radio;
readOnly?: boolean;
} }
const { control } = Astro.props; const { control, readOnly = false } = Astro.props;
const options = control.options.map((option: string | ControlOption) => { const options = control.options.map((option: string | ControlOption) => {
if (typeof option === 'string') { if (typeof option === 'string') {
@ -29,6 +30,8 @@ const options = control.options.map((option: string | ControlOption) => {
name={control.name} name={control.name}
value={option.value} value={option.value}
checked={option.value === control.value} checked={option.value === control.value}
readonly={readOnly || null}
disabled={readOnly || null}
/>{' '} />{' '}
{option.label} {option.label}
</div> </div>

View file

@ -149,4 +149,27 @@ describe('Dropdown.astro test', () => {
// assert // assert
expect(actualResult).to.contain(expectedResult); expect(actualResult).to.contain(expectedResult);
}); });
it('Should render disabled attribute if the prop readOnly is passed as true', async () => {
// arrange
const expectedOptions = 1;
const element = /disabled/g;
const props = {
control: {
label: 'FAKE LABEL',
name: 'FAKE NAME',
type: 'dropdown',
options: ['one', 'two', 'three'],
},
readOnly: true,
};
// act
component = await getComponentOutput('./components/controls/Dropdown.astro', props);
const actualResult = cleanString(component.html);
const matches = actualResult.match(element) || [];
// assert
expect(matches.length).to.equal(expectedOptions);
});
}); });

View file

@ -79,4 +79,27 @@ describe('Field.astro test', () => {
// assert // assert
expect(actualResult).to.contain(expectedResult); expect(actualResult).to.contain(expectedResult);
}); });
it('Should render field with readOnly attribute when readOnly is true', async () => {
// arrange
const expectedLabel = 'TestLabel';
const expectedAttribute = 'readonly';
const props = {
control: {
label: expectedLabel,
name: 'username',
labelPosition: 'left',
validators: ['validator-required'],
},
readOnly: true,
};
// act
component = await getComponentOutput('./components/Field.astro', props);
const actualResult = cleanString(component.html);
// assert
expect(actualResult).to.contain(expectedLabel);
expect(actualResult).to.contain(expectedAttribute);
});
}); });

View file

@ -85,6 +85,29 @@ describe('Form.astro test', () => {
const actualResult = cleanString(component.html); const actualResult = cleanString(component.html);
const matches = actualResult.match(element) || []; const matches = actualResult.match(element) || [];
// assert
expect(matches.length).to.equal(expectedCount);
});
it('Should render readOnly fields if the flag is passed as true', async () => {
// arrange
const expectedCount = 3;
const element = /readonly/g;
const fakeFormGroup = {
controls: [
{
type: 'text',
name: 'fake-checkbox',
label: 'FAKE CHECKBOX',
},
],
};
const props = { formGroups: Array(expectedCount).fill(fakeFormGroup), readOnly: true };
component = await getComponentOutput('./components/Form.astro', props);
// act
const actualResult = cleanString(component.html);
const matches = actualResult.match(element) || [];
// assert // assert
expect(matches.length).to.equal(expectedCount); expect(matches.length).to.equal(expectedCount);
}); });

View file

@ -87,4 +87,52 @@ describe('RadioGroup.astro test', () => {
// assert // assert
expect(actualResult).to.contain(expectedResult); expect(actualResult).to.contain(expectedResult);
}); });
it('Should render readonly attribute if the prop readOnly is passed as true', async () => {
// arrange
const expectedOptions = 3;
const element = /readonly/g;
const props = {
control: {
label: 'FAKE LABEL',
name: 'FAKE NAME',
type: 'radio',
options: ['one', 'two', 'three'],
},
showValidationHints: true,
readOnly: true,
};
// act
component = await getComponentOutput('./components/controls/RadioGroup.astro', props);
const actualResult = cleanString(component.html);
const matches = actualResult.match(element) || [];
// assert
expect(matches.length).to.equal(expectedOptions);
});
it('Should render disabled attribute if the prop readOnly is passed as true', async () => {
// arrange
const expectedOptions = 3;
const element = /disabled/g;
const props = {
control: {
label: 'FAKE LABEL',
name: 'FAKE NAME',
type: 'radio',
options: ['one', 'two', 'three'],
},
showValidationHints: true,
readOnly: true,
};
// act
component = await getComponentOutput('./components/controls/RadioGroup.astro', props);
const actualResult = cleanString(component.html);
const matches = actualResult.match(element) || [];
// assert
expect(matches.length).to.equal(expectedOptions);
});
}); });