无头测试中未提供嵌套的@Self() ngControl

Nested @Self() ngControl not provided in headless tests

我有一个带有构造函数的通用输入组件

constructor(private fg: FormGroupDirective, @Self() public ngControl: NgControl) {
    if (this.ngControl) {
      this.ngControl.valueAccessor = this;
    }
}

这在我的 angular 组件中使用:

<!-- Email -->
    <app-input
      ngDefaultControl
      formControlName="email"
      [additionalClasses]="['mb-32']"
      [idPrefix]="'email'"
      [label]="'Email address'"
      [maxLength]="100"
      [size]="fullWidthInput">
    </app-input>

自定义输入组件继承自一个BaseInput组件

@Component({ template: `` })
export abstract class BaseInputComponent implements ControlValueAccessor, OnDestroy {
  @Input() additionalClasses: string[] = [];
  @Input() additionalInputClasses: string[] = [];
  @Input() additionalLabelClasses: string[] = [];
  @Input() idPrefix: string;
  @Input() label: string;
  @Input() required = true;
  @Input() size: InputSize = InputSize.Small;

  protected ngUnsubscribe = new Subject<void>();

  constructor(private fg: FormGroupDirective, @Self() protected ngControl: NgControl) {
    if (this.ngControl) {
      this.ngControl.valueAccessor = this;
    }
  }

  get errorId(): string {
    return `${this.idPrefix}-error`;
  }

  get errorMessage(): string {
    return InputErrors.getErrorMessageForControl(this.ngControl, this.label);
  }

  get formControl(): AbstractControl | null {
    return this.ngControl?.control;
  }

  get inputId(): string {
    return `${this.idPrefix}-input`;
  }

  get labelClasses(): string {
    return this.additionalLabelClasses.join(' ');
  }

  get isInvalid(): boolean {
    return CustomValidation.isFormControlInvalid(this.ngControl, this.fg);
  }

  get labelId(): string {
    return `${this.idPrefix}-label`;
  }

  get parentForm(): FormGroup {
    return this.fg.form;
  }

  get value(): any {
    return this.ngControl.value;
  }

  set value(v: any) {
    this.propagateChange(v);
    this.propagateTouched();
  }

  get wrapperClasses(): string[] {
    const classes = ['input', this.size.toString(), ...this.additionalClasses];

    if (this.isInvalid) {
      classes.push('ft-invalid');
    }

    return classes;
  }

  // propagate changes to form control
  propagateChange = (_: string) => {};

  // propagate touched changes to form control
  propagateTouched = () => {};

  // From ControlValueAccessor interface
  registerOnChange(fn: any): void {
    this.propagateChange = fn;
  }

  // From ControlValueAccessor interface
  registerOnTouched(fn: any): void {
    this.propagateTouched = fn;
  }

  // From ControlValueAccessor interface
  writeValue(value: string): void {
    if (value && value !== this.value) {
      this.value = value;
    }
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
}

自定义输入组件:

<mat-form-field [ngClass]="inputClasses" [floatLabel]="'never'" *ngIf="!readonly; else ReadonlyInput">
    <mat-placeholder *ngIf="placeholder">{{ placeholder }}</mat-placeholder>
    <input
      matInput
      [id]="inputId"
      [formControl]="formControl"
      [maxLength]="maxLength"
      [type]="type"
      [value]="value"
      (input)="propagateChange($event.target.value)"
      (keyup.enter)="onEnter()" />
    <div *ngIf="isInvalid" [id]="errorId">
      <mat-error>{{ errorMessage }}</mat-error>
    </div>
  </mat-form-field>

get value() 是导致错误的原因(我认为),因为注入的 ngControl 未定义。

我有一组 Karma/Jasmine 测试。当 运行 在本地命令 ng test 时,浏览器打开,所有测试通过并且控制台中没有错误或警告。

在DevOps中运行命令运行时,ng test --codeCoverage=true --watch=false --browsers ChromeHeadless,(在DevOps和本地),显示以下错误:

TypeError: Cannot read property 'value' of undefined
            at <Jasmine>
            at InputComponent.get value [as value] (http://localhost:9877/_karma_webpack_/src/app/shared/components/form/base-input.component.ts:72:4)
            at InputComponent_mat_form_field_2_Template (ng:///InputComponent.js:83:62)
            at executeTemplate (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7447:1)
            at refreshView (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7316:1)
            at refreshEmbeddedViews (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:8408:1)
            at refreshView (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7340:1)
            at refreshComponent (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:8454:1)
            at refreshChildComponents (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7109:1)
            at refreshView (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7366:1)
            at refreshComponent (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:8454:1)
        TypeError: Cannot read property 'getCheckedValue' of undefined
            at <Jasmine>
            at http://localhost:9877/_karma_webpack_/src/app/users/components/new-user-dialog/new-user-dialog.component.spec.ts:101:55
            at <Jasmine>
            at http://localhost:9877/_karma_webpack_/main.js:364670:71
            at new ZoneAwarePromise (http://localhost:9877/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:960:1)
            at ./src/app/users/components/new-user-dialog/new-user-dialog.component.spec.ts.__awaiter (http://localhost:9877/_karma_webpack_/main.js:364666:12)
            at UserContext.<anonymous> (http://localhost:9877/_karma_webpack_/src/app/users/components/new-user-dialog/new-user-dialog.component.spec.ts:100:47)
            at ZoneDelegate.invoke (http://localhost:9877/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9877/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
            at ZoneDelegate.invoke (http://localhost:9877/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:363:1)

谁能帮我解决一下?这适用于浏览器的事实让我很困惑——所有依赖注入等似乎都配置正确。

对于任何看到这个,看到同样问题的人,问题出在基础组件中的构造函数上。

在基础组件中:

constructor(private fg: FormGroupDirective, @Self() protected ngControl: NgControl) {
  if (this.ngControl) {
    this.ngControl.valueAccessor = this;
  }
}

而在输入组件中,根本没有构造函数。对于无头测试,这似乎是必需的。

constructor(fg: FormGroupDirective, @Self() ngControl: NgControl) {
  super(fg, ngControl);
}