diff --git a/package.json b/package.json index 765b19423..43255b8e7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "semantic", - "version": "0.15.0", + "version": "0.15.4", "title": "Semantic UI", "description": "Semantic empowers designers and developers by creating a shared vocabulary for UI.", "homepage": "http://www.semantic-ui.com", @@ -13,36 +13,41 @@ "type": "git", "url": "git://github.com/Semantic-Org/Semantic-UI.git" }, - "dependencies": {}, + "dependencies": { + "jquery": "x.x.x" + }, "devDependencies": { - "docpad": "~6.63.3", - "docpad-plugin-eco": "~2.0.3", - "docpad-plugin-ghpages": "~2.4.3", - "docpad-plugin-partials": "~2.8.1", - "grunt": "~0.4.1", - "grunt-contrib-watch": "~0.5.3", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-uglify": "~0.2.2", - "grunt-contrib-cssmin": "~0.6.1", - "grunt-contrib-less": "~0.7.0", - "grunt-contrib-copy": "~0.4.1", - "grunt-docco": "~0.2.0", - "grunt-bower-task": "~0.3.1", - "grunt-css": "~0.5.4", - "grunt-contrib-compress": "~0.5.2", - "grunt-contrib-concat": "~0.3.0", - "grunt-docco-multi": "0.0.2", - "grunt-cssjanus": "~0.1.1", - "karma": "~0.10.2", - "grunt-karma": "~0.6.2", - "karma-spec-reporter": "0.0.5", - "grunt-clear": "~0.2.1", - "grunt-autoprefixer": "~0.4.0", - "grunt-karma-coveralls": "~2.2.0", - "karma-coverage": "~0.1.4", - "docpad-plugin-partials": "~2.8.1", - "docpad-plugin-ghpages": "~2.4.3", - "docpad-plugin-eco": "~2.0.3", - "docpad": "~6.63.3" + "docpad": "x.x.x", + "docpad-plugin-eco": "x.x.x", + "docpad-plugin-ghpages": "x.x.x", + "docpad-plugin-partials": "x.x.x", + "grunt": "x.x.x", + "grunt-contrib-watch": "x.x.x", + "grunt-contrib-clean": "x.x.x", + "grunt-contrib-uglify": "x.x.x", + "grunt-contrib-cssmin": "x.x.x", + "grunt-contrib-less": "x.x.x", + "grunt-contrib-copy": "x.x.x", + "grunt-docco": "x.x.x", + "grunt-bower-task": "x.x.x", + "grunt-css": "x.x.x", + "grunt-contrib-compress": "x.x.x", + "grunt-contrib-concat": "x.x.x", + "grunt-replace": "x.x.x", + "grunt-docco-multi": "x.x.x", + "grunt-cssjanus": "x.x.x", + "karma": "x.x.x", + "grunt-karma": "x.x.x", + "karma-spec-reporter": "x.x.x", + "karma-phantomjs-launcher": "x.x.x", + "karma-jasmine": "x.x.x", + "grunt-clear": "x.x.x", + "grunt-autoprefixer": "x.x.x", + "grunt-karma-coveralls": "x.x.x", + "karma-coverage": "~0.x.x", + "docpad-plugin-partials": "x.x.x", + "docpad-plugin-ghpages": "x.x.x", + "docpad-plugin-eco": "x.x.x", + "docpad": "x.x.x" } -} +} \ No newline at end of file diff --git a/test/helpers/jasmine-jquery.js b/test/helpers/jasmine-jquery.js index 14539ea4d..c85a8e8b1 100755 --- a/test/helpers/jasmine-jquery.js +++ b/test/helpers/jasmine-jquery.js @@ -1,11 +1,11 @@ /*! Jasmine-jQuery: a set of jQuery helpers for Jasmine tests. -Version 1.5.91 +Version 2.1.0 https://github.com/velesin/jasmine-jquery -Copyright (c) 2010-2013 Wojciech Zawistowski, Travis Jeffery +Copyright (c) 2010-2014 Wojciech Zawistowski, Travis Jeffery Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27,7 +27,7 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -+function (jasmine, $) { "use strict"; ++function (window, jasmine, $) { "use strict"; jasmine.spiedEventsKey = function (selector, eventName) { return [$(selector).selector, eventName].toString() @@ -104,7 +104,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jasmine.Fixtures.prototype.addToContainer_ = function (html){ var container = $(document.body).find('#'+this.containerId).append(html) - if(!container.length){ + + if (!container.length) { this.createContainer_(html) } } @@ -119,17 +120,36 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { var self = this , url = this.makeFixtureUrl_(relativeUrl) + , htmlText = '' , request = $.ajax({ async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded cache: false, url: url, success: function (data, status, $xhr) { - self.fixturesCache_[relativeUrl] = $xhr.responseText - }, - error: function (jqXHR, status, errorThrown) { - throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')') + htmlText = $xhr.responseText } + }).fail(function ($xhr, status, err) { + throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') + }) + + var scripts = $($.parseHTML(htmlText, true)).find('script[src]') || []; + + scripts.each(function(){ + $.ajax({ + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded + cache: false, + dataType: 'script', + url: $(this).attr('src'), + success: function (data, status, $xhr) { + htmlText += '' + }, + error: function ($xhr, status, err) { + throw new Error('Script could not be loaded: ' + scriptSrc + ' (status: ' + status + ', message: ' + err.message + ')') + } + }); }) + + self.fixturesCache_[relativeUrl] = htmlText; } jasmine.Fixtures.prototype.makeFixtureUrl_ = function (relativeUrl){ @@ -235,8 +255,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. success: function (data) { self.fixturesCache_[relativeUrl] = data }, - error: function (jqXHR, status, errorThrown) { - throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')') + error: function ($xhr, status, err) { + throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') } }) } @@ -245,461 +265,548 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. return this[methodName].apply(this, passedArguments) } - jasmine.JQuery = function () {} + jasmine.jQuery = function () {} - jasmine.JQuery.browserTagCaseIndependentHtml = function (html) { + jasmine.jQuery.browserTagCaseIndependentHtml = function (html) { return $('
').append(html).html() } - jasmine.JQuery.elementToString = function (element) { - var domEl = $(element).get(0) + jasmine.jQuery.elementToString = function (element) { + return $(element).map(function () { return this.outerHTML; }).toArray().join(', ') + } - if (domEl === undefined || domEl.cloneNode) - return $('').append($(element).clone()).html() - else - return element.toString() + var data = { + spiedEvents: {} + , handlers: [] } - jasmine.JQuery.matchersClass = {} + jasmine.jQuery.events = { + spyOn: function (selector, eventName) { + var handler = function (e) { + data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = jasmine.util.argsToArray(arguments) + } - !function (namespace) { - var data = { - spiedEvents: {} - , handlers: [] - } + $(selector).on(eventName, handler) + data.handlers.push(handler) - namespace.events = { - spyOn: function (selector, eventName) { - var handler = function (e) { - data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = jasmine.util.argsToArray(arguments) + return { + selector: selector, + eventName: eventName, + handler: handler, + reset: function (){ + delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] } + } + }, - $(selector).on(eventName, handler) - data.handlers.push(handler) + args: function (selector, eventName) { + var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - return { - selector: selector, - eventName: eventName, - handler: handler, - reset: function (){ - delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - } - } - }, + if (!actualArgs) { + throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent." + } - args: function (selector, eventName) { - var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + return actualArgs + }, - if (!actualArgs) { - throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent." - } + wasTriggered: function (selector, eventName) { + return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) + }, - return actualArgs - }, + wasTriggeredWith: function (selector, eventName, expectedArgs, util, customEqualityTesters) { + var actualArgs = jasmine.jQuery.events.args(selector, eventName).slice(1) - wasTriggered: function (selector, eventName) { - return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) - }, + if (Object.prototype.toString.call(expectedArgs) !== '[object Array]') + actualArgs = actualArgs[0] - wasTriggeredWith: function (selector, eventName, expectedArgs, env) { - var actualArgs = jasmine.JQuery.events.args(selector, eventName).slice(1) - if (Object.prototype.toString.call(expectedArgs) !== '[object Array]') { - actualArgs = actualArgs[0] - } - return env.equals_(expectedArgs, actualArgs) - }, + return util.equals(expectedArgs, actualArgs, customEqualityTesters) + }, - wasPrevented: function (selector, eventName) { - var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - , e = args ? args[0] : undefined + wasPrevented: function (selector, eventName) { + var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + , e = args ? args[0] : undefined - return e && e.isDefaultPrevented() - }, + return e && e.isDefaultPrevented() + }, - wasStopped: function (selector, eventName) { - var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - , e = args ? args[0] : undefined - return e && e.isPropagationStopped() - }, + wasStopped: function (selector, eventName) { + var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + , e = args ? args[0] : undefined + return e && e.isPropagationStopped() + }, - cleanUp: function () { - data.spiedEvents = {} - data.handlers = [] - } + cleanUp: function () { + data.spiedEvents = {} + data.handlers = [] } - }(jasmine.JQuery) + } + + var hasProperty = function (actualValue, expectedValue) { + if (expectedValue === undefined) + return actualValue !== undefined + + return actualValue === expectedValue + } - !function (){ - var jQueryMatchers = { - toHaveClass: function (className) { - return this.actual.hasClass(className) + beforeEach(function () { + jasmine.addMatchers({ + toHaveClass: function () { + return { + compare: function (actual, className) { + return { pass: $(actual).hasClass(className) } + } + } }, - toHaveCss: function (css){ - for (var prop in css){ - var value = css[prop] - // see issue #147 on gh - ;if (value === 'auto' && this.actual.get(0).style[prop] === 'auto') continue - if (this.actual.css(prop) !== value) return false + toHaveCss: function () { + return { + compare: function (actual, css) { + for (var prop in css){ + var value = css[prop] + // see issue #147 on gh + ;if (value === 'auto' && $(actual).get(0).style[prop] === 'auto') continue + if ($(actual).css(prop) !== value) return { pass: false } + } + return { pass: true } + } } - return true }, toBeVisible: function () { - return this.actual.is(':visible') + return { + compare: function (actual) { + return { pass: $(actual).is(':visible') } + } + } }, toBeHidden: function () { - return this.actual.is(':hidden') + return { + compare: function (actual) { + return { pass: $(actual).is(':hidden') } + } + } }, toBeSelected: function () { - return this.actual.is(':selected') + return { + compare: function (actual) { + return { pass: $(actual).is(':selected') } + } + } }, toBeChecked: function () { - return this.actual.is(':checked') + return { + compare: function (actual) { + return { pass: $(actual).is(':checked') } + } + } }, toBeEmpty: function () { - return this.actual.is(':empty') + return { + compare: function (actual) { + return { pass: $(actual).is(':empty') } + } + } }, - toExist: function () { - return this.actual.length + toBeInDOM: function () { + return { + compare: function (actual) { + return { pass: $.contains(document.documentElement, $(actual)[0]) } + } + } }, - toHaveLength: function (length) { - return this.actual.length === length + toExist: function () { + return { + compare: function (actual) { + return { pass: $(actual).length } + } + } }, - toHaveAttr: function (attributeName, expectedAttributeValue) { - return hasProperty(this.actual.attr(attributeName), expectedAttributeValue) + toHaveLength: function () { + return { + compare: function (actual, length) { + return { pass: $(actual).length === length } + } + } }, - toHaveProp: function (propertyName, expectedPropertyValue) { - return hasProperty(this.actual.prop(propertyName), expectedPropertyValue) + toHaveAttr: function () { + return { + compare: function (actual, attributeName, expectedAttributeValue) { + return { pass: hasProperty($(actual).attr(attributeName), expectedAttributeValue) } + } + } }, - toHaveId: function (id) { - return this.actual.attr('id') == id + toHaveProp: function () { + return { + compare: function (actual, propertyName, expectedPropertyValue) { + return { pass: hasProperty($(actual).prop(propertyName), expectedPropertyValue) } + } + } }, - toHaveHtml: function (html) { - return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html) + toHaveId: function () { + return { + compare: function (actual, id) { + return { pass: $(actual).attr('id') == id } + } + } }, - toContainHtml: function (html){ - var actualHtml = this.actual.html() - , expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html) - - return (actualHtml.indexOf(expectedHtml) >= 0) + toHaveHtml: function () { + return { + compare: function (actual, html) { + return { pass: $(actual).html() == jasmine.jQuery.browserTagCaseIndependentHtml(html) } + } + } }, - toHaveText: function (text) { - var trimmedText = $.trim(this.actual.text()) + toContainHtml: function () { + return { + compare: function (actual, html) { + var actualHtml = $(actual).html() + , expectedHtml = jasmine.jQuery.browserTagCaseIndependentHtml(html) - if (text && $.isFunction(text.test)) { - return text.test(trimmedText) - } else { - return trimmedText == text + return { pass: (actualHtml.indexOf(expectedHtml) >= 0) } + } } }, - toContainText: function (text) { - var trimmedText = $.trim(this.actual.text()) + toHaveText: function () { + return { + compare: function (actual, text) { + var trimmedText = $.trim($(actual).text()) - if (text && $.isFunction(text.test)) { - return text.test(trimmedText) - } else { - return trimmedText.indexOf(text) != -1 + if (text && $.isFunction(text.test)) { + return { pass: text.test(trimmedText) } + } else { + return { pass: trimmedText == text } + } + } } }, - toHaveValue: function (value) { - return this.actual.val() === value + toContainText: function () { + return { + compare: function (actual, text) { + var trimmedText = $.trim($(actual).text()) + + if (text && $.isFunction(text.test)) { + return { pass: text.test(trimmedText) } + } else { + return { pass: trimmedText.indexOf(text) != -1 } + } + } + } }, - toHaveData: function (key, expectedValue) { - return hasProperty(this.actual.data(key), expectedValue) + toHaveValue: function () { + return { + compare: function (actual, value) { + return { pass: $(actual).val() === value } + } + } }, - toBe: function (selector) { - return this.actual.is(selector) + toHaveData: function () { + return { + compare: function (actual, key, expectedValue) { + return { pass: hasProperty($(actual).data(key), expectedValue) } + } + } }, - toContain: function (selector) { - return this.actual.find(selector).length + toContainElement: function () { + return { + compare: function (actual, selector) { + if (window.debug) debugger + return { pass: $(actual).find(selector).length } + } + } }, - toBeMatchedBy: function (selector) { - return this.actual.filter(selector).length + toBeMatchedBy: function () { + return { + compare: function (actual, selector) { + return { pass: $(actual).filter(selector).length } + } + } }, - toBeDisabled: function (selector){ - return this.actual.is(':disabled') + toBeDisabled: function () { + return { + compare: function (actual, selector) { + return { pass: $(actual).is(':disabled') } + } + } }, toBeFocused: function (selector) { - return this.actual[0] === this.actual[0].ownerDocument.activeElement + return { + compare: function (actual, selector) { + return { pass: $(actual)[0] === $(actual)[0].ownerDocument.activeElement } + } + } }, - toHandle: function (event) { - var events = $._data(this.actual.get(0), "events") + toHandle: function () { + return { + compare: function (actual, event) { + var events = $._data($(actual).get(0), "events") - if(!events || !event || typeof event !== "string") { - return false - } + if (!events || !event || typeof event !== "string") { + return { pass: false } + } - var namespaces = event.split(".") - , eventType = namespaces.shift() - , sortedNamespaces = namespaces.slice(0).sort() - , namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") + var namespaces = event.split(".") + , eventType = namespaces.shift() + , sortedNamespaces = namespaces.slice(0).sort() + , namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") - if(events[eventType] && namespaces.length) { - for(var i = 0; i < events[eventType].length; i++) { - var namespace = events[eventType][i].namespace + if (events[eventType] && namespaces.length) { + for (var i = 0; i < events[eventType].length; i++) { + var namespace = events[eventType][i].namespace - if(namespaceRegExp.test(namespace)) { - return true + if (namespaceRegExp.test(namespace)) + return { pass: true } + } + } else { + return { pass: (events[eventType] && events[eventType].length > 0) } } + + return { pass: false } } - } else { - return events[eventType] && events[eventType].length > 0 } }, - toHandleWith: function (eventName, eventHandler) { - var normalizedEventName = eventName.split('.')[0] - , stack = $._data(this.actual.get(0), "events")[normalizedEventName] + toHandleWith: function () { + return { + compare: function (actual, eventName, eventHandler) { + var normalizedEventName = eventName.split('.')[0] + , stack = $._data($(actual).get(0), "events")[normalizedEventName] - for (var i = 0; i < stack.length; i++) { - if (stack[i].handler == eventHandler) return true + for (var i = 0; i < stack.length; i++) { + if (stack[i].handler == eventHandler) return { pass: true } + } + + return { pass: false } + } } + }, - return false - } - } + toHaveBeenTriggeredOn: function () { + return { + compare: function (actual, selector) { + var result = { pass: jasmine.jQuery.events.wasTriggered(selector, actual) } - var hasProperty = function (actualValue, expectedValue) { - if (expectedValue === undefined) return actualValue !== undefined + result.message = result.pass ? + "Expected event " + $(actual) + " not to have been triggered on " + selector : + "Expected event " + $(actual) + " to have been triggered on " + selector - return actualValue == expectedValue - } + return result; + } + } + }, - var bindMatcher = function (methodName) { - var builtInMatcher = jasmine.Matchers.prototype[methodName] + toHaveBeenTriggered: function (){ + return { + compare: function (actual) { + var eventName = actual.eventName + , selector = actual.selector + , result = { pass: jasmine.jQuery.events.wasTriggered(selector, eventName) } + + result.message = result.pass ? + "Expected event " + eventName + " not to have been triggered on " + selector : + "Expected event " + eventName + " to have been triggered on " + selector - jasmine.JQuery.matchersClass[methodName] = function () { - if (this.actual - && (this.actual instanceof $ - || jasmine.isDomNode(this.actual))) { - this.actual = $(this.actual) - var result = jQueryMatchers[methodName].apply(this, arguments) - , element + return result + } + } + }, - if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML") - this.actual = jasmine.JQuery.elementToString(this.actual) + toHaveBeenTriggeredOnAndWith: function (j$, customEqualityTesters) { + return { + compare: function (actual, selector, expectedArgs) { + var wasTriggered = jasmine.jQuery.events.wasTriggered(selector, actual) + , result = { pass: wasTriggered && jasmine.jQuery.events.wasTriggeredWith(selector, actual, expectedArgs, j$, customEqualityTesters) } + + if (wasTriggered) { + var actualArgs = jasmine.jQuery.events.args(selector, actual, expectedArgs)[1] + result.message = result.pass ? + "Expected event " + actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) : + "Expected event " + actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) + + } else { + // todo check on this + result.message = result.pass ? + "Expected event " + actual + " not to have been triggered on " + selector : + "Expected event " + actual + " to have been triggered on " + selector + } return result - } + } + } + }, - if (builtInMatcher) { - return builtInMatcher.apply(this, arguments) - } + toHaveBeenPreventedOn: function () { + return { + compare: function (actual, selector) { + var result = { pass: jasmine.jQuery.events.wasPrevented(selector, actual) } - return false - } - } + result.message = result.pass ? + "Expected event " + actual + " not to have been prevented on " + selector : + "Expected event " + actual + " to have been prevented on " + selector - for(var methodName in jQueryMatchers) { - bindMatcher(methodName) - } - }() + return result + } + } + }, - beforeEach(function () { - this.addMatchers(jasmine.JQuery.matchersClass) - this.addMatchers({ - toHaveBeenTriggeredOn: function (selector) { - this.message = function () { - return [ - "Expected event " + this.actual + " to have been triggered on " + selector, - "Expected event " + this.actual + " not to have been triggered on " + selector - ] - } - return jasmine.JQuery.events.wasTriggered(selector, this.actual) - } - }) - this.addMatchers({ - toHaveBeenTriggered: function (){ - var eventName = this.actual.eventName - , selector = this.actual.selector + toHaveBeenPrevented: function () { + return { + compare: function (actual) { + var eventName = actual.eventName + , selector = actual.selector + , result = { pass: jasmine.jQuery.events.wasPrevented(selector, eventName) } - this.message = function () { - return [ - "Expected event " + eventName + " to have been triggered on " + selector, - "Expected event " + eventName + " not to have been triggered on " + selector - ] - } + result.message = result.pass ? + "Expected event " + eventName + " not to have been prevented on " + selector : + "Expected event " + eventName + " to have been prevented on " + selector - return jasmine.JQuery.events.wasTriggered(selector, eventName) - } - }) - this.addMatchers({ - toHaveBeenTriggeredOnAndWith: function () { - var selector = arguments[0] - , expectedArgs = arguments[1] - , wasTriggered = jasmine.JQuery.events.wasTriggered(selector, this.actual) - - this.message = function () { - if (wasTriggered) { - var actualArgs = jasmine.JQuery.events.args(selector, this.actual, expectedArgs)[1] - return [ - "Expected event " + this.actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs), - "Expected event " + this.actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) - ] - } else { - return [ - "Expected event " + this.actual + " to have been triggered on " + selector, - "Expected event " + this.actual + " not to have been triggered on " + selector - ] - } - } - - return wasTriggered && jasmine.JQuery.events.wasTriggeredWith(selector, this.actual, expectedArgs, this.env) - } - }) - this.addMatchers({ - toHaveBeenPreventedOn: function (selector) { - this.message = function () { - return [ - "Expected event " + this.actual + " to have been prevented on " + selector, - "Expected event " + this.actual + " not to have been prevented on " + selector - ] + return result + } } + }, - return jasmine.JQuery.events.wasPrevented(selector, this.actual) - } - }) - this.addMatchers({ - toHaveBeenPrevented: function () { - var eventName = this.actual.eventName - , selector = this.actual.selector - this.message = function () { - return [ - "Expected event " + eventName + " to have been prevented on " + selector, - "Expected event " + eventName + " not to have been prevented on " + selector - ] - } + toHaveBeenStoppedOn: function () { + return { + compare: function (actual, selector) { + var result = { pass: jasmine.jQuery.events.wasStopped(selector, actual) } - return jasmine.JQuery.events.wasPrevented(selector, eventName) - } - }) - this.addMatchers({ - toHaveBeenStoppedOn: function (selector) { - this.message = function () { - return [ - "Expected event " + this.actual + " to have been stopped on " + selector, - "Expected event " + this.actual + " not to have been stopped on " + selector - ] + result.message = result.pass ? + "Expected event " + actual + " not to have been stopped on " + selector : + "Expected event " + actual + " to have been stopped on " + selector + + return result; + } } + }, - return jasmine.JQuery.events.wasStopped(selector, this.actual) - } - }) - this.addMatchers({ toHaveBeenStopped: function () { - var eventName = this.actual.eventName - , selector = this.actual.selector - this.message = function () { - return [ - "Expected event " + eventName + " to have been stopped on " + selector, - "Expected event " + eventName + " not to have been stopped on " + selector - ] - } - return jasmine.JQuery.events.wasStopped(selector, eventName) - } - }) - jasmine.getEnv().addEqualityTester(function (a, b) { - if(a instanceof jQuery && b instanceof jQuery) { - if(a.size() != b.size()) { - return jasmine.undefined - } - else if(a.is(b)) { - return true + return { + compare: function (actual) { + var eventName = actual.eventName + , selector = actual.selector + , result = { pass: jasmine.jQuery.events.wasStopped(selector, eventName) } + + result.message = result.pass ? + "Expected event " + eventName + " not to have been stopped on " + selector : + "Expected event " + eventName + " to have been stopped on " + selector + + return result + } } } + }) + + jasmine.getEnv().addCustomEqualityTester(function(a, b) { + if (a && b) { + if (a instanceof $ || jasmine.isDomNode(a)) { + var $a = $(a) + + if (b instanceof $) + return $a.length == b.length && a.is(b) + + return $a.is(b); + } - return jasmine.undefined + if (b instanceof $ || jasmine.isDomNode(b)) { + var $b = $(b) + + if (a instanceof jQuery) + return a.length == $b.length && $b.is(a) + + return $(b).is(a); + } + } + }) + + jasmine.getEnv().addCustomEqualityTester(function (a, b) { + if (a instanceof jQuery && b instanceof jQuery && a.size() == b.size()) + return a.is(b) }) }) afterEach(function () { jasmine.getFixtures().cleanUp() jasmine.getStyleFixtures().cleanUp() - jasmine.JQuery.events.cleanUp() + jasmine.jQuery.events.cleanUp() }) -}(window.jasmine, window.jQuery) - -+function (jasmine, global) { "use strict"; - global.readFixtures = function () { + window.readFixtures = function () { return jasmine.getFixtures().proxyCallTo_('read', arguments) } - global.preloadFixtures = function () { + window.preloadFixtures = function () { jasmine.getFixtures().proxyCallTo_('preload', arguments) } - global.loadFixtures = function () { + window.loadFixtures = function () { jasmine.getFixtures().proxyCallTo_('load', arguments) } - global.appendLoadFixtures = function () { + window.appendLoadFixtures = function () { jasmine.getFixtures().proxyCallTo_('appendLoad', arguments) } - global.setFixtures = function (html) { + window.setFixtures = function (html) { return jasmine.getFixtures().proxyCallTo_('set', arguments) } - global.appendSetFixtures = function () { + window.appendSetFixtures = function () { jasmine.getFixtures().proxyCallTo_('appendSet', arguments) } - global.sandbox = function (attributes) { + window.sandbox = function (attributes) { return jasmine.getFixtures().sandbox(attributes) } - global.spyOnEvent = function (selector, eventName) { - return jasmine.JQuery.events.spyOn(selector, eventName) + window.spyOnEvent = function (selector, eventName) { + return jasmine.jQuery.events.spyOn(selector, eventName) } - global.preloadStyleFixtures = function () { + window.preloadStyleFixtures = function () { jasmine.getStyleFixtures().proxyCallTo_('preload', arguments) } - global.loadStyleFixtures = function () { + window.loadStyleFixtures = function () { jasmine.getStyleFixtures().proxyCallTo_('load', arguments) } - global.appendLoadStyleFixtures = function () { + window.appendLoadStyleFixtures = function () { jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments) } - global.setStyleFixtures = function (html) { + window.setStyleFixtures = function (html) { jasmine.getStyleFixtures().proxyCallTo_('set', arguments) } - global.appendSetStyleFixtures = function (html) { + window.appendSetStyleFixtures = function (html) { jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments) } - global.loadJSONFixtures = function () { + window.loadJSONFixtures = function () { return jasmine.getJSONFixtures().proxyCallTo_('load', arguments) } - global.getJSONFixture = function (url) { + window.getJSONFixture = function (url) { return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url] } -}(jasmine, window); \ No newline at end of file +}(window, window.jasmine, window.jQuery); diff --git a/test/helpers/jasmine-sinon.js b/test/helpers/jasmine-sinon.js index c27d233f3..a70f7a2fe 100755 --- a/test/helpers/jasmine-sinon.js +++ b/test/helpers/jasmine-sinon.js @@ -1,43 +1,300 @@ -/* global jasmine */ +(function() { + var createCustomMatcher, createMatcher, jasmineMessageGenerator, jasmineName, sinonMatchers, sinonName, sinonToJasmineMap, spies, + __slice = [].slice; -'use strict'; + sinonMatchers = {}; -(function(jasmine, beforeEach) { + sinonToJasmineMap = { + 'called': 'toHaveBeenCalled', + 'calledOnce': 'toHaveBeenCalledOnce', + 'calledTwice': 'toHaveBeenCalledTwice', + 'calledThrice': 'toHaveBeenCalledThrice', + 'calledBefore': 'toHaveBeenCalledBefore', + 'calledAfter': 'toHaveBeenCalledAfter', + 'calledOn': 'toHaveBeenCalledOn', + 'alwaysCalledOn': 'toHaveBeenAlwaysCalledOn', + 'calledWith': 'toHaveBeenCalledWith', + 'alwaysCalledWith': 'toHaveBeenAlwaysCalledWith', + 'calledWithExactly': 'toHaveBeenCalledWithExactly', + 'alwaysCalledWithExactly': 'toHaveBeenAlwaysCalledWithExactly', + 'calledWithMatch': 'toHaveBeenCalledWithMatch', + 'alwaysCalledWithMatch': 'toHaveBeenAlwaysCalledWithMatch', + 'returned': 'toHaveReturned', + 'alwaysReturned': 'toHaveAlwaysReturned', + 'threw': 'toHaveThrown', + 'alwaysThrew': 'toHaveAlwaysThrown' + }; - var spyMatchers = 'called calledOnce calledTwice calledThrice calledBefore calledAfter calledOn alwaysCalledOn calledWith alwaysCalledWith calledWithExactly alwaysCalledWithExactly calledWithMatch alwaysCalledWithMatch'.split(' '), - i = spyMatchers.length, - spyMatcherHash = {}, - unusualMatchers = { - "returned": "toHaveReturned", - "alwaysReturned": "toHaveAlwaysReturned", - "threw": "toHaveThrown", - "alwaysThrew": "toHaveAlwaysThrown" + jasmineMessageGenerator = { + 'toHaveBeenCalled': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalled"; + return message.trim(); }, + 'toHaveBeenCalledOnce': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledOnce"; + return message.trim(); + }, + 'toHaveBeenCalledTwice': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledTwice"; + return message.trim(); + }, + 'toHaveBeenCalledThrice': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledThrice"; + return message.trim(); + }, + 'toHaveBeenCalledBefore': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledBefore "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was CalledBefore " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenCalledAfter': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledAfter "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was CalledAfter " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenCalledOn': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledOn "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was CalledOn " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenAlwaysCalledOn': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenAlwaysCalledOn "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was AlwaysCalledOn " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenCalledWith': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledWith "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was CalledWith " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenAlwaysCalledWith': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenAlwaysCalledWith "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was AlwaysCalledWith " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenCalledWithExactly': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledWithExactly "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was CalledWithExactly " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenAlwaysCalledWithExactly': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenAlwaysCalledWithExactly "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was AlwaysCalledWithExactly " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenCalledWithMatch': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenCalledWithMatch "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was CalledWithMatch " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveBeenAlwaysCalledWithMatch': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveBeenAlwaysCalledWithMatch "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but was AlwaysCalledWithMatch " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveReturned': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveReturned "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but returned " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveAlwaysReturned': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveAlwaysReturned "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but returned " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveThrown': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveThrown "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but threw " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + }, + 'toHaveAlwaysThrown': function(passed, spy, other_args) { + var message; + message = "Expected spy '" + spy + "' "; + if (passed) { + message += "not "; + } + message += "toHaveAlwaysThrown "; + if ((other_args != null ? other_args.length : void 0) > 0) { + message += jasmine.pp(other_args) + (" but threw " + (jasmine.pp(spy.lastCall.args))); + } + return message.trim(); + } + }; - getMatcherFunction = function(sinonName, matcherName) { - var original = jasmine.Matchers.prototype[matcherName]; - return function () { - if (jasmine.isSpy(this.actual) && original) { - return original.apply(this, arguments); + createCustomMatcher = function(arg, util, customEqualityTesters) { + return sinon.match(function(val) { + return util.equals(val, arg, customEqualityTesters); + }); + }; + + createMatcher = function(sinonName, jasmineName) { + var original; + original = jasmineRequire[jasmineName]; + return function(util, customEqualityTesters) { + return { + compare: function() { + var arg, args, compare, i, message, pass, sinonProperty, spy, _i, _len; + args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + spy = args[0]; + if (original && jasmine.isSpy(spy)) { + compare = original(jasmine)(util, customEqualityTesters).compare; + return compare.apply(null, args); + } + sinonProperty = spy[sinonName]; + for (i = _i = 0, _len = args.length; _i < _len; i = ++_i) { + arg = args[i]; + if (arg != null) { + if ((typeof arg.jasmineMatches === 'function') || (arg instanceof jasmine.ObjectContaining)) { + args[i] = createCustomMatcher(arg, util, customEqualityTesters); + } + } + } + if (typeof sinonProperty === 'function') { + pass = sinonProperty.apply(spy, args.slice(1)); + } else { + pass = sinonProperty; + } + message = jasmineMessageGenerator[jasmineName](pass, spy, args.slice(1)); + return { + pass: pass, + message: message + }; } - var sinonProperty = this.actual[sinonName]; - return (typeof sinonProperty === 'function') ? sinonProperty.apply(this.actual, arguments) : sinonProperty; }; }; + }; - while(i--) { - var sinonName = spyMatchers[i], - matcherName = "toHaveBeen" + sinonName.charAt(0).toUpperCase() + sinonName.slice(1); - - spyMatcherHash[matcherName] = getMatcherFunction(sinonName, matcherName); + for (sinonName in sinonToJasmineMap) { + jasmineName = sinonToJasmineMap[sinonName]; + sinonMatchers[jasmineName] = createMatcher(sinonName, jasmineName); } - for (var j in unusualMatchers) { - spyMatcherHash[unusualMatchers[j]] = getMatcherFunction(j, unusualMatchers[j]); - } + jasmine.Expectation.addCoreMatchers(sinonMatchers); + + spies = []; + + this.sinonSpyOn = function(obj, method) { + return spies.push(sinon.spy(obj, method)); + }; - beforeEach(function() { - this.addMatchers(spyMatcherHash); + afterEach(function() { + var spy, _i, _len; + for (_i = 0, _len = spies.length; _i < _len; _i++) { + spy = spies[_i]; + spy.restore(); + } + return spies = []; }); -})(jasmine, beforeEach); \ No newline at end of file +}).call(this); \ No newline at end of file diff --git a/test/helpers/sinon.js b/test/helpers/sinon.js index 2a32eb229..2f5890a31 100755 --- a/test/helpers/sinon.js +++ b/test/helpers/sinon.js @@ -1,12 +1,12 @@ /** - * Sinon.JS 1.7.3, 2013/06/20 + * Sinon.JS 1.9.1, 2014/04/03 * * @author Christian Johansen (christian@cjohansen.no) * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS * * (The BSD License) * - * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no + * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -34,337 +34,497 @@ */ this.sinon = (function () { -var buster = (function (setTimeout, B) { - var isNode = typeof require == "function" && typeof module == "object"; - var div = typeof document != "undefined" && document.createElement("div"); - var F = function () {}; - - var buster = { - bind: function bind(obj, methOrProp) { - var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; - var args = Array.prototype.slice.call(arguments, 2); - return function () { - var allArgs = args.concat(Array.prototype.slice.call(arguments)); - return method.apply(obj, allArgs); - }; - }, +var samsam, formatio; +function define(mod, deps, fn) { if (mod == "samsam") { samsam = deps(); } else { formatio = fn(samsam); } } +define.amd = true; +((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) || + (typeof module === "object" && + function (m) { module.exports = m(); }) || // Node + function (m) { this.samsam = m(); } // Browser globals +)(function () { + var o = Object.prototype; + var div = typeof document !== "undefined" && document.createElement("div"); + + function isNaN(value) { + // Unlike global isNaN, this avoids type coercion + // typeof check avoids IE host object issues, hat tip to + // lodash + var val = value; // JsLint thinks value !== value is "weird" + return typeof value === "number" && value !== val; + } - partial: function partial(fn) { - var args = [].slice.call(arguments, 1); - return function () { - return fn.apply(this, args.concat([].slice.call(arguments))); - }; - }, + function getClass(value) { + // Returns the internal [[Class]] by calling Object.prototype.toString + // with the provided value as this. Return value is a string, naming the + // internal class, e.g. "Array" + return o.toString.call(value).split(/[ \]]/)[1]; + } - create: function create(object) { - F.prototype = object; - return new F(); - }, + /** + * @name samsam.isArguments + * @param Object object + * + * Returns ``true`` if ``object`` is an ``arguments`` object, + * ``false`` otherwise. + */ + function isArguments(object) { + if (typeof object !== "object" || typeof object.length !== "number" || + getClass(object) === "Array") { + return false; + } + if (typeof object.callee == "function") { return true; } + try { + object[object.length] = 6; + delete object[object.length]; + } catch (e) { + return true; + } + return false; + } - extend: function extend(target) { - if (!target) { return; } - for (var i = 1, l = arguments.length, prop; i < l; ++i) { - for (prop in arguments[i]) { - target[prop] = arguments[i][prop]; - } - } - return target; - }, + /** + * @name samsam.isElement + * @param Object object + * + * Returns ``true`` if ``object`` is a DOM element node. Unlike + * Underscore.js/lodash, this function will return ``false`` if ``object`` + * is an *element-like* object, i.e. a regular object with a ``nodeType`` + * property that holds the value ``1``. + */ + function isElement(object) { + if (!object || object.nodeType !== 1 || !div) { return false; } + try { + object.appendChild(div); + object.removeChild(div); + } catch (e) { + return false; + } + return true; + } - nextTick: function nextTick(callback) { - if (typeof process != "undefined" && process.nextTick) { - return process.nextTick(callback); - } - setTimeout(callback, 0); - }, + /** + * @name samsam.keys + * @param Object object + * + * Return an array of own property names. + */ + function keys(object) { + var ks = [], prop; + for (prop in object) { + if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); } + } + return ks; + } - functionName: function functionName(func) { - if (!func) return ""; - if (func.displayName) return func.displayName; - if (func.name) return func.name; - var matches = func.toString().match(/function\s+([^\(]+)/m); - return matches && matches[1] || ""; - }, + /** + * @name samsam.isDate + * @param Object value + * + * Returns true if the object is a ``Date``, or *date-like*. Duck typing + * of date objects work by checking that the object has a ``getTime`` + * function whose return value equals the return value from the object's + * ``valueOf``. + */ + function isDate(value) { + return typeof value.getTime == "function" && + value.getTime() == value.valueOf(); + } - isNode: function isNode(obj) { - if (!div) return false; - try { - obj.appendChild(div); - obj.removeChild(div); - } catch (e) { - return false; - } - return true; - }, + /** + * @name samsam.isNegZero + * @param Object value + * + * Returns ``true`` if ``value`` is ``-0``. + */ + function isNegZero(value) { + return value === 0 && 1 / value === -Infinity; + } - isElement: function isElement(obj) { - return obj && obj.nodeType === 1 && buster.isNode(obj); - }, + /** + * @name samsam.equal + * @param Object obj1 + * @param Object obj2 + * + * Returns ``true`` if two objects are strictly equal. Compared to + * ``===`` there are two exceptions: + * + * - NaN is considered equal to NaN + * - -0 and +0 are not considered equal + */ + function identical(obj1, obj2) { + if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) { + return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2); + } + } - isArray: function isArray(arr) { - return Object.prototype.toString.call(arr) == "[object Array]"; - }, - flatten: function flatten(arr) { - var result = [], arr = arr || []; - for (var i = 0, l = arr.length; i < l; ++i) { - result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); - } - return result; - }, + /** + * @name samsam.deepEqual + * @param Object obj1 + * @param Object obj2 + * + * Deep equal comparison. Two values are "deep equal" if: + * + * - They are equal, according to samsam.identical + * - They are both date objects representing the same time + * - They are both arrays containing elements that are all deepEqual + * - They are objects with the same set of properties, and each property + * in ``obj1`` is deepEqual to the corresponding property in ``obj2`` + * + * Supports cyclic objects. + */ + function deepEqualCyclic(obj1, obj2) { + + // used for cyclic comparison + // contain already visited objects + var objects1 = [], + objects2 = [], + // contain pathes (position in the object structure) + // of the already visited objects + // indexes same as in objects arrays + paths1 = [], + paths2 = [], + // contains combinations of already compared objects + // in the manner: { "$1['ref']$2['ref']": true } + compared = {}; + + /** + * used to check, if the value of a property is an object + * (cyclic logic is only needed for objects) + * only needed for cyclic logic + */ + function isObject(value) { + + if (typeof value === 'object' && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String)) { - each: function each(arr, callback) { - for (var i = 0, l = arr.length; i < l; ++i) { - callback(arr[i]); + return true; } - }, - map: function map(arr, callback) { - var results = []; - for (var i = 0, l = arr.length; i < l; ++i) { - results.push(callback(arr[i])); - } - return results; - }, + return false; + } - parallel: function parallel(fns, callback) { - function cb(err, res) { - if (typeof callback == "function") { - callback(err, res); - callback = null; + /** + * returns the index of the given object in the + * given objects array, -1 if not contained + * only needed for cyclic logic + */ + function getIndex(objects, obj) { + + var i; + for (i = 0; i < objects.length; i++) { + if (objects[i] === obj) { + return i; } } - if (fns.length == 0) { return cb(null, []); } - var remaining = fns.length, results = []; - function makeDone(num) { - return function done(err, result) { - if (err) { return cb(err); } - results[num] = result; - if (--remaining == 0) { cb(null, results); } - }; - } - for (var i = 0, l = fns.length; i < l; ++i) { - fns[i](makeDone(i)); - } - }, - series: function series(fns, callback) { - function cb(err, res) { - if (typeof callback == "function") { - callback(err, res); - } + return -1; + } + + // does the recursion for the deep equal check + return (function deepEqual(obj1, obj2, path1, path2) { + var type1 = typeof obj1; + var type2 = typeof obj2; + + // == null also matches undefined + if (obj1 === obj2 || + isNaN(obj1) || isNaN(obj2) || + obj1 == null || obj2 == null || + type1 !== "object" || type2 !== "object") { + + return identical(obj1, obj2); } - var remaining = fns.slice(); - var results = []; - function callNext() { - if (remaining.length == 0) return cb(null, results); - var promise = remaining.shift()(next); - if (promise && typeof promise.then == "function") { - promise.then(buster.partial(next, null), next); + + // Elements are only equal if identical(expected, actual) + if (isElement(obj1) || isElement(obj2)) { return false; } + + var isDate1 = isDate(obj1), isDate2 = isDate(obj2); + if (isDate1 || isDate2) { + if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) { + return false; } } - function next(err, result) { - if (err) return cb(err); - results.push(result); - callNext(); - } - callNext(); - }, - countdown: function countdown(num, done) { - return function () { - if (--num == 0) done(); - }; - } - }; - - if (typeof process === "object" && - typeof require === "function" && typeof module === "object") { - var crypto = require("crypto"); - var path = require("path"); + if (obj1 instanceof RegExp && obj2 instanceof RegExp) { + if (obj1.toString() !== obj2.toString()) { return false; } + } - buster.tmpFile = function (fileName) { - var hashed = crypto.createHash("sha1"); - hashed.update(fileName); - var tmpfileName = hashed.digest("hex"); + var class1 = getClass(obj1); + var class2 = getClass(obj2); + var keys1 = keys(obj1); + var keys2 = keys(obj2); - if (process.platform == "win32") { - return path.join(process.env["TEMP"], tmpfileName); + if (isArguments(obj1) || isArguments(obj2)) { + if (obj1.length !== obj2.length) { return false; } } else { - return path.join("/tmp", tmpfileName); + if (type1 !== type2 || class1 !== class2 || + keys1.length !== keys2.length) { + return false; + } } - }; - } - if (Array.prototype.some) { - buster.some = function (arr, fn, thisp) { - return arr.some(fn, thisp); - }; - } else { - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some - buster.some = function (arr, fun, thisp) { - if (arr == null) { throw new TypeError(); } - arr = Object(arr); - var len = arr.length >>> 0; - if (typeof fun !== "function") { throw new TypeError(); } - - for (var i = 0; i < len; i++) { - if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { + var key, i, l, + // following vars are used for the cyclic logic + value1, value2, + isObject1, isObject2, + index1, index2, + newPath1, newPath2; + + for (i = 0, l = keys1.length; i < l; i++) { + key = keys1[i]; + if (!o.hasOwnProperty.call(obj2, key)) { + return false; + } + + // Start of the cyclic logic + + value1 = obj1[key]; + value2 = obj2[key]; + + isObject1 = isObject(value1); + isObject2 = isObject(value2); + + // determine, if the objects were already visited + // (it's faster to check for isObject first, than to + // get -1 from getIndex for non objects) + index1 = isObject1 ? getIndex(objects1, value1) : -1; + index2 = isObject2 ? getIndex(objects2, value2) : -1; + + // determine the new pathes of the objects + // - for non cyclic objects the current path will be extended + // by current property name + // - for cyclic objects the stored path is taken + newPath1 = index1 !== -1 + ? paths1[index1] + : path1 + '[' + JSON.stringify(key) + ']'; + newPath2 = index2 !== -1 + ? paths2[index2] + : path2 + '[' + JSON.stringify(key) + ']'; + + // stop recursion if current objects are already compared + if (compared[newPath1 + newPath2]) { return true; } - } - return false; - }; - } + // remember the current objects and their pathes + if (index1 === -1 && isObject1) { + objects1.push(value1); + paths1.push(newPath1); + } + if (index2 === -1 && isObject2) { + objects2.push(value2); + paths2.push(newPath2); + } - if (Array.prototype.filter) { - buster.filter = function (arr, fn, thisp) { - return arr.filter(fn, thisp); - }; - } else { - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter - buster.filter = function (fn, thisp) { - if (this == null) { throw new TypeError(); } - - var t = Object(this); - var len = t.length >>> 0; - if (typeof fn != "function") { throw new TypeError(); } - - var res = []; - for (var i = 0; i < len; i++) { - if (i in t) { - var val = t[i]; // in case fun mutates this - if (fn.call(thisp, val, i, t)) { res.push(val); } + // remember that the current objects are already compared + if (isObject1 && isObject2) { + compared[newPath1 + newPath2] = true; + } + + // End of cyclic logic + + // neither value1 nor value2 is a cycle + // continue with next level + if (!deepEqual(value1, value2, newPath1, newPath2)) { + return false; } } - return res; - }; + return true; + + }(obj1, obj2, '$1', '$2')); } - if (isNode) { - module.exports = buster; - buster.eventEmitter = require("./buster-event-emitter"); - Object.defineProperty(buster, "defineVersionGetter", { - get: function () { - return require("./define-version-getter"); + var match; + + function arrayContains(array, subset) { + if (subset.length === 0) { return true; } + var i, l, j, k; + for (i = 0, l = array.length; i < l; ++i) { + if (match(array[i], subset[0])) { + for (j = 0, k = subset.length; j < k; ++j) { + if (!match(array[i + j], subset[j])) { return false; } + } + return true; } - }); + } + return false; } - return buster.extend(B || {}, buster); -}(setTimeout, buster)); -if (typeof buster === "undefined") { - var buster = {}; -} + /** + * @name samsam.match + * @param Object object + * @param Object matcher + * + * Compare arbitrary value ``object`` with matcher. + */ + match = function match(object, matcher) { + if (matcher && typeof matcher.test === "function") { + return matcher.test(object); + } -if (typeof module === "object" && typeof require === "function") { - buster = require("buster-core"); -} + if (typeof matcher === "function") { + return matcher(object) === true; + } + + if (typeof matcher === "string") { + matcher = matcher.toLowerCase(); + var notNull = typeof object === "string" || !!object; + return notNull && + (String(object)).toLowerCase().indexOf(matcher) >= 0; + } + + if (typeof matcher === "number") { + return matcher === object; + } + + if (typeof matcher === "boolean") { + return matcher === object; + } -buster.format = buster.format || {}; -buster.format.excludeConstructors = ["Object", /^.$/]; -buster.format.quoteStrings = true; + if (getClass(object) === "Array" && getClass(matcher) === "Array") { + return arrayContains(object, matcher); + } -buster.format.ascii = (function () { + if (matcher && typeof matcher === "object") { + var prop; + for (prop in matcher) { + if (!match(object[prop], matcher[prop])) { + return false; + } + } + return true; + } + + throw new Error("Matcher was not a string, a number, a " + + "function, a boolean or an object"); + }; + + return { + isArguments: isArguments, + isElement: isElement, + isDate: isDate, + isNegZero: isNegZero, + identical: identical, + deepEqual: deepEqualCyclic, + match: match, + keys: keys + }; +}); +((typeof define === "function" && define.amd && function (m) { + define("formatio", ["samsam"], m); +}) || (typeof module === "object" && function (m) { + module.exports = m(require("samsam")); +}) || function (m) { this.formatio = m(this.samsam); } +)(function (samsam) { + + var formatio = { + excludeConstructors: ["Object", /^.$/], + quoteStrings: true + }; var hasOwn = Object.prototype.hasOwnProperty; var specialObjects = []; - if (typeof global != "undefined") { - specialObjects.push({ obj: global, value: "[object global]" }); + if (typeof global !== "undefined") { + specialObjects.push({ object: global, value: "[object global]" }); + } + if (typeof document !== "undefined") { + specialObjects.push({ + object: document, + value: "[object HTMLDocument]" + }); } - if (typeof document != "undefined") { - specialObjects.push({ obj: document, value: "[object HTMLDocument]" }); + if (typeof window !== "undefined") { + specialObjects.push({ object: window, value: "[object Window]" }); } - if (typeof window != "undefined") { - specialObjects.push({ obj: window, value: "[object Window]" }); + + function functionName(func) { + if (!func) { return ""; } + if (func.displayName) { return func.displayName; } + if (func.name) { return func.name; } + var matches = func.toString().match(/function\s+([^\(]+)/m); + return (matches && matches[1]) || ""; } - function keys(object) { - var k = Object.keys && Object.keys(object) || []; + function constructorName(f, object) { + var name = functionName(object && object.constructor); + var excludes = f.excludeConstructors || + formatio.excludeConstructors || []; - if (k.length == 0) { - for (var prop in object) { - if (hasOwn.call(object, prop)) { - k.push(prop); - } + var i, l; + for (i = 0, l = excludes.length; i < l; ++i) { + if (typeof excludes[i] === "string" && excludes[i] === name) { + return ""; + } else if (excludes[i].test && excludes[i].test(name)) { + return ""; } } - return k.sort(); + return name; } function isCircular(object, objects) { - if (typeof object != "object") { - return false; - } - - for (var i = 0, l = objects.length; i < l; ++i) { - if (objects[i] === object) { - return true; - } + if (typeof object !== "object") { return false; } + var i, l; + for (i = 0, l = objects.length; i < l; ++i) { + if (objects[i] === object) { return true; } } - return false; } - function ascii(object, processed, indent) { - if (typeof object == "string") { - var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; + function ascii(f, object, processed, indent) { + if (typeof object === "string") { + var qs = f.quoteStrings; + var quote = typeof qs !== "boolean" || qs; return processed || quote ? '"' + object + '"' : object; } - if (typeof object == "function" && !(object instanceof RegExp)) { + if (typeof object === "function" && !(object instanceof RegExp)) { return ascii.func(object); } processed = processed || []; - if (isCircular(object, processed)) { - return "[Circular]"; - } + if (isCircular(object, processed)) { return "[Circular]"; } - if (Object.prototype.toString.call(object) == "[object Array]") { - return ascii.array.call(this, object, processed); + if (Object.prototype.toString.call(object) === "[object Array]") { + return ascii.array.call(f, object, processed); } - if (!object) { - return "" + object; - } - - if (buster.isElement(object)) { - return ascii.element(object); - } + if (!object) { return String((1/object) === -Infinity ? "-0" : object); } + if (samsam.isElement(object)) { return ascii.element(object); } - if (typeof object.toString == "function" && - object.toString !== Object.prototype.toString) { + if (typeof object.toString === "function" && + object.toString !== Object.prototype.toString) { return object.toString(); } - for (var i = 0, l = specialObjects.length; i < l; i++) { - if (object === specialObjects[i].obj) { + var i, l; + for (i = 0, l = specialObjects.length; i < l; i++) { + if (object === specialObjects[i].object) { return specialObjects[i].value; } } - return ascii.object.call(this, object, processed, indent); + return ascii.object.call(f, object, processed, indent); } ascii.func = function (func) { - return "function " + buster.functionName(func) + "() {}"; + return "function " + functionName(func) + "() {}"; }; ascii.array = function (array, processed) { processed = processed || []; processed.push(array); - var pieces = []; - - for (var i = 0, l = array.length; i < l; ++i) { - pieces.push(ascii.call(this, array[i], processed)); + var i, l, pieces = []; + for (i = 0, l = array.length; i < l; ++i) { + pieces.push(ascii(this, array[i], processed)); } - return "[" + pieces.join(", ") + "]"; }; @@ -372,13 +532,9 @@ buster.format.ascii = (function () { processed = processed || []; processed.push(object); indent = indent || 0; - var pieces = [], properties = keys(object), prop, str, obj; - var is = ""; + var pieces = [], properties = samsam.keys(object).sort(); var length = 3; - - for (var i = 0, l = indent; i < l; ++i) { - is += " "; - } + var prop, str, obj, i, l; for (i = 0, l = properties.length; i < l; ++i) { prop = properties[i]; @@ -387,7 +543,7 @@ buster.format.ascii = (function () { if (isCircular(obj, processed)) { str = "[Circular]"; } else { - str = ascii.call(this, obj, processed, indent + 2); + str = ascii(this, obj, processed, indent + 2); } str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; @@ -395,28 +551,28 @@ buster.format.ascii = (function () { pieces.push(str); } - var cons = ascii.constructorName.call(this, object); - var prefix = cons ? "[" + cons + "] " : "" + var cons = constructorName(this, object); + var prefix = cons ? "[" + cons + "] " : ""; + var is = ""; + for (i = 0, l = indent; i < l; ++i) { is += " "; } - return (length + indent) > 80 ? - prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : - prefix + "{ " + pieces.join(", ") + " }"; + if (length + indent > 80) { + return prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + + is + "}"; + } + return prefix + "{ " + pieces.join(", ") + " }"; }; ascii.element = function (element) { var tagName = element.tagName.toLowerCase(); - var attrs = element.attributes, attribute, pairs = [], attrName; - - for (var i = 0, l = attrs.length; i < l; ++i) { - attribute = attrs.item(i); - attrName = attribute.nodeName.toLowerCase().replace("html:", ""); - - if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { - continue; - } + var attrs = element.attributes, attr, pairs = [], attrName, i, l, val; - if (!!attribute.nodeValue) { - pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); + for (i = 0, l = attrs.length; i < l; ++i) { + attr = attrs.item(i); + attrName = attr.nodeName.toLowerCase().replace("html:", ""); + val = attr.nodeValue; + if (attrName !== "contenteditable" || val !== "inherit") { + if (!!val) { pairs.push(attrName + "=\"" + val + "\""); } } } @@ -427,32 +583,36 @@ buster.format.ascii = (function () { content = content.substr(0, 20) + "[...]"; } - var res = formatted + pairs.join(" ") + ">" + content + "" + tagName + ">"; + var res = formatted + pairs.join(" ") + ">" + content + + "" + tagName + ">"; return res.replace(/ contentEditable="inherit"/, ""); }; - ascii.constructorName = function (object) { - var name = buster.functionName(object && object.constructor); - var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; - - for (var i = 0, l = excludes.length; i < l; ++i) { - if (typeof excludes[i] == "string" && excludes[i] == name) { - return ""; - } else if (excludes[i].test && excludes[i].test(name)) { - return ""; - } + function Formatio(options) { + for (var opt in options) { + this[opt] = options[opt]; } + } - return name; - }; + Formatio.prototype = { + functionName: functionName, - return ascii; -}()); + configure: function (options) { + return new Formatio(options); + }, -if (typeof module != "undefined") { - module.exports = buster.format; -} + constructorName: function (object) { + return constructorName(this, object); + }, + + ascii: function (object, processed, indent) { + return ascii(this, object, processed, indent); + } + }; + + return Formatio.prototype; +}); /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ /*global module, require, __dirname, document*/ /** @@ -464,7 +624,7 @@ if (typeof module != "undefined") { * Copyright (c) 2010-2013 Christian Johansen */ -var sinon = (function (buster) { +var sinon = (function (formatio) { var div = typeof document != "undefined" && document.createElement("div"); var hasOwn = Object.prototype.hasOwnProperty; @@ -495,6 +655,10 @@ var sinon = (function (buster) { return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply); } + function isReallyNaN(val) { + return typeof val === 'number' && isNaN(val); + } + function mirrorProperties(target, source) { for (var prop in source) { if (!hasOwn.call(target, prop)) { @@ -517,26 +681,38 @@ var sinon = (function (buster) { throw new TypeError("Method wrapper should be function"); } - var wrappedMethod = object[property]; + var wrappedMethod = object[property], + error; if (!isFunction(wrappedMethod)) { - throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + property + " as function"); } if (wrappedMethod.restore && wrappedMethod.restore.sinon) { - throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); + error = new TypeError("Attempted to wrap " + property + " which is already wrapped"); } if (wrappedMethod.calledBefore) { var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; - throw new TypeError("Attempted to wrap " + property + " which is already " + verb); + error = new TypeError("Attempted to wrap " + property + " which is already " + verb); + } + + if (error) { + if (wrappedMethod._stack) { + error.stack += '\n--------------\n' + wrappedMethod._stack; + } + throw error; } - // IE 8 does not support hasOwnProperty on the window object. - var owned = hasOwn.call(object, property); + // IE 8 does not support hasOwnProperty on the window object and Firefox has a problem + // when using hasOwn.call on objects from other frames. + var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property); object[property] = method; method.displayName = property; + // Set up a stack trace which can be used later to find what line of + // code the original method was created on. + method._stack = (new Error('Stack Trace for original')).stack; method.restore = function () { // For prototype properties try to reset by delete first. @@ -584,8 +760,13 @@ var sinon = (function (buster) { if (sinon.match && sinon.match.isMatcher(a)) { return a.test(b); } - if (typeof a != "object" || typeof b != "object") { - return a === b; + + if (typeof a != 'object' || typeof b != 'object') { + if (isReallyNaN(a) && isReallyNaN(b)) { + return true; + } else { + return a === b; + } } if (isElement(a) || isElement(b)) { @@ -600,31 +781,26 @@ var sinon = (function (buster) { return false; } + if (a instanceof RegExp && b instanceof RegExp) { + return (a.source === b.source) && (a.global === b.global) && + (a.ignoreCase === b.ignoreCase) && (a.multiline === b.multiline); + } + var aString = Object.prototype.toString.call(a); if (aString != Object.prototype.toString.call(b)) { return false; } - if (aString == "[object Array]") { - if (a.length !== b.length) { - return false; - } - - for (var i = 0, l = a.length; i < l; i += 1) { - if (!deepEqual(a[i], b[i])) { - return false; - } - } - - return true; - } - if (aString == "[object Date]") { return a.valueOf() === b.valueOf(); } var prop, aLength = 0, bLength = 0; + if (aString == "[object Array]" && a.length !== b.length) { + return false; + } + for (prop in a) { aLength += 1; @@ -731,7 +907,7 @@ var sinon = (function (buster) { log: function () {}, logError: function (label, err) { - var msg = label + " threw exception: " + var msg = label + " threw exception: "; sinon.log(msg + "[" + err.name + "] " + err.message); if (err.stack) { sinon.log(err.stack); } @@ -773,14 +949,21 @@ var sinon = (function (buster) { } }; - var isNode = typeof module == "object" && typeof require == "function"; + var isNode = typeof module !== "undefined" && module.exports; + var isAMD = typeof define === 'function' && typeof define.amd === 'object' && define.amd; - if (isNode) { + if (isAMD) { + define(function(){ + return sinon; + }); + } else if (isNode) { try { - buster = { format: require("buster-format") }; + formatio = require("formatio"); } catch (e) {} module.exports = sinon; module.exports.spy = require("./sinon/spy"); + module.exports.spyCall = require("./sinon/call"); + module.exports.behavior = require("./sinon/behavior"); module.exports.stub = require("./sinon/stub"); module.exports.mock = require("./sinon/mock"); module.exports.collection = require("./sinon/collection"); @@ -792,9 +975,8 @@ var sinon = (function (buster) { module.exports.match = require("./sinon/match"); } - if (buster) { - var formatter = sinon.create(buster.format); - formatter.quoteStrings = false; + if (formatio) { + var formatter = formatio.configure({ quoteStrings: false }); sinon.format = function () { return formatter.ascii.apply(formatter, arguments); }; @@ -811,7 +993,7 @@ var sinon = (function (buster) { } return sinon; -}(typeof buster == "object" && buster)); +}(typeof formatio == "object" && formatio)); /* @depend ../sinon.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ @@ -826,7 +1008,7 @@ var sinon = (function (buster) { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -879,8 +1061,10 @@ var sinon = (function (buster) { } matcher.or = function (m2) { - if (!isMatcher(m2)) { + if (!arguments.length) { throw new TypeError("Matcher expected"); + } else if (!isMatcher(m2)) { + m2 = match(m2); } var m1 = this; var or = sinon.create(matcher); @@ -892,8 +1076,10 @@ var sinon = (function (buster) { }; matcher.and = function (m2) { - if (!isMatcher(m2)) { + if (!arguments.length) { throw new TypeError("Matcher expected"); + } else if (!isMatcher(m2)) { + m2 = match(m2); } var m1 = this; var and = sinon.create(matcher); @@ -1069,13 +1255,16 @@ var sinon = (function (buster) { * Copyright (c) 2013 Maximilian Antoni */ -var commonJSModule = typeof module == "object" && typeof require == "function"; +(function (sinon) { + var commonJSModule = typeof module !== 'undefined' && module.exports; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } -if (!this.sinon && commonJSModule) { - var sinon = require("../sinon"); -} + if (!sinon) { + return; + } -(function (sinon) { function throwYieldError(proxy, text, args) { var msg = sinon.functionName(proxy) + text; if (args.length) { @@ -1140,8 +1329,8 @@ if (!this.sinon && commonJSModule) { return this.exception === error || this.exception.name === error; }, - calledWithNew: function calledWithNew(thisValue) { - return this.thisValue instanceof this.proxy; + calledWithNew: function calledWithNew() { + return this.proxy.prototype && this.thisValue instanceof this.proxy; }, calledBefore: function (other) { @@ -1241,14 +1430,20 @@ if (!this.sinon && commonJSModule) { proxyCall.callId = id; return proxyCall; - }; + } createSpyCall.toString = callProto.toString; // used by mocks - sinon.spyCall = createSpyCall; + if (commonJSModule) { + module.exports = createSpyCall; + } else { + sinon.spyCall = createSpyCall; + } }(typeof sinon == "object" && sinon || null)); + /** * @depend ../sinon.js + * @depend call.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global module, require, sinon*/ @@ -1262,11 +1457,19 @@ if (!this.sinon && commonJSModule) { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; var push = Array.prototype.push; var slice = Array.prototype.slice; var callId = 0; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + function spy(object, property) { if (!property && typeof object == "function") { return spy.create(object); @@ -1285,8 +1488,6 @@ if (!this.sinon && commonJSModule) { return; } - var alen = args.length; - for (var i = 0, l = fakes.length; i < l; i++) { if (fakes[i].matches(args, strict)) { return fakes[i]; @@ -1393,18 +1594,24 @@ if (!this.sinon && commonJSModule) { } else { returnValue = (this.func || func).apply(thisValue, args); } + + var thisCall = this.getCall(this.callCount - 1); + if (thisCall.calledWithNew() && typeof returnValue !== 'object') { + returnValue = thisValue; + } } catch (e) { - push.call(this.returnValues, undefined); exception = e; - throw e; - } finally { - push.call(this.exceptions, exception); } + push.call(this.exceptions, exception); push.call(this.returnValues, returnValue); createCallProperties.call(this); + if (exception !== undefined) { + throw exception; + } + return returnValue; }, @@ -1418,6 +1625,17 @@ if (!this.sinon && commonJSModule) { this.callIds[i]); }, + getCalls: function () { + var calls = []; + var i; + + for (i = 0; i < this.callCount; i++) { + calls.push(this.getCall(i)); + } + + return calls; + }, + calledBefore: function calledBefore(spyFn) { if (!this.called) { return false; @@ -1454,6 +1672,7 @@ if (!this.sinon && commonJSModule) { var original = this; var fake = this._create(); fake.matchingAguments = args; + fake.parent = this; push.call(this.fakes, fake); fake.withArgs = function () { @@ -1494,7 +1713,7 @@ if (!this.sinon && commonJSModule) { if (typeof formatter == "function") { return formatter.call(null, spy, args); - } else if (!isNaN(parseInt(specifyer), 10)) { + } else if (!isNaN(parseInt(specifyer, 10))) { return sinon.format(args[specifyer - 1]); } @@ -1600,38 +1819,370 @@ if (!this.sinon && commonJSModule) { "t": function (spy) { var objects = []; - for (var i = 0, l = spy.callCount; i < l; ++i) { - push.call(objects, sinon.format(spy.thisValues[i])); + for (var i = 0, l = spy.callCount; i < l; ++i) { + push.call(objects, sinon.format(spy.thisValues[i])); + } + + return objects.join(", "); + }, + + "*": function (spy, args) { + var formatted = []; + + for (var i = 0, l = args.length; i < l; ++i) { + push.call(formatted, sinon.format(args[i])); + } + + return formatted.join(", "); + } + }; + + sinon.extend(spy, spyApi); + + spy.spyCall = sinon.spyCall; + + if (commonJSModule) { + module.exports = spy; + } else { + sinon.spy = spy; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + */ +/*jslint eqeqeq: false, onevar: false*/ +/*global module, require, sinon, process, setImmediate, setTimeout*/ +/** + * Stub behavior + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Tim Fischbach (mail@timfischbach.de) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module !== 'undefined' && module.exports; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + var slice = Array.prototype.slice; + var join = Array.prototype.join; + var proto; + + var nextTick = (function () { + if (typeof process === "object" && typeof process.nextTick === "function") { + return process.nextTick; + } else if (typeof setImmediate === "function") { + return setImmediate; + } else { + return function (callback) { + setTimeout(callback, 0); + }; + } + })(); + + function throwsException(error, message) { + if (typeof error == "string") { + this.exception = new Error(message || ""); + this.exception.name = error; + } else if (!error) { + this.exception = new Error("Error"); + } else { + this.exception = error; + } + + return this; + } + + function getCallback(behavior, args) { + var callArgAt = behavior.callArgAt; + + if (callArgAt < 0) { + var callArgProp = behavior.callArgProp; + + for (var i = 0, l = args.length; i < l; ++i) { + if (!callArgProp && typeof args[i] == "function") { + return args[i]; + } + + if (callArgProp && args[i] && + typeof args[i][callArgProp] == "function") { + return args[i][callArgProp]; + } + } + + return null; + } + + return args[callArgAt]; + } + + function getCallbackError(behavior, func, args) { + if (behavior.callArgAt < 0) { + var msg; + + if (behavior.callArgProp) { + msg = sinon.functionName(behavior.stub) + + " expected to yield to '" + behavior.callArgProp + + "', but no object with such a property was passed."; + } else { + msg = sinon.functionName(behavior.stub) + + " expected to yield, but no callback was passed."; + } + + if (args.length > 0) { + msg += " Received [" + join.call(args, ", ") + "]"; + } + + return msg; + } + + return "argument at index " + behavior.callArgAt + " is not a function: " + func; + } + + function callCallback(behavior, args) { + if (typeof behavior.callArgAt == "number") { + var func = getCallback(behavior, args); + + if (typeof func != "function") { + throw new TypeError(getCallbackError(behavior, func, args)); + } + + if (behavior.callbackAsync) { + nextTick(function() { + func.apply(behavior.callbackContext, behavior.callbackArguments); + }); + } else { + func.apply(behavior.callbackContext, behavior.callbackArguments); + } + } + } + + proto = { + create: function(stub) { + var behavior = sinon.extend({}, sinon.behavior); + delete behavior.create; + behavior.stub = stub; + + return behavior; + }, + + isPresent: function() { + return (typeof this.callArgAt == 'number' || + this.exception || + typeof this.returnArgAt == 'number' || + this.returnThis || + this.returnValueDefined); + }, + + invoke: function(context, args) { + callCallback(this, args); + + if (this.exception) { + throw this.exception; + } else if (typeof this.returnArgAt == 'number') { + return args[this.returnArgAt]; + } else if (this.returnThis) { + return context; + } + + return this.returnValue; + }, + + onCall: function(index) { + return this.stub.onCall(index); + }, + + onFirstCall: function() { + return this.stub.onFirstCall(); + }, + + onSecondCall: function() { + return this.stub.onSecondCall(); + }, + + onThirdCall: function() { + return this.stub.onThirdCall(); + }, + + withArgs: function(/* arguments */) { + throw new Error('Defining a stub by invoking "stub.onCall(...).withArgs(...)" is not supported. ' + + 'Use "stub.withArgs(...).onCall(...)" to define sequential behavior for calls with certain arguments.'); + }, + + callsArg: function callsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAt = pos; + this.callbackArguments = []; + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + callsArgOn: function callsArgOn(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = pos; + this.callbackArguments = []; + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + callsArgWith: function callsArgWith(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAt = pos; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + callsArgOnWith: function callsArgWith(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = pos; + this.callbackArguments = slice.call(arguments, 2); + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yields: function () { + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 0); + this.callbackContext = undefined; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yieldsOn: function (context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = context; + this.callArgProp = undefined; + this.callbackAsync = false; + + return this; + }, + + yieldsTo: function (prop) { + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 1); + this.callbackContext = undefined; + this.callArgProp = prop; + this.callbackAsync = false; + + return this; + }, + + yieldsToOn: function (prop, context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); } - return objects.join(", "); + this.callArgAt = -1; + this.callbackArguments = slice.call(arguments, 2); + this.callbackContext = context; + this.callArgProp = prop; + this.callbackAsync = false; + + return this; }, - "*": function (spy, args) { - var formatted = []; - for (var i = 0, l = args.length; i < l; ++i) { - push.call(formatted, sinon.format(args[i])); + "throws": throwsException, + throwsException: throwsException, + + returns: function returns(value) { + this.returnValue = value; + this.returnValueDefined = true; + + return this; + }, + + returnsArg: function returnsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); } - return formatted.join(", "); + this.returnArgAt = pos; + + return this; + }, + + returnsThis: function returnsThis() { + this.returnThis = true; + + return this; } }; - sinon.extend(spy, spyApi); - - spy.spyCall = sinon.spyCall; + // create asynchronous versions of callsArg* and yields* methods + for (var method in proto) { + // need to avoid creating anotherasync versions of the newly added async methods + if (proto.hasOwnProperty(method) && + method.match(/^(callsArg|yields)/) && + !method.match(/Async/)) { + proto[method + 'Async'] = (function (syncFnName) { + return function () { + var result = this[syncFnName].apply(this, arguments); + this.callbackAsync = true; + return result; + }; + })(method); + } + } if (commonJSModule) { - module.exports = spy; + module.exports = proto; } else { - sinon.spy = spy; + sinon.behavior = proto; } }(typeof sinon == "object" && sinon || null)); - /** * @depend ../sinon.js * @depend spy.js + * @depend behavior.js */ /*jslint eqeqeq: false, onevar: false*/ /*global module, require, sinon*/ @@ -1645,7 +2196,7 @@ if (!this.sinon && commonJSModule) { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -1668,11 +2219,11 @@ if (!this.sinon && commonJSModule) { wrapper = stub.create(); } - if (!object && !property) { + if (!object && typeof property === "undefined") { return sinon.stub.create(); } - if (!property && !!object && typeof object == "object") { + if (typeof property === "undefined" && typeof object == "object") { for (var prop in object) { if (typeof object[prop] === "function") { stub(object, prop); @@ -1685,128 +2236,26 @@ if (!this.sinon && commonJSModule) { return sinon.wrapMethod(object, property, wrapper); } - function getChangingValue(stub, property) { - var index = stub.callCount - 1; - var values = stub[property]; - var prop = index in values ? values[index] : values[values.length - 1]; - stub[property + "Last"] = prop; - - return prop; - } - - function getCallback(stub, args) { - var callArgAt = getChangingValue(stub, "callArgAts"); - - if (callArgAt < 0) { - var callArgProp = getChangingValue(stub, "callArgProps"); - - for (var i = 0, l = args.length; i < l; ++i) { - if (!callArgProp && typeof args[i] == "function") { - return args[i]; - } - - if (callArgProp && args[i] && - typeof args[i][callArgProp] == "function") { - return args[i][callArgProp]; - } - } - - return null; - } - - return args[callArgAt]; + function getDefaultBehavior(stub) { + return stub.defaultBehavior || getParentBehaviour(stub) || sinon.behavior.create(stub); } - var join = Array.prototype.join; - - function getCallbackError(stub, func, args) { - if (stub.callArgAtsLast < 0) { - var msg; - - if (stub.callArgPropsLast) { - msg = sinon.functionName(stub) + - " expected to yield to '" + stub.callArgPropsLast + - "', but no object with such a property was passed." - } else { - msg = sinon.functionName(stub) + - " expected to yield, but no callback was passed." - } - - if (args.length > 0) { - msg += " Received [" + join.call(args, ", ") + "]"; - } - - return msg; - } - - return "argument at index " + stub.callArgAtsLast + " is not a function: " + func; + function getParentBehaviour(stub) { + return (stub.parent && getCurrentBehavior(stub.parent)); } - var nextTick = (function () { - if (typeof process === "object" && typeof process.nextTick === "function") { - return process.nextTick; - } else if (typeof setImmediate === "function") { - return setImmediate; - } else { - return function (callback) { - setTimeout(callback, 0); - }; - } - })(); - - function callCallback(stub, args) { - if (stub.callArgAts.length > 0) { - var func = getCallback(stub, args); - - if (typeof func != "function") { - throw new TypeError(getCallbackError(stub, func, args)); - } - - var callbackArguments = getChangingValue(stub, "callbackArguments"); - var callbackContext = getChangingValue(stub, "callbackContexts"); - - if (stub.callbackAsync) { - nextTick(function() { - func.apply(callbackContext, callbackArguments); - }); - } else { - func.apply(callbackContext, callbackArguments); - } - } + function getCurrentBehavior(stub) { + var behavior = stub.behaviors[stub.callCount - 1]; + return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stub); } var uuid = 0; sinon.extend(stub, (function () { - var slice = Array.prototype.slice, proto; - - function throwsException(error, message) { - if (typeof error == "string") { - this.exception = new Error(message || ""); - this.exception.name = error; - } else if (!error) { - this.exception = new Error("Error"); - } else { - this.exception = error; - } - - return this; - } - - proto = { + var proto = { create: function create() { var functionStub = function () { - - callCallback(functionStub, arguments); - - if (functionStub.exception) { - throw functionStub.exception; - } else if (typeof functionStub.returnArgAt == 'number') { - return arguments[functionStub.returnArgAt]; - } else if (functionStub.returnThis) { - return this; - } - return functionStub.returnValue; + return getCurrentBehavior(functionStub).invoke(this, arguments); }; functionStub.id = "stub#" + uuid++; @@ -1814,26 +2263,22 @@ if (!this.sinon && commonJSModule) { functionStub = sinon.spy.create(functionStub); functionStub.func = orig; - functionStub.callArgAts = []; - functionStub.callbackArguments = []; - functionStub.callbackContexts = []; - functionStub.callArgProps = []; - sinon.extend(functionStub, stub); functionStub._create = sinon.stub.create; functionStub.displayName = "stub"; functionStub.toString = sinon.functionToString; + functionStub.defaultBehavior = null; + functionStub.behaviors = []; + return functionStub; }, resetBehavior: function () { var i; - this.callArgAts = []; - this.callbackArguments = []; - this.callbackContexts = []; - this.callArgProps = []; + this.defaultBehavior = null; + this.behaviors = []; delete this.returnValue; delete this.returnArgAt; @@ -1846,151 +2291,44 @@ if (!this.sinon && commonJSModule) { } }, - returns: function returns(value) { - this.returnValue = value; - - return this; - }, - - returnsArg: function returnsArg(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - - this.returnArgAt = pos; - - return this; - }, - - returnsThis: function returnsThis() { - this.returnThis = true; - - return this; - }, - - "throws": throwsException, - throwsException: throwsException, - - callsArg: function callsArg(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - - this.callArgAts.push(pos); - this.callbackArguments.push([]); - this.callbackContexts.push(undefined); - this.callArgProps.push(undefined); - - return this; - }, - - callsArgOn: function callsArgOn(pos, context) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); - } - - this.callArgAts.push(pos); - this.callbackArguments.push([]); - this.callbackContexts.push(context); - this.callArgProps.push(undefined); - - return this; - }, - - callsArgWith: function callsArgWith(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - - this.callArgAts.push(pos); - this.callbackArguments.push(slice.call(arguments, 1)); - this.callbackContexts.push(undefined); - this.callArgProps.push(undefined); - - return this; - }, - - callsArgOnWith: function callsArgWith(pos, context) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); + onCall: function(index) { + if (!this.behaviors[index]) { + this.behaviors[index] = sinon.behavior.create(this); } - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); - } - - this.callArgAts.push(pos); - this.callbackArguments.push(slice.call(arguments, 2)); - this.callbackContexts.push(context); - this.callArgProps.push(undefined); - return this; - }, - - yields: function () { - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 0)); - this.callbackContexts.push(undefined); - this.callArgProps.push(undefined); - - return this; + return this.behaviors[index]; }, - yieldsOn: function (context) { - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); - } - - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 1)); - this.callbackContexts.push(context); - this.callArgProps.push(undefined); - - return this; + onFirstCall: function() { + return this.onCall(0); }, - yieldsTo: function (prop) { - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 1)); - this.callbackContexts.push(undefined); - this.callArgProps.push(prop); - - return this; + onSecondCall: function() { + return this.onCall(1); }, - yieldsToOn: function (prop, context) { - if (typeof context != "object") { - throw new TypeError("argument context is not an object"); - } - - this.callArgAts.push(-1); - this.callbackArguments.push(slice.call(arguments, 2)); - this.callbackContexts.push(context); - this.callArgProps.push(prop); - - return this; + onThirdCall: function() { + return this.onCall(2); } }; - // create asynchronous versions of callsArg* and yields* methods - for (var method in proto) { - // need to avoid creating anotherasync versions of the newly added async methods - if (proto.hasOwnProperty(method) && - method.match(/^(callsArg|yields|thenYields$)/) && - !method.match(/Async/)) { - proto[method + 'Async'] = (function (syncFnName) { - return function () { - this.callbackAsync = true; - return this[syncFnName].apply(this, arguments); + for (var method in sinon.behavior) { + if (sinon.behavior.hasOwnProperty(method) && + !proto.hasOwnProperty(method) && + method != 'create' && + method != 'withArgs' && + method != 'invoke') { + proto[method] = (function(behaviorMethod) { + return function() { + this.defaultBehavior = this.defaultBehavior || sinon.behavior.create(this); + this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments); + return this; }; - })(method); + }(method)); } } return proto; - }())); if (commonJSModule) { @@ -2016,8 +2354,9 @@ if (!this.sinon && commonJSModule) { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; var push = [].push; + var match; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -2027,6 +2366,12 @@ if (!this.sinon && commonJSModule) { return; } + match = sinon.match; + + if (!match && commonJSModule) { + match = require("./match"); + } + function mock(object) { if (!object) { return sinon.expectation.create("Anonymous mock"); @@ -2207,6 +2552,14 @@ if (!this.sinon && commonJSModule) { return expectation.callCount == expectation.maxCalls; } + function verifyMatcher(possibleMatcher, arg){ + if (match && match.isMatcher(possibleMatcher)) { + return possibleMatcher.test(arg); + } else { + return true; + } + } + return { minCalls: 1, maxCalls: 1, @@ -2316,6 +2669,12 @@ if (!this.sinon && commonJSModule) { } for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + + if (!verifyMatcher(this.expectedArguments[i],args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", didn't match " + this.expectedArguments.toString()); + } + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + ", expected " + sinon.format(this.expectedArguments)); @@ -2348,6 +2707,10 @@ if (!this.sinon && commonJSModule) { } for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!verifyMatcher(this.expectedArguments[i],args[i])) { + return false; + } + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { return false; } @@ -2441,7 +2804,7 @@ if (!this.sinon && commonJSModule) { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; var push = [].push; var hasOwnProperty = Object.prototype.hasOwnProperty; @@ -2602,6 +2965,13 @@ if (typeof sinon == "undefined") { } (function (global) { + // node expects setTimeout/setInterval to return a fn object w/ .ref()/.unref() + // browsers, a number. + // see https://github.com/cjohansen/Sinon.JS/pull/436 + var timeoutResult = setTimeout(function() {}, 0); + var addTimerReturnsObject = typeof timeoutResult === 'object'; + clearTimeout(timeoutResult); + var id = 1; function addTimer(args, recurring) { @@ -2609,6 +2979,10 @@ if (typeof sinon == "undefined") { throw new Error("Function requires at least 1 parameter"); } + if (typeof args[0] === "undefined") { + throw new Error("Callback must be provided to timer calls"); + } + var toId = id++; var delay = args[1] || 0; @@ -2627,7 +3001,16 @@ if (typeof sinon == "undefined") { this.timeouts[toId].interval = delay; } - return toId; + if (addTimerReturnsObject) { + return { + id: toId, + ref: function() {}, + unref: function() {} + }; + } + else { + return toId; + } } function parseTime(str) { @@ -2710,6 +3093,16 @@ if (typeof sinon == "undefined") { this.clearTimeout(timerId); }, + setImmediate: function setImmediate(callback) { + var passThruArgs = Array.prototype.slice.call(arguments, 1); + + return addTimer.call(this, [callback, 0].concat(passThruArgs), false); + }, + + clearImmediate: function clearImmediate(timerId) { + this.clearTimeout(timerId); + }, + tick: function tick(ms) { ms = typeof ms == "number" ? ms : parseTime(ms); var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; @@ -2740,7 +3133,7 @@ if (typeof sinon == "undefined") { }, firstTimerInRange: function (from, to) { - var timer, smallest, originalTimer; + var timer, smallest = null, originalTimer; for (var id in this.timeouts) { if (this.timeouts.hasOwnProperty(id)) { @@ -2748,7 +3141,7 @@ if (typeof sinon == "undefined") { continue; } - if (!smallest || this.timeouts[id].callAt < smallest) { + if (smallest === null || this.timeouts[id].callAt < smallest) { originalTimer = this.timeouts[id]; smallest = this.timeouts[id].callAt; @@ -2854,21 +3247,39 @@ if (typeof sinon == "undefined") { target.parse = source.parse; target.UTC = source.UTC; target.prototype.toUTCString = source.prototype.toUTCString; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + return target; } var methods = ["Date", "setTimeout", "setInterval", "clearTimeout", "clearInterval"]; + if (typeof global.setImmediate !== "undefined") { + methods.push("setImmediate"); + } + + if (typeof global.clearImmediate !== "undefined") { + methods.push("clearImmediate"); + } + function restore() { var method; for (var i = 0, l = this.methods.length; i < l; i++) { method = this.methods[i]; + if (global[method].hadOwnProperty) { global[method] = this["_" + method]; } else { - delete global[method]; + try { + delete global[method]; + } catch (e) {} } } @@ -2919,12 +3330,14 @@ if (typeof sinon == "undefined") { sinon.timers = { setTimeout: setTimeout, clearTimeout: clearTimeout, + setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined), + clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate: undefined), setInterval: setInterval, clearInterval: clearInterval, Date: Date }; -if (typeof module == "object" && typeof require == "function") { +if (typeof module !== 'undefined' && module.exports) { module.exports = sinon; } @@ -2969,14 +3382,33 @@ if (typeof sinon == "undefined") { } }; + sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) { + this.initEvent(type, false, false, target); + this.loaded = progressEventRaw.loaded || null; + this.total = progressEventRaw.total || null; + }; + + sinon.ProgressEvent.prototype = new sinon.Event(); + + sinon.ProgressEvent.prototype.constructor = sinon.ProgressEvent; + + sinon.CustomEvent = function CustomEvent(type, customData, target) { + this.initEvent(type, false, false, target); + this.detail = customData.detail || null; + }; + + sinon.CustomEvent.prototype = new sinon.Event(); + + sinon.CustomEvent.prototype.constructor = sinon.CustomEvent; + sinon.EventTarget = { - addEventListener: function addEventListener(event, listener, useCapture) { + addEventListener: function addEventListener(event, listener) { this.eventListeners = this.eventListeners || {}; this.eventListeners[event] = this.eventListeners[event] || []; push.call(this.eventListeners[event], listener); }, - removeEventListener: function removeEventListener(event, listener, useCapture) { + removeEventListener: function removeEventListener(event, listener) { var listeners = this.eventListeners && this.eventListeners[event] || []; for (var i = 0, l = listeners.length; i < l; ++i) { @@ -3018,13 +3450,15 @@ if (typeof sinon == "undefined") { * Copyright (c) 2010-2013 Christian Johansen */ -if (typeof sinon == "undefined") { - this.sinon = {}; -} -sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; - // wrapper for global (function(global) { + if (typeof sinon === "undefined") { + global.sinon = {}; + } + + var supportsProgress = typeof ProgressEvent !== "undefined"; + var supportsCustomEvent = typeof CustomEvent !== "undefined"; + sinon.xhr = { XMLHttpRequest: global.XMLHttpRequest }; var xhr = sinon.xhr; xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; xhr.GlobalActiveXObject = global.ActiveXObject; @@ -3032,6 +3466,7 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; + xhr.supportsCORS = 'withCredentials' in (new sinon.xhr.GlobalXMLHttpRequest()); /*jsl:ignore*/ var unsafeHeaders = { @@ -3062,6 +3497,11 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; this.requestBody = null; this.status = 0; this.statusText = ""; + this.upload = new UploadProgress(); + if (sinon.xhr.supportsCORS) { + this.withCredentials = false; + } + var xhr = this; var events = ["loadstart", "load", "abort", "loadend"]; @@ -3071,7 +3511,7 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; var listener = xhr["on" + eventName]; if (listener && typeof listener == "function") { - listener(event); + listener.call(this, event); } }); } @@ -3085,6 +3525,41 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; } } + // An upload object is created for each + // FakeXMLHttpRequest and allows upload + // events to be simulated using uploadProgress + // and uploadError. + function UploadProgress() { + this.eventListeners = { + "progress": [], + "load": [], + "abort": [], + "error": [] + } + } + + UploadProgress.prototype.addEventListener = function(event, listener) { + this.eventListeners[event].push(listener); + }; + + UploadProgress.prototype.removeEventListener = function(event, listener) { + var listeners = this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] == listener) { + return listeners.splice(i, 1); + } + } + }; + + UploadProgress.prototype.dispatchEvent = function(event) { + var listeners = this.eventListeners[event.type] || []; + + for (var i = 0, listener; (listener = listeners[i]) != null; i++) { + listener(event); + } + }; + function verifyState(xhr) { if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { throw new Error("INVALID_STATE_ERR"); @@ -3106,7 +3581,7 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; function some(collection, callback) { for (var index = 0; index < collection.length; index++) { if(callback(collection[index]) === true) return true; - }; + } return false; } // largest arity in XHR is 5 - XHR#open @@ -3118,7 +3593,7 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; case 3: return obj[method](args[0],args[1],args[2]); case 4: return obj[method](args[0],args[1],args[2],args[3]); case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); - }; + } }; FakeXMLHttpRequest.filters = []; @@ -3157,7 +3632,7 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; if(xhr.readyState === FakeXMLHttpRequest.DONE) { copyAttrs(["responseXML"]); } - if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr); + if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr }); }; if(xhr.addEventListener) { for(var event in fakeXhr.eventListeners) { @@ -3175,6 +3650,12 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; }; FakeXMLHttpRequest.useFilters = false; + function verifyRequestOpened(xhr) { + if (xhr.readyState != FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR - " + xhr.readyState); + } + } + function verifyRequestSent(xhr) { if (xhr.readyState == FakeXMLHttpRequest.DONE) { throw new Error("Request done"); @@ -3238,6 +3719,10 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; case FakeXMLHttpRequest.DONE: this.dispatchEvent(new sinon.Event("load", false, false, this)); this.dispatchEvent(new sinon.Event("loadend", false, false, this)); + this.upload.dispatchEvent(new sinon.Event("load", false, false, this)); + if (supportsProgress) { + this.upload.dispatchEvent(new sinon.ProgressEvent('progress', {loaded: 100, total: 100})); + } break; } }, @@ -3258,6 +3743,7 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; // Helps testing setResponseHeaders: function setResponseHeaders(headers) { + verifyRequestOpened(this); this.responseHeaders = {}; for (var header in headers) { @@ -3313,6 +3799,9 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; this.readyState = sinon.FakeXMLHttpRequest.UNSENT; this.dispatchEvent(new sinon.Event("abort", false, false, this)); + + this.upload.dispatchEvent(new sinon.Event("abort", false, false, this)); + if (typeof this.onerror === "function") { this.onerror(); } @@ -3392,14 +3881,22 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; }, respond: function respond(status, headers, body) { - this.setResponseHeaders(headers || {}); this.status = typeof status == "number" ? status : 200; this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; + this.setResponseHeaders(headers || {}); this.setResponseBody(body || ""); - if (typeof this.onload === "function"){ - this.onload(); + }, + + uploadProgress: function uploadProgress(progressEventRaw) { + if (supportsProgress) { + this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); } + }, + uploadError: function uploadError(error) { + if (supportsCustomEvent) { + this.upload.dispatchEvent(new sinon.CustomEvent("error", {"detail": error})); + } } }); @@ -3506,9 +4003,10 @@ sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; }; sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; -})(this); -if (typeof module == "object" && typeof require == "function") { +})(typeof global === "object" ? global : this); + +if (typeof module !== 'undefined' && module.exports) { module.exports = sinon; } @@ -3570,7 +4068,6 @@ sinon.fakeServer = (function () { } function match(response, request) { - var requestMethod = this.getHTTPMethod(request); var requestUrl = request.url; if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { @@ -3580,7 +4077,7 @@ sinon.fakeServer = (function () { if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { if (typeof response.response == "function") { var ru = response.url; - var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1)); + var args = [request].concat(ru && typeof ru.exec == "function" ? ru.exec(requestUrl).slice(1) : []); return response.response.apply(response, args); } @@ -3618,16 +4115,16 @@ sinon.fakeServer = (function () { xhrObj.onSend = function () { server.handleRequest(this); - }; - if (this.autoRespond && !this.responding) { - setTimeout(function () { - server.responding = false; - server.respond(); - }, this.autoRespondAfter || 10); + if (server.autoRespond && !server.responding) { + setTimeout(function () { + server.responding = false; + server.respond(); + }, server.autoRespondAfter || 10); - this.responding = true; - } + server.responding = true; + } + }; }, getHTTPMethod: function getHTTPMethod(request) { @@ -3680,9 +4177,10 @@ sinon.fakeServer = (function () { respond: function respond() { if (arguments.length > 0) this.respondWith.apply(this, arguments); var queue = this.queue || []; + var requests = queue.splice(0); var request; - while(request = queue.shift()) { + while(request = requests.shift()) { this.processRequest(request); } }, @@ -3696,7 +4194,7 @@ sinon.fakeServer = (function () { var response = this.response || [404, {}, ""]; if (this.responses) { - for (var i = 0, l = this.responses.length; i < l; i++) { + for (var l = this.responses.length, i = l - 1; i >= 0; i--) { if (match.call(this, this.responses[i], request)) { response = this.responses[i].response; break; @@ -3720,7 +4218,7 @@ sinon.fakeServer = (function () { }; }()); -if (typeof module == "object" && typeof require == "function") { +if (typeof module !== 'undefined' && module.exports) { module.exports = sinon; } @@ -3825,7 +4323,7 @@ if (typeof module == "object" && typeof require == "function") { * Copyright (c) 2010-2013 Christian Johansen */ -if (typeof module == "object" && typeof require == "function") { +if (typeof module !== 'undefined' && module.exports) { var sinon = require("../sinon"); sinon.extend(sinon, require("./util/fake_timers")); } @@ -3838,8 +4336,9 @@ if (typeof module == "object" && typeof require == "function") { return; } - if (config.injectInto) { + if (config.injectInto && !(key in config.injectInto)) { config.injectInto[key] = value; + sandbox.injectedKeys.push(key); } else { push.call(sandbox.args, value); } @@ -3902,6 +4401,20 @@ if (typeof module == "object" && typeof require == "function") { return obj; }, + restore: function () { + sinon.collection.restore.apply(this, arguments); + this.restoreContext(); + }, + + restoreContext: function () { + if (this.injectedKeys) { + for (var i = 0, j = this.injectedKeys.length; i < j; i++) { + delete this.injectInto[this.injectedKeys[i]]; + } + this.injectedKeys = []; + } + }, + create: function (config) { if (!config) { return sinon.create(sinon.sandbox); @@ -3909,6 +4422,8 @@ if (typeof module == "object" && typeof require == "function") { var sandbox = prepareSandboxFromConfig(config); sandbox.args = sandbox.args || []; + sandbox.injectedKeys = []; + sandbox.injectInto = config.injectInto; var prop, value, exposed = sandbox.inject({}); if (config.properties) { @@ -3927,7 +4442,7 @@ if (typeof module == "object" && typeof require == "function") { sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; - if (typeof module == "object" && typeof require == "function") { + if (typeof module !== 'undefined' && module.exports) { module.exports = sinon.sandbox; } }()); @@ -3950,7 +4465,7 @@ if (typeof module == "object" && typeof require == "function") { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -4023,7 +4538,7 @@ if (typeof module == "object" && typeof require == "function") { */ (function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== 'undefined' && module.exports; if (!sinon && commonJSModule) { sinon = require("../sinon"); @@ -4120,7 +4635,7 @@ if (typeof module == "object" && typeof require == "function") { */ (function (sinon, global) { - var commonJSModule = typeof module == "object" && typeof require == "function"; + var commonJSModule = typeof module !== "undefined" && module.exports; var slice = Array.prototype.slice; var assert; @@ -4188,7 +4703,7 @@ if (typeof module == "object" && typeof require == "function") { function exposedName(prefix, prop) { return !prefix || /^fail/.test(prop) ? prop : prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); - }; + } assert = { failException: "AssertError", @@ -4256,6 +4771,20 @@ if (typeof module == "object" && typeof require == "function") { } return target; + }, + + match: function match(actual, expectation) { + var matcher = sinon.match(expectation); + if (matcher.test(actual)) { + assert.pass("match"); + } else { + var formatted = [ + "expected value to match", + " expected = " + sinon.format(expectation), + " actual = " + sinon.format(actual) + ] + failAssertion(this, formatted.join("\n")); + } } };