我想使用模板形式以及[min][max]指令,因此我创建了它们并且它们可以工作。但是测试使我感到困惑:验证不是异步执行的,但是在更改了我的值和内容之后,我必须经历以下过程:

component.makeSomeChangeThatInvalidatesMyInput();
// control.invalid = false, expected

fixture.detectChanges();
// control.invalid is still false, not expected

// but if I go do this
fixture.whenStable().then(() => {
  // control.invalid is STILL false, not expected
  fixture.detectChanges();
  // control.invalid now true
  // expect(... .errors ... ) now passes
})

我不明白为什么我什至需要那个whenStable(),更不用说另一个detectChanges()循环了。我在这里想念什么? 为什么要执行此验证需要2个周期的变更检测?

是否以async运行测试都没有关系。

这是我的测试:
@Component({
    selector: 'test-cmp',
    template: `<form>
        <input [max]="maxValue" [(ngModel)]="numValue" name="numValue" #val="ngModel">
        <span class="error" *ngIf="val.invalid">Errors there.</span>
    </form>`
})
class TestMaxDirectiveComponent {
    maxValue: number;
    numValue: number;
}
fdescribe('ValidateMaxDirective', () => {
    let fixture: ComponentFixture<TestMaxDirectiveComponent>;
    let component: TestMaxDirectiveComponent;

    beforeEach(async(() => TestBed.configureTestingModule({
        imports: [FormsModule],
        declarations: [TestMaxDirectiveComponent, ValidateMaxDirective],
    }).compileComponents()
        .then(() => {
            fixture = TestBed.createComponent(TestMaxDirectiveComponent);
            component = fixture.componentInstance;
            return fixture.detectChanges();
        })
    ));
    fit('should have errors even when value is greater than maxValue', async(() => {
        component.numValue = 42;
        component.maxValue = 2;
        fixture.detectChanges();
        fixture.whenStable().then(() => {
            fixture.detectChanges();
            expect(fixture.nativeElement.querySelector('.error')).toBeTruthy();
        });
    }));
});

这是指令本身(简化了一点):
const VALIDATE_MAX_PROVIDER = {
    provide: NG_VALIDATORS, useExisting: forwardRef(() => ValidateMaxDirective), multi: true,
};
@Directive({
    selector: '[max][ngModel]',
    providers: [VALIDATE_MAX_PROVIDER],
})
export class ValidateMaxDirective implements Validator {
    private _max: number | string;
    @Input() get max(): number | string {
        return this._max;
    }
    set max(value: number | string) {
        this._max = value;
    }

    validate(control: AbstractControl): ValidationErrors | null {
        if (isEmptyInputValue(control.value) || isEmptyInputValue(this._max)) {
            return null;  // don't validate empty values to allow optional controls
        }
        const value = parseFloat(control.value);
        return !isNaN(value) && value > this._max ? {'max': {'max': this._max, 'actual': control.value}} : null;
    }
}

我已经在ng new app版本1.6.8和最新Angular 5.2的全新@angular/cli上进行了测试。

最佳答案

经过我们的交谈,我明白了。您问我上面的代码中什么是异步的:
validate()是!

我们看到此方法将control: AbstractControl作为参数

docs中,您会发现它还能处理异步验证的同步行为。

因此,我在这里假设添加该参数会使validate()异步。

反过来,这意味着您需要等待它的return最终评估是否已更改。

...这是唯一可能触发更改的函数,我们在.detectChanges();时依赖它。

并且在任何异步情况下,在javascript值(变量)中,都应使用时间维度来想象它们所拥有的其他任何事物之上。

例如javascript社区中的开发人员已采用“字符串上的大理石”或“电话线上的鸟”的隐喻来帮助解释它们。

共同的主题是生命线/时间表。这是我个人的另一个代表:

angular - 为什么此Angular Validation指令看起来不同步?-LMLPHP

您必须.subscribe().then()才能在水合/返回时执行要执行的操作。

所以当你:

component.makeSomeChangeThatInvalidatesMyInput(); // (1)
fixture.detectChanges();                          // (2)
fixture.whenStable()                              // (3)
.then(() => {                                     // (not a step) :we are now outside the
                        //logic of "order of execution" this code could happen much after.
  fixture.detectChanges();
})
  • 在步骤(2)中,您实际上是在上面的图表中进行了第一次评估,该评估直接进入了尚未发生任何事情的时间轴。
  • ,但是在(不是步骤)您每次都在监听变化(因此可能有许多调用)时,您就在监听。您最终在此处获得了期望值,因为用于评估的代码执行是“按时”进行的,以捕获正确的结果;更好的是,发生的原因是结果的
  • ,是的,只有detectChanges()可以检测到更改,因此在运行detectChanges()之前进行评估(即使在.then()中运行一次)也将返回过早的值。

  • 结果是您的第一个.detectChanges()没有检测到更改,而您的fixture.whenStable().then(() => {fixture.detectChanges()})没有检测到错误,并且是javascript正常运行的结果。

    (其中包括 Jasmine , Jasmine 是纯JavaScript)

    所以你有它!毕竟没有奇怪的行为:)

    希望这可以帮助!

    09-25 17:48