diff options
Diffstat (limited to 'core/js/tests')
21 files changed, 1543 insertions, 9778 deletions
diff --git a/core/js/tests/html-domparser.js b/core/js/tests/html-domparser.js new file mode 100644 index 00000000000..945d4b1f441 --- /dev/null +++ b/core/js/tests/html-domparser.js @@ -0,0 +1,44 @@ +/** + * DOMParser HTML extension + * 2012-09-04 + * + * By Eli Grey, http://eligrey.com + * Public domain. + * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + * + * SPDX-FileCopyrightText: 2012 Eli Grey, http://eligrey.com + * SPDX-License-Identifier: CC0-1.0 + */ + +/*! @source https://gist.github.com/1129031 */ +/*global document, DOMParser*/ + +(function(DOMParser) { + "use strict"; + + var DOMParser_proto = DOMParser.prototype; + var real_parseFromString = DOMParser_proto.parseFromString; + + // Firefox/Opera/IE throw errors on unsupported types + try { + // WebKit returns null on unsupported types + if ((new DOMParser).parseFromString("", "text/html")) { + // text/html parsing is natively supported + return; + } + } catch (ex) {} + + DOMParser_proto.parseFromString = function(markup, type) { + if (/^\s*text\/html\s*(?:;|$)/i.test(type)) { + var doc = document.implementation.createHTMLDocument(""); + if (markup.toLowerCase().indexOf('<!doctype') > -1) { + doc.documentElement.innerHTML = markup; + } else { + doc.body.innerHTML = markup; + } + return doc; + } else { + return real_parseFromString.apply(this, arguments); + } + }; +}(DOMParser)); diff --git a/core/js/tests/lib/sinon-1.15.4.js b/core/js/tests/lib/sinon-1.15.4.js deleted file mode 100644 index 20bc9e208d7..00000000000 --- a/core/js/tests/lib/sinon-1.15.4.js +++ /dev/null @@ -1,5949 +0,0 @@ -/** - * Sinon.JS 1.15.4, 2015/06/27 - * - * @author Christian Johansen (christian@cjohansen.no) - * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS - * - * (The BSD License) - * - * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * * Neither the name of Christian Johansen nor the names of his contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -(function (root, factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - define('sinon', [], function () { - return (root.sinon = factory()); - }); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.sinon = factory(); - } -}(this, function () { - 'use strict'; - var samsam, formatio, lolex; - (function () { - function define(mod, deps, fn) { - if (mod == "samsam") { - samsam = deps(); - } else if (typeof deps === "function" && mod.length === 0) { - lolex = deps(); - } else if (typeof fn === "function") { - formatio = fn(samsam); - } - } - define.amd = {}; -((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; - } - - 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]; - } - - /** - * @name samsam.isArguments - * @param Object object - * - * Returns ``true`` if ``object`` is an ``arguments`` object, - * ``false`` otherwise. - */ - function isArguments(object) { - if (getClass(object) === 'Arguments') { return true; } - 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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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(); - } - - /** - * @name samsam.isNegZero - * @param Object value - * - * Returns ``true`` if ``value`` is ``-0``. - */ - function isNegZero(value) { - return value === 0 && 1 / value === -Infinity; - } - - /** - * @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); - } - } - - - /** - * @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)) { - - return true; - } - - return false; - } - - /** - * 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; - } - } - - 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); - } - - // 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; - } - } - - if (obj1 instanceof RegExp && obj2 instanceof RegExp) { - if (obj1.toString() !== obj2.toString()) { return false; } - } - - var class1 = getClass(obj1); - var class2 = getClass(obj2); - var keys1 = keys(obj1); - var keys2 = keys(obj2); - - if (isArguments(obj1) || isArguments(obj2)) { - if (obj1.length !== obj2.length) { return false; } - } else { - if (type1 !== type2 || class1 !== class2 || - keys1.length !== keys2.length) { - return false; - } - } - - 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; - } - - // 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); - } - - // 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 true; - - }(obj1, obj2, '$1', '$2')); - } - - 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; - } - - /** - * @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 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; - } - - if (typeof(matcher) === "undefined") { - return typeof(object) === "undefined"; - } - - if (matcher === null) { - return object === null; - } - - if (getClass(object) === "Array" && getClass(matcher) === "Array") { - return arrayContains(object, matcher); - } - - if (matcher && typeof matcher === "object") { - if (matcher === object) { - return true; - } - var prop; - for (prop in matcher) { - var value = object[prop]; - if (typeof value === "undefined" && - typeof object.getAttribute === "function") { - value = object.getAttribute(prop); - } - if (matcher[prop] === null || typeof matcher[prop] === 'undefined') { - if (value !== matcher[prop]) { - return false; - } - } else if (typeof value === "undefined" || !match(value, 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, - limitChildrenCount: 0 - }; - - var hasOwn = Object.prototype.hasOwnProperty; - - var specialObjects = []; - if (typeof global !== "undefined") { - specialObjects.push({ object: global, value: "[object global]" }); - } - if (typeof document !== "undefined") { - specialObjects.push({ - object: document, - value: "[object HTMLDocument]" - }); - } - if (typeof window !== "undefined") { - specialObjects.push({ object: 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 constructorName(f, object) { - var name = functionName(object && object.constructor); - var excludes = f.excludeConstructors || - formatio.excludeConstructors || []; - - 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 name; - } - - function isCircular(object, objects) { - 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(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)) { - return ascii.func(object); - } - - processed = processed || []; - - if (isCircular(object, processed)) { return "[Circular]"; } - - if (Object.prototype.toString.call(object) === "[object Array]") { - return ascii.array.call(f, object, processed); - } - - 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) { - return object.toString(); - } - - 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(f, object, processed, indent); - } - - ascii.func = function (func) { - return "function " + functionName(func) + "() {}"; - }; - - ascii.array = function (array, processed) { - processed = processed || []; - processed.push(array); - var pieces = []; - var i, l; - l = (this.limitChildrenCount > 0) ? - Math.min(this.limitChildrenCount, array.length) : array.length; - - for (i = 0; i < l; ++i) { - pieces.push(ascii(this, array[i], processed)); - } - - if(l < array.length) - pieces.push("[... " + (array.length - l) + " more elements]"); - - return "[" + pieces.join(", ") + "]"; - }; - - ascii.object = function (object, processed, indent) { - processed = processed || []; - processed.push(object); - indent = indent || 0; - var pieces = [], properties = samsam.keys(object).sort(); - var length = 3; - var prop, str, obj, i, k, l; - l = (this.limitChildrenCount > 0) ? - Math.min(this.limitChildrenCount, properties.length) : properties.length; - - for (i = 0; i < l; ++i) { - prop = properties[i]; - obj = object[prop]; - - if (isCircular(obj, processed)) { - str = "[Circular]"; - } else { - str = ascii(this, obj, processed, indent + 2); - } - - str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; - length += str.length; - pieces.push(str); - } - - var cons = constructorName(this, object); - var prefix = cons ? "[" + cons + "] " : ""; - var is = ""; - for (i = 0, k = indent; i < k; ++i) { is += " "; } - - if(l < properties.length) - pieces.push("[... " + (properties.length - l) + " more elements]"); - - 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, attr, pairs = [], attrName, i, l, val; - - 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 + "\""); } - } - } - - var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); - var content = element.innerHTML; - - if (content.length > 20) { - content = content.substr(0, 20) + "[...]"; - } - - var res = formatted + pairs.join(" ") + ">" + content + - "</" + tagName + ">"; - - return res.replace(/ contentEditable="inherit"/, ""); - }; - - function Formatio(options) { - for (var opt in options) { - this[opt] = options[opt]; - } - } - - Formatio.prototype = { - functionName: functionName, - - configure: function (options) { - return new Formatio(options); - }, - - constructorName: function (object) { - return constructorName(this, object); - }, - - ascii: function (object, processed, indent) { - return ascii(this, object, processed, indent); - } - }; - - return Formatio.prototype; -}); -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.lolex=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ -(function (global){ -/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ -/*global global*/ -/** - * @author Christian Johansen (christian@cjohansen.no) and contributors - * @license BSD - * - * Copyright (c) 2010-2014 Christian Johansen - */ - -// 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 NativeDate = Date; -var id = 1; - -/** - * Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into - * number of milliseconds. This is used to support human-readable strings passed - * to clock.tick() - */ -function parseTime(str) { - if (!str) { - return 0; - } - - var strings = str.split(":"); - var l = strings.length, i = l; - var ms = 0, parsed; - - if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { - throw new Error("tick only understands numbers and 'h:m:s'"); - } - - while (i--) { - parsed = parseInt(strings[i], 10); - - if (parsed >= 60) { - throw new Error("Invalid time " + str); - } - - ms += parsed * Math.pow(60, (l - i - 1)); - } - - return ms * 1000; -} - -/** - * Used to grok the `now` parameter to createClock. - */ -function getEpoch(epoch) { - if (!epoch) { return 0; } - if (typeof epoch.getTime === "function") { return epoch.getTime(); } - if (typeof epoch === "number") { return epoch; } - throw new TypeError("now should be milliseconds since UNIX epoch"); -} - -function inRange(from, to, timer) { - return timer && timer.callAt >= from && timer.callAt <= to; -} - -function mirrorDateProperties(target, source) { - if (source.now) { - target.now = function now() { - return target.clock.now; - }; - } else { - delete target.now; - } - - if (source.toSource) { - target.toSource = function toSource() { - return source.toSource(); - }; - } else { - delete target.toSource; - } - - target.toString = function toString() { - return source.toString(); - }; - - target.prototype = source.prototype; - 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; -} - -function createDate() { - function ClockDate(year, month, date, hour, minute, second, ms) { - // Defensive and verbose to avoid potential harm in passing - // explicit undefined when user does not pass argument - switch (arguments.length) { - case 0: - return new NativeDate(ClockDate.clock.now); - case 1: - return new NativeDate(year); - case 2: - return new NativeDate(year, month); - case 3: - return new NativeDate(year, month, date); - case 4: - return new NativeDate(year, month, date, hour); - case 5: - return new NativeDate(year, month, date, hour, minute); - case 6: - return new NativeDate(year, month, date, hour, minute, second); - default: - return new NativeDate(year, month, date, hour, minute, second, ms); - } - } - - return mirrorDateProperties(ClockDate, NativeDate); -} - -function addTimer(clock, timer) { - if (typeof timer.func === "undefined") { - throw new Error("Callback must be provided to timer calls"); - } - - if (!clock.timers) { - clock.timers = {}; - } - - timer.id = id++; - timer.createdAt = clock.now; - timer.callAt = clock.now + (timer.delay || 0); - - clock.timers[timer.id] = timer; - - if (addTimerReturnsObject) { - return { - id: timer.id, - ref: function() {}, - unref: function() {} - }; - } - else { - return timer.id; - } -} - -function firstTimerInRange(clock, from, to) { - var timers = clock.timers, timer = null; - - for (var id in timers) { - if (!inRange(from, to, timers[id])) { - continue; - } - - if (!timer || ~compareTimers(timer, timers[id])) { - timer = timers[id]; - } - } - - return timer; -} - -function compareTimers(a, b) { - // Sort first by absolute timing - if (a.callAt < b.callAt) { - return -1; - } - if (a.callAt > b.callAt) { - return 1; - } - - // Sort next by immediate, immediate timers take precedence - if (a.immediate && !b.immediate) { - return -1; - } - if (!a.immediate && b.immediate) { - return 1; - } - - // Sort next by creation time, earlier-created timers take precedence - if (a.createdAt < b.createdAt) { - return -1; - } - if (a.createdAt > b.createdAt) { - return 1; - } - - // Sort next by id, lower-id timers take precedence - if (a.id < b.id) { - return -1; - } - if (a.id > b.id) { - return 1; - } - - // As timer ids are unique, no fallback `0` is necessary -} - -function callTimer(clock, timer) { - if (typeof timer.interval == "number") { - clock.timers[timer.id].callAt += timer.interval; - } else { - delete clock.timers[timer.id]; - } - - try { - if (typeof timer.func == "function") { - timer.func.apply(null, timer.args); - } else { - eval(timer.func); - } - } catch (e) { - var exception = e; - } - - if (!clock.timers[timer.id]) { - if (exception) { - throw exception; - } - return; - } - - if (exception) { - throw exception; - } -} - -function uninstall(clock, target) { - var method; - - for (var i = 0, l = clock.methods.length; i < l; i++) { - method = clock.methods[i]; - - if (target[method].hadOwnProperty) { - target[method] = clock["_" + method]; - } else { - try { - delete target[method]; - } catch (e) {} - } - } - - // Prevent multiple executions which will completely remove these props - clock.methods = []; -} - -function hijackMethod(target, method, clock) { - clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method); - clock["_" + method] = target[method]; - - if (method == "Date") { - var date = mirrorDateProperties(clock[method], target[method]); - target[method] = date; - } else { - target[method] = function () { - return clock[method].apply(clock, arguments); - }; - - for (var prop in clock[method]) { - if (clock[method].hasOwnProperty(prop)) { - target[method][prop] = clock[method][prop]; - } - } - } - - target[method].clock = clock; -} - -var timers = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined), - clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate: undefined), - setInterval: setInterval, - clearInterval: clearInterval, - Date: Date -}; - -var keys = Object.keys || function (obj) { - var ks = []; - for (var key in obj) { - ks.push(key); - } - return ks; -}; - -exports.timers = timers; - -var createClock = exports.createClock = function (now) { - var clock = { - now: getEpoch(now), - timeouts: {}, - Date: createDate() - }; - - clock.Date.clock = clock; - - clock.setTimeout = function setTimeout(func, timeout) { - return addTimer(clock, { - func: func, - args: Array.prototype.slice.call(arguments, 2), - delay: timeout - }); - }; - - clock.clearTimeout = function clearTimeout(timerId) { - if (!timerId) { - // null appears to be allowed in most browsers, and appears to be - // relied upon by some libraries, like Bootstrap carousel - return; - } - if (!clock.timers) { - clock.timers = []; - } - // in Node, timerId is an object with .ref()/.unref(), and - // its .id field is the actual timer id. - if (typeof timerId === "object") { - timerId = timerId.id - } - if (timerId in clock.timers) { - delete clock.timers[timerId]; - } - }; - - clock.setInterval = function setInterval(func, timeout) { - return addTimer(clock, { - func: func, - args: Array.prototype.slice.call(arguments, 2), - delay: timeout, - interval: timeout - }); - }; - - clock.clearInterval = function clearInterval(timerId) { - clock.clearTimeout(timerId); - }; - - clock.setImmediate = function setImmediate(func) { - return addTimer(clock, { - func: func, - args: Array.prototype.slice.call(arguments, 1), - immediate: true - }); - }; - - clock.clearImmediate = function clearImmediate(timerId) { - clock.clearTimeout(timerId); - }; - - clock.tick = function tick(ms) { - ms = typeof ms == "number" ? ms : parseTime(ms); - var tickFrom = clock.now, tickTo = clock.now + ms, previous = clock.now; - var timer = firstTimerInRange(clock, tickFrom, tickTo); - - var firstException; - while (timer && tickFrom <= tickTo) { - if (clock.timers[timer.id]) { - tickFrom = clock.now = timer.callAt; - try { - callTimer(clock, timer); - } catch (e) { - firstException = firstException || e; - } - } - - timer = firstTimerInRange(clock, previous, tickTo); - previous = tickFrom; - } - - clock.now = tickTo; - - if (firstException) { - throw firstException; - } - - return clock.now; - }; - - clock.reset = function reset() { - clock.timers = {}; - }; - - return clock; -}; - -exports.install = function install(target, now, toFake) { - if (typeof target === "number") { - toFake = now; - now = target; - target = null; - } - - if (!target) { - target = global; - } - - var clock = createClock(now); - - clock.uninstall = function () { - uninstall(clock, target); - }; - - clock.methods = toFake || []; - - if (clock.methods.length === 0) { - clock.methods = keys(timers); - } - - for (var i = 0, l = clock.methods.length; i < l; i++) { - hijackMethod(target, clock.methods[i], clock); - } - - return clock; -}; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}]},{},[1])(1) -}); - })(); - var define; -/** - * Sinon core utilities. For internal use only. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -var sinon = (function () { -"use strict"; - - var sinon; - var isNode = typeof module !== "undefined" && module.exports && typeof require === "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - sinon = module.exports = require("./sinon/util/core"); - require("./sinon/extend"); - require("./sinon/typeOf"); - require("./sinon/times_in_words"); - require("./sinon/spy"); - require("./sinon/call"); - require("./sinon/behavior"); - require("./sinon/stub"); - require("./sinon/mock"); - require("./sinon/collection"); - require("./sinon/assert"); - require("./sinon/sandbox"); - require("./sinon/test"); - require("./sinon/test_case"); - require("./sinon/match"); - require("./sinon/format"); - require("./sinon/log_error"); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - sinon = module.exports; - } else { - sinon = {}; - } - - return sinon; -}()); - -/** - * @depend ../../sinon.js - */ -/** - * Sinon core utilities. For internal use only. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - var div = typeof document != "undefined" && document.createElement("div"); - var hasOwn = Object.prototype.hasOwnProperty; - - function isDOMNode(obj) { - var success = false; - - try { - obj.appendChild(div); - success = div.parentNode == obj; - } catch (e) { - return false; - } finally { - try { - obj.removeChild(div); - } catch (e) { - // Remove failed, not much we can do about that - } - } - - return success; - } - - function isElement(obj) { - return div && obj && obj.nodeType === 1 && isDOMNode(obj); - } - - function isFunction(obj) { - 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)) { - target[prop] = source[prop]; - } - } - } - - function isRestorable(obj) { - return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon; - } - - // Cheap way to detect if we have ES5 support. - var hasES5Support = "keys" in Object; - - function makeApi(sinon) { - sinon.wrapMethod = function wrapMethod(object, property, method) { - if (!object) { - throw new TypeError("Should wrap property of object"); - } - - if (typeof method != "function" && typeof method != "object") { - throw new TypeError("Method wrapper should be a function or a property descriptor"); - } - - function checkWrappedMethod(wrappedMethod) { - if (!isFunction(wrappedMethod)) { - error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + - property + " as function"); - } else if (wrappedMethod.restore && wrappedMethod.restore.sinon) { - error = new TypeError("Attempted to wrap " + property + " which is already wrapped"); - } else if (wrappedMethod.calledBefore) { - var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; - error = new TypeError("Attempted to wrap " + property + " which is already " + verb); - } - - if (error) { - if (wrappedMethod && wrappedMethod.stackTrace) { - error.stack += "\n--------------\n" + wrappedMethod.stackTrace; - } - throw error; - } - } - - var error, wrappedMethod; - - // 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); - - if (hasES5Support) { - var methodDesc = (typeof method == "function") ? {value: method} : method, - wrappedMethodDesc = sinon.getPropertyDescriptor(object, property), - i; - - if (!wrappedMethodDesc) { - error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + - property + " as function"); - } else if (wrappedMethodDesc.restore && wrappedMethodDesc.restore.sinon) { - error = new TypeError("Attempted to wrap " + property + " which is already wrapped"); - } - if (error) { - if (wrappedMethodDesc && wrappedMethodDesc.stackTrace) { - error.stack += "\n--------------\n" + wrappedMethodDesc.stackTrace; - } - throw error; - } - - var types = sinon.objectKeys(methodDesc); - for (i = 0; i < types.length; i++) { - wrappedMethod = wrappedMethodDesc[types[i]]; - checkWrappedMethod(wrappedMethod); - } - - mirrorProperties(methodDesc, wrappedMethodDesc); - for (i = 0; i < types.length; i++) { - mirrorProperties(methodDesc[types[i]], wrappedMethodDesc[types[i]]); - } - Object.defineProperty(object, property, methodDesc); - } else { - wrappedMethod = object[property]; - checkWrappedMethod(wrappedMethod); - object[property] = method; - method.displayName = property; - } - - 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.stackTrace = (new Error("Stack Trace for original")).stack; - - method.restore = function () { - // For prototype properties try to reset by delete first. - // If this fails (ex: localStorage on mobile safari) then force a reset - // via direct assignment. - if (!owned) { - // In some cases `delete` may throw an error - try { - delete object[property]; - } catch (e) {} - // For native code functions `delete` fails without throwing an error - // on Chrome < 43, PhantomJS, etc. - } else if (hasES5Support) { - Object.defineProperty(object, property, wrappedMethodDesc); - } - - // Use strict equality comparison to check failures then force a reset - // via direct assignment. - if (object[property] === method) { - object[property] = wrappedMethod; - } - }; - - method.restore.sinon = true; - - if (!hasES5Support) { - mirrorProperties(method, wrappedMethod); - } - - return method; - }; - - sinon.create = function create(proto) { - var F = function () {}; - F.prototype = proto; - return new F(); - }; - - sinon.deepEqual = function deepEqual(a, b) { - if (sinon.match && sinon.match.isMatcher(a)) { - return a.test(b); - } - - if (typeof a != "object" || typeof b != "object") { - if (isReallyNaN(a) && isReallyNaN(b)) { - return true; - } else { - return a === b; - } - } - - if (isElement(a) || isElement(b)) { - return a === b; - } - - if (a === b) { - return true; - } - - if ((a === null && b !== null) || (a !== null && b === null)) { - 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 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; - - if (!(prop in b)) { - return false; - } - - if (!deepEqual(a[prop], b[prop])) { - return false; - } - } - - for (prop in b) { - bLength += 1; - } - - return aLength == bLength; - }; - - sinon.functionName = function functionName(func) { - var name = func.displayName || func.name; - - // Use function decomposition as a last resort to get function - // name. Does not rely on function decomposition to work - if it - // doesn't debugging will be slightly less informative - // (i.e. toString will say 'spy' rather than 'myFunc'). - if (!name) { - var matches = func.toString().match(/function ([^\s\(]+)/); - name = matches && matches[1]; - } - - return name; - }; - - sinon.functionToString = function toString() { - if (this.getCall && this.callCount) { - var thisValue, prop, i = this.callCount; - - while (i--) { - thisValue = this.getCall(i).thisValue; - - for (prop in thisValue) { - if (thisValue[prop] === this) { - return prop; - } - } - } - } - - return this.displayName || "sinon fake"; - }; - - sinon.objectKeys = function objectKeys(obj) { - if (obj !== Object(obj)) { - throw new TypeError("sinon.objectKeys called on a non-object"); - } - - var keys = []; - var key; - for (key in obj) { - if (hasOwn.call(obj, key)) { - keys.push(key); - } - } - - return keys; - }; - - sinon.getPropertyDescriptor = function getPropertyDescriptor(object, property) { - var proto = object, descriptor; - while (proto && !(descriptor = Object.getOwnPropertyDescriptor(proto, property))) { - proto = Object.getPrototypeOf(proto); - } - return descriptor; - } - - sinon.getConfig = function (custom) { - var config = {}; - custom = custom || {}; - var defaults = sinon.defaultConfig; - - for (var prop in defaults) { - if (defaults.hasOwnProperty(prop)) { - config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; - } - } - - return config; - }; - - sinon.defaultConfig = { - injectIntoThis: true, - injectInto: null, - properties: ["spy", "stub", "mock", "clock", "server", "requests"], - useFakeTimers: true, - useFakeServer: true - }; - - sinon.timesInWords = function timesInWords(count) { - return count == 1 && "once" || - count == 2 && "twice" || - count == 3 && "thrice" || - (count || 0) + " times"; - }; - - sinon.calledInOrder = function (spies) { - for (var i = 1, l = spies.length; i < l; i++) { - if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) { - return false; - } - } - - return true; - }; - - sinon.orderByFirstCall = function (spies) { - return spies.sort(function (a, b) { - // uuid, won't ever be equal - var aCall = a.getCall(0); - var bCall = b.getCall(0); - var aId = aCall && aCall.callId || -1; - var bId = bCall && bCall.callId || -1; - - return aId < bId ? -1 : 1; - }); - }; - - sinon.createStubInstance = function (constructor) { - if (typeof constructor !== "function") { - throw new TypeError("The constructor should be a function."); - } - return sinon.stub(sinon.create(constructor.prototype)); - }; - - sinon.restore = function (object) { - if (object !== null && typeof object === "object") { - for (var prop in object) { - if (isRestorable(object[prop])) { - object[prop].restore(); - } - } - } else if (isRestorable(object)) { - object.restore(); - } - }; - - return sinon; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports) { - makeApi(exports); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - */ - -(function (sinon) { - function makeApi(sinon) { - - // Adapted from https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug - var hasDontEnumBug = (function () { - var obj = { - constructor: function () { - return "0"; - }, - toString: function () { - return "1"; - }, - valueOf: function () { - return "2"; - }, - toLocaleString: function () { - return "3"; - }, - prototype: function () { - return "4"; - }, - isPrototypeOf: function () { - return "5"; - }, - propertyIsEnumerable: function () { - return "6"; - }, - hasOwnProperty: function () { - return "7"; - }, - length: function () { - return "8"; - }, - unique: function () { - return "9" - } - }; - - var result = []; - for (var prop in obj) { - result.push(obj[prop]()); - } - return result.join("") !== "0123456789"; - })(); - - /* Public: Extend target in place with all (own) properties from sources in-order. Thus, last source will - * override properties in previous sources. - * - * target - The Object to extend - * sources - Objects to copy properties from. - * - * Returns the extended target - */ - function extend(target /*, sources */) { - var sources = Array.prototype.slice.call(arguments, 1), - source, i, prop; - - for (i = 0; i < sources.length; i++) { - source = sources[i]; - - for (prop in source) { - if (source.hasOwnProperty(prop)) { - target[prop] = source[prop]; - } - } - - // Make sure we copy (own) toString method even when in JScript with DontEnum bug - // See https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug - if (hasDontEnumBug && source.hasOwnProperty("toString") && source.toString !== target.toString) { - target.toString = source.toString; - } - } - - return target; - }; - - sinon.extend = extend; - return sinon.extend; - } - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - module.exports = makeApi(sinon); - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - */ - -(function (sinon) { - function makeApi(sinon) { - - function timesInWords(count) { - switch (count) { - case 1: - return "once"; - case 2: - return "twice"; - case 3: - return "thrice"; - default: - return (count || 0) + " times"; - } - } - - sinon.timesInWords = timesInWords; - return sinon.timesInWords; - } - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - module.exports = makeApi(sinon); - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - */ -/** - * Format functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2014 Christian Johansen - */ - -(function (sinon, formatio) { - function makeApi(sinon) { - function typeOf(value) { - if (value === null) { - return "null"; - } else if (value === undefined) { - return "undefined"; - } - var string = Object.prototype.toString.call(value); - return string.substring(8, string.length - 1).toLowerCase(); - }; - - sinon.typeOf = typeOf; - return sinon.typeOf; - } - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - module.exports = makeApi(sinon); - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}( - (typeof sinon == "object" && sinon || null), - (typeof formatio == "object" && formatio) -)); - -/** - * @depend util/core.js - * @depend typeOf.js - */ -/*jslint eqeqeq: false, onevar: false, plusplus: false*/ -/*global module, require, sinon*/ -/** - * Match functions - * - * @author Maximilian Antoni (mail@maxantoni.de) - * @license BSD - * - * Copyright (c) 2012 Maximilian Antoni - */ - -(function (sinon) { - function makeApi(sinon) { - function assertType(value, type, name) { - var actual = sinon.typeOf(value); - if (actual !== type) { - throw new TypeError("Expected type of " + name + " to be " + - type + ", but was " + actual); - } - } - - var matcher = { - toString: function () { - return this.message; - } - }; - - function isMatcher(object) { - return matcher.isPrototypeOf(object); - } - - function matchObject(expectation, actual) { - if (actual === null || actual === undefined) { - return false; - } - for (var key in expectation) { - if (expectation.hasOwnProperty(key)) { - var exp = expectation[key]; - var act = actual[key]; - if (match.isMatcher(exp)) { - if (!exp.test(act)) { - return false; - } - } else if (sinon.typeOf(exp) === "object") { - if (!matchObject(exp, act)) { - return false; - } - } else if (!sinon.deepEqual(exp, act)) { - return false; - } - } - } - return true; - } - - matcher.or = function (m2) { - if (!arguments.length) { - throw new TypeError("Matcher expected"); - } else if (!isMatcher(m2)) { - m2 = match(m2); - } - var m1 = this; - var or = sinon.create(matcher); - or.test = function (actual) { - return m1.test(actual) || m2.test(actual); - }; - or.message = m1.message + ".or(" + m2.message + ")"; - return or; - }; - - matcher.and = function (m2) { - if (!arguments.length) { - throw new TypeError("Matcher expected"); - } else if (!isMatcher(m2)) { - m2 = match(m2); - } - var m1 = this; - var and = sinon.create(matcher); - and.test = function (actual) { - return m1.test(actual) && m2.test(actual); - }; - and.message = m1.message + ".and(" + m2.message + ")"; - return and; - }; - - var match = function (expectation, message) { - var m = sinon.create(matcher); - var type = sinon.typeOf(expectation); - switch (type) { - case "object": - if (typeof expectation.test === "function") { - m.test = function (actual) { - return expectation.test(actual) === true; - }; - m.message = "match(" + sinon.functionName(expectation.test) + ")"; - return m; - } - var str = []; - for (var key in expectation) { - if (expectation.hasOwnProperty(key)) { - str.push(key + ": " + expectation[key]); - } - } - m.test = function (actual) { - return matchObject(expectation, actual); - }; - m.message = "match(" + str.join(", ") + ")"; - break; - case "number": - m.test = function (actual) { - return expectation == actual; - }; - break; - case "string": - m.test = function (actual) { - if (typeof actual !== "string") { - return false; - } - return actual.indexOf(expectation) !== -1; - }; - m.message = "match(\"" + expectation + "\")"; - break; - case "regexp": - m.test = function (actual) { - if (typeof actual !== "string") { - return false; - } - return expectation.test(actual); - }; - break; - case "function": - m.test = expectation; - if (message) { - m.message = message; - } else { - m.message = "match(" + sinon.functionName(expectation) + ")"; - } - break; - default: - m.test = function (actual) { - return sinon.deepEqual(expectation, actual); - }; - } - if (!m.message) { - m.message = "match(" + expectation + ")"; - } - return m; - }; - - match.isMatcher = isMatcher; - - match.any = match(function () { - return true; - }, "any"); - - match.defined = match(function (actual) { - return actual !== null && actual !== undefined; - }, "defined"); - - match.truthy = match(function (actual) { - return !!actual; - }, "truthy"); - - match.falsy = match(function (actual) { - return !actual; - }, "falsy"); - - match.same = function (expectation) { - return match(function (actual) { - return expectation === actual; - }, "same(" + expectation + ")"); - }; - - match.typeOf = function (type) { - assertType(type, "string", "type"); - return match(function (actual) { - return sinon.typeOf(actual) === type; - }, "typeOf(\"" + type + "\")"); - }; - - match.instanceOf = function (type) { - assertType(type, "function", "type"); - return match(function (actual) { - return actual instanceof type; - }, "instanceOf(" + sinon.functionName(type) + ")"); - }; - - function createPropertyMatcher(propertyTest, messagePrefix) { - return function (property, value) { - assertType(property, "string", "property"); - var onlyProperty = arguments.length === 1; - var message = messagePrefix + "(\"" + property + "\""; - if (!onlyProperty) { - message += ", " + value; - } - message += ")"; - return match(function (actual) { - if (actual === undefined || actual === null || - !propertyTest(actual, property)) { - return false; - } - return onlyProperty || sinon.deepEqual(value, actual[property]); - }, message); - }; - } - - match.has = createPropertyMatcher(function (actual, property) { - if (typeof actual === "object") { - return property in actual; - } - return actual[property] !== undefined; - }, "has"); - - match.hasOwn = createPropertyMatcher(function (actual, property) { - return actual.hasOwnProperty(property); - }, "hasOwn"); - - match.bool = match.typeOf("boolean"); - match.number = match.typeOf("number"); - match.string = match.typeOf("string"); - match.object = match.typeOf("object"); - match.func = match.typeOf("function"); - match.array = match.typeOf("array"); - match.regexp = match.typeOf("regexp"); - match.date = match.typeOf("date"); - - sinon.match = match; - return match; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./typeOf"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - */ -/** - * Format functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2014 Christian Johansen - */ - -(function (sinon, formatio) { - function makeApi(sinon) { - function valueFormatter(value) { - return "" + value; - } - - function getFormatioFormatter() { - var formatter = formatio.configure({ - quoteStrings: false, - limitChildrenCount: 250 - }); - - function format() { - return formatter.ascii.apply(formatter, arguments); - }; - - return format; - } - - function getNodeFormatter(value) { - function format(value) { - return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value; - }; - - try { - var util = require("util"); - } catch (e) { - /* Node, but no util module - would be very old, but better safe than sorry */ - } - - return util ? format : valueFormatter; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function", - formatter; - - if (isNode) { - try { - formatio = require("formatio"); - } catch (e) {} - } - - if (formatio) { - formatter = getFormatioFormatter() - } else if (isNode) { - formatter = getNodeFormatter(); - } else { - formatter = valueFormatter; - } - - sinon.format = formatter; - return sinon.format; - } - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - module.exports = makeApi(sinon); - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}( - (typeof sinon == "object" && sinon || null), - (typeof formatio == "object" && formatio) -)); - -/** - * @depend util/core.js - * @depend match.js - * @depend format.js - */ -/** - * Spy calls - * - * @author Christian Johansen (christian@cjohansen.no) - * @author Maximilian Antoni (mail@maxantoni.de) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - * Copyright (c) 2013 Maximilian Antoni - */ - -(function (sinon) { - function makeApi(sinon) { - function throwYieldError(proxy, text, args) { - var msg = sinon.functionName(proxy) + text; - if (args.length) { - msg += " Received [" + slice.call(args).join(", ") + "]"; - } - throw new Error(msg); - } - - var slice = Array.prototype.slice; - - var callProto = { - calledOn: function calledOn(thisValue) { - if (sinon.match && sinon.match.isMatcher(thisValue)) { - return thisValue.test(this.thisValue); - } - return this.thisValue === thisValue; - }, - - calledWith: function calledWith() { - var l = arguments.length; - if (l > this.args.length) { - return false; - } - for (var i = 0; i < l; i += 1) { - if (!sinon.deepEqual(arguments[i], this.args[i])) { - return false; - } - } - - return true; - }, - - calledWithMatch: function calledWithMatch() { - var l = arguments.length; - if (l > this.args.length) { - return false; - } - for (var i = 0; i < l; i += 1) { - var actual = this.args[i]; - var expectation = arguments[i]; - if (!sinon.match || !sinon.match(expectation).test(actual)) { - return false; - } - } - return true; - }, - - calledWithExactly: function calledWithExactly() { - return arguments.length == this.args.length && - this.calledWith.apply(this, arguments); - }, - - notCalledWith: function notCalledWith() { - return !this.calledWith.apply(this, arguments); - }, - - notCalledWithMatch: function notCalledWithMatch() { - return !this.calledWithMatch.apply(this, arguments); - }, - - returned: function returned(value) { - return sinon.deepEqual(value, this.returnValue); - }, - - threw: function threw(error) { - if (typeof error === "undefined" || !this.exception) { - return !!this.exception; - } - - return this.exception === error || this.exception.name === error; - }, - - calledWithNew: function calledWithNew() { - return this.proxy.prototype && this.thisValue instanceof this.proxy; - }, - - calledBefore: function (other) { - return this.callId < other.callId; - }, - - calledAfter: function (other) { - return this.callId > other.callId; - }, - - callArg: function (pos) { - this.args[pos](); - }, - - callArgOn: function (pos, thisValue) { - this.args[pos].apply(thisValue); - }, - - callArgWith: function (pos) { - this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1))); - }, - - callArgOnWith: function (pos, thisValue) { - var args = slice.call(arguments, 2); - this.args[pos].apply(thisValue, args); - }, - - yield: function () { - this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0))); - }, - - yieldOn: function (thisValue) { - var args = this.args; - for (var i = 0, l = args.length; i < l; ++i) { - if (typeof args[i] === "function") { - args[i].apply(thisValue, slice.call(arguments, 1)); - return; - } - } - throwYieldError(this.proxy, " cannot yield since no callback was passed.", args); - }, - - yieldTo: function (prop) { - this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1))); - }, - - yieldToOn: function (prop, thisValue) { - var args = this.args; - for (var i = 0, l = args.length; i < l; ++i) { - if (args[i] && typeof args[i][prop] === "function") { - args[i][prop].apply(thisValue, slice.call(arguments, 2)); - return; - } - } - throwYieldError(this.proxy, " cannot yield to '" + prop + - "' since no callback was passed.", args); - }, - - toString: function () { - var callStr = this.proxy.toString() + "("; - var args = []; - - for (var i = 0, l = this.args.length; i < l; ++i) { - args.push(sinon.format(this.args[i])); - } - - callStr = callStr + args.join(", ") + ")"; - - if (typeof this.returnValue != "undefined") { - callStr += " => " + sinon.format(this.returnValue); - } - - if (this.exception) { - callStr += " !" + this.exception.name; - - if (this.exception.message) { - callStr += "(" + this.exception.message + ")"; - } - } - - return callStr; - } - }; - - callProto.invokeCallback = callProto.yield; - - function createSpyCall(spy, thisValue, args, returnValue, exception, id) { - if (typeof id !== "number") { - throw new TypeError("Call id is not a number"); - } - var proxyCall = sinon.create(callProto); - proxyCall.proxy = spy; - proxyCall.thisValue = thisValue; - proxyCall.args = args; - proxyCall.returnValue = returnValue; - proxyCall.exception = exception; - proxyCall.callId = id; - - return proxyCall; - } - createSpyCall.toString = callProto.toString; // used by mocks - - sinon.spyCall = createSpyCall; - return createSpyCall; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./match"); - require("./format"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend times_in_words.js - * @depend util/core.js - * @depend extend.js - * @depend call.js - * @depend format.js - */ -/** - * Spy functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - - function makeApi(sinon) { - var push = Array.prototype.push; - var slice = Array.prototype.slice; - var callId = 0; - - function spy(object, property, types) { - if (!property && typeof object == "function") { - return spy.create(object); - } - - if (!object && !property) { - return spy.create(function () { }); - } - - if (types) { - var methodDesc = sinon.getPropertyDescriptor(object, property); - for (var i = 0; i < types.length; i++) { - methodDesc[types[i]] = spy.create(methodDesc[types[i]]); - } - return sinon.wrapMethod(object, property, methodDesc); - } else { - var method = object[property]; - return sinon.wrapMethod(object, property, spy.create(method)); - } - } - - function matchingFake(fakes, args, strict) { - if (!fakes) { - return; - } - - for (var i = 0, l = fakes.length; i < l; i++) { - if (fakes[i].matches(args, strict)) { - return fakes[i]; - } - } - } - - function incrementCallCount() { - this.called = true; - this.callCount += 1; - this.notCalled = false; - this.calledOnce = this.callCount == 1; - this.calledTwice = this.callCount == 2; - this.calledThrice = this.callCount == 3; - } - - function createCallProperties() { - this.firstCall = this.getCall(0); - this.secondCall = this.getCall(1); - this.thirdCall = this.getCall(2); - this.lastCall = this.getCall(this.callCount - 1); - } - - var vars = "a,b,c,d,e,f,g,h,i,j,k,l"; - function createProxy(func, proxyLength) { - // Retain the function length: - var p; - if (proxyLength) { - eval("p = (function proxy(" + vars.substring(0, proxyLength * 2 - 1) + - ") { return p.invoke(func, this, slice.call(arguments)); });"); - } else { - p = function proxy() { - return p.invoke(func, this, slice.call(arguments)); - }; - } - p.isSinonProxy = true; - return p; - } - - var uuid = 0; - - // Public API - var spyApi = { - reset: function () { - if (this.invoking) { - var err = new Error("Cannot reset Sinon function while invoking it. " + - "Move the call to .reset outside of the callback."); - err.name = "InvalidResetException"; - throw err; - } - - this.called = false; - this.notCalled = true; - this.calledOnce = false; - this.calledTwice = false; - this.calledThrice = false; - this.callCount = 0; - this.firstCall = null; - this.secondCall = null; - this.thirdCall = null; - this.lastCall = null; - this.args = []; - this.returnValues = []; - this.thisValues = []; - this.exceptions = []; - this.callIds = []; - if (this.fakes) { - for (var i = 0; i < this.fakes.length; i++) { - this.fakes[i].reset(); - } - } - - return this; - }, - - create: function create(func, spyLength) { - var name; - - if (typeof func != "function") { - func = function () { }; - } else { - name = sinon.functionName(func); - } - - if (!spyLength) { - spyLength = func.length; - } - - var proxy = createProxy(func, spyLength); - - sinon.extend(proxy, spy); - delete proxy.create; - sinon.extend(proxy, func); - - proxy.reset(); - proxy.prototype = func.prototype; - proxy.displayName = name || "spy"; - proxy.toString = sinon.functionToString; - proxy.instantiateFake = sinon.spy.create; - proxy.id = "spy#" + uuid++; - - return proxy; - }, - - invoke: function invoke(func, thisValue, args) { - var matching = matchingFake(this.fakes, args); - var exception, returnValue; - - incrementCallCount.call(this); - push.call(this.thisValues, thisValue); - push.call(this.args, args); - push.call(this.callIds, callId++); - - // Make call properties available from within the spied function: - createCallProperties.call(this); - - try { - this.invoking = true; - - if (matching) { - returnValue = matching.invoke(func, thisValue, args); - } 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) { - exception = e; - } finally { - delete this.invoking; - } - - push.call(this.exceptions, exception); - push.call(this.returnValues, returnValue); - - // Make return value and exception available in the calls: - createCallProperties.call(this); - - if (exception !== undefined) { - throw exception; - } - - return returnValue; - }, - - named: function named(name) { - this.displayName = name; - return this; - }, - - getCall: function getCall(i) { - if (i < 0 || i >= this.callCount) { - return null; - } - - return sinon.spyCall(this, this.thisValues[i], this.args[i], - this.returnValues[i], this.exceptions[i], - 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; - } - - if (!spyFn.called) { - return true; - } - - return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1]; - }, - - calledAfter: function calledAfter(spyFn) { - if (!this.called || !spyFn.called) { - return false; - } - - return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; - }, - - withArgs: function () { - var args = slice.call(arguments); - - if (this.fakes) { - var match = matchingFake(this.fakes, args, true); - - if (match) { - return match; - } - } else { - this.fakes = []; - } - - var original = this; - var fake = this.instantiateFake(); - fake.matchingAguments = args; - fake.parent = this; - push.call(this.fakes, fake); - - fake.withArgs = function () { - return original.withArgs.apply(original, arguments); - }; - - for (var i = 0; i < this.args.length; i++) { - if (fake.matches(this.args[i])) { - incrementCallCount.call(fake); - push.call(fake.thisValues, this.thisValues[i]); - push.call(fake.args, this.args[i]); - push.call(fake.returnValues, this.returnValues[i]); - push.call(fake.exceptions, this.exceptions[i]); - push.call(fake.callIds, this.callIds[i]); - } - } - createCallProperties.call(fake); - - return fake; - }, - - matches: function (args, strict) { - var margs = this.matchingAguments; - - if (margs.length <= args.length && - sinon.deepEqual(margs, args.slice(0, margs.length))) { - return !strict || margs.length == args.length; - } - }, - - printf: function (format) { - var spy = this; - var args = slice.call(arguments, 1); - var formatter; - - return (format || "").replace(/%(.)/g, function (match, specifyer) { - formatter = spyApi.formatters[specifyer]; - - if (typeof formatter == "function") { - return formatter.call(null, spy, args); - } else if (!isNaN(parseInt(specifyer, 10))) { - return sinon.format(args[specifyer - 1]); - } - - return "%" + specifyer; - }); - } - }; - - function delegateToCalls(method, matchAny, actual, notCalled) { - spyApi[method] = function () { - if (!this.called) { - if (notCalled) { - return notCalled.apply(this, arguments); - } - return false; - } - - var currentCall; - var matches = 0; - - for (var i = 0, l = this.callCount; i < l; i += 1) { - currentCall = this.getCall(i); - - if (currentCall[actual || method].apply(currentCall, arguments)) { - matches += 1; - - if (matchAny) { - return true; - } - } - } - - return matches === this.callCount; - }; - } - - delegateToCalls("calledOn", true); - delegateToCalls("alwaysCalledOn", false, "calledOn"); - delegateToCalls("calledWith", true); - delegateToCalls("calledWithMatch", true); - delegateToCalls("alwaysCalledWith", false, "calledWith"); - delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch"); - delegateToCalls("calledWithExactly", true); - delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly"); - delegateToCalls("neverCalledWith", false, "notCalledWith", function () { - return true; - }); - delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", function () { - return true; - }); - delegateToCalls("threw", true); - delegateToCalls("alwaysThrew", false, "threw"); - delegateToCalls("returned", true); - delegateToCalls("alwaysReturned", false, "returned"); - delegateToCalls("calledWithNew", true); - delegateToCalls("alwaysCalledWithNew", false, "calledWithNew"); - delegateToCalls("callArg", false, "callArgWith", function () { - throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); - }); - spyApi.callArgWith = spyApi.callArg; - delegateToCalls("callArgOn", false, "callArgOnWith", function () { - throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); - }); - spyApi.callArgOnWith = spyApi.callArgOn; - delegateToCalls("yield", false, "yield", function () { - throw new Error(this.toString() + " cannot yield since it was not yet invoked."); - }); - // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode. - spyApi.invokeCallback = spyApi.yield; - delegateToCalls("yieldOn", false, "yieldOn", function () { - throw new Error(this.toString() + " cannot yield since it was not yet invoked."); - }); - delegateToCalls("yieldTo", false, "yieldTo", function (property) { - throw new Error(this.toString() + " cannot yield to '" + property + - "' since it was not yet invoked."); - }); - delegateToCalls("yieldToOn", false, "yieldToOn", function (property) { - throw new Error(this.toString() + " cannot yield to '" + property + - "' since it was not yet invoked."); - }); - - spyApi.formatters = { - c: function (spy) { - return sinon.timesInWords(spy.callCount); - }, - - n: function (spy) { - return spy.toString(); - }, - - C: function (spy) { - var calls = []; - - for (var i = 0, l = spy.callCount; i < l; ++i) { - var stringifiedCall = " " + spy.getCall(i).toString(); - if (/\n/.test(calls[i - 1])) { - stringifiedCall = "\n" + stringifiedCall; - } - push.call(calls, stringifiedCall); - } - - return calls.length > 0 ? "\n" + calls.join("\n") : ""; - }, - - t: function (spy) { - var objects = []; - - 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; - sinon.spy = spy; - - return spy; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./call"); - require("./extend"); - require("./times_in_words"); - require("./format"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - * @depend extend.js - */ -/** - * 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 slice = Array.prototype.slice; - var join = Array.prototype.join; - var useLeftMostCallback = -1; - var useRightMostCallback = -2; - - 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) { - return args[callArgAt]; - } - - var argumentList; - - if (callArgAt === useLeftMostCallback) { - argumentList = args; - } - - if (callArgAt === useRightMostCallback) { - argumentList = slice.call(args).reverse(); - } - - var callArgProp = behavior.callArgProp; - - for (var i = 0, l = argumentList.length; i < l; ++i) { - if (!callArgProp && typeof argumentList[i] == "function") { - return argumentList[i]; - } - - if (callArgProp && argumentList[i] && - typeof argumentList[i][callArgProp] == "function") { - return argumentList[i][callArgProp]; - } - } - - return null; - } - - function makeApi(sinon) { - 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); - } - } - } - - var proto = { - create: function create(stub) { - var behavior = sinon.extend({}, sinon.behavior); - delete behavior.create; - behavior.stub = stub; - - return behavior; - }, - - isPresent: function isPresent() { - return (typeof this.callArgAt == "number" || - this.exception || - typeof this.returnArgAt == "number" || - this.returnThis || - this.returnValueDefined); - }, - - invoke: function invoke(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 onCall(index) { - return this.stub.onCall(index); - }, - - onFirstCall: function onFirstCall() { - return this.stub.onFirstCall(); - }, - - onSecondCall: function onSecondCall() { - return this.stub.onSecondCall(); - }, - - onThirdCall: function onThirdCall() { - return this.stub.onThirdCall(); - }, - - withArgs: function withArgs(/* 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 = useLeftMostCallback; - this.callbackArguments = slice.call(arguments, 0); - this.callbackContext = undefined; - this.callArgProp = undefined; - this.callbackAsync = false; - - return this; - }, - - yieldsRight: function () { - this.callArgAt = useRightMostCallback; - 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 = useLeftMostCallback; - this.callbackArguments = slice.call(arguments, 1); - this.callbackContext = context; - this.callArgProp = undefined; - this.callbackAsync = false; - - return this; - }, - - yieldsTo: function (prop) { - this.callArgAt = useLeftMostCallback; - 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"); - } - - this.callArgAt = useLeftMostCallback; - this.callbackArguments = slice.call(arguments, 2); - this.callbackContext = context; - this.callArgProp = prop; - this.callbackAsync = false; - - return this; - }, - - 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"); - } - - this.returnArgAt = pos; - - return this; - }, - - returnsThis: function returnsThis() { - this.returnThis = true; - - return this; - } - }; - - // 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); - } - } - - sinon.behavior = proto; - return proto; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./extend"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - * @depend extend.js - * @depend spy.js - * @depend behavior.js - */ -/** - * Stub functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - function makeApi(sinon) { - function stub(object, property, func) { - if (!!func && typeof func != "function" && typeof func != "object") { - throw new TypeError("Custom stub should be a function or a property descriptor"); - } - - var wrapper; - - if (func) { - if (typeof func == "function") { - wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; - } else { - wrapper = func; - if (sinon.spy && sinon.spy.create) { - var types = sinon.objectKeys(wrapper); - for (var i = 0; i < types.length; i++) { - wrapper[types[i]] = sinon.spy.create(wrapper[types[i]]); - } - } - } - } else { - var stubLength = 0; - if (typeof object == "object" && typeof object[property] == "function") { - stubLength = object[property].length; - } - wrapper = stub.create(stubLength); - } - - if (!object && typeof property === "undefined") { - return sinon.stub.create(); - } - - if (typeof property === "undefined" && typeof object == "object") { - for (var prop in object) { - if (typeof sinon.getPropertyDescriptor(object, prop).value === "function") { - stub(object, prop); - } - } - - return object; - } - - return sinon.wrapMethod(object, property, wrapper); - } - - function getDefaultBehavior(stub) { - return stub.defaultBehavior || getParentBehaviour(stub) || sinon.behavior.create(stub); - } - - function getParentBehaviour(stub) { - return (stub.parent && getCurrentBehavior(stub.parent)); - } - - function getCurrentBehavior(stub) { - var behavior = stub.behaviors[stub.callCount - 1]; - return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stub); - } - - var uuid = 0; - - var proto = { - create: function create(stubLength) { - var functionStub = function () { - return getCurrentBehavior(functionStub).invoke(this, arguments); - }; - - functionStub.id = "stub#" + uuid++; - var orig = functionStub; - functionStub = sinon.spy.create(functionStub, stubLength); - functionStub.func = orig; - - sinon.extend(functionStub, stub); - functionStub.instantiateFake = sinon.stub.create; - functionStub.displayName = "stub"; - functionStub.toString = sinon.functionToString; - - functionStub.defaultBehavior = null; - functionStub.behaviors = []; - - return functionStub; - }, - - resetBehavior: function () { - var i; - - this.defaultBehavior = null; - this.behaviors = []; - - delete this.returnValue; - delete this.returnArgAt; - this.returnThis = false; - - if (this.fakes) { - for (i = 0; i < this.fakes.length; i++) { - this.fakes[i].resetBehavior(); - } - } - }, - - onCall: function onCall(index) { - if (!this.behaviors[index]) { - this.behaviors[index] = sinon.behavior.create(this); - } - - return this.behaviors[index]; - }, - - onFirstCall: function onFirstCall() { - return this.onCall(0); - }, - - onSecondCall: function onSecondCall() { - return this.onCall(1); - }, - - onThirdCall: function onThirdCall() { - return this.onCall(2); - } - }; - - 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)); - } - } - - sinon.extend(stub, proto); - sinon.stub = stub; - - return stub; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./behavior"); - require("./spy"); - require("./extend"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend times_in_words.js - * @depend util/core.js - * @depend call.js - * @depend extend.js - * @depend match.js - * @depend spy.js - * @depend stub.js - * @depend format.js - */ -/** - * Mock functions. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - function makeApi(sinon) { - var push = [].push; - var match = sinon.match; - - function mock(object) { - // if (typeof console !== undefined && console.warn) { - // console.warn("mock will be removed from Sinon.JS v2.0"); - // } - - if (!object) { - return sinon.expectation.create("Anonymous mock"); - } - - return mock.create(object); - } - - function each(collection, callback) { - if (!collection) { - return; - } - - for (var i = 0, l = collection.length; i < l; i += 1) { - callback(collection[i]); - } - } - - sinon.extend(mock, { - create: function create(object) { - if (!object) { - throw new TypeError("object is null"); - } - - var mockObject = sinon.extend({}, mock); - mockObject.object = object; - delete mockObject.create; - - return mockObject; - }, - - expects: function expects(method) { - if (!method) { - throw new TypeError("method is falsy"); - } - - if (!this.expectations) { - this.expectations = {}; - this.proxies = []; - } - - if (!this.expectations[method]) { - this.expectations[method] = []; - var mockObject = this; - - sinon.wrapMethod(this.object, method, function () { - return mockObject.invokeMethod(method, this, arguments); - }); - - push.call(this.proxies, method); - } - - var expectation = sinon.expectation.create(method); - push.call(this.expectations[method], expectation); - - return expectation; - }, - - restore: function restore() { - var object = this.object; - - each(this.proxies, function (proxy) { - if (typeof object[proxy].restore == "function") { - object[proxy].restore(); - } - }); - }, - - verify: function verify() { - var expectations = this.expectations || {}; - var messages = [], met = []; - - each(this.proxies, function (proxy) { - each(expectations[proxy], function (expectation) { - if (!expectation.met()) { - push.call(messages, expectation.toString()); - } else { - push.call(met, expectation.toString()); - } - }); - }); - - this.restore(); - - if (messages.length > 0) { - sinon.expectation.fail(messages.concat(met).join("\n")); - } else if (met.length > 0) { - sinon.expectation.pass(messages.concat(met).join("\n")); - } - - return true; - }, - - invokeMethod: function invokeMethod(method, thisValue, args) { - var expectations = this.expectations && this.expectations[method]; - var length = expectations && expectations.length || 0, i; - - for (i = 0; i < length; i += 1) { - if (!expectations[i].met() && - expectations[i].allowsCall(thisValue, args)) { - return expectations[i].apply(thisValue, args); - } - } - - var messages = [], available, exhausted = 0; - - for (i = 0; i < length; i += 1) { - if (expectations[i].allowsCall(thisValue, args)) { - available = available || expectations[i]; - } else { - exhausted += 1; - } - push.call(messages, " " + expectations[i].toString()); - } - - if (exhausted === 0) { - return available.apply(thisValue, args); - } - - messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ - proxy: method, - args: args - })); - - sinon.expectation.fail(messages.join("\n")); - } - }); - - var times = sinon.timesInWords; - var slice = Array.prototype.slice; - - function callCountInWords(callCount) { - if (callCount == 0) { - return "never called"; - } else { - return "called " + times(callCount); - } - } - - function expectedCallCountInWords(expectation) { - var min = expectation.minCalls; - var max = expectation.maxCalls; - - if (typeof min == "number" && typeof max == "number") { - var str = times(min); - - if (min != max) { - str = "at least " + str + " and at most " + times(max); - } - - return str; - } - - if (typeof min == "number") { - return "at least " + times(min); - } - - return "at most " + times(max); - } - - function receivedMinCalls(expectation) { - var hasMinLimit = typeof expectation.minCalls == "number"; - return !hasMinLimit || expectation.callCount >= expectation.minCalls; - } - - function receivedMaxCalls(expectation) { - if (typeof expectation.maxCalls != "number") { - return false; - } - - return expectation.callCount == expectation.maxCalls; - } - - function verifyMatcher(possibleMatcher, arg) { - if (match && match.isMatcher(possibleMatcher)) { - return possibleMatcher.test(arg); - } else { - return true; - } - } - - sinon.expectation = { - minCalls: 1, - maxCalls: 1, - - create: function create(methodName) { - var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); - delete expectation.create; - expectation.method = methodName; - - return expectation; - }, - - invoke: function invoke(func, thisValue, args) { - this.verifyCallAllowed(thisValue, args); - - return sinon.spy.invoke.apply(this, arguments); - }, - - atLeast: function atLeast(num) { - if (typeof num != "number") { - throw new TypeError("'" + num + "' is not number"); - } - - if (!this.limitsSet) { - this.maxCalls = null; - this.limitsSet = true; - } - - this.minCalls = num; - - return this; - }, - - atMost: function atMost(num) { - if (typeof num != "number") { - throw new TypeError("'" + num + "' is not number"); - } - - if (!this.limitsSet) { - this.minCalls = null; - this.limitsSet = true; - } - - this.maxCalls = num; - - return this; - }, - - never: function never() { - return this.exactly(0); - }, - - once: function once() { - return this.exactly(1); - }, - - twice: function twice() { - return this.exactly(2); - }, - - thrice: function thrice() { - return this.exactly(3); - }, - - exactly: function exactly(num) { - if (typeof num != "number") { - throw new TypeError("'" + num + "' is not a number"); - } - - this.atLeast(num); - return this.atMost(num); - }, - - met: function met() { - return !this.failed && receivedMinCalls(this); - }, - - verifyCallAllowed: function verifyCallAllowed(thisValue, args) { - if (receivedMaxCalls(this)) { - this.failed = true; - sinon.expectation.fail(this.method + " already called " + times(this.maxCalls)); - } - - if ("expectedThis" in this && this.expectedThis !== thisValue) { - sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " + - this.expectedThis); - } - - if (!("expectedArguments" in this)) { - return; - } - - if (!args) { - sinon.expectation.fail(this.method + " received no arguments, expected " + - sinon.format(this.expectedArguments)); - } - - if (args.length < this.expectedArguments.length) { - sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) + - "), expected " + sinon.format(this.expectedArguments)); - } - - if (this.expectsExactArgCount && - args.length != this.expectedArguments.length) { - sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + - "), expected " + sinon.format(this.expectedArguments)); - } - - 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)); - } - } - }, - - allowsCall: function allowsCall(thisValue, args) { - if (this.met() && receivedMaxCalls(this)) { - return false; - } - - if ("expectedThis" in this && this.expectedThis !== thisValue) { - return false; - } - - if (!("expectedArguments" in this)) { - return true; - } - - args = args || []; - - if (args.length < this.expectedArguments.length) { - return false; - } - - if (this.expectsExactArgCount && - args.length != this.expectedArguments.length) { - return false; - } - - 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; - } - } - - return true; - }, - - withArgs: function withArgs() { - this.expectedArguments = slice.call(arguments); - return this; - }, - - withExactArgs: function withExactArgs() { - this.withArgs.apply(this, arguments); - this.expectsExactArgCount = true; - return this; - }, - - on: function on(thisValue) { - this.expectedThis = thisValue; - return this; - }, - - toString: function () { - var args = (this.expectedArguments || []).slice(); - - if (!this.expectsExactArgCount) { - push.call(args, "[...]"); - } - - var callStr = sinon.spyCall.toString.call({ - proxy: this.method || "anonymous mock expectation", - args: args - }); - - var message = callStr.replace(", [...", "[, ...") + " " + - expectedCallCountInWords(this); - - if (this.met()) { - return "Expectation met: " + message; - } - - return "Expected " + message + " (" + - callCountInWords(this.callCount) + ")"; - }, - - verify: function verify() { - if (!this.met()) { - sinon.expectation.fail(this.toString()); - } else { - sinon.expectation.pass(this.toString()); - } - - return true; - }, - - pass: function pass(message) { - sinon.assert.pass(message); - }, - - fail: function fail(message) { - var exception = new Error(message); - exception.name = "ExpectationError"; - - throw exception; - } - }; - - sinon.mock = mock; - return mock; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./times_in_words"); - require("./call"); - require("./extend"); - require("./match"); - require("./spy"); - require("./stub"); - require("./format"); - - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - * @depend spy.js - * @depend stub.js - * @depend mock.js - */ -/** - * Collections of stubs, spies and mocks. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - var push = [].push; - var hasOwnProperty = Object.prototype.hasOwnProperty; - - function getFakes(fakeCollection) { - if (!fakeCollection.fakes) { - fakeCollection.fakes = []; - } - - return fakeCollection.fakes; - } - - function each(fakeCollection, method) { - var fakes = getFakes(fakeCollection); - - for (var i = 0, l = fakes.length; i < l; i += 1) { - if (typeof fakes[i][method] == "function") { - fakes[i][method](); - } - } - } - - function compact(fakeCollection) { - var fakes = getFakes(fakeCollection); - var i = 0; - while (i < fakes.length) { - fakes.splice(i, 1); - } - } - - function makeApi(sinon) { - var collection = { - verify: function resolve() { - each(this, "verify"); - }, - - restore: function restore() { - each(this, "restore"); - compact(this); - }, - - reset: function restore() { - each(this, "reset"); - }, - - verifyAndRestore: function verifyAndRestore() { - var exception; - - try { - this.verify(); - } catch (e) { - exception = e; - } - - this.restore(); - - if (exception) { - throw exception; - } - }, - - add: function add(fake) { - push.call(getFakes(this), fake); - return fake; - }, - - spy: function spy() { - return this.add(sinon.spy.apply(sinon, arguments)); - }, - - stub: function stub(object, property, value) { - if (property) { - var original = object[property]; - - if (typeof original != "function") { - if (!hasOwnProperty.call(object, property)) { - throw new TypeError("Cannot stub non-existent own property " + property); - } - - object[property] = value; - - return this.add({ - restore: function () { - object[property] = original; - } - }); - } - } - if (!property && !!object && typeof object == "object") { - var stubbedObj = sinon.stub.apply(sinon, arguments); - - for (var prop in stubbedObj) { - if (typeof stubbedObj[prop] === "function") { - this.add(stubbedObj[prop]); - } - } - - return stubbedObj; - } - - return this.add(sinon.stub.apply(sinon, arguments)); - }, - - mock: function mock() { - return this.add(sinon.mock.apply(sinon, arguments)); - }, - - inject: function inject(obj) { - var col = this; - - obj.spy = function () { - return col.spy.apply(col, arguments); - }; - - obj.stub = function () { - return col.stub.apply(col, arguments); - }; - - obj.mock = function () { - return col.mock.apply(col, arguments); - }; - - return obj; - } - }; - - sinon.collection = collection; - return collection; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./mock"); - require("./spy"); - require("./stub"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/*global lolex */ - -/** - * Fake timer API - * setTimeout - * setInterval - * clearTimeout - * clearInterval - * tick - * reset - * Date - * - * Inspired by jsUnitMockTimeOut from JsUnit - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -if (typeof sinon == "undefined") { - var sinon = {}; -} - -(function (global) { - function makeApi(sinon, lol) { - var llx = typeof lolex !== "undefined" ? lolex : lol; - - sinon.useFakeTimers = function () { - var now, methods = Array.prototype.slice.call(arguments); - - if (typeof methods[0] === "string") { - now = 0; - } else { - now = methods.shift(); - } - - var clock = llx.install(now || 0, methods); - clock.restore = clock.uninstall; - return clock; - }; - - sinon.clock = { - create: function (now) { - return llx.createClock(now); - } - }; - - sinon.timers = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined), - clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate : undefined), - setInterval: setInterval, - clearInterval: clearInterval, - Date: Date - }; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, epxorts, module, lolex) { - var sinon = require("./core"); - makeApi(sinon, lolex); - module.exports = sinon; - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module, require("lolex")); - } else { - makeApi(sinon); - } -}(typeof global != "undefined" && typeof global !== "function" ? global : this)); - -/** - * Minimal Event interface implementation - * - * Original implementation by Sven Fuchs: https://gist.github.com/995028 - * Modifications and tests by Christian Johansen. - * - * @author Sven Fuchs (svenfuchs@artweb-design.de) - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2011 Sven Fuchs, Christian Johansen - */ - -if (typeof sinon == "undefined") { - this.sinon = {}; -} - -(function () { - var push = [].push; - - function makeApi(sinon) { - sinon.Event = function Event(type, bubbles, cancelable, target) { - this.initEvent(type, bubbles, cancelable, target); - }; - - sinon.Event.prototype = { - initEvent: function (type, bubbles, cancelable, target) { - this.type = type; - this.bubbles = bubbles; - this.cancelable = cancelable; - this.target = target; - }, - - stopPropagation: function () {}, - - preventDefault: function () { - this.defaultPrevented = true; - } - }; - - sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) { - this.initEvent(type, false, false, target); - this.loaded = progressEventRaw.loaded || null; - this.total = progressEventRaw.total || null; - this.lengthComputable = !!progressEventRaw.total; - }; - - 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) { - this.eventListeners = this.eventListeners || {}; - this.eventListeners[event] = this.eventListeners[event] || []; - push.call(this.eventListeners[event], listener); - }, - - removeEventListener: function removeEventListener(event, listener) { - var listeners = this.eventListeners && this.eventListeners[event] || []; - - for (var i = 0, l = listeners.length; i < l; ++i) { - if (listeners[i] == listener) { - return listeners.splice(i, 1); - } - } - }, - - dispatchEvent: function dispatchEvent(event) { - var type = event.type; - var listeners = this.eventListeners && this.eventListeners[type] || []; - - for (var i = 0; i < listeners.length; i++) { - if (typeof listeners[i] == "function") { - listeners[i].call(this, event); - } else { - listeners[i].handleEvent(event); - } - } - - return !!event.defaultPrevented; - } - }; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require) { - var sinon = require("./core"); - makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require); - } else { - makeApi(sinon); - } -}()); - -/** - * @depend util/core.js - */ -/** - * Logs errors - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2014 Christian Johansen - */ - -(function (sinon) { - // cache a reference to setTimeout, so that our reference won't be stubbed out - // when using fake timers and errors will still get logged - // https://github.com/cjohansen/Sinon.JS/issues/381 - var realSetTimeout = setTimeout; - - function makeApi(sinon) { - - function log() {} - - function logError(label, err) { - var msg = label + " threw exception: "; - - sinon.log(msg + "[" + err.name + "] " + err.message); - - if (err.stack) { - sinon.log(err.stack); - } - - logError.setTimeout(function () { - err.message = msg + err.message; - throw err; - }, 0); - }; - - // wrap realSetTimeout with something we can stub in tests - logError.setTimeout = function (func, timeout) { - realSetTimeout(func, timeout); - } - - var exports = {}; - exports.log = sinon.log = log; - exports.logError = sinon.logError = logError; - - return exports; - } - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - module.exports = makeApi(sinon); - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend core.js - * @depend ../extend.js - * @depend event.js - * @depend ../log_error.js - */ -/** - * Fake XDomainRequest object - */ - -if (typeof sinon == "undefined") { - this.sinon = {}; -} - -// wrapper for global -(function (global) { - var xdr = { XDomainRequest: global.XDomainRequest }; - xdr.GlobalXDomainRequest = global.XDomainRequest; - xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined"; - xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest : false; - - function makeApi(sinon) { - sinon.xdr = xdr; - - function FakeXDomainRequest() { - this.readyState = FakeXDomainRequest.UNSENT; - this.requestBody = null; - this.requestHeaders = {}; - this.status = 0; - this.timeout = null; - - if (typeof FakeXDomainRequest.onCreate == "function") { - FakeXDomainRequest.onCreate(this); - } - } - - function verifyState(xdr) { - if (xdr.readyState !== FakeXDomainRequest.OPENED) { - throw new Error("INVALID_STATE_ERR"); - } - - if (xdr.sendFlag) { - throw new Error("INVALID_STATE_ERR"); - } - } - - function verifyRequestSent(xdr) { - if (xdr.readyState == FakeXDomainRequest.UNSENT) { - throw new Error("Request not sent"); - } - if (xdr.readyState == FakeXDomainRequest.DONE) { - throw new Error("Request done"); - } - } - - function verifyResponseBodyType(body) { - if (typeof body != "string") { - var error = new Error("Attempted to respond to fake XDomainRequest with " + - body + ", which is not a string."); - error.name = "InvalidBodyException"; - throw error; - } - } - - sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, { - open: function open(method, url) { - this.method = method; - this.url = url; - - this.responseText = null; - this.sendFlag = false; - - this.readyStateChange(FakeXDomainRequest.OPENED); - }, - - readyStateChange: function readyStateChange(state) { - this.readyState = state; - var eventName = ""; - switch (this.readyState) { - case FakeXDomainRequest.UNSENT: - break; - case FakeXDomainRequest.OPENED: - break; - case FakeXDomainRequest.LOADING: - if (this.sendFlag) { - //raise the progress event - eventName = "onprogress"; - } - break; - case FakeXDomainRequest.DONE: - if (this.isTimeout) { - eventName = "ontimeout" - } else if (this.errorFlag || (this.status < 200 || this.status > 299)) { - eventName = "onerror"; - } else { - eventName = "onload" - } - break; - } - - // raising event (if defined) - if (eventName) { - if (typeof this[eventName] == "function") { - try { - this[eventName](); - } catch (e) { - sinon.logError("Fake XHR " + eventName + " handler", e); - } - } - } - }, - - send: function send(data) { - verifyState(this); - - if (!/^(get|head)$/i.test(this.method)) { - this.requestBody = data; - } - this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; - - this.errorFlag = false; - this.sendFlag = true; - this.readyStateChange(FakeXDomainRequest.OPENED); - - if (typeof this.onSend == "function") { - this.onSend(this); - } - }, - - abort: function abort() { - this.aborted = true; - this.responseText = null; - this.errorFlag = true; - - if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) { - this.readyStateChange(sinon.FakeXDomainRequest.DONE); - this.sendFlag = false; - } - }, - - setResponseBody: function setResponseBody(body) { - verifyRequestSent(this); - verifyResponseBodyType(body); - - var chunkSize = this.chunkSize || 10; - var index = 0; - this.responseText = ""; - - do { - this.readyStateChange(FakeXDomainRequest.LOADING); - this.responseText += body.substring(index, index + chunkSize); - index += chunkSize; - } while (index < body.length); - - this.readyStateChange(FakeXDomainRequest.DONE); - }, - - respond: function respond(status, contentType, body) { - // content-type ignored, since XDomainRequest does not carry this - // we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease - // test integration across browsers - this.status = typeof status == "number" ? status : 200; - this.setResponseBody(body || ""); - }, - - simulatetimeout: function simulatetimeout() { - this.status = 0; - this.isTimeout = true; - // Access to this should actually throw an error - this.responseText = undefined; - this.readyStateChange(FakeXDomainRequest.DONE); - } - }); - - sinon.extend(FakeXDomainRequest, { - UNSENT: 0, - OPENED: 1, - LOADING: 3, - DONE: 4 - }); - - sinon.useFakeXDomainRequest = function useFakeXDomainRequest() { - sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) { - if (xdr.supportsXDR) { - global.XDomainRequest = xdr.GlobalXDomainRequest; - } - - delete sinon.FakeXDomainRequest.restore; - - if (keepOnCreate !== true) { - delete sinon.FakeXDomainRequest.onCreate; - } - }; - if (xdr.supportsXDR) { - global.XDomainRequest = sinon.FakeXDomainRequest; - } - return sinon.FakeXDomainRequest; - }; - - sinon.FakeXDomainRequest = FakeXDomainRequest; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./core"); - require("../extend"); - require("./event"); - require("../log_error"); - makeApi(sinon); - module.exports = sinon; - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else { - makeApi(sinon); - } -})(typeof global !== "undefined" ? global : self); - -/** - * @depend core.js - * @depend ../extend.js - * @depend event.js - * @depend ../log_error.js - */ -/** - * Fake XMLHttpRequest object - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (global) { - - var supportsProgress = typeof ProgressEvent !== "undefined"; - var supportsCustomEvent = typeof CustomEvent !== "undefined"; - var supportsFormData = typeof FormData !== "undefined"; - var sinonXhr = { XMLHttpRequest: global.XMLHttpRequest }; - sinonXhr.GlobalXMLHttpRequest = global.XMLHttpRequest; - sinonXhr.GlobalActiveXObject = global.ActiveXObject; - sinonXhr.supportsActiveX = typeof sinonXhr.GlobalActiveXObject != "undefined"; - sinonXhr.supportsXHR = typeof sinonXhr.GlobalXMLHttpRequest != "undefined"; - sinonXhr.workingXHR = sinonXhr.supportsXHR ? sinonXhr.GlobalXMLHttpRequest : sinonXhr.supportsActiveX - ? function () { - return new sinonXhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") - } : false; - sinonXhr.supportsCORS = sinonXhr.supportsXHR && "withCredentials" in (new sinonXhr.GlobalXMLHttpRequest()); - - /*jsl:ignore*/ - var unsafeHeaders = { - "Accept-Charset": true, - "Accept-Encoding": true, - Connection: true, - "Content-Length": true, - Cookie: true, - Cookie2: true, - "Content-Transfer-Encoding": true, - Date: true, - Expect: true, - Host: true, - "Keep-Alive": true, - Referer: true, - TE: true, - Trailer: true, - "Transfer-Encoding": true, - Upgrade: true, - "User-Agent": true, - Via: true - }; - /*jsl:end*/ - - // Note that for FakeXMLHttpRequest to work pre ES5 - // we lose some of the alignment with the spec. - // To ensure as close a match as possible, - // set responseType before calling open, send or respond; - function FakeXMLHttpRequest() { - this.readyState = FakeXMLHttpRequest.UNSENT; - this.requestHeaders = {}; - this.requestBody = null; - this.status = 0; - this.statusText = ""; - this.upload = new UploadProgress(); - this.responseType = ""; - this.response = ""; - if (sinonXhr.supportsCORS) { - this.withCredentials = false; - } - - var xhr = this; - var events = ["loadstart", "load", "abort", "loadend"]; - - function addEventListener(eventName) { - xhr.addEventListener(eventName, function (event) { - var listener = xhr["on" + eventName]; - - if (listener && typeof listener == "function") { - listener.call(this, event); - } - }); - } - - for (var i = events.length - 1; i >= 0; i--) { - addEventListener(events[i]); - } - - if (typeof FakeXMLHttpRequest.onCreate == "function") { - FakeXMLHttpRequest.onCreate(this); - } - } - - // 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 addEventListener(event, listener) { - this.eventListeners[event].push(listener); - }; - - UploadProgress.prototype.removeEventListener = function removeEventListener(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 dispatchEvent(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"); - } - - if (xhr.sendFlag) { - throw new Error("INVALID_STATE_ERR"); - } - } - - function getHeader(headers, header) { - header = header.toLowerCase(); - - for (var h in headers) { - if (h.toLowerCase() == header) { - return h; - } - } - - return null; - } - - // filtering to enable a white-list version of Sinon FakeXhr, - // where whitelisted requests are passed through to real XHR - function each(collection, callback) { - if (!collection) { - return; - } - - for (var i = 0, l = collection.length; i < l; i += 1) { - callback(collection[i]); - } - } - 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 - var apply = function (obj, method, args) { - switch (args.length) { - case 0: return obj[method](); - case 1: return obj[method](args[0]); - case 2: return obj[method](args[0], args[1]); - 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 = []; - FakeXMLHttpRequest.addFilter = function addFilter(fn) { - this.filters.push(fn) - }; - var IE6Re = /MSIE 6/; - FakeXMLHttpRequest.defake = function defake(fakeXhr, xhrArgs) { - var xhr = new sinonXhr.workingXHR(); - each([ - "open", - "setRequestHeader", - "send", - "abort", - "getResponseHeader", - "getAllResponseHeaders", - "addEventListener", - "overrideMimeType", - "removeEventListener" - ], function (method) { - fakeXhr[method] = function () { - return apply(xhr, method, arguments); - }; - }); - - var copyAttrs = function (args) { - each(args, function (attr) { - try { - fakeXhr[attr] = xhr[attr] - } catch (e) { - if (!IE6Re.test(navigator.userAgent)) { - throw e; - } - } - }); - }; - - var stateChange = function stateChange() { - fakeXhr.readyState = xhr.readyState; - if (xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { - copyAttrs(["status", "statusText"]); - } - if (xhr.readyState >= FakeXMLHttpRequest.LOADING) { - copyAttrs(["responseText", "response"]); - } - if (xhr.readyState === FakeXMLHttpRequest.DONE) { - copyAttrs(["responseXML"]); - } - if (fakeXhr.onreadystatechange) { - fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr }); - } - }; - - if (xhr.addEventListener) { - for (var event in fakeXhr.eventListeners) { - if (fakeXhr.eventListeners.hasOwnProperty(event)) { - each(fakeXhr.eventListeners[event], function (handler) { - xhr.addEventListener(event, handler); - }); - } - } - xhr.addEventListener("readystatechange", stateChange); - } else { - xhr.onreadystatechange = stateChange; - } - apply(xhr, "open", xhrArgs); - }; - 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"); - } - } - - function verifyHeadersReceived(xhr) { - if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { - throw new Error("No headers received"); - } - } - - function verifyResponseBodyType(body) { - if (typeof body != "string") { - var error = new Error("Attempted to respond to fake XMLHttpRequest with " + - body + ", which is not a string."); - error.name = "InvalidBodyException"; - throw error; - } - } - - FakeXMLHttpRequest.parseXML = function parseXML(text) { - var xmlDoc; - - if (typeof DOMParser != "undefined") { - var parser = new DOMParser(); - xmlDoc = parser.parseFromString(text, "text/xml"); - } else { - xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); - xmlDoc.async = "false"; - xmlDoc.loadXML(text); - } - - return xmlDoc; - }; - - FakeXMLHttpRequest.statusCodes = { - 100: "Continue", - 101: "Switching Protocols", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi-Status", - 300: "Multiple Choice", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 422: "Unprocessable Entity", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported" - }; - - function makeApi(sinon) { - sinon.xhr = sinonXhr; - - sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { - async: true, - - open: function open(method, url, async, username, password) { - this.method = method; - this.url = url; - this.async = typeof async == "boolean" ? async : true; - this.username = username; - this.password = password; - this.responseText = null; - this.response = this.responseType === "json" ? null : ""; - this.responseXML = null; - this.requestHeaders = {}; - this.sendFlag = false; - - if (FakeXMLHttpRequest.useFilters === true) { - var xhrArgs = arguments; - var defake = some(FakeXMLHttpRequest.filters, function (filter) { - return filter.apply(this, xhrArgs) - }); - if (defake) { - return FakeXMLHttpRequest.defake(this, arguments); - } - } - this.readyStateChange(FakeXMLHttpRequest.OPENED); - }, - - readyStateChange: function readyStateChange(state) { - this.readyState = state; - - if (typeof this.onreadystatechange == "function") { - try { - this.onreadystatechange(); - } catch (e) { - sinon.logError("Fake XHR onreadystatechange handler", e); - } - } - - switch (this.readyState) { - case FakeXMLHttpRequest.DONE: - if (supportsProgress) { - this.upload.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100})); - this.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100})); - } - this.upload.dispatchEvent(new sinon.Event("load", false, false, this)); - this.dispatchEvent(new sinon.Event("load", false, false, this)); - this.dispatchEvent(new sinon.Event("loadend", false, false, this)); - break; - } - - this.dispatchEvent(new sinon.Event("readystatechange")); - }, - - setRequestHeader: function setRequestHeader(header, value) { - verifyState(this); - - if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { - throw new Error("Refused to set unsafe header \"" + header + "\""); - } - - if (this.requestHeaders[header]) { - this.requestHeaders[header] += "," + value; - } else { - this.requestHeaders[header] = value; - } - }, - - // Helps testing - setResponseHeaders: function setResponseHeaders(headers) { - verifyRequestOpened(this); - this.responseHeaders = {}; - - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - this.responseHeaders[header] = headers[header]; - } - } - - if (this.async) { - this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); - } else { - this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; - } - }, - - // Currently treats ALL data as a DOMString (i.e. no Document) - send: function send(data) { - verifyState(this); - - if (!/^(get|head)$/i.test(this.method)) { - var contentType = getHeader(this.requestHeaders, "Content-Type"); - if (this.requestHeaders[contentType]) { - var value = this.requestHeaders[contentType].split(";"); - this.requestHeaders[contentType] = value[0] + ";charset=utf-8"; - } else if (supportsFormData && !(data instanceof FormData)) { - this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; - } - - this.requestBody = data; - } - - this.errorFlag = false; - this.sendFlag = this.async; - this.response = this.responseType === "json" ? null : ""; - this.readyStateChange(FakeXMLHttpRequest.OPENED); - - if (typeof this.onSend == "function") { - this.onSend(this); - } - - this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); - }, - - abort: function abort() { - this.aborted = true; - this.responseText = null; - this.response = this.responseType === "json" ? null : ""; - this.errorFlag = true; - this.requestHeaders = {}; - this.responseHeaders = {}; - - if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { - this.readyStateChange(FakeXMLHttpRequest.DONE); - this.sendFlag = false; - } - - this.readyState = 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(); - } - }, - - getResponseHeader: function getResponseHeader(header) { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return null; - } - - if (/^Set-Cookie2?$/i.test(header)) { - return null; - } - - header = getHeader(this.responseHeaders, header); - - return this.responseHeaders[header] || null; - }, - - getAllResponseHeaders: function getAllResponseHeaders() { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return ""; - } - - var headers = ""; - - for (var header in this.responseHeaders) { - if (this.responseHeaders.hasOwnProperty(header) && - !/^Set-Cookie2?$/i.test(header)) { - headers += header + ": " + this.responseHeaders[header] + "\r\n"; - } - } - - return headers; - }, - - setResponseBody: function setResponseBody(body) { - verifyRequestSent(this); - verifyHeadersReceived(this); - verifyResponseBodyType(body); - - var chunkSize = this.chunkSize || 10; - var index = 0; - this.responseText = ""; - - do { - if (this.async) { - this.readyStateChange(FakeXMLHttpRequest.LOADING); - } - - this.responseText += body.substring(index, index + chunkSize); - index += chunkSize; - } while (index < body.length); - - var type = this.getResponseHeader("Content-Type"); - - if (this.responseText && - (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { - try { - this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); - } catch (e) { - // Unable to parse XML - no biggie - } - } - - this.response = this.responseType === "json" ? JSON.parse(this.responseText) : this.responseText; - this.readyStateChange(FakeXMLHttpRequest.DONE); - }, - - respond: function respond(status, headers, body) { - this.status = typeof status == "number" ? status : 200; - this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; - this.setResponseHeaders(headers || {}); - this.setResponseBody(body || ""); - }, - - uploadProgress: function uploadProgress(progressEventRaw) { - if (supportsProgress) { - this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); - } - }, - - downloadProgress: function downloadProgress(progressEventRaw) { - if (supportsProgress) { - this.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); - } - }, - - uploadError: function uploadError(error) { - if (supportsCustomEvent) { - this.upload.dispatchEvent(new sinon.CustomEvent("error", {detail: error})); - } - } - }); - - sinon.extend(FakeXMLHttpRequest, { - UNSENT: 0, - OPENED: 1, - HEADERS_RECEIVED: 2, - LOADING: 3, - DONE: 4 - }); - - sinon.useFakeXMLHttpRequest = function () { - FakeXMLHttpRequest.restore = function restore(keepOnCreate) { - if (sinonXhr.supportsXHR) { - global.XMLHttpRequest = sinonXhr.GlobalXMLHttpRequest; - } - - if (sinonXhr.supportsActiveX) { - global.ActiveXObject = sinonXhr.GlobalActiveXObject; - } - - delete FakeXMLHttpRequest.restore; - - if (keepOnCreate !== true) { - delete FakeXMLHttpRequest.onCreate; - } - }; - if (sinonXhr.supportsXHR) { - global.XMLHttpRequest = FakeXMLHttpRequest; - } - - if (sinonXhr.supportsActiveX) { - global.ActiveXObject = function ActiveXObject(objId) { - if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { - - return new FakeXMLHttpRequest(); - } - - return new sinonXhr.GlobalActiveXObject(objId); - }; - } - - return FakeXMLHttpRequest; - }; - - sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./core"); - require("../extend"); - require("./event"); - require("../log_error"); - makeApi(sinon); - module.exports = sinon; - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (typeof sinon === "undefined") { - return; - } else { - makeApi(sinon); - } - -})(typeof global !== "undefined" ? global : self); - -/** - * @depend fake_xdomain_request.js - * @depend fake_xml_http_request.js - * @depend ../format.js - * @depend ../log_error.js - */ -/** - * The Sinon "server" mimics a web server that receives requests from - * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, - * both synchronously and asynchronously. To respond synchronuously, canned - * answers have to be provided upfront. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -if (typeof sinon == "undefined") { - var sinon = {}; -} - -(function () { - var push = [].push; - function F() {} - - function create(proto) { - F.prototype = proto; - return new F(); - } - - function responseArray(handler) { - var response = handler; - - if (Object.prototype.toString.call(handler) != "[object Array]") { - response = [200, {}, handler]; - } - - if (typeof response[2] != "string") { - throw new TypeError("Fake server response body should be string, but was " + - typeof response[2]); - } - - return response; - } - - var wloc = typeof window !== "undefined" ? window.location : {}; - var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); - - function matchOne(response, reqMethod, reqUrl) { - var rmeth = response.method; - var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); - var url = response.url; - var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); - - return matchMethod && matchUrl; - } - - function match(response, request) { - var requestUrl = request.url; - - if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { - requestUrl = requestUrl.replace(rCurrLoc, ""); - } - - if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { - if (typeof response.response == "function") { - var ru = response.url; - var args = [request].concat(ru && typeof ru.exec == "function" ? ru.exec(requestUrl).slice(1) : []); - return response.response.apply(response, args); - } - - return true; - } - - return false; - } - - function makeApi(sinon) { - sinon.fakeServer = { - create: function () { - var server = create(this); - if (!sinon.xhr.supportsCORS) { - this.xhr = sinon.useFakeXDomainRequest(); - } else { - this.xhr = sinon.useFakeXMLHttpRequest(); - } - server.requests = []; - - this.xhr.onCreate = function (xhrObj) { - server.addRequest(xhrObj); - }; - - return server; - }, - - addRequest: function addRequest(xhrObj) { - var server = this; - push.call(this.requests, xhrObj); - - xhrObj.onSend = function () { - server.handleRequest(this); - - if (server.respondImmediately) { - server.respond(); - } else if (server.autoRespond && !server.responding) { - setTimeout(function () { - server.responding = false; - server.respond(); - }, server.autoRespondAfter || 10); - - server.responding = true; - } - }; - }, - - getHTTPMethod: function getHTTPMethod(request) { - if (this.fakeHTTPMethods && /post/i.test(request.method)) { - var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); - return !!matches ? matches[1] : request.method; - } - - return request.method; - }, - - handleRequest: function handleRequest(xhr) { - if (xhr.async) { - if (!this.queue) { - this.queue = []; - } - - push.call(this.queue, xhr); - } else { - this.processRequest(xhr); - } - }, - - log: function log(response, request) { - var str; - - str = "Request:\n" + sinon.format(request) + "\n\n"; - str += "Response:\n" + sinon.format(response) + "\n\n"; - - sinon.log(str); - }, - - respondWith: function respondWith(method, url, body) { - if (arguments.length == 1 && typeof method != "function") { - this.response = responseArray(method); - return; - } - - if (!this.responses) { - this.responses = []; - } - - if (arguments.length == 1) { - body = method; - url = method = null; - } - - if (arguments.length == 2) { - body = url; - url = method; - method = null; - } - - push.call(this.responses, { - method: method, - url: url, - response: typeof body == "function" ? body : responseArray(body) - }); - }, - - respond: function respond() { - if (arguments.length > 0) { - this.respondWith.apply(this, arguments); - } - - var queue = this.queue || []; - var requests = queue.splice(0, queue.length); - var request; - - while (request = requests.shift()) { - this.processRequest(request); - } - }, - - processRequest: function processRequest(request) { - try { - if (request.aborted) { - return; - } - - var response = this.response || [404, {}, ""]; - - if (this.responses) { - 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; - } - } - } - - if (request.readyState != 4) { - this.log(response, request); - - request.respond(response[0], response[1], response[2]); - } - } catch (e) { - sinon.logError("Fake server request processing", e); - } - }, - - restore: function restore() { - return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); - } - }; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./core"); - require("./fake_xdomain_request"); - require("./fake_xml_http_request"); - require("../format"); - makeApi(sinon); - module.exports = sinon; - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else { - makeApi(sinon); - } -}()); - -/** - * @depend fake_server.js - * @depend fake_timers.js - */ -/** - * Add-on for sinon.fakeServer that automatically handles a fake timer along with - * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery - * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, - * it polls the object for completion with setInterval. Dispite the direct - * motivation, there is nothing jQuery-specific in this file, so it can be used - * in any environment where the ajax implementation depends on setInterval or - * setTimeout. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function () { - function makeApi(sinon) { - function Server() {} - Server.prototype = sinon.fakeServer; - - sinon.fakeServerWithClock = new Server(); - - sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { - if (xhr.async) { - if (typeof setTimeout.clock == "object") { - this.clock = setTimeout.clock; - } else { - this.clock = sinon.useFakeTimers(); - this.resetClock = true; - } - - if (!this.longestTimeout) { - var clockSetTimeout = this.clock.setTimeout; - var clockSetInterval = this.clock.setInterval; - var server = this; - - this.clock.setTimeout = function (fn, timeout) { - server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); - - return clockSetTimeout.apply(this, arguments); - }; - - this.clock.setInterval = function (fn, timeout) { - server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); - - return clockSetInterval.apply(this, arguments); - }; - } - } - - return sinon.fakeServer.addRequest.call(this, xhr); - }; - - sinon.fakeServerWithClock.respond = function respond() { - var returnVal = sinon.fakeServer.respond.apply(this, arguments); - - if (this.clock) { - this.clock.tick(this.longestTimeout || 0); - this.longestTimeout = 0; - - if (this.resetClock) { - this.clock.restore(); - this.resetClock = false; - } - } - - return returnVal; - }; - - sinon.fakeServerWithClock.restore = function restore() { - if (this.clock) { - this.clock.restore(); - } - - return sinon.fakeServer.restore.apply(this, arguments); - }; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require) { - var sinon = require("./core"); - require("./fake_server"); - require("./fake_timers"); - makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require); - } else { - makeApi(sinon); - } -}()); - -/** - * @depend util/core.js - * @depend extend.js - * @depend collection.js - * @depend util/fake_timers.js - * @depend util/fake_server_with_clock.js - */ -/** - * Manages fake collections as well as fake utilities such as Sinon's - * timers and fake XHR implementation in one convenient object. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function () { - function makeApi(sinon) { - var push = [].push; - - function exposeValue(sandbox, config, key, value) { - if (!value) { - return; - } - - if (config.injectInto && !(key in config.injectInto)) { - config.injectInto[key] = value; - sandbox.injectedKeys.push(key); - } else { - push.call(sandbox.args, value); - } - } - - function prepareSandboxFromConfig(config) { - var sandbox = sinon.create(sinon.sandbox); - - if (config.useFakeServer) { - if (typeof config.useFakeServer == "object") { - sandbox.serverPrototype = config.useFakeServer; - } - - sandbox.useFakeServer(); - } - - if (config.useFakeTimers) { - if (typeof config.useFakeTimers == "object") { - sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); - } else { - sandbox.useFakeTimers(); - } - } - - return sandbox; - } - - sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { - useFakeTimers: function useFakeTimers() { - this.clock = sinon.useFakeTimers.apply(sinon, arguments); - - return this.add(this.clock); - }, - - serverPrototype: sinon.fakeServer, - - useFakeServer: function useFakeServer() { - var proto = this.serverPrototype || sinon.fakeServer; - - if (!proto || !proto.create) { - return null; - } - - this.server = proto.create(); - return this.add(this.server); - }, - - inject: function (obj) { - sinon.collection.inject.call(this, obj); - - if (this.clock) { - obj.clock = this.clock; - } - - if (this.server) { - obj.server = this.server; - obj.requests = this.server.requests; - } - - obj.match = sinon.match; - - 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); - } - - var sandbox = prepareSandboxFromConfig(config); - sandbox.args = sandbox.args || []; - sandbox.injectedKeys = []; - sandbox.injectInto = config.injectInto; - var prop, value, exposed = sandbox.inject({}); - - if (config.properties) { - for (var i = 0, l = config.properties.length; i < l; i++) { - prop = config.properties[i]; - value = exposed[prop] || prop == "sandbox" && sandbox; - exposeValue(sandbox, config, prop, value); - } - } else { - exposeValue(sandbox, config, "sandbox", value); - } - - return sandbox; - }, - - match: sinon.match - }); - - sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; - - return sinon.sandbox; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./extend"); - require("./util/fake_server_with_clock"); - require("./util/fake_timers"); - require("./collection"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}()); - -/** - * @depend util/core.js - * @depend sandbox.js - */ -/** - * Test function, sandboxes fakes - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - function makeApi(sinon) { - var slice = Array.prototype.slice; - - function test(callback) { - var type = typeof callback; - - if (type != "function") { - throw new TypeError("sinon.test needs to wrap a test function, got " + type); - } - - function sinonSandboxedTest() { - var config = sinon.getConfig(sinon.config); - config.injectInto = config.injectIntoThis && this || config.injectInto; - var sandbox = sinon.sandbox.create(config); - var args = slice.call(arguments); - var oldDone = args.length && args[args.length - 1]; - var exception, result; - - if (typeof oldDone == "function") { - args[args.length - 1] = function sinonDone(result) { - if (result) { - sandbox.restore(); - throw exception; - } else { - sandbox.verifyAndRestore(); - } - oldDone(result); - }; - } - - try { - result = callback.apply(this, args.concat(sandbox.args)); - } catch (e) { - exception = e; - } - - if (typeof oldDone != "function") { - if (typeof exception !== "undefined") { - sandbox.restore(); - throw exception; - } else { - sandbox.verifyAndRestore(); - } - } - - return result; - } - - if (callback.length) { - return function sinonAsyncSandboxedTest(callback) { - return sinonSandboxedTest.apply(this, arguments); - }; - } - - return sinonSandboxedTest; - } - - test.config = { - injectIntoThis: true, - injectInto: null, - properties: ["spy", "stub", "mock", "clock", "server", "requests"], - useFakeTimers: true, - useFakeServer: true - }; - - sinon.test = test; - return test; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./sandbox"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (sinon) { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend util/core.js - * @depend test.js - */ -/** - * Test case, sandboxes all test functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon) { - function createTest(property, setUp, tearDown) { - return function () { - if (setUp) { - setUp.apply(this, arguments); - } - - var exception, result; - - try { - result = property.apply(this, arguments); - } catch (e) { - exception = e; - } - - if (tearDown) { - tearDown.apply(this, arguments); - } - - if (exception) { - throw exception; - } - - return result; - }; - } - - function makeApi(sinon) { - function testCase(tests, prefix) { - if (!tests || typeof tests != "object") { - throw new TypeError("sinon.testCase needs an object with test functions"); - } - - prefix = prefix || "test"; - var rPrefix = new RegExp("^" + prefix); - var methods = {}, testName, property, method; - var setUp = tests.setUp; - var tearDown = tests.tearDown; - - for (testName in tests) { - if (tests.hasOwnProperty(testName) && !/^(setUp|tearDown)$/.test(testName)) { - property = tests[testName]; - - if (typeof property == "function" && rPrefix.test(testName)) { - method = property; - - if (setUp || tearDown) { - method = createTest(property, setUp, tearDown); - } - - methods[testName] = sinon.test(method); - } else { - methods[testName] = tests[testName]; - } - } - } - - return methods; - } - - sinon.testCase = testCase; - return testCase; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./test"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend times_in_words.js - * @depend util/core.js - * @depend match.js - * @depend format.js - */ -/** - * Assertions matching the test spy retrieval interface. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2013 Christian Johansen - */ - -(function (sinon, global) { - var slice = Array.prototype.slice; - - function makeApi(sinon) { - var assert; - - function verifyIsStub() { - var method; - - for (var i = 0, l = arguments.length; i < l; ++i) { - method = arguments[i]; - - if (!method) { - assert.fail("fake is not a spy"); - } - - if (method.proxy && method.proxy.isSinonProxy) { - verifyIsStub(method.proxy); - } else { - if (typeof method != "function") { - assert.fail(method + " is not a function"); - } - - if (typeof method.getCall != "function") { - assert.fail(method + " is not stubbed"); - } - } - - } - } - - function failAssertion(object, msg) { - object = object || global; - var failMethod = object.fail || assert.fail; - failMethod.call(object, msg); - } - - function mirrorPropAsAssertion(name, method, message) { - if (arguments.length == 2) { - message = method; - method = name; - } - - assert[name] = function (fake) { - verifyIsStub(fake); - - var args = slice.call(arguments, 1); - var failed = false; - - if (typeof method == "function") { - failed = !method(fake); - } else { - failed = typeof fake[method] == "function" ? - !fake[method].apply(fake, args) : !fake[method]; - } - - if (failed) { - failAssertion(this, (fake.printf || fake.proxy.printf).apply(fake, [message].concat(args))); - } else { - assert.pass(name); - } - }; - } - - function exposedName(prefix, prop) { - return !prefix || /^fail/.test(prop) ? prop : - prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); - } - - assert = { - failException: "AssertError", - - fail: function fail(message) { - var error = new Error(message); - error.name = this.failException || assert.failException; - - throw error; - }, - - pass: function pass(assertion) {}, - - callOrder: function assertCallOrder() { - verifyIsStub.apply(null, arguments); - var expected = "", actual = ""; - - if (!sinon.calledInOrder(arguments)) { - try { - expected = [].join.call(arguments, ", "); - var calls = slice.call(arguments); - var i = calls.length; - while (i) { - if (!calls[--i].called) { - calls.splice(i, 1); - } - } - actual = sinon.orderByFirstCall(calls).join(", "); - } catch (e) { - // If this fails, we'll just fall back to the blank string - } - - failAssertion(this, "expected " + expected + " to be " + - "called in order but were called as " + actual); - } else { - assert.pass("callOrder"); - } - }, - - callCount: function assertCallCount(method, count) { - verifyIsStub(method); - - if (method.callCount != count) { - var msg = "expected %n to be called " + sinon.timesInWords(count) + - " but was called %c%C"; - failAssertion(this, method.printf(msg)); - } else { - assert.pass("callCount"); - } - }, - - expose: function expose(target, options) { - if (!target) { - throw new TypeError("target is null or undefined"); - } - - var o = options || {}; - var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix; - var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail; - - for (var method in this) { - if (method != "expose" && (includeFail || !/^(fail)/.test(method))) { - target[exposedName(prefix, method)] = this[method]; - } - } - - 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")); - } - } - }; - - mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); - mirrorPropAsAssertion("notCalled", function (spy) { - return !spy.called; - }, "expected %n to not have been called but was called %c%C"); - mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C"); - mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); - mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); - mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); - mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); - mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); - mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new"); - mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C"); - mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C"); - mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); - mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C"); - mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); - mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); - mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C"); - mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C"); - mirrorPropAsAssertion("threw", "%n did not throw exception%C"); - mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); - - sinon.assert = assert; - return assert; - } - - var isNode = typeof module !== "undefined" && module.exports && typeof require == "function"; - var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd; - - function loadDependencies(require, exports, module) { - var sinon = require("./util/core"); - require("./match"); - require("./format"); - module.exports = makeApi(sinon); - } - - if (isAMD) { - define(loadDependencies); - } else if (isNode) { - loadDependencies(require, module.exports, module); - } else if (!sinon) { - return; - } else { - makeApi(sinon); - } - -}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global)); - - return sinon; -})); diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js index f9bdeae0d64..77958488df7 100644 --- a/core/js/tests/specHelper.js +++ b/core/js/tests/specHelper.js @@ -1,23 +1,8 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2014 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2014 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ /** * Simulate the variables that are normally set by PHP code @@ -86,20 +71,36 @@ window.firstDay = 0; // setup dummy webroots /* jshint camelcase: false */ window.oc_debug = true; -window.oc_isadmin = false; -// FIXME: oc_webroot is supposed to be only the path!!! -window.oc_webroot = location.href + '/'; -window.oc_appswebroots = { - "files": window.oc_webroot + '/apps/files/' + +// Mock @nextcloud/capabilities +window._oc_capabilities = { + files_sharing: { + api_enabled: true + } +} + +// FIXME: OC.webroot is supposed to be only the path!!! +window._oc_webroot = location.href + '/'; +window._oc_appswebroots = { + "files": window.webroot + '/apps/files/', + "files_sharing": window.webroot + '/apps/files_sharing/' }; -window.oc_config = { + +window.OC ??= {}; + +OC.config = { session_lifetime: 600 * 1000, - session_keepalive: false + session_keepalive: false, + blacklist_files_regex: '\.(part|filepart)$', }; -window.oc_appconfig = { +OC.appConfig = { core: {} }; -window.oc_defaults = {}; +OC.theme = { + docPlaceholderUrl: 'https://docs.example.org/PLACEHOLDER' +}; +window.oc_capabilities = { +} /* jshint camelcase: true */ @@ -112,6 +113,11 @@ window.Snap.prototype = { }; window.isPhantom = /phantom/i.test(navigator.userAgent); +document.documentElement.lang = navigator.language; +const el = document.createElement('input'); +el.id = 'initial-state-core-config'; +el.value = btoa(JSON.stringify(window.OC.config)) +document.body.append(el); // global setup for all tests (function setupTests() { @@ -177,6 +183,10 @@ window.isPhantom = /phantom/i.test(navigator.userAgent); delete($.fn.select2); ajaxErrorStub.restore(); + + // reset pop state handlers + OC.Util.History._handlers = []; + }); })(); diff --git a/core/js/tests/specs/appsSpec.js b/core/js/tests/specs/appsSpec.js deleted file mode 100644 index 536d41c7f10..00000000000 --- a/core/js/tests/specs/appsSpec.js +++ /dev/null @@ -1,48 +0,0 @@ -/** -* ownCloud -* -* @author Vincent Petry -* @copyright 2015 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ - -describe('Apps base tests', function() { - describe('Sidebar utility functions', function() { - beforeEach(function() { - $('#testArea').append('<div id="app-content">Content</div><div id="app-sidebar">The sidebar</div>'); - }); - it('shows sidebar', function() { - var $el = $('#app-sidebar'); - OC.Apps.showAppSidebar(); - expect($el.hasClass('disappear')).toEqual(false); - }); - it('hides sidebar', function() { - var $el = $('#app-sidebar'); - OC.Apps.showAppSidebar(); - OC.Apps.hideAppSidebar(); - expect($el.hasClass('disappear')).toEqual(true); - }); - it('triggers appresize event when visibility changed', function() { - var eventStub = sinon.stub(); - $('#app-content').on('appresized', eventStub); - OC.Apps.showAppSidebar(); - expect(eventStub.calledOnce).toEqual(true); - OC.Apps.hideAppSidebar(); - expect(eventStub.calledTwice).toEqual(true); - }); - }); -}); - diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index f18ecbc1a44..3cbd7623a47 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -1,25 +1,19 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2014 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2014 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ describe('Core base tests', function() { + var debounceStub + beforeEach(function() { + debounceStub = sinon.stub(_, 'debounce').callsFake(function(callback) { + return function() { + // defer instead of debounce, to make it work with clock + _.defer(callback); + }; + }); + }); afterEach(function() { // many tests call window.initCore so need to unregister global events // ideally in the future we'll need a window.unloadCore() function @@ -28,173 +22,21 @@ describe('Core base tests', function() { $(document).off('beforeunload.main'); OC._userIsNavigatingAway = false; OC._reloadCalled = false; + debounceStub.restore(); }); describe('Base values', function() { it('Sets webroots', function() { - expect(OC.webroot).toBeDefined(); - expect(OC.appswebroots).toBeDefined(); - }); - }); - describe('basename', function() { - it('Returns the nothing if no file name given', function() { - expect(OC.basename('')).toEqual(''); - }); - it('Returns the nothing if dir is root', function() { - expect(OC.basename('/')).toEqual(''); - }); - it('Returns the same name if no path given', function() { - expect(OC.basename('some name.txt')).toEqual('some name.txt'); - }); - it('Returns the base name if root path given', function() { - expect(OC.basename('/some name.txt')).toEqual('some name.txt'); - }); - it('Returns the base name if double root path given', function() { - expect(OC.basename('//some name.txt')).toEqual('some name.txt'); - }); - it('Returns the base name if subdir given without root', function() { - expect(OC.basename('subdir/some name.txt')).toEqual('some name.txt'); - }); - it('Returns the base name if subdir given with root', function() { - expect(OC.basename('/subdir/some name.txt')).toEqual('some name.txt'); - }); - it('Returns the base name if subdir given with double root', function() { - expect(OC.basename('//subdir/some name.txt')).toEqual('some name.txt'); - }); - it('Returns the base name if subdir has dot', function() { - expect(OC.basename('/subdir.dat/some name.txt')).toEqual('some name.txt'); - }); - it('Returns dot if file name is dot', function() { - expect(OC.basename('/subdir/.')).toEqual('.'); - }); - // TODO: fix the source to make it work like PHP's basename - it('Returns the dir itself if no file name given', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.basename('subdir/')).toEqual('subdir'); - expect(OC.basename('subdir/')).toEqual(''); - }); - it('Returns the dir itself if no file name given with root', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.basename('/subdir/')).toEqual('subdir'); - expect(OC.basename('/subdir/')).toEqual(''); - }); - }); - describe('dirname', function() { - it('Returns the nothing if no file name given', function() { - expect(OC.dirname('')).toEqual(''); - }); - it('Returns the root if dir is root', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.dirname('/')).toEqual('/'); - expect(OC.dirname('/')).toEqual(''); - }); - it('Returns the root if dir is double root', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.dirname('//')).toEqual('/'); - expect(OC.dirname('//')).toEqual('/'); // oh no... - }); - it('Returns dot if dir is dot', function() { - expect(OC.dirname('.')).toEqual('.'); - }); - it('Returns dot if no root given', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.dirname('some dir')).toEqual('.'); - expect(OC.dirname('some dir')).toEqual('some dir'); // oh no... - }); - it('Returns the dir name if file name and root path given', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.dirname('/some name.txt')).toEqual('/'); - expect(OC.dirname('/some name.txt')).toEqual(''); - }); - it('Returns the dir name if double root path given', function() { - expect(OC.dirname('//some name.txt')).toEqual('/'); // how lucky... - }); - it('Returns the dir name if subdir given without root', function() { - expect(OC.dirname('subdir/some name.txt')).toEqual('subdir'); - }); - it('Returns the dir name if subdir given with root', function() { - expect(OC.dirname('/subdir/some name.txt')).toEqual('/subdir'); - }); - it('Returns the dir name if subdir given with double root', function() { - // TODO: fix the source to make it work like PHP's dirname - // expect(OC.dirname('//subdir/some name.txt')).toEqual('/subdir'); - expect(OC.dirname('//subdir/some name.txt')).toEqual('//subdir'); // oh... - }); - it('Returns the dir name if subdir has dot', function() { - expect(OC.dirname('/subdir.dat/some name.txt')).toEqual('/subdir.dat'); - }); - it('Returns the dir name if file name is dot', function() { - expect(OC.dirname('/subdir/.')).toEqual('/subdir'); - }); - it('Returns the dir name if no file name given', function() { - expect(OC.dirname('subdir/')).toEqual('subdir'); - }); - it('Returns the dir name if no file name given with root', function() { - expect(OC.dirname('/subdir/')).toEqual('/subdir'); - }); - }); - describe('escapeHTML', function() { - it('Returns nothing if no string was given', function() { - expect(escapeHTML('')).toEqual(''); - }); - it('Returns a sanitized string if a string containing HTML is given', function() { - expect(escapeHTML('There needs to be a <script>alert(\"Unit\" + \'test\')</script> for it!')).toEqual('There needs to be a <script>alert("Unit" + 'test')</script> for it!'); - }); - it('Returns the string without modification if no potentially dangerous character is passed.', function() { - expect(escapeHTML('This is a good string without HTML.')).toEqual('This is a good string without HTML.'); - }); - }); - describe('joinPaths', function() { - it('returns empty string with no or empty arguments', function() { - expect(OC.joinPaths()).toEqual(''); - expect(OC.joinPaths('')).toEqual(''); - expect(OC.joinPaths('', '')).toEqual(''); - }); - it('returns joined path sections', function() { - expect(OC.joinPaths('abc')).toEqual('abc'); - expect(OC.joinPaths('abc', 'def')).toEqual('abc/def'); - expect(OC.joinPaths('abc', 'def', 'ghi')).toEqual('abc/def/ghi'); - }); - it('keeps leading slashes', function() { - expect(OC.joinPaths('/abc')).toEqual('/abc'); - expect(OC.joinPaths('/abc', '')).toEqual('/abc'); - expect(OC.joinPaths('', '/abc')).toEqual('/abc'); - expect(OC.joinPaths('/abc', 'def')).toEqual('/abc/def'); - expect(OC.joinPaths('/abc', 'def', 'ghi')).toEqual('/abc/def/ghi'); - }); - it('keeps trailing slashes', function() { - expect(OC.joinPaths('', 'abc/')).toEqual('abc/'); - expect(OC.joinPaths('abc/')).toEqual('abc/'); - expect(OC.joinPaths('abc/', '')).toEqual('abc/'); - expect(OC.joinPaths('abc', 'def/')).toEqual('abc/def/'); - expect(OC.joinPaths('abc', 'def', 'ghi/')).toEqual('abc/def/ghi/'); - }); - it('splits paths in specified strings and discards extra slashes', function() { - expect(OC.joinPaths('//abc//')).toEqual('/abc/'); - expect(OC.joinPaths('//abc//def//')).toEqual('/abc/def/'); - expect(OC.joinPaths('//abc//', '//def//')).toEqual('/abc/def/'); - expect(OC.joinPaths('//abc//', '//def//', '//ghi//')).toEqual('/abc/def/ghi/'); - expect(OC.joinPaths('//abc//def//', '//ghi//jkl/mno/', '//pqr//')) - .toEqual('/abc/def/ghi/jkl/mno/pqr/'); - expect(OC.joinPaths('/abc', '/def')).toEqual('/abc/def'); - expect(OC.joinPaths('/abc/', '/def')).toEqual('/abc/def'); - expect(OC.joinPaths('/abc/', 'def')).toEqual('/abc/def'); - }); - it('discards empty sections', function() { - expect(OC.joinPaths('abc', '', 'def')).toEqual('abc/def'); - }); - it('returns root if only slashes', function() { - expect(OC.joinPaths('//')).toEqual('/'); - expect(OC.joinPaths('/', '/')).toEqual('/'); - expect(OC.joinPaths('/', '//', '/')).toEqual('/'); + expect(OC.getRootPath()).toBeDefined(); + expect(window._oc_appswebroots).toBeDefined(); }); }); describe('filePath', function() { beforeEach(function() { - OC.webroot = 'http://localhost'; - OC.appswebroots['files'] = OC.webroot + '/apps3/files'; + window._oc_webroot = 'http://localhost'; + window._oc_appswebroots.files = OC.getRootPath() + '/apps3/files'; }); afterEach(function() { - delete OC.appswebroots['files']; + delete window._oc_appswebroots.files; }); it('Uses a direct link for css and images,' , function() { @@ -210,43 +52,33 @@ describe('Core base tests', function() { }); describe('Link functions', function() { var TESTAPP = 'testapp'; - var TESTAPP_ROOT = OC.webroot + '/appsx/testapp'; + var TESTAPP_ROOT = OC.getRootPath() + '/appsx/testapp'; beforeEach(function() { - OC.appswebroots[TESTAPP] = TESTAPP_ROOT; + window._oc_appswebroots[TESTAPP] = TESTAPP_ROOT; }); afterEach(function() { // restore original array - delete OC.appswebroots[TESTAPP]; + delete window._oc_appswebroots[TESTAPP]; }); it('Generates correct links for core apps', function() { - expect(OC.linkTo('core', 'somefile.php')).toEqual(OC.webroot + '/core/somefile.php'); - expect(OC.linkTo('admin', 'somefile.php')).toEqual(OC.webroot + '/admin/somefile.php'); + expect(OC.linkTo('core', 'somefile.php')).toEqual(OC.getRootPath() + '/core/somefile.php'); + expect(OC.linkTo('admin', 'somefile.php')).toEqual(OC.getRootPath() + '/admin/somefile.php'); }); it('Generates correct links for regular apps', function() { - expect(OC.linkTo(TESTAPP, 'somefile.php')).toEqual(OC.webroot + '/index.php/apps/' + TESTAPP + '/somefile.php'); + expect(OC.linkTo(TESTAPP, 'somefile.php')).toEqual(OC.getRootPath() + '/index.php/apps/' + TESTAPP + '/somefile.php'); }); it('Generates correct remote links', function() { - expect(OC.linkToRemote('webdav')).toEqual(window.location.protocol + '//' + window.location.host + OC.webroot + '/remote.php/webdav'); + expect(OC.linkToRemote('webdav')).toEqual(window.location.protocol + '//' + window.location.host + OC.getRootPath() + '/remote.php/webdav'); }); describe('Images', function() { it('Generates image path with given extension', function() { - var svgSupportStub = sinon.stub(OC.Util, 'hasSVGSupport', function() { return true; }); - expect(OC.imagePath('core', 'somefile.jpg')).toEqual(OC.webroot + '/core/img/somefile.jpg'); + expect(OC.imagePath('core', 'somefile.jpg')).toEqual(OC.getRootPath() + '/core/img/somefile.jpg'); expect(OC.imagePath(TESTAPP, 'somefile.jpg')).toEqual(TESTAPP_ROOT + '/img/somefile.jpg'); - svgSupportStub.restore(); }); - it('Generates image path with svg extension when svg support exists', function() { - var svgSupportStub = sinon.stub(OC.Util, 'hasSVGSupport', function() { return true; }); - expect(OC.imagePath('core', 'somefile')).toEqual(OC.webroot + '/core/img/somefile.svg'); + it('Generates image path with svg extension', function() { + expect(OC.imagePath('core', 'somefile')).toEqual(OC.getRootPath() + '/core/img/somefile.svg'); expect(OC.imagePath(TESTAPP, 'somefile')).toEqual(TESTAPP_ROOT + '/img/somefile.svg'); - svgSupportStub.restore(); - }); - it('Generates image path with png ext when svg support is not available', function() { - var svgSupportStub = sinon.stub(OC.Util, 'hasSVGSupport', function() { return false; }); - expect(OC.imagePath('core', 'somefile')).toEqual(OC.webroot + '/core/img/somefile.png'); - expect(OC.imagePath(TESTAPP, 'somefile')).toEqual(TESTAPP_ROOT + '/img/somefile.png'); - svgSupportStub.restore(); }); }); }); @@ -287,96 +119,6 @@ describe('Core base tests', function() { })).toEqual('number=123'); }); }); - describe('Session heartbeat', function() { - var clock, - oldConfig, - routeStub, - counter; - - beforeEach(function() { - clock = sinon.useFakeTimers(); - oldConfig = window.oc_config; - routeStub = sinon.stub(OC, 'generateUrl').returns('/heartbeat'); - counter = 0; - - fakeServer.autoRespond = true; - fakeServer.autoRespondAfter = 0; - fakeServer.respondWith(/\/heartbeat/, function(xhr) { - counter++; - xhr.respond(200, {'Content-Type': 'application/json'}, '{}'); - }); - }); - afterEach(function() { - clock.restore(); - /* jshint camelcase: false */ - window.oc_config = oldConfig; - routeStub.restore(); - $(document).off('ajaxError'); - }); - it('sends heartbeat half the session lifetime when heartbeat enabled', function() { - /* jshint camelcase: false */ - window.oc_config = { - session_keepalive: true, - session_lifetime: 300 - }; - window.initCore(); - expect(routeStub.calledWith('/heartbeat')).toEqual(true); - - expect(counter).toEqual(0); - - // less than half, still nothing - clock.tick(100 * 1000); - expect(counter).toEqual(0); - - // reach past half (160), one call - clock.tick(55 * 1000); - expect(counter).toEqual(1); - - // almost there to the next, still one - clock.tick(140 * 1000); - expect(counter).toEqual(1); - - // past it, second call - clock.tick(20 * 1000); - expect(counter).toEqual(2); - }); - it('does no send heartbeat when heartbeat disabled', function() { - /* jshint camelcase: false */ - window.oc_config = { - session_keepalive: false, - session_lifetime: 300 - }; - window.initCore(); - expect(routeStub.notCalled).toEqual(true); - - expect(counter).toEqual(0); - - clock.tick(1000000); - - // still nothing - expect(counter).toEqual(0); - }); - it('limits the heartbeat between one minute and one day', function() { - /* jshint camelcase: false */ - var setIntervalStub = sinon.stub(window, 'setInterval'); - window.oc_config = { - session_keepalive: true, - session_lifetime: 5 - }; - window.initCore(); - expect(setIntervalStub.getCall(0).args[1]).toEqual(60 * 1000); - setIntervalStub.reset(); - - window.oc_config = { - session_keepalive: true, - session_lifetime: 48 * 3600 - }; - window.initCore(); - expect(setIntervalStub.getCall(0).args[1]).toEqual(24 * 3600 * 1000); - - setIntervalStub.restore(); - }); - }); describe('Parse query string', function() { it('Parses query string from full URL', function() { var query = OC.parseQueryString('http://localhost/stuff.php?q=a&b=x'); @@ -445,117 +187,74 @@ describe('Core base tests', function() { }); describe('Generate Url', function() { it('returns absolute urls', function() { - expect(OC.generateUrl('heartbeat')).toEqual(OC.webroot + '/index.php/heartbeat'); - expect(OC.generateUrl('/heartbeat')).toEqual(OC.webroot + '/index.php/heartbeat'); + expect(OC.generateUrl('csrftoken')).toEqual(OC.getRootPath() + '/index.php/csrftoken'); + expect(OC.generateUrl('/csrftoken')).toEqual(OC.getRootPath() + '/index.php/csrftoken'); }); it('substitutes parameters which are escaped by default', function() { - expect(OC.generateUrl('apps/files/download/{file}', {file: '<">ImAnUnescapedString/!'})).toEqual(OC.webroot + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!'); + expect(OC.generateUrl('apps/files/download/{file}', {file: '<">ImAnUnescapedString/!'})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!'); }); it('substitutes parameters which can also be unescaped via option flag', function() { - expect(OC.generateUrl('apps/files/download/{file}', {file: 'subfolder/Welcome.txt'}, {escape: false})).toEqual(OC.webroot + '/index.php/apps/files/download/subfolder/Welcome.txt'); + expect(OC.generateUrl('apps/files/download/{file}', {file: 'subfolder/Welcome.txt'}, {escape: false})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/subfolder/Welcome.txt'); }); it('substitutes multiple parameters which are escaped by default', function() { - expect(OC.generateUrl('apps/files/download/{file}/{id}', {file: '<">ImAnUnescapedString/!', id: 5})).toEqual(OC.webroot + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!/5'); + expect(OC.generateUrl('apps/files/download/{file}/{id}', {file: '<">ImAnUnescapedString/!', id: 5})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!/5'); }); it('substitutes multiple parameters which can also be unescaped via option flag', function() { - expect(OC.generateUrl('apps/files/download/{file}/{id}', {file: 'subfolder/Welcome.txt', id: 5}, {escape: false})).toEqual(OC.webroot + '/index.php/apps/files/download/subfolder/Welcome.txt/5'); + expect(OC.generateUrl('apps/files/download/{file}/{id}', {file: 'subfolder/Welcome.txt', id: 5}, {escape: false})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/subfolder/Welcome.txt/5'); }); it('doesnt error out with no params provided', function () { - expect(OC.generateUrl('apps/files/download{file}')).toEqual(OC.webroot + '/index.php/apps/files/download%7Bfile%7D'); - }); - }); - describe('Main menu mobile toggle', function() { - var clock; - var $toggle; - var $navigation; - - beforeEach(function() { - clock = sinon.useFakeTimers(); - $('#testArea').append('<div id="header">' + - '<a class="menutoggle header-appname-container" href="#">' + - '<h1 class="header-appname"></h1>' + - '<div class="icon-caret"></div>' + - '</a>' + - '</div>' + - '<div id="navigation"></div>'); - $toggle = $('#header').find('.menutoggle'); - $navigation = $('#navigation'); - }); - afterEach(function() { - clock.restore(); - $(document).off('ajaxError'); - }); - it('Sets up menu toggle', function() { - window.initCore(); - expect($navigation.hasClass('menu')).toEqual(true); - }); - it('Clicking menu toggle toggles navigation in', function() { - window.initCore(); - $navigation.hide(); // normally done through media query triggered CSS - expect($navigation.is(':visible')).toEqual(false); - $toggle.click(); - clock.tick(1 * 1000); - expect($navigation.is(':visible')).toEqual(true); - $toggle.click(); - clock.tick(1 * 1000); - expect($navigation.is(':visible')).toEqual(false); - }); - }); - describe('SVG extension replacement', function() { - var svgSupportStub; - - beforeEach(function() { - svgSupportStub = sinon.stub(OC.Util, 'hasSVGSupport'); - }); - afterEach(function() { - svgSupportStub.restore(); - }); - it('does not replace svg extension with png when SVG is supported', function() { - svgSupportStub.returns(true); - expect( - OC.Util.replaceSVGIcon('/path/to/myicon.svg?someargs=1') - ).toEqual( - '/path/to/myicon.svg?someargs=1' - ); - }); - it('replaces svg extension with png when SVG not supported', function() { - svgSupportStub.returns(false); - expect( - OC.Util.replaceSVGIcon('/path/to/myicon.svg?someargs=1') - ).toEqual( - '/path/to/myicon.png?someargs=1' - ); + expect(OC.generateUrl('apps/files/download{file}')).toEqual(OC.getRootPath() + '/index.php/apps/files/download%7Bfile%7D'); }); }); describe('Util', function() { - describe('humanFileSize', function() { - it('renders file sizes with the correct unit', function() { + describe('computerFileSize', function() { + it('correctly parses file sizes from a human readable formated string', function() { var data = [ - [0, '0 B'], - ["0", '0 B'], - ["A", 'NaN B'], - [125, '125 B'], - [128000, '125 KB'], - [128000000, '122.1 MB'], - [128000000000, '119.2 GB'], - [128000000000000, '116.4 TB'] + ['125', 125], + ['125.25', 125], + ['125.25B', 125], + ['125.25 B', 125], + ['0 B', 0], + ['99999999999999999999999999999999999999999999 B', 99999999999999999999999999999999999999999999], + ['0 MB', 0], + ['0 kB', 0], + ['0kB', 0], + ['125 B', 125], + ['125b', 125], + ['125 KB', 128000], + ['125kb', 128000], + ['122.1 MB', 128031130], + ['122.1mb', 128031130], + ['119.2 GB', 127990025421], + ['119.2gb', 127990025421], + ['116.4 TB', 127983153473126], + ['116.4tb', 127983153473126], + ['8776656778888777655.4tb', 9.650036181387265e+30], + [1234, null], + [-1234, null], + ['-1234 B', null], + ['B', null], + ['40/0', null], + ['40,30 kb', null], + [' 122.1 MB ', 128031130], + ['122.1 MB ', 128031130], + [' 122.1 MB ', 128031130], + [' 122.1 MB ', 128031130], + ['122.1 MB ', 128031130], + [' 125', 125], + [' 125 ', 125], ]; for (var i = 0; i < data.length; i++) { - expect(OC.Util.humanFileSize(data[i][0])).toEqual(data[i][1]); + expect(OC.Util.computerFileSize(data[i][0])).toEqual(data[i][1]); } }); - it('renders file sizes with the correct unit for small sizes', function() { - var data = [ - [0, '0 KB'], - [125, '< 1 KB'], - [128000, '125 KB'], - [128000000, '122.1 MB'], - [128000000000, '119.2 GB'], - [128000000000000, '116.4 TB'] - ]; - for (var i = 0; i < data.length; i++) { - expect(OC.Util.humanFileSize(data[i][0], true)).toEqual(data[i][1]); - } + it('returns null if the parameter is not a string', function() { + expect(OC.Util.computerFileSize(NaN)).toEqual(null); + expect(OC.Util.computerFileSize(125)).toEqual(null); + }); + it('returns null if the string is unparsable', function() { + expect(OC.Util.computerFileSize('')).toEqual(null); + expect(OC.Util.computerFileSize('foobar')).toEqual(null); }); }); describe('stripTime', function() { @@ -573,7 +272,7 @@ describe('Core base tests', function() { // to make sure they run. var cit = window.isPhantom?xit:it; - // must provide the same results as \OC_Util::naturalSortCompare + // must provide the same results as \OCP\Util::naturalSortCompare it('sorts alphabetically', function() { var a = [ 'def', @@ -756,250 +455,693 @@ describe('Core base tests', function() { }); }); describe('Notifications', function() { - var showSpy; var showHtmlSpy; - var hideSpy; var clock; + /** + * Returns the HTML or plain text of the given notification row. + * + * This is needed to ignore the close button that is added to the + * notification row after the text. + */ + var getNotificationText = function($node) { + return $node.contents()[0].outerHTML || + $node.contents()[0].nodeValue; + } + beforeEach(function() { clock = sinon.useFakeTimers(); - showSpy = sinon.spy(OC.Notification, 'show'); - showHtmlSpy = sinon.spy(OC.Notification, 'showHtml'); - hideSpy = sinon.spy(OC.Notification, 'hide'); - - $('#testArea').append('<div id="notification"></div>'); }); afterEach(function() { - showSpy.restore(); - showHtmlSpy.restore(); - hideSpy.restore(); // jump past animations clock.tick(10000); clock.restore(); + $('body .toastify').remove(); }); describe('showTemporary', function() { it('shows a plain text notification with default timeout', function() { - var $row = OC.Notification.showTemporary('My notification test'); - - expect(showSpy.calledOnce).toEqual(true); - expect(showSpy.firstCall.args[0]).toEqual('My notification test'); - expect(showSpy.firstCall.args[1]).toEqual({isHTML: false, timeout: 7}); + OC.Notification.showTemporary('My notification test'); - expect($row).toBeDefined(); - expect($row.text()).toEqual('My notification test'); + var $row = $('body .toastify'); + expect($row.length).toEqual(1); + expect(getNotificationText($row)).toEqual('My notification test'); }); it('shows a HTML notification with default timeout', function() { - var $row = OC.Notification.showTemporary('<a>My notification test</a>', { isHTML: true }); - - expect(showSpy.notCalled).toEqual(true); - expect(showHtmlSpy.calledOnce).toEqual(true); - expect(showHtmlSpy.firstCall.args[0]).toEqual('<a>My notification test</a>'); - expect(showHtmlSpy.firstCall.args[1]).toEqual({isHTML: true, timeout: 7}); + OC.Notification.showTemporary('<a>My notification test</a>', { isHTML: true }); - expect($row).toBeDefined(); - expect($row.text()).toEqual('My notification test'); + var $row = $('body .toastify'); + expect($row.length).toEqual(1); + expect(getNotificationText($row)).toEqual('<a>My notification test</a>'); }); it('hides itself after 7 seconds', function() { - var $row = OC.Notification.showTemporary(''); + OC.Notification.showTemporary(''); + + var $row = $('body .toastify'); + expect($row.length).toEqual(1); + + // travel in time +7000 milliseconds + clock.tick(7500); + + $row = $('body .toastify'); + expect($row.length).toEqual(0); + }); + it('hides itself after a given time', function() { + OC.Notification.showTemporary('', {timeout: 10000}); + + var $row = $('body .toastify'); + expect($row.length).toEqual(1); // travel in time +7000 milliseconds - clock.tick(7000); + clock.tick(7500); + + $row = $('body .toastify'); + expect($row.length).toEqual(1); - expect(hideSpy.calledOnce).toEqual(true); - expect(hideSpy.firstCall.args[0]).toEqual($row); + // travel in time another 4000 milliseconds + clock.tick(4000); + + $row = $('body .toastify'); + expect($row.length).toEqual(0); }); }); describe('show', function() { it('hides itself after a given time', function() { - OC.Notification.show('', { timeout: 10 }); - - // travel in time +9 seconds - clock.tick(9000); + OC.Notification.show('', {timeout: 10000}); - expect(hideSpy.notCalled).toEqual(true); + var $row = $('body .toastify'); + expect($row.length).toEqual(1); - // travel in time +1 seconds - clock.tick(1000); + clock.tick(11500); - expect(hideSpy.calledOnce).toEqual(true); + $row = $('body .toastify'); + expect($row.length).toEqual(0); }); - it('does not hide itself after a given time if a timeout of 0 is defined', function() { - OC.Notification.show('', { timeout: 0 }); + it('does not hide itself if no timeout given to show', function() { + OC.Notification.show(''); + + var $row = $('body .toastify'); + expect($row.length).toEqual(1); // travel in time +1000 seconds clock.tick(1000000); - expect(hideSpy.notCalled).toEqual(true); + $row = $('body .toastify'); + expect($row.length).toEqual(1); + }); + }); + describe('showHtml', function() { + it('hides itself after a given time', function() { + OC.Notification.showHtml('<p></p>', {timeout: 10000}); + + var $row = $('body .toastify'); + expect($row.length).toEqual(1); + + clock.tick(11500); + + $row = $('body .toastify'); + expect($row.length).toEqual(0); }); it('does not hide itself if no timeout given to show', function() { - OC.Notification.show(''); + OC.Notification.showHtml('<p></p>'); + + var $row = $('body .toastify'); + expect($row.length).toEqual(1); // travel in time +1000 seconds clock.tick(1000000); - expect(hideSpy.notCalled).toEqual(true); + $row = $('body .toastify'); + expect($row.length).toEqual(1); }); }); - it('cumulates several notifications', function() { - var $row1 = OC.Notification.showTemporary('One'); - var $row2 = OC.Notification.showTemporary('Two', {timeout: 2}); - var $row3 = OC.Notification.showTemporary('Three'); + describe('hide', function() { + it('hides a temporary notification before its timeout expires', function() { + var hideCallback = sinon.spy(); - var $el = $('#notification'); - var $rows = $el.find('.row'); - expect($rows.length).toEqual(3); + var notification = OC.Notification.showTemporary(''); - expect($rows.eq(0).is($row1)).toEqual(true); - expect($rows.eq(1).is($row2)).toEqual(true); - expect($rows.eq(2).is($row3)).toEqual(true); + var $row = $('body .toastify'); + expect($row.length).toEqual(1); - clock.tick(3000); + OC.Notification.hide(notification, hideCallback); - $rows = $el.find('.row'); - expect($rows.length).toEqual(2); + // Give time to the hide animation to finish + clock.tick(1000); - expect($rows.eq(0).is($row1)).toEqual(true); - expect($rows.eq(1).is($row3)).toEqual(true); - }); - it('shows close button for error types', function() { - var $row = OC.Notification.showTemporary('One'); - var $rowError = OC.Notification.showTemporary('Two', {type: 'error'}); - expect($row.find('.close').length).toEqual(0); - expect($rowError.find('.close').length).toEqual(1); + $row = $('body .toastify'); + expect($row.length).toEqual(0); - // after clicking, row is gone - $rowError.find('.close').click(); + expect(hideCallback.calledOnce).toEqual(true); + }); + it('hides a notification before its timeout expires', function() { + var hideCallback = sinon.spy(); - var $rows = $('#notification').find('.row'); - expect($rows.length).toEqual(1); - expect($rows.eq(0).is($row)).toEqual(true); - }); - it('fades out the last notification but not the other ones', function() { - var fadeOutStub = sinon.stub($.fn, 'fadeOut'); - var $row1 = OC.Notification.show('One', {type: 'error'}); - var $row2 = OC.Notification.show('Two', {type: 'error'}); - OC.Notification.showTemporary('Three', {timeout: 2}); + var notification = OC.Notification.show('', {timeout: 10000}); - var $el = $('#notification'); - var $rows = $el.find('.row'); - expect($rows.length).toEqual(3); + var $row = $('body .toastify'); + expect($row.length).toEqual(1); - clock.tick(3000); + OC.Notification.hide(notification, hideCallback); - $rows = $el.find('.row'); - expect($rows.length).toEqual(2); + // Give time to the hide animation to finish + clock.tick(1000); - $row1.find('.close').click(); - clock.tick(1000); + $row = $('body .toastify'); + expect($row.length).toEqual(0); - expect(fadeOutStub.notCalled).toEqual(true); + expect(hideCallback.calledOnce).toEqual(true); + }); + it('hides a notification without timeout', function() { + var hideCallback = sinon.spy(); - $row2.find('.close').click(); - clock.tick(1000); - expect(fadeOutStub.calledOnce).toEqual(true); + var notification = OC.Notification.show(''); - expect($el.is(':empty')).toEqual(false); - fadeOutStub.yield(); - expect($el.is(':empty')).toEqual(true); + var $row = $('body .toastify'); + expect($row.length).toEqual(1); - fadeOutStub.restore(); + OC.Notification.hide(notification, hideCallback); + + // Give time to the hide animation to finish + clock.tick(1000); + + $row = $('body .toastify'); + expect($row.length).toEqual(0); + + expect(hideCallback.calledOnce).toEqual(true); + }); }); - it('hides the first notification when calling hide without arguments', function() { - var $row1 = OC.Notification.show('One'); - var $row2 = OC.Notification.show('Two'); + it('cumulates several notifications', function() { + var $row1 = OC.Notification.showTemporary('One'); + var $row2 = OC.Notification.showTemporary('Two', {timeout: 2000}); + var $row3 = OC.Notification.showTemporary('Three'); - var $el = $('#notification'); - var $rows = $el.find('.row'); - expect($rows.length).toEqual(2); + var $el = $('body'); + var $rows = $el.find('.toastify'); + expect($rows.length).toEqual(3); - OC.Notification.hide(); + expect($rows.eq(0).is($row3)).toEqual(true); + expect($rows.eq(1).is($row2)).toEqual(true); + expect($rows.eq(2).is($row1)).toEqual(true); - $rows = $el.find('.row'); - expect($rows.length).toEqual(1); - expect($rows.eq(0).is($row2)).toEqual(true); + clock.tick(3000); + + $rows = $el.find('.toastify'); + expect($rows.length).toEqual(2); + + expect($rows.eq(0).is($row3)).toEqual(true); + expect($rows.eq(1).is($row1)).toEqual(true); }); it('hides the given notification when calling hide with argument', function() { var $row1 = OC.Notification.show('One'); var $row2 = OC.Notification.show('Two'); - var $el = $('#notification'); - var $rows = $el.find('.row'); + var $el = $('body'); + var $rows = $el.find('.toastify'); expect($rows.length).toEqual(2); OC.Notification.hide($row2); + clock.tick(3000); - $rows = $el.find('.row'); + $rows = $el.find('.toastify'); expect($rows.length).toEqual(1); expect($rows.eq(0).is($row1)).toEqual(true); }); }); describe('global ajax errors', function() { var reloadStub, ajaxErrorStub, clock; + var notificationStub; + var waitTimeMs = 6500; + var oldCurrentUser; beforeEach(function() { + oldCurrentUser = OC.currentUser; + OC.currentUser = 'dummy'; clock = sinon.useFakeTimers(); reloadStub = sinon.stub(OC, 'reload'); + document.head.dataset.user = 'dummy' + notificationStub = sinon.stub(OC.Notification, 'show'); // unstub the error processing method ajaxErrorStub = OC._processAjaxError; ajaxErrorStub.restore(); window.initCore(); }); afterEach(function() { + OC.currentUser = oldCurrentUser; reloadStub.restore(); + notificationStub.restore(); clock.restore(); }); - it('reloads current page in case of auth error', function() { - var dataProvider = [ - [200, false], - [400, false], - [0, true], - [401, true], - [302, true], - [303, true], - [307, true] - ]; + it('does not reload the page if the user was navigating away', function() { + var xhr = { status: 0 }; + OC._userIsNavigatingAway = true; + clock.tick(100); - for (var i = 0; i < dataProvider.length; i++) { - var xhr = { status: dataProvider[i][0] }; - var expectedCall = dataProvider[i][1]; + $(document).trigger(new $.Event('ajaxError'), xhr); - reloadStub.reset(); - OC._reloadCalled = false; + clock.tick(waitTimeMs); + expect(reloadStub.notCalled).toEqual(true); + }); - $(document).trigger(new $.Event('ajaxError'), xhr); + it('shows a temporary notification if the connection is lost', function() { + var xhr = { status: 0 }; + spyOn(OC, '_ajaxConnectionLostHandler'); - // trigger timers - clock.tick(1000); + $(document).trigger(new $.Event('ajaxError'), xhr); + clock.tick(101); - if (expectedCall) { - expect(reloadStub.calledOnce).toEqual(true); - } else { - expect(reloadStub.notCalled).toEqual(true); - } - } + expect(OC._ajaxConnectionLostHandler.calls.count()).toBe(1); + }); + }); + describe('Snapper', function() { + var snapConstructorStub; + var snapperStub; + var clock; + + beforeEach(function() { + snapConstructorStub = sinon.stub(window, 'Snap'); + snapperStub = {}; + + snapperStub.enable = sinon.stub(); + snapperStub.disable = sinon.stub(); + snapperStub.close = sinon.stub(); + snapperStub.on = sinon.stub(); + snapperStub.state = sinon.stub().returns({ + state: sinon.stub() + }); + + snapConstructorStub.returns(snapperStub); + + clock = sinon.useFakeTimers(); + + // _.now could have been set to Date.now before Sinon replaced it + // with a fake version, so _.now must be stubbed to ensure that the + // fake Date.now will be called instead of the original one. + _.now = sinon.stub(_, 'now').callsFake(function() { + return new Date().getTime(); + }); + + $('#testArea').append('<div id="app-navigation">The navigation bar</div><div id="app-content">Content</div>'); + }); + afterEach(function() { + snapConstructorStub.restore(); + + clock.restore(); + + _.now.restore(); + + // Remove the event handler for the resize event added to the window + // due to calling window.initCore() when there is an #app-navigation + // element. + $(window).off('resize'); + + viewport.reset(); }); - it('reload only called once in case of auth error', function() { - var xhr = { status: 401 }; - $(document).trigger(new $.Event('ajaxError'), xhr); - $(document).trigger(new $.Event('ajaxError'), xhr); + it('is enabled on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapConstructorStub.calledOnce).toBe(true); + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + }); + it('is disabled when disallowing the gesture on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is not disabled again when disallowing the gesture twice on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is enabled when allowing the gesture after disallowing it on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.allowNavigationBarSlideGesture(); - // trigger timers + expect(snapperStub.enable.calledTwice).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is not enabled again when allowing the gesture twice after disallowing it on a narrow screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledTwice).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledTwice).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.called).toBe(false); + }); + it('is disabled on a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapConstructorStub.calledOnce).toBe(true); + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is not disabled again when disallowing the gesture on a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + it('is not enabled when allowing the gesture after disallowing it on a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + it('is enabled when resizing to a narrow screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. clock.tick(1000); - expect(reloadStub.calledOnce).toEqual(true); + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); }); - it('does not reload the page if the user was navigating away', function() { - var xhr = { status: 0 }; - OC._userIsNavigatingAway = true; - clock.tick(100); + it('is not enabled when resizing to a narrow screen after disallowing the gesture', function() { + viewport.set(1280); - $(document).trigger(new $.Event('ajaxError'), xhr); + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. clock.tick(1000); - expect(reloadStub.notCalled).toEqual(true); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is enabled when resizing to a narrow screen after disallowing the gesture and allowing it', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is enabled when allowing the gesture after disallowing it and resizing to a narrow screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + }); + it('is disabled when disallowing the gesture after disallowing it, resizing to a narrow screen and allowing it', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledTwice).toBe(true); + expect(snapperStub.disable.getCall(1).calledWithExactly(true)).toBe(true); + }); + it('is disabled when resizing to a wide screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + viewport.set(1280); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + it('is not disabled again when disallowing the gesture after resizing to a wide screen', function() { + viewport.set(480); + + window.initCore(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.called).toBe(false); + expect(snapperStub.close.called).toBe(false); + + viewport.set(1280); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.calledOnce).toBe(true); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + }); + it('is not enabled when allowing the gesture after disallowing it, resizing to a narrow screen and resizing to a wide screen', function() { + viewport.set(1280); + + window.initCore(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + OC.disallowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + viewport.set(480); + + // Setting the viewport width does not automatically trigger a + // resize. + $(window).resize(); + + // The resize handler is debounced to be executed a few milliseconds + // after the resize event. + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledOnce).toBe(true); + expect(snapperStub.close.calledOnce).toBe(true); + + viewport.set(1280); + + $(window).resize(); + + clock.tick(1000); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledTwice).toBe(true); + expect(snapperStub.close.calledTwice).toBe(true); + + OC.allowNavigationBarSlideGesture(); + + expect(snapperStub.enable.called).toBe(false); + expect(snapperStub.disable.calledTwice).toBe(true); + expect(snapperStub.close.calledTwice).toBe(true); }); }); }); - diff --git a/core/js/tests/specs/files/clientSpec.js b/core/js/tests/specs/files/clientSpec.js index 7673ec6e0fc..105af079ced 100644 --- a/core/js/tests/specs/files/clientSpec.js +++ b/core/js/tests/specs/files/clientSpec.js @@ -1,23 +1,8 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2015 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ /* global dav */ @@ -50,7 +35,7 @@ describe('OC.Files.Client tests', function() { * status code * * @param {Promise} promise promise - * @param {int} status status to test + * @param {number} status status to test */ function respondAndCheckStatus(promise, status) { var successHandler = sinon.stub(); @@ -79,7 +64,7 @@ describe('OC.Files.Client tests', function() { * status code * * @param {Promise} promise promise object - * @param {int} status error status to test + * @param {number} status error status to test */ function respondAndCheckError(promise, status) { var successHandler = sinon.stub(); @@ -87,14 +72,28 @@ describe('OC.Files.Client tests', function() { promise.done(successHandler); promise.fail(failHandler); + var errorXml = + '<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">' + + ' <s:exception>Sabre\\DAV\\Exception\\SomeException</s:exception>' + + ' <s:message>Some error message</s:message>' + + '</d:error>'; + + var parser = new DOMParser(); + requestDeferred.resolve({ status: status, - body: '' + body: errorXml, + xhr: { + responseXML: parser.parseFromString(errorXml, 'application/xml') + } }); promise.then(function() { expect(failHandler.calledOnce).toEqual(true); - expect(failHandler.calledWith(status)).toEqual(true); + expect(failHandler.getCall(0).args[0]).toEqual(status); + expect(failHandler.getCall(0).args[1].status).toEqual(status); + expect(failHandler.getCall(0).args[1].message).toEqual('Some error message'); + expect(failHandler.getCall(0).args[1].exception).toEqual('Sabre\\DAV\\Exception\\SomeException'); expect(successHandler.notCalled).toEqual(true); }); @@ -164,7 +163,7 @@ describe('OC.Files.Client tests', function() { 'd:resourcetype': '<d:collection/>', 'oc:id': '00000011oc2d13a6a068', 'oc:fileid': '11', - 'oc:permissions': 'RDNVCK', + 'oc:permissions': 'GRDNVCK', 'oc:size': '120' }, [ @@ -196,7 +195,7 @@ describe('OC.Files.Client tests', function() { 'd:resourcetype': '<d:collection/>', 'oc:id': '00000015oc2d13a6a068', 'oc:fileid': '15', - 'oc:permissions': 'RDNVCK', + 'oc:permissions': 'GRDNVCK', 'oc:size': '100' }, [ @@ -213,7 +212,7 @@ describe('OC.Files.Client tests', function() { expect(requestStub.calledOnce).toEqual(true); expect(requestStub.lastCall.args[0]).toEqual('PROPFIND'); expect(requestStub.lastCall.args[1]).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); - expect(requestStub.lastCall.args[2].Depth).toEqual(1); + expect(requestStub.lastCall.args[2].Depth).toEqual('1'); var props = getRequestedProperties(requestStub.lastCall.args[3]); expect(props).toContain('{DAV:}getlastmodified'); @@ -224,6 +223,7 @@ describe('OC.Files.Client tests', function() { expect(props).toContain('{http://owncloud.org/ns}fileid'); expect(props).toContain('{http://owncloud.org/ns}size'); expect(props).toContain('{http://owncloud.org/ns}permissions'); + expect(props).toContain('{http://nextcloud.org/ns}is-encrypted'); }); it('sends PROPFIND to base url when empty path given', function() { client.getFolderContents(''); @@ -257,11 +257,12 @@ describe('OC.Files.Client tests', function() { expect(info.id).toEqual(51); expect(info.path).toEqual('/path/to space/文件夹'); expect(info.name).toEqual('One.txt'); - expect(info.permissions).toEqual(27); + expect(info.permissions).toEqual(26); expect(info.size).toEqual(250); expect(info.mtime).toEqual(1436535485000); expect(info.mimetype).toEqual('text/plain'); expect(info.etag).toEqual('559fcabd79a38'); + expect(info.isEncrypted).toEqual(false); // sub entry info = response[1]; @@ -274,6 +275,7 @@ describe('OC.Files.Client tests', function() { expect(info.mtime).toEqual(1436536800000); expect(info.mimetype).toEqual('httpd/unix-directory'); expect(info.etag).toEqual('66cfcabd79abb'); + expect(info.isEncrypted).toEqual(false); }); }); it('returns parent node in result if specified', function() { @@ -303,6 +305,7 @@ describe('OC.Files.Client tests', function() { expect(info.mtime).toEqual(1436522405000); expect(info.mimetype).toEqual('httpd/unix-directory'); expect(info.etag).toEqual('56cfcabd79abb'); + expect(info.isEncrypted).toEqual(false); // the two other entries follow expect(response[1].id).toEqual(51); @@ -422,6 +425,7 @@ describe('OC.Files.Client tests', function() { expect(props).toContain('{http://owncloud.org/ns}fileid'); expect(props).toContain('{http://owncloud.org/ns}size'); expect(props).toContain('{http://owncloud.org/ns}permissions'); + expect(props).toContain('{http://nextcloud.org/ns}is-encrypted'); }); it('parses the result list into a FileInfo array', function() { var promise = client.getFilteredFiles({ @@ -448,7 +452,7 @@ describe('OC.Files.Client tests', function() { expect(info.id).toEqual(11); // file entry - var info = response[1]; + info = response[1]; expect(info instanceof OC.Files.FileInfo).toEqual(true); expect(info.id).toEqual(51); @@ -461,9 +465,7 @@ describe('OC.Files.Client tests', function() { it('throws exception if arguments are missing', function() { var thrown = null; try { - client.getFilteredFiles({ - systemTagIds: [] - }); + client.getFilteredFiles({}); } catch (e) { thrown = true; } @@ -475,7 +477,7 @@ describe('OC.Files.Client tests', function() { describe('file info', function() { var responseXml = dav.Client.prototype.parseMultiStatus( '<?xml version="1.0" encoding="utf-8"?>' + - '<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' + + '<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">' + makeResponseBlock( '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/', { @@ -484,8 +486,9 @@ describe('OC.Files.Client tests', function() { 'd:resourcetype': '<d:collection/>', 'oc:id': '00000011oc2d13a6a068', 'oc:fileid': '11', - 'oc:permissions': 'RDNVCK', - 'oc:size': '120' + 'oc:permissions': 'GRDNVCK', + 'oc:size': '120', + 'nc:is-encrypted': '1' }, [ 'd:getcontenttype', @@ -501,7 +504,7 @@ describe('OC.Files.Client tests', function() { expect(requestStub.calledOnce).toEqual(true); expect(requestStub.lastCall.args[0]).toEqual('PROPFIND'); expect(requestStub.lastCall.args[1]).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); - expect(requestStub.lastCall.args[2].Depth).toEqual(0); + expect(requestStub.lastCall.args[2].Depth).toEqual('0'); var props = getRequestedProperties(requestStub.lastCall.args[3]); expect(props).toContain('{DAV:}getlastmodified'); @@ -512,6 +515,7 @@ describe('OC.Files.Client tests', function() { expect(props).toContain('{http://owncloud.org/ns}fileid'); expect(props).toContain('{http://owncloud.org/ns}size'); expect(props).toContain('{http://owncloud.org/ns}permissions'); + expect(props).toContain('{http://nextcloud.org/ns}is-encrypted'); }); it('parses the result into a FileInfo', function() { var promise = client.getFileInfo('path/to space/文件夹'); @@ -537,6 +541,7 @@ describe('OC.Files.Client tests', function() { expect(info.mtime).toEqual(1436522405000); expect(info.mimetype).toEqual('httpd/unix-directory'); expect(info.etag).toEqual('56cfcabd79abb'); + expect(info.isEncrypted).toEqual(true); }); }); it('properly parses entry inside root', function() { @@ -551,7 +556,7 @@ describe('OC.Files.Client tests', function() { 'd:resourcetype': '<d:collection/>', 'oc:id': '00000011oc2d13a6a068', 'oc:fileid': '11', - 'oc:permissions': 'RDNVCK', + 'oc:permissions': 'GRDNVCK', 'oc:size': '120' }, [ @@ -585,6 +590,7 @@ describe('OC.Files.Client tests', function() { expect(info.mtime).toEqual(1436522405000); expect(info.mimetype).toEqual('httpd/unix-directory'); expect(info.etag).toEqual('56cfcabd79abb'); + expect(info.isEncrypted).toEqual(false); }); }); it('rejects promise when an error occurred', function() { @@ -642,14 +648,14 @@ describe('OC.Files.Client tests', function() { function testPermission(permission, isFile, expectedPermissions) { var promise = getFileInfoWithPermission(permission, isFile); - promise.then(function(result) { + promise.then(function(status, result) { expect(result.permissions).toEqual(expectedPermissions); }); } function testMountType(permission, isFile, expectedMountType) { var promise = getFileInfoWithPermission(permission, isFile); - promise.then(function(result) { + promise.then(function(status, result) { expect(result.mountType).toEqual(expectedMountType); }); } @@ -657,43 +663,29 @@ describe('OC.Files.Client tests', function() { it('properly parses file permissions', function() { // permission, isFile, expectedPermissions var testCases = [ - ['', true, OC.PERMISSION_READ], - ['C', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE], - ['K', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE], - ['W', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE], - ['D', true, OC.PERMISSION_READ | OC.PERMISSION_DELETE], - ['R', true, OC.PERMISSION_READ | OC.PERMISSION_SHARE], - ['CKWDR', true, OC.PERMISSION_ALL] - ]; - _.each(testCases, function(testCase) { - return testPermission.apply(testCase); - }); - }); - it('properly parses folder permissions', function() { - var testCases = [ - ['', false, OC.PERMISSION_READ], - ['C', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE], - ['K', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE], - ['W', false, OC.PERMISSION_READ | OC.PERMISSION_UPDATE], - ['D', false, OC.PERMISSION_READ | OC.PERMISSION_DELETE], - ['R', false, OC.PERMISSION_READ | OC.PERMISSION_SHARE], - ['CKWDR', false, OC.PERMISSION_ALL] + ['', true, OC.PERMISSION_NONE], + ['C', true, OC.PERMISSION_CREATE], + ['K', true, OC.PERMISSION_CREATE], + ['G', true, OC.PERMISSION_READ], + ['W', true, OC.PERMISSION_UPDATE], + ['D', true, OC.PERMISSION_DELETE], + ['R', true, OC.PERMISSION_SHARE], + ['CKGWDR', true, OC.PERMISSION_ALL] ]; - _.each(testCases, function(testCase) { - return testPermission.apply(testCase); + return testPermission.apply(this, testCase); }); }); it('properly parses mount types', function() { var testCases = [ - ['CKWDR', false, null], + ['CKGWDR', false, null], ['M', false, 'external'], ['S', false, 'shared'], ['SM', false, 'shared'] ]; _.each(testCases, function(testCase) { - return testMountType.apply(testCase); + return testMountType.apply(this, testCase); }); }); }); diff --git a/core/js/tests/specs/jquery.avatarSpec.js b/core/js/tests/specs/jquery.avatarSpec.js index f5caae10020..c01cd9b9603 100644 --- a/core/js/tests/specs/jquery.avatarSpec.js +++ b/core/js/tests/specs/jquery.avatarSpec.js @@ -1,17 +1,13 @@ /** - * Copyright (c) 2015 Roeland Jago Douma <roeland@famdouma.nl> - * - * This file is licensed under the Affero General Public License version 3 - * or later. - * - * See the COPYING-README file. - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later */ describe('jquery.avatar tests', function() { var $div; - var devicePixelRatio + var devicePixelRatio; beforeEach(function() { $('#testArea').append($('<div id="avatardiv">')); @@ -19,100 +15,105 @@ describe('jquery.avatar tests', function() { devicePixelRatio = window.devicePixelRatio; window.devicePixelRatio = 1; + + spyOn(window, 'Image').and.returnValue({ + onload: function() { + }, + onerror: function() { + } + }); }); afterEach(function() { $div.remove(); - window.devicePixelRatio = devicePixelRatio + window.devicePixelRatio = devicePixelRatio; }); describe('size', function() { it('undefined', function() { $div.avatar('foo'); - expect($div.height()).toEqual(64); - expect($div.width()).toEqual(64); + expect(Math.round($div.height())).toEqual(64); + expect(Math.round($div.width())).toEqual(64); }); it('undefined but div has height', function() { $div.height(9); $div.avatar('foo'); - expect($div.height()).toEqual(9); - expect($div.width()).toEqual(9); + expect(window.Image).toHaveBeenCalled(); + window.Image().onerror(); + + expect(Math.round($div.height())).toEqual(9); + expect(Math.round($div.width())).toEqual(9); }); it('undefined but data size is set', function() { $div.data('size', 10); $div.avatar('foo'); - expect($div.height()).toEqual(10); - expect($div.width()).toEqual(10); + expect(window.Image).toHaveBeenCalled(); + window.Image().onerror(); + + expect(Math.round($div.height())).toEqual(10); + expect(Math.round($div.width())).toEqual(10); }); it('defined', function() { $div.avatar('foo', 8); - expect($div.height()).toEqual(8); - expect($div.width()).toEqual(8); + expect(window.Image).toHaveBeenCalled(); + window.Image().onerror(); + + expect(Math.round($div.height())).toEqual(8); + expect(Math.round($div.width())).toEqual(8); }); }); it('undefined user', function() { spyOn($div, 'imageplaceholder'); + spyOn($div, 'css'); $div.avatar(); - expect($div.imageplaceholder).toHaveBeenCalledWith('x'); + expect($div.imageplaceholder).toHaveBeenCalledWith('?'); + expect($div.css).toHaveBeenCalledWith('background-color', '#b9b9b9'); }); describe('no avatar', function() { it('show placeholder for existing user', function() { spyOn($div, 'imageplaceholder'); - $div.avatar('foo'); - - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - data: {displayname: 'bar'} - }) - ); + $div.avatar('foo', undefined, undefined, undefined, undefined, 'bar'); + expect(window.Image).toHaveBeenCalled(); + window.Image().onerror(); expect($div.imageplaceholder).toHaveBeenCalledWith('foo', 'bar'); }); it('show placeholder for non existing user', function() { spyOn($div, 'imageplaceholder'); + spyOn($div, 'css'); $div.avatar('foo'); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - data: {} - }) - ); + expect(window.Image).toHaveBeenCalled(); + window.Image().onerror(); - expect($div.imageplaceholder).toHaveBeenCalledWith('foo', 'X'); + expect($div.imageplaceholder).toHaveBeenCalledWith('?'); + expect($div.css).toHaveBeenCalledWith('background-color', '#b9b9b9'); }); - it('show no placeholder', function() { + it('show no placeholder is ignored', function() { spyOn($div, 'imageplaceholder'); + spyOn($div, 'css'); $div.avatar('foo', undefined, undefined, true); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - data: {} - }) - ); + expect(window.Image).toHaveBeenCalled(); + window.Image().onerror(); - expect($div.imageplaceholder.calls.any()).toEqual(false); - expect($div.css('display')).toEqual('none'); + expect($div.imageplaceholder).toHaveBeenCalledWith('?'); + expect($div.css).toHaveBeenCalledWith('background-color', '#b9b9b9'); }); }); @@ -125,24 +126,24 @@ describe('jquery.avatar tests', function() { window.devicePixelRatio = 1; $div.avatar('foo', 32); - expect(fakeServer.requests[0].method).toEqual('GET'); - expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/avatar/foo/32'); + expect(window.Image).toHaveBeenCalled(); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32'); }); it('high DPI icon', function() { window.devicePixelRatio = 4; $div.avatar('foo', 32); - expect(fakeServer.requests[0].method).toEqual('GET'); - expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/avatar/foo/128'); + expect(window.Image).toHaveBeenCalled(); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/128'); }); it('high DPI icon round up size', function() { window.devicePixelRatio = 1.9; $div.avatar('foo', 32); - expect(fakeServer.requests[0].method).toEqual('GET'); - expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/avatar/foo/61'); + expect(window.Image).toHaveBeenCalled(); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/61'); }); }); @@ -154,17 +155,12 @@ describe('jquery.avatar tests', function() { it('default (no ie8 fix)', function() { $div.avatar('foo', 32); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'image/jpeg' }, - '' - ); - - var img = $div.children('img')[0]; + expect(window.Image).toHaveBeenCalled(); + window.Image().onload(); - expect(img.height).toEqual(32); - expect(img.width).toEqual(32); - expect(img.src).toEqual('http://localhost/index.php/avatar/foo/32'); + expect(window.Image().height).toEqual(32); + expect(window.Image().width).toEqual(32); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32'); }); it('default high DPI icon', function() { @@ -172,37 +168,23 @@ describe('jquery.avatar tests', function() { $div.avatar('foo', 32); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'image/jpeg' }, - '' - ); - - var img = $div.children('img')[0]; + expect(window.Image).toHaveBeenCalled(); + window.Image().onload(); - expect(img.height).toEqual(32); - expect(img.width).toEqual(32); - expect(img.src).toEqual('http://localhost/index.php/avatar/foo/61'); + expect(window.Image().height).toEqual(32); + expect(window.Image().width).toEqual(32); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/61'); }); - it('with ie8 fix', function() { - sinon.stub(Math, 'random', function() { - return 0.5; - }); - + it('with ie8 fix (ignored)', function() { $div.avatar('foo', 32, true); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'image/jpeg' }, - '' - ); - - var img = $div.children('img')[0]; + expect(window.Image).toHaveBeenCalled(); + window.Image().onload(); - expect(img.height).toEqual(32); - expect(img.width).toEqual(32); - expect(img.src).toEqual('http://localhost/index.php/avatar/foo/32#500'); + expect(window.Image().height).toEqual(32); + expect(window.Image().width).toEqual(32); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32'); }); it('unhide div', function() { @@ -210,13 +192,12 @@ describe('jquery.avatar tests', function() { $div.avatar('foo', 32); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'image/jpeg' }, - '' - ); + expect(window.Image).toHaveBeenCalled(); + window.Image().onload(); - expect($div.css('display')).toEqual('block'); + expect(window.Image().height).toEqual(32); + expect(window.Image().width).toEqual(32); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32'); }); it('callback called', function() { @@ -228,12 +209,12 @@ describe('jquery.avatar tests', function() { observer.callback(); }); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'image/jpeg' }, - '' - ); + expect(window.Image).toHaveBeenCalled(); + window.Image().onload(); + expect(window.Image().height).toEqual(32); + expect(window.Image().width).toEqual(32); + expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32'); expect(observer.callback).toHaveBeenCalled(); }); }); diff --git a/core/js/tests/specs/jquery.contactsmenuSpec.js b/core/js/tests/specs/jquery.contactsmenuSpec.js new file mode 100644 index 00000000000..62aaef89edc --- /dev/null +++ b/core/js/tests/specs/jquery.contactsmenuSpec.js @@ -0,0 +1,235 @@ +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +describe('jquery.contactsMenu tests', function() { + + var $selector1, $selector2, $appendTo; + + beforeEach(function() { + $('#testArea').append($('<div id="selector1">')); + $('#testArea').append($('<div id="selector2">')); + $('#testArea').append($('<div id="appendTo">')); + $selector1 = $('#selector1'); + $selector2 = $('#selector2'); + $appendTo = $('#appendTo'); + }); + + afterEach(function() { + $selector1.off(); + $selector1.remove(); + $selector2.off(); + $selector2.remove(); + $appendTo.remove(); + }); + + describe('shareType', function() { + it('stops if type not supported', function() { + $selector1.contactsMenu('user', 1, $appendTo); + expect($appendTo.children().length).toEqual(0); + + $selector1.contactsMenu('user', 2, $appendTo); + expect($appendTo.children().length).toEqual(0); + + $selector1.contactsMenu('user', 3, $appendTo); + expect($appendTo.children().length).toEqual(0); + + $selector1.contactsMenu('user', 5, $appendTo); + expect($appendTo.children().length).toEqual(0); + }); + + it('append list if shareType supported', function() { + $selector1.contactsMenu('user', 0, $appendTo); + expect($appendTo.children().length).toEqual(1); + expect($appendTo.html().replace(/[\r\n\t]?(\ \ +)?/g, '')).toEqual('<div class="menu popovermenu menu-left hidden contactsmenu-popover"><ul><li><a><span class="icon-loading-small"></span></a></li></ul></div>'); + }); + }); + + describe('open on click', function() { + it('with one selector', function() { + $selector1.contactsMenu('user', 0, $appendTo); + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false); + }); + + it('with multiple selectors - 1', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false); + }); + + it('with multiple selectors - 2', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true); + $selector2.click(); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false); + }); + + it ('should close when clicking the selector again - 1', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(false); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + }); + + it ('should close when clicking the selector again - 1', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(false); + $selector2.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + }); + }); + + describe('send requests to the server and render', function() { + it('load a topaction only', function(done) { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify({ + "id": null, + "fullName": "Name 123", + "topAction": { + "title": "bar@baz.wtf", + "icon": "foo.svg", + "hyperlink": "mailto:bar%40baz.wtf"}, + "actions": [] + }) + ); + + $selector1.on('load', function() { + // FIXME: don't compare HTML one to one but check specific text in the output + expect($appendTo.html().replace(/[\r\n\t]?(\ \ +)?/g, '')).toEqual('<div class="menu popovermenu menu-left contactsmenu-popover loaded" style="display: block;"><ul><li class="hidden"><a><span class="icon-loading-small"></span></a></li><li><a href="mailto:bar%40baz.wtf"><img src="foo.svg"><span>bar@baz.wtf</span></a></li></ul></div>'); + + done(); + }); + }); + + it('load topaction and more actions', function(done) { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify({ + "id": null, + "fullName": "Name 123", + "topAction": { + "title": "bar@baz.wtf", + "icon": "foo.svg", + "hyperlink": "mailto:bar%40baz.wtf"}, + "actions": [{ + "title": "Details", + "icon": "details.svg", + "hyperlink": "http:\/\/localhost\/index.php\/apps\/contacts" + }] + }) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + $selector1.on('load', function() { + // FIXME: don't compare HTML one to one but check specific text in the output + expect($appendTo.html().replace(/[\r\n\t]?(\ \ +)?/g, '')).toEqual('<div class="menu popovermenu menu-left contactsmenu-popover loaded" style="display: block;"><ul><li class="hidden"><a><span class="icon-loading-small"></span></a></li><li><a href="mailto:bar%40baz.wtf"><img src="foo.svg"><span>bar@baz.wtf</span></a></li><li><a href="http://localhost/index.php/apps/contacts"><img src="details.svg"><span>Details</span></a></li></ul></div>'); + + done(); + }); + }); + + it('load no actions', function(done) { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify({ + "id": null, + "fullName": "Name 123", + "topAction": null, + "actions": [] + }) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + $selector1.on('load', function() { + // FIXME: don't compare HTML one to one but check specific text in the output + expect($appendTo.html().replace(/[\r\n\t]?(\ \ +)?/g, '')).toEqual('<div class="menu popovermenu menu-left contactsmenu-popover loaded" style="display: block;"><ul><li class="hidden"><a><span class="icon-loading-small"></span></a></li><li><a href="#"><span>No action available</span></a></li></ul></div>'); + + done(); + }); + }); + + it('should throw an error', function(done) { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 400, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify([]) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + $selector1.on('loaderror', function() { + // FIXME: don't compare HTML one to one but check specific text in the output + expect($appendTo.html().replace(/[\r\n\t]?(\ \ +)?/g, '')).toEqual('<div class="menu popovermenu menu-left contactsmenu-popover loaded" style="display: block;"><ul><li class="hidden"><a><span class="icon-loading-small"></span></a></li><li><a href="#"><span>Error fetching contact actions</span></a></li></ul></div>'); + + done(); + }); + }); + + it('should handle 404', function(done) { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 404, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify([]) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + $selector1.on('loaderror', function() { + // FIXME: don't compare HTML one to one but check specific text in the output + expect($appendTo.html().replace(/[\r\n\t]?(\ \ +)?/g, '')).toEqual('<div class="menu popovermenu menu-left contactsmenu-popover loaded" style="display: block;"><ul><li class="hidden"><a><span class="icon-loading-small"></span></a></li><li><a href="#"><span>No action available</span></a></li></ul></div>'); + + done(); + }); + }); + + it('click anywhere else to close the menu', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(false); + $(document).click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + }); + }); +}); diff --git a/core/js/tests/specs/jquery.placeholderSpec.js b/core/js/tests/specs/jquery.placeholderSpec.js new file mode 100644 index 00000000000..436defdd703 --- /dev/null +++ b/core/js/tests/specs/jquery.placeholderSpec.js @@ -0,0 +1,38 @@ +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +describe('jquery.placeholder tests', function() { + + var $div; + + beforeEach(function() { + $('#testArea').append($('<div id="placeholderdiv">')); + $div = $('#placeholderdiv'); + }); + + afterEach(function() { + $div.remove(); + }); + + describe('placeholder text', function() { + it('shows one first letter if one word in a input text', function() { + spyOn($div, 'html'); + $div.imageplaceholder('Seed', 'Name') + expect($div.html).toHaveBeenCalledWith('N'); + }); + + it('shows two first letters if two words in a input text', function() { + spyOn($div, 'html'); + $div.imageplaceholder('Seed', 'First Second') + expect($div.html).toHaveBeenCalledWith('FS'); + }); + + it('shows two first letters if more then two words in a input text', function() { + spyOn($div, 'html'); + $div.imageplaceholder('Seed', 'First Second Middle') + expect($div.html).toHaveBeenCalledWith('FS'); + }); + }); +}); diff --git a/core/js/tests/specs/l10nSpec.js b/core/js/tests/specs/l10nSpec.js index 2ceb2f4a916..bd93a13fe74 100644 --- a/core/js/tests/specs/l10nSpec.js +++ b/core/js/tests/specs/l10nSpec.js @@ -1,26 +1,28 @@ /** - * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> - * - * This file is licensed under the Affero General Public License version 3 - * or later. - * - * See the COPYING-README file. - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2014 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later */ describe('OC.L10N tests', function() { var TEST_APP = 'jsunittestapp'; beforeEach(function() { - OC.appswebroots[TEST_APP] = OC.webroot + '/apps3/jsunittestapp'; + window._oc_appswebroots[TEST_APP] = OC.getRootPath() + '/apps3/jsunittestapp'; + + window.OC = window.OC ?? {} + window.OC.appswebroots = window.OC.appswebroots || {} + window.OC.appswebroots[TEST_APP] = OC.getRootPath() + '/apps3/jsunittestapp' }); afterEach(function() { - delete OC.L10N._bundles[TEST_APP]; - delete OC.appswebroots[TEST_APP]; + OC.L10N._unregister(TEST_APP); + delete window._oc_appswebroots[TEST_APP]; + delete window.OC.appswebroots[TEST_APP]; }); describe('text translation', function() { beforeEach(function() { + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { 'Hello world!': 'Hallo Welt!', 'Hello {name}, the weather is {weather}': 'Hallo {name}, das Wetter ist {weather}', @@ -28,7 +30,7 @@ describe('OC.L10N tests', function() { }); }); it('returns untranslated text when no bundle exists', function() { - delete OC.L10N._bundles[TEST_APP]; + OC.L10N._unregister(TEST_APP); expect(t(TEST_APP, 'unknown text')).toEqual('unknown text'); }); it('returns untranslated text when no key exists', function() { @@ -52,6 +54,11 @@ describe('OC.L10N tests', function() { t(TEST_APP, 'Hello {name}', {name: '<strong>Steve</strong>'}, null, {escape: false}) ).toEqual('Hello <strong>Steve</strong>'); }); + it('uses DOMPurify to escape the text', function() { + expect( + t(TEST_APP, '<strong>These are your search results<script>alert(1)</script></strong>', null, {escape: false}) + ).toEqual('<strong>These are your search results</strong>'); + }); it('keeps old texts when registering existing bundle', function() { OC.L10N.register(TEST_APP, { 'sunny': 'sonnig', @@ -78,6 +85,7 @@ describe('OC.L10N tests', function() { } it('generates plural for default text when translation does not exist', function() { + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { }); expect( @@ -94,78 +102,12 @@ describe('OC.L10N tests', function() { ).toEqual('download 1024 files'); }); it('generates plural with default function when no forms specified', function() { + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { '_download %n file_::_download %n files_': ['%n Datei herunterladen', '%n Dateien herunterladen'] }); checkPlurals(); }); - it('generates plural with generated function when forms is specified', function() { - OC.L10N.register(TEST_APP, { - '_download %n file_::_download %n files_': - ['%n Datei herunterladen', '%n Dateien herunterladen'] - }, 'nplurals=2; plural=(n != 1);'); - checkPlurals(); - }); - it('generates plural with function when forms is specified as function', function() { - OC.L10N.register(TEST_APP, { - '_download %n file_::_download %n files_': - ['%n Datei herunterladen', '%n Dateien herunterladen'] - }, function(n) { - return { - nplurals: 2, - plural: (n !== 1) ? 1 : 0 - }; - }); - checkPlurals(); - }); - }); - describe('async loading of translations', function() { - it('loads bundle for given app and calls callback', function() { - var localeStub = sinon.stub(OC, 'getLocale').returns('zh_CN'); - var callbackStub = sinon.stub(); - var promiseStub = sinon.stub(); - OC.L10N.load(TEST_APP, callbackStub).then(promiseStub); - expect(callbackStub.notCalled).toEqual(true); - expect(promiseStub.notCalled).toEqual(true); - expect(fakeServer.requests.length).toEqual(1); - var req = fakeServer.requests[0]; - expect(req.url).toEqual( - OC.webroot + '/apps3/' + TEST_APP + '/l10n/zh_CN.json' - ); - req.respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - translations: {'Hello world!': 'ä½ å¥½ä¸–ç•Œ!'}, - pluralForm: 'nplurals=2; plural=(n != 1);' - }) - ); - - expect(callbackStub.calledOnce).toEqual(true); - expect(promiseStub.calledOnce).toEqual(true); - expect(t(TEST_APP, 'Hello world!')).toEqual('ä½ å¥½ä¸–ç•Œ!'); - localeStub.restore(); - }); - it('calls callback if translation already available', function() { - var promiseStub = sinon.stub(); - var callbackStub = sinon.stub(); - OC.L10N.register(TEST_APP, { - 'Hello world!': 'Hallo Welt!' - }); - OC.L10N.load(TEST_APP, callbackStub).then(promiseStub); - expect(callbackStub.calledOnce).toEqual(true); - expect(promiseStub.calledOnce).toEqual(true); - expect(fakeServer.requests.length).toEqual(0); - }); - it('calls callback if locale is en', function() { - var localeStub = sinon.stub(OC, 'getLocale').returns('en'); - var promiseStub = sinon.stub(); - var callbackStub = sinon.stub(); - OC.L10N.load(TEST_APP, callbackStub).then(promiseStub); - expect(callbackStub.calledOnce).toEqual(true); - expect(promiseStub.calledOnce).toEqual(true); - expect(fakeServer.requests.length).toEqual(0); - }); }); }); diff --git a/core/js/tests/specs/mimeTypeSpec.js b/core/js/tests/specs/mimeTypeSpec.js index 182941de1a9..4fe5481541d 100644 --- a/core/js/tests/specs/mimeTypeSpec.js +++ b/core/js/tests/specs/mimeTypeSpec.js @@ -1,21 +1,7 @@ /** - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ describe('MimeType tests', function() { @@ -26,17 +12,17 @@ describe('MimeType tests', function() { beforeEach(function() { _files = OC.MimeTypeList.files; _aliases = OC.MimeTypeList.aliases; - _theme = OC.MimeTypeList.themes['abc']; + _theme = OC.MimeTypeList.themes.abc; OC.MimeTypeList.files = ['folder', 'folder-shared', 'folder-external', 'foo-bar', 'foo', 'file']; OC.MimeTypeList.aliases = {'app/foobar': 'foo/bar'}; - OC.MimeTypeList.themes['abc'] = ['folder']; + OC.MimeTypeList.themes.abc = ['folder']; }); afterEach(function() { OC.MimeTypeList.files = _files; OC.MimeTypeList.aliases = _aliases; - OC.MimeTypeList.themes['abc'] = _theme; + OC.MimeTypeList.themes.abc = _theme; }); describe('_getFile', function() { @@ -100,7 +86,7 @@ describe('MimeType tests', function() { it('return the url for the mimetype file', function() { var res = OC.MimeType.getIconUrl('file'); - expect(res).toEqual(OC.webroot + '/core/img/filetypes/file.svg'); + expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/file.svg'); }); it('test if the cache works correctly', function() { @@ -109,16 +95,16 @@ describe('MimeType tests', function() { var res = OC.MimeType.getIconUrl('dir'); expect(Object.keys(OC.MimeType._mimeTypeIcons).length).toEqual(1); - expect(OC.MimeType._mimeTypeIcons['dir']).toEqual(res); + expect(OC.MimeType._mimeTypeIcons.dir).toEqual(res); - var res = OC.MimeType.getIconUrl('dir-shared'); + res = OC.MimeType.getIconUrl('dir-shared'); expect(Object.keys(OC.MimeType._mimeTypeIcons).length).toEqual(2); expect(OC.MimeType._mimeTypeIcons['dir-shared']).toEqual(res); }); it('test if alaiases are converted correctly', function() { var res = OC.MimeType.getIconUrl('app/foobar'); - expect(res).toEqual(OC.webroot + '/core/img/filetypes/foo-bar.svg'); + expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/foo-bar.svg'); expect(OC.MimeType._mimeTypeIcons['foo/bar']).toEqual(res); }); }); @@ -139,12 +125,12 @@ describe('MimeType tests', function() { it('test if theme path is used if a theme icon is availble', function() { var res = OC.MimeType.getIconUrl('dir'); - expect(res).toEqual(OC.webroot + '/themes/abc/core/img/filetypes/folder.svg'); + expect(res).toEqual(OC.getRootPath() + '/themes/abc/core/img/filetypes/folder.svg'); }); it('test if we fallback to the default theme if no icon is available in the theme', function() { var res = OC.MimeType.getIconUrl('dir-shared'); - expect(res).toEqual(OC.webroot + '/core/img/filetypes/folder-shared.svg'); + expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/folder-shared.svg'); }); }); }); diff --git a/core/js/tests/specs/oc-backbone-webdavSpec.js b/core/js/tests/specs/oc-backbone-webdavSpec.js index 97281e982ce..bf4d6eef6ca 100644 --- a/core/js/tests/specs/oc-backbone-webdavSpec.js +++ b/core/js/tests/specs/oc-backbone-webdavSpec.js @@ -1,23 +1,8 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2014 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2014 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ /* global dav */ @@ -71,7 +56,7 @@ describe('Backbone Webdav extension', function() { }); }); - it('makes a POST request to create model into collection', function() { + it('makes a POST request to create model into collection', function(done) { var collection = new TestCollection(); var model = collection.create({ firstName: 'Hello', @@ -104,10 +89,14 @@ describe('Backbone Webdav extension', function() { } }); - expect(model.id).toEqual('123'); + setTimeout(function() { + expect(model.id).toEqual('123'); + + done(); + }, 0) }); - it('uses PROPFIND to retrieve collection', function() { + it('uses PROPFIND to retrieve collection', function(done) { var successStub = sinon.stub(); var errorStub = sinon.stub(); var collection = new TestCollection(); @@ -164,23 +153,27 @@ describe('Backbone Webdav extension', function() { ] }); - expect(collection.length).toEqual(2); + setTimeout(function() { + expect(collection.length).toEqual(2); - var model = collection.get('123'); - expect(model.id).toEqual('123'); - expect(model.get('firstName')).toEqual('Hello'); - expect(model.get('lastName')).toEqual('World'); + var model = collection.get('123'); + expect(model.id).toEqual('123'); + expect(model.get('firstName')).toEqual('Hello'); + expect(model.get('lastName')).toEqual('World'); + + model = collection.get('456'); + expect(model.id).toEqual('456'); + expect(model.get('firstName')).toEqual('Test'); + expect(model.get('lastName')).toEqual('Person'); - model = collection.get('456'); - expect(model.id).toEqual('456'); - expect(model.get('firstName')).toEqual('Test'); - expect(model.get('lastName')).toEqual('Person'); + expect(successStub.calledOnce).toEqual(true); + expect(errorStub.notCalled).toEqual(true); - expect(successStub.calledOnce).toEqual(true); - expect(errorStub.notCalled).toEqual(true); + done(); + }, 0) }); - function testMethodError(doCall) { + function testMethodError(doCall, done) { var successStub = sinon.stub(); var errorStub = sinon.stub(); @@ -191,20 +184,24 @@ describe('Backbone Webdav extension', function() { body: '' }); - expect(successStub.notCalled).toEqual(true); - expect(errorStub.calledOnce).toEqual(true); + setTimeout(function() { + expect(successStub.notCalled).toEqual(true); + expect(errorStub.calledOnce).toEqual(true); + + done(); + }, 0) } - it('calls error handler if error status in PROPFIND response', function() { + it('calls error handler if error status in PROPFIND response', function(done) { testMethodError(function(success, error) { var collection = new TestCollection(); collection.fetch({ success: success, error: error }); - }); + }, done); }); - it('calls error handler if error status in POST response', function() { + it('calls error handler if error status in POST response', function(done) { testMethodError(function(success, error) { var collection = new TestCollection(); collection.create({ @@ -214,7 +211,7 @@ describe('Backbone Webdav extension', function() { success: success, error: error }); - }); + }, done); }); }); describe('models', function() { @@ -281,7 +278,7 @@ describe('Backbone Webdav extension', function() { expect(model.get('married')).toEqual(true); }); - it('uses PROPFIND to fetch single model', function() { + it('uses PROPFIND to fetch single model', function(done) { var model = new TestModel({ id: '123' }); @@ -319,11 +316,15 @@ describe('Backbone Webdav extension', function() { } }); - expect(model.id).toEqual('123'); - expect(model.get('firstName')).toEqual('Hello'); - expect(model.get('lastName')).toEqual('World'); - expect(model.get('age')).toEqual(35); - expect(model.get('married')).toEqual(true); + setTimeout(function() { + expect(model.id).toEqual('123'); + expect(model.get('firstName')).toEqual('Hello'); + expect(model.get('lastName')).toEqual('World'); + expect(model.get('age')).toEqual(35); + expect(model.get('married')).toEqual(true); + + done(); + }); }); it('makes a DELETE request to destroy model', function() { var model = new TestModel({ @@ -350,7 +351,7 @@ describe('Backbone Webdav extension', function() { }); }); - function testMethodError(doCall) { + function testMethodError(doCall, done) { var successStub = sinon.stub(); var errorStub = sinon.stub(); @@ -361,20 +362,24 @@ describe('Backbone Webdav extension', function() { body: '' }); - expect(successStub.notCalled).toEqual(true); - expect(errorStub.calledOnce).toEqual(true); + setTimeout(function() { + expect(successStub.notCalled).toEqual(true); + expect(errorStub.calledOnce).toEqual(true); + + done(); + }); } - it('calls error handler if error status in PROPFIND response', function() { + it('calls error handler if error status in PROPFIND response', function(done) { testMethodError(function(success, error) { var model = new TestModel(); model.fetch({ success: success, error: error }); - }); + }, done); }); - it('calls error handler if error status in PROPPATCH response', function() { + it('calls error handler if error status in PROPPATCH response', function(done) { testMethodError(function(success, error) { var model = new TestModel(); model.save({ @@ -383,7 +388,7 @@ describe('Backbone Webdav extension', function() { success: success, error: error }); - }); + }, done); }); }); }); diff --git a/core/js/tests/specs/public/commentsSpec.js b/core/js/tests/specs/public/commentsSpec.js new file mode 100644 index 00000000000..6e15ddca6a7 --- /dev/null +++ b/core/js/tests/specs/public/commentsSpec.js @@ -0,0 +1,38 @@ +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +describe('OCP.Comments tests', function() { + function dataProvider() { + return [ + {input: 'nextcloud.com', expected: 'nextcloud.com'}, + {input: 'http://nextcloud.com', expected: '<a class="external" target="_blank" rel="noopener noreferrer" href="http://nextcloud.com">http://nextcloud.com</a>'}, + {input: 'https://nextcloud.com', expected: '<a class="external" target="_blank" rel="noopener noreferrer" href="https://nextcloud.com">nextcloud.com</a>'}, + {input: 'hi nextcloud.com', expected: 'hi nextcloud.com'}, + {input: 'hi http://nextcloud.com', expected: 'hi <a class="external" target="_blank" rel="noopener noreferrer" href="http://nextcloud.com">http://nextcloud.com</a>'}, + {input: 'hi https://nextcloud.com', expected: 'hi <a class="external" target="_blank" rel="noopener noreferrer" href="https://nextcloud.com">nextcloud.com</a>'}, + {input: 'nextcloud.com foobar', expected: 'nextcloud.com foobar'}, + {input: 'http://nextcloud.com foobar', expected: '<a class="external" target="_blank" rel="noopener noreferrer" href="http://nextcloud.com">http://nextcloud.com</a> foobar'}, + {input: 'https://nextcloud.com foobar', expected: '<a class="external" target="_blank" rel="noopener noreferrer" href="https://nextcloud.com">nextcloud.com</a> foobar'}, + {input: 'hi nextcloud.com foobar', expected: 'hi nextcloud.com foobar'}, + {input: 'hi http://nextcloud.com foobar', expected: 'hi <a class="external" target="_blank" rel="noopener noreferrer" href="http://nextcloud.com">http://nextcloud.com</a> foobar'}, + {input: 'hi https://nextcloud.com foobar', expected: 'hi <a class="external" target="_blank" rel="noopener noreferrer" href="https://nextcloud.com">nextcloud.com</a> foobar'}, + {input: 'hi help.nextcloud.com/category/topic foobar', expected: 'hi help.nextcloud.com/category/topic foobar'}, + {input: 'hi http://help.nextcloud.com/category/topic foobar', expected: 'hi <a class="external" target="_blank" rel="noopener noreferrer" href="http://help.nextcloud.com/category/topic">http://help.nextcloud.com/category/topic</a> foobar'}, + {input: 'hi https://help.nextcloud.com/category/topic foobar', expected: 'hi <a class="external" target="_blank" rel="noopener noreferrer" href="https://help.nextcloud.com/category/topic">help.nextcloud.com/category/topic</a> foobar'}, + {input: 'noreply@nextcloud.com', expected: 'noreply@nextcloud.com'}, + {input: 'hi noreply@nextcloud.com', expected: 'hi noreply@nextcloud.com'}, + {input: 'hi <noreply@nextcloud.com>', expected: 'hi <noreply@nextcloud.com>'}, + {input: 'FirebaseInstanceId.getInstance().deleteInstanceId()', expected: 'FirebaseInstanceId.getInstance().deleteInstanceId()'}, + {input: 'I mean...it', expected: 'I mean...it'}, + ]; + } + + it('should parse URLs only', function () { + dataProvider().forEach(function(data) { + var result = OCP.Comments.plainToRich(data.input); + expect(result).toEqual(data.expected); + }); + }); +}); diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index 6dd8657a077..9f75ad501d1 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -1,11 +1,7 @@ /** - * Copyright (c) 2015 Lukas Reschke <lukas@owncloud.com> - * - * This file is licensed under the Affero General Public License version 3 - * or later. - * - * See the COPYING-README file. - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later */ describe('OC.SetupChecks tests', function() { @@ -22,123 +18,6 @@ describe('OC.SetupChecks tests', function() { protocolStub.restore(); }); - describe('checkWebDAV', function() { - it('should fail with another response status code than 201 or 207', function(done) { - var async = OC.SetupChecks.checkWebDAV(); - - suite.server.requests[0].respond(200); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'Your web server is not yet set up properly to allow file synchronization because the WebDAV interface seems to be broken.', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - }]); - done(); - }); - }); - - it('should return no error with a response status code of 207', function(done) { - var async = OC.SetupChecks.checkWebDAV(); - - suite.server.requests[0].respond(207); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - - it('should return no error with a response status code of 401', function(done) { - var async = OC.SetupChecks.checkWebDAV(); - - suite.server.requests[0].respond(401); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - }); - - describe('checkWellKnownUrl', function() { - it('should fail with another response status code than 207', function(done) { - var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav/', 'http://example.org/PLACEHOLDER', true); - - suite.server.requests[0].respond(200); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'Your web server is not set up properly to resolve "/.well-known/caldav/". Further information can be found in our <a target="_blank" rel="noreferrer" href="http://example.org/admin-setup-well-known-URL">documentation</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_INFO - }]); - done(); - }); - }); - - it('should return no error with a response status code of 207', function(done) { - var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav/', 'http://example.org/PLACEHOLDER', true); - - suite.server.requests[0].respond(207); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - - it('should return no error when no check should be run', function(done) { - var async = OC.SetupChecks.checkWellKnownUrl('/.well-known/caldav/', 'http://example.org/PLACEHOLDER', false); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - }); - - describe('checkDataProtected', function() { - - oc_dataURL = "data"; - - it('should return an error if data directory is not protected', function(done) { - var async = OC.SetupChecks.checkDataProtected(); - - suite.server.requests[0].respond(200); - - async.done(function( data, s, x ){ - expect(data).toEqual([ - { - msg: 'Your data directory and your files are probably accessible from the Internet. The .htaccess file is not working. We strongly suggest that you configure your web server in a way that the data directory is no longer accessible or you move the data directory outside the web server document root.', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - }]); - done(); - }); - }); - - it('should not return an error if data directory is protected', function(done) { - var async = OC.SetupChecks.checkDataProtected(); - - suite.server.requests[0].respond(403); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - - it('should return an error if data directory is a boolean', function(done) { - - oc_dataURL = false; - - var async = OC.SetupChecks.checkDataProtected(); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - }); - describe('checkSetup', function() { it('should return an error if server has no internet connection', function(done) { var async = OC.SetupChecks.checkSetup(); @@ -149,24 +28,25 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json' }, JSON.stringify({ - isUrandomAvailable: true, - serverHasInternetConnection: false, - memcacheDocs: 'https://doc.owncloud.org/server/go.php?to=admin-performance', - forwardedForHeadersWorking: true, - isCorrectMemcachedPHPModuleInstalled: true, - hasPassedCodeIntegrityCheck: true, + generic: { + network: { + "Internet connectivity": { + severity: "warning", + description: 'This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.', + linkToDoc: null + } + }, + }, }) ); async.done(function( data, s, x ){ expect(data).toEqual([ { - msg: 'This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features.', + msg: 'This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }, { - msg: 'No memory cache has been configured. To enhance your performance please configure a memcache if available. Further information can be found in our <a target="_blank" rel="noreferrer" href="https://doc.owncloud.org/server/go.php?to=admin-performance">documentation</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_INFO - }]); + }, + ]); done(); }); }); @@ -180,25 +60,25 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json' }, JSON.stringify({ - isUrandomAvailable: true, - serverHasInternetConnection: false, - memcacheDocs: 'https://doc.owncloud.org/server/go.php?to=admin-performance', - forwardedForHeadersWorking: true, - isCorrectMemcachedPHPModuleInstalled: true, - hasPassedCodeIntegrityCheck: true, + generic: { + network: { + "Internet connectivity": { + severity: "warning", + description: 'This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.', + linkToDoc: null + } + }, + }, }) ); async.done(function( data, s, x ){ expect(data).toEqual([ { - msg: 'This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features.', + msg: 'This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING }, - { - msg: 'No memory cache has been configured. To enhance your performance please configure a memcache if available. Further information can be found in our <a target="_blank" rel="noreferrer" href="https://doc.owncloud.org/server/go.php?to=admin-performance">documentation</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_INFO - }]); + ]); done(); }); }); @@ -212,19 +92,22 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ - isUrandomAvailable: true, - serverHasInternetConnection: false, - isMemcacheConfigured: true, - forwardedForHeadersWorking: true, - isCorrectMemcachedPHPModuleInstalled: true, - hasPassedCodeIntegrityCheck: true, + generic: { + network: { + "Internet connectivity": { + severity: "warning", + description: 'This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.', + linkToDoc: null + } + }, + }, }) ); async.done(function( data, s, x ){ expect(data).toEqual([ { - msg: 'This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features.', + msg: 'This server has no working internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the internet to enjoy all features.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING } ]); @@ -232,7 +115,7 @@ describe('OC.SetupChecks tests', function() { }); }); - it('should return an error if /dev/urandom is not accessible', function(done) { + it('should return a warning if the memory limit is below the recommended value', function(done) { var async = OC.SetupChecks.checkSetup(); suite.server.requests[0].respond( @@ -241,54 +124,60 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ - isUrandomAvailable: false, - securityDocs: 'https://docs.owncloud.org/myDocs.html', - serverHasInternetConnection: true, - isMemcacheConfigured: true, - forwardedForHeadersWorking: true, - isCorrectMemcachedPHPModuleInstalled: true, - hasPassedCodeIntegrityCheck: true, + generic: { + network: { + "Internet connectivity": { + severity: "success", + description: null, + linkToDoc: null + } + }, + php: { + "Internet connectivity": { + severity: "success", + description: null, + linkToDoc: null + }, + "PHP memory limit": { + severity: "error", + description: "The PHP memory limit is below the recommended value of 512MB.", + linkToDoc: null + }, + }, + }, }) ); async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: '/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our <a target="_blank" rel="noreferrer" href="https://docs.owncloud.org/myDocs.html">documentation</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING + msg: 'The PHP memory limit is below the recommended value of 512MB.', + type: OC.SetupChecks.MESSAGE_TYPE_ERROR }]); done(); }); }); - it('should return an error if the wrong memcache PHP module is installed', function(done) { + it('should return an error if the response has no statuscode 200', function(done) { var async = OC.SetupChecks.checkSetup(); suite.server.requests[0].respond( - 200, + 500, { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, - JSON.stringify({ - isUrandomAvailable: true, - securityDocs: 'https://docs.owncloud.org/myDocs.html', - serverHasInternetConnection: true, - isMemcacheConfigured: true, - forwardedForHeadersWorking: true, - isCorrectMemcachedPHPModuleInstalled: false, - hasPassedCodeIntegrityCheck: true, - }) + JSON.stringify({data: {serverHasInternetConnectionProblems: true}}) ); async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: 'Memcached is configured as distributed cache, but the wrong PHP module "memcache" is installed. \\OC\\Memcache\\Memcached only supports "memcached" and not "memcache". See the <a target="_blank" rel="noreferrer" href="https://code.google.com/p/memcached/wiki/PHPClientComparison">memcached wiki about both modules</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING + msg: 'Error occurred while checking server setup', + type: OC.SetupChecks.MESSAGE_TYPE_ERROR }]); done(); }); }); - it('should return an error if the forwarded for headers are not working', function(done) { + it('should return an error if the php version is no longer supported', function(done) { var async = OC.SetupChecks.checkSetup(); suite.server.requests[0].respond( @@ -297,46 +186,35 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ - isUrandomAvailable: true, - serverHasInternetConnection: true, - isMemcacheConfigured: true, - forwardedForHeadersWorking: false, - reverseProxyDocs: 'https://docs.owncloud.org/foo/bar.html', - isCorrectMemcachedPHPModuleInstalled: true, - hasPassedCodeIntegrityCheck: true, + generic: { + network: { + "Internet connectivity": { + severity: "success", + description: null, + linkToDoc: null + } + }, + security: { + "Checking for PHP version": { + severity: "warning", + description: "You are currently running PHP 8.0.30. PHP 8.0 is now deprecated in Nextcloud 27. Nextcloud 28 may require at least PHP 8.1. Please upgrade to one of the officially supported PHP versions provided by the PHP Group as soon as possible.", + linkToDoc: "https://secure.php.net/supported-versions.php" + } + }, + }, }) ); async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: 'The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a target="_blank" rel="noreferrer" href="https://docs.owncloud.org/foo/bar.html">documentation</a>.', + msg: 'You are currently running PHP 8.0.30. PHP 8.0 is now deprecated in Nextcloud 27. Nextcloud 28 may require at least PHP 8.1. Please upgrade to one of the officially supported PHP versions provided by the PHP Group as soon as possible. For more details see the <a target="_blank" rel="noreferrer noopener" class="external" href="https://secure.php.net/supported-versions.php">documentation ↗</a>.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING }]); done(); }); }); - it('should return an error if the response has no statuscode 200', function(done) { - var async = OC.SetupChecks.checkSetup(); - - suite.server.requests[0].respond( - 500, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({data: {serverHasInternetConnection: false}}) - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'Error occurred while checking server setup', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - }]); - done(); - }); - }); - - it('should return an error if the php version is no longer supported', function(done) { + it('should not return an error if the protocol is http and the server generates http links', function(done) { var async = OC.SetupChecks.checkSetup(); suite.server.requests[0].respond( @@ -345,337 +223,59 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ - isUrandomAvailable: true, - securityDocs: 'https://docs.owncloud.org/myDocs.html', - serverHasInternetConnection: true, - isMemcacheConfigured: true, - forwardedForHeadersWorking: true, - phpSupported: {eol: true, version: '5.4.0'}, - isCorrectMemcachedPHPModuleInstalled: true, - hasPassedCodeIntegrityCheck: true, + generic: { + network: { + "Internet connectivity": { + severity: "success", + description: null, + linkToDoc: null + } + }, + }, }) ); async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'You are currently running PHP 5.4.0. We encourage you to upgrade your PHP version to take advantage of <a target="_blank" rel="noreferrer" href="https://secure.php.net/supported-versions.php">performance and security updates provided by the PHP Group</a> as soon as your distribution supports it.', - type: OC.SetupChecks.MESSAGE_TYPE_INFO - }]); - done(); - }); - }); - }); - - describe('checkGeneric', function() { - it('should return an error if the response has no statuscode 200', function(done) { - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond( - 500, - { - 'Content-Type': 'application/json' - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'Error occurred while checking server setup', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - },{ - msg: 'Error occurred while checking server setup', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - }]); + expect(data).toEqual([]); done(); }); }); - it('should return all errors if all headers are missing', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); + it('should return an info if there is no default phone region', function(done) { + var async = OC.SetupChecks.checkSetup(); suite.server.requests[0].respond( 200, { 'Content-Type': 'application/json', - 'Strict-Transport-Security': 'max-age=15768000' - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([ - { - msg: 'The "X-XSS-Protection" HTTP header is not configured to equal to "1; mode=block". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }, { - msg: 'The "X-Content-Type-Options" HTTP header is not configured to equal to "nosniff". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }, { - msg: 'The "X-Robots-Tag" HTTP header is not configured to equal to "none". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - - }, { - msg: 'The "X-Frame-Options" HTTP header is not configured to equal to "SAMEORIGIN". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }, { - msg: 'The "X-Download-Options" HTTP header is not configured to equal to "noopen". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }, { - msg: 'The "X-Permitted-Cross-Domain-Policies" HTTP header is not configured to equal to "none". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING }, - ]); - done(); - }); - }); - - it('should return only some errors if just some headers are missing', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond( - 200, - { - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'Strict-Transport-Security': 'max-age=15768000;preload', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - } + JSON.stringify({ + generic: { + network: { + "Internet connectivity": { + severity: "success", + description: null, + linkToDoc: null + } + }, + config: { + "Checking for default phone region": { + severity: "info", + description: "Your installation has no default phone region set. This is required to validate phone numbers in the profile settings without a country code. To allow numbers without a country code, please add \"default_phone_region\" with the respective ISO 3166-1 code of the region to your config file.", + linkToDoc: "https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements" + }, + }, + }, + }) ); async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: 'The "X-XSS-Protection" HTTP header is not configured to equal to "1; mode=block". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING, - }, { - msg: 'The "X-Content-Type-Options" HTTP header is not configured to equal to "nosniff". This is a potential security or privacy risk and we recommend adjusting this setting.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING + msg: 'Your installation has no default phone region set. This is required to validate phone numbers in the profile settings without a country code. To allow numbers without a country code, please add "default_phone_region" with the respective ISO 3166-1 code of the region to your config file. For more details see the <a target="_blank" rel="noreferrer noopener" class="external" href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements">documentation ↗</a>.', + type: OC.SetupChecks.MESSAGE_TYPE_INFO }]); done(); }); }); - - it('should return none errors if all headers are there', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond( - 200, - { - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'Strict-Transport-Security': 'max-age=15768000', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - }); - - it('should return a SSL warning if HTTPS is not used', function(done) { - protocolStub.returns('http'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, - { - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'You are accessing this site via HTTP. We strongly suggest you configure your server to require using HTTPS instead as described in our <a href="#admin-tips">security tips</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }]); - done(); - }); - }); - - it('should return an error if the response has no statuscode 200', function(done) { - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond( - 500, - { - 'Content-Type': 'application/json' - }, - JSON.stringify({data: {serverHasInternetConnection: false}}) - ); - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'Error occurred while checking server setup', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - }, { - msg: 'Error occurred while checking server setup', - type: OC.SetupChecks.MESSAGE_TYPE_ERROR - }]); - done(); - }); - }); - - it('should return a SSL warning if SSL used without Strict-Transport-Security-Header', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, - { - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'The "Strict-Transport-Security" HTTP header is not configured to at least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips" rel="noreferrer">security tips</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }]); - done(); - }); - }); - - it('should return a SSL warning if SSL used with to small Strict-Transport-Security-Header', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, - { - 'Strict-Transport-Security': 'max-age=15767999', - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'The "Strict-Transport-Security" HTTP header is not configured to at least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips" rel="noreferrer">security tips</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }]); - done(); - }); - }); - - it('should return a SSL warning if SSL used with to a bogus Strict-Transport-Security-Header', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, - { - 'Strict-Transport-Security': 'iAmABogusHeader342', - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - } - ); - - async.done(function( data, s, x ){ - expect(data).toEqual([{ - msg: 'The "Strict-Transport-Security" HTTP header is not configured to at least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips" rel="noreferrer">security tips</a>.', - type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }]); - done(); - }); - }); - - it('should return no SSL warning if SSL used with to exact the minimum Strict-Transport-Security-Header', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, { - 'Strict-Transport-Security': 'max-age=15768000', - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - }); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - - it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, { - 'Strict-Transport-Security': 'max-age=99999999', - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - }); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - - it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header and includeSubDomains parameter', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, { - 'Strict-Transport-Security': 'max-age=99999999; includeSubDomains', - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - }); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); - }); - - it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header and includeSubDomains and preload parameter', function(done) { - protocolStub.returns('https'); - var async = OC.SetupChecks.checkGeneric(); - - suite.server.requests[0].respond(200, { - 'Strict-Transport-Security': 'max-age=99999999; preload; includeSubDomains', - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', - 'X-Frame-Options': 'SAMEORIGIN', - 'X-Download-Options': 'noopen', - 'X-Permitted-Cross-Domain-Policies': 'none', - }); - - async.done(function( data, s, x ){ - expect(data).toEqual([]); - done(); - }); }); }); diff --git a/core/js/tests/specs/shareSpec.js b/core/js/tests/specs/shareSpec.js deleted file mode 100644 index 5c76ea600b8..00000000000 --- a/core/js/tests/specs/shareSpec.js +++ /dev/null @@ -1,243 +0,0 @@ -/** -* ownCloud -* -* @author Vincent Petry -* @copyright 2014 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ - -/* global oc_appconfig */ -describe('OC.Share tests', function() { - describe('markFileAsShared', function() { - var $file; - var tipsyStub; - - beforeEach(function() { - tipsyStub = sinon.stub($.fn, 'tipsy'); - $file = $('<tr><td class="filename"><div class="thumbnail"></div><span class="name">File name</span></td></tr>'); - $file.find('.filename').append( - '<span class="fileactions">' + - '<a href="#" class="action action-share" data-action="Share">' + - '<img></img><span> Share</span>' + - '</a>' + - '</span>' - ); - }); - afterEach(function() { - $file = null; - tipsyStub.restore(); - }); - describe('displaying the share owner', function() { - function checkOwner(input, output, title) { - var $action; - - $file.attr('data-share-owner', input); - OC.Share.markFileAsShared($file); - - $action = $file.find('.action-share>span'); - expect($action.text().trim()).toEqual(output); - if (_.isString(title)) { - expect($action.find('.remoteAddress').attr('title')).toEqual(title); - } else { - expect($action.find('.remoteAddress').attr('title')).not.toBeDefined(); - } - expect(tipsyStub.calledOnce).toEqual(true); - tipsyStub.reset(); - } - - it('displays the local share owner as is', function() { - checkOwner('User One', 'User One', null); - }); - it('displays the user name part of a remote share owner', function() { - checkOwner( - 'User One@someserver.com', - 'User One@…', - 'User One@someserver.com' - ); - checkOwner( - 'User One@someserver.com/', - 'User One@…', - 'User One@someserver.com' - ); - checkOwner( - 'User One@someserver.com/root/of/owncloud', - 'User One@…', - 'User One@someserver.com' - ); - }); - it('displays the user name part with domain of a remote share owner', function() { - checkOwner( - 'User One@example.com@someserver.com', - 'User One@example.com', - 'User One@example.com@someserver.com' - ); - checkOwner( - 'User One@example.com@someserver.com/', - 'User One@example.com', - 'User One@example.com@someserver.com' - ); - checkOwner( - 'User One@example.com@someserver.com/root/of/owncloud', - 'User One@example.com', - 'User One@example.com@someserver.com' - ); - }); - }); - - describe('displaying the folder icon', function() { - function checkIcon(expectedImage) { - var imageUrl = OC.TestUtil.getImageUrl($file.find('.filename .thumbnail')); - expectedIcon = OC.imagePath('core', expectedImage); - expect(imageUrl).toEqual(expectedIcon); - } - - it('shows a plain folder icon for non-shared folders', function() { - $file.attr('data-type', 'dir'); - OC.Share.markFileAsShared($file); - - checkIcon('filetypes/folder'); - }); - it('shows a shared folder icon for folders shared with another user', function() { - $file.attr('data-type', 'dir'); - OC.Share.markFileAsShared($file, true); - - checkIcon('filetypes/folder-shared'); - }); - it('shows a shared folder icon for folders shared with the current user', function() { - $file.attr('data-type', 'dir'); - $file.attr('data-share-owner', 'someoneelse'); - OC.Share.markFileAsShared($file); - - checkIcon('filetypes/folder-shared'); - }); - it('shows a link folder icon for folders shared with link', function() { - $file.attr('data-type', 'dir'); - OC.Share.markFileAsShared($file, false, true); - - checkIcon('filetypes/folder-public'); - }); - it('shows a link folder icon for folders shared with both link and another user', function() { - $file.attr('data-type', 'dir'); - OC.Share.markFileAsShared($file, true, true); - - checkIcon('filetypes/folder-public'); - }); - it('shows a link folder icon for folders reshared with link', function() { - $file.attr('data-type', 'dir'); - $file.attr('data-share-owner', 'someoneelse'); - OC.Share.markFileAsShared($file, false, true); - - checkIcon('filetypes/folder-public'); - }); - it('shows external storage icon if external mount point', function() { - $file.attr('data-type', 'dir'); - $file.attr('data-mountType', 'external'); - OC.Share.markFileAsShared($file, false, false); - - checkIcon('filetypes/folder-external'); - }); - }); - - describe('displaying the recipoients', function() { - function checkRecipients(input, output, title) { - var $action; - - $file.attr('data-share-recipients', input); - OC.Share.markFileAsShared($file, true); - - $action = $file.find('.action-share>span'); - expect($action.text().trim()).toEqual(output); - if (_.isString(title)) { - expect($action.find('.remoteAddress').attr('title')).toEqual(title); - } else if (_.isArray(title)) { - var tooltips = $action.find('.remoteAddress'); - expect(tooltips.length).toEqual(title.length); - - tooltips.each(function(i) { - expect($(this).attr('title')).toEqual(title[i]); - }); - } else { - expect($action.find('.remoteAddress').attr('title')).not.toBeDefined(); - } - expect(tipsyStub.calledOnce).toEqual(true); - tipsyStub.reset(); - } - - it('displays the local share owner as is', function() { - checkRecipients('User One', 'Shared with User One', null); - }); - it('displays the user name part of a remote recipient', function() { - checkRecipients( - 'User One@someserver.com', - 'Shared with User One@…', - 'User One@someserver.com' - ); - checkRecipients( - 'User One@someserver.com/', - 'Shared with User One@…', - 'User One@someserver.com' - ); - checkRecipients( - 'User One@someserver.com/root/of/owncloud', - 'Shared with User One@…', - 'User One@someserver.com' - ); - }); - it('displays the user name part with domain of a remote share owner', function() { - checkRecipients( - 'User One@example.com@someserver.com', - 'Shared with User One@example.com', - 'User One@example.com@someserver.com' - ); - checkRecipients( - 'User One@example.com@someserver.com/', - 'Shared with User One@example.com', - 'User One@example.com@someserver.com' - ); - checkRecipients( - 'User One@example.com@someserver.com/root/of/owncloud', - 'Shared with User One@example.com', - 'User One@example.com@someserver.com' - ); - }); - it('display multiple remote recipients', function() { - checkRecipients( - 'One@someserver.com, two@otherserver.com', - 'Shared with One@…, two@…', - ['One@someserver.com', 'two@otherserver.com'] - ); - checkRecipients( - 'One@someserver.com/, two@otherserver.com', - 'Shared with One@…, two@…', - ['One@someserver.com', 'two@otherserver.com'] - ); - checkRecipients( - 'One@someserver.com/root/of/owncloud, two@otherserver.com', - 'Shared with One@…, two@…', - ['One@someserver.com', 'two@otherserver.com'] - ); - }); - it('display mixed recipients', function() { - checkRecipients( - 'One, two@otherserver.com', - 'Shared with One, two@…', - ['two@otherserver.com'] - ); - }); - }); - }); -}); - diff --git a/core/js/tests/specs/sharedialogshareelistview.js b/core/js/tests/specs/sharedialogshareelistview.js deleted file mode 100644 index cef97469753..00000000000 --- a/core/js/tests/specs/sharedialogshareelistview.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * ownCloud - * - * @author Tom Needham - * @copyright 2015 Tom Needham <tom@owncloud.com> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - * - */ - -/* global oc_appconfig */ -describe('OC.Share.ShareDialogShareeListView', function () { - - var oldCurrentUser; - var fileInfoModel; - var configModel; - var shareModel; - var listView; - var updateShareStub; - - beforeEach(function () { - /* jshint camelcase:false */ - oldAppConfig = _.extend({}, oc_appconfig.core); - oc_appconfig.core.enforcePasswordForPublicLink = false; - - $('#testArea').append('<input id="mailNotificationEnabled" name="mailNotificationEnabled" type="hidden" value="yes">'); - - fileInfoModel = new OCA.Files.FileInfoModel({ - id: 123, - name: 'shared_file_name.txt', - path: '/subdir', - size: 100, - mimetype: 'text/plain', - permissions: 31, - sharePermissions: 31 - }); - - var attributes = { - itemType: fileInfoModel.isDirectory() ? 'folder' : 'file', - itemSource: fileInfoModel.get('id'), - possiblePermissions: 31, - permissions: 31 - }; - - shareModel = new OC.Share.ShareItemModel(attributes, { - configModel: configModel, - fileInfoModel: fileInfoModel - }); - - configModel = new OC.Share.ShareConfigModel({ - enforcePasswordForPublicLink: false, - isResharingAllowed: true, - enforcePasswordForPublicLink: false, - isDefaultExpireDateEnabled: false, - isDefaultExpireDateEnforced: false, - defaultExpireDate: 7 - }); - - listView = new OC.Share.ShareDialogShareeListView({ - configModel: configModel, - model: shareModel - }); - - // required for proper event propagation when simulating clicks in some cases (jquery bugs) - $('#testArea').append(listView.$el); - - shareModel.set({ - linkShare: {isLinkShare: false} - }); - - oldCurrentUser = OC.currentUser; - OC.currentUser = 'user0'; - updateShareStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'updateShare'); - }); - - afterEach(function () { - OC.currentUser = oldCurrentUser; - /* jshint camelcase:false */ - oc_appconfig.core = oldAppConfig; - listView.remove(); - updateShareStub.restore(); - }); - - describe('Manages checkbox events correctly', function () { - it('Checks cruds boxes when edit box checked', function () { - shareModel.set('shares', [{ - id: 100, - item_source: 123, - permissions: 1, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - }]); - listView.render(); - listView.$el.find("input[name='edit']").click(); - expect(listView.$el.find("input[name='update']").is(':checked')).toEqual(true); - expect(updateShareStub.calledOnce).toEqual(true); - }); - - it('Checks edit box when create/update/delete are checked', function () { - shareModel.set('shares', [{ - id: 100, - item_source: 123, - permissions: 1, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - }]); - listView.render(); - listView.$el.find("input[name='update']").click(); - expect(listView.$el.find("input[name='edit']").is(':checked')).toEqual(true); - expect(updateShareStub.calledOnce).toEqual(true); - }); - - it('shows cruds checkboxes when toggled', function () { - shareModel.set('shares', [{ - id: 100, - item_source: 123, - permissions: 1, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - }]); - listView.render(); - listView.$el.find('a.showCruds').click(); - expect(listView.$el.find('li.cruds').hasClass('hidden')).toEqual(false); - }); - - it('sends notification to user when checkbox clicked', function () { - shareModel.set('shares', [{ - id: 100, - item_source: 123, - permissions: 1, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - }]); - listView.render(); - var notificationStub = sinon.stub(listView.model, 'sendNotificationForShare'); - listView.$el.find("input[name='mailNotification']").click(); - expect(notificationStub.called).toEqual(true); - notificationStub.restore(); - }); - - }); - -}); diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js deleted file mode 100644 index 23214a7fe86..00000000000 --- a/core/js/tests/specs/sharedialogviewSpec.js +++ /dev/null @@ -1,1064 +0,0 @@ -/** -* ownCloud -* -* @author Vincent Petry -* @copyright 2015 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ - -/* global oc_appconfig */ -describe('OC.Share.ShareDialogView', function() { - var $container; - var oldAppConfig; - var autocompleteStub; - var oldEnableAvatars; - var avatarStub; - var placeholderStub; - var oldCurrentUser; - var saveLinkShareStub; - - var fetchStub; - var notificationStub; - - var configModel; - var shareModel; - var fileInfoModel; - var dialog; - - beforeEach(function() { - // horrible parameters - $('#testArea').append('<input id="allowShareWithLink" type="hidden" value="yes">'); - $('#testArea').append('<input id="mailPublicNotificationEnabled" name="mailPublicNotificationEnabled" type="hidden" value="yes">'); - $container = $('#shareContainer'); - /* jshint camelcase:false */ - oldAppConfig = _.extend({}, oc_appconfig.core); - oc_appconfig.core.enforcePasswordForPublicLink = false; - - fetchStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'fetch'); - saveLinkShareStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'saveLinkShare'); - - fileInfoModel = new OCA.Files.FileInfoModel({ - id: 123, - name: 'shared_file_name.txt', - path: '/subdir', - size: 100, - mimetype: 'text/plain', - permissions: 31, - sharePermissions: 31 - }); - - var attributes = { - itemType: fileInfoModel.isDirectory() ? 'folder' : 'file', - itemSource: fileInfoModel.get('id'), - possiblePermissions: 31, - permissions: 31 - }; - configModel = new OC.Share.ShareConfigModel({ - enforcePasswordForPublicLink: false, - isResharingAllowed: true, - enforcePasswordForPublicLink: false, - isDefaultExpireDateEnabled: false, - isDefaultExpireDateEnforced: false, - defaultExpireDate: 7 - }); - shareModel = new OC.Share.ShareItemModel(attributes, { - configModel: configModel, - fileInfoModel: fileInfoModel - }); - dialog = new OC.Share.ShareDialogView({ - configModel: configModel, - model: shareModel - }); - - // required for proper event propagation when simulating clicks in some cases (jquery bugs) - $('#testArea').append(dialog.$el); - - // triggers rendering - shareModel.set({ - shares: [], - linkShare: {isLinkShare: false} - }); - - autocompleteStub = sinon.stub($.fn, 'autocomplete', function() { - // dummy container with the expected attributes - if (!$(this).length) { - // simulate the real autocomplete that returns - // nothing at all when no element is specified - // (and potentially break stuff) - return null; - } - var $el = $('<div></div>').data('ui-autocomplete', {}); - return $el; - }); - - oldEnableAvatars = oc_config.enable_avatars; - oc_config.enable_avatars = false; - avatarStub = sinon.stub($.fn, 'avatar'); - placeholderStub = sinon.stub($.fn, 'imageplaceholder'); - - oldCurrentUser = OC.currentUser; - OC.currentUser = 'user0'; - }); - afterEach(function() { - OC.currentUser = oldCurrentUser; - /* jshint camelcase:false */ - oc_appconfig.core = oldAppConfig; - - dialog.remove(); - fetchStub.restore(); - saveLinkShareStub.restore(); - - autocompleteStub.restore(); - avatarStub.restore(); - placeholderStub.restore(); - oc_config.enable_avatars = oldEnableAvatars; - }); - describe('Share with link', function() { - // TODO: test ajax calls - // TODO: test password field visibility (whenever enforced or not) - it('update password on focus out', function() { - $('#allowShareWithLink').val('yes'); - - dialog.model.set('linkShare', { - isLinkShare: true - }); - dialog.render(); - - // Enable password, enter password and focusout - dialog.$el.find('[name=showPassword]').click(); - dialog.$el.find('.linkPassText').focus(); - dialog.$el.find('.linkPassText').val('foo'); - dialog.$el.find('.linkPassText').focusout(); - - expect(saveLinkShareStub.calledOnce).toEqual(true); - expect(saveLinkShareStub.firstCall.args[0]).toEqual({ - password: 'foo' - }); - }); - it('update password on enter', function() { - $('#allowShareWithLink').val('yes'); - - dialog.model.set('linkShare', { - isLinkShare: true - }); - dialog.render(); - - // Toggle linkshare - dialog.$el.find('.linkCheckbox').click(); - - // Enable password and enter password - dialog.$el.find('[name=showPassword]').click(); - dialog.$el.find('.linkPassText').focus(); - dialog.$el.find('.linkPassText').val('foo'); - dialog.$el.find('.linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - - expect(saveLinkShareStub.calledOnce).toEqual(true); - expect(saveLinkShareStub.firstCall.args[0]).toEqual({ - password: 'foo' - }); - }); - it('shows share with link checkbox when allowed', function() { - $('#allowShareWithLink').val('yes'); - - dialog.render(); - - expect(dialog.$el.find('.linkCheckbox').length).toEqual(1); - }); - it('does not show share with link checkbox when not allowed', function() { - $('#allowShareWithLink').val('no'); - - dialog.render(); - - expect(dialog.$el.find('.linkCheckbox').length).toEqual(0); - expect(dialog.$el.find('.shareWithField').length).toEqual(1); - }); - it('shows populated link share when a link share exists', function() { - // this is how the OC.Share class does it... - var link = parent.location.protocol + '//' + location.host + - OC.generateUrl('/s/') + 'tehtoken'; - shareModel.set('linkShare', { - isLinkShare: true, - token: 'tehtoken', - link: link, - expiration: '', - permissions: OC.PERMISSION_READ, - stime: 1403884258, - }); - - dialog.render(); - - expect(dialog.$el.find('.linkCheckbox').prop('checked')).toEqual(true); - expect(dialog.$el.find('.linkText').val()).toEqual(link); - }); - it('autofocus link text when clicked', function() { - $('#allowShareWithLink').val('yes'); - - dialog.model.set('linkShare', { - isLinkShare: true - }); - dialog.render(); - - var focusStub = sinon.stub($.fn, 'focus'); - var selectStub = sinon.stub($.fn, 'select'); - dialog.$el.find('.linkText').click(); - - expect(focusStub.calledOnce).toEqual(true); - expect(selectStub.calledOnce).toEqual(true); - - focusStub.restore(); - selectStub.restore(); - }); - describe('password', function() { - var slideToggleStub; - - beforeEach(function() { - $('#allowShareWithLink').val('yes'); - configModel.set({ - enforcePasswordForPublicLink: false - }); - - slideToggleStub = sinon.stub($.fn, 'slideToggle'); - }); - afterEach(function() { - slideToggleStub.restore(); - }); - - it('enforced but toggled does not fire request', function() { - configModel.set('enforcePasswordForPublicLink', true); - dialog.render(); - - dialog.$el.find('.linkCheckbox').click(); - - // The password linkPass field is shown (slideToggle is called). - // No request is made yet - expect(slideToggleStub.callCount).toEqual(1); - expect(slideToggleStub.getCall(0).thisValue.eq(0).attr('id')).toEqual('linkPass'); - expect(fakeServer.requests.length).toEqual(0); - - // Now untoggle share by link - dialog.$el.find('.linkCheckbox').click(); - dialog.render(); - - // Password field disappears and no ajax requests have been made - expect(fakeServer.requests.length).toEqual(0); - expect(slideToggleStub.callCount).toEqual(2); - expect(slideToggleStub.getCall(1).thisValue.eq(0).attr('id')).toEqual('linkPass'); - }); - }); - describe('expiration date', function() { - var shareData; - var shareItem; - var clock; - var expectedMinDate; - - beforeEach(function() { - // pick a fake date - clock = sinon.useFakeTimers(new Date(2014, 0, 20, 14, 0, 0).getTime()); - expectedMinDate = new Date(2014, 0, 21, 14, 0, 0); - - configModel.set({ - enforcePasswordForPublicLink: false, - isDefaultExpireDateEnabled: false, - isDefaultExpireDateEnforced: false, - defaultExpireDate: 7 - }); - - shareModel.set('linkShare', { - isLinkShare: true, - token: 'tehtoken', - permissions: OC.PERMISSION_READ, - expiration: null - }); - }); - afterEach(function() { - clock.restore(); - }); - - it('does not check expiration date checkbox when no date was set', function() { - shareModel.get('linkShare').expiration = null; - dialog.render(); - - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false); - expect(dialog.$el.find('.datepicker').val()).toEqual(''); - }); - it('does not check expiration date checkbox for new share', function() { - dialog.render(); - - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false); - expect(dialog.$el.find('.datepicker').val()).toEqual(''); - }); - it('checks expiration date checkbox and populates field when expiration date was set', function() { - shareModel.get('linkShare').expiration = '2014-02-01 00:00:00'; - dialog.render(); - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); - expect(dialog.$el.find('.datepicker').val()).toEqual('01-02-2014'); - }); - it('sets default date when default date setting is enabled', function() { - configModel.set('isDefaultExpireDateEnabled', true); - dialog.render(); - dialog.$el.find('.linkCheckbox').click(); - // here fetch would be called and the server returns the expiration date - shareModel.get('linkShare').expiration = '2014-1-27 00:00:00'; - dialog.render(); - - // enabled by default - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); - expect(dialog.$el.find('.datepicker').val()).toEqual('27-01-2014'); - - // disabling is allowed - dialog.$el.find('[name=expirationCheckbox]').click(); - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false); - }); - it('enforces default date when enforced date setting is enabled', function() { - configModel.set({ - isDefaultExpireDateEnabled: true, - isDefaultExpireDateEnforced: true - }); - dialog.render(); - dialog.$el.find('.linkCheckbox').click(); - // here fetch would be called and the server returns the expiration date - shareModel.get('linkShare').expiration = '2014-1-27 00:00:00'; - dialog.render(); - - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); - expect(dialog.$el.find('.datepicker').val()).toEqual('27-01-2014'); - - // disabling is not allowed - expect(dialog.$el.find('[name=expirationCheckbox]').prop('disabled')).toEqual(true); - dialog.$el.find('[name=expirationCheckbox]').click(); - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); - }); - it('enforces default date when enforced date setting is enabled and password is enforced', function() { - configModel.set({ - enforcePasswordForPublicLink: true, - isDefaultExpireDateEnabled: true, - isDefaultExpireDateEnforced: true - }); - dialog.render(); - dialog.$el.find('.linkCheckbox').click(); - // here fetch would be called and the server returns the expiration date - shareModel.get('linkShare').expiration = '2014-1-27 00:00:00'; - dialog.render(); - - //Enter password - dialog.$el.find('.linkPassText').val('foo'); - dialog.$el.find('.linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); - expect(dialog.$el.find('.datepicker').val()).toEqual('27-01-2014'); - - // disabling is not allowed - expect(dialog.$el.find('[name=expirationCheckbox]').prop('disabled')).toEqual(true); - dialog.$el.find('[name=expirationCheckbox]').click(); - expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); - }); - it('sets picker minDate to today and no maxDate by default', function() { - dialog.render(); - dialog.$el.find('.linkCheckbox').click(); - dialog.$el.find('[name=expirationCheckbox]').click(); - expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); - expect($.datepicker._defaults.maxDate).toEqual(null); - }); - it('limits the date range to X days after share time when enforced', function() { - configModel.set({ - isDefaultExpireDateEnabled: true, - isDefaultExpireDateEnforced: true - }); - dialog.render(); - dialog.$el.find('.linkCheckbox').click(); - expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); - expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); - }); - it('limits the date range to X days after share time when enforced, even when redisplayed the next days', function() { - // item exists, was created two days ago - var shareItem = shareModel.get('linkShare'); - shareItem.expiration = '2014-1-27'; - // share time has time component but must be stripped later - shareItem.stime = new Date(2014, 0, 20, 11, 0, 25).getTime() / 1000; - configModel.set({ - isDefaultExpireDateEnabled: true, - isDefaultExpireDateEnforced: true - }); - dialog.render(); - expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); - expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); - }); - }); - describe('send link by email', function() { - var sendEmailPrivateLinkStub; - var clock; - - beforeEach(function() { - configModel.set({ - isMailPublicNotificationEnabled: true - }); - - shareModel.set('linkShare', { - isLinkShare: true, - token: 'tehtoken', - permissions: OC.PERMISSION_READ, - expiration: null - }); - - sendEmailPrivateLinkStub = sinon.stub(dialog.model, "sendEmailPrivateLink"); - clock = sinon.useFakeTimers(); - }); - afterEach(function() { - sendEmailPrivateLinkStub.restore(); - clock.restore(); - }); - - it('displays form when sending emails is enabled', function() { - $('input[name=mailPublicNotificationEnabled]').val('yes'); - dialog.render(); - expect(dialog.$('.emailPrivateLinkForm').length).toEqual(1); - }); - it('form not rendered when sending emails is disabled', function() { - $('input[name=mailPublicNotificationEnabled]').val('no'); - dialog.render(); - expect(dialog.$('.emailPrivateLinkForm').length).toEqual(0); - }); - it('input cleared on success', function() { - var defer = $.Deferred(); - sendEmailPrivateLinkStub.returns(defer.promise()); - - $('input[name=mailPublicNotificationEnabled]').val('yes'); - dialog.render(); - - dialog.$el.find('.emailPrivateLinkForm .emailField').val('a@b.c'); - dialog.$el.find('.emailPrivateLinkForm').trigger('submit'); - - expect(sendEmailPrivateLinkStub.callCount).toEqual(1); - expect(dialog.$el.find('.emailPrivateLinkForm .emailField').val()).toEqual('Sending ...'); - - defer.resolve(); - expect(dialog.$el.find('.emailPrivateLinkForm .emailField').val()).toEqual('Email sent'); - - clock.tick(2000); - expect(dialog.$el.find('.emailPrivateLinkForm .emailField').val()).toEqual(''); - }); - it('input not cleared on failure', function() { - var defer = $.Deferred(); - sendEmailPrivateLinkStub.returns(defer.promise()); - - $('input[name=mailPublicNotificationEnabled]').val('yes'); - dialog.render(); - - dialog.$el.find('.emailPrivateLinkForm .emailField').val('a@b.c'); - dialog.$el.find('.emailPrivateLinkForm').trigger('submit'); - - expect(sendEmailPrivateLinkStub.callCount).toEqual(1); - expect(dialog.$el.find('.emailPrivateLinkForm .emailField').val()).toEqual('Sending ...'); - - defer.reject(); - expect(dialog.$el.find('.emailPrivateLinkForm .emailField').val()).toEqual('a@b.c'); - }); - }); - }); - describe('check for avatar', function() { - beforeEach(function() { - shareModel.set({ - reshare: { - share_type: OC.Share.SHARE_TYPE_USER, - uid_owner: 'owner', - displayname_owner: 'Owner', - permissions: 31 - }, - shares: [{ - id: 100, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - },{ - id: 101, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group', - share_with_displayname: 'group' - },{ - id: 102, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_REMOTE, - share_with: 'foo@bar.com/baz', - share_with_displayname: 'foo@bar.com/baz' - - }] - }); - }); - - describe('avatars enabled', function() { - beforeEach(function() { - oc_config.enable_avatars = true; - avatarStub.reset(); - dialog.render(); - }); - - afterEach(function() { - oc_config.enable_avatars = false; - }); - - it('test correct function calls', function() { - expect(avatarStub.calledTwice).toEqual(true); - expect(placeholderStub.calledTwice).toEqual(true); - expect(dialog.$('.shareWithList').children().length).toEqual(3); - expect(dialog.$('.avatar').length).toEqual(4); - }); - - it('test avatar owner', function() { - var args = avatarStub.getCall(0).args; - expect(args.length).toEqual(2); - expect(args[0]).toEqual('owner'); - }); - - it('test avatar user', function() { - var args = avatarStub.getCall(1).args; - expect(args.length).toEqual(2); - expect(args[0]).toEqual('user1'); - }); - - it('test avatar for groups', function() { - var args = placeholderStub.getCall(0).args; - expect(args.length).toEqual(1); - expect(args[0]).toEqual('group ' + OC.Share.SHARE_TYPE_GROUP); - }); - - it('test avatar for remotes', function() { - var args = placeholderStub.getCall(1).args; - expect(args.length).toEqual(1); - expect(args[0]).toEqual('foo@bar.com/baz ' + OC.Share.SHARE_TYPE_REMOTE); - }); - }); - - describe('avatars disabled', function() { - beforeEach(function() { - dialog.render(); - }); - - it('no avatar classes', function() { - expect($('.avatar').length).toEqual(0); - expect(avatarStub.callCount).toEqual(0); - expect(placeholderStub.callCount).toEqual(0); - }); - }); - }); - describe('remote sharing', function() { - it('shows remote share info when allowed', function() { - configModel.set({ - isRemoteShareAllowed: true - }); - dialog.render(); - expect(dialog.$el.find('.shareWithRemoteInfo').length).toEqual(1); - }); - it('does not show remote share info when not allowed', function() { - configModel.set({ - isRemoteShareAllowed: false - }); - dialog.render(); - expect(dialog.$el.find('.shareWithRemoteInfo').length).toEqual(0); - }); - }); - describe('autocompletion of users', function() { - it('triggers autocomplete display and focus with data when ajax search succeeds', function () { - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'bob'}, response); - var jsonData = JSON.stringify({ - 'ocs' : { - 'meta' : { - 'status' : 'success', - 'statuscode' : 100, - 'message' : null - }, - 'data' : { - 'exact' : { - 'users' : [], - 'groups' : [], - 'remotes': [] - }, - 'users' : [{'label': 'bob', 'value': {'shareType': 0, 'shareWith': 'test'}}], - 'groups' : [], - 'remotes': [] - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly(JSON.parse(jsonData).ocs.data.users)).toEqual(true); - expect(autocompleteStub.calledWith("option", "autoFocus", true)).toEqual(true); - }); - - describe('filter out', function() { - it('the current user', function () { - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'bob'}, response); - var jsonData = JSON.stringify({ - 'ocs': { - 'meta': { - 'status': 'success', - 'statuscode': 100, - 'message': null - }, - 'data': { - 'exact': { - 'users': [], - 'groups': [], - 'remotes': [] - }, - 'users': [ - { - 'label': 'bob', - 'value': { - 'shareType': 0, - 'shareWith': OC.currentUser - } - }, - { - 'label': 'bobby', - 'value': { - 'shareType': 0, - 'shareWith': 'imbob' - } - } - ], - 'groups': [], - 'remotes': [] - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly([{ - 'label': 'bobby', - 'value': {'shareType': 0, 'shareWith': 'imbob'} - }])).toEqual(true); - expect(autocompleteStub.calledWith("option", "autoFocus", true)).toEqual(true); - }); - - it('the share owner', function () { - shareModel.set({ - reshare: { - uid_owner: 'user1' - }, - shares: [], - permissions: OC.PERMISSION_READ - }); - - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'bob'}, response); - var jsonData = JSON.stringify({ - 'ocs': { - 'meta': { - 'status': 'success', - 'statuscode': 100, - 'message': null - }, - 'data': { - 'exact': { - 'users': [], - 'groups': [], - 'remotes': [] - }, - 'users': [ - { - 'label': 'bob', - 'value': { - 'shareType': 0, - 'shareWith': 'user1' - } - }, - { - 'label': 'bobby', - 'value': { - 'shareType': 0, - 'shareWith': 'imbob' - } - } - ], - 'groups': [], - 'remotes': [] - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly([{ - 'label': 'bobby', - 'value': {'shareType': 0, 'shareWith': 'imbob'} - }])).toEqual(true); - expect(autocompleteStub.calledWith("option", "autoFocus", true)).toEqual(true); - }); - - describe('already shared with', function () { - beforeEach(function() { - shareModel.set({ - reshare: {}, - shares: [{ - id: 100, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - },{ - id: 101, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group', - share_with_displayname: 'group' - },{ - id: 102, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_REMOTE, - share_with: 'foo@bar.com/baz', - share_with_displayname: 'foo@bar.com/baz' - - }] - }); - }); - - it('users', function () { - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'bob'}, response); - var jsonData = JSON.stringify({ - 'ocs': { - 'meta': { - 'status': 'success', - 'statuscode': 100, - 'message': null - }, - 'data': { - 'exact': { - 'users': [], - 'groups': [], - 'remotes': [] - }, - 'users': [ - { - 'label': 'bob', - 'value': { - 'shareType': OC.Share.SHARE_TYPE_USER, - 'shareWith': 'user1' - } - }, - { - 'label': 'bobby', - 'value': { - 'shareType': OC.Share.SHARE_TYPE_USER, - 'shareWith': 'imbob' - } - } - ], - 'groups': [], - 'remotes': [] - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly([{ - 'label': 'bobby', - 'value': {'shareType': OC.Share.SHARE_TYPE_USER, 'shareWith': 'imbob'} - }])).toEqual(true); - expect(autocompleteStub.calledWith("option", "autoFocus", true)).toEqual(true); - }); - - it('groups', function () { - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'group'}, response); - var jsonData = JSON.stringify({ - 'ocs': { - 'meta': { - 'status': 'success', - 'statuscode': 100, - 'message': null - }, - 'data': { - 'exact': { - 'users': [], - 'groups': [], - 'remotes': [] - }, - 'users': [], - 'groups': [ - { - 'label': 'group', - 'value': { - 'shareType': OC.Share.SHARE_TYPE_GROUP, - 'shareWith': 'group' - } - }, - { - 'label': 'group2', - 'value': { - 'shareType': OC.Share.SHARE_TYPE_GROUP, - 'shareWith': 'group2' - } - } - ], - 'remotes': [] - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly([{ - 'label': 'group2', - 'value': {'shareType': OC.Share.SHARE_TYPE_GROUP, 'shareWith': 'group2'} - }])).toEqual(true); - expect(autocompleteStub.calledWith("option", "autoFocus", true)).toEqual(true); - }); - - it('remotes', function () { - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'bob'}, response); - var jsonData = JSON.stringify({ - 'ocs': { - 'meta': { - 'status': 'success', - 'statuscode': 100, - 'message': null - }, - 'data': { - 'exact': { - 'users': [], - 'groups': [], - 'remotes': [] - }, - 'users': [], - 'groups': [], - 'remotes': [ - { - 'label': 'foo@bar.com/baz', - 'value': { - 'shareType': OC.Share.SHARE_TYPE_REMOTE, - 'shareWith': 'foo@bar.com/baz' - } - }, - { - 'label': 'foo2@bar.com/baz', - 'value': { - 'shareType': OC.Share.SHARE_TYPE_REMOTE, - 'shareWith': 'foo2@bar.com/baz' - } - } - ] - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly([{ - 'label': 'foo2@bar.com/baz', - 'value': {'shareType': OC.Share.SHARE_TYPE_REMOTE, 'shareWith': 'foo2@bar.com/baz'} - }])).toEqual(true); - expect(autocompleteStub.calledWith("option", "autoFocus", true)).toEqual(true); - }); - }); - }); - - it('gracefully handles successful ajax call with failure content', function () { - dialog.render(); - var response = sinon.stub(); - dialog.autocompleteHandler({term: 'bob'}, response); - var jsonData = JSON.stringify({ - 'ocs' : { - 'meta' : { - 'status': 'failure', - 'statuscode': 400 - } - } - }); - fakeServer.requests[0].respond( - 200, - {'Content-Type': 'application/json'}, - jsonData - ); - expect(response.calledWithExactly()).toEqual(true); - }); - - it('throws a notification when the ajax search lookup fails', function () { - notificationStub = sinon.stub(OC.Notification, 'show'); - dialog.render(); - dialog.autocompleteHandler({term: 'bob'}, sinon.stub()); - fakeServer.requests[0].respond(500); - expect(notificationStub.calledOnce).toEqual(true); - notificationStub.restore(); - }); - - describe('renders the autocomplete elements', function() { - it('renders a group element', function() { - dialog.render(); - var el = dialog.autocompleteRenderItem( - $("<ul></ul>"), - {label: "1", value: { shareType: OC.Share.SHARE_TYPE_GROUP }} - ); - expect(el.is('li')).toEqual(true); - expect(el.hasClass('group')).toEqual(true); - }); - - it('renders a remote element', function() { - dialog.render(); - var el = dialog.autocompleteRenderItem( - $("<ul></ul>"), - {label: "1", value: { shareType: OC.Share.SHARE_TYPE_REMOTE }} - ); - expect(el.is('li')).toEqual(true); - expect(el.hasClass('user')).toEqual(true); - }); - }); - - it('calls addShare after selection', function() { - dialog.render(); - - var shareWith = $('.shareWithField')[0]; - var $shareWith = $(shareWith); - var addShareStub = sinon.stub(shareModel, 'addShare'); - var autocompleteOptions = autocompleteStub.getCall(0).args[0]; - autocompleteOptions.select(new $.Event('select', {target: shareWith}), { - item: { - label: 'User Two', - value: { - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - } - } - }); - - expect(addShareStub.calledOnce).toEqual(true); - expect(addShareStub.firstCall.args[0]).toEqual({ - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - }); - - //Input is locked - expect($shareWith.val()).toEqual('User Two'); - expect($shareWith.attr('disabled')).toEqual('disabled'); - - //Callback is called - addShareStub.firstCall.args[1].success(); - - //Input is unlocked - expect($shareWith.val()).toEqual(''); - expect($shareWith.attr('disabled')).toEqual(undefined); - - addShareStub.restore(); - }); - - it('calls addShare after selection and fail to share', function() { - dialog.render(); - - var shareWith = $('.shareWithField')[0]; - var $shareWith = $(shareWith); - var addShareStub = sinon.stub(shareModel, 'addShare'); - var autocompleteOptions = autocompleteStub.getCall(0).args[0]; - autocompleteOptions.select(new $.Event('select', {target: shareWith}), { - item: { - label: 'User Two', - value: { - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - } - } - }); - - expect(addShareStub.calledOnce).toEqual(true); - expect(addShareStub.firstCall.args[0]).toEqual({ - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - }); - - //Input is locked - expect($shareWith.val()).toEqual('User Two'); - expect($shareWith.attr('disabled')).toEqual('disabled'); - - //Callback is called - addShareStub.firstCall.args[1].error(); - - //Input is unlocked - expect($shareWith.val()).toEqual('User Two'); - expect($shareWith.attr('disabled')).toEqual(undefined); - - addShareStub.restore(); - }); - }); - describe('reshare permissions', function() { - it('does not show sharing options when sharing not allowed', function() { - shareModel.set({ - reshare: {}, - shares: [], - permissions: OC.PERMISSION_READ - }); - dialog.render(); - expect(dialog.$el.find('.shareWithField').prop('disabled')).toEqual(true); - }); - it('shows reshare owner', function() { - shareModel.set({ - reshare: { - uid_owner: 'user1' - }, - shares: [], - permissions: OC.PERMISSION_READ - }); - dialog.render(); - expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(1); - }); - it('does not show reshare owner if owner is current user', function() { - shareModel.set({ - reshare: { - uid_owner: OC.currentUser - }, - shares: [], - permissions: OC.PERMISSION_READ - }); - dialog.render(); - expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(0); - }); - }); -}); diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js deleted file mode 100644 index 8c9560d2646..00000000000 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ /dev/null @@ -1,828 +0,0 @@ -/** -* ownCloud -* -* @author Vincent Petry -* @copyright 2015 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ - -/* global oc_appconfig */ -describe('OC.Share.ShareItemModel', function() { - var fetchSharesStub, fetchReshareStub; - var fetchSharesDeferred, fetchReshareDeferred; - var fileInfoModel, configModel, model; - var oldCurrentUser; - - beforeEach(function() { - oldCurrentUser = OC.currentUser; - - fetchSharesDeferred = new $.Deferred(); - fetchSharesStub = sinon.stub(OC.Share.ShareItemModel.prototype, '_fetchShares') - .returns(fetchSharesDeferred.promise()); - fetchReshareDeferred = new $.Deferred(); - fetchReshareStub = sinon.stub(OC.Share.ShareItemModel.prototype, '_fetchReshare') - .returns(fetchReshareDeferred.promise()); - - fileInfoModel = new OCA.Files.FileInfoModel({ - id: 123, - name: 'shared_file_name.txt', - path: '/subdir', - size: 100, - mimetype: 'text/plain', - permissions: 31, - sharePermissions: 31 - }); - - var attributes = { - itemType: fileInfoModel.isDirectory() ? 'folder' : 'file', - itemSource: fileInfoModel.get('id'), - possiblePermissions: fileInfoModel.get('sharePermissions') - }; - configModel = new OC.Share.ShareConfigModel(); - model = new OC.Share.ShareItemModel(attributes, { - configModel: configModel, - fileInfoModel: fileInfoModel - }); - }); - afterEach(function() { - if (fetchSharesStub) { - fetchSharesStub.restore(); - } - if (fetchReshareStub) { - fetchReshareStub.restore(); - } - OC.currentUser = oldCurrentUser; - }); - - function makeOcsResponse(data) { - return [{ - ocs: { - data: data - } - }]; - } - - describe('Fetching and parsing', function() { - it('fetches both outgoing shares and the current incoming share', function() { - model.fetch(); - - expect(fetchSharesStub.calledOnce).toEqual(true); - expect(fetchReshareStub.calledOnce).toEqual(true); - }); - it('fetches shares for the current path', function() { - fetchSharesStub.restore(); - - model._fetchShares(); - - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].method).toEqual('GET'); - expect(fakeServer.requests[0].url).toEqual( - OC.linkToOCS('apps/files_sharing/api/v1', 2) + - 'shares?format=json&path=%2Fsubdir%2Fshared_file_name.txt&reshares=true' - ); - - fetchSharesStub = null; - }); - it('fetches reshare for the current path', function() { - fetchReshareStub.restore(); - - model._fetchReshare(); - - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].method).toEqual('GET'); - expect(fakeServer.requests[0].url).toEqual( - OC.linkToOCS('apps/files_sharing/api/v1', 2) + - 'shares?format=json&path=%2Fsubdir%2Fshared_file_name.txt&shared_with_me=true' - ); - - fetchReshareStub = null; - }); - it('populates attributes with parsed response', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([ - { - share_type: OC.Share.SHARE_TYPE_USER, - uid_owner: 'owner', - displayname_owner: 'Owner', - permissions: 31 - } - ])); - fetchSharesDeferred.resolve(makeOcsResponse([ - { - id: 100, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - }, { - id: 101, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group', - share_with_displayname: 'group' - }, { - id: 102, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_REMOTE, - share_with: 'foo@bar.com/baz', - share_with_displayname: 'foo@bar.com/baz' - - }, { - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - } - ])); - - OC.currentUser = 'root'; - - model.fetch(); - - var shares = model.get('shares'); - expect(shares.length).toEqual(3); - expect(shares[0].id).toEqual(100); - expect(shares[0].permissions).toEqual(31); - expect(shares[0].share_type).toEqual(OC.Share.SHARE_TYPE_USER); - expect(shares[0].share_with).toEqual('user1'); - expect(shares[0].share_with_displayname).toEqual('User One'); - - var linkShare = model.get('linkShare'); - expect(linkShare.isLinkShare).toEqual(true); - - // TODO: check more attributes - }); - it('does not parse link share when for a different file', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - displayname_owner: 'root', - expiration: null, - file_source: 456, - file_target: '/folder', - id: 20, - item_source: '456', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }] - )); - - model.fetch(); - - var shares = model.get('shares'); - // remaining share appears in this list - expect(shares.length).toEqual(1); - - var linkShare = model.get('linkShare'); - expect(linkShare.isLinkShare).toEqual(false); - }); - it('parses correct link share when a nested link share exists along with parent one', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - displayname_owner: 'root', - expiration: '2015-10-12 00:00:00', - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'file', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }, { - displayname_owner: 'root', - expiration: '2015-10-15 00:00:00', - file_source: 456, - file_target: '/file_in_folder.txt', - id: 21, - item_source: '456', - item_type: 'file', - mail_send: '0', - parent: null, - path: '/folder/file_in_folder.txt', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884509, - storage: 1, - token: 'anothertoken', - uid_owner: 'root' - }] - )); - OC.currentUser = 'root'; - model.fetch(); - - var shares = model.get('shares'); - // the parent share remains in the list - expect(shares.length).toEqual(1); - - var linkShare = model.get('linkShare'); - expect(linkShare.isLinkShare).toEqual(true); - expect(linkShare.token).toEqual('tehtoken'); - - // TODO: check child too - }); - it('reduces reshare permissions to the ones from the original share', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([{ - id: 123, - permissions: OC.PERMISSION_READ, - uid_owner: 'user1' - }])); - fetchSharesDeferred.resolve(makeOcsResponse([])); - model.fetch(); - - // no resharing allowed - expect(model.get('permissions')).toEqual(OC.PERMISSION_READ); - }); - it('reduces reshare permissions to possible permissions', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([{ - id: 123, - permissions: OC.PERMISSION_ALL, - uid_owner: 'user1' - }])); - fetchSharesDeferred.resolve(makeOcsResponse([])); - - model.set('possiblePermissions', OC.PERMISSION_READ); - model.fetch(); - - // no resharing allowed - expect(model.get('permissions')).toEqual(OC.PERMISSION_READ); - }); - it('allows owner to share their own share when they are also the recipient', function() { - OC.currentUser = 'user1'; - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([])); - - model.fetch(); - - // sharing still allowed - expect(model.get('permissions') & OC.PERMISSION_SHARE).toEqual(OC.PERMISSION_SHARE); - }); - it('properly parses integer values when the server is in the mood of returning ints as string', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - displayname_owner: 'root', - expiration: '2015-10-12 00:00:00', - file_source: '123', - file_target: '/folder', - id: '20', - item_source: '123', - item_type: 'file', - mail_send: '0', - parent: '999', - path: '/folder', - permissions: '' + OC.PERMISSION_READ, - share_type: '' + OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - stime: '1403884258', - storage: '1', - token: 'tehtoken', - uid_owner: 'root' - }] - )); - - model.fetch(); - - var shares = model.get('shares'); - expect(shares.length).toEqual(1); - - var share = shares[0]; - expect(share.id).toEqual(20); - expect(share.file_source).toEqual(123); - expect(share.file_target).toEqual('/folder'); - expect(share.item_source).toEqual(123); - expect(share.item_type).toEqual('file'); - expect(share.displayname_owner).toEqual('root'); - expect(share.mail_send).toEqual(0); - expect(share.parent).toEqual(999); - expect(share.path).toEqual('/folder'); - expect(share.permissions).toEqual(OC.PERMISSION_READ); - expect(share.share_type).toEqual(OC.Share.SHARE_TYPE_USER); - expect(share.share_with).toEqual('user1'); - expect(share.stime).toEqual(1403884258); - expect(share.expiration).toEqual('2015-10-12 00:00:00'); - }); - }); - describe('hasUserShares', function() { - it('returns false when no user shares exist', function() { - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([])); - - model.fetch(); - - expect(model.hasUserShares()).toEqual(false); - }); - it('returns true when user shares exist on the current item', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - id: 1, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - item_source: '123' - }])); - - model.fetch(); - - expect(model.hasUserShares()).toEqual(true); - }); - it('returns true when group shares exist on the current item', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - id: 1, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group1', - item_source: '123' - }])); - - model.fetch(); - - expect(model.hasUserShares()).toEqual(true); - }); - it('returns false when share exist on parent item', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - id: 1, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group1', - item_source: '111' - }])); - - model.fetch(); - - expect(model.hasUserShares()).toEqual(false); - }); - }); - - describe('Util', function() { - it('parseTime should properly parse strings', function() { - - _.each([ - [ '123456', 123456], - [ 123456 , 123456], - ['0123456', 123456], - ['abcdefg', null], - ['0x12345', null], - [ '', null], - ], function(value) { - expect(OC.Share.ShareItemModel.prototype._parseTime(value[0])).toEqual(value[1]); - }); - - }); - }); - - describe('sendEmailPrivateLink', function() { - it('succeeds', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }])); - OC.currentUser = 'root'; - model.fetch(); - - var res = model.sendEmailPrivateLink('foo@bar.com'); - - expect(res.state()).toEqual('pending'); - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].url).toEqual(OC.generateUrl('core/ajax/share.php')); - expect(OC.parseQueryString(fakeServer.requests[0].requestBody)).toEqual( - { - action: 'email', - toaddress: 'foo@bar.com', - link: model.get('linkShare').link, - itemType: 'file', - itemSource: '123', - file: 'shared_file_name.txt', - expiration: '' - } - ) - - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - expect(res.state()).toEqual('resolved'); - }); - - it('fails', function() { - /* jshint camelcase: false */ - fetchReshareDeferred.resolve(makeOcsResponse([])); - fetchSharesDeferred.resolve(makeOcsResponse([{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }])); - OC.currentUser = 'root'; - model.fetch(); - - var res = model.sendEmailPrivateLink('foo@bar.com'); - - expect(res.state()).toEqual('pending'); - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].url).toEqual(OC.generateUrl('core/ajax/share.php')); - expect(OC.parseQueryString(fakeServer.requests[0].requestBody)).toEqual( - { - action: 'email', - toaddress: 'foo@bar.com', - link: model.get('linkShare').link, - itemType: 'file', - itemSource: '123', - file: 'shared_file_name.txt', - expiration: '' - } - ) - - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {message: 'fail'}, status: 'error'}) - ); - expect(res.state()).toEqual('rejected'); - }); - }); - describe('share permissions', function() { - beforeEach(function() { - oc_appconfig.core.resharingAllowed = true; - }); - - /** - * Tests sharing with the given possible permissions - * - * @param {int} possiblePermissions - * @return {int} permissions sent to the server - */ - function testWithPermissions(possiblePermissions) { - model.set({ - permissions: possiblePermissions, - possiblePermissions: possiblePermissions - }); - model.addShare({ - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - }); - - var requestBody = OC.parseQueryString(_.last(fakeServer.requests).requestBody); - return parseInt(requestBody.permissions, 10); - } - - describe('regular sharing', function() { - it('shares with given permissions with default config', function() { - configModel.set('isResharingAllowed', true); - model.set({ - reshare: {}, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_SHARE); - }); - it('removes share permission when not allowed', function() { - configModel.set('isResharingAllowed', false); - model.set({ - reshare: {}, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE); - }); - it('automatically adds READ permission even when not specified', function() { - configModel.set('isResharingAllowed', false); - model.set({ - reshare: {}, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_UPDATE); - }); - }); - }); - - describe('saveLinkShare', function() { - var addShareStub; - var updateShareStub; - - beforeEach(function() { - addShareStub = sinon.stub(model, 'addShare'); - updateShareStub = sinon.stub(model, 'updateShare'); - }); - afterEach(function() { - addShareStub.restore(); - updateShareStub.restore(); - }); - - it('creates a new share if no link share exists', function() { - model.set({ - linkShare: { - isLinkShare: false - } - }); - - model.saveLinkShare(); - - expect(addShareStub.calledOnce).toEqual(true); - expect(addShareStub.firstCall.args[0]).toEqual({ - password: '', - passwordChanged: false, - permissions: OC.PERMISSION_READ, - expireDate: '', - shareType: OC.Share.SHARE_TYPE_LINK - }); - expect(updateShareStub.notCalled).toEqual(true); - }); - it('creates a new share with default expiration date', function() { - var clock = sinon.useFakeTimers(Date.UTC(2015, 6, 17, 1, 2, 0, 3)); - configModel.set({ - isDefaultExpireDateEnabled: true, - defaultExpireDate: 7 - }); - model.set({ - linkShare: { - isLinkShare: false - } - }); - - model.saveLinkShare(); - - expect(addShareStub.calledOnce).toEqual(true); - expect(addShareStub.firstCall.args[0]).toEqual({ - password: '', - passwordChanged: false, - permissions: OC.PERMISSION_READ, - expireDate: '2015-07-24 00:00:00', - shareType: OC.Share.SHARE_TYPE_LINK - }); - expect(updateShareStub.notCalled).toEqual(true); - clock.restore(); - }); - it('updates link share if it exists', function() { - model.set({ - linkShare: { - isLinkShare: true, - id: 123 - } - }); - - model.saveLinkShare({ - password: 'test' - }); - - expect(addShareStub.notCalled).toEqual(true); - expect(updateShareStub.calledOnce).toEqual(true); - expect(updateShareStub.firstCall.args[0]).toEqual(123); - expect(updateShareStub.firstCall.args[1]).toEqual({ - password: 'test' - }); - }); - it('forwards error message on add', function() { - var errorStub = sinon.stub(); - model.set({ - linkShare: { - isLinkShare: false - } - }, { - }); - - model.saveLinkShare({ - password: 'test' - }, { - error: errorStub - }); - - addShareStub.yieldTo('error', 'Some error message'); - - expect(errorStub.calledOnce).toEqual(true); - expect(errorStub.lastCall.args[0]).toEqual('Some error message'); - }); - it('forwards error message on update', function() { - var errorStub = sinon.stub(); - model.set({ - linkShare: { - isLinkShare: true, - id: '123' - } - }, { - }); - - model.saveLinkShare({ - password: 'test' - }, { - error: errorStub - }); - - updateShareStub.yieldTo('error', 'Some error message'); - - expect(errorStub.calledOnce).toEqual(true); - expect(errorStub.lastCall.args[0]).toEqual('Some error message'); - }); - }); - describe('creating shares', function() { - it('sends POST method to endpoint with passed values', function() { - model.addShare({ - shareType: OC.Share.SHARE_TYPE_GROUP, - shareWith: 'group1' - }); - - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].method).toEqual('POST'); - expect(fakeServer.requests[0].url).toEqual( - OC.linkToOCS('apps/files_sharing/api/v1', 2) + - 'shares?format=json' - ); - expect(OC.parseQueryString(fakeServer.requests[0].requestBody)).toEqual({ - path: '/subdir/shared_file_name.txt', - permissions: '' + OC.PERMISSION_READ, - shareType: '' + OC.Share.SHARE_TYPE_GROUP, - shareWith: 'group1' - }); - }); - it('calls error handler with error message', function() { - var errorStub = sinon.stub(); - model.addShare({ - shareType: OC.Share.SHARE_TYPE_GROUP, - shareWith: 'group1' - }, { - error: errorStub - }); - - expect(fakeServer.requests.length).toEqual(1); - fakeServer.requests[0].respond( - 400, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - ocs: { - meta: { - message: 'Some error message' - } - } - }) - ); - - expect(errorStub.calledOnce).toEqual(true); - expect(errorStub.lastCall.args[1]).toEqual('Some error message'); - }); - }); - describe('updating shares', function() { - it('sends PUT method to endpoint with passed values', function() { - model.updateShare(123, { - permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE - }); - - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].method).toEqual('PUT'); - expect(fakeServer.requests[0].url).toEqual( - OC.linkToOCS('apps/files_sharing/api/v1', 2) + - 'shares/123?format=json' - ); - expect(OC.parseQueryString(fakeServer.requests[0].requestBody)).toEqual({ - permissions: '' + (OC.PERMISSION_READ | OC.PERMISSION_SHARE) - }); - }); - it('calls error handler with error message', function() { - var errorStub = sinon.stub(); - model.updateShare(123, { - permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE - }, { - error: errorStub - }); - - expect(fakeServer.requests.length).toEqual(1); - fakeServer.requests[0].respond( - 400, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - ocs: { - meta: { - message: 'Some error message' - } - } - }) - ); - - expect(errorStub.calledOnce).toEqual(true); - expect(errorStub.lastCall.args[1]).toEqual('Some error message'); - }); - }); - describe('removing shares', function() { - it('sends DELETE method to endpoint with share id', function() { - model.removeShare(123); - - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].method).toEqual('DELETE'); - expect(fakeServer.requests[0].url).toEqual( - OC.linkToOCS('apps/files_sharing/api/v1', 2) + - 'shares/123?format=json' - ); - }); - it('calls error handler with error message', function() { - var errorStub = sinon.stub(); - model.removeShare(123, { - error: errorStub - }); - - expect(fakeServer.requests.length).toEqual(1); - fakeServer.requests[0].respond( - 400, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - ocs: { - meta: { - message: 'Some error message' - } - } - }) - ); - - expect(errorStub.calledOnce).toEqual(true); - expect(errorStub.lastCall.args[1]).toEqual('Some error message'); - }); - }); -}); - diff --git a/core/js/tests/specs/systemtags/systemtagsSpec.js b/core/js/tests/specs/systemtags/systemtagsSpec.js index 515b75258a0..52fe954f454 100644 --- a/core/js/tests/specs/systemtags/systemtagsSpec.js +++ b/core/js/tests/specs/systemtags/systemtagsSpec.js @@ -1,29 +1,14 @@ /** -* ownCloud -* -* @author Joas Schilling -* @copyright 2016 Joas Schilling <nickvergessen@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ describe('OC.SystemTags tests', function() { it('describes non existing tag', function() { var $return = OC.SystemTags.getDescriptiveTag('23'); - expect($return.text()).toEqual('Non-existing tag #23'); - expect($return.hasClass('non-existing-tag')).toEqual(true); + expect($return.textContent).toEqual('Non-existing tag #23'); + expect($return.classList.contains('non-existing-tag')).toEqual(true); }); it('describes SystemTagModel', function() { @@ -34,8 +19,8 @@ describe('OC.SystemTags tests', function() { userVisible: true }); var $return = OC.SystemTags.getDescriptiveTag(tag); - expect($return.text()).toEqual('Twenty Three'); - expect($return.hasClass('non-existing-tag')).toEqual(false); + expect($return.textContent).toEqual('Twenty Three'); + expect($return.classList.contains('non-existing-tag')).toEqual(false); }); it('describes JSON tag object', function() { @@ -45,8 +30,8 @@ describe('OC.SystemTags tests', function() { userAssignable: true, userVisible: true }); - expect($return.text()).toEqual('Fourty Two'); - expect($return.hasClass('non-existing-tag')).toEqual(false); + expect($return.textContent).toEqual('Fourty Two'); + expect($return.classList.contains('non-existing-tag')).toEqual(false); }); it('scope', function() { @@ -57,13 +42,13 @@ describe('OC.SystemTags tests', function() { userAssignable: userAssignable, userVisible: userVisible }); - expect($return.text()).toEqual(expectedText); - expect($return.hasClass('non-existing-tag')).toEqual(false); + expect($return.textContent).toEqual(expectedText); + expect($return.classList.contains('non-existing-tag')).toEqual(false); } testScope(true, true, 'Fourty Two'); - testScope(false, true, 'Fourty Two (invisible)'); - testScope(false, false, 'Fourty Two (invisible)'); - testScope(true, false, 'Fourty Two (not assignable)'); + testScope(false, true, 'Fourty Two (Invisible)'); + testScope(false, false, 'Fourty Two (Invisible)'); + testScope(true, false, 'Fourty Two (Restricted)'); }); }); diff --git a/core/js/tests/specs/systemtags/systemtagscollectionSpec.js b/core/js/tests/specs/systemtags/systemtagscollectionSpec.js index 6f2d8361754..f2a3bd067a5 100644 --- a/core/js/tests/specs/systemtags/systemtagscollectionSpec.js +++ b/core/js/tests/specs/systemtags/systemtagscollectionSpec.js @@ -1,23 +1,7 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2016 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2016 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ describe('OC.SystemTags.SystemTagsCollection tests', function() { var collection; diff --git a/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js b/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js index 22bf0d2c82a..988bcfc8c24 100644 --- a/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js +++ b/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js @@ -1,23 +1,8 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2016 Vincent Petry <pvince81@owncloud.com> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ describe('OC.SystemTags.SystemTagsInputField tests', function() { var view, select2Stub, clock; @@ -68,7 +53,7 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { view.collection.add([ new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}), - new OC.SystemTags.SystemTagModel({id: '3', name: 'abd', userAssignable: false}), + new OC.SystemTags.SystemTagModel({id: '3', name: 'abd', userAssignable: false, canAssign: false}), ]); }); it('does not create dummy tag when user types non-matching name', function() { @@ -84,6 +69,7 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { expect(result.isNew).toEqual(true); expect(result.userVisible).toEqual(true); expect(result.userAssignable).toEqual(true); + expect(result.canAssign).toEqual(true); }); it('creates dummy tag when user types non-matching name even with prefix of existing tag', function() { var opts = select2Stub.getCall(0).args[0]; @@ -93,6 +79,7 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { expect(result.isNew).toEqual(true); expect(result.userVisible).toEqual(true); expect(result.userAssignable).toEqual(true); + expect(result.canAssign).toEqual(true); }); it('creates the real tag and fires select event after user selects the dummy tag', function() { var selectHandler = sinon.stub(); @@ -110,14 +97,16 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { expect(createStub.getCall(0).args[0]).toEqual({ name: 'newname', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true }); var newModel = new OC.SystemTags.SystemTagModel({ id: '123', name: 'newname', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true }); // not called yet @@ -186,7 +175,8 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { expect(createStub.getCall(0).args[0]).toEqual({ name: 'newname', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true }); var newModel = new OC.SystemTags.SystemTagModel({ @@ -262,20 +252,6 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { saveStub.restore(); }); - it('deletes model and submits change when clicking delete', function() { - var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); - - expect($dropdown.find('.delete').length).toEqual(0); - $dropdown.find('.rename').mouseup(); - // delete button appears - expect($dropdown.find('.delete').length).toEqual(1); - $dropdown.find('.delete').mouseup(); - - expect(destroyStub.calledOnce).toEqual(true); - expect(destroyStub.calledOn(view.collection.get('1'))); - - destroyStub.restore(); - }); }); describe('setting data', function() { it('sets value when calling setValues', function() { @@ -294,22 +270,28 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { }); describe('as admin', function() { + var $dropdown; + beforeEach(function() { view = new OC.SystemTags.SystemTagsInputField({ isAdmin: true }); - view.render(); $('.testInputContainer').append(view.$el); + $dropdown = $('<div class="select2-dropdown"></div>'); + select2Stub.withArgs('dropdown').returns($dropdown); + $('#testArea').append($dropdown); + + view.render(); }); it('formatResult renders tag name with visibility', function() { var opts = select2Stub.getCall(0).args[0]; var $el = $(opts.formatResult({id: '1', name: 'test', userVisible: false, userAssignable: false})); - expect($el.find('.label').text()).toEqual('test (invisible)'); + expect($el.find('.label').text()).toEqual('test (Invisible)'); }); it('formatSelection renders tag name with visibility', function() { var opts = select2Stub.getCall(0).args[0]; var $el = $(opts.formatSelection({id: '1', name: 'test', userVisible: false, userAssignable: false})); - expect($el.text().trim()).toEqual('test (invisible)'); + expect($el.text().trim()).toEqual('test (Invisible)'); }); describe('initSelection', function() { var fetchStub; @@ -320,7 +302,8 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { testTags = [ new OC.SystemTags.SystemTagModel({id: '1', name: 'test1'}), new OC.SystemTags.SystemTagModel({id: '2', name: 'test2'}), - new OC.SystemTags.SystemTagModel({id: '3', name: 'test3', userAssignable: false}), + new OC.SystemTags.SystemTagModel({id: '3', name: 'test3', userAssignable: false, canAssign: false}), + new OC.SystemTags.SystemTagModel({id: '4', name: 'test4', userAssignable: false, canAssign: true}) ]; }); afterEach(function() { @@ -328,7 +311,7 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { }); it('grabs values from the full collection', function() { var $el = view.$el.find('input'); - $el.val('1,3'); + $el.val('1,3,4'); var opts = select2Stub.getCall(0).args[0]; var callback = sinon.stub(); opts.initSelection($el, callback); @@ -339,11 +322,16 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { expect(callback.calledOnce).toEqual(true); var models = callback.getCall(0).args[0]; - expect(models.length).toEqual(2); + expect(models.length).toEqual(3); expect(models[0].id).toEqual('1'); expect(models[0].name).toEqual('test1'); + expect(models[0].locked).toBeFalsy(); expect(models[1].id).toEqual('3'); expect(models[1].name).toEqual('test3'); + expect(models[1].locked).toBeFalsy(); + expect(models[2].id).toEqual('4'); + expect(models[2].name).toEqual('test4'); + expect(models[2].locked).toBeFalsy(); }); }); describe('autocomplete', function() { @@ -356,7 +344,7 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { view.collection.add([ new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}), - new OC.SystemTags.SystemTagModel({id: '3', name: 'abd', userAssignable: false}), + new OC.SystemTags.SystemTagModel({id: '3', name: 'abd', userAssignable: false, canAssign: false}), new OC.SystemTags.SystemTagModel({id: '4', name: 'Deg'}), ]); }); @@ -379,13 +367,15 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { id: '1', name: 'abc', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true }, { id: '3', name: 'abd', userVisible: true, - userAssignable: false + userAssignable: false, + canAssign: false } ]); }); @@ -405,26 +395,63 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { id: '2', name: 'def', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true }, { id: '4', name: 'Deg', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true } ]); }); }); + describe('tag actions', function() { + var opts; + + beforeEach(function() { + + opts = select2Stub.getCall(0).args[0]; + + view.collection.add([ + new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), + ]); + + $dropdown.append(opts.formatResult(view.collection.get('1').toJSON())); + + }); + it('deletes model and submits change when clicking delete', function() { + var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); + + expect($dropdown.find('.delete').length).toEqual(0); + $dropdown.find('.rename').mouseup(); + // delete button appears + expect($dropdown.find('.delete').length).toEqual(1); + $dropdown.find('.delete').mouseup(); + + expect(destroyStub.calledOnce).toEqual(true); + expect(destroyStub.calledOn(view.collection.get('1'))); + + destroyStub.restore(); + }); + }); }); describe('as user', function() { + var $dropdown; + beforeEach(function() { view = new OC.SystemTags.SystemTagsInputField({ isAdmin: false }); - view.render(); $('.testInputContainer').append(view.$el); + $dropdown = $('<div class="select2-dropdown"></div>'); + select2Stub.withArgs('dropdown').returns($dropdown); + $('#testArea').append($dropdown); + + view.render(); }); it('formatResult renders tag name only', function() { var opts = select2Stub.getCall(0).args[0]; @@ -445,7 +472,8 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { testTags = [ new OC.SystemTags.SystemTagModel({id: '1', name: 'test1'}), new OC.SystemTags.SystemTagModel({id: '2', name: 'test2'}), - new OC.SystemTags.SystemTagModel({id: '3', name: 'test3', userAssignable: false}), + new OC.SystemTags.SystemTagModel({id: '3', name: 'test3', userAssignable: false, canAssign: false}), + new OC.SystemTags.SystemTagModel({id: '4', name: 'test4', userAssignable: false, canAssign: true}) ]; view.render(); }); @@ -454,7 +482,7 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { }); it('grabs values from the full collection', function() { var $el = view.$el.find('input'); - $el.val('1,3'); + $el.val('1,3,4'); var opts = select2Stub.getCall(0).args[0]; var callback = sinon.stub(); opts.initSelection($el, callback); @@ -465,11 +493,17 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { expect(callback.calledOnce).toEqual(true); var models = callback.getCall(0).args[0]; - expect(models.length).toEqual(2); + expect(models.length).toEqual(3); expect(models[0].id).toEqual('1'); expect(models[0].name).toEqual('test1'); + expect(models[0].locked).toBeFalsy(); expect(models[1].id).toEqual('3'); expect(models[1].name).toEqual('test3'); + // restricted / cannot assign locks the entry + expect(models[1].locked).toEqual(true); + expect(models[2].id).toEqual('4'); + expect(models[2].name).toEqual('test4'); + expect(models[2].locked).toBeFalsy(); }); }); describe('autocomplete', function() { @@ -483,8 +517,9 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { view.collection.add([ new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}), - new OC.SystemTags.SystemTagModel({id: '3', name: 'abd', userAssignable: false}), + new OC.SystemTags.SystemTagModel({id: '3', name: 'abd', userAssignable: false, canAssign: false}), new OC.SystemTags.SystemTagModel({id: '4', name: 'Deg'}), + new OC.SystemTags.SystemTagModel({id: '5', name: 'abe', userAssignable: false, canAssign: true}) ]); }); afterEach(function() { @@ -506,7 +541,15 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { id: '1', name: 'abc', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true + }, + { + id: '5', + name: 'abe', + userVisible: true, + userAssignable: false, + canAssign: true } ]); }); @@ -526,16 +569,46 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { id: '2', name: 'def', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true }, { id: '4', name: 'Deg', userVisible: true, - userAssignable: true + userAssignable: true, + canAssign: true } ]); }); }); + describe('tag actions', function() { + var opts; + + beforeEach(function() { + + opts = select2Stub.getCall(0).args[0]; + + view.collection.add([ + new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), + ]); + + $dropdown.append(opts.formatResult(view.collection.get('1').toJSON())); + + }); + it('deletes model and submits change when clicking delete', function() { + var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); + + expect($dropdown.find('.delete').length).toEqual(0); + $dropdown.find('.rename').mouseup(); + // delete button appears only for admins + expect($dropdown.find('.delete').length).toEqual(0); + $dropdown.find('.delete').mouseup(); + + expect(destroyStub.notCalled).toEqual(true); + + destroyStub.restore(); + }); + }); }); }); |