UNPKG

siesta-lite

Version:

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

729 lines (576 loc) 29.1 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-BDD'>/** </span>@class Siesta.Test.BDD A mixin providing a BDD style layer for most of the assertion methods. It is consumed by {@link Siesta.Test}, so all of its methods are available in all tests. */ Role(&#39;Siesta.Test.BDD&#39;, { requires : [ &#39;getSubTest&#39;, &#39;chain&#39; ], has : { specType : null, // `describe` or `it` beforeEachHooks : Joose.I.Array, afterEachHooks : Joose.I.Array, sequentialSubTests : Joose.I.Array, // flag, whether the &quot;run&quot; function of the test (containing actual test code) have been already run codeProcessed : false, launchTimeout : null, // Siesta.Test.BDD.Expectation should already present on the page expectationClass : Siesta.Test.BDD.Expectation, failOnExclusiveSpecsWhenAutomated : false, spies : Joose.I.Array }, methods : { checkSpecFunction : function (func, type, name) { if (!func) throw new Error(Siesta.Resource(&#39;Siesta.Test.BDD&#39;, &#39;codeBodyMissing&#39;) + &quot; &quot; + (type == &#39;describe&#39; ? &#39;suite&#39; : &#39;spec&#39;) + &#39; [&#39; + name + &#39;]&#39;) if (!func.length) throw new Error(Siesta.Resource(&#39;Siesta.Test.BDD&#39;, &#39;codeBodyOf&#39;) + &quot; &quot; + (type == &#39;describe&#39; ? &#39;suite&#39; : &#39;spec&#39;) + &#39; [&#39; + name + &#39;] &#39; + Siesta.Resource(&#39;Siesta.Test.BDD&#39;, &#39;missingFirstArg&#39;)) }, <span id='Siesta-Test-BDD-method-ddescribe'> /** </span> * This is an &quot;exclusive&quot; version of the regular {@link #describe} suite. When such suites presents in some test file, * the other regular suites at the same level will not be executed, only &quot;exclusive&quot; ones. * * @param {String} name The name or description of the suite * @param {Function} code The code function for this suite. It will receive a test instance as the first argument which should be used for all assertion methods. * @param {Number} [timeout] A maximum duration for this suite. If not provided {@link Siesta.Project#subTestTimeout} value is used. */ ddescribe : function (name, code, timeout) { this.describe(name, code, timeout, true) }, <span id='Siesta-Test-BDD-method-xdescribe'> /** </span> * This is a no-op method, allowing you to quickly ignore some suites. */ xdescribe : function () { }, <span id='Siesta-Test-BDD-method-describe'> /** </span> * This method starts a sub test with *suite* (in BDD terms). Such suite consists from one or more *specs* (see method {@link #it}} or other suites. * The number of nesting levels is not limited. All suites of the same nesting level are executed sequentially. * * For example: * t.describe(&#39;A product&#39;, function (t) { t.it(&#39;should have feature X&#39;, function (t) { ... }) t.describe(&#39;feature X&#39;, function (t) { t.it(&#39;should be cool&#39;, function (t) { ... }) }) }) * * See also {@link #beforeEach}, {@link #afterEach}, {@link #xdescribe}, {@link #ddescribe} * * @param {String} name The name or description of the suite * @param {Function} code The code function for this suite. It will receive a test instance as the first argument which should be used for all assertion methods. * @param {Number} [timeout] A maximum duration for this suite. If not provided {@link Siesta.Project#subTestTimeout} value is used. */ describe : function (name, code, timeout, isExclusive) { this.checkSpecFunction(code, &#39;describe&#39;, name) var subTest = this.getSubTest({ name : name, run : code, isExclusive : isExclusive, specType : &#39;describe&#39;, timeout : timeout }) if (this.codeProcessed) this.scheduleSpecsLaunch() this.sequentialSubTests.push(subTest) }, <span id='Siesta-Test-BDD-method-iit'> /** </span> * This is an &quot;exclusive&quot; version of the regular {@link #it} spec. When such specs presents in some suite, * the other regular specs at the same level will not be executed, only &quot;exclusive&quot; ones. Note, that even &quot;regular&quot; suites (`t.describe`) sections * will be ignored, if they are on the same level with the exclusive `iit` section. * * @param {String} name The name or description of the spec * @param {Function} code The code function for this spec. It will receive a test instance as the first argument which should be used for all assertion methods. * @param {Number} [timeout] A maximum duration for this spec. If not provided {@link Siesta.Project#subTestTimeout} value is used. */ iit : function (name, code, timeout) { if (this.project.isAutomated) { if (this.failOnExclusiveSpecsWhenAutomated) this.fail(Siesta.Resource(&#39;Siesta.Test.BDD&#39;, &#39;iitFound&#39;)); } this.it(name, code, timeout, true) }, <span id='Siesta-Test-BDD-method-xit'> /** </span> * This is a no-op method, allowing you to quickly ignore some specs. */ xit : function () { }, <span id='Siesta-Test-BDD-method-it'> /** </span> * This method starts a sub test with *spec* (in BDD terms). Such spec consists from one or more assertions (or *expectations*, *matchers*, etc) or other nested specs * and/or suites. See the {@link #expect} method. The number of nesting levels is not limited. All specs of the same nesting level are executed sequentially. * * For example: * t.describe(&#39;A product&#39;, function (t) { t.it(&#39;should have feature X&#39;, function (t) { ... }) t.it(&#39;should have feature Y&#39;, function (t) { ... }) }) * * See also {@link #beforeEach}, {@link #afterEach}, {@link #xit}, {@link #iit} * * @param {String} name The name or description of the spec * @param {Function} code The code function for this spec. It will receive a test instance as the first argument which should be used for all assertion methods. * @param {Number} [timeout] A maximum duration for this spec. If not provided {@link Siesta.Project#subTestTimeout} value is used. */ it : function (name, code, timeout, isExclusive, isTodo) { this.checkSpecFunction(code, &#39;it&#39;, name) var subTest = this.getSubTest({ name : name, run : code, isExclusive : isExclusive, isTodo : Boolean(isTodo) || this.isTodo, specType : &#39;it&#39;, timeout : timeout }) if (this.codeProcessed) this.scheduleSpecsLaunch() this.sequentialSubTests.push(subTest) }, <span id='Siesta-Test-BDD-method-expect'> /** </span> * This method returns an &quot;expectation&quot; instance, which can be used to check various assertions about the passed value. * * **Note**, that every expectation has a special property `not`, that contains another expectation, but with the negated meaning. * * For example: * t.expect(1).toBe(1) t.expect(1).not.toBe(2) t.expect(&#39;Foo&#39;).toContain(&#39;oo&#39;) t.expect(&#39;Foo&#39;).not.toContain(&#39;bar&#39;) * Please refer to the documentation of the {@link Siesta.Test.BDD.Expectation} class for the list of available methods. * * @param {Mixed} value Any value, that will be assert about * @return {Siesta.Test.BDD.Expectation} Expectation instance */ expect : function (value) { return new this.expectationClass({ t : this, value : value }) }, <span id='Siesta-Test-BDD-method-any'> /** </span> * This method returns a *placeholder*, denoting any instance of the provided class constructor. Such placeholder can be used in various * comparison assertions, like {@link #is}, {@link #isDeeply}, {@link Siesta.Test.BDD.Expectation#toBe expect().toBe()}, * {@link Siesta.Test.BDD.Expectation#toBe expect().toEqual()} and so on. * * For example: t.is(1, t.any(Number)) t.expect(1).toBe(t.any(Number)) t.isDeeply({ name : &#39;John&#39;, age : 45 }, { name : &#39;John&#39;, age : t.any(Number)) t.expect({ name : &#39;John&#39;, age : 45 }).toEqual({ name : &#39;John&#39;, age : t.any(Number)) t.is(NaN, t.any(), &#39;When class constructor is not provided `t.any()` should match anything&#39;) * * See also {@link #anyNumberApprox}, {@link #anyStringLike}. * * @param {Function} clsConstructor A class constructor instances of which are denoted with this placeholder. As a special case if this argument * is not provided, a placeholder will match any value. * * @return {Object} A placeholder object */ any : function (clsConstructor) { return new Siesta.Test.BDD.Placeholder({ clsConstructor : clsConstructor, t : this, context : this.global }) }, <span id='Siesta-Test-BDD-method-anyNumberApprox'> /** </span> * This method returns a *placeholder*, denoting any number approximately equal to the provided value. * Such placeholder can be used in various comparison assertions, like {@link #is}, {@link #isDeeply}, * {@link Siesta.Test.BDD.Expectation#toBe expect().toBe()}, * {@link Siesta.Test.BDD.Expectation#toBe expect().toEqual()} and so on. * * For example: t.is(1, t.anyNumberApprox(1.2, 0.5)) t.expect(1).toBe(t.anyNumberApprox(1.2, 0.5)) * * @param {Number} value The approximate value * @param {Number} [threshold] The threshold. If omitted, it is set to 5% from the `value`. * * @return {Object} A placeholder object */ anyNumberApprox : function (value, threshold) { return new Siesta.Test.BDD.NumberPlaceholder({ value : value, threshold : threshold }) }, <span id='Siesta-Test-BDD-method-anyStringLike'> /** </span> * This method returns a *placeholder*, denoting any string that matches provided value. * Such placeholder can be used in various comparison assertions, like {@link #is}, {@link #isDeeply}, * {@link Siesta.Test.BDD.Expectation#toBe expect().toBe()}, * {@link Siesta.Test.BDD.Expectation#toBe expect().toEqual()} and so on. * * For example: t.is(&#39;foo&#39;, t.anyStringLike(&#39;oo&#39;)) t.expect(&#39;bar&#39;).toBe(t.anyStringLike(/ar$/)) * * @param {String/RegExp} value If given as string will denote a substring a string being checked should contain, * if given as RegExp instance then string being checked should match this RegExp * * @return {Object} A placeholder object */ anyStringLike : function (value) { return new Siesta.Test.BDD.StringPlaceholder({ value : value }) }, scheduleSpecsLaunch : function () { if (this.launchTimeout) return var async = this.beginAsync() var originalSetTimeout = this.originalSetTimeout var me = this this.launchTimeout = originalSetTimeout(function () { me.endAsync(async) me.launchTimeout = null me.launchSpecs() }, 0) }, runBeforeSpecHooks : function (sourceTest, done) { var me = this var runOwnHooks = function (done) { me.chainForArray(me.beforeEachHooks, function (hook) { return function (next) { var code = hook.code if (me.typeOf(code) === &#39;AsyncFunction&#39;) { return code(sourceTest, function () {}) } else { if (hook.isAsync) { code(sourceTest, next) } else { code(sourceTest) next() } } } }, done) } if (this.parent) this.parent.runBeforeSpecHooks(sourceTest, function () { runOwnHooks(done) }) else runOwnHooks(done) }, runAfterSpecHooks : function (sourceTest, done) { var me = this me.chainForArray( this.afterEachHooks, function (hook) { return function (next) { var code = hook.code if (me.typeOf(code) === &#39;AsyncFunction&#39;) { return code(sourceTest, function () {}) } else { if (hook.isAsync) { code(sourceTest, next) } else { code(sourceTest) next() } } } }, function () { me.parent ? me.parent.runAfterSpecHooks(sourceTest, done) : done() }, // reverse true ) }, launchSpecs : function () { var me = this var sequentialSubTests = this.sequentialSubTests this.sequentialSubTests = [] // hackish way to pass a config to `t.chain` this.chain.actionDelay = 0 var exclusiveSubTests = [] Joose.A.each(sequentialSubTests, function (subTest) { if (subTest.isExclusive) exclusiveSubTests.push(subTest) }) this.chainForArray(exclusiveSubTests.length ? exclusiveSubTests : sequentialSubTests, function (subTest) { return [ subTest.specType == &#39;it&#39; ? function (next) { if (me.finalizationStarted || me.endDate) next() else me.runBeforeSpecHooks(subTest, next) } : null, subTest, subTest.specType == &#39;it&#39; ? function (next) { if (me.finalizationStarted || me.endDate) next() else me.runAfterSpecHooks(subTest, next) } : null ] }) }, <span id='Siesta-Test-BDD-method-beforeEach'> /** </span> * This method allows you to execute some &quot;setup&quot; code hook before every spec (&quot;it&quot; block) of the current test. * Such hooks are **not** executed for the &quot;describe&quot; blocks and sub-tests generated with * the {@link Siesta.Test#getSubTest getSubTest} method. * * Note, that specs can be nested and all `beforeEach` hooks are executed in order, starting from the outer-most one. * * The 1st argument of the hook function is always the test instance being launched. * * If the hook function is async (`async () =&gt; {}`) Siesta will &quot;await&quot; until it completes. * * If hook is declared with 2 arguments - it is supposed to be asynchronous (you can also force the asynchronous * mode with the `isAsync` argument, see below). The completion callback will be provided as the 2nd argument for the hook. * * This method can be called several times, providing several &quot;hook&quot; functions. * * For example: StartTest(function (t) { var baz = 0 t.beforeEach(function (t) { // the `t` instance here is the &quot;t&quot; instance from the &quot;it&quot; block below baz = 0 }) t.it(&quot;This feature should work&quot;, function (t) { t.expect(myFunction(baz++)).toEqual(&#39;someResult&#39;) }) }) * * @param {Function} code A function to execute before every spec * @param {Siesta.Test} code.t A test instance being launched * @param {Function} code.next A callback to call when the `beforeEach` method completes. This argument is only provided * when hook function is declared with 2 arguments (or the `isAsync` argument is passed as `true`) * @param {Boolean} isAsync When passed as `true` this argument makes the `beforeEach` method asynchronous. In this case, * the `code` function will receive an additional callback argument, which should be called once the method has completed its work. * * Note, that `beforeEach` method should complete within {@link Siesta.Test#defaultTimeout defaultTimeout} time, otherwise * failing assertion will be added to the test. * * Example of asynchronous hook: StartTest(function (t) { var baz = 0 var delay = (time) =&gt; new Promise(resolve =&gt; setTimeout(resolve, time)) // asynchronous hook function t.beforeEach(async t =&gt; { await delay(100) baz = 0 }) // asynchronous setup code t.beforeEach(function (t, next) { // `beforeEach` will complete in 100ms setTimeout(function () { baz = 0 next() }, 100) }) t.describe(&quot;This feature should work&quot;, function (t) { t.expect(myFunction(baz++)).toEqual(&#39;someResult&#39;) }) }) */ beforeEach : function (code, isAsync) { this.beforeEachHooks.push({ code : code, isAsync : isAsync || code.length == 2 }) }, <span id='Siesta-Test-BDD-method-afterEach'> /** </span> * This method allows you to execute some &quot;setup&quot; code hook after every spec (&quot;it&quot; block) of the current test. * Such hooks are **not** executed for the &quot;describe&quot; blocks and sub-tests generated with * the {@link Siesta.Test#getSubTest getSubTest} method. * * Note, that specs can be nested and all `afterEach` hooks are executed in order, starting from the most-nested one. * * The 1st argument of the hook function is always the test instance being launched. * * If the hook function is async (`async () =&gt; {}`) Siesta will &quot;await&quot; until it completes. * * If hook is declared with 2 arguments - it is supposed to be asynchronous (you can also force the asynchronous * mode with the `isAsync` argument, see below). The completion callback will be provided as the 2nd argument for the hook. * * This method can be called several times, providing several &quot;hook&quot; functions. * * For example: StartTest(function (t) { var baz = 0 t.afterEach(function (t) { // the `t` instance here is the &quot;t&quot; instance from the &quot;it&quot; block below baz = 0 }) t.it(&quot;This feature should work&quot;, function (t) { t.expect(myFunction(baz++)).toEqual(&#39;someResult&#39;) }) }) * * @param {Function} code A function to execute after every spec * @param {Siesta.Test} code.t A test instance being completed * @param {Function} code.next A callback to call when the `afterEach` method completes. This argument is only provided * when hook function is declared with 2 arguments (or the `isAsync` argument is passed as `true`) * @param {Boolean} isAsync When passed as `true` this argument makes the `afterEach` method asynchronous. In this case, * the `code` function will receive an additional callback argument, which should be called once the method has completed its work. * * Note, that `afterEach` method should complete within {@link Siesta.Test#defaultTimeout defaultTimeout} time, otherwise * failing assertion will be added to the test. * * Example of asynchronous hook: StartTest(function (t) { var baz = 0 var delay = (time) =&gt; new Promise(resolve =&gt; setTimeout(resolve, time)) // asynchronous hook function t.beforeEach(async t =&gt; { await delay(100) baz = 0 }) // asynchronous setup code t.afterEach(function (t, next) { // `afterEach` will complete in 100ms setTimeout(function () { baz = 0 next() }, 100) }) t.describe(&quot;This feature should work&quot;, function (t) { t.expect(myFunction(baz++)).toEqual(&#39;someResult&#39;) }) }) */ afterEach : function (code, isAsync) { this.afterEachHooks.push({ code : code, isAsync : isAsync || code.length == 2 }) }, <span id='Siesta-Test-BDD-method-spyOn'> /** </span> * This method installs a &quot;spy&quot; instead of normal function in some object. The &quot;spy&quot; is basically another function, * which tracks the calls to itself. With spies, one can verify that some function was called and that * it was called with certain arguments. * * By default, spy will call the original method and return a value from it. To enable different behavior, you can use one of these methods: * * - {@link Siesta.Test.BDD.Spy#returnValue returnValue} - return a specific value * - {@link Siesta.Test.BDD.Spy#callThrough callThrough} - call the original method and return a value from it * - {@link Siesta.Test.BDD.Spy#stub stub} - call the original method and return a value from it * - {@link Siesta.Test.BDD.Spy#callFake callFake} - call the provided function and return a value from it * - {@link Siesta.Test.BDD.Spy#throwError throwError} - throw a specific exception object * const spy = t.spyOn(obj, &#39;process&#39;) // or, if you need to call some method instead const spy = t.spyOn(obj, &#39;process&#39;).and.callFake(() =&gt; { // is called instead of `process` method }) // call the method obj.process(&#39;fast&#39;, 1) t.expect(spy).toHaveBeenCalled(); t.expect(spy).toHaveBeenCalledWith(&#39;fast&#39;, 1); * * See also {@link #createSpy}, {@link #createSpyObj}, {@link Siesta.Test.BDD.Expectation#toHaveBeenCalled toHaveBeenCalled}, * {@link Siesta.Test.BDD.Expectation#toHaveBeenCalledWith toHaveBeenCalledWith} * * See also the {@link Siesta.Test.BDD.Spy} class for additional details. * * @param {Object} object An object which property is being spied * @param {String} propertyName A name of the property over which to install the spy. * * @return {Siesta.Test.BDD.Spy} spy Created spy instance */ spyOn : function (object, propertyName) { var R = Siesta.Resource(&#39;Siesta.Test.BDD&#39;) if (!object) { this.warn(R.get(&#39;noObject&#39;)); return; } return new Siesta.Test.BDD.Spy({ name : propertyName, t : this, hostObject : object, propertyName : propertyName }) }, <span id='Siesta-Test-BDD-method-createSpy'> /** </span> * This method create a standalone spy function, which tracks all calls to it. Tracking is done using the associated * spy instance, which is available as `and` property. One can use the {@link Siesta.Test.BDD.Spy} class API to * verify the calls to the spy function. * * Example: var spyFunc = t.createSpy(&#39;onadd listener&#39;) myObservable.addEventListener(&#39;add&#39;, spyFunc) // do something that triggers the `add` event on the `myObservable` t.expect(spyFunc).toHaveBeenCalled() t.expect(spyFunc.calls.argsFor(1)).toEqual([ &#39;Arg1&#39;, &#39;Arg2&#39; ]) * * See also: {@link #spyOn} * * @param {String} [spyName=&#39;James Bond&#39;] A name of the spy for debugging purposes * * @return {Function} Created function. The associated spy instance is assigned to it as the `and` property */ createSpy : function (spyName) { return (new Siesta.Test.BDD.Spy({ name : spyName || &#39;James Bond&#39;, t : this })).stub().getProcessor() }, <span id='Siesta-Test-BDD-method-createSpyObj'> /** </span> * This method creates an object, which properties are spy functions. Such object can later be used as a mockup. * * This method can be called with one argument only, which should be an array of properties. * * Example: var mockup = t.createSpyObj(&#39;encoder-mockup&#39;, [ &#39;encode&#39;, &#39;decode&#39; ]) // or just var mockup = t.createSpyObj([ &#39;encode&#39;, &#39;decode&#39; ]) mockup.encode(&#39;string&#39;) mockup.decode(&#39;string&#39;) t.expect(mockup.encode).toHaveBeenCalled() * * See also: {@link #createSpy} * * @param {String} spyName A name of the spy object. Can be omitted. * @param {Array[String]} properties An array of the property names. For each property name a spy function will be created. * * @return {Object} A mockup object */ createSpyObj : function (spyName, properties) { if (arguments.length == 1) { properties = spyName; spyName = null } spyName = spyName || &#39;spyObject&#39; var me = this var obj = {} Joose.A.each(properties, function (propertyName) { obj[ propertyName ] = me.createSpy(spyName) }) return obj } }, override : { cleanup : function () { this.beforeEachHooks = this.afterEachHooks = null this.SUPER() }, onTestFinalize : function () { Joose.A.each(this.spies, function (spy) { spy.remove() }) this.spies = null this.SUPER() }, afterLaunch : function () { this.codeProcessed = true this.launchSpecs() this.SUPERARG(arguments) } } }) //eof Siesta.Test.BDD </pre> </body> </html>