UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

431 lines (355 loc) 18.5 kB
<!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-Observable'>/** </span>@class Siesta.Test.Observable This is a mixin, with assertions/ helper methods for testing observable pattern (in NodeJS world known as `EventEmitter`). */ Role(&#39;Siesta.Test.Observable&#39;, { does : [ Siesta.Util.Role.CanGetType ], requires : [ &#39;addListenerToObservable&#39;, &#39;removeListenerFromObservable&#39;, &#39;getSourceLine&#39;, &#39;pass&#39;, &#39;fail&#39;, &#39;processCallbackFromTest&#39; ], has : { }, methods : { <span id='Siesta-Test-Observable-method-waitForEvent'> /** </span> * This method will wait for the first browser `event`, fired by the provided `observable` and will then call the provided callback. * * @param {Mixed} observable Any browser observable, window object, element instances, CSS selector. * @param {String} event The name of the event to wait for * @param {Function} callback The callback to call * @param {Object} scope The scope for the callback * @param {Number} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value. */ waitForEvent : function (observable, event, callback, scope, timeout) { var me = this var R = Siesta.Resource(&#39;Siesta.Test.Browser&#39;); var eventFired = false var listener = function () { eventFired = true } this.addListenerToObservable(observable, event, listener) return this.waitFor({ method : function() { return eventFired; }, callback : function () { me.removeListenerFromObservable(observable, event, listener); // `scope` will be already applied callback &amp;&amp; callback.apply(this, arguments) }, scope : scope, timeout : timeout, assertionName : &#39;waitForEvent&#39;, description : &#39; &#39; + R.get(&#39;waitForEvent&#39;) + &#39; &quot;&#39; + event + &#39;&quot; &#39; + R.get(&#39;event&#39;) }); }, <span id='Siesta-Test-Observable-method-firesOk'> /** </span> * This assertion verifies the number of certain events fired by the provided observable instance during provided function (possibly `async`) or time period. * * For example: * t.firesOk({ observable : store, events : { update : 1, add : 2, datachanged : &#39;&gt; 1&#39; }, during : function () { store.getAt(0).set(&#39;Foo&#39;, &#39;Bar&#39;); store.add({ FooBar : &#39;BazQuix&#39; }) store.add({ Foo : &#39;Baz&#39; }) }, desc : &#39;Correct events fired&#39; }) // or async await t.firesOk({ observable : someObservable, events : { datachanged : &#39;&gt; 1&#39; }, during : async () =&gt; { await someObservable.loadData() }, desc : &#39;Correct events fired&#39; }) // or t.firesOk({ observable : store, events : { update : 1, add : 2, datachanged : &#39;&gt;= 1&#39; }, during : 1 }) store.getAt(0).set(&#39;Foo&#39;, &#39;Bar&#39;); store.add({ FooBar : &#39;BazQuix&#39; }) store.add({ Foo : &#39;Baz&#39; }) * * Normally this method accepts a single object with various options (as shown above), but also can be called in 2 additional shortcuts forms: * // 1st form for multiple events t.firesOk(observable, { event1 : 1, event2 : &#39;&gt;1&#39; }, description) // 2nd form for single event t.firesOk(observable, eventName, 1, description) t.firesOk(observable, eventName, &#39;&gt;1&#39;, description) * * In both forms, `during` is assumed to be undefined and `description` is optional. * * @param {Object} options An obect with the following properties: * @param {Ext.util.Observable/Ext.Element/HTMLElement} options.observable Any browser observable, window object, element instances, CSS selector. * @param {Object} options.events The object, properties of which corresponds to event names and values - to expected * number of this event triggering. If value of some property is a number then exact that number of events is expected. If value * of some property is a string starting with one of the comparison operators like &quot;\&lt;&quot;, &quot;\&lt;=&quot;, &quot;==&quot; etc and followed by the number * then Siesta will perform that comparison with the number of actualy fired events. * @param {Number/Function} [options.during] If provided as a number denotes the number of milliseconds during which * this assertion will &quot;record&quot; the events from observable, if provided as regular function - then this assertion will &quot;record&quot; * only events fired during execution of this function (`async` functions are supported, in this case, don&#39;t forget to `await` * on the assertion call itself). If not provided at all - assertions are recorded until the end of * current test (or sub-test) * @param {Function} [options.callback] A callback to call after this assertion has been checked. Only used if `during` value is provided. * @param {String} [options.desc] A description for this assertion */ firesOk: function (options, events, n, timeOut, func, desc, callback) { // | backward compat arguments | var me = this; var sourceLine = me.getSourceLine(); var R = Siesta.Resource(&#39;Siesta.Test.Browser&#39;); var nbrArgs = arguments.length var observable, during if (nbrArgs == 1) { observable = options.observable events = options.events during = options.during desc = options.desc || options.description callback = options.callback timeOut = this.typeOf(during) == &#39;Number&#39; ? during : null func = /Function/.test(this.typeOf(during)) ? during : null } else if (nbrArgs &gt;= 5) { // old signature, backward compat observable = options if (this.typeOf(events) == &#39;String&#39;) { var obj = {} obj[ events ] = n events = obj } } else if (nbrArgs &lt;= 3 &amp;&amp; this.typeOf(events) == &#39;Object&#39;) { // shortcut form 1 observable = options desc = n } else if (nbrArgs &lt;= 4 &amp;&amp; this.typeOf(events) == &#39;String&#39;) { // shortcut form 2 observable = options var obj = {} obj[ events ] = n events = obj desc = timeOut timeOut = null } else throw new Error(R.get(&#39;unrecognizedSignature&#39;)) // start recording var counters = {}; var countFuncs = {}; Joose.O.each(events, function (expected, eventName) { counters[ eventName ] = 0 var countFunc = countFuncs[ eventName ] = function () { counters[ eventName ]++ } me.addListenerToObservable(observable, eventName, countFunc); }) // stop recording and verify the results var stopRecording = function () { Joose.O.each(events, function (expected, eventName) { me.removeListenerFromObservable(observable, eventName, countFuncs[ eventName ]); var actualNumber = counters[ eventName ] if (me.verifyExpectedNumber(actualNumber, expected)) me.pass(desc, { descTpl : R.get(&#39;observableFired&#39;) + &#39; &#39; + actualNumber + &#39; `&#39; + eventName + &#39;` &#39; + R.get(&#39;events&#39;) }); else me.fail(desc, { assertionName : &#39;firesOk&#39;, sourceLine : sourceLine, descTpl : R.get(&#39;observableFiredOk&#39;) + &#39; `&#39; + eventName + &#39;` &#39; + R.get(&#39;events&#39;), got : actualNumber, gotDesc : R.get(&#39;actualNbrEvents&#39;), need : expected, needDesc : R.get(&#39;expectedNbrEvents&#39;) }); }) } if (timeOut) { var async = this.beginAsync(timeOut + 100); var originalSetTimeout = this.originalSetTimeout; originalSetTimeout(function () { me.endAsync(async); stopRecording() me.processCallbackFromTest(callback); }, timeOut); } else if (func) { var typeOf = this.typeOf(func) var cont = function () { stopRecording() me.processCallbackFromTest(callback) } if (typeOf === &#39;Function&#39;) { var res = func() if (me.typeOf(res) === &#39;Promise&#39; || me.global.Promise &amp;&amp; (res instanceof me.global.Promise)) { return res.then(cont, cont) } else cont() } else if (typeOf === &#39;AsyncFunction&#39;) { return func().then(cont, cont) } } else { this.on(&#39;beforetestfinalizeearly&#39;, stopRecording) } }, <span id='Siesta-Test-Observable-method-willFireNTimes'> /** </span> * This assertion passes if the observable fires the specified event exactly (n) times during the test execution. * * @param {Ext.util.Observable/Ext.Element/HTMLElement} observable The observable instance * @param {String} event The name of event * @param {Number} n The expected number of events to be fired * @param {String} [desc] The description of the assertion. */ willFireNTimes: function (observable, event, n, desc, isGreaterEqual) { this.firesOk(observable, event, isGreaterEqual ? &#39;&gt;=&#39; + n : n, desc) }, getObjectWithExpectedEvents : function (event, expected) { var events = {} if (this.typeOf(event) == &#39;Array&#39;) Joose.A.each(event, function (eventName) { events[ eventName ] = expected }) else events[ event ] = expected return events }, <span id='Siesta-Test-Observable-method-wontFire'> /** </span> * This assertion passes if the observable does not fire the specified event(s) after calling this method. * * @param {Mixed} observable Any browser observable, window object, element instances, CSS selector. * @param {String/Array[String]} event The name of event or array of such * @param {String} [desc] The description of the assertion. */ wontFire : function(observable, event, desc) { this.firesOk({ observable : observable, events : this.getObjectWithExpectedEvents(event, 0), desc : desc }); }, <span id='Siesta-Test-Observable-method-firesOnce'> /** </span> * This assertion passes if the observable fires the specified event exactly once after calling this method. * * @param {Mixed} observable Any browser observable, window object, element instances, CSS selector. * @param {String/Array[String]} event The name of event or array of such * @param {String} [desc] The description of the assertion. */ firesOnce : function(observable, event, desc) { this.firesOk({ observable : observable, events : this.getObjectWithExpectedEvents(event, 1), desc : desc }); }, <span id='Siesta-Test-Observable-method-isntFired'> /** </span> * Alias for {@link #wontFire} method * * @param {Mixed} observable Any browser observable, window object, element instances, CSS selector. * @param {String/Array[String]} event The name of event or array of such * @param {String} [desc] The description of the assertion. */ isntFired : function() { this.wontFire.apply(this, arguments); }, <span id='Siesta-Test-Observable-method-firesAtLeastNTimes'> /** </span> * This assertion passes if the observable fires the specified event at least `n` times after calling this method. * * @param {Mixed} observable Any browser observable, window object, element instances, CSS selector. * @param {String} event The name of event * @param {Number} n The minimum number of events to be fired * @param {String} [desc] The description of the assertion. */ firesAtLeastNTimes : function(observable, event, n, desc) { this.firesOk(observable, event, &#39;&gt;=&#39; + n, desc); }, <span id='Siesta-Test-Observable-method-isFiredWithSignature'> /** </span> * This assertion will verify that the observable fires the specified event and supplies the correct parameters to the listener function. * A checker method should be supplied that verifies the arguments passed to the listener function, and then returns true or false depending on the result. * If the event was never fired, this assertion fails. If the event is fired multiple times, all events will be checked, but * only one pass/fail message will be reported. * * For example: * t.isFiredWithSignature(store, &#39;add&#39;, function (store, records, index) { return (store instanceof Ext.data.Store) &amp;&amp; (records instanceof Array) &amp;&amp; t.typeOf(index) == &#39;Number&#39; }) * @param {Ext.util.Observable/Siesta.Test.ActionTarget} observable Ext.util.Observable instance or target as specified by the {@link Siesta.Test.ActionTarget} rules with * the only difference that component queries will be resolved till the component level, and not the DOM element. * @param {String} event The name of event * @param {Function} checkerFn A method that should verify each argument, and return true or false depending on the result. * @param {String} [desc] The description of the assertion. */ isFiredWithSignature : function(observable, event, checkerFn, description) { var eventFired; var me = this; var sourceLine = me.getSourceLine(); var R = Siesta.Resource(&#39;Siesta.Test.ExtJS.Observable&#39;); var verifyFiredFn = function () { me.removeListenerFromObservable(observable, event, listener) if (!eventFired) { me.fail(&#39;The [&#39; + event + &quot;] &quot; + R.get(&#39;isFiredWithSignatureNotFired&#39;)); } }; me.on(&#39;beforetestfinalizeearly&#39;, verifyFiredFn); var listener = function () { var result = checkerFn.apply(me, arguments); if (!eventFired &amp;&amp; result) { me.pass(description || R.get(&#39;observableFired&#39;) + &#39; &#39; + event + &#39; &#39; + R.get(&#39;correctSignature&#39;), { sourceLine : sourceLine }); } if (!result) { me.fail(description || R.get(&#39;observableFired&#39;) + &#39; &#39; + event + &#39; &#39; + R.get(&#39;incorrectSignature&#39;), { sourceLine : sourceLine }); // Don&#39;t spam the assertion grid with failure, one failure is enough me.removeListenerFromObservable(observable, event, listener) } eventFired = true }; me.addListenerToObservable(observable, event, listener) } } }); </pre> </body> </html>