2
votes

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?

2

2 Answers

1
votes

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

0
votes

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