Core package of ngx-formly - a dynamic (JSON powered) form library for Angular that brings unmatched maintainability to your application's forms
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Testing utilities for creating and testing Formly components and field configurations, providing helpers for unit and integration testing scenarios.
Utility functions for creating test components and field components with proper setup and configuration.
/**
* Create a test component with specified options
* @param options - Configuration options for the test component
* @returns FixtureUtils with testing helpers
*/
function createComponent<T>(options: IComponentOptions<T>): FixtureUtils;
/**
* Create a field component for testing with field configuration
* @param field - The field configuration to test
* @param config - Optional additional configuration
* @returns FixtureUtils with testing helpers and field-specific methods
*/
function createFieldComponent(field: FormlyFieldConfig, config?: any): FixtureUtils;Usage Examples:
import { createFieldComponent, createComponent } from '@ngx-formly/core/testing';
import { FormlyFieldConfig } from '@ngx-formly/core';
describe('Custom Field Component', () => {
let fixture: FixtureUtils;
beforeEach(() => {
const field: FormlyFieldConfig = {
key: 'testField',
type: 'input',
props: {
label: 'Test Field',
placeholder: 'Enter test value',
required: true
}
};
fixture = createFieldComponent(field);
});
it('should render field with correct label', () => {
expect(fixture.query('label').textContent).toContain('Test Field');
});
it('should mark field as required', () => {
const input = fixture.query('input');
expect(input.hasAttribute('required')).toBe(true);
});
it('should update model on input change', () => {
const input = fixture.query('input');
input.value = 'test value';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(fixture.field.model.testField).toBe('test value');
});
});import { Component } from '@angular/core';
import { FormlyFieldConfig, FieldType } from '@ngx-formly/core';
import { createComponent } from '@ngx-formly/core/testing';
@Component({
selector: 'formly-field-custom-input',
template: `
<div class="custom-field">
<label [for]="id">{{ props.label }}</label>
<input
[id]="id"
[formControl]="formControl"
[placeholder]="props.placeholder"
[class.error]="showError"
(focus)="onFocus()"
(blur)="onBlur()">
<div *ngIf="showError" class="error-message">
{{ getErrorMessage() }}
</div>
</div>
`
})
export class CustomInputFieldComponent extends FieldType {
onFocus() {
console.log('Field focused');
}
onBlur() {
console.log('Field blurred');
}
getErrorMessage(): string {
if (this.formControl.errors?.['required']) {
return 'This field is required';
}
return '';
}
}
describe('CustomInputFieldComponent', () => {
let fixture: FixtureUtils;
beforeEach(() => {
fixture = createComponent({
component: CustomInputFieldComponent,
fieldConfig: {
key: 'customInput',
type: 'custom-input',
props: {
label: 'Custom Input',
placeholder: 'Enter value',
required: true
}
}
});
});
it('should display custom styling', () => {
const customField = fixture.query('.custom-field');
expect(customField).toBeTruthy();
});
it('should show error message when invalid', () => {
fixture.field.formControl.markAsTouched();
fixture.detectChanges();
const errorMessage = fixture.query('.error-message');
expect(errorMessage.textContent).toContain('This field is required');
});
it('should handle focus and blur events', () => {
spyOn(fixture.component, 'onFocus');
spyOn(fixture.component, 'onBlur');
const input = fixture.query('input');
input.dispatchEvent(new Event('focus'));
input.dispatchEvent(new Event('blur'));
expect(fixture.component.onFocus).toHaveBeenCalled();
expect(fixture.component.onBlur).toHaveBeenCalled();
});
});interface IComponentOptions<T> {
/** The component class to test */
component: Type<T>;
/** Field configuration for the component */
fieldConfig?: FormlyFieldConfig;
/** Form model data */
model?: any;
/** Form options */
options?: FormlyFormOptions;
/** Additional providers for testing */
providers?: Provider[];
/** Additional imports for the test module */
imports?: any[];
/** Additional declarations for the test module */
declarations?: any[];
}
interface FixtureUtils {
/** Angular ComponentFixture instance */
fixture: ComponentFixture<any>;
/** Component instance */
component: any;
/** Field configuration used in the test */
field: FormlyFieldConfig;
/** Form model data */
model: any;
/** Form instance */
form: FormGroup;
/** Trigger change detection */
detectChanges(): void;
/** Query for DOM element by selector */
query<T extends Element = Element>(selector: string): T;
/** Query for all DOM elements by selector */
queryAll<T extends Element = Element>(selector: string): T[];
/** Set field value and trigger change detection */
setFieldValue(key: string, value: any): void;
/** Check if field has specific error */
hasFieldError(key: string, errorType: string): boolean;
/** Get field error message */
getFieldError(key: string): string | null;
}/**
* Helper type for creating mock field configurations
*/
interface MockFieldConfig extends Partial<FormlyFieldConfig> {
key: string;
type: string;
}
/**
* Helper interface for mock form data
*/
interface MockFormData {
[key: string]: any;
}
/**
* Configuration for creating test scenarios
*/
interface TestScenario {
name: string;
fieldConfig: FormlyFieldConfig;
initialModel?: any;
expectedValue?: any;
actions?: Array<{
type: 'input' | 'select' | 'click' | 'focus' | 'blur';
selector: string;
value?: any;
}>;
}import {
createComponent,
createFieldComponent,
FixtureUtils,
IComponentOptions
} from '@ngx-formly/core/testing';import { TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { FormlyModule } from '@ngx-formly/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
NoopAnimationsModule,
FormlyModule.forRoot({
types: [
{ name: 'input', component: FormlyFieldInput },
{ name: 'select', component: FormlyFieldSelect },
{ name: 'textarea', component: FormlyFieldTextarea }
]
})
]
}).compileComponents();
});describe('Form Validation', () => {
let fixture: FixtureUtils;
beforeEach(() => {
const fields: FormlyFieldConfig[] = [
{
key: 'email',
type: 'input',
props: {
label: 'Email',
type: 'email',
required: true
},
validators: {
email: {
validation: Validators.email,
message: 'Please enter a valid email address'
}
}
},
{
key: 'password',
type: 'input',
props: {
label: 'Password',
type: 'password',
required: true,
minLength: 8
}
}
];
fixture = createComponent({
fields,
model: {}
});
});
it('should show validation errors for invalid email', () => {
fixture.setFieldValue('email', 'invalid-email');
expect(fixture.hasFieldError('email', 'email')).toBe(true);
expect(fixture.getFieldError('email')).toContain('valid email address');
});
it('should validate password length', () => {
fixture.setFieldValue('password', '123');
expect(fixture.hasFieldError('password', 'minlength')).toBe(true);
});
it('should be valid with correct values', () => {
fixture.setFieldValue('email', 'user@example.com');
fixture.setFieldValue('password', 'securepassword123');
expect(fixture.form.valid).toBe(true);
});
});describe('Dynamic Fields', () => {
it('should show/hide fields based on conditions', () => {
const fields: FormlyFieldConfig[] = [
{
key: 'hasAccount',
type: 'checkbox',
props: {
label: 'I have an existing account'
}
},
{
key: 'username',
type: 'input',
props: {
label: 'Username'
},
expressions: {
hide: '!model.hasAccount'
}
}
];
const fixture = createComponent({ fields, model: {} });
// Initially username should be hidden
expect(fixture.query('input[id*="username"]')).toBeFalsy();
// Check the checkbox
fixture.setFieldValue('hasAccount', true);
fixture.detectChanges();
// Now username should be visible
expect(fixture.query('input[id*="username"]')).toBeTruthy();
});
});function customValidator(control: AbstractControl): ValidationErrors | null {
if (control.value && control.value.length < 5) {
return { tooShort: { actualLength: control.value.length, requiredLength: 5 } };
}
return null;
}
describe('Custom Validator', () => {
it('should validate with custom validator', () => {
const field: FormlyFieldConfig = {
key: 'customField',
type: 'input',
validators: {
custom: {
validation: customValidator,
message: 'Value must be at least 5 characters long'
}
}
};
const fixture = createFieldComponent(field);
fixture.setFieldValue('customField', '123');
expect(fixture.hasFieldError('customField', 'tooShort')).toBe(true);
expect(fixture.getFieldError('customField')).toContain('5 characters long');
});
});