UNPKG

siesta-lite

Version:

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

478 lines (375 loc) 19.6 kB
/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ /** @class Siesta.Test.BDD.Expectation This class is the central point for writing assertions in BDD style. Instances of this class can be generated with the {@link Siesta.Test#expect expect} method. Then, calling some method on the instance will create a new assertion in the test. * **Note**, that to negate any assertion, you can use a special property {@link #not}, that contains an expectation instance with the opposite meaning. For example: t.expect(1).toBe(1) t.expect(1).not.toBe(2) t.expect('Foo').toContain('oo') t.expect('Foo').not.toContain('bar') */ Class('Siesta.Test.BDD.Expectation', { does : [ Siesta.Util.Role.CanGetType ], has : { value : null, isNot : false, /** * @property {Siesta.Test.BDD.Expectation} not Another expectation instance with the negated meaning. */ not : null, t : null }, methods : { initialize : function () { if (!this.isNot) this.not = new this.constructor({ isNot : true, t : this.t, value : this.value }) }, process : function (passed, config) { var isNot = this.isNot config = config || {} config.not = config.not || isNot ? 'not ' : '' config.got = config.hasOwnProperty('got') ? config.got : this.value if (config.noGot) delete config.got var assertionName = config.assertionName if (assertionName && isNot) config.assertionName = assertionName.replace(/^(expect\(.+?\)\.)/, '$1not.') passed = isNot ? !passed : passed this.t[ passed ? 'pass' : 'fail' ](null, config) }, /** * This assertion compares the value provided to the {@link Siesta.Test#expect expect} method with the `expectedValue` argument. * Comparison is done with `===` operator, so it should be used **only with the primitivies** - numbers, strings, booleans etc. * * To deeply compare Date, Arrays and JSON objects in general, use {@link #toEqual} method. * * This method works correctly with the placeholders generated with {@link Siesta.Test#any any} method * * @param {Primitive} expectedValue An expected value */ toBe : function (expectedValue) { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.t.compareObjects(this.value, expectedValue, true, true), { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeText') + ' {need}', assertionName : 'expect(got).toBe(need)', need : expectedValue, needDesc : this.isNot ? R.get('needNotText') : R.get('needText') }) }, /** * This assertion compares the value provided to the {@link Siesta.Test#expect expect} method with the `expectedValue` argument. * * Comparison works for Date, Array, and JSON objects in general. It is performed "deeply". * Right now the values should not contain cyclic references. * * This method works correctly with the placeholders generated with {@link Siesta.Test#any any} method * * @param {Mixed} expectedValue An expected value */ toEqual : function (expectedValue) { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.t.compareObjects(this.value, expectedValue, true), { descTpl : R.get('expectText') +' {got} {!not}' + R.get('toBeEqualToText') + ' {need}', assertionName : 'expect(got).toEqual(need)', need : expectedValue, needDesc : this.isNot ? R.get('needNotText') : R.get('needText') }) }, /** * This assertion passes, when value provided to the {@link Siesta.Test#expect expect} method is `null`. */ toBeNull : function () { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.t.compareObjects(this.value, null, true, true), { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeText') + ' null', assertionName : 'expect(got).toBeNull()', need : null, needDesc : this.isNot ? R.get('needNotText') : R.get('needText') }) }, /** * This assertion passes, when value provided to the {@link Siesta.Test#expect expect} method is `NaN`. */ toBeNaN : function () { var value = this.value var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.t.typeOf(value) == 'Number' && value != value, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeText') + ' NaN', assertionName : 'expect(got).toBeNaN()', need : NaN, needDesc : this.isNot ? R.get('needNotText') : R.get('needText') }) }, /** * This assertion passes, when value provided to the {@link Siesta.Test#expect expect} method is not the `undefined` value. */ toBeDefined : function () { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.value !== undefined, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeDefinedText'), assertionName : 'expect(got).toBeDefined()' }) }, /** * This assertion passes, when value provided to the {@link Siesta.Test#expect expect} method is the `undefined` value. */ toBeUndefined : function (value) { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.value === undefined, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeUndefinedText'), assertionName : 'expect(got).toBeUndefined()' }) }, /** * This assertion passes, when value provided to the {@link Siesta.Test#expect expect} method is "truthy" - evaluates to `true`. * For example - non empty strings, numbers except the 0, objects, arrays etc. */ toBeTruthy : function () { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.value, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeTruthyText'), assertionName : 'expect(got).toBeTruthy()' }) }, /** * This assertion passes, when value provided to the {@link Siesta.Test#expect expect} method is "falsy" - evaluates to `false`. * For example - empty strings, number 0, `null`, `undefined`, etc. */ toBeFalsy : function () { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(!this.value, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeFalsyText'), assertionName : 'expect(got).toBeFalsy()' }) }, /** * This assertion passes, when the string provided to the {@link Siesta.Test#expect expect} method matches the regular expression. * * @param {RegExp} regexp The regular expression to match the string against */ toMatch : function (regexp) { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); if (this.t.typeOf(regexp) != 'RegExp') throw new Error("`expect().toMatch()` matcher expects a regular expression") this.process(new RegExp(regexp).test(this.value), { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toMatchText') + ' {need}', assertionName : 'expect(got).toMatch(need)', need : regexp, needDesc : this.isNot ? R.get('needNotMatchingText') : R.get('needMatchingText') }) }, /** * This assertion passes in 2 cases: * * 1) When the value provided to the {@link Siesta.Test#expect expect} method is a string, and it contains a passed substring. * 2) When the value provided to the {@link Siesta.Test#expect expect} method is an array (or array-like), and it contains a passed element. * * @param {String/Mixed} element The element of the array or a sub-string */ toContain : function (element) { var value = this.value var t = this.t var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); var passed = false if (t.typeOf(value) == 'String') { this.process(value.indexOf(element) >= 0, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toContainText') + ' {need}', assertionName : 'expect(got).toContain(need)', need : element, needDesc : this.isNot ? R.get('needStringNotContainingText') : R.get('needStringContainingText') }) } else { // Normalize to allow NodeList, Arguments etc. value = Array.prototype.slice.call(value); for (var i = 0; i < value.length; i++) if (t.compareObjects(element, value[ i ], true)) { passed = true break } this.process(passed, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toContainText') + ' {need}', assertionName : 'expect(got).toContain(need)', need : element, needDesc : this.isNot ? R.get('needArrayNotContainingText') : R.get('needArrayContainingText') }) } }, /** * This assertion passes, when the number provided to the {@link Siesta.Test#expect expect} method is less than the * expected number. * * @param {Number} expectedValue The number to compare with */ toBeLessThan : function (expectedValue) { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.value < expectedValue, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeLessThanText') + ' {need}', assertionName : 'expect(got).toBeLessThan(need)', need : expectedValue, needDesc : this.isNot ? R.get('needGreaterEqualThanText') : R.get('needLessThanText') }) }, /** * This assertion passes, when the number provided to the {@link Siesta.Test#expect expect} method is greater than the * expected number. * * @param {Number} expectedValue The number to compare with */ toBeGreaterThan : function (expectedValue) { var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(this.value > expectedValue, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeGreaterThanText') + ' {need}', assertionName : 'expect(got).toBeGreaterThan(need)', need : expectedValue, needDesc : this.isNot ? R.get('needLessEqualThanText') : R.get('needGreaterThanText') }) }, /** * This assertion passes, when the number provided to the {@link Siesta.Test#expect expect} method is approximately equal * the given number. The proximity can be defined as the `precision` argument * * @param {Number} expectedValue The number to compare with * @param {Number} [precision=2] The number of digits after dot (comma) that should be same in both numbers. */ toBeCloseTo : function (expectedValue, precision) { precision = precision != null ? precision : 2 // not sure why we divide the precision by 2, but jasmine does that for some reason var threshold = Math.pow(10, -precision) / 2 var delta = Math.abs(this.value - expectedValue) var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); this.process(delta < threshold, { descTpl : R.get('expectText') + ' {got} {!not}' + R.get('toBeCloseToText') +' {need}', assertionName : 'expect(got).toBeCloseTo(need)', need : expectedValue, needDesc : this.isNot ? R.get('needValueNotCloseToText') : R.get('needValueCloseToText'), annotation : delta ? R.get('thresholdIsText') + threshold : R.get('exactMatchText') }) }, /** * This assertion passes when the function provided to the {@link Siesta.Test#expect expect} method, throws an exception * during its execution. * * t.expect(function(){ * throw "oopsie"; * }).toThrow()); * */ toThrow : function () { var func = this.value var t = this.t var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); if (t.typeOf(func) != 'Function') throw new Error("`expect().toMatch()` matcher expects a function") var e = t.getExceptionCatcher()(func) if (e instanceof t.getTestErrorClass()) //IE uses non-standard 'description' property for error msg e = e.message || e.description this.process(e !== undefined, { descTpl : R.get('expectText') + ' function {!not}' + R.get('toThrowText'), assertionName : 'expect(func).toThrow()', annotation : e ? (R.get('thrownExceptionText') + ': ' + Siesta.Util.Serializer.stringify(e)) : R.get('noExceptionThrownText'), noGot : true }) }, /** * This assertion passes, if a spy, provided to the {@link Siesta.Test#expect expect} method have been * called expected number of times. The expected number of times can be provided as the 1st argument and by default * is 1. * * One can also provide the function, spied on, to the {@link Siesta.Test#expect expect} method. * * Examples: * var spy = t.spyOn(obj, 'process') // call the method 2 times obj.process() obj.process() // following 2 calls are equivalent t.expect(spy).toHaveBeenCalled(); t.expect(obj.process).toHaveBeenCalled(); // one can also use exact number of calls or comparison operators t.expect(obj.process).toHaveBeenCalled(2); t.expect(obj.process).toHaveBeenCalled('>1'); t.expect(obj.process).toHaveBeenCalled('<=3'); * * See also {@link #toHaveBeenCalledWith} * * @param {Number/String} expectedNumber Expected number of calls. Can be either a number, specifying the exact * number of calls, or a string. In the latter case one can include a comparison operator in front of the number. * */ toHaveBeenCalled : function (expectedNumber) { expectedNumber = expectedNumber != null ? expectedNumber : '>=1' var spy = this.value var t = this.t var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); if (this.typeOf(spy) == 'Function') { if (!spy.__SIESTA_SPY__) throw new Error(R.get('wrongSpy')) spy = spy.__SIESTA_SPY__ } if (!(spy instanceof Siesta.Test.BDD.Spy)) throw new Error(R.get('wrongSpy')) this.process(t.verifyExpectedNumber(spy.callsLog.length, expectedNumber), { descTpl : R.get('toHaveBeenCalledDescTpl'), assertionName : 'expect(func).toHaveBeenCalled()', methodName : spy.propertyName || '[function]', got : spy.callsLog.length, gotDesc : R.get('actualNbrOfCalls'), need : (this.isNot ? 'not ' : '') + expectedNumber, needDesc : R.get('expectedNbrOfCalls') }) }, /** * This assertion passes, if a spy, provided to the {@link Siesta.Test#expect expect} method have been * called at least once with the specified arguments. * * One can also provide the function, spied on, to the {@link Siesta.Test#expect expect} method. * * One can use placeholders, generated with the {@link Siesta.Test.BDD#any any} method to verify the arguments. * * Example: * var spy = t.spyOn(obj, 'process') // call the method 2 times with different arguments obj.build('development', '1.0.0') obj.build('release', '1.0.1') t.expect(spy).toHaveBeenCalledWith('development', '1.0.0'); // or t.expect(obj.process).toHaveBeenCalledWith('development', t.any(String)); * * See also {@link #toHaveBeenCalled} * * @param {Object} arg1 Argument to a call * @param {Object} arg2 Argument to a call * @param {Object} argN Argument to a call */ toHaveBeenCalledWith : function () { var spy = this.value var t = this.t var R = Siesta.Resource('Siesta.Test.BDD.Expectation'); if (this.typeOf(spy) == 'Function') { if (!spy.__SIESTA_SPY__) throw new Error(R.get('wrongSpy')) spy = spy.__SIESTA_SPY__ } if (!(spy instanceof Siesta.Test.BDD.Spy)) throw new Error(R.get('wrongSpy')) var args = Array.prototype.slice.call(arguments) var foundCallWithMatchingArgs = false Joose.A.each(spy.callsLog, function (call) { if (t.compareObjects(call.args, args)) { foundCallWithMatchingArgs = true; return false } }) this.process(foundCallWithMatchingArgs, { descTpl : R.get('toHaveBeenCalledWithDescTpl'), assertionName : 'expect(func).toHaveBeenCalledWith()', methodName : spy.propertyName, noGot : true }) } } })