detectChanges() 无法在使用 ControlValueAccessor 的 Angular 独立组件测试中工作

问题描述 投票:0回答:1

我为一个简单的自定义表单控件组件“my-input”(实现 ControlValueAccessor 接口)编写了一个组件测试,该组件仅包含一个输入字段。

在组件测试中,我通过“ngModel”传递内部输入字段的值,我希望该值出现在输入字段中,但事实并非如此。

我的输入.component.html

<input [(ngModel)]="value" />

我的输入.component.ts

import { Component, forwardRef } from '@angular/core';
import {
  ControlValueAccessor,
  FormsModule,
  NG_VALUE_ACCESSOR,
} from '@angular/forms';

@Component({
  selector: 'my-input',
  templateUrl: './my-input.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MyInputComponent),
      multi: true,
    },
  ],
  standalone: true,
  imports: [FormsModule],
})
export class MyInputComponent implements ControlValueAccessor {
  inputValue?: string;

  onChange: any = () => {};
  onTouch: any = () => {};

  public get value(): string {
    return this.inputValue || '';
  }

  public set value(value: string) {
    this.inputValue = value;
    this.onChange(this.inputValue);
    this.onTouch(this.inputValue);
  }

  writeValue(value: string) {
    this.inputValue = value;
    this.onChange(this.value);
  }

  registerOnChange(fn: any) {
    this.onChange = fn;
  }

  registerOnTouched(fn: any) {
    this.onTouch = fn;
  }
}

我的输入.组件.spec.ts

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

import { MyInputComponent } from './my-input.component';
import { Component, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

describe('MyInputComponent', () => {
  @Component({
    imports: [MyInputComponent, FormsModule],
    standalone: true,
    template: `<my-input [(ngModel)]="someValue"></my-input>`,
  })
  class TestHostComponent {
    someValue?: string;
  }

  let hostComponent: TestHostComponent;
  let hostFixture: ComponentFixture<TestHostComponent>;
  let componentDe: DebugElement;

  beforeEach(async () => {
    await TestBed.compileComponents();
  });

  beforeEach(() => {
    hostFixture = TestBed.createComponent(TestHostComponent);
    hostComponent = hostFixture.componentInstance;
    componentDe = hostFixture.debugElement;
    hostFixture.detectChanges();
  });

  describe('when setting a value via ngModel', () => {
    it('should set its value correctly', async () => {
      const inputElement: HTMLInputElement = componentDe.query(
        By.css('input')
      ).nativeElement;

      expect(inputElement).toBeDefined();
      expect(inputElement.value).toEqual('');

      hostComponent.someValue = 'Hello';

      hostFixture.detectChanges();
      await hostFixture.whenStable();

      expect(inputElement.value).toEqual('Hello');
    });
  });
});

我在 Angular 应用程序中测试了“我的输入”组件,效果很好:

main.ts

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { MyInputComponent } from './my-input-component/my-input.component';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MyInputComponent, FormsModule],
  template: `
    <h1>Hello from {{ name }}!</h1>
    <my-input [(ngModel)]="name"></my-input>
  `,
})
export class App {
  name = 'Angular';
}

bootstrapApplication(App);

您可以在 Stackblitz 上尝试:https://stackblitz.com/edit/stackblitz-starters-2uoepe?file=src%2Fmy-input-component%2Fmy-input.component.spec.ts

angular data-binding karma-jasmine
1个回答
0
投票

由于某种原因,我需要调用

hostFixture.detectChanges(); await hostFixture.whenStable();
两次,才能使测试成功。

it('should set its value correctly', async () => {
     const inputElement: HTMLInputElement = componentDe.query(
       By.css('input')
     ).nativeElement;

     expect(inputElement).toBeDefined();
     expect(inputElement.value).toEqual('');

     hostComponent.someValue = 'Hello';

     hostFixture.detectChanges();
     await hostFixture.whenStable();
     hostFixture.detectChanges();
     await hostFixture.whenStable();

     expect(inputElement.value).toEqual('Hello');
});
© www.soinside.com 2019 - 2024. All rights reserved.