Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Exitare committed Jan 27, 2025
1 parent b009f20 commit 9262e7d
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 56 deletions.
134 changes: 84 additions & 50 deletions libs/shared/directives/src/validate-input.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Component, DebugElement } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ReactiveFormsModule, FormControl, FormsModule, NgControl } from '@angular/forms';
import { ReactiveFormsModule, FormControl, FormsModule, NgControl, FormControlStatus } from '@angular/forms';
import { InputValidationDirective } from './validate-input.directive';
import { ValidationService } from '@keira/shared/common-services';
import { take } from 'rxjs';
import { Observable, take } from 'rxjs';

@Component({
template: `
Expand All @@ -27,7 +27,7 @@ describe('InputValidationDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
imports: [ReactiveFormsModule, FormsModule, InputValidationDirective], // Add the directive to imports
imports: [ReactiveFormsModule, FormsModule, InputValidationDirective],
providers: [ValidationService],
}).compileComponents();

Expand Down Expand Up @@ -66,9 +66,8 @@ describe('InputValidationDirective', () => {
let errorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(errorDiv).toBeTruthy();

// Make the control valid
control?.clearValidators();
control?.setValue('Valid value'); // Set a valid value
control?.setValue('Valid value');
control?.updateValueAndValidity();

fixture.detectChanges();
Expand All @@ -79,112 +78,147 @@ describe('InputValidationDirective', () => {

it('should handle empty error object gracefully', () => {
const control = debugElement.injector.get(NgControl).control;
control?.setValidators(() => ({})); // No errors
control?.markAsTouched();

control?.setValidators(() => null); // Simulate no errors
control?.setValue('');
control?.updateValueAndValidity();

fixture.detectChanges();

const errorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(errorDiv).toBeNull();
expect(errorDiv).toBeNull(); // Ensure no error message is created
});

it('should handle multiple error types gracefully', () => {
it('should handle multiple error types and display first error', () => {
const control = debugElement.injector.get(NgControl).control;
control?.setValidators(() => ({ required: true, minlength: true })); // Multiple errors

control?.setValidators(() => ({ required: true, minlength: true }));
control?.markAsTouched();
control?.updateValueAndValidity();

fixture.detectChanges();

const errorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(errorDiv).toBeTruthy();
expect(errorDiv.textContent).toBe('This field is required'); // Test only the first error message
});

it('should not throw when control is null', () => {
const ngControl = debugElement.injector.get(NgControl);
spyOnProperty(ngControl, 'control', 'get').and.returnValue(null); // Mock control as null

expect(() => {
fixture.detectChanges();
}).not.toThrow();
expect(errorDiv.textContent).toBe('This field is required');
});

it('should not create duplicate error messages if errorDiv already exists', () => {
it('should not create errorDiv if parentNode is null', () => {
const control = debugElement.injector.get(NgControl).control;
spyOnProperty(debugElement.nativeElement, 'parentNode', 'get').and.returnValue(null);

control?.setValidators(() => ({ required: true }));
control?.markAsTouched();
control?.updateValueAndValidity();

fixture.detectChanges();

const initialErrorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(initialErrorDiv).toBeTruthy();
expect(debugElement.nativeElement.parentNode?.querySelector('.error-message')).toBeFalsy();
});

it('should mark control as touched when invalid', () => {
const control = debugElement.injector.get(NgControl).control;
spyOn(control!, 'markAsTouched').and.callThrough();

control?.setValidators(() => ({ required: true }));
control?.setValue('');
control?.updateValueAndValidity();

// Trigger the updateErrorMessage logic again
fixture.detectChanges();

const updatedErrorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(updatedErrorDiv).toBe(initialErrorDiv); // Same errorDiv should remain
expect(control?.markAsTouched).toHaveBeenCalled();
});

it('should not add error message if parentNode is null', () => {
it('should update validationPassed$ with correct value', (done: DoneFn) => {
const control = debugElement.injector.get(NgControl).control;

// Mock parentNode as null
spyOnProperty(debugElement.nativeElement, 'parentNode', 'get').and.returnValue(null);

control?.setValidators(() => ({ required: true }));
control?.markAsTouched();
control?.setValue('');
control?.updateValueAndValidity();

fixture.detectChanges();

const errorDiv = debugElement.nativeElement.parentNode?.querySelector('.error-message');
expect(errorDiv).toBeFalsy(); // Error message should not be added
validationService.validationPassed$.pipe(take(1)).subscribe((isValid) => {
expect(isValid).toBe(false);
done();
});

control?.setValue('Valid value');
control?.updateValueAndValidity();

fixture.detectChanges();
});

it('should update validationPassed$ in ValidationService', (done: DoneFn) => {
it('should not throw error when ngControl.control is null', () => {
const ngControl = debugElement.injector.get(NgControl);
spyOnProperty(ngControl, 'control', 'get').and.returnValue(null);

expect(() => {
fixture.detectChanges();
}).not.toThrow();
});

it('should safely remove errorDiv if already exists', () => {
const control = debugElement.injector.get(NgControl).control;

// Set invalid state to create an errorDiv
control?.setValidators(() => ({ required: true }));
control?.markAsTouched();
control?.updateValueAndValidity();

validationService.validationPassed$
.pipe(take(1)) // Take only the first emission
.subscribe((isValid) => {
expect(isValid).toBe(false); // Initially invalid
done();
});

// Test invalid state
control?.setValue('');
fixture.detectChanges();

// Test valid state
control?.setValue('Valid value');
let errorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(errorDiv).toBeTruthy();

// Re-trigger the update with no errors
control?.clearValidators();
control?.updateValueAndValidity();

fixture.detectChanges();

errorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(errorDiv).toBeNull();
});

it('should set touched when control is invalid', () => {
it('should not throw error if errorDiv is already null', () => {
const directive = debugElement.injector.get(InputValidationDirective);
directive['errorDiv'] = null; // Manually set to null
const control = debugElement.injector.get(NgControl).control;

spyOn(control!, 'markAsTouched');
control?.setValidators(() => ({ required: true }));
control?.markAsTouched();
control?.updateValueAndValidity();

fixture.detectChanges();
expect(() => fixture.detectChanges()).not.toThrow();
});

expect(control?.markAsTouched).toHaveBeenCalled();
it('should handle statusChanges being null gracefully', () => {
const control = debugElement.injector.get(NgControl).control;

// Mock the statusChanges property as null
spyOnProperty(control!, 'statusChanges', 'get').and.returnValue(null as unknown as Observable<FormControlStatus>);

expect(() => {
fixture.detectChanges();
control?.updateValueAndValidity();
}).not.toThrow();
});

it('should not create errorDiv if parentNode is null', () => {
it('should not throw error if control.errors is null', () => {
const control = debugElement.injector.get(NgControl).control;
spyOnProperty(control!, 'errors', 'get').and.returnValue(null);

fixture.detectChanges();

const errorDiv = debugElement.nativeElement.parentNode.querySelector('.error-message');
expect(errorDiv).toBeNull();
});

it('should not add errorDiv if parentNode is null', () => {
spyOnProperty(debugElement.nativeElement, 'parentNode', 'get').and.returnValue(null);

const control = debugElement.injector.get(NgControl).control;
control?.setValidators(() => ({ required: true }));
control?.markAsTouched();
control?.updateValueAndValidity();
Expand Down
8 changes: 2 additions & 6 deletions libs/shared/directives/src/validate-input.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,16 @@ export class InputValidationDirective extends SubscriptionHandler implements OnI
}

private updateErrorMessage(control: AbstractControl | null): void {
// Safely remove the existing errorDiv if it exists
if (this.errorDiv) {
const parent = this.el.nativeElement.parentNode;
if (parent) {
this.renderer.removeChild(parent, this.errorDiv);
}
this.renderer.removeChild(this.el.nativeElement.parentNode, this.errorDiv);
this.errorDiv = null;
}

if (control?.invalid) {
control.markAsTouched();
}

if (control?.touched && control?.invalid && control.errors && Object.keys(control.errors).length && this.el.nativeElement.parentNode) {
if (control?.touched && control?.invalid && control.errors) {
const parent = this.el.nativeElement.parentNode;
if (parent) {
this.errorDiv = this.renderer.createElement('div');
Expand Down

0 comments on commit 9262e7d

Please sign in to comment.