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;
showValidationHints: boolean;
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;
---
@ -25,12 +26,12 @@ const hasErrors: boolean | null = !!control.errors?.length;
<Label control={control} showValidationHints={showValidationHints}>
{
control.type === 'radio' ? (
<RadioGroup control={control as Radio} />
<RadioGroup control={control as Radio} readOnly={readOnly} />
) :
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} />}

View file

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

View file

@ -9,9 +9,10 @@ export interface Props {
submitControl?: Submit;
theme?: 'light' | 'dark';
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 formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
@ -26,10 +27,10 @@ const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
{
Array.isArray(formGroups)
? formGroups?.map((group) => (
<FieldSet showValidationHints={showValidationHints} group={group} />
<FieldSet showValidationHints={showValidationHints} group={group} readOnly={readOnly}/>
))
: 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 {
control: Dropdown;
readOnly?: boolean;
}
const { control } = Astro.props;
const { control, readOnly } = Astro.props;
const options = control.options.map((option: string | ControlOption) => {
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 && (
<option value="" disabled selected={!control?.value}>
@ -39,4 +44,4 @@ const options = control.options.map((option: string | ControlOption) => {
</option>
))
}
</select>
</select>

View file

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

View file

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

View file

@ -149,4 +149,27 @@ describe('Dropdown.astro test', () => {
// assert
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
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 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
expect(matches.length).to.equal(expectedCount);
});

View file

@ -87,4 +87,52 @@ describe('RadioGroup.astro test', () => {
// assert
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);
});
});