Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Clean up async tests, "extensions", and runner code for Jasmine.
- Loading branch information
Showing
3 changed files
with
153 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,116 +1,105 @@ | ||
describe("Throttled observables", function() { | ||
|
||
it("Should notify subscribers asynchronously after writes stop for the specified timeout duration", function() { | ||
var observable = ko.observable('A').extend({ throttle: 50 }); | ||
var notifiedValues = []; | ||
observable.subscribe(function(value) { | ||
notifiedValues.push(value); | ||
it("Should notify subscribers asynchronously after writes stop for the specified timeout duration", function() { | ||
var observable = ko.observable('A').extend({ throttle: 50 }); | ||
var notifiedValues = []; | ||
observable.subscribe(function(value) { | ||
notifiedValues.push(value); | ||
}); | ||
|
||
// Mutate a few times | ||
observable('B'); | ||
observable('C'); | ||
observable('D'); | ||
expect(notifiedValues.length).toEqual(0); // Should not notify synchronously | ||
|
||
// Wait | ||
waits(20); | ||
runs(function() { | ||
// Mutate more | ||
observable('E'); | ||
observable('F'); | ||
expect(notifiedValues.length).toEqual(0); // Should not notify until end of throttle timeout | ||
}); | ||
|
||
// Wait until after timeout | ||
waitsFor(function() { | ||
return notifiedValues.length > 0; | ||
}, 60); | ||
runs(function() { | ||
expect(notifiedValues.length).toEqual(1); | ||
expect(notifiedValues[0]).toEqual("F"); | ||
}); | ||
}); | ||
|
||
runs(function() { | ||
|
||
// Mutate a few times | ||
observable('B'); | ||
observable('C'); | ||
observable('D'); | ||
expect(notifiedValues.length).toEqual(0); // Should not notify synchronously | ||
|
||
// Wait | ||
setTimeout(function() { | ||
// Mutate more | ||
observable('E'); | ||
observable('F'); | ||
expect(notifiedValues.length).toEqual(0); // Should not notify until end of throttle timeout | ||
}, 20); | ||
}); | ||
|
||
waitsFor(function() { | ||
// Wait until after timeout | ||
return notifiedValues.length > 0; | ||
}, 80); | ||
|
||
runs(function() { | ||
expect(notifiedValues.length).toEqual(1); | ||
expect(notifiedValues[0]).toEqual("F"); | ||
}); | ||
|
||
}); | ||
}); | ||
|
||
describe("Throttled dependent observables", function() { | ||
|
||
it("Should notify subscribers asynchronously after dependencies stop updating for the specified timeout duration", function() { | ||
var underlying = ko.observable(); | ||
var asyncDepObs = ko.dependentObservable(function() { | ||
return underlying(); | ||
}).extend({ throttle: 100 }); | ||
var notifiedValues = []; | ||
asyncDepObs.subscribe(function(value) { | ||
notifiedValues.push(value); | ||
}); | ||
|
||
|
||
runs(function() { | ||
// Check initial state | ||
expect(asyncDepObs()).toBeUndefined(); | ||
|
||
// Mutate | ||
underlying('New value'); | ||
expect(asyncDepObs()).toBeUndefined(); // Should not update synchronously | ||
expect(notifiedValues.length).toEqual(0); | ||
|
||
// Wait | ||
setTimeout(function() { | ||
// After 50ms, still shouldn't have evaluated | ||
expect(asyncDepObs()).toBeUndefined(); // Should not update until throttle timeout | ||
it("Should notify subscribers asynchronously after dependencies stop updating for the specified timeout duration", function() { | ||
var underlying = ko.observable(); | ||
var asyncDepObs = ko.dependentObservable(function() { | ||
return underlying(); | ||
}).extend({ throttle: 100 }); | ||
var notifiedValues = []; | ||
asyncDepObs.subscribe(function(value) { | ||
notifiedValues.push(value); | ||
}); | ||
|
||
// Check initial state | ||
expect(asyncDepObs()).toBeUndefined(); | ||
|
||
// Mutate | ||
underlying('New value'); | ||
expect(asyncDepObs()).toBeUndefined(); // Should not update synchronously | ||
expect(notifiedValues.length).toEqual(0); | ||
}, 50); | ||
}); | ||
|
||
waitsFor(function() { | ||
// Now wait for throttle timeout | ||
return notifiedValues.length > 0; | ||
}, 110); | ||
|
||
runs(function() { | ||
expect(asyncDepObs()).toEqual('New value'); | ||
expect(notifiedValues.length).toEqual(1); | ||
expect(notifiedValues[0]).toEqual('New value'); | ||
}); | ||
|
||
}); | ||
|
||
it("Should run evaluator only once when dependencies stop updating for the specified timeout duration", function() { | ||
var evaluationCount = 0; | ||
var someDependency = ko.observable(); | ||
var asyncDepObs = ko.dependentObservable(function() { | ||
evaluationCount++; | ||
return someDependency(); | ||
}).extend({ throttle: 100 }); | ||
|
||
runs(function() { | ||
// Mutate a few times synchronously | ||
expect(evaluationCount).toEqual(1); // Evaluates synchronously when first created, like all dependent observables | ||
someDependency("A"); | ||
someDependency("B"); | ||
someDependency("C"); | ||
expect(evaluationCount).toEqual(1); // Should not re-evaluate synchronously when dependencies update | ||
|
||
// Also mutate async | ||
setTimeout(function() { | ||
someDependency("D"); | ||
expect(evaluationCount).toEqual(1); | ||
}, 10); | ||
// After 50ms, still shouldn't have evaluated | ||
waits(50); | ||
runs(function() { | ||
expect(asyncDepObs()).toBeUndefined(); // Should not update until throttle timeout | ||
expect(notifiedValues.length).toEqual(0); | ||
}); | ||
|
||
// Now wait for throttle timeout | ||
waitsFor(function() { | ||
return notifiedValues.length > 0; | ||
}, 60); | ||
runs(function() { | ||
expect(asyncDepObs()).toEqual('New value'); | ||
expect(notifiedValues.length).toEqual(1); | ||
expect(notifiedValues[0]).toEqual('New value'); | ||
}); | ||
}); | ||
|
||
waitsFor(function() { | ||
// Now wait for throttle timeout | ||
return evaluationCount > 1; | ||
}, 120); | ||
|
||
runs(function() { | ||
expect(evaluationCount).toEqual(2); // Finally, it's evaluated | ||
expect(asyncDepObs()).toEqual("D"); | ||
it("Should run evaluator only once when dependencies stop updating for the specified timeout duration", function() { | ||
var evaluationCount = 0; | ||
var someDependency = ko.observable(); | ||
var asyncDepObs = ko.dependentObservable(function() { | ||
evaluationCount++; | ||
return someDependency(); | ||
}).extend({ throttle: 100 }); | ||
|
||
// Mutate a few times synchronously | ||
expect(evaluationCount).toEqual(1); // Evaluates synchronously when first created, like all dependent observables | ||
someDependency("A"); | ||
someDependency("B"); | ||
someDependency("C"); | ||
expect(evaluationCount).toEqual(1); // Should not re-evaluate synchronously when dependencies update | ||
|
||
// Also mutate async | ||
waits(10); | ||
runs(function() { | ||
someDependency("D"); | ||
expect(evaluationCount).toEqual(1); | ||
}); | ||
|
||
// Now wait for throttle timeout | ||
waitsFor(function() { | ||
return evaluationCount > 1; | ||
}, 110); | ||
runs(function() { | ||
expect(evaluationCount).toEqual(2); // Finally, it's evaluated | ||
expect(asyncDepObs()).toEqual("D"); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,74 @@ | ||
beforeEach(function() { | ||
this.addMatchers({ | ||
toEqualOneOf: function (expectedPossibilities) { | ||
for (var i = 0; i < expectedPossibilities.length; i++) { | ||
jasmine.Matchers.prototype.toEqualOneOf = function (expectedPossibilities) { | ||
for (var i = 0; i < expectedPossibilities.length; i++) { | ||
if (this.env.equals_(this.actual, expectedPossibilities[i])) { | ||
return true; | ||
return true; | ||
} | ||
} | ||
return false; | ||
}, | ||
toContainHtml: function (expectedHtml) { | ||
var cleanedHtml = this.actual.innerHTML.toLowerCase().replace(/\r\n/g, ""); | ||
// IE < 9 strips whitespace immediately following comment nodes. Normalize by doing the same on all browsers. | ||
cleanedHtml = cleanedHtml.replace(/(<!--.*?-->)\s*/g, "$1"); | ||
expectedHtml = expectedHtml.replace(/(<!--.*?-->)\s*/g, "$1"); | ||
// Also remove __ko__ expando properties (for DOM data) - most browsers hide these anyway but IE < 9 includes them in innerHTML | ||
cleanedHtml = cleanedHtml.replace(/ __ko__\d+=\"(ko\d+|null)\"/g, ""); | ||
// Fix explanatory message | ||
this.actual = cleanedHtml; | ||
return cleanedHtml === expectedHtml; | ||
}, | ||
toContainText: function (expectedText) { | ||
var actualText = 'textContent' in this.actual ? this.actual.textContent : this.actual.innerText; | ||
var cleanedActualText = actualText.replace(/\r\n/g, "\n"); | ||
// Fix explanatory message | ||
this.actual = cleanedActualText; | ||
return cleanedActualText === expectedText; | ||
}, | ||
toHaveOwnProperties: function (expectedProperties) { | ||
var ownProperties = []; | ||
for (var prop in this.actual) { | ||
} | ||
return false; | ||
}; | ||
|
||
jasmine.Matchers.prototype.toContainHtml = function (expectedHtml) { | ||
var cleanedHtml = this.actual.innerHTML.toLowerCase().replace(/\r\n/g, ""); | ||
// IE < 9 strips whitespace immediately following comment nodes. Normalize by doing the same on all browsers. | ||
cleanedHtml = cleanedHtml.replace(/(<!--.*?-->)\s*/g, "$1"); | ||
expectedHtml = expectedHtml.replace(/(<!--.*?-->)\s*/g, "$1"); | ||
// Also remove __ko__ expando properties (for DOM data) - most browsers hide these anyway but IE < 9 includes them in innerHTML | ||
cleanedHtml = cleanedHtml.replace(/ __ko__\d+=\"(ko\d+|null)\"/g, ""); | ||
this.actual = cleanedHtml; // Fix explanatory message | ||
return cleanedHtml === expectedHtml; | ||
}; | ||
|
||
jasmine.Matchers.prototype.toContainText = function (expectedText) { | ||
var actualText = 'textContent' in this.actual ? this.actual.textContent : this.actual.innerText; | ||
var cleanedActualText = actualText.replace(/\r\n/g, "\n"); | ||
this.actual = cleanedActualText; // Fix explanatory message | ||
return cleanedActualText === expectedText; | ||
}; | ||
|
||
jasmine.Matchers.prototype.toHaveOwnProperties = function (expectedProperties) { | ||
var ownProperties = []; | ||
for (var prop in this.actual) { | ||
if (this.actual.hasOwnProperty(prop)) { | ||
ownProperties.push(prop); | ||
ownProperties.push(prop); | ||
} | ||
} | ||
return this.env.equals_(ownProperties, expectedProperties); | ||
}, | ||
toHaveSelectedValues: function (expectedValues) { | ||
var selectedNodes = ko.utils.arrayFilter(this.actual.childNodes, function (node) { return node.selected; }), | ||
selectedValues = ko.utils.arrayMap(selectedNodes, function (node) { return ko.selectExtensions.readValue(node); }); | ||
// Fix explanatory message | ||
this.actual = selectedValues; | ||
return this.env.equals_(selectedValues, expectedValues); | ||
} | ||
}); | ||
}); | ||
return this.env.equals_(ownProperties, expectedProperties); | ||
}; | ||
|
||
jasmine.Matchers.prototype.toHaveSelectedValues = function (expectedValues) { | ||
var selectedNodes = ko.utils.arrayFilter(this.actual.childNodes, function (node) { return node.selected; }), | ||
selectedValues = ko.utils.arrayMap(selectedNodes, function (node) { return ko.selectExtensions.readValue(node); }); | ||
this.actual = selectedValues; // Fix explanatory message | ||
return this.env.equals_(selectedValues, expectedValues); | ||
}; | ||
|
||
jasmine.addScriptReference = function(scriptUrl) { | ||
if (window.console) | ||
console.log("Loading " + scriptUrl + "..."); | ||
document.write("<scr" + "ipt type='text/javascript' src='" + scriptUrl + "'></sc" + "ript>"); | ||
if (window.console) | ||
console.log("Loading " + scriptUrl + "..."); | ||
document.write("<scr" + "ipt type='text/javascript' src='" + scriptUrl + "'></sc" + "ript>"); | ||
}; | ||
|
||
jasmine.prepareTestNode = function() { | ||
// The bindings specs make frequent use of this utility function to set up | ||
// a clean new DOM node they can execute code against | ||
var existingNode = document.getElementById("testNode"); | ||
if (existingNode != null) | ||
existingNode.parentNode.removeChild(existingNode); | ||
testNode = document.createElement("div"); | ||
testNode.id = "testNode"; | ||
document.body.appendChild(testNode); | ||
// The bindings specs make frequent use of this utility function to set up | ||
// a clean new DOM node they can execute code against | ||
var existingNode = document.getElementById("testNode"); | ||
if (existingNode != null) | ||
existingNode.parentNode.removeChild(existingNode); | ||
testNode = document.createElement("div"); | ||
testNode.id = "testNode"; | ||
document.body.appendChild(testNode); | ||
}; | ||
|
||
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10. | ||
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser. | ||
// If there is a future need to detect specific versions of IE10+, we will amend this. | ||
jasmine.ieVersion = (function() { | ||
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); | ||
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); | ||
|
||
// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment | ||
while ( | ||
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', | ||
iElems[0] | ||
); | ||
return version > 4 ? version : undefined; | ||
}()); | ||
// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment | ||
while ( | ||
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', | ||
iElems[0] | ||
); | ||
return version > 4 ? version : undefined; | ||
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Did you mean to add this file? It doesn't appear to be in the repo.