siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
1,167 lines (954 loc) • 74.1 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js">/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
<span id='Siesta-Test-More'>/**
</span>@class Siesta.Test.More
A mixin with additional generic assertion methods, which can work cross-platform between browsers and NodeJS.
Is being consumed by {@link Siesta.Test}, so all of them are available in all tests.
*/
Role('Siesta.Test.More', {
requires : [ 'isFailed', 'typeOf', 'on' ],
has : {
autoCheckGlobals : false,
expectedGlobals : Joose.I.Array,
disableGlobalsCheck : false,
browserGlobals : {
init : [
'console',
'getInterface',
'ExtBox1',
'__IE_DEVTOOLBAR_CONSOLE_COMMAND_LINE',
/__BROWSERTOOLS/, // IE11 with console open
'seleniumAlert',
'onload',
'onerror',
'StartTest',
'startTest',
'__loaderInstrumentationHookInstalled__',
'describe',
// will be reported in IE8 after overriding
'setTimeout',
'clearTimeout',
'requestAnimationFrame',
'cancelAnimationFrame',
'__coverage__',
/cov_\w+/
]
},
<span id='Siesta-Test-More-cfg-waitForTimeout'> /**
</span> * @cfg {Number} waitForTimeout Default timeout for `waitFor` (in milliseconds). Default value is 10000.
*/
waitForTimeout : 10000,
waitForPollInterval : 100,
suppressPassedWaitForAssertion : false
},
methods : {
<span id='Siesta-Test-More-method-isGreater'> /**
</span> * This assertion passes, when the comparison of 1st with 2nd, using `>` operator will return `true` and fails otherwise.
*
* @param {Number/Date} value1 The 1st value to compare
* @param {Number/Date} value2 The 2nd value to compare
* @param {String} [desc] The description of the assertion
*/
isGreater : function (value1, value2, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (value1 > value2)
this.pass(desc, {
descTpl : R.get('isGreaterPassTpl'),
value1 : value1,
value2 : value2
})
else
this.fail(desc, {
assertionName : 'isGreater',
got : value1,
need : value2,
needDesc : R.get('needGreaterThan')
})
},
<span id='Siesta-Test-More-method-isLess'> /**
</span> * This assertion passes, when the comparison of 1st with 2nd, using `<` operator will return `true` and fails otherwise.
*
* @param {Number/Date} value1 The 1st value to compare
* @param {Number/Date} value2 The 2nd value to compare
* @param {String} [desc] The description of the assertion
*/
isLess : function (value1, value2, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (value1 < value2)
this.pass(desc, {
descTpl : R.get('isLessPassTpl'),
value1 : value1,
value2 : value2
})
else
this.fail(desc, {
assertionName : 'isLess',
got : value1,
need : value2,
needDesc : R.get('needLessThan')
})
},
isGE : function () {
this.isGreaterOrEqual.apply(this, arguments)
},
<span id='Siesta-Test-More-method-isGreaterOrEqual'> /**
</span> * This assertion passes, when the comparison of 1st with 2nd, using `>=` operator will return `true` and fails otherwise.
*
* It has a synonym - `isGE`.
*
* @param {Number/Date} value1 The 1st value to compare
* @param {Number/Date} value2 The 2nd value to compare
* @param {String} [desc] The description of the assertion
*/
isGreaterOrEqual : function (value1, value2, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (value1 >= value2)
this.pass(desc, {
descTpl : R.get('isGreaterEqualPassTpl'),
value1 : value1,
value2 : value2
})
else
this.fail(desc, {
assertionName : 'isGreaterOrEqual',
got : value1,
need : value2,
needDesc : R.get('needGreaterEqualTo')
})
},
isLE : function () {
this.isLessOrEqual.apply(this, arguments)
},
<span id='Siesta-Test-More-method-isLessOrEqual'> /**
</span> * This assertion passes, when the comparison of 1st with 2nd, using `<=` operator will return `true` and fails otherwise.
*
* It has a synonym - `isLE`.
*
* @param {Number/Date} value1 The 1st value to compare
* @param {Number/Date} value2 The 2nd value to compare
* @param {String} [desc] The description of the assertion
*/
isLessOrEqual : function (value1, value2, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (value1 <= value2)
this.pass(desc, {
descTpl : R.get('isLessEqualPassTpl'),
value1 : value1,
value2 : value2
})
else
this.fail(desc, {
assertionName : 'isLessOrEqual',
got : value1,
need : value2,
needDesc : R.get('needLessEqualTo')
})
},
<span id='Siesta-Test-More-method-isApprox'> /**
</span> * This assertion suppose to compare the numeric values. It passes when the passed values are approximately the same (the difference
* is withing a threshold). A threshold can be provided explicitly (when assertion is called with 4 arguments),
* or it will be set to 5% from the 1st value (when calling assertion with 3 arguments).
*
* @param {Number} value1 The 1st value to compare
* @param {Number} value2 The 2nd value to compare
* @param {Number} threshold The maximum allowed difference between values. This argument can be omitted.
* @param {String} [desc] The description of the assertion
*/
isApprox : function (value1, value2, threshold, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (arguments.length == 2) threshold = Math.abs(value1 * 0.05)
if (arguments.length == 3) {
if (this.typeOf(threshold) == 'String') {
desc = threshold
threshold = Math.abs(value1 * 0.05)
}
}
// this function normalizes the fractional numbers to fixed point presentation
// for example in JS: 1.05 - 1 = 0.050000000000000044
// so what we do is: (1.05 * 10^2 - 1 * 10^2) / 10^2 = (105 - 100) / 100 = 0.05
var subtract = function (value1, value2) {
var fractionalLength = function (v) {
var afterPointPart = (v + '').split('.')[ 1 ]
return afterPointPart && afterPointPart.length || 0
}
var maxLength = Math.max(fractionalLength(value1), fractionalLength(value2))
var k = Math.pow(10, maxLength);
return (value1 * k - value2 * k) / k;
};
if (Math.abs(subtract(value2, value1)) <= threshold)
this.pass(desc, {
descTpl : R.get('isApproxToPassTpl'),
value1 : value1,
value2 : value2,
annotation : value2 == value1 ? R.get('exactMatch') : (R.get('withinThreshold') + ': ' + threshold)
})
else
this.fail(desc, {
assertionName : 'isApprox',
got : value1,
need : value2,
needDesc : R.get('needApprox'),
annotation : R.get('thresholdIs') + ': ' + threshold
})
},
<span id='Siesta-Test-More-method-like'> /**
</span> * This assertion passes when the passed `string` matches to a regular expression `regex`. When `regex` is a string,
* assertion will check that it is a substring of `string`
*
* @param {String} string The string to check for "likeness"
* @param {String/RegExp} regex The regex against which to test the string, can be also a plain string
* @param {String} [desc] The description of the assertion
*/
like : function (string, regex, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(regex) == "RegExp")
if (string.match(regex))
this.pass(desc, {
descTpl : R.get('stringMatchesRe'),
string : string,
regex : regex
})
else
this.fail(desc, {
assertionName : 'like',
got : string,
need : regex,
needDesc : R.get('needStringMatching')
})
else
if (string.indexOf(regex) != -1)
this.pass(desc, {
descTpl : R.get('stringHasSubstring'),
string : string,
regex : regex
})
else
this.fail(desc, {
assertionName : 'like',
got : string,
need : regex,
needDesc : R.get('needStringContaining')
})
},
<span id='Siesta-Test-More-method-unlike'> /**
</span> * This method is the opposite of 'like', it adds failed assertion, when the string matches the passed regex.
*
* @param {String} string The string to check for "unlikeness"
* @param {String/RegExp} regex The regex against which to test the string, can be also a plain string
* @param {String} [desc] The description of the assertion
*/
unlike : function(string, regex, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(regex) == "RegExp")
if (!string.match(regex))
this.pass(desc, {
descTpl : R.get('stringNotMatchesRe'),
string : string,
regex : regex
})
else
this.fail(desc, {
assertionName : 'unlike',
got : string,
need : regex,
needDesc : R.get('needStringNotMatching')
})
else
if (string.indexOf(regex) == -1)
this.pass(desc, {
descTpl : R.get('stringHasNoSubstring'),
string : string,
regex : regex
})
else
this.fail(desc, {
assertionName : 'unlike',
got : string,
need : regex,
needDesc : R.get('needStringNotContaining')
})
},
"throws" : function () {
this.throwsOk.apply(this, arguments)
},
throws_ok : function () {
this.throwsOk.apply(this, arguments)
},
<span id='Siesta-Test-More-method-throwsOk'> /**
</span> * This assertion passes if the `func` function throws an exception during executing, and the
* stringified exception passes the 'like' assertion (with 'expected' parameter).
*
* It has synonyms - `throws_ok` and `throws`.
*
* t.throwsOk(function(){
* throw "oopsie";
* }, 'oopsie', 'Some description text');
*
* See also {@link Siesta.Test#livesOk} method.
*
* @param {Function} func The function which should throw an exception
* @param {String/RegExp} expected The regex against which to test the stringified exception, can be also a plain string
* @param {String} [desc] The description of the assertion
*/
throwsOk : function (func, expected, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(func) != 'Function') throw new Error(R.get('throwsOkInvalid'))
var e = this.getExceptionCatcher()(func)
// assuming no one will throw undefined exception..
if (e === undefined) {
this.fail(desc, {
assertionName : 'throws_ok',
annotation : R.get('didntThrow')
})
return
}
if (e instanceof this.getTestErrorClass())
//IE uses non-standard 'description' property for error msg
e = e.message || e.description
e = '' + e
if (this.typeOf(expected) == "RegExp")
if (e.match(expected))
this.pass(desc, {
descTpl : R.get('exMatchesRe'),
expected : expected
})
else
this.fail(desc, {
assertionName : 'throws_ok',
got : e,
gotDesc : R.get('exceptionStringifiesTo'),
need : expected,
needDesc : R.get('needStringMatching')
})
else
if (e.indexOf(expected) != -1)
this.pass(desc, {
descTpl : R.get('exContainsSubstring'),
expected : expected
})
else
this.fail(desc, {
assertionName : 'throws_ok',
got : e,
gotDesc : R.get('exceptionStringifiesTo'),
need : expected,
needDesc : R.get('needStringContaining')
})
},
lives_ok : function () {
this.livesOk.apply(this, arguments)
},
lives : function () {
this.livesOk.apply(this, arguments)
},
<span id='Siesta-Test-More-method-livesOk'> /**
</span> * This assertion passes, when the supplied `func` function doesn't throw an exception during execution.
*
* See also {@link Siesta.Test#throwsOk} method.
*
* This method has two synonyms: `lives_ok` and `lives`
*
* @param {Function} func The function which is not supposed to throw an exception
* @param {String} [desc] The description of the assertion
*/
livesOk : function (func, desc) {
if (this.typeOf(func) != 'Function') {
func = [ desc, desc = func ][ 0 ]
}
var R = Siesta.Resource('Siesta.Test.More');
var e = this.getExceptionCatcher()(func)
if (e === undefined)
this.pass(desc, {
descTpl : R.get('fnDoesntThrow')
})
else
this.fail(desc, {
assertionName : 'lives_ok',
annotation : R.get('fnThrew') + ': ' + e
})
},
isa_ok : function (value, className, desc) {
this.isInstanceOf(value, className, desc)
},
isaOk : function (value, className, desc) {
this.isInstanceOf(value, className, desc)
},
<span id='Siesta-Test-More-method-isInstanceOf'> /**
</span> * This assertion passes, when the supplied `value` is the instance of the `className`. The check is performed with
* `instanceof` operator. The `className` parameter can be supplied as class constructor or as string, representing the class
* name. In the latter case the `class` will eval'ed to receive the class constructor.
*
* This method has synonyms: `isaOk`, `isa_ok`
*
* @param {Mixed} value The value to check for 'isa' relationship
* @param {Class/String} className The class to check for 'isa' relationship with `value`
* @param {String} [desc] The description of the assertion
*/
isInstanceOf : function (value, className, desc) {
var R = Siesta.Resource('Siesta.Test.More');
try {
if (this.typeOf(className) == 'String') className = this.global.eval(className)
} catch (e) {
this.fail(desc, {
assertionName : 'isa_ok',
annotation : Siesta.Resource('Siesta.Test.Function', 'exceptionEvalutingClass')
})
return
}
if (value instanceof className)
this.pass(desc, {
descTpl : R.get('isInstanceOfPass')
})
else
this.fail(desc, {
assertionName : 'isa_ok',
got : value,
need : String(className),
needDesc : R.get('needInstanceOf')
})
},
<span id='Siesta-Test-More-method-isString'> /**
</span> * This assertion passes, if supplied value is a String.
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isString : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'String')
this.pass(desc, {
descTpl : R.get('isAString'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('aStringValue')
})
},
<span id='Siesta-Test-More-method-isObject'> /**
</span> * This assertion passes, if supplied value is an Object
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isObject : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'Object')
this.pass(desc, {
descTpl : R.get('isAnObject'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('anObject')
})
},
<span id='Siesta-Test-More-method-isArray'> /**
</span> * This assertion passes, if supplied value is an Array
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isArray : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'Array')
this.pass(desc, {
descTpl : R.get('isAnArray'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('anArrayValue')
})
},
<span id='Siesta-Test-More-method-isNumber'> /**
</span> * This assertion passes, if supplied value is a Number.
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isNumber : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'Number')
this.pass(desc, {
descTpl : R.get('isANumber'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('aNumberValue')
})
},
<span id='Siesta-Test-More-method-isBoolean'> /**
</span> * This assertion passes, if supplied value is a Boolean.
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isBoolean : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'Boolean')
this.pass(desc, {
descTpl : R.get('isABoolean'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('aBooleanValue')
})
},
<span id='Siesta-Test-More-method-isDate'> /**
</span> * This assertion passes, if supplied value is a Date.
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isDate : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'Date')
this.pass(desc, {
descTpl : R.get('isADate'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('aDateValue')
})
},
<span id='Siesta-Test-More-method-isRegExp'> /**
</span> * This assertion passes, if supplied value is a RegExp.
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isRegExp : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'RegExp')
this.pass(desc, {
descTpl : R.get('isARe'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('aReValue')
})
},
<span id='Siesta-Test-More-method-isFunction'> /**
</span> * This assertion passes, if supplied value is a Function.
*
* @param {Mixed} value The value to check.
* @param {String} [desc] The description of the assertion
*/
isFunction : function (value, desc) {
var R = Siesta.Resource('Siesta.Test.More');
if (this.typeOf(value) == 'Function' || this.typeOf(value) == 'AsyncFunction')
this.pass(desc, {
descTpl : R.get('isAFunction'),
value : value
})
else
this.fail(desc, {
got : value,
need : R.get('aFunctionValue')
})
},
is_deeply : function (obj1, obj2, desc) {
this.isDeeply.apply(this, arguments)
},
<span id='Siesta-Test-More-method-isDeeply'> /**
</span> * This assertion passes when in-depth comparison of 1st and 2nd arguments (which are assumed to be JSON objects) shows that they are equal.
* Comparison is performed with '==' operator, so `[ 1 ]` and `[ "1" ] objects will be equal. The objects should not contain cyclic references.
*
* This method works correctly with the *placeholders* generated with method {@link #any}.
*
* This method has a synonym: `is_deeply`
*
* @param {Object} obj1 The 1st object to compare
* @param {Object} obj2 The 2nd object to compare
* @param {String} [desc] The description of the assertion
*/
isDeeply : function (obj1, obj2, desc) {
var R = Siesta.Resource('Siesta.Test.More');
var diff
if (this.typeOf(obj1) === this.typeOf(obj2) && this.compareObjects(obj1, obj2)) {
this.pass(desc, {
descTpl : R.get('isDeeplyPassTpl'),
obj1 : obj1,
obj2 : obj2
})
}
// DeepDiff Not supported in IE8
else if (typeof DeepDiff != 'undefined' && (diff = DeepDiff(obj1, obj2))) {
if (diff.length > 5) {
this.diag(R.get('tooManyDifferences', { num : 5, total : diff.length}))
}
for (var i = 0; i < Math.min(diff.length, 5); i++) {
var diffItem = diff[i];
var path = (diffItem.path || []).join('.');
var saw = path ? (path + ': ' + diffItem.lhs) : obj1;
var expected = path ? (path + ': ' + diffItem.rhs) : obj2;
this.fail(desc, {
assertionName : 'isDeeply',
got : saw,
need : expected
})
// Also log it to console for easy inspection
window.console && console.log('DIFF RESULT:', diffItem);
}
} else {
this.fail(desc, {
assertionName : 'isDeeply',
got : obj1,
need : obj2
})
}
},
<span id='Siesta-Test-More-method-isDeeplyStrict'> /**
</span> * This assertion passes when in-depth comparison of 1st and 2nd arguments (which are assumed to be JSON objects) shows that they are equal.
* Comparison is performed with '===' operator, so `[ 1 ]` and `[ "1" ] objects will be different. The objects should not contain cyclic references.
*
* This method works correctly with the *placeholders* generated with method {@link #any}.
*
* @param {Object} obj1 The 1st object to compare
* @param {Object} obj2 The 2nd object to compare
* @param {String} [desc] The description of the assertion
*/
isDeeplyStrict : function (obj1, obj2, desc) {
if (this.typeOf(obj1) === this.typeOf(obj2) && this.compareObjects(obj1, obj2, true)) {
var R = Siesta.Resource('Siesta.Test.More');
this.pass(desc, {
descTpl : R.get('isDeeplyStrictPassTpl'),
obj1 : obj1,
obj2 : obj2
})
}
else
this.fail(desc, {
assertionName : 'isDeeplyStrict',
got : obj1,
need : obj2
})
},
expectGlobal : function () {
this.expectGlobals.apply(this, arguments)
},
<span id='Siesta-Test-More-method-expectGlobals'> /**
</span> * This method accepts a variable number of names of expected properties in the global scope. When verifying the globals with {@link #verifyGlobals}
* assertions, the expected gloabls will not be counted as failed assertions.
*
* This method has a synonym with singular name: `expectGlobal`
*
* @param {String/RegExp} name1 The name of global property or the regular expression to match several properties
* @param {String/RegExp} name2 The name of global property or the regular expression to match several properties
* @param {String/RegExp} nameN The name of global property or the regular expression to match several properties
*/
expectGlobals : function () {
this.expectedGlobals.push.apply(this.expectedGlobals, arguments)
},
isGlobalExpected : function (name, index) {
var me = this
if (!index || index && !index.expectedStrings) {
if (!index) index = {}
Joose.O.extend(index, {
expectedStrings : {},
expectedRegExps : []
})
Joose.A.each(this.expectedGlobals.concat(this.browserGlobals), function (value) {
if (me.typeOf(value) == 'RegExp')
index.expectedRegExps.push(value)
else
index.expectedStrings[ value ] = true
})
}
if (index.expectedStrings[ name ]) return true
var imageWithIdCreatesGlobalEnumerable = Siesta.Project.Browser.FeatureSupport().supports.imageWithIdCreatesGlobalEnumerable;
// remove after https://bugzilla.mozilla.org/show_bug.cgi?id=959992 will be fixed
if (imageWithIdCreatesGlobalEnumerable) {
var domEl = this.global.document.getElementById(name)
if (domEl && domEl.tagName.toLowerCase() == 'img') return true;
}
for (var i = 0; i < index.expectedRegExps.length; i++)
if (index.expectedRegExps[ i ].test(name)) return true
return false
},
forEachUnexpectedGlobal : function (func, scope) {
scope = scope || this
var index = {}
for (var name in this.global)
if (!this.isGlobalExpected(name, index)) {
if (func.call(scope, name) === false) {
break;
}
}
},
<span id='Siesta-Test-More-method-verifyGlobals'> /**
</span> * This method accepts a variable number of names of expected properties in the global scope and then performs a globals check.
*
* It will scan all globals properties in the scope of test and compare them with the list of expected globals. Expected globals can be provided with:
* {@link #expectGlobals} method or {@link Siesta.Project#expectedGlobals expectedGlobals} configuration option of project.
*
* You can enable this assertion to automatically happen at the end of each test, using {@link Siesta.Project#autoCheckGlobals autoCheckGlobals} option of the project.
*
* @param {String/RegExp} name1 The name of global property or the regular expression to match several properties
* @param {String/RegExp} name2 The name of global property or the regular expression to match several properties
* @param {String/RegExp} nameN The name of global property or the regular expression to match several properties
*/
verifyGlobals : function () {
var R = Siesta.Resource('Siesta.Test.More');
if (this.disableGlobalsCheck) {
this.diag(R.get('globalCheckNotSupported'));
return
}
this.expectGlobals.apply(this, arguments)
this.diag(R.get('globalVariables'))
var failed = false
var i = 0
this.forEachUnexpectedGlobal(function (name) {
this.fail(
R.get('globalFound'),
R.get('globalName') + ': ' + name + ', ' + R.get('value') + ': ' + Siesta.Util.Serializer.stringify(this.global[ name ])
)
failed = true
return i++ < 50 // Only report first 50 globals to protect against legacy apps with thousands of globals
})
if (!failed) this.pass(R.get('noGlobalsFound'))
},
// will create a half-realized, "phantom", "isWaitFor" assertion, which is only purposed
// for user to get the instant feedback about "waitFor" actions
// this assertion will be "finalized" and added to the test results in the "finalizeWaiting"
startWaiting : function (description, sourceLine) {
var result = new Siesta.Result.Assertion({
description : description,
isWaitFor : true,
sourceLine : sourceLine
});
this.fireEvent('testupdate', this, result, this.getResults())
return result;
},
finalizeWaiting : function (result, passed, desc, annotation, errback, suppressPassedWaitForAssertion) {
// Treat this is an ordinary assertion from now on
result.completed = true;
if (passed) {
if (this.suppressPassedWaitForAssertion || suppressPassedWaitForAssertion) {
// Make sure UI is updated and the "noise" is removed
this.fireEvent('assertiondiscard', this, result)
} else {
this.pass(desc, annotation, result)
}
}
else {
this.fail(desc, annotation, result);
errback && errback()
}
},
conditionCheckerToString : function (checker) {
if (this.typeOf(checker) !== 'Function') return ''
var sources = checker.toString().split('\n')
var minCommonLeadingWhitespace = Infinity
Joose.A.each(sources, function (line, index) {
// ignore first line, which won't have the common leading whitespace
if (index === 0) return
var leadingWhitespaceMatch = /^(\s*)/.exec(line)
if (leadingWhitespaceMatch) {
var leadingWhitespace = leadingWhitespaceMatch[ 1 ]
// ignore whitespace-only lines
if (leadingWhitespace === line) return
if (leadingWhitespace.length < minCommonLeadingWhitespace) minCommonLeadingWhitespace = leadingWhitespace.length
}
})
if (minCommonLeadingWhitespace < Infinity) Joose.A.each(sources, function (line, index) {
// ignore first line, which won't have the common leading whitespace
if (index === 0) return
sources[ index ] = line.slice(minCommonLeadingWhitespace)
})
return '[code]' + sources.join('\n') + '[/code]'
},
<span id='Siesta-Test-More-method-waitFor'> /**
</span> * Waits for passed checker method to return true (or any non-false value, like for example DOM element or array), and calls the callback when this happens.
* As an additional feature, the callback will receive the result from the checker method as the 1st argument.
*
t.waitFor(
function () { return document.getElementById('someEl') },
function (el) {
// waited for element #someEl to appear
// element will be available in the callback as 1st argument "el"
}
)
* You can also call this method with a single Object having the following properties: `method`, `callback`, `scope`, `timeout`, `interval`, `description`:
t.waitFor({
method : function () { return document.getElementById('someEl') },
callback : function (el) {
// waited for element #someEl to appear
// element will be available in the callback as 1st argument "el"
}
})
*
* @param {Function/Number/Object} condition Either a function which should return true (or any other "truthy" value) when a certain condition has been fulfilled,
* or a number of ms to wait before calling the callback. Can be also an object with the following properties:
* @param {Function} condition.callback A function to call when the condition has been met. Will receive a result from checker function.
* @param {Function} condition.method A condition checker function.
* @param {Object} condition.scope The scope for the callback.
* @param {Number} condition.timeout The maximum amount of time (in milliseconds) to wait for the condition to be fulfilled.
* @param {Number} condition.interval The polling interval (in milliseconds)
* @param {String} condition.description The assertion description
*
* @param {Function} callback A function to call when the condition has been met. Will receive a result from checker function.
* @param {Object} scope The scope for the callback
* @param {Int} timeout The maximum amount of time (in milliseconds) to wait for the condition to be fulfilled.
* Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value. If condition is not fullfilled within this time, a failed assertion will be added to the test.
* @param {Int} [interval=100] The polling interval (in milliseconds)
*
* @return {Promise} A promise which will be resolved when wait completes (either successfully or by timeout). In case of successfull resolution
* promise will be resolved to the result from the checker function. Additionally it has a `force` property as noted below.
* @return {Function} return.force A function, that will force this wait operation to immediately complete (and call the callback).
* No call to checker will be performed and callback will not receive a result from it.
*/
waitFor : function (method, callback, scope, timeout, interval) {
var R = Siesta.Resource('Siesta.Test.More');
var description = ' ' + R.get('conditionToBeFulfilled');
var assertionName = 'waitFor';
var me = this;
var sourceLine = me.getSourceLine();
var originalSetTimeout = me.originalSetTimeout;
var originalClearTimeout = me.originalClearTimeout;
var errback;
var suppressAssertion;
if (arguments.length === 1 && this.typeOf(method) == 'Object') {
var options = method;
method = options.method;
callback = options.callback;
scope = options.scope;
timeout = options.timeout;
interval = options.interval
description = options.description || description;
assertionName = options.assertionName || assertionName;
suppressAssertion = options.suppressAssertion;
// errback is called in case "waitFor" has failed
errback = options.errback
} else
options = {}
var isWaitingForTime = this.typeOf(method) == 'Number'
callback = callback || function () {}
description = isWaitingForTime ? (method + ' ' + R.get('ms')) : description;
var pollTimeout
// early notification about the started "waitFor" operation
var waitAssertion = me.startWaiting(R.get('waitingFor') + ' ' + description, sourceLine);
interval = interval || this.waitForPollInterval
timeout = timeout || this.waitForTimeout
var resolve
var res = new Promise(function (resolution) {
resolve = resolution
})
// this async frame is not supposed to fail, because it's delayed to `timeout + 3 * interval`
// failure supposed to be generated in the "pollFunc" and this async frame to be closed
// however, in IE the async frame may end earlier than failure from "pollFunc"
// in such case we report the same error as in "pollFunc"
var async = this.beginAsync((isWaitingForTime ? method : timeout) + 3 * interval, function () {
isDone = true
originalClearTimeout(pollTimeout)
me.finalizeWaiting(waitAssertion, false, R.get('waitedTooLong') + ': ' + description, {
assertionName : assertionName,
annotation : me.typeOf(options.annotation) === 'Function' ? options.annotation() : R.get('conditionNotFulfilled') + ' ' + timeout + R.get('ms') + '. \n\n' + me.conditionCheckerToString(method)
}, errback, suppressAssertion)
resolve()
return true
})
var isDone = false
var beforeFinalizeListener
// stop polling, if this test instance has finalized (probably because of exception)
this.on('beforetestfinalize', beforeFinalizeListener = function () {
if (!isDone) {
isDone = true
me.finalizeWaiting(waitAssertion, false, R.get('waitingAborted'), null, null, suppressAssertion);
me.endAsync(async)
originalClearTimeout(pollTimeout)
}
}, null, { single : true })
if (isWaitingForTime) {
if (method < 0) {
throw new Error('Cannot wait for a negative amount of time');
}
pollTimeout = originalSetTimeout(function() {
isDone = true
me.un('beforetestfinalize', beforeFinalizeListener)
me.finalizeWaiting(waitAssertion, true, R.get('Waited') + ' ' + method + ' ' + R.get('ms'), null, null, suppressAssertion || method === 0);
me.endAsync(async);
me.processCallbackFromTest(callback, [], scope || me)
resolve()
}, method);
} else {
var result;
var startDate = new Date()
var pollFunc = function () {
var time = new Date() - startDate;
if (time > timeout) {
me.endAsync(async);
isDone = true
try {
me.un('beforetestfinalize', beforeFinalizeListener)
me.finalizeWaiting(waitAssertion, false, R.get('waitedTooLong') + ': ' + description, {
assertionName : assertionName,
annotation : me.typeOf(options.annotation) === 'Function' ? options.annotation() : R.get('conditionNotFulfilled') + ' ' + timeout + R.get('ms') + '. \n\n' + me.conditionCheckerToString(method)
}, errback, suppressAssertion)
} catch (e) {
if (!/__SIESTA_TEST_EXIT_EXCEPTION__/.test(String(e))) throw e
}
resolve()
return
}
try {
result = method.call(scope || me);
} catch (e) {
me.endAsync(async);
try {
me.un('beforetestfinalize', beforeFinalizeListener)
me.finalizeWaiting(waitAssertion, false, assertionName + ' ' + R.get('checkerException'), {
assertionName : assertionName,
annotation : me.stringifyException(e)
}, errback, suppressAssertion)
} catch (e) {
if (!/__SIESTA_TEST_EXIT_EXCEPTION__/.test(String(e))) throw e
}
isDone = true
resolve()
return
}
if (result != null && result !== false) {
me.endAsync(async);
isDone = true
me.un('beforetestfinalize', beforeFinalizeListener)
me.finalizeWaiting(
waitAssertion,
true,
R.get('Waited') + ' ' + time + ' ' + R.get('msFor') + ' ' + description,
me.typeOf(options.annotation) === 'Function' ? options.annotation() : null,
null,
// always add assertion (set "suppress" to false), if user has provided description