I have a controller with a watch that uses debounce from lodash to delay filtering a list by 500ms.
$scope.$watch('filter.keywords', _.debounce(function () {
$scope.$apply(function () {
$scope.filtered = _.where(list, filter);
});
}, 500));
I am trying to write a Jasmine test that simulates entering filter keywords that are not found followed by keywords that are found.
My initial attempt was to use $digest after assigning a new value to keywords, which I assume didn't work because of the debounce.
it('should filter list by reference', function () {
expect(scope.filtered).toContain(item);
scope.filter.keywords = 'rubbish';
scope.$digest();
expect(scope.filtered).not.toContain(item);
scope.filter.keywords = 'test';
scope.$digest();
expect(scope.filtered).toContain(item);
});
So I tried using $timeout, but that doesn't work either.
it('should filter list by reference', function () {
expect(scope.filtered).toContain(item);
$timeout(function() {
scope.filter.keywords = 'rubbish';
});
$timeout.flush();
expect(scope.filtered).not.toContain(item);
$timeout(function() {
scope.filter.keywords = 'test';
});
$timeout.flush();
expect(scope.filtered).toContain(item);
});
I have also tried giving $timeout a value greater than the 500ms set on debounce.
How have others solved this problem?
EDIT: I've found a solution which was to wrap the expectation in a $timeout function then call $apply on the scope.
it('should filter list by reference', function () {
expect(scope.filtered).toContain(item);
scope.filter.keywords = 'rubbish';
$timeout(function() {
expect(scope.filtered).not.toContain(item);
});
scope.$apply();
scope.filter.keywords = 'test';
$timeout(function() {
expect(scope.filtered).toContain(item);
});
scope.$apply();
});
I'm still interested to know whether this approach is best though.
This is a bad approach. You should use an angular-specific debounce such as this that uses $timeout instead of setTimeout. That way, you can do
$timeout.flush();
expect(scope.filtered).toContain(item);
and the spec will pass as expected.
I've used this:
beforeEach(function() {
...
spyOn(_, 'debounce').and.callFake(function (fn) {
return function () {
//stack the function (fn) code out of the current thread execution
//this would prevent $apply to be invoked inside the $digest
$timeout(fn);
};
});
});
function digest() {
//let the $watch be invoked
scope.$digest();
//now run the debounced function
$timeout.flush();
}
it('the test', function() {
scope.filter.keywords = ...;
digest();
expect(...);
});
Hope it helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With