Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: registerOnChange is never called while testing component with custom ControlValueAccessor

As I describe in this answer, I created a custom ControlValueAccessor directive to have control on when to fire the onChange event of my component, and everything works perfectly, except when I test it, registerOnChange is never called, and thus, my test fails.

My directive looks like this:

export const MASK_CONTROL_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => MaskDirective),
  multi: true
};

@Directive({
  selector: "[testMask]",
  providers: [MASK_CONTROL_VALUE_ACCESSOR]
})

export class MaskDirective implements ControlValueAccessor {

  private onChange;
  private nativeElement;

  constructor(private element: ElementRef) {
    this.nativeElement = this.element.nativeElement;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
  }
  setDisabledState(isDisabled: boolean): void {
    this.nativeElement.disabled = isDisabled;
  }
  writeValue(newValue) {
    newValue = newValue == null ? "" : newValue;
    this.nativeElement.value = newValue;
  }
  @HostListener("input", ["$event"])
  onInput(event: KeyboardEvent) {
    /*DO YOUR STUFF HERE*/
    // Call onChange to fire the valueChanged listeners
    this.onChange(newValue);
  }
}

And my test:

describe("Test Custom Directive", () => {
  @Component({
    template:
        `<input type="text" [formControl]=inputFormControl testMask/>`
  })
  class TestComponent {

    _inputFormControl: FormControl;

    constructor(private element: ElementRef) {
      this._inputFormControl = new FormControl();
    }

    get inputFormControl() {
      return this._inputFormControl;
    }
  }

  let fixture: ComponentFixture<TestComponent>;
  let inputField: HTMLInputElement;

  const getInput = (fix: ComponentFixture<TestComponent>) => {
    const inputDebug = fix.debugElement.query(By.directive(MaskDirective));
    return inputDebug.nativeElement as HTMLInputElement;
  };

 beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        MaskDirective
      ]
    });
  });
    beforeEach(() => {
      fixture = TestBed.createComponent(TestComponent);
      fixture.detectChanges();
      inputField = getInput(fixture);
    });

    it("test", async () => {
      //Tests fails because there's no onChange callback
    });
}

Based on what I read on "Never again be confused when implementing ControlValueAccessor in Angular forms" I was under the assumption that just adding a FormControl to my input field should've triggered setupControl, but that's apparently not the case. What am I missing?

like image 784
FreemanAMG Avatar asked Sep 06 '25 03:09

FreemanAMG


2 Answers

you need to import FormsModule in the configureTestingModule declaration.

I made a little diferent. By using ReactiveFormsModule and a directive named 'directiveForm'

@Component({
  template: `<form [formGroup]="testForm"><input type="text" formControlName="testControl" directiveForm></form>`
})
class TestComponent {

  public testForm: FormGroup;

  constructor(private fb: FormBuilder) {

    this.testForm = this.fb.group({
      testControl: new FormControl()
    });
  }
}

describe('Directive Test', () => {

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        DirectiveForm,
        TestComponent
      ],
      imports: [DirectiveFormModule],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    }).compileComponents();
  }));

This way works for me

like image 154
Andres Diaz Avatar answered Sep 07 '25 20:09

Andres Diaz


got same problem here, the cause for me was that registerOnChange was invoked after main test and the fix was wrapping TestBed.createComponent(TestComponent) in fakeAsync

like image 32
user656449 Avatar answered Sep 07 '25 21:09

user656449