Update of multiple frontend libs.
This commit is contained in:
parent
de261dbde5
commit
a9c6ddc03b
276 changed files with 41257 additions and 19300 deletions
app/bower_components/lodash/test
|
@ -15,27 +15,8 @@
|
|||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Registers an event listener on an element.
|
||||
*
|
||||
* @private
|
||||
* @param {Element} element The element.
|
||||
* @param {string} eventName The name of the event.
|
||||
* @param {Function} handler The event handler.
|
||||
* @returns {Element} The element.
|
||||
*/
|
||||
function addListener(element, eventName, handler) {
|
||||
if (typeof element.addEventListener != 'undefined') {
|
||||
element.addEventListener(eventName, handler, false);
|
||||
} else if (typeof element.attachEvent != 'undefined') {
|
||||
element.attachEvent('on' + eventName, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
// Initialize controls.
|
||||
addListener(window, 'load', function() {
|
||||
addEventListener('load', function() {
|
||||
function eventHandler(event) {
|
||||
var buildIndex = buildList.selectedIndex,
|
||||
loaderIndex = loaderList.selectedIndex,
|
||||
|
@ -59,8 +40,8 @@
|
|||
setTimeout(init, 15);
|
||||
return;
|
||||
}
|
||||
toolbar.appendChild(span1);
|
||||
toolbar.appendChild(span2);
|
||||
toolbar.insertBefore(span2, toolbar.lastChild);
|
||||
toolbar.insertBefore(span1, span2);
|
||||
|
||||
buildList.selectedIndex = (function() {
|
||||
switch (build) {
|
||||
|
@ -84,12 +65,11 @@
|
|||
return -1;
|
||||
}());
|
||||
|
||||
addListener(buildList, 'change', eventHandler);
|
||||
addListener(loaderList, 'change', eventHandler);
|
||||
buildList.addEventListener('change', eventHandler);
|
||||
loaderList.addEventListener('change', eventHandler);
|
||||
}
|
||||
|
||||
var span1 = document.createElement('span');
|
||||
span1.style.cssText = 'float:right';
|
||||
span1.innerHTML =
|
||||
'<label for="qunit-build">Build: </label>' +
|
||||
'<select id="qunit-build">' +
|
||||
|
@ -100,7 +80,6 @@
|
|||
'</select>';
|
||||
|
||||
var span2 = document.createElement('span');
|
||||
span2.style.cssText = 'float:right';
|
||||
span2.innerHTML =
|
||||
'<label for="qunit-loader">Loader: </label>' +
|
||||
'<select id="qunit-loader">' +
|
||||
|
@ -110,6 +89,12 @@
|
|||
'<option value="requirejs">RequireJS</option>' +
|
||||
'</select>';
|
||||
|
||||
span1.style.cssText =
|
||||
span2.style.cssText = 'display:inline-block;float:right;line-height:2.1em;margin-left:1em;margin-top:0;';
|
||||
|
||||
span1.firstChild.style.cssText =
|
||||
span2.firstChild.style.cssText = 'display:inline-block;margin-right:.5em;';
|
||||
|
||||
var buildList = span1.lastChild,
|
||||
loaderList = span2.lastChild;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
self.console || (self.console = { 'log': function() {} });
|
||||
|
||||
addEventListener('message', function(e) {
|
||||
|
@ -12,4 +14,4 @@ addEventListener('message', function(e) {
|
|||
}
|
||||
postMessage(_.VERSION);
|
||||
}
|
||||
}, false);
|
||||
});
|
||||
|
|
50
app/bower_components/lodash/test/backbone.html
vendored
50
app/bower_components/lodash/test/backbone.html
vendored
|
@ -40,36 +40,38 @@
|
|||
var lodash = _.noConflict();
|
||||
|
||||
return function(_) {
|
||||
lodash.defaultsDeep(_, { 'templateSettings': lodash.templateSettings });
|
||||
lodash.mixin(_, lodash.pick(lodash, lodash.difference([
|
||||
'countBy',
|
||||
'debounce',
|
||||
'difference',
|
||||
'find',
|
||||
'findIndex',
|
||||
'findLastIndex',
|
||||
'groupBy',
|
||||
'includes',
|
||||
'invert',
|
||||
'invokeMap',
|
||||
'keyBy',
|
||||
'omit',
|
||||
'partition',
|
||||
'reduceRight',
|
||||
'reject',
|
||||
'sample',
|
||||
'without'
|
||||
], lodash.functions(_))));
|
||||
lodash(_)
|
||||
.defaultsDeep({ 'templateSettings': lodash.templateSettings })
|
||||
.mixin(lodash.pick(lodash, lodash.difference([
|
||||
'countBy',
|
||||
'debounce',
|
||||
'difference',
|
||||
'find',
|
||||
'findIndex',
|
||||
'findLastIndex',
|
||||
'groupBy',
|
||||
'includes',
|
||||
'invert',
|
||||
'invokeMap',
|
||||
'keyBy',
|
||||
'omit',
|
||||
'partition',
|
||||
'reduceRight',
|
||||
'reject',
|
||||
'sample',
|
||||
'without'
|
||||
], lodash.functions(_))))
|
||||
.value();
|
||||
|
||||
lodash.forOwn(keyMap, function(realName, otherName) {
|
||||
_[otherName] = lodash[realName];
|
||||
_.prototype[otherName] = lodash.prototype[realName];
|
||||
});
|
||||
|
||||
lodash.forOwn(aliasToReal, function(realName, alias) {
|
||||
_[alias] = _[realName];
|
||||
_.prototype[alias] = _.prototype[realName];
|
||||
});
|
||||
return _;
|
||||
};
|
||||
}());
|
||||
|
||||
|
@ -147,7 +149,7 @@
|
|||
QUnit.config.autostart = false;
|
||||
|
||||
require(getConfig(), ['underscore'], function(lodash) {
|
||||
mixinPrereqs(lodash);
|
||||
_ = mixinPrereqs(lodash);
|
||||
require(getConfig(), ['backbone'], function() {
|
||||
require(getConfig(), [
|
||||
'test/setup/dom-setup',
|
||||
|
@ -159,9 +161,7 @@
|
|||
'test/router',
|
||||
'test/view',
|
||||
'test/sync'
|
||||
], function() {
|
||||
QUnit.start();
|
||||
});
|
||||
], QUnit.start);
|
||||
});
|
||||
});
|
||||
}());
|
||||
|
|
12
app/bower_components/lodash/test/index.html
vendored
12
app/bower_components/lodash/test/index.html
vendored
|
@ -83,17 +83,7 @@
|
|||
|
||||
// Set bad shims.
|
||||
setProperty(Object, '_create', Object.create);
|
||||
setProperty(Object, 'create', (function() {
|
||||
function object() {}
|
||||
return function(prototype) {
|
||||
if (prototype === Object(prototype)) {
|
||||
object.prototype = prototype;
|
||||
var result = new object;
|
||||
object.prototype = undefined;
|
||||
}
|
||||
return result || {};
|
||||
};
|
||||
}()));
|
||||
setProperty(Object, 'create', undefined);
|
||||
|
||||
setProperty(Object, '_getOwnPropertySymbols', Object.getOwnPropertySymbols);
|
||||
setProperty(Object, 'getOwnPropertySymbols', undefined);
|
||||
|
|
44
app/bower_components/lodash/test/saucelabs.js
vendored
44
app/bower_components/lodash/test/saucelabs.js
vendored
|
@ -4,11 +4,6 @@
|
|||
/** Environment shortcut. */
|
||||
var env = process.env;
|
||||
|
||||
if (env.TRAVIS_SECURE_ENV_VARS == 'false') {
|
||||
console.log('Skipping Sauce Labs jobs; secure environment variables are unavailable');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/** Load Node.js modules. */
|
||||
var EventEmitter = require('events').EventEmitter,
|
||||
http = require('http'),
|
||||
|
@ -104,17 +99,16 @@ var browserNameMap = {
|
|||
/** List of platforms to load the runner on. */
|
||||
var platforms = [
|
||||
['Linux', 'android', '5.1'],
|
||||
['Windows 10', 'chrome', '50'],
|
||||
['Windows 10', 'chrome', '49'],
|
||||
['Windows 10', 'firefox', '46'],
|
||||
['Windows 10', 'firefox', '45'],
|
||||
['Windows 10', 'microsoftedge', '13'],
|
||||
['Windows 10', 'chrome', '54'],
|
||||
['Windows 10', 'chrome', '53'],
|
||||
['Windows 10', 'firefox', '50'],
|
||||
['Windows 10', 'firefox', '49'],
|
||||
['Windows 10', 'microsoftedge', '14'],
|
||||
['Windows 10', 'internet explorer', '11'],
|
||||
['Windows 8', 'internet explorer', '10'],
|
||||
['Windows 7', 'internet explorer', '9'],
|
||||
// ['OS X 10.10', 'ipad', '9.1'],
|
||||
['OS X 10.11', 'safari', '9'],
|
||||
['OS X 10.10', 'safari', '8']
|
||||
['macOS 10.12', 'safari', '10'],
|
||||
['OS X 10.11', 'safari', '9']
|
||||
];
|
||||
|
||||
/** Used to tailor the `platforms` array. */
|
||||
|
@ -294,7 +288,7 @@ function optionToArray(name, string) {
|
|||
function optionToValue(name, string) {
|
||||
var result = string.match(RegExp('^' + name + '(?:=([\\s\\S]+))?$'));
|
||||
if (result) {
|
||||
result = _.result(result, 1);
|
||||
result = _.get(result, 1);
|
||||
result = result ? _.trim(result) : true;
|
||||
}
|
||||
if (result === 'false') {
|
||||
|
@ -366,8 +360,8 @@ function onJobStart(error, res, body) {
|
|||
if (this.stopping) {
|
||||
return;
|
||||
}
|
||||
var statusCode = _.result(res, 'statusCode'),
|
||||
taskId = _.first(_.result(body, 'js tests'));
|
||||
var statusCode = _.get(res, 'statusCode'),
|
||||
taskId = _.first(_.get(body, 'js tests'));
|
||||
|
||||
if (error || !taskId || statusCode != 200) {
|
||||
if (this.attempts < this.retries) {
|
||||
|
@ -408,19 +402,19 @@ function onJobStatus(error, res, body) {
|
|||
if (!this.running || this.stopping) {
|
||||
return;
|
||||
}
|
||||
var completed = _.result(body, 'completed', false),
|
||||
data = _.first(_.result(body, 'js tests')),
|
||||
var completed = _.get(body, 'completed', false),
|
||||
data = _.first(_.get(body, 'js tests')),
|
||||
elapsed = (_.now() - this.timestamp) / 1000,
|
||||
jobId = _.result(data, 'job_id', null),
|
||||
jobResult = _.result(data, 'result', null),
|
||||
jobStatus = _.result(data, 'status', ''),
|
||||
jobUrl = _.result(data, 'url', null),
|
||||
jobId = _.get(data, 'job_id', null),
|
||||
jobResult = _.get(data, 'result', null),
|
||||
jobStatus = _.get(data, 'status', ''),
|
||||
jobUrl = _.get(data, 'url', null),
|
||||
expired = (elapsed >= queueTimeout && !_.includes(jobStatus, 'in progress')),
|
||||
options = this.options,
|
||||
platform = options.platforms[0];
|
||||
|
||||
if (_.isObject(jobResult)) {
|
||||
var message = _.result(jobResult, 'message');
|
||||
var message = _.get(jobResult, 'message');
|
||||
} else {
|
||||
if (typeof jobResult == 'string') {
|
||||
message = jobResult;
|
||||
|
@ -442,7 +436,7 @@ function onJobStatus(error, res, body) {
|
|||
}
|
||||
var description = browserName(platform[1]) + ' ' + platform[2] + ' on ' + _.startCase(platform[0]),
|
||||
errored = !jobResult || !jobResult.passed || reError.test(message) || reError.test(jobStatus),
|
||||
failures = _.result(jobResult, 'failed'),
|
||||
failures = _.get(jobResult, 'failed'),
|
||||
label = options.name + ':',
|
||||
tunnel = this.tunnel;
|
||||
|
||||
|
@ -463,7 +457,7 @@ function onJobStatus(error, res, body) {
|
|||
return;
|
||||
}
|
||||
else {
|
||||
if (typeof message == 'undefined') {
|
||||
if (message === undefined) {
|
||||
message = 'Results are unavailable. ' + details;
|
||||
}
|
||||
console.error(label, description, chalk.red('failed') + ';', message);
|
||||
|
|
311
app/bower_components/lodash/test/test-fp.js
vendored
311
app/bower_components/lodash/test/test-fp.js
vendored
|
@ -1,4 +1,5 @@
|
|||
;(function() {
|
||||
'use strict';
|
||||
|
||||
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
|
||||
var undefined;
|
||||
|
@ -22,6 +23,7 @@
|
|||
/** Math helpers. */
|
||||
var add = function(x, y) { return x + y; },
|
||||
isEven = function(n) { return n % 2 == 0; },
|
||||
isEvenIndex = function(n, index) { return isEven(index); },
|
||||
square = function(n) { return n * n; };
|
||||
|
||||
// Leak to avoid sporadic `noglobals` fails on Edge in Sauce Labs.
|
||||
|
@ -140,11 +142,8 @@
|
|||
|
||||
if (!document) {
|
||||
var array = [1, 2, 3, 4],
|
||||
lodash = convert({ 'remove': _.remove }, allFalseOptions);
|
||||
|
||||
var actual = lodash.remove(array, function(n, index) {
|
||||
return isEven(index);
|
||||
});
|
||||
lodash = convert({ 'remove': _.remove }, allFalseOptions),
|
||||
actual = lodash.remove(array, isEvenIndex);
|
||||
|
||||
assert.deepEqual(array, [2, 4]);
|
||||
assert.deepEqual(actual, [1, 3]);
|
||||
|
@ -159,11 +158,8 @@
|
|||
assert.expect(3);
|
||||
|
||||
var array = [1, 2, 3, 4],
|
||||
lodash = convert(_.runInContext(), allFalseOptions);
|
||||
|
||||
var actual = lodash.remove(array, function(n, index) {
|
||||
return isEven(index);
|
||||
});
|
||||
lodash = convert(_.runInContext(), allFalseOptions),
|
||||
actual = lodash.remove(array, isEvenIndex);
|
||||
|
||||
assert.deepEqual(array, [2, 4]);
|
||||
assert.deepEqual(actual, [1, 3]);
|
||||
|
@ -175,11 +171,8 @@
|
|||
|
||||
var array = [1, 2, 3, 4],
|
||||
runInContext = convert('runInContext', _.runInContext, allFalseOptions),
|
||||
lodash = runInContext();
|
||||
|
||||
var actual = lodash.remove(array, function(n, index) {
|
||||
return isEven(index);
|
||||
});
|
||||
lodash = runInContext(),
|
||||
actual = lodash.remove(array, isEvenIndex);
|
||||
|
||||
assert.deepEqual(array, [2, 4]);
|
||||
assert.deepEqual(actual, [1, 3]);
|
||||
|
@ -192,7 +185,7 @@
|
|||
var array = [1, 2, 3, 4],
|
||||
value = _.clone(array),
|
||||
remove = convert('remove', _.remove, { 'cap': false }),
|
||||
actual = remove(function(n, index) { return isEven(index); })(value);
|
||||
actual = remove(isEvenIndex)(value);
|
||||
|
||||
assert.deepEqual(value, [1, 2, 3, 4]);
|
||||
assert.deepEqual(actual, [2, 4]);
|
||||
|
@ -280,6 +273,25 @@
|
|||
assert.strictEqual(fp.isArray(array), true);
|
||||
assert.strictEqual(isArray()(array), true);
|
||||
});
|
||||
|
||||
QUnit.test('should convert method aliases', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var all = fp.all.convert({ 'rearg': false }),
|
||||
actual = all([0])(_.identity);
|
||||
|
||||
assert.strictEqual(actual, false);
|
||||
});
|
||||
|
||||
QUnit.test('should convert remapped methods', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var extendAll = fp.extendAll.convert({ 'immutable': false }),
|
||||
object = {};
|
||||
|
||||
extendAll([object, { 'a': 1 }, { 'b': 2 }]);
|
||||
assert.deepEqual(object, { 'a': 1, 'b': 2 });
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
@ -295,11 +307,8 @@
|
|||
|
||||
var array = [1, 2, 3, 4],
|
||||
lodash = func(allFalseOptions),
|
||||
remove = isFp ? lodash.remove : lodash;
|
||||
|
||||
var actual = remove(array, function(n, index) {
|
||||
return isEven(index);
|
||||
});
|
||||
remove = isFp ? lodash.remove : lodash,
|
||||
actual = remove(array, isEvenIndex);
|
||||
|
||||
assert.deepEqual(array, [2, 4]);
|
||||
assert.deepEqual(actual, [1, 3]);
|
||||
|
@ -311,11 +320,8 @@
|
|||
|
||||
var array = [1, 2, 3, 4],
|
||||
lodash = func({ 'cap': false }),
|
||||
remove = (isFp ? lodash.remove : lodash).convert({ 'rearg': false });
|
||||
|
||||
var actual = remove(array)(function(n, index) {
|
||||
return isEven(index);
|
||||
});
|
||||
remove = (isFp ? lodash.remove : lodash).convert({ 'rearg': false }),
|
||||
actual = remove(array)(isEvenIndex);
|
||||
|
||||
assert.deepEqual(array, [1, 2, 3, 4]);
|
||||
assert.deepEqual(actual, [2, 4]);
|
||||
|
@ -344,7 +350,7 @@
|
|||
var aryCap = index + 1;
|
||||
|
||||
var methodNames = _.filter(mapping.aryMethod[aryCap], function(methodName) {
|
||||
var key = _.result(mapping.remap, methodName, methodName),
|
||||
var key = _.get(mapping.remap, methodName, methodName),
|
||||
arity = _[key].length;
|
||||
|
||||
return arity != 0 && arity < aryCap;
|
||||
|
@ -384,7 +390,7 @@
|
|||
'method', 'methodOf', 'rest', 'runInContext'
|
||||
];
|
||||
|
||||
var exceptions = funcMethods.concat('mixin', 'template'),
|
||||
var exceptions = funcMethods.concat('mixin', 'nthArg', 'template'),
|
||||
expected = _.map(mapping.aryMethod[1], _.constant(true));
|
||||
|
||||
var actual = _.map(mapping.aryMethod[1], function(methodName) {
|
||||
|
@ -413,7 +419,7 @@
|
|||
'wrap'
|
||||
];
|
||||
|
||||
var exceptions = _.difference(funcMethods.concat('matchesProperty'), ['cloneDeepWith', 'cloneWith', 'delay']),
|
||||
var exceptions = _.without(funcMethods.concat('matchesProperty'), 'delay'),
|
||||
expected = _.map(mapping.aryMethod[2], _.constant(true));
|
||||
|
||||
var actual = _.map(mapping.aryMethod[2], function(methodName) {
|
||||
|
@ -642,7 +648,7 @@
|
|||
_.forOwn(mapping.placeholder, function(truthy, methodName) {
|
||||
var func = fp[methodName];
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should have a `placeholder` property', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should have a `placeholder` property', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
assert.ok(_.isObject(func.placeholder));
|
||||
|
@ -711,7 +717,7 @@
|
|||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('assign methods');
|
||||
QUnit.module('object assignments');
|
||||
|
||||
_.each(['assign', 'assignIn', 'defaults', 'defaultsDeep', 'merge'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
@ -727,9 +733,19 @@
|
|||
});
|
||||
});
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
_.each(['assignAll', 'assignInAll', 'defaultsAll', 'defaultsDeepAll', 'mergeAll'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
||||
QUnit.module('assignWith methods');
|
||||
QUnit.test('`fp.' + methodName + '` should not mutate values', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var objects = [{ 'a': 1 }, { 'b': 2 }],
|
||||
actual = func(objects);
|
||||
|
||||
assert.deepEqual(objects[0], { 'a': 1 });
|
||||
assert.deepEqual(actual, { 'a': 1, 'b': 2 });
|
||||
});
|
||||
});
|
||||
|
||||
_.each(['assignWith', 'assignInWith', 'extendWith'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
@ -745,19 +761,31 @@
|
|||
|
||||
assert.deepEqual(args, [undefined, 2, 'b', { 'a': 1 }, { 'b': 2 }]);
|
||||
});
|
||||
});
|
||||
|
||||
_.each(['assignAllWith', 'assignInAllWith', 'extendAllWith', 'mergeAllWith'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
||||
QUnit.test('`fp.' + methodName + '` should not mutate values', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var object = { 'a': 1 };
|
||||
var objects = [{ 'a': 1 }, { 'b': 2 }],
|
||||
actual = func(_.noop)(objects);
|
||||
|
||||
var actual = func(function(objValue, srcValue) {
|
||||
return srcValue;
|
||||
})(object)({ 'b': 2 });
|
||||
|
||||
assert.deepEqual(object, { 'a': 1 });
|
||||
assert.deepEqual(objects[0], { 'a': 1 });
|
||||
assert.deepEqual(actual, { 'a': 1, 'b': 2 });
|
||||
});
|
||||
|
||||
QUnit.test('`fp.' + methodName + '` should work with more than two sources', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var pass = false,
|
||||
objects = [{ 'a': 1 }, { 'b': 2 }, { 'c': 3 }],
|
||||
actual = func(function() { pass = true; })(objects);
|
||||
|
||||
assert.ok(pass);
|
||||
assert.deepEqual(actual, { 'a': 1, 'b': 2, 'c': 3 });
|
||||
});
|
||||
});
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
@ -809,7 +837,7 @@
|
|||
_.each(['curry', 'curryRight'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should only accept a `func` param', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should only accept a `func` param', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
assert.raises(function() { func(1, _.noop); }, TypeError);
|
||||
|
@ -823,7 +851,7 @@
|
|||
_.each(['curryN', 'curryRightN'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should accept an `arity` param', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should accept an `arity` param', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var actual = func(1)(function(a, b) { return [a, b]; })('a');
|
||||
|
@ -833,6 +861,19 @@
|
|||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.defaultTo');
|
||||
|
||||
(function() {
|
||||
QUnit.test('should have an argument order of `defaultValue` then `value`', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
assert.strictEqual(fp.defaultTo(1)(0), 0);
|
||||
assert.strictEqual(fp.defaultTo(1)(undefined), 1);
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.difference');
|
||||
|
||||
(function() {
|
||||
|
@ -910,10 +951,9 @@
|
|||
|
||||
var object = { 'a': 1 },
|
||||
extend = convert('extend', _.extend),
|
||||
value = _.clone(object),
|
||||
actual = extend(value)(new Foo);
|
||||
actual = extend(object)(new Foo);
|
||||
|
||||
assert.deepEqual(value, object);
|
||||
assert.deepEqual(object, { 'a': 1 });
|
||||
assert.deepEqual(actual, { 'a': 1, 'b': 2 });
|
||||
});
|
||||
}());
|
||||
|
@ -948,7 +988,7 @@
|
|||
_.each(['findFrom', 'findIndexFrom', 'findLastFrom', 'findLastIndexFrom'], function(methodName) {
|
||||
var func = fp[methodName];
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should provide the correct `predicate` arguments', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should provide the correct `predicate` arguments', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var args;
|
||||
|
@ -1007,13 +1047,13 @@
|
|||
var func = fp[methodName],
|
||||
resolve = methodName == 'findIndexFrom' ? fp.eq : _.identity;
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should have an argument order of `value`, `fromIndex`, then `array`', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should have an argument order of `value`, `fromIndex`, then `array`', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var array = [1, 2, 3, 1, 2, 3];
|
||||
|
||||
assert.deepEqual(func(resolve(1))(2)(array), 3);
|
||||
assert.deepEqual(func(resolve(2))(-3)(array), 4);
|
||||
assert.strictEqual(func(resolve(1))(2)(array), 3);
|
||||
assert.strictEqual(func(resolve(2))(-3)(array), 4);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1025,13 +1065,13 @@
|
|||
var func = fp[methodName],
|
||||
resolve = methodName == 'findLastIndexFrom' ? fp.eq : _.identity;
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should have an argument order of `value`, `fromIndex`, then `array`', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should have an argument order of `value`, `fromIndex`, then `array`', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var array = [1, 2, 3, 1, 2, 3];
|
||||
|
||||
assert.deepEqual(func(resolve(2))(3)(array), 1);
|
||||
assert.deepEqual(func(resolve(3))(-3)(array), 2);
|
||||
assert.strictEqual(func(resolve(2))(3)(array), 1);
|
||||
assert.strictEqual(func(resolve(3))(-3)(array), 2);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1273,7 +1313,7 @@
|
|||
var args,
|
||||
iteration = 0,
|
||||
objects = [{ 'a': 1 }, { 'a': 2 }],
|
||||
stack = { '__data__': { '__data__': [objects] } },
|
||||
stack = { '__data__': { '__data__': [objects, objects.slice().reverse()], 'size': 2 }, 'size': 2 },
|
||||
expected = [1, 2, 'a', objects[0], objects[1], stack];
|
||||
|
||||
fp.isEqualWith(function() {
|
||||
|
@ -1299,7 +1339,7 @@
|
|||
|
||||
var args,
|
||||
objects = [{ 'a': 1 }, { 'a': 2 }],
|
||||
stack = { '__data__': { '__data__': [] } },
|
||||
stack = { '__data__': { '__data__': [], 'size': 0 }, 'size': 0 },
|
||||
expected = [2, 1, 'a', objects[1], objects[0], stack];
|
||||
|
||||
fp.isMatchWith(function() {
|
||||
|
@ -1408,12 +1448,12 @@
|
|||
assert.expect(1);
|
||||
|
||||
var args,
|
||||
stack = { '__data__': { '__data__': [] } },
|
||||
expected = [[1], [2, 3], 'a', { 'a': [1] }, { 'a': [2, 3] }, stack];
|
||||
stack = { '__data__': { '__data__': [], 'size': 0 }, 'size': 0 },
|
||||
expected = [[1, 2], [3], 'a', { 'a': [1, 2] }, { 'a': [3] }, stack];
|
||||
|
||||
fp.mergeWith(function() {
|
||||
args || (args = _.map(arguments, _.cloneDeep));
|
||||
})({ 'a': [1] })({ 'a': [2, 3] });
|
||||
})({ 'a': [1, 2] })({ 'a': [3] });
|
||||
|
||||
args[5] = _.omitBy(args[5], _.isFunction);
|
||||
args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction);
|
||||
|
@ -1424,17 +1464,35 @@
|
|||
QUnit.test('should not mutate values', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var object = { 'a': { 'b': 2, 'c': 3 } };
|
||||
object.a.b = [1];
|
||||
var objects = [{ 'a': [1, 2] }, { 'a': [3] }],
|
||||
actual = fp.mergeWith(_.noop, objects[0], objects[1]);
|
||||
|
||||
var actual = fp.mergeWith(function(objValue, srcValue) {
|
||||
if (_.isArray(objValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
}
|
||||
}, object, { 'a': { 'b': [2, 3] } });
|
||||
assert.deepEqual(objects[0], { 'a': [1, 2] });
|
||||
assert.deepEqual(actual, { 'a': [3, 2] });
|
||||
});
|
||||
}());
|
||||
|
||||
assert.deepEqual(object, { 'a': { 'b': [1], 'c': 3 } });
|
||||
assert.deepEqual(actual, { 'a': { 'b': [1, 2, 3], 'c': 3 } });
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.mergeAllWith');
|
||||
|
||||
(function() {
|
||||
QUnit.test('should provide the correct `customizer` arguments', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var args,
|
||||
objects = [{ 'a': [1, 2] }, { 'a': [3] }],
|
||||
stack = { '__data__': { '__data__': [], 'size': 0 }, 'size': 0 },
|
||||
expected = [[1, 2], [3], 'a', { 'a': [1, 2] }, { 'a': [3] }, stack];
|
||||
|
||||
fp.mergeAllWith(function() {
|
||||
args || (args = _.map(arguments, _.cloneDeep));
|
||||
})(objects);
|
||||
|
||||
args[5] = _.omitBy(args[5], _.isFunction);
|
||||
args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction);
|
||||
|
||||
assert.deepEqual(args, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
|
@ -1515,11 +1573,27 @@
|
|||
Foo.mixin = object.mixin;
|
||||
Foo.mixin(source);
|
||||
|
||||
assert.strictEqual(typeof Foo.a, 'function');
|
||||
assert.ok('a' in Foo);
|
||||
assert.notOk('a' in Foo.prototype);
|
||||
|
||||
object.mixin(source);
|
||||
assert.strictEqual(typeof object.a, 'function');
|
||||
assert.ok('a' in object);
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.nthArg');
|
||||
|
||||
(function() {
|
||||
QUnit.test('should return a curried function', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var func = fp.nthArg(1);
|
||||
assert.strictEqual(func(1)(2), 2);
|
||||
|
||||
func = fp.nthArg(-1);
|
||||
assert.strictEqual(func(1), 1);
|
||||
});
|
||||
}());
|
||||
|
||||
|
@ -1567,7 +1641,7 @@
|
|||
isPad = methodName == 'padChars',
|
||||
isStart = methodName == 'padCharsStart';
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should truncate pad characters to fit the pad length', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should truncate pad characters to fit the pad length', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
if (isPad) {
|
||||
|
@ -1586,7 +1660,7 @@
|
|||
var func = fp[methodName],
|
||||
isPartial = methodName == 'partial';
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should accept an `args` param', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should accept an `args` param', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var expected = isPartial ? [1, 2, 3] : [0, 1, 2];
|
||||
|
@ -1598,7 +1672,7 @@
|
|||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should convert by name', function(assert) {
|
||||
QUnit.test('fp.' + methodName + '` should convert by name', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var expected = isPartial ? [1, 2, 3] : [0, 1, 2],
|
||||
|
@ -1621,6 +1695,21 @@
|
|||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.propertyOf');
|
||||
|
||||
(function() {
|
||||
QUnit.test('should be curried', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var object = { 'a': 1 };
|
||||
|
||||
assert.strictEqual(fp.propertyOf(object, 'a'), 1);
|
||||
assert.strictEqual(fp.propertyOf(object)('a'), 1);
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.pull');
|
||||
|
||||
(function() {
|
||||
|
@ -1689,13 +1778,55 @@
|
|||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.range');
|
||||
QUnit.module('range methods');
|
||||
|
||||
(function() {
|
||||
QUnit.test('should have an argument order of `start` then `end`', function(assert) {
|
||||
_.each(['range', 'rangeRight'], function(methodName) {
|
||||
var func = fp[methodName],
|
||||
isRange = methodName == 'range';
|
||||
|
||||
QUnit.test('fp.' + methodName + '` should have an argument order of `start` then `end`', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
assert.deepEqual(fp.range(1)(4), [1, 2, 3]);
|
||||
assert.deepEqual(func(1)(4), isRange ? [1, 2, 3] : [3, 2, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('rangeStep methods');
|
||||
|
||||
_.each(['rangeStep', 'rangeStepRight'], function(methodName) {
|
||||
var func = fp[methodName],
|
||||
isRange = methodName == 'rangeStep';
|
||||
|
||||
QUnit.test('fp.' + methodName + '` should have an argument order of `step`, `start`, then `end`', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
assert.deepEqual(func(2)(1)(4), isRange ? [1, 3] : [3, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.rearg');
|
||||
|
||||
(function() {
|
||||
function fn(a, b, c) {
|
||||
return [a, b, c];
|
||||
}
|
||||
|
||||
QUnit.test('should be curried', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var rearged = fp.rearg([1, 2, 0])(fn);
|
||||
assert.deepEqual(rearged('c', 'a', 'b'), ['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
QUnit.test('should return a curried function', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var rearged = fp.rearg([1, 2, 0], fn);
|
||||
assert.deepEqual(rearged('c')('a')('b'), ['a', 'b', 'c']);
|
||||
});
|
||||
}());
|
||||
|
||||
|
@ -1707,7 +1838,7 @@
|
|||
var func = fp[methodName],
|
||||
isReduce = methodName == 'reduce';
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an array', function(assert) {
|
||||
QUnit.test('`fp.' + methodName + '` should provide the correct `iteratee` arguments when iterating an array', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var args;
|
||||
|
@ -1716,10 +1847,10 @@
|
|||
args || (args = slice.call(arguments));
|
||||
})(0)([1, 2, 3]);
|
||||
|
||||
assert.deepEqual(args, isReduce ? [0, 1] : [0, 3]);
|
||||
assert.deepEqual(args, isReduce ? [0, 1] : [3, 0]);
|
||||
});
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an object', function(assert) {
|
||||
QUnit.test('`fp.' + methodName + '` should provide the correct `iteratee` arguments when iterating an object', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var args,
|
||||
|
@ -1727,8 +1858,8 @@
|
|||
isFIFO = _.keys(object)[0] == 'a';
|
||||
|
||||
var expected = isFIFO
|
||||
? (isReduce ? [0, 1] : [0, 2])
|
||||
: (isReduce ? [0, 2] : [0, 1]);
|
||||
? (isReduce ? [0, 1] : [2, 0])
|
||||
: (isReduce ? [0, 2] : [1, 0]);
|
||||
|
||||
func(function() {
|
||||
args || (args = slice.call(arguments));
|
||||
|
@ -1881,7 +2012,7 @@
|
|||
}
|
||||
parts = parts.join(' and ');
|
||||
|
||||
QUnit.test('`_.' + methodName + '` should remove ' + parts + ' `chars`', function(assert) {
|
||||
QUnit.test('`fp.' + methodName + '` should remove ' + parts + ' `chars`', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var string = '-_-a-b-c-_-',
|
||||
|
@ -1951,11 +2082,8 @@
|
|||
QUnit.test('should work with an `iteratee` argument', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var expected = objects.slice(0, 3);
|
||||
|
||||
var actual = fp.uniqBy(function(object) {
|
||||
return object.a;
|
||||
})(objects);
|
||||
var expected = objects.slice(0, 3),
|
||||
actual = fp.uniqBy(_.property('a'))(objects);
|
||||
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
|
@ -2135,6 +2263,18 @@
|
|||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.zipAll');
|
||||
|
||||
(function() {
|
||||
QUnit.test('should zip together an array of arrays', function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
assert.deepEqual(fp.zipAll([[1, 2], [3, 4], [5, 6]]), [[1, 3, 5], [2, 4, 6]]);
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('fp.zipObject');
|
||||
|
||||
(function() {
|
||||
|
@ -2169,5 +2309,6 @@
|
|||
if (!document) {
|
||||
QUnit.config.noglobals = true;
|
||||
QUnit.load();
|
||||
QUnit.start();
|
||||
}
|
||||
}.call(this));
|
||||
|
|
3834
app/bower_components/lodash/test/test.js
vendored
3834
app/bower_components/lodash/test/test.js
vendored
File diff suppressed because it is too large
Load diff
86
app/bower_components/lodash/test/underscore.html
vendored
86
app/bower_components/lodash/test/underscore.html
vendored
|
@ -56,9 +56,6 @@
|
|||
'works on an arguments object',
|
||||
'can handle very deep arrays'
|
||||
],
|
||||
'head': [
|
||||
'is an alias for first'
|
||||
],
|
||||
'indexOf': [
|
||||
"sorted indexOf doesn't uses binary search",
|
||||
'0'
|
||||
|
@ -86,9 +83,6 @@
|
|||
'an array of pairs zipped together into an object',
|
||||
'an object converted to pairs and back to an object'
|
||||
],
|
||||
'range': [
|
||||
'range with two arguments a & b, b<a generates an empty array'
|
||||
],
|
||||
'rest': [
|
||||
'returns the whole array when index is 0',
|
||||
'returns elements starting at the given index',
|
||||
|
@ -129,7 +123,8 @@
|
|||
'Iterating objects with sketchy length properties': true,
|
||||
'Resistant to collection length and properties changing while iterating': true,
|
||||
'countBy': [
|
||||
'true'
|
||||
'{}',
|
||||
'[{}]'
|
||||
],
|
||||
'each': [
|
||||
'context object property accessed'
|
||||
|
@ -141,24 +136,17 @@
|
|||
],
|
||||
'filter': [
|
||||
'given context',
|
||||
'[{"a":1,"b":2},{"a":1,"b":3},{"a":1,"b":4}]',
|
||||
'[{"a":1,"b":2},{"a":2,"b":2}]',
|
||||
'Empty object accepts all items',
|
||||
'OO-filter'
|
||||
],
|
||||
'find': [
|
||||
'{"a":1,"b":4}',
|
||||
'undefined when not found',
|
||||
'undefined when searching empty list',
|
||||
'works on objects',
|
||||
'undefined',
|
||||
'called with context'
|
||||
],
|
||||
'findWhere': [
|
||||
'checks properties given function'
|
||||
],
|
||||
'groupBy': [
|
||||
'true'
|
||||
'{}',
|
||||
'[{}]'
|
||||
],
|
||||
'includes': [
|
||||
"doesn't delegate to binary search"
|
||||
|
@ -198,8 +186,7 @@
|
|||
'partition': [
|
||||
'can reference the array index',
|
||||
'Died on test #8',
|
||||
'partition takes a context argument',
|
||||
'function(a){[code]}'
|
||||
'partition takes a context argument'
|
||||
],
|
||||
'pluck': [
|
||||
'[1]'
|
||||
|
@ -223,39 +210,36 @@
|
|||
'checks properties given function'
|
||||
],
|
||||
'Can use various collection methods on NodeLists': [
|
||||
'<span id="id2"></span>',
|
||||
'<span id="id1"></span>'
|
||||
'<span id="id2"></span>'
|
||||
]
|
||||
},
|
||||
'Functions': {
|
||||
'debounce asap': true,
|
||||
'debounce asap cancel': true,
|
||||
'debounce after system time is set backwards': true,
|
||||
'debounce asap recursively': true,
|
||||
'debounce after system time is set backwards': true,
|
||||
'debounce re-entrant': true,
|
||||
'throttle repeatedly with results': true,
|
||||
'more throttle does not trigger leading call when leading is set to false': true,
|
||||
'throttle does not trigger trailing call when trailing is set to false': true,
|
||||
'before': [
|
||||
'stores a memo to the last value',
|
||||
'provides context'
|
||||
],
|
||||
'before': true,
|
||||
'bind': [
|
||||
'Died on test #2'
|
||||
],
|
||||
'bindAll': [
|
||||
'throws an error for bindAll with no functions named'
|
||||
],
|
||||
'debounce': [
|
||||
'incr was debounced'
|
||||
],
|
||||
'iteratee': [
|
||||
'"bbiz"',
|
||||
'"foo"',
|
||||
'1'
|
||||
],
|
||||
'memoize': [
|
||||
'{"bar":"BAR","foo":"FOO"}',
|
||||
'Died on test #8'
|
||||
],
|
||||
'partial':[
|
||||
'can partially apply with placeholders',
|
||||
'accepts more arguments than the number of placeholders',
|
||||
'accepts fewer arguments than the number of placeholders',
|
||||
'unfilled placeholders are undefined',
|
||||
'keeps prototype',
|
||||
'allows the placeholder to be swapped out'
|
||||
]
|
||||
},
|
||||
'Objects': {
|
||||
|
@ -265,10 +249,6 @@
|
|||
'is not fooled by sparse arrays with additional properties',
|
||||
'[]'
|
||||
],
|
||||
'defaults': [
|
||||
'defaults skips nulls',
|
||||
'defaults skips undefined'
|
||||
],
|
||||
'extend': [
|
||||
'extending null results in null',
|
||||
'extending undefined results in undefined'
|
||||
|
@ -285,15 +265,13 @@
|
|||
'Commutative equality is implemented for `0` and `-0`',
|
||||
'`new Number(0)` and `-0` are not equal',
|
||||
'Commutative equality is implemented for `new Number(0)` and `-0`',
|
||||
'Invalid dates are not equal',
|
||||
'false'
|
||||
],
|
||||
'isFinite': [
|
||||
'Numeric strings are numbers',
|
||||
'Number instances can be finite'
|
||||
],
|
||||
'isMatch': [
|
||||
'doesnt falsey match constructor on undefined/null'
|
||||
],
|
||||
'isSet': [
|
||||
'Died on test #9'
|
||||
],
|
||||
|
@ -309,10 +287,6 @@
|
|||
'called with context',
|
||||
'mapValue identity'
|
||||
],
|
||||
'matcher': [
|
||||
'null matches null',
|
||||
'treats primitives as empty'
|
||||
],
|
||||
'omit': [
|
||||
'can accept a predicate',
|
||||
'function is given context'
|
||||
|
@ -323,7 +297,12 @@
|
|||
]
|
||||
},
|
||||
'Utility': {
|
||||
'noConflict (node vm)': true,
|
||||
'_.escape & unescape': [
|
||||
'` is escaped',
|
||||
'` can be unescaped',
|
||||
'can escape multiple occurances of `',
|
||||
'multiple occurrences of ` can be unescaped'
|
||||
],
|
||||
'now': [
|
||||
'Produces the correct time in milliseconds'
|
||||
],
|
||||
|
@ -370,18 +349,20 @@
|
|||
var lodash = _.noConflict();
|
||||
|
||||
return function(_) {
|
||||
lodash.defaultsDeep(_, { 'templateSettings': lodash.templateSettings });
|
||||
lodash.mixin(_, lodash.pick(lodash, lodash.difference(lodash.functions(lodash), lodash.functions(_))));
|
||||
lodash(_)
|
||||
.defaultsDeep({ 'templateSettings': lodash.templateSettings })
|
||||
.mixin(lodash.pick(lodash, lodash.difference(lodash.functions(lodash), lodash.functions(_))))
|
||||
.value();
|
||||
|
||||
lodash.forOwn(keyMap, function(realName, otherName) {
|
||||
_[otherName] = lodash[realName];
|
||||
_.prototype[otherName] = lodash.prototype[realName];
|
||||
});
|
||||
|
||||
lodash.forOwn(aliasToReal, function(realName, alias) {
|
||||
_[alias] = _[realName];
|
||||
_.prototype[alias] = _.prototype[realName];
|
||||
});
|
||||
return _;
|
||||
};
|
||||
}());
|
||||
|
||||
|
@ -463,9 +444,12 @@
|
|||
}
|
||||
|
||||
QUnit.config.autostart = false;
|
||||
QUnit.config.excused.Functions.iteratee = true;
|
||||
QUnit.config.excused.Utility.noConflict = true;
|
||||
QUnit.config.excused.Utility['noConflict (node vm)'] = true;
|
||||
|
||||
require(getConfig(), [moduleId], function(lodash) {
|
||||
mixinPrereqs(lodash);
|
||||
_ = mixinPrereqs(lodash);
|
||||
require(getConfig(), [
|
||||
'test/collections',
|
||||
'test/arrays',
|
||||
|
@ -474,9 +458,7 @@
|
|||
'test/cross-document',
|
||||
'test/utility',
|
||||
'test/chaining'
|
||||
], function() {
|
||||
QUnit.start();
|
||||
});
|
||||
], QUnit.start);
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue