or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdfield-components.mdfield-types.mdindex.mdjson-schema.mdselect.mdservices.mdtesting.mdutilities.mdvalidation.md

testing.mddocs/

0

# Testing Utilities

1

2

Testing utilities for creating and testing Formly components and field configurations, providing helpers for unit and integration testing scenarios.

3

4

## Capabilities

5

6

### Component Factory Functions

7

8

Utility functions for creating test components and field components with proper setup and configuration.

9

10

```typescript { .api }

11

/**

12

* Create a test component with specified options

13

* @param options - Configuration options for the test component

14

* @returns FixtureUtils with testing helpers

15

*/

16

function createComponent<T>(options: IComponentOptions<T>): FixtureUtils;

17

18

/**

19

* Create a field component for testing with field configuration

20

* @param field - The field configuration to test

21

* @param config - Optional additional configuration

22

* @returns FixtureUtils with testing helpers and field-specific methods

23

*/

24

function createFieldComponent(field: FormlyFieldConfig, config?: any): FixtureUtils;

25

```

26

27

**Usage Examples:**

28

29

```typescript

30

import { createFieldComponent, createComponent } from '@ngx-formly/core/testing';

31

import { FormlyFieldConfig } from '@ngx-formly/core';

32

33

describe('Custom Field Component', () => {

34

let fixture: FixtureUtils;

35

36

beforeEach(() => {

37

const field: FormlyFieldConfig = {

38

key: 'testField',

39

type: 'input',

40

props: {

41

label: 'Test Field',

42

placeholder: 'Enter test value',

43

required: true

44

}

45

};

46

47

fixture = createFieldComponent(field);

48

});

49

50

it('should render field with correct label', () => {

51

expect(fixture.query('label').textContent).toContain('Test Field');

52

});

53

54

it('should mark field as required', () => {

55

const input = fixture.query('input');

56

expect(input.hasAttribute('required')).toBe(true);

57

});

58

59

it('should update model on input change', () => {

60

const input = fixture.query('input');

61

input.value = 'test value';

62

input.dispatchEvent(new Event('input'));

63

fixture.detectChanges();

64

65

expect(fixture.field.model.testField).toBe('test value');

66

});

67

});

68

```

69

70

### Custom Component Testing

71

72

```typescript

73

import { Component } from '@angular/core';

74

import { FormlyFieldConfig, FieldType } from '@ngx-formly/core';

75

import { createComponent } from '@ngx-formly/core/testing';

76

77

@Component({

78

selector: 'formly-field-custom-input',

79

template: `

80

<div class="custom-field">

81

<label [for]="id">{{ props.label }}</label>

82

<input

83

[id]="id"

84

[formControl]="formControl"

85

[placeholder]="props.placeholder"

86

[class.error]="showError"

87

(focus)="onFocus()"

88

(blur)="onBlur()">

89

<div *ngIf="showError" class="error-message">

90

{{ getErrorMessage() }}

91

</div>

92

</div>

93

`

94

})

95

export class CustomInputFieldComponent extends FieldType {

96

onFocus() {

97

console.log('Field focused');

98

}

99

100

onBlur() {

101

console.log('Field blurred');

102

}

103

104

getErrorMessage(): string {

105

if (this.formControl.errors?.['required']) {

106

return 'This field is required';

107

}

108

return '';

109

}

110

}

111

112

describe('CustomInputFieldComponent', () => {

113

let fixture: FixtureUtils;

114

115

beforeEach(() => {

116

fixture = createComponent({

117

component: CustomInputFieldComponent,

118

fieldConfig: {

119

key: 'customInput',

120

type: 'custom-input',

121

props: {

122

label: 'Custom Input',

123

placeholder: 'Enter value',

124

required: true

125

}

126

}

127

});

128

});

129

130

it('should display custom styling', () => {

131

const customField = fixture.query('.custom-field');

132

expect(customField).toBeTruthy();

133

});

134

135

it('should show error message when invalid', () => {

136

fixture.field.formControl.markAsTouched();

137

fixture.detectChanges();

138

139

const errorMessage = fixture.query('.error-message');

140

expect(errorMessage.textContent).toContain('This field is required');

141

});

142

143

it('should handle focus and blur events', () => {

144

spyOn(fixture.component, 'onFocus');

145

spyOn(fixture.component, 'onBlur');

146

147

const input = fixture.query('input');

148

input.dispatchEvent(new Event('focus'));

149

input.dispatchEvent(new Event('blur'));

150

151

expect(fixture.component.onFocus).toHaveBeenCalled();

152

expect(fixture.component.onBlur).toHaveBeenCalled();

153

});

154

});

155

```

156

157

## Types

158

159

### Testing Configuration Types

160

161

```typescript { .api }

162

interface IComponentOptions<T> {

163

/** The component class to test */

164

component: Type<T>;

165

166

/** Field configuration for the component */

167

fieldConfig?: FormlyFieldConfig;

168

169

/** Form model data */

170

model?: any;

171

172

/** Form options */

173

options?: FormlyFormOptions;

174

175

/** Additional providers for testing */

176

providers?: Provider[];

177

178

/** Additional imports for the test module */

179

imports?: any[];

180

181

/** Additional declarations for the test module */

182

declarations?: any[];

183

}

184

185

interface FixtureUtils {

186

/** Angular ComponentFixture instance */

187

fixture: ComponentFixture<any>;

188

189

/** Component instance */

190

component: any;

191

192

/** Field configuration used in the test */

193

field: FormlyFieldConfig;

194

195

/** Form model data */

196

model: any;

197

198

/** Form instance */

199

form: FormGroup;

200

201

/** Trigger change detection */

202

detectChanges(): void;

203

204

/** Query for DOM element by selector */

205

query<T extends Element = Element>(selector: string): T;

206

207

/** Query for all DOM elements by selector */

208

queryAll<T extends Element = Element>(selector: string): T[];

209

210

/** Set field value and trigger change detection */

211

setFieldValue(key: string, value: any): void;

212

213

/** Check if field has specific error */

214

hasFieldError(key: string, errorType: string): boolean;

215

216

/** Get field error message */

217

getFieldError(key: string): string | null;

218

}

219

```

220

221

### Mock Data Types

222

223

```typescript { .api }

224

/**

225

* Helper type for creating mock field configurations

226

*/

227

interface MockFieldConfig extends Partial<FormlyFieldConfig> {

228

key: string;

229

type: string;

230

}

231

232

/**

233

* Helper interface for mock form data

234

*/

235

interface MockFormData {

236

[key: string]: any;

237

}

238

239

/**

240

* Configuration for creating test scenarios

241

*/

242

interface TestScenario {

243

name: string;

244

fieldConfig: FormlyFieldConfig;

245

initialModel?: any;

246

expectedValue?: any;

247

actions?: Array<{

248

type: 'input' | 'select' | 'click' | 'focus' | 'blur';

249

selector: string;

250

value?: any;

251

}>;

252

}

253

```

254

255

## Import

256

257

```typescript

258

import {

259

createComponent,

260

createFieldComponent,

261

FixtureUtils,

262

IComponentOptions

263

} from '@ngx-formly/core/testing';

264

```

265

266

## Test Setup

267

268

### Basic Test Module Configuration

269

270

```typescript

271

import { TestBed } from '@angular/core/testing';

272

import { ReactiveFormsModule } from '@angular/forms';

273

import { FormlyModule } from '@ngx-formly/core';

274

import { NoopAnimationsModule } from '@angular/platform-browser/animations';

275

276

beforeEach(async () => {

277

await TestBed.configureTestingModule({

278

imports: [

279

ReactiveFormsModule,

280

NoopAnimationsModule,

281

FormlyModule.forRoot({

282

types: [

283

{ name: 'input', component: FormlyFieldInput },

284

{ name: 'select', component: FormlyFieldSelect },

285

{ name: 'textarea', component: FormlyFieldTextarea }

286

]

287

})

288

]

289

}).compileComponents();

290

});

291

```

292

293

## Advanced Testing Examples

294

295

### Form Validation Testing

296

297

```typescript

298

describe('Form Validation', () => {

299

let fixture: FixtureUtils;

300

301

beforeEach(() => {

302

const fields: FormlyFieldConfig[] = [

303

{

304

key: 'email',

305

type: 'input',

306

props: {

307

label: 'Email',

308

type: 'email',

309

required: true

310

},

311

validators: {

312

email: {

313

validation: Validators.email,

314

message: 'Please enter a valid email address'

315

}

316

}

317

},

318

{

319

key: 'password',

320

type: 'input',

321

props: {

322

label: 'Password',

323

type: 'password',

324

required: true,

325

minLength: 8

326

}

327

}

328

];

329

330

fixture = createComponent({

331

fields,

332

model: {}

333

});

334

});

335

336

it('should show validation errors for invalid email', () => {

337

fixture.setFieldValue('email', 'invalid-email');

338

339

expect(fixture.hasFieldError('email', 'email')).toBe(true);

340

expect(fixture.getFieldError('email')).toContain('valid email address');

341

});

342

343

it('should validate password length', () => {

344

fixture.setFieldValue('password', '123');

345

346

expect(fixture.hasFieldError('password', 'minlength')).toBe(true);

347

});

348

349

it('should be valid with correct values', () => {

350

fixture.setFieldValue('email', 'user@example.com');

351

fixture.setFieldValue('password', 'securepassword123');

352

353

expect(fixture.form.valid).toBe(true);

354

});

355

});

356

```

357

358

### Dynamic Field Testing

359

360

```typescript

361

describe('Dynamic Fields', () => {

362

it('should show/hide fields based on conditions', () => {

363

const fields: FormlyFieldConfig[] = [

364

{

365

key: 'hasAccount',

366

type: 'checkbox',

367

props: {

368

label: 'I have an existing account'

369

}

370

},

371

{

372

key: 'username',

373

type: 'input',

374

props: {

375

label: 'Username'

376

},

377

expressions: {

378

hide: '!model.hasAccount'

379

}

380

}

381

];

382

383

const fixture = createComponent({ fields, model: {} });

384

385

// Initially username should be hidden

386

expect(fixture.query('input[id*="username"]')).toBeFalsy();

387

388

// Check the checkbox

389

fixture.setFieldValue('hasAccount', true);

390

fixture.detectChanges();

391

392

// Now username should be visible

393

expect(fixture.query('input[id*="username"]')).toBeTruthy();

394

});

395

});

396

```

397

398

### Custom Validator Testing

399

400

```typescript

401

function customValidator(control: AbstractControl): ValidationErrors | null {

402

if (control.value && control.value.length < 5) {

403

return { tooShort: { actualLength: control.value.length, requiredLength: 5 } };

404

}

405

return null;

406

}

407

408

describe('Custom Validator', () => {

409

it('should validate with custom validator', () => {

410

const field: FormlyFieldConfig = {

411

key: 'customField',

412

type: 'input',

413

validators: {

414

custom: {

415

validation: customValidator,

416

message: 'Value must be at least 5 characters long'

417

}

418

}

419

};

420

421

const fixture = createFieldComponent(field);

422

423

fixture.setFieldValue('customField', '123');

424

425

expect(fixture.hasFieldError('customField', 'tooShort')).toBe(true);

426

expect(fixture.getFieldError('customField')).toContain('5 characters long');

427

});

428

});

429

```