refactor: form package cleanup (#135)
* refactor: form package cleanup * refactor: organize components folder
This commit is contained in:
parent
b9995b8a63
commit
3d361fcb5b
18 changed files with 287 additions and 234 deletions
|
@ -34,8 +34,8 @@ const form = new FormGroup([
|
||||||
label: "Agreement",
|
label: "Agreement",
|
||||||
type: "radio",
|
type: "radio",
|
||||||
value: [
|
value: [
|
||||||
{ label: "Agree", value: "yes" },
|
{ label: "Agree", value: "yes", checked: true },
|
||||||
{ label: "Disagree", value: "no", labelPosition: "right" },
|
{ label: "Disagree", value: "no" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -61,9 +61,12 @@ form.get("is-awesome")?.setValue("checked");
|
||||||
|
|
||||||
// setting an invalid value will cause errors as server-rendered
|
// setting an invalid value will cause errors as server-rendered
|
||||||
form.get("email")?.setValue("invalid-email");
|
form.get("email")?.setValue("invalid-email");
|
||||||
|
|
||||||
|
// switch between light and dark mode
|
||||||
|
const theme = "dark";
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en" class={theme}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
@ -71,8 +74,15 @@ form.get("email")?.setValue("invalid-email");
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<title>Astro</title>
|
<title>Astro</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class={theme}>
|
||||||
|
<nav>Examples: <a href="/pizza-delivery">Pizza Delivery</a></nav>
|
||||||
<h1>Astro Reactive Form</h1>
|
<h1>Astro Reactive Form</h1>
|
||||||
<Form showValidationHints={true} formGroups={form} />
|
<Form showValidationHints={true} formGroups={form} theme={theme} />
|
||||||
|
<style>
|
||||||
|
html.dark,
|
||||||
|
body.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -98,6 +98,7 @@ infoForm.name = "Customer Info";
|
||||||
<title>Astro</title>
|
<title>Astro</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<nav><a href="/">Home</a></nav>
|
||||||
<h1>Pizza Form Demo</h1>
|
<h1>Pizza Form Demo</h1>
|
||||||
<Form
|
<Form
|
||||||
showValidationHints={true}
|
showValidationHints={true}
|
||||||
|
|
|
@ -46,7 +46,7 @@ export interface Radio extends Omit<ControlBase, "value"> {
|
||||||
value: string[] | RadioOption[];
|
value: string[] | RadioOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RadioOption extends Omit<ControlBase, "name"> {
|
export interface RadioOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
---
|
|
||||||
import type { Submit, Radio, RadioOption } from 'common/types';
|
|
||||||
import { FormGroup, FormControl } from './core';
|
|
||||||
import FieldSet from './components/FieldSet.astro';
|
|
||||||
import Field from './components/Field.astro';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
formGroups: FormGroup | FormGroup[];
|
|
||||||
submitControl?: Submit;
|
|
||||||
theme?: 'light' | 'dark';
|
|
||||||
showValidationHints?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { submitControl, formGroups: form, theme, showValidationHints = false } = Astro.props;
|
|
||||||
|
|
||||||
const formTheme = theme ?? 'light';
|
|
||||||
const formName = Array.isArray(form) ? null : form?.name || null;
|
|
||||||
---
|
|
||||||
|
|
||||||
<form class={formTheme} name={formName} id={formName}>
|
|
||||||
{
|
|
||||||
Array.isArray(form)
|
|
||||||
? form?.map((group) => <FieldSet showValidationHints={showValidationHints} group={group} />)
|
|
||||||
: form?.controls.map((control) =>
|
|
||||||
control.type === 'radio' ? (
|
|
||||||
[
|
|
||||||
<Field
|
|
||||||
showValidationHints={showValidationHints}
|
|
||||||
control={control}
|
|
||||||
showOnlyLabel={true}
|
|
||||||
/>,
|
|
||||||
...(control as Radio)?.value?.map((v: string | RadioOption) => (
|
|
||||||
<Field
|
|
||||||
showValidationHints={showValidationHints}
|
|
||||||
control={
|
|
||||||
typeof v === 'string'
|
|
||||||
? new FormControl({
|
|
||||||
name: control.name,
|
|
||||||
type: 'radio',
|
|
||||||
id: control.name + v,
|
|
||||||
label: v,
|
|
||||||
value: v,
|
|
||||||
})
|
|
||||||
: new FormControl({
|
|
||||||
name: control.name,
|
|
||||||
type: 'radio',
|
|
||||||
id: control.name + v.label,
|
|
||||||
...(v as RadioOption),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)),
|
|
||||||
]
|
|
||||||
) : (
|
|
||||||
<Field showValidationHints={showValidationHints} control={control} />
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
submitControl && (
|
|
||||||
<Field showValidationHints={showValidationHints} control={new FormControl(submitControl)} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.light {
|
|
||||||
/**
|
|
||||||
* run dev server with: "npm start",
|
|
||||||
* then open browser to "localhost:3000"
|
|
||||||
* add a class="light" to the <form> element above to test changes
|
|
||||||
* INSERT STYLES FOR LIGHT MODE BELOW: */
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
/**
|
|
||||||
* run dev server with: "npm start",
|
|
||||||
* then open browser to "localhost:3000"
|
|
||||||
* add a class="dark" to the <form> element above to test changes
|
|
||||||
* INSERT STYLES FOR DARK MODE BELOW: */
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
17
packages/form/components/Errors.astro
Normal file
17
packages/form/components/Errors.astro
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
import type { ValidationError } from 'common/types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
errors: ValidationError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { errors = [] } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
errors.map((error) => (
|
||||||
|
<li>
|
||||||
|
{error.error} {error.limit}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
|
@ -1,98 +1,34 @@
|
||||||
---
|
---
|
||||||
|
/**
|
||||||
|
* DEFAULT CONTROL COMPONENT
|
||||||
|
*/
|
||||||
|
import type { Radio } from 'common/types';
|
||||||
import type { FormControl } from '../core/form-control';
|
import type { FormControl } from '../core/form-control';
|
||||||
|
import Input from './controls/Input.astro';
|
||||||
|
import RadioGroup from './controls/RadioGroup.astro';
|
||||||
|
import Errors from './Errors.astro';
|
||||||
|
import Label from './Label.astro';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
control: FormControl;
|
control: FormControl;
|
||||||
showValidationHints: boolean;
|
showValidationHints: boolean;
|
||||||
showErrors?: boolean;
|
showErrors?: boolean; // feature flag for showing validation errors
|
||||||
showOnlyLabel?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { control, showValidationHints, showErrors = false, showOnlyLabel = false } = Astro.props;
|
const { control, showValidationHints, showErrors = false } = Astro.props;
|
||||||
|
|
||||||
const { validators = [] } = control;
|
const hasErrors: boolean | null = !!control.errors?.length;
|
||||||
|
|
||||||
const isRequired: boolean = showValidationHints && validators.includes('validator-required');
|
|
||||||
|
|
||||||
const hasErrors: boolean = showValidationHints && !!control.errors?.length;
|
|
||||||
|
|
||||||
const validatorAttributes: Record<string, string> = validators?.reduce(
|
|
||||||
(prev, validator) => {
|
|
||||||
const split: string[] = validator.split(':');
|
|
||||||
const label: string = `data-${split[0]}` || 'invalid';
|
|
||||||
const value: string | null = split.length > 1 ? split[1] ?? null : 'true';
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
[label]: value,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div class="field" data-validator-haserrors={hasErrors ? hasErrors.toString() : null}>
|
||||||
class="field"
|
<Label control={control} showValidationHints={showValidationHints}>
|
||||||
data-validator-hints={showValidationHints?.toString()}
|
{
|
||||||
data-validator-haserrors={hasErrors ? hasErrors?.toString() : null}
|
control.type === 'radio' ? (
|
||||||
>
|
<RadioGroup control={control as Radio} />
|
||||||
{
|
) : (
|
||||||
control.label && control.labelPosition === 'left' && (
|
<Input control={control} />
|
||||||
<label for={control?.id ?? control.name}>
|
)
|
||||||
{isRequired && <span>*</span>}
|
}
|
||||||
{control.label}
|
{showErrors && <Errors errors={control.errors} />}
|
||||||
</label>
|
</Label>
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!showOnlyLabel && (
|
|
||||||
<input
|
|
||||||
name={control.name}
|
|
||||||
id={control?.id ?? control.name}
|
|
||||||
type={control.type}
|
|
||||||
value={control.value?.toString()}
|
|
||||||
checked={control.value === 'checked'}
|
|
||||||
placeholder={control.placeholder}
|
|
||||||
data-label={control.label}
|
|
||||||
data-label-position={control.labelPosition}
|
|
||||||
data-validator-haserrors={hasErrors ? hasErrors?.toString() : null}
|
|
||||||
class:list={[{ 'has-errors': hasErrors }]}
|
|
||||||
{...validatorAttributes}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
showErrors &&
|
|
||||||
hasErrors &&
|
|
||||||
control.errors.map((error) => (
|
|
||||||
<li>
|
|
||||||
{error.error} {error.limit}
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
control.label && control.labelPosition === 'right' && (
|
|
||||||
<label for={control?.id ?? control.name}>
|
|
||||||
{isRequired && <span>*</span>}
|
|
||||||
{control.label}
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
/**
|
|
||||||
TODO: remove usage of data-validator-haserrors,
|
|
||||||
class has-errors is sufficient
|
|
||||||
*/
|
|
||||||
[data-validator-hints='true'][data-validator-haserrors='true'],
|
|
||||||
[data-validator-hints='true'] [data-validator-haserrors='true'],
|
|
||||||
.has-errors {
|
|
||||||
color: red;
|
|
||||||
border-color: red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
58
packages/form/components/Form.astro
Normal file
58
packages/form/components/Form.astro
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
import type { Submit } from 'common/types';
|
||||||
|
import { FormGroup, FormControl } from '../core';
|
||||||
|
import FieldSet from './FieldSet.astro';
|
||||||
|
import Field from './Field.astro';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
formGroups: FormGroup | FormGroup[];
|
||||||
|
submitControl?: Submit;
|
||||||
|
theme?: 'light' | 'dark';
|
||||||
|
showValidationHints?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { submitControl, formGroups = [], theme, showValidationHints = false } = Astro.props;
|
||||||
|
|
||||||
|
const formTheme = theme ?? 'light';
|
||||||
|
const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
|
||||||
|
---
|
||||||
|
|
||||||
|
<form
|
||||||
|
class={formTheme}
|
||||||
|
name={formName}
|
||||||
|
id={formName}
|
||||||
|
data-validator-hints={showValidationHints.toString()}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
Array.isArray(formGroups)
|
||||||
|
? formGroups?.map((group) => (
|
||||||
|
<FieldSet showValidationHints={showValidationHints} group={group} />
|
||||||
|
))
|
||||||
|
: formGroups?.controls.map((control) => (
|
||||||
|
<Field showValidationHints={showValidationHints} control={control} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
submitControl && (
|
||||||
|
<Field showValidationHints={showValidationHints} control={new FormControl(submitControl)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.light {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
[data-validator-hints='true'][data-validator-haserrors='true'],
|
||||||
|
[data-validator-hints='true'] [data-validator-haserrors='true'] {
|
||||||
|
color: red;
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
</style>
|
36
packages/form/components/Label.astro
Normal file
36
packages/form/components/Label.astro
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
import type { FormControl } from '../core';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
control: FormControl;
|
||||||
|
showValidationHints: boolean;
|
||||||
|
showErrors?: boolean; // feature flag for showing validation errors
|
||||||
|
}
|
||||||
|
|
||||||
|
const { control, showValidationHints } = Astro.props;
|
||||||
|
|
||||||
|
const { validators = [] } = control;
|
||||||
|
|
||||||
|
const isRequired: boolean = showValidationHints && validators.includes('validator-required');
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
control.label && control.labelPosition === 'left' && (
|
||||||
|
<label for={control.name}>
|
||||||
|
{isRequired && <span>*</span>}
|
||||||
|
{control.label}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
{
|
||||||
|
control.label && control.labelPosition === 'right' && (
|
||||||
|
<label for={control.name}>
|
||||||
|
{isRequired && <span>*</span>}
|
||||||
|
{control.label}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
40
packages/form/components/controls/Input.astro
Normal file
40
packages/form/components/controls/Input.astro
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
/**
|
||||||
|
* DEFAULT INPUT COMPONENT
|
||||||
|
*/
|
||||||
|
import type { FormControl } from '../../core/form-control';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
control: FormControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { control } = Astro.props;
|
||||||
|
|
||||||
|
const { validators = [] } = control;
|
||||||
|
|
||||||
|
const hasErrors: boolean = !!control.errors?.length;
|
||||||
|
|
||||||
|
const validatorAttributes: Record<string, string> = validators?.reduce((prev, validator) => {
|
||||||
|
const split: string[] = validator.split(':');
|
||||||
|
const label: string = `data-${split[0]}` || 'invalid';
|
||||||
|
const value: string | null = split.length > 1 ? split[1] ?? null : 'true';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[label]: value,
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
---
|
||||||
|
|
||||||
|
<input
|
||||||
|
name={control.name}
|
||||||
|
id={control.name}
|
||||||
|
type={control.type}
|
||||||
|
value={control.value?.toString()}
|
||||||
|
checked={control.value === 'checked'}
|
||||||
|
placeholder={control.placeholder}
|
||||||
|
data-label={control.label}
|
||||||
|
data-label-position={control.labelPosition}
|
||||||
|
data-validator-haserrors={hasErrors.toString()}
|
||||||
|
{...validatorAttributes}
|
||||||
|
/>
|
31
packages/form/components/controls/RadioGroup.astro
Normal file
31
packages/form/components/controls/RadioGroup.astro
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
/**
|
||||||
|
* RADIO GROUP COMPONENT
|
||||||
|
*/
|
||||||
|
import type { Radio } from 'common/types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
control: Radio;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { control } = Astro.props;
|
||||||
|
|
||||||
|
const options = control.value.map((option) => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return {
|
||||||
|
label: option,
|
||||||
|
value: option,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
options.map((option) => (
|
||||||
|
<div class="radio-option">
|
||||||
|
<input type="radio" name={control.name} value={option.value} checked={option.checked} />{' '}
|
||||||
|
{option.label}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
|
@ -13,15 +13,14 @@ export type ControlConfig = ControlBase | Checkbox | Radio | Submit | Button;
|
||||||
|
|
||||||
export class FormControl {
|
export class FormControl {
|
||||||
private _name = '';
|
private _name = '';
|
||||||
private _id = '';
|
|
||||||
private _type: ControlType = 'text';
|
private _type: ControlType = 'text';
|
||||||
private _value?: string | number | null | string[] | RadioOption[];
|
private _value?: string | number | null | string[] | RadioOption[];
|
||||||
private _label?: string;
|
private _label = '';
|
||||||
private _labelPosition?: 'right' | 'left' = 'left';
|
private _labelPosition?: 'right' | 'left' = 'left';
|
||||||
private _isValid = true;
|
private _isValid = true;
|
||||||
private _isPristine = true;
|
private _isPristine = true;
|
||||||
private _placeholder?: string;
|
private _placeholder: string | null = null;
|
||||||
private _validators?: string[];
|
private _validators: string[] = [];
|
||||||
private _errors: ValidationError[] = [];
|
private _errors: ValidationError[] = [];
|
||||||
|
|
||||||
private validate: (value: string, validators: string[]) => ValidationError[] = (
|
private validate: (value: string, validators: string[]) => ValidationError[] = (
|
||||||
|
@ -34,18 +33,25 @@ export class FormControl {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private config: ControlConfig) {
|
constructor(private config: ControlConfig) {
|
||||||
const { name, id, type, value, label, labelPosition, placeholder, validators = [] } = config;
|
const {
|
||||||
|
name,
|
||||||
|
type = 'text',
|
||||||
|
value = null,
|
||||||
|
label = '',
|
||||||
|
labelPosition = 'left',
|
||||||
|
placeholder = null,
|
||||||
|
validators = [],
|
||||||
|
} = config;
|
||||||
|
|
||||||
this._name = name;
|
this._name = name;
|
||||||
this._id = id ?? name;
|
this._type = type;
|
||||||
this._type = type ?? 'text';
|
this._value = value;
|
||||||
this._value = value ?? null;
|
this._label = label;
|
||||||
this._label = label ?? '';
|
this._labelPosition = labelPosition;
|
||||||
this._labelPosition = labelPosition ?? 'left';
|
this._placeholder = placeholder;
|
||||||
this._placeholder = placeholder ?? '';
|
|
||||||
this._validators = validators;
|
this._validators = validators;
|
||||||
|
|
||||||
// dynamic import of the validator package
|
// dynamic import of the validator package
|
||||||
// if user did not install the validator, then errors should be empty
|
|
||||||
import('@astro-reactive/validator').then((validator) => {
|
import('@astro-reactive/validator').then((validator) => {
|
||||||
if (validator) {
|
if (validator) {
|
||||||
this.validate = validator.validate;
|
this.validate = validator.validate;
|
||||||
|
@ -53,6 +59,7 @@ export class FormControl {
|
||||||
const valueStr: string = this._value?.toString() || '';
|
const valueStr: string = this._value?.toString() || '';
|
||||||
this._errors = this.validate(valueStr, validators);
|
this._errors = this.validate(valueStr, validators);
|
||||||
} else {
|
} else {
|
||||||
|
// if user did not install the validator, then errors should be empty
|
||||||
this._errors = [];
|
this._errors = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -62,10 +69,6 @@ export class FormControl {
|
||||||
return this._name;
|
return this._name;
|
||||||
}
|
}
|
||||||
|
|
||||||
get id() {
|
|
||||||
return this._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
return this._type;
|
return this._type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Form from './Form.astro';
|
import Form from './components/Form.astro';
|
||||||
export default Form;
|
export default Form;
|
||||||
export * from './core';
|
export * from './core';
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
"files": [
|
"files": [
|
||||||
"core/",
|
"core/",
|
||||||
"components/",
|
"components/",
|
||||||
"Form.astro",
|
|
||||||
"index.ts"
|
"index.ts"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { beforeEach, describe, it } from 'mocha';
|
import { beforeEach, describe, it } from 'mocha';
|
||||||
import { getComponentOutput } from 'astro-component-tester';
|
import { getComponentOutput } from 'astro-component-tester';
|
||||||
|
import { cleanString } from './utils/index.js';
|
||||||
|
|
||||||
describe('Field.astro test', () => {
|
describe('Field.astro test', () => {
|
||||||
let component;
|
let component;
|
||||||
|
@ -29,28 +30,6 @@ describe('Field.astro test', () => {
|
||||||
expect(actualResult).to.contain(expectedLabel);
|
expect(actualResult).to.contain(expectedLabel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should render only label', async () => {
|
|
||||||
// arrange
|
|
||||||
const expectedLabel = 'TestLabel';
|
|
||||||
const props = {
|
|
||||||
control: {
|
|
||||||
label: expectedLabel,
|
|
||||||
name: 'username',
|
|
||||||
labelPosition: 'left',
|
|
||||||
},
|
|
||||||
showValidationHints: false,
|
|
||||||
showOnlyLabel: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// act
|
|
||||||
component = await getComponentOutput('./components/Field.astro', props);
|
|
||||||
const actualResult = cleanString(component.html);
|
|
||||||
|
|
||||||
// assert
|
|
||||||
expect(actualResult).to.contain(expectedLabel);
|
|
||||||
expect(actualResult).to.not.contain('input');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should render required fields with asterisk in label when showValidationHints is true', async () => {
|
it('Should render required fields with asterisk in label when showValidationHints is true', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedLabel = 'TestLabel';
|
const expectedLabel = 'TestLabel';
|
||||||
|
@ -75,7 +54,7 @@ describe('Field.astro test', () => {
|
||||||
|
|
||||||
it('Should server-render validation error attributes', async () => {
|
it('Should server-render validation error attributes', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedClass = 'has-errors';
|
const expectedResult = 'data-validator-haserrors="true"';
|
||||||
const props = {
|
const props = {
|
||||||
control: {
|
control: {
|
||||||
label: 'FAKE LABEL',
|
label: 'FAKE LABEL',
|
||||||
|
@ -97,10 +76,6 @@ describe('Field.astro test', () => {
|
||||||
const actualResult = cleanString(component.html);
|
const actualResult = cleanString(component.html);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(actualResult).to.contain(expectedClass);
|
expect(actualResult).to.contain(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function cleanString(str) {
|
|
||||||
return str.replace(/\s/g, '');
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { beforeEach, describe, it } from 'mocha';
|
import { beforeEach, describe, it } from 'mocha';
|
||||||
import { getComponentOutput } from 'astro-component-tester';
|
import { getComponentOutput } from 'astro-component-tester';
|
||||||
|
import { cleanString } from './utils/index.js';
|
||||||
|
|
||||||
describe('FieldSet.astro test', () => {
|
describe('FieldSet.astro test', () => {
|
||||||
let component;
|
let component;
|
||||||
|
@ -36,7 +37,3 @@ describe('FieldSet.astro test', () => {
|
||||||
expect(actualResult).to.contain(`<legend>${expectedName}</legend>`);
|
expect(actualResult).to.contain(`<legend>${expectedName}</legend>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function cleanString(str) {
|
|
||||||
return str.replace(/\s/g, '');
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { describe, beforeEach, it } from 'mocha';
|
import { describe, beforeEach, it } from 'mocha';
|
||||||
import { getComponentOutput } from 'astro-component-tester';
|
import { getComponentOutput } from 'astro-component-tester';
|
||||||
|
import { cleanString } from './utils/index.js';
|
||||||
|
|
||||||
describe('Form.astro test', () => {
|
describe('Form.astro test', () => {
|
||||||
let component;
|
let component;
|
||||||
|
@ -15,7 +16,7 @@ describe('Form.astro test', () => {
|
||||||
const expectedCount = 0;
|
const expectedCount = 0;
|
||||||
const element = /<fieldset>/g;
|
const element = /<fieldset>/g;
|
||||||
const props = { formGroups: undefined };
|
const props = { formGroups: undefined };
|
||||||
component = await getComponentOutput('./Form.astro', props);
|
component = await getComponentOutput('./components/Form.astro', props);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const actualResult = cleanString(component.html);
|
const actualResult = cleanString(component.html);
|
||||||
|
@ -30,7 +31,7 @@ describe('Form.astro test', () => {
|
||||||
const expectedCount = 0;
|
const expectedCount = 0;
|
||||||
const element = /<fieldset>/g;
|
const element = /<fieldset>/g;
|
||||||
const props = { formGroups: [] };
|
const props = { formGroups: [] };
|
||||||
component = await getComponentOutput('./Form.astro', props);
|
component = await getComponentOutput('./components/Form.astro', props);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const actualResult = cleanString(component.html);
|
const actualResult = cleanString(component.html);
|
||||||
|
@ -54,7 +55,7 @@ describe('Form.astro test', () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const props = { formGroups: Array(expectedCount).fill(fakeFormGroup) };
|
const props = { formGroups: Array(expectedCount).fill(fakeFormGroup) };
|
||||||
component = await getComponentOutput('./Form.astro', props);
|
component = await getComponentOutput('./components/Form.astro', props);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const actualResult = cleanString(component.html);
|
const actualResult = cleanString(component.html);
|
||||||
|
@ -78,7 +79,7 @@ describe('Form.astro test', () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const props = { formGroups: fakeFormGroup };
|
const props = { formGroups: fakeFormGroup };
|
||||||
component = await getComponentOutput('./Form.astro', props);
|
component = await getComponentOutput('./components/Form.astro', props);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const actualResult = cleanString(component.html);
|
const actualResult = cleanString(component.html);
|
||||||
|
@ -89,7 +90,3 @@ describe('Form.astro test', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function cleanString(str) {
|
|
||||||
return str.replace(/\s/g, '');
|
|
||||||
}
|
|
||||||
|
|
34
packages/form/test/RadioGroup.astro.test.js
Normal file
34
packages/form/test/RadioGroup.astro.test.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { describe, beforeEach, it } from 'mocha';
|
||||||
|
import { getComponentOutput } from 'astro-component-tester';
|
||||||
|
import { cleanString } from './utils/index.js';
|
||||||
|
|
||||||
|
describe('RadioGroup.astro test', () => {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component = undefined;
|
||||||
|
});
|
||||||
|
it('Should render all radio options', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedOptions = 3;
|
||||||
|
const element = /radio-option/g;
|
||||||
|
const props = {
|
||||||
|
control: {
|
||||||
|
label: 'FAKE LABEL',
|
||||||
|
name: 'FAKE NAME',
|
||||||
|
type: 'radio',
|
||||||
|
value: ['one', 'two', 'three'],
|
||||||
|
},
|
||||||
|
showValidationHints: 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);
|
||||||
|
});
|
||||||
|
});
|
3
packages/form/test/utils/index.js
Normal file
3
packages/form/test/utils/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function cleanString(str) {
|
||||||
|
return str.replace(/\s/g, '');
|
||||||
|
}
|
Loading…
Reference in a new issue