Я хотел использовать шаблонные формы и директивы [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 с @angular/cli версии 1.6.8 и последней версии angular 5.2.
Да, спасибо за мнение. Я знаю, как заставить его работать, я просто задаю вопрос, чтобы выяснить, почему он не работает так, как я думал.
Привет! интересный вопрос! Я знаю, что это может быть немного неприятно слышать, но в данном случае законно никак не обойтись. не могли бы вы выложить свои файлы весьнеповрежденный и включая импорт, пожалуйста? У меня есть идея, почему, но сначала мне нужно ее проверить.
@tatsu cool, спецификация размещена здесь: pastebin.com/gD9Q7CPA и директива здесь: pastebin.com/eUWEkSpV То же самое касается других подобных директив.
ОК. может я чего-то упускаю, но как вы вызываете .detectChanges(); без импорта ChangeDetectorRef?
Это ComponentFixture, у него есть собственный.
проблема, которую я вижу в этом, и снова, пожалуйста, поправьте меня, если я ошибаюсь, заключается в том, что вы не можете настроить ChangeDetectionStrategy на ComponentFixture так же, как на ChangeDetectorRef, или, если можете, это то, что вы должны сделать: angular.io/api/core/ChangeDetectionStrategy#OnPush если я правильно следую, вам нужен ChangeDetectionStrategy.Default
Не совсем. Понимаете, почему обнаружение изменений работает, но только после того, как я вызываю detectChanges (), а затем whenStable (), а затем снова detectChanges ()? Для меня это звучит совершенно неверно. Если я вызываю только первый метод detectChanges, изменение не обнаруживается. Так что здесь не виновата стратегия. Тестовый фреймворк вроде есть.
@Zlatko, поэтому, если я хорошо это читаю: codecraft.tv/courses/angular/unit-testing/asynchronous/… "Только когда все эти ожидающие обещания будут разрешены, он разрешит обещание, возвращенное из whenStable." и angular.io/guide/testing#async-test-with-async, то такое поведение ожидается при работе с асинхронными функциями. вы сможете использовать detectChanges() только внутри обратного вызова .whenStable() и успешно получить текущее состояние. если подумать, вот как работают все подписываемые объекты (включая наблюдаемые).
Итак, что такое асинхронность в приведенном выше коде?





После нашего разговора я понял. Вы спросили меня, что такое асинхронность в приведенном выше коде:
validate() есть!
мы видим, что этот метод принимает control: AbstractControl в качестве параметра
в документы вы обнаружите, что помимо синхронного поведения он обрабатывает асинхронную проверку.
Итак, я исхожу из предположения, что добавление этого параметра превратило validate() в асинхронный.
это, в свою очередь, означает, что вам нужно дождаться появления return, чтобы оценить, были ли изменения или нет.
... Это единственная функция, которая может вызвать изменение, мы зависим от нее, когда используем .detectChanges();.
и в любом случае async в javascript значения (переменные) должны быть представлены с использованием измерения времени поверх всех других, которыми они уже могут обладать.
поскольку такие разработчики в сообществе javascript приняли метафоры «шарики на веревочке» или «птицы на телефонной линии», чтобы помочь их объяснить.
общей темой является линия жизни / временная шкала. Вот еще одно, мое личное представление:
вам понадобится .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 функционирует должным образом.
(включая жасмин, жасмин - это чистый javascript)
Вот и все! в конце концов, странного поведения не было :)
надеюсь это поможет!
Но я использую вакидатор синхронизации. Проверьте токены NG_VALIDATORS и NG_VALIDATORS_ASYNC. Вы должны явно сделать асинхронный валидатор, чтобы angular обрабатывал так. Тем не менее, я награжу вас наградой как лучший ответ, но я не приму ее, так как мой вопрос остается без ответа. Спасибо за все усилия и терпение.
Кроме того, метод проверки - это синхронизация. Он сразу возвращается.
@Хорошо. извините за отклонение от нормы. Я думаю, вы можете быть осторожны, потому что метод, возвращающийся сразу же, не является достаточным индикатором того, синхронизирован он или нет. Но я тебе верю.
Я знаю. Просто, если интерфейс явный (NG_VALIDATORS против NG_VALIDATORS_ASYNC), я не знаю, что я могу сделать больше. Даже если я заменю приведенный выше код на return true, результат будет тот же. Как еще можно добиться большей синхронизации?
Раньше я использовал
whenStable, как и вы, но тесты очень сложны, чтобы получить их правильно для непростых тестовых случаев, таких как тестирование многих условий одно за другим. Я предпочитаю создавать неасинхронные тесты с использованием fakeAsync и / или тика. таким образом у вас будет больше контроля над вашими тестами. вы можете попробовать создать демонстрацию, как я сделал здесь в одном из моих ответов, чтобы вы могли дать людям понять и помочь вам больше: stackblitz.com/edit/angular-testing-c25ezq?file=app/…