<commit_before>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2012-2014 Axelor (<http://axelor.com>).
 *
 * This program 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
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function(){

var ui = angular.module('axelor.ui');

/**
 * The Form widget.
 *
 */
ui.formWidget('Form', {

	priority: 100,

	css: "dynamic-form",

	scope: false,

	compile: function(element, attrs) {

		element.hide();
		element.find('[x-field],[data-field]').each(function(){

			var elem = $(this),
				name = elem.attr('x-field') || elem.attr('data-field');

			if (name && elem.attr('ui-button') === undefined) {
				if (!elem.attr('ng-model')) {
					elem.attr('ng-model', 'record.' + name);
				}
				if (!elem.attr('ng-required')) {
					// always attache a required validator to make
					// dynamic `required` attribute change effective
					elem.attr('ng-required', false);
				}
			}
		});

		return ui.formCompile.apply(this, arguments);
	},

	link: function(scope, element, attrs, controller) {

		element.on('submit', function(e) {
			e.preventDefault();
		});

		scope.$watch('record', function(rec, old) {
			if (element.is(':visible')) {
				return;
			}
			scope.ajaxStop(function() {
				element.show();
				axelor.$adjustSize();
			});
		});
	}
});

/**
 * This directive is used filter $watch on scopes of inactive tabs.
 *
 */
ui.directive('uiTabGate', function() {

	return {

		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					scope.$watchChecker(function(current) {
						if (current.tabSelected === undefined) {
							return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
						}
						return current.tabSelected;
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes of hidden forms.
 *
 */
ui.directive('uiFormGate', function() {

	return {
		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					var parent = null;
					scope.$watchChecker(function(current) {
						if (scope.tabSelected === false) {
							return false;
						}
						if (parent === null) {
							parent = element.parents('[ui-show]:first');
						}
						return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes based on some condition.
 *
 */
ui.directive('uiWatchIf', ['$parse', function($parse) {

	return {
		compile: function compile(tElement, tAttrs) {
			return {
				pre: function preLink(scope, element, attrs) {
					var value = false,
						expression = $parse(attrs.uiWatchIf);

					scope.$watchChecker(function (current) {
						if (current === scope) {
							return value = expression(scope);
						}
						return value;
					});
				}
			};
		}
	};
}]);

function toBoolean(value) {
	if (value && value.length !== 0) {
		var v = angular.lowercase("" + value);
		value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
	} else {
		value = false;
	}
	return value;
}

/**
 * This directive is used to speedup uiFormGate.
 */
ui.directive('uiShow', function() {

	return {
		scope: true, // create new scope to always watch the expression
		link: function link(scope, element, attrs) {
			scope.$$shouldWatch = true;
			scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
				element.css('display', toBoolean(value) ? '' : 'none').toggleClass('ui-hide', !toBoolean(value));
			});
		}
	};
});

ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {

	function isValid(scope, name) {
		if (!name) return scope.isValid();
		var ctrl = scope.form;
		if (ctrl) {
			ctrl = ctrl[name];
		}
		if (ctrl) {
			return ctrl.$valid;
		}
	}

	function withContext(scope, record) {
		var values = record;
		if (scope._context) {
			values = _.extend({}, scope._context, values);
		}
		return values;
	}

	function handleCondition(scope, field, attr, condition, negative) {

		if (!condition) {
			return;
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});

		scope.$watch("isReadonly()", watcher);
		scope.$watch("isRequired()", watcher);
		scope.$watch("isValid()", watcher);

		var expr = $parse(condition);

		function watcher(current, old) {
			if (current !== old) handle(scope.record);
		}

		function handle(rec) {
			var value = undefined;
			try {
				value = axelor.$eval(scope, expr, withContext(scope, rec));
			} catch (e) {}
			scope.attr(attr, negative ? !value : value);
		}
	}

	function handleHilites(scope, field) {
		if (!field || _.isEmpty(field.hilites)) {
			return;
		}

		var hilites = field.hilites || [];
		var exprs = _.map(_.pluck(hilites, 'condition'), $parse);

		function handle(rec) {
			for (var i = 0; i < hilites.length; i++) {
				var hilite = hilites[i];
				var expr = exprs[i];
				var value = false;
				try {
					value = axelor.$eval(scope, expr, withContext(scope, rec));
				} catch (e) {}
				if (value) {
					return scope.attr('highlight', {
						hilite: hilite,
						passed: value
					});
				}
			}
			return scope.attr('highlight', {});
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleBind(scope, field) {

		if (!field.bind || !field.name) {
			return;
		}

		var expr = $interpolate(field.bind);

		function handle(rec) {
			var value = undefined;
			try {
				value = expr(withContext(scope, rec)).trim();
			} catch (e) {}

			if (scope.setValue) {
				scope.setValue(value);
			}
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleFor(scope, field, attr, conditional, negative) {
		if (field[conditional]) {
			handleCondition(scope, field, attr, field[conditional], negative);
		}
	}

	function handleForField(scope) {
		var field = scope.field;
		if (!field) return;
		handleFor(scope, field, "valid", "validIf");
		handleFor(scope, field, "hidden", "hideIf");
		handleFor(scope, field, "hidden", "showIf", true);
		handleFor(scope, field, "readonly", "readonlyIf");
		handleFor(scope, field, "required", "requiredIf");
		handleFor(scope, field, "collapse", "collapseIf");
		handleHilites(scope, field);
		handleBind(scope, field);
	}

	function handleForView(scope) {
		var field = scope.schema;
		if (!field) return;
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canSave", "canSave");
		handleFor(scope, field, "canCopy", "canCopy");
		handleFor(scope, field, "canDelete", "canDelete");
		handleFor(scope, field, "canAttach", "canAttach");
	}

	return function(scope, element, attrs) {
		scope.$evalAsync(function() {
			if (element.is('[ui-form]')) {
				return handleForView(scope);
			}
			handleForField(scope);
		});
	};
}]);

})(this);
<commit_msg>Fixed value bind issue.<commit_after>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2012-2014 Axelor (<http://axelor.com>).
 *
 * This program 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
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function(){

var ui = angular.module('axelor.ui');

/**
 * The Form widget.
 *
 */
ui.formWidget('Form', {

	priority: 100,

	css: "dynamic-form",

	scope: false,

	compile: function(element, attrs) {

		element.hide();
		element.find('[x-field],[data-field]').each(function(){

			var elem = $(this),
				name = elem.attr('x-field') || elem.attr('data-field');

			if (name && elem.attr('ui-button') === undefined) {
				if (!elem.attr('ng-model')) {
					elem.attr('ng-model', 'record.' + name);
				}
				if (!elem.attr('ng-required')) {
					// always attache a required validator to make
					// dynamic `required` attribute change effective
					elem.attr('ng-required', false);
				}
			}
		});

		return ui.formCompile.apply(this, arguments);
	},

	link: function(scope, element, attrs, controller) {

		element.on('submit', function(e) {
			e.preventDefault();
		});

		scope.$watch('record', function(rec, old) {
			if (element.is(':visible')) {
				return;
			}
			scope.ajaxStop(function() {
				element.show();
				axelor.$adjustSize();
			});
		});
	}
});

/**
 * This directive is used filter $watch on scopes of inactive tabs.
 *
 */
ui.directive('uiTabGate', function() {

	return {

		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					scope.$watchChecker(function(current) {
						if (current.tabSelected === undefined) {
							return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
						}
						return current.tabSelected;
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes of hidden forms.
 *
 */
ui.directive('uiFormGate', function() {

	return {
		compile: function compile(tElement, tAttrs) {

			return {
				pre: function preLink(scope, element, attrs) {
					var parent = null;
					scope.$watchChecker(function(current) {
						if (scope.tabSelected === false) {
							return false;
						}
						if (parent === null) {
							parent = element.parents('[ui-show]:first');
						}
						return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
					});
				}
			};
		}
	};
});

/**
 * This directive is used to filter $watch on scopes based on some condition.
 *
 */
ui.directive('uiWatchIf', ['$parse', function($parse) {

	return {
		compile: function compile(tElement, tAttrs) {
			return {
				pre: function preLink(scope, element, attrs) {
					var value = false,
						expression = $parse(attrs.uiWatchIf);

					scope.$watchChecker(function (current) {
						if (current === scope) {
							return value = expression(scope);
						}
						return value;
					});
				}
			};
		}
	};
}]);

function toBoolean(value) {
	if (value && value.length !== 0) {
		var v = angular.lowercase("" + value);
		value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
	} else {
		value = false;
	}
	return value;
}

/**
 * This directive is used to speedup uiFormGate.
 */
ui.directive('uiShow', function() {

	return {
		scope: true, // create new scope to always watch the expression
		link: function link(scope, element, attrs) {
			scope.$$shouldWatch = true;
			scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
				element.css('display', toBoolean(value) ? '' : 'none').toggleClass('ui-hide', !toBoolean(value));
			});
		}
	};
});

ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {

	function isValid(scope, name) {
		if (!name) return scope.isValid();
		var ctrl = scope.form;
		if (ctrl) {
			ctrl = ctrl[name];
		}
		if (ctrl) {
			return ctrl.$valid;
		}
	}

	function withContext(scope, record) {
		var values = record;
		if (scope._context) {
			values = _.extend({}, scope._context, values);
		}
		return values;
	}

	function handleCondition(scope, field, attr, condition, negative) {

		if (!condition) {
			return;
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});

		scope.$watch("isReadonly()", watcher);
		scope.$watch("isRequired()", watcher);
		scope.$watch("isValid()", watcher);

		var expr = $parse(condition);

		function watcher(current, old) {
			if (current !== old) handle(scope.record);
		}

		function handle(rec) {
			var value = undefined;
			try {
				value = axelor.$eval(scope, expr, withContext(scope, rec));
			} catch (e) {}
			scope.attr(attr, negative ? !value : value);
		}
	}

	function handleHilites(scope, field) {
		if (!field || _.isEmpty(field.hilites)) {
			return;
		}

		var hilites = field.hilites || [];
		var exprs = _.map(_.pluck(hilites, 'condition'), $parse);

		function handle(rec) {
			for (var i = 0; i < hilites.length; i++) {
				var hilite = hilites[i];
				var expr = exprs[i];
				var value = false;
				try {
					value = axelor.$eval(scope, expr, withContext(scope, rec));
				} catch (e) {}
				if (value) {
					return scope.attr('highlight', {
						hilite: hilite,
						passed: value
					});
				}
			}
			return scope.attr('highlight', {});
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleBind(scope, field) {

		if (!field.bind || !field.name) {
			return;
		}

		var expr = $interpolate(field.bind);

		function handle(rec) {
			var value = undefined;
			try {
				value = expr(withContext(scope, rec)).trim();
				if (value.length == 0) {
					value = null;
				}
			} catch (e) {}

			if (scope.setValue) {
				scope.setValue(value);
			}
		}

		scope.$on("on:record-change", function(e, rec) {
			if (rec === scope.record) {
				handle(rec);
			}
		});
	}

	function handleFor(scope, field, attr, conditional, negative) {
		if (field[conditional]) {
			handleCondition(scope, field, attr, field[conditional], negative);
		}
	}

	function handleForField(scope) {
		var field = scope.field;
		if (!field) return;
		handleFor(scope, field, "valid", "validIf");
		handleFor(scope, field, "hidden", "hideIf");
		handleFor(scope, field, "hidden", "showIf", true);
		handleFor(scope, field, "readonly", "readonlyIf");
		handleFor(scope, field, "required", "requiredIf");
		handleFor(scope, field, "collapse", "collapseIf");
		handleHilites(scope, field);
		handleBind(scope, field);
	}

	function handleForView(scope) {
		var field = scope.schema;
		if (!field) return;
		handleFor(scope, field, "canNew", "canNew");
		handleFor(scope, field, "canEdit", "canEdit");
		handleFor(scope, field, "canSave", "canSave");
		handleFor(scope, field, "canCopy", "canCopy");
		handleFor(scope, field, "canDelete", "canDelete");
		handleFor(scope, field, "canAttach", "canAttach");
	}

	return function(scope, element, attrs) {
		scope.$evalAsync(function() {
			if (element.is('[ui-form]')) {
				return handleForView(scope);
			}
			handleForField(scope);
		});
	};
}]);

})(this);
<|endoftext|>


<commit_before> const { src } = require('../../helper/path');

 const _ = require('lodash');

 const db = require(`${src}/handler/db`);
 const { templates } = require(`${src}/utils/responseCodes`);

 const LoginRecord = require(`${src}/models/loginRecord`);

 const testSaveLoginRecord = () => {
     const checkResults = (fn, teamspace, arr, dataOverride) => {
         expect(fn.mock.calls.length).toBe(1);
         expect(fn.mock.calls[0][2]).toEqual({ user });
         expect(fn.mock.calls[0][3]).toEqual(determineAction(teamspace, arr, dataOverride));
     };

     describe('Add containers to favourites', () => {
         test('Should add favourite containers if the user has no favourites entry', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: {} });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace3';
             const arr = ['e', 'f'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr, {});
         });

         test('Should add favourite containers under a new teamspace', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: favouritesData });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace3';
             const arr = ['e', 'f'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr);
         });

         test('Should add favourite containers on an existing teamspace', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: favouritesData });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace1';
             const arr = ['d', 'e'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr);
         });

         test('Should add favourite containers on an existing teamspace and ignore duplicate entries', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: favouritesData });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace1';
             const arr = ['a', 'b', 'c', ' d', 'e'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr);
         });

         test('Should return error when trying to add favourites to a user that doesnt exist', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue(undefined);
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             await expect(User.appendFavourites(user, 'teamspace3', ['e', 'f']))
                 .rejects.toEqual(templates.userNotFound);
             expect(fn.mock.calls.length).toBe(0);
         });
     });
 };

 describe('models/loginRecord', () => {
    testSaveLoginRecord();
 });
 <commit_msg>ISSUE #2865 mock  implementation of getLocationFromIPAddress<commit_after> const { src } = require('../../helper/path');

 const _ = require('lodash');

 const db = require(`${src}/handler/db`);
 const { templates } = require(`${src}/utils/responseCodes`);
 const Strings  = require(`${src}/helpers/strings`);

 Strings.getLocationFromIPAddress.mockImplementation(() => { "United Kingdom", "London" });

 const LoginRecord = require(`${src}/models/loginRecord`);

 const testSaveLoginRecord = () => {
     const checkResults = (fn, teamspace, arr, dataOverride) => {
         expect(fn.mock.calls.length).toBe(1);
         expect(fn.mock.calls[0][2]).toEqual({ user });
         expect(fn.mock.calls[0][3]).toEqual(determineAction(teamspace, arr, dataOverride));
     };

     describe('Add containers to favourites', () => {
         test('Should add favourite containers if the user has no favourites entry', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: {} });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace3';
             const arr = ['e', 'f'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr, {});
         });

         test('Should add favourite containers under a new teamspace', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: favouritesData });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace3';
             const arr = ['e', 'f'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr);
         });

         test('Should add favourite containers on an existing teamspace', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: favouritesData });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace1';
             const arr = ['d', 'e'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr);
         });

         test('Should add favourite containers on an existing teamspace and ignore duplicate entries', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue({ customData: favouritesData });
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             const teamspace = 'teamspace1';
             const arr = ['a', 'b', 'c', ' d', 'e'];
             await expect(User.appendFavourites(user, teamspace, arr)).resolves.toBe(undefined);
             checkResults(fn, teamspace, arr);
         });

         test('Should return error when trying to add favourites to a user that doesnt exist', async () => {
             jest.spyOn(db, 'findOne').mockResolvedValue(undefined);
             const fn = jest.spyOn(db, 'updateOne').mockImplementation(() => {});
             await expect(User.appendFavourites(user, 'teamspace3', ['e', 'f']))
                 .rejects.toEqual(templates.userNotFound);
             expect(fn.mock.calls.length).toBe(0);
         });
     });
 };

 describe('models/loginRecord', () => {
    testSaveLoginRecord();
 });
 <|endoftext|>


<commit_before>module.exports = {
  root: true,
  parser: "@typescript-eslint/parser",
  env: {
    browser: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:prettier/recommended",
    "prettier",
    "prettier/@typescript-eslint",
    "prettier/react",
  ],
  parserOptions: {
    project: "tsconfig.json",
    sourceType: "module",
  },
  plugins: ["@typescript-eslint"],
  rules: {
    "@typescript-eslint/array-type": [
      "error",
      {
        default: "generic",
      },
    ],
    "@typescript-eslint/await-thenable": "error",
    // "@typescript-eslint/return-await": ["error", "always"], // なんかasync関数じゃないのにfixしたらawaitつけてくる…
    "@typescript-eslint/no-empty-function": "off",
    "@typescript-eslint/no-empty-interface": "off",
    "@typescript-eslint/no-floating-promises": "warn", // そのうちエラーにしたい(Reactのコールバック的に今は無理)
    "@typescript-eslint/prefer-for-of": "error",
    "@typescript-eslint/prefer-function-type": "error",
    camelcase: "error",
    complexity: "warn", // 便利そう
    "dot-notation": "error",
    eqeqeq: ["error"],
    "guard-for-in": "error",
    "new-parens": "error",
    "no-bitwise": "error",
    "no-caller": "error",
    "no-console": "warn", // そのうち
    "no-fallthrough": "error",
    "no-new-wrappers": "error",
    "no-shadow": [
      "error",
      {
        builtinGlobals: false, // そのうち
        hoist: "all",
      },
    ],
    "no-underscore-dangle": [
      "error",
      {
        allow: [
          "__typename",
          "__ENV__",
          "__RAW_ENV__",
          "__BUILD_DATE__",
          "__MODE__",
          "__ENABLE_BFF__",
        ],
      },
    ],
    "no-unsafe-finally": "error",
    "no-unused-expressions": "error",
    "no-var": "error",
    "prefer-const": "error",
    // strict-type-predicates入れたい: https://github.com/typescript-eslint/typescript-eslint/pull/738
    "react/jsx-boolean-value": ["error", "always"],
    "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
    // unused-importがほしい: https://github.com/typescript-eslint/typescript-eslint/issues/371
    "react/display-name": "warn",
    "@typescript-eslint/no-use-before-define": [
      "error",
      {
        functions: false,
      },
    ],
    "@typescript-eslint/require-await": "warn",
  },
  settings: {
    react: {
      createClass: "createReactClass",
      pragma: "React",
      version: "detect",
    },
  },
};
<commit_msg>camelcaseがtsプラグインにもあったので無効化<commit_after>module.exports = {
  root: true,
  parser: "@typescript-eslint/parser",
  env: {
    browser: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:prettier/recommended",
    "prettier",
    "prettier/@typescript-eslint",
    "prettier/react",
  ],
  parserOptions: {
    project: "tsconfig.json",
    sourceType: "module",
  },
  plugins: ["@typescript-eslint"],
  rules: {
    "@typescript-eslint/array-type": [
      "error",
      {
        default: "generic",
      },
    ],
    "@typescript-eslint/await-thenable": "error",
    // "@typescript-eslint/return-await": ["error", "always"], // なんかasync関数じゃないのにfixしたらawaitつけてくる…
    "@typescript-eslint/no-empty-function": "off",
    "@typescript-eslint/no-empty-interface": "off",
    "@typescript-eslint/no-floating-promises": "warn", // そのうちエラーにしたい(Reactのコールバック的に今は無理)
    "@typescript-eslint/prefer-for-of": "error",
    "@typescript-eslint/prefer-function-type": "error",
    complexity: "warn", // 便利そう
    "dot-notation": "error",
    eqeqeq: ["error"],
    "guard-for-in": "error",
    "new-parens": "error",
    "no-bitwise": "error",
    "no-caller": "error",
    "no-console": "warn", // そのうち
    "no-fallthrough": "error",
    "no-new-wrappers": "error",
    "no-shadow": [
      "error",
      {
        builtinGlobals: false, // そのうち
        hoist: "all",
      },
    ],
    "no-underscore-dangle": [
      "error",
      {
        allow: [
          "__typename",
          "__ENV__",
          "__RAW_ENV__",
          "__BUILD_DATE__",
          "__MODE__",
          "__ENABLE_BFF__",
        ],
      },
    ],
    "no-unsafe-finally": "error",
    "no-unused-expressions": "error",
    "no-var": "error",
    "prefer-const": "error",
    // strict-type-predicates入れたい: https://github.com/typescript-eslint/typescript-eslint/pull/738
    "react/jsx-boolean-value": ["error", "always"],
    "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
    // unused-importがほしい: https://github.com/typescript-eslint/typescript-eslint/issues/371
    "react/display-name": "warn",
    "@typescript-eslint/no-use-before-define": [
      "error",
      {
        functions: false,
      },
    ],
    "@typescript-eslint/require-await": "warn",
  },
  settings: {
    react: {
      createClass: "createReactClass",
      pragma: "React",
      version: "detect",
    },
  },
};
<|endoftext|>


<commit_before>var test                         = require('tap').test;
       rift                         = require('../index'),
       RiftError               = rift.RiftError,
       RiftRequestError = rift.RiftRequestError,
       RiftSocket            = require('../src/rift_socket'),
       RiftResolver         = rift.RiftResolver,
       client                    = require('./test.rift'),
       nock                     = require('nock'),
       sinon                    = require('sinon'),
       Promise                = require('bluebird'),
       server                   = require('./test_socket_server');

// record http traffic
// nock.recorder.rec();

var api = rift();
api.set('base', '/api');
api.define(client);
api.use(RiftSocket);
api.registerResolver(RiftResolver);

test('should make a request using socket', function (t) {
  server.start();

  api.request('socketTest').then(function (response) {
    t.equals(response.data.message, 'socketTest has been received.');
    t.end();
    server.stop();
  });
});
<commit_msg>adding catch to socket promise<commit_after>var test                         = require('tap').test;
       rift                         = require('../index'),
       RiftError               = rift.RiftError,
       RiftRequestError = rift.RiftRequestError,
       RiftSocket            = require('../src/rift_socket'),
       RiftResolver         = rift.RiftResolver,
       client                    = require('./test.rift'),
       nock                     = require('nock'),
       sinon                    = require('sinon'),
       Promise                = require('bluebird'),
       server                   = require('./test_socket_server');

// record http traffic
// nock.recorder.rec();

var api = rift();
api.set('base', '/api');
api.define(client);
api.use(RiftSocket);
api.registerResolver(RiftResolver);

test('should make a request using socket', function (t) {
  server.start();

  api.request('socketTest').then(function (response) {
    t.equals(response.data.message, 'socketTest has been received.');
    t.end();
    server.stop();
  }).catch(function () {
    t.end();
    server.stop();
  });
});
<|endoftext|>


<commit_before>import React, {Component} from "react";
import {connect} from "react-redux";
import {getHostname, extractMainDomain, getSetting, prepareCookieDomain} from "../../services/libs";
import FilteredExpression from "./components/FilteredExpression";
import RowAction from "./components/RowAction";
import {addExpressionUI, cookieCleanupUI, updateSettingUI} from "../UIActions";
import CheckboxSetting from "../common_components/CheckboxSetting";

const styles = {
	rowText: {
		margin: "0 5px 0px 15px",
		fontSize: "18px",
		fontWeight: "bold"
	}
};

class App extends Component {
	constructor(props) {
		super(props);
		this.state = {
			tab: {},
			storeId: ""
		};
	}

	async componentDidMount() {
		const tabs = await browser.tabs.query({
			currentWindow: true, active: true
		});
		this.setState({
			tab: tabs[0],
			storeId: !this.props.contextualIdentities || tabs[0].cookieStoreId === "firefox-default" ? "default" : tabs[0].cookieStoreId
		});
	}

	// Flash a green background if successfull
	animateSuccess(element) {
		element.classList.add("successAnimated");
		setTimeout(() => {
			element.classList.remove("successAnimated");
		}, 1500);
	}

	// Flash a red background if it failed or couldn't be done
	animateFailure(element) {
		element.classList.add("failureAnimated");
		setTimeout(() => {
			element.classList.remove("failureAnimated");
		}, 1500);
	}

	async clearCookiesForThisDomain(hostname) {
		const {
			cookieStoreId
		} = this.state.tab;
		const cookies = await browser.cookies.getAll({
			domain: hostname,
			storeId: cookieStoreId
		});
		if (cookies.length > 0) {
			cookies.forEach((cookie) => browser.cookies.remove({
				url: prepareCookieDomain(cookie), name: cookie.name, storeId: cookie.storeId
			}));
			this.animateSuccess(document.getElementById("cookieCleanup3"));
		} else {
			this.animateFailure(document.getElementById("cookieCleanup3"));
		}
	}

	render() {
		const {
			tab, storeId
		} = this.state;
		const {
			onNewExpression, onCookieCleanup, onUpdateSetting, contextualIdentities, cache, settings
		} = this.props;
		const hostname = getHostname(tab.url);
		const mainDomain = extractMainDomain(hostname);
		return (

						{browser.i18n.getMessage("hostWebsiteText")}
						 browser.runtime.openOptionsPage()} className="fa fa-cog fa-2x cursorPoint" aria-hidden="true">

						{!contextualIdentities ? `${hostname}` : `${hostname} (${cache[storeId]})`}

					{`${browser.i18n.getMessage("switchToGreyListText")}`}

				{
					hostname !== mainDomain ?
						<RowAction
							text={`- *.${mainDomain}`}
							action={() => onNewExpression({
								expression: `*.${mainDomain}`, listType: "GREY", storeId
							})}
							labelFor="addExpressionGrey"
						/> : ""
				}
				<RowAction
					text={`- ${hostname}`}
					action={() => onNewExpression({
						expression: `${hostname}`, listType: "GREY", storeId
					})}
					labelFor="addExpressionGrey"
				/>
				<RowAction
					text={`- *.${hostname}`}
					action={() => onNewExpression({
						expression: `*.${hostname}`, listType: "GREY", storeId
					})}
					labelFor="addExpressionGrey"
				/>

					{`${browser.i18n.getMessage("switchToWhiteListText")}`}

				{
					hostname !== mainDomain ?
						<RowAction
							text={`- *.${mainDomain}`}
							action={() => onNewExpression({
								expression: `*.${mainDomain}`, listType: "WHITE", storeId
							})}
							labelFor="addExpressionGrey"
						/> : ""
				}

				<RowAction
					text={`- ${hostname}`}
					action={() => onNewExpression({
						expression: `${hostname}`, listType: "WHITE", storeId
					})}
					labelFor="addExpressionGrey"
				/>

				<RowAction
					text={`- *.${hostname}`}
					action={() => onNewExpression({
						expression: `*.${hostname}`, listType: "WHITE", storeId
					})}
					labelFor="addExpressionWhite"
				/>

				<RowAction
					text={browser.i18n.getMessage("cookieCleanupText")}
					action={() => {
						onCookieCleanup({
							greyCleanup: false, ignoreOpenTabs: false
						});
						this.animateSuccess(document.getElementById("cookieCleanup1"));
					}}
					labelFor="cookieCleanup1"
				/>
				<RowAction
					text={browser.i18n.getMessage("cookieCleanupIgnoreOpenTabsText")}
					action={() => {
						onCookieCleanup({
							greyCleanup: false, ignoreOpenTabs: true
						});
						this.animateSuccess(document.getElementById("cookieCleanup2"));
					}}
					labelFor="cookieCleanup2"
				/>
				<RowAction
					text={browser.i18n.getMessage("clearCookiesForDomainText")}
					action={() => this.clearCookiesForThisDomain(hostname)}
					labelFor="cookieCleanup3"
				/>

					<CheckboxSetting
						text={browser.i18n.getMessage("activeModeText")}
						settingObject={settings.activeMode}
						inline={true}
						bsStyle={"checkbox-success"}
						updateSetting={(payload) => onUpdateSetting(payload)}
					/>
					<input
						type="number"
						className="form-control"
						style={{
							display: "inline", width: "15%"
						}}
						onChange={(e) => onUpdateSetting({
							name: settings.delayBeforeClean.name, value: e.target.value, id: settings.delayBeforeClean.id
						})}
						value={settings.delayBeforeClean.value}
						min="1"
					/>
					{browser.i18n.getMessage("minutesText")}

					<FilteredExpression url={hostname} storeId={storeId}/>

		);
	}
}

const mapStateToProps = (state) => {
	const {
		cache, settings
	} = state;
	return {
		contextualIdentities: getSetting(state, "contextualIdentities"), cache, settings
	};
};

const mapDispatchToProps = (dispatch) => (
	{
		onUpdateSetting(newSetting) {
			dispatch(
				updateSettingUI(newSetting)
			);
		},
		onNewExpression(payload) {
			dispatch(
				addExpressionUI(payload)
			);
		},
		onCookieCleanup(payload) {
			dispatch(
				cookieCleanupUI(payload)
			);
		}
	});

export default connect(mapStateToProps, mapDispatchToProps)(App);
<commit_msg>Simple Switch for Active Mode in Popup<commit_after>import React, {Component} from "react";
import {connect} from "react-redux";
import {getHostname, extractMainDomain, getSetting, prepareCookieDomain} from "../../services/libs";
import FilteredExpression from "./components/FilteredExpression";
import RowAction from "./components/RowAction";
import {addExpressionUI, cookieCleanupUI, updateSettingUI} from "../UIActions";
import CheckboxSetting from "../common_components/CheckboxSetting";

const styles = {
	rowText: {
		margin: "0 5px 0px 15px",
		fontSize: "18px",
		fontWeight: "bold"
	}
};

class App extends Component {
	constructor(props) {
		super(props);
		this.state = {
			tab: {},
			storeId: ""
		};
	}

	async componentDidMount() {
		const tabs = await browser.tabs.query({
			currentWindow: true, active: true
		});
		this.setState({
			tab: tabs[0],
			storeId: !this.props.contextualIdentities || tabs[0].cookieStoreId === "firefox-default" ? "default" : tabs[0].cookieStoreId
		});
	}

	// Flash a green background if successfull
	animateSuccess(element) {
		element.classList.add("successAnimated");
		setTimeout(() => {
			element.classList.remove("successAnimated");
		}, 1500);
	}

	// Flash a red background if it failed or couldn't be done
	animateFailure(element) {
		element.classList.add("failureAnimated");
		setTimeout(() => {
			element.classList.remove("failureAnimated");
		}, 1500);
	}

	async clearCookiesForThisDomain(hostname) {
		const {
			cookieStoreId
		} = this.state.tab;
		const cookies = await browser.cookies.getAll({
			domain: hostname,
			storeId: cookieStoreId
		});
		if (cookies.length > 0) {
			cookies.forEach((cookie) => browser.cookies.remove({
				url: prepareCookieDomain(cookie), name: cookie.name, storeId: cookie.storeId
			}));
			this.animateSuccess(document.getElementById("cookieCleanup3"));
		} else {
			this.animateFailure(document.getElementById("cookieCleanup3"));
		}
	}

	render() {
		const {
			tab, storeId
		} = this.state;
		const {
			onNewExpression, onCookieCleanup, onUpdateSetting, contextualIdentities, cache, settings
		} = this.props;
		const hostname = getHostname(tab.url);
		const mainDomain = extractMainDomain(hostname);
		return (

						{browser.i18n.getMessage("hostWebsiteText")}
						 browser.runtime.openOptionsPage()} className="fa fa-cog fa-2x cursorPoint" aria-hidden="true">

						{!contextualIdentities ? `${hostname}` : `${hostname} (${cache[storeId]})`}

					{`${browser.i18n.getMessage("switchToGreyListText")}`}

				{
					hostname !== mainDomain ?
						<RowAction
							text={`- *.${mainDomain}`}
							action={() => onNewExpression({
								expression: `*.${mainDomain}`, listType: "GREY", storeId
							})}
							labelFor="addExpressionGrey"
						/> : ""
				}
				<RowAction
					text={`- ${hostname}`}
					action={() => onNewExpression({
						expression: `${hostname}`, listType: "GREY", storeId
					})}
					labelFor="addExpressionGrey"
				/>
				<RowAction
					text={`- *.${hostname}`}
					action={() => onNewExpression({
						expression: `*.${hostname}`, listType: "GREY", storeId
					})}
					labelFor="addExpressionGrey"
				/>

					{`${browser.i18n.getMessage("switchToWhiteListText")}`}

				{
					hostname !== mainDomain ?
						<RowAction
							text={`- *.${mainDomain}`}
							action={() => onNewExpression({
								expression: `*.${mainDomain}`, listType: "WHITE", storeId
							})}
							labelFor="addExpressionGrey"
						/> : ""
				}

				<RowAction
					text={`- ${hostname}`}
					action={() => onNewExpression({
						expression: `${hostname}`, listType: "WHITE", storeId
					})}
					labelFor="addExpressionGrey"
				/>

				<RowAction
					text={`- *.${hostname}`}
					action={() => onNewExpression({
						expression: `*.${hostname}`, listType: "WHITE", storeId
					})}
					labelFor="addExpressionWhite"
				/>

				<RowAction
					text={browser.i18n.getMessage("cookieCleanupText")}
					action={() => {
						onCookieCleanup({
							greyCleanup: false, ignoreOpenTabs: false
						});
						this.animateSuccess(document.getElementById("cookieCleanup1"));
					}}
					labelFor="cookieCleanup1"
				/>
				<RowAction
					text={browser.i18n.getMessage("cookieCleanupIgnoreOpenTabsText")}
					action={() => {
						onCookieCleanup({
							greyCleanup: false, ignoreOpenTabs: true
						});
						this.animateSuccess(document.getElementById("cookieCleanup2"));
					}}
					labelFor="cookieCleanup2"
				/>
				<RowAction
					text={browser.i18n.getMessage("clearCookiesForDomainText")}
					action={() => this.clearCookiesForThisDomain(hostname)}
					labelFor="cookieCleanup3"
				/>

					<CheckboxSetting
						text={browser.i18n.getMessage("activeModeSwitchText")}
						settingObject={settings.activeMode}
						inline={true}
						bsStyle={"checkbox-success"}
						updateSetting={(payload) => onUpdateSetting(payload)}
					/>

					<FilteredExpression url={hostname} storeId={storeId}/>

		);
	}
}

const mapStateToProps = (state) => {
	const {
		cache, settings
	} = state;
	return {
		contextualIdentities: getSetting(state, "contextualIdentities"), cache, settings
	};
};

const mapDispatchToProps = (dispatch) => (
	{
		onUpdateSetting(newSetting) {
			dispatch(
				updateSettingUI(newSetting)
			);
		},
		onNewExpression(payload) {
			dispatch(
				addExpressionUI(payload)
			);
		},
		onCookieCleanup(payload) {
			dispatch(
				cookieCleanupUI(payload)
			);
		}
	});

export default connect(mapStateToProps, mapDispatchToProps)(App);
<|endoftext|>


<commit_before>/**
 * Room constructor
 *
 * @class Roguelike.Room
 * @classdesc A room object that creates his own layout!
 *
 * @param {number} x - The x coordinate of the top left corner of this room
 * @param {number} y - The y coordinate of the top left corner of this room
 * @param {number} w - The width of this room
 * @param {number} h - The height of this room
 */
Roguelike.Room = function(x, y, w, h) {

	/**
	 * @property {number} x1 - The X position of the top left corner of this room
	 */
	this.x1 = x;

	/**
	 * @property {number} x2 - The X position of the top right corner of this room
	 */
	this.x2 = w + x;

	/**
	 * @property {number} y1 - The Y position of top left corner of this room
	 */
	this.y1 = y;

	/**
	 * @property {number} y2 - The Y position of bottom left corner of this room
	 */
	this.y2 = y + h;

	/**
	 * @property {number} w - The width of this room, defined in tiles
	 */
	this.w = w;

	/**
	 * @property {number} h - The height of this room, defined in tiles
	 */
	this.h = h;

	/**
	 * @property {array} layout - The array that contains the layout of this room
	 */
	this.layout = [];

	/**
	 * @property {object} exit - An object that holds the exit coordinates of this room
	 */
	this.exit = null;

};

Roguelike.Room.prototype = {

	/**
	 * Initialize the layout of the room, filling it with default tiles
	 * @protected
	 */
	//TODO: Remove game passed along here
	initialize: function(game) {

		//Loop through every horizontal row
		for(var y = 0; y < this.h; y++) {

			//Initialize this row
			this.layout[y] = [];

			//Loop through every vertical row
			for(var x = 0; x < this.w; x++) {

				//Check if the position filled has to be a wall or floor
				if(y === 0 || y === this.h - 1 || x === 0 || x === this.w - 1) {

					//Create a new wall tile
					this.layout[y][x] = new Roguelike.Tile(1, true, this);

				}else{

					//Create a new floor tile
					this.layout[y][x] = new Roguelike.Tile(2, false, this);

					//TODO: Don't generate grass in a room, create a map decorator!
					//Have a random chance to spawn grass on this tile
					if(Roguelike.Utils.randomNumber(0, 100) >= 70){

						grassEntity = new Roguelike.DecorationFactory.newGrass({x: x, y: y});

						this.layout[y][x].entities.push(grassEntity);

						//Add the entity to the map
						game.map.entities.addEntity(grassEntity);

					}

				}

			}

		}

	},

	/**
	 * Generate an exit on a random side of this room
	 * @protected
	 */
	generateExit: function() {

		//Add an exit on one of the sides of the wall
		//But one tile further into the room, so we don't get weird openings
		//when the generation of a corridor goes the other direction
		switch(Roguelike.Utils.randomNumber(1, 4)) {

			case(1): //Top

				this.exit = {x: Roguelike.Utils.randomNumber(1, this.w - 2), y: this.h - 2};

				break;

			case(2): //Bottom

				this.exit = {x: Roguelike.Utils.randomNumber(1, this.w - 2), y: 1};

				break;

			case(3): //Left

				this.exit = {x: this.w - 2, y: Roguelike.Utils.randomNumber(1, this.h - 2)};

				break;

			case(4): //Right

				this.exit = {x: 1, y: Roguelike.Utils.randomNumber(1, this.h - 2)};

				break;

		}

	},

	/**
	 * Returns a random position that is inside the current room
	 * @protected
	 */
	getRandomPosition: function() {

		//Get random positions within the room
		var positionX = Roguelike.Utils.randomNumber(2, this.w - 3);
		var positionY = Roguelike.Utils.randomNumber(2, this.h - 3);

		//Translate these values to real world coordinates
		positionX += this.x1;
		positionY += this.y1;

		//Return the position as an object
		return {x: positionX, y: positionY};

	}

};
<commit_msg>Fixed the "TODO: Remove game passed along here".<commit_after>/**
 * Room constructor
 *
 * @class Roguelike.Room
 * @classdesc A room object that creates his own layout!
 *
 * @param {number} x - The x coordinate of the top left corner of this room
 * @param {number} y - The y coordinate of the top left corner of this room
 * @param {number} w - The width of this room
 * @param {number} h - The height of this room
 */
Roguelike.Room = function(x, y, w, h) {

	/**
	 * @property {number} x1 - The X position of the top left corner of this room
	 */
	this.x1 = x;

	/**
	 * @property {number} x2 - The X position of the top right corner of this room
	 */
	this.x2 = w + x;

	/**
	 * @property {number} y1 - The Y position of top left corner of this room
	 */
	this.y1 = y;

	/**
	 * @property {number} y2 - The Y position of bottom left corner of this room
	 */
	this.y2 = y + h;

	/**
	 * @property {number} w - The width of this room, defined in tiles
	 */
	this.w = w;

	/**
	 * @property {number} h - The height of this room, defined in tiles
	 */
	this.h = h;

	/**
	 * @property {array} layout - The array that contains the layout of this room
	 */
	this.layout = [];

	/**
	 * @property {object} exit - An object that holds the exit coordinates of this room
	 */
	this.exit = null;

};

Roguelike.Room.prototype = {

	/**
	 * Initialize the layout of the room, filling it with default tiles
	 * @protected
	 */
	initialize: function() {

		//Loop through every horizontal row
		for(var y = 0; y < this.h; y++) {

			//Initialize this row
			this.layout[y] = [];

			//Loop through every vertical row
			for(var x = 0; x < this.w; x++) {

				//Check if the position filled has to be a wall or floor
				if(y === 0 || y === this.h - 1 || x === 0 || x === this.w - 1) {

					//Create a new wall tile
					this.layout[y][x] = new Roguelike.Tile(1, true, this, 0, 1);

				}else{

					//Create a new floor tile
					this.layout[y][x] = new Roguelike.Tile(2, false, this, 0, Roguelike.Utils.randomNumber(2, 5));

					//TODO: Don't generate grass in a room, create a map decorator!
					//Have a random chance to spawn grass on this tile
					if(Roguelike.Utils.randomNumber(0, 100) >= 70){

						grassEntity = new Roguelike.DecorationFactory.newGrass({x: x, y: y});

						this.layout[y][x].entities.push(grassEntity);

						//Add the entity to the map
						//game.map.entities.addEntity(grassEntity);

					}

				}

			}

		}

	},

	/**
	 * Generate an exit on a random side of this room
	 * @protected
	 */
	generateExit: function() {

		//Add an exit on one of the sides of the wall
		//But one tile further into the room, so we don't get weird openings
		//when the generation of a corridor goes the other direction
		switch(Roguelike.Utils.randomNumber(1, 4)) {

			case(1): //Top

				this.exit = {x: Roguelike.Utils.randomNumber(1, this.w - 2), y: this.h - 2};

				break;

			case(2): //Bottom

				this.exit = {x: Roguelike.Utils.randomNumber(1, this.w - 2), y: 1};

				break;

			case(3): //Left

				this.exit = {x: this.w - 2, y: Roguelike.Utils.randomNumber(1, this.h - 2)};

				break;

			case(4): //Right

				this.exit = {x: 1, y: Roguelike.Utils.randomNumber(1, this.h - 2)};

				break;

		}

	},

	/**
	 * Returns a random position that is inside the current room
	 * @protected
	 */
	getRandomPosition: function() {

		//Get random positions within the room
		var positionX = Roguelike.Utils.randomNumber(2, this.w - 3);
		var positionY = Roguelike.Utils.randomNumber(2, this.h - 3);

		//Translate these values to real world coordinates
		positionX += this.x1;
		positionY += this.y1;

		//Return the position as an object
		return {x: positionX, y: positionY};

	}

};
<|endoftext|>


<commit_before>include('const.js');
/**
 * Shortcut access to uki.build, uki.Selector.find and uki.Collection constructor
 * uki('#id') is also a shortcut for search by id
 *
 * @param {String|uki.view.Base|Object|Array.<uki.view.Base>} val
 * @param {Array.<uki.view.Base>=} optional context for selector
 * @class
 * @namespace
 * @name uki
 * @return {uki.Collection}
 */
root.uki = root.uki || function(val, context) {
    if (typeof val == 'string') {
        var m = val.match(/^#((?:[\w\u00c0-\uFFFF_-]|\\.)+)$/),
            e = m && uki._ids[m[1]];
        if (m && !context) {
            return new uki.Collection( e ? [e] : [] );
        }
        return uki.find(val, context);
    }
    if (val.length === undefined) val = [val];
    if (val.length > 0 && uki.isFunction(val[0].typeName)) return new uki.Collection(val);
    return uki.build(val);
};

/**
 * @type string
 * @field
 */
uki.version = '0.1.0.1';

/**
 * Empty function
 * @type function():boolean
 */
uki.F = function() { return false; };
uki._ids = {};

uki.registerId = function(comp) {
    uki._ids[uki.attr(comp, 'id')] = comp;
};
uki.unregisterId = function(comp) {
    uki._ids[uki.attr(comp, 'id')] = undefined;
};

<commit_msg>version up<commit_after>include('const.js');
/**
 * Shortcut access to uki.build, uki.Selector.find and uki.Collection constructor
 * uki('#id') is also a shortcut for search by id
 *
 * @param {String|uki.view.Base|Object|Array.<uki.view.Base>} val
 * @param {Array.<uki.view.Base>=} optional context for selector
 * @class
 * @namespace
 * @name uki
 * @return {uki.Collection}
 */
root.uki = root.uki || function(val, context) {
    if (typeof val == 'string') {
        var m = val.match(/^#((?:[\w\u00c0-\uFFFF_-]|\\.)+)$/),
            e = m && uki._ids[m[1]];
        if (m && !context) {
            return new uki.Collection( e ? [e] : [] );
        }
        return uki.find(val, context);
    }
    if (val.length === undefined) val = [val];
    if (val.length > 0 && uki.isFunction(val[0].typeName)) return new uki.Collection(val);
    return uki.build(val);
};

/**
 * @type string
 * @field
 */
uki.version = '0.1.1';

/**
 * Empty function
 * @type function():boolean
 */
uki.F = function() { return false; };
uki._ids = {};

uki.registerId = function(comp) {
    uki._ids[uki.attr(comp, 'id')] = comp;
};
uki.unregisterId = function(comp) {
    uki._ids[uki.attr(comp, 'id')] = undefined;
};

<|endoftext|>


<commit_before>const Promise = require('bluebird');
const _ = require('lodash');

function Topics(mediator) {
  this.mediator = mediator;
  this.subscriptions = {};
}

/**
 * Sets the prefix configuration for this instance, which will be part of the name
 * of the handled topics.
 * @param  {String} prefix
 * @return {Topics}        returns self for chaining
 */
Topics.prototype.prefix = function(prefix) {
  this.prefix = prefix;
  return this;
};

/**
 * Sets the entity configuration for this instance, which will be part of the name
 * of the handled topics.
 * This property is present as a convenience so it can be accessed by handlers via `this.entity`
 *
 * @param  {String} entity
 * @return {Topics}        returns self for chaining
 */
Topics.prototype.entity = function(entity) {
  this.entity = entity;
  return this;
};

/**
 * Internal function to add a subscription to the internal collection
 * @param {string}   topic topic id
 * @param {Function} fn    handler for the topic
 */
Topics.prototype.addSubscription = function(topic, fn) {
  this.subscriptions[topic] = this.mediator.subscribe(topic, fn);
};

/**
 * Builds a topic name out of the configured {@link prefix} and {@link entity}
 * @param  {String} topicName The name of the sub-topic to build
 * @param  {String} prefix    An optional prefix to the final topic, i.e. 'done'
 * @return {String}           The complete topic name,
 *                              i.e. {prefix}:{this.prefix}:{this.entity}:{topicName}
 */
Topics.prototype.getTopic = function(topicName, prefix) {
  // create, done => done:wfm:user:create
  var parts = _.compact([this.prefix, this.entity, topicName]);
  if (prefix) {
    parts.unshift(prefix);
  }
  return parts.join(':');
};

/**
 * Internal function to wrap a `on` handler in a promise that will publish to the
 * related 'done:' and 'error:' topics
 * @param  {Topics}   self   The instance, receive as a param to avoid exposing this function
 *                           in the prototype
 * @param  {String}   method The base topic to publish results to
 * @param  {Function} fn     Handler to wrap, can return a value or a Promise, will be invoked bound to self
 * @return {Function}        Wrapped handler
 */
function wrapInMediatorPromise(self, method, fn) {
  function publishDone(result) {
    if (_.isUndefined(result)) {
      return;
    }

    var topic = self.getTopic(method, 'done');
    if (_.has(result, 'id')) {
      topic = [topic, result.id].join(':');
    } else if (typeof result === 'string') {
      topic = [topic, result].join(':');
    }
    self.mediator.publish(topic, result);
    return result
  }

  function publishError(error) {
    var topic = self.getTopic(method, 'error');
    if (_.has(error, 'id')) {
      topic = [topic, error.id].join(':');
    }
    self.mediator.publish(topic, error);
  }

  return function() {
    return Promise.resolve(fn.apply(self, arguments))
      .then(publishDone)
      .catch(publishError);
  };
}

/**
 * Setup a handler for a namespaced topic, if this handler returns a value or throws an Error,
 * it will get published to 'done' and 'error'-predixed topics as per convention.
 *
 * @param  {String}   method Topic name inside the namespace
 * @param  {Function} fn     Handler that can optionally return a value or a Promise
 *                             that will be treated as the result of a `request`
 * @return {Topics}          Returns self for chaining
 */
Topics.prototype.on = function(method, fn) {
  var topic = this.getTopic(method);
  this.addSubscription(topic, wrapInMediatorPromise(this, method, fn));
  return this;
};

/**
 * Setup a handler for a namespaced topic, with the 'done:' prefix
 * @param  {String}   method Topic name inside the namespace
 * @param  {Function} fn     Handler function for the topic
 * @return {Topics}          Returns self for chaining
 */
Topics.prototype.onDone = function(method, fn) {
  var topic = this.getTopic(method, 'done');
  this.addSubscription(topic, fn.bind(this));
  return this;
};

/**
 * Setup a handler for a namespaced topic, with the 'done:' prefix
 * @param  {String}   method Topic name inside the namespace
 * @param  {Function} fn     Handler function for the topic
 * @return {Topics}          Returns self for chaining
 */
Topics.prototype.onError = function(method, fn) {
  var topic = this.getTopic(method, 'error');
  this.addSubscription(topic, fn.bind(this));
  return this;
};

/**
 * Modifies mediator to unsubscribe to all topics configured through this instance
 */
Topics.prototype.unsubscribeAll = function() {
  var subId;
  for (var topic in this.subscriptions) {
    if (this.subscriptions.hasOwnProperty(topic)) {
      subId = this.subscriptions[topic].id;
      this.mediator.remove(topic, subId);
    }
  }
};

/**
 * Does a {@link Mediator.request} in the context of the namespaced topics
 * @param  {String} topic   Base topic inside the configured namespace
 * @param  {Any} params     Data for the `request`
 * @param  {Object} options Options for the `request`
 * @return {Promise}        The result of the `request`
 */
Topics.prototype.request = function(topic, params, options) {
  return this.mediator.request(this.getTopic(topic), params, options);
};

module.exports = Topics;<commit_msg>fix failing build<commit_after>const Promise = require('bluebird');
const _ = require('lodash');

function Topics(mediator) {
  this.mediator = mediator;
  this.subscriptions = {};
}

/**
 * Sets the prefix configuration for this instance, which will be part of the name
 * of the handled topics.
 * @param  {String} prefix
 * @return {Topics}        returns self for chaining
 */
Topics.prototype.prefix = function(prefix) {
  this.prefix = prefix;
  return this;
};

/**
 * Sets the entity configuration for this instance, which will be part of the name
 * of the handled topics.
 * This property is present as a convenience so it can be accessed by handlers via `this.entity`
 *
 * @param  {String} entity
 * @return {Topics}        returns self for chaining
 */
Topics.prototype.entity = function(entity) {
  this.entity = entity;
  return this;
};

/**
 * Internal function to add a subscription to the internal collection
 * @param {string}   topic topic id
 * @param {Function} fn    handler for the topic
 */
Topics.prototype.addSubscription = function(topic, fn) {
  this.subscriptions[topic] = this.mediator.subscribe(topic, fn);
};

/**
 * Builds a topic name out of the configured {@link prefix} and {@link entity}
 * @param  {String} topicName The name of the sub-topic to build
 * @param  {String} prefix    An optional prefix to the final topic, i.e. 'done'
 * @return {String}           The complete topic name,
 *                              i.e. {prefix}:{this.prefix}:{this.entity}:{topicName}
 */
Topics.prototype.getTopic = function(topicName, prefix) {
  // create, done => done:wfm:user:create
  var parts = _.compact([this.prefix, this.entity, topicName]);
  if (prefix) {
    parts.unshift(prefix);
  }
  return parts.join(':');
};

/**
 * Internal function to wrap a `on` handler in a promise that will publish to the
 * related 'done:' and 'error:' topics
 * @param  {Topics}   self   The instance, receive as a param to avoid exposing this function
 *                           in the prototype
 * @param  {String}   method The base topic to publish results to
 * @param  {Function} fn     Handler to wrap, can return a value or a Promise, will be invoked bound to self
 * @return {Function}        Wrapped handler
 */
function wrapInMediatorPromise(self, method, fn) {
  function publishDone(result) {
    if (_.isUndefined(result)) {
      return;
    }

    var topic = self.getTopic(method, 'done');
    if (_.has(result, 'id')) {
      topic = [topic, result.id].join(':');
    } else if (typeof result === 'string') {
      topic = [topic, result].join(':');
    }
    self.mediator.publish(topic, result);
    return result;
  }

  function publishError(error) {
    var topic = self.getTopic(method, 'error');
    if (_.has(error, 'id')) {
      topic = [topic, error.id].join(':');
    }
    self.mediator.publish(topic, error);
  }

  return function() {
    return Promise.resolve(fn.apply(self, arguments))
      .then(publishDone)
      .catch(publishError);
  };
}

/**
 * Setup a handler for a namespaced topic, if this handler returns a value or throws an Error,
 * it will get published to 'done' and 'error'-predixed topics as per convention.
 *
 * @param  {String}   method Topic name inside the namespace
 * @param  {Function} fn     Handler that can optionally return a value or a Promise
 *                             that will be treated as the result of a `request`
 * @return {Topics}          Returns self for chaining
 */
Topics.prototype.on = function(method, fn) {
  var topic = this.getTopic(method);
  this.addSubscription(topic, wrapInMediatorPromise(this, method, fn));
  return this;
};

/**
 * Setup a handler for a namespaced topic, with the 'done:' prefix
 * @param  {String}   method Topic name inside the namespace
 * @param  {Function} fn     Handler function for the topic
 * @return {Topics}          Returns self for chaining
 */
Topics.prototype.onDone = function(method, fn) {
  var topic = this.getTopic(method, 'done');
  this.addSubscription(topic, fn.bind(this));
  return this;
};

/**
 * Setup a handler for a namespaced topic, with the 'done:' prefix
 * @param  {String}   method Topic name inside the namespace
 * @param  {Function} fn     Handler function for the topic
 * @return {Topics}          Returns self for chaining
 */
Topics.prototype.onError = function(method, fn) {
  var topic = this.getTopic(method, 'error');
  this.addSubscription(topic, fn.bind(this));
  return this;
};

/**
 * Modifies mediator to unsubscribe to all topics configured through this instance
 */
Topics.prototype.unsubscribeAll = function() {
  var subId;
  for (var topic in this.subscriptions) {
    if (this.subscriptions.hasOwnProperty(topic)) {
      subId = this.subscriptions[topic].id;
      this.mediator.remove(topic, subId);
    }
  }
};

/**
 * Does a {@link Mediator.request} in the context of the namespaced topics
 * @param  {String} topic   Base topic inside the configured namespace
 * @param  {Any} params     Data for the `request`
 * @param  {Object} options Options for the `request`
 * @return {Promise}        The result of the `request`
 */
Topics.prototype.request = function(topic, params, options) {
  return this.mediator.request(this.getTopic(topic), params, options);
};

module.exports = Topics;<|endoftext|>


<commit_before>describe('Parent Component', function () {
  'use strict';
  var Component = React.createClass({
    mixins: [Backbone.React.Component.mixin],
    render: function () {
      if (spy) spy.call(this);
      return React.DOM.div({}, this.props.hello);
    }
  });

  var component, el, mountedComponent, model1, model2, collection1, collection2, spy;

  beforeEach(function () {
    el = document.createElement('div');
    model1 = new Backbone.Model({hello: 'world!'});
    model2 = new Backbone.Model({goodbye: 'other world!'});
    collection1 = new Backbone.Collection([model1, model2]);
    collection2 = new Backbone.Collection([model2, model1]);
  });

  afterEach(function () {
    model1.stopListening();
    collection1.stopListening();
    React.unmountComponentAtNode(el);
  });

  it('gets the model(s)', function () {
    component = Component({model: model1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.getModel()).toEqual(model1);
  });

  it('gets the collection(s)', function () {
    component = Component({collection: collection1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.getCollection()).toEqual(collection1);
  });

  it('gets the owner', function () {
    component = Component();
    mountedComponent = React.render(component, el);
    expect(mountedComponent.getOwner()).toEqual(mountedComponent);
  });

  it('binds to a model', function (done) {
    component = Component({model: model1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.hello).toEqual('world!');
    model1.set('hello', 'again');
    // Defering because setting props is defered as well
    _.defer(function () {
      expect(mountedComponent.props.hello).toEqual('again');
      done();
    });
  });

  it('binds to multiple models', function () {
    component = Component({
      model: {
        firstModel: model1,
        secondModel: model2
      }
    });
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.firstModel.hello).toEqual('world!');
    expect(mountedComponent.props.secondModel.goodbye).toEqual('other world!');
  });

  it('binds to a collection', function (done) {
    component = Component({collection: collection1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.collection[0].hello).toEqual('world!');
    mountedComponent.getCollection().at(0).set('hello', 'again');
    // Defering because setting props is defered as well
    _.defer(function () {
      expect(mountedComponent.props.collection[0].hello).toEqual('again');
      done();
    });
  });

  it('binds to multiple collections', function (done) {
    component = Component({
      collection: {
        firstCollection: collection1,
        secondCollection: collection2
      }
    });
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.firstCollection[0].hello).toEqual('world!');
    expect(mountedComponent.props.secondCollection[0].goodbye).toEqual('other world!');
    mountedComponent.getCollection().firstCollection.at(0).set('hello', 'again');
    mountedComponent.getCollection().secondCollection.at(0).set('goodbye', 'other again');
    // Defering because setting props is defered as well
    _.defer(function () {
      expect(mountedComponent.props.firstCollection[0].hello).toEqual('again');
      expect(mountedComponent.props.secondCollection[0].goodbye).toEqual('other again');
      done();
    });
  });

  it('updates on collection reset', function (done) {
    component = Component({
        collection: collection1
    });
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.collection[0].hello).toEqual('world!');
    collection1.reset({hello: 'other world!'});
    _.defer(function () {
      expect(mountedComponent.props.collection[0].hello).toEqual('other world!');
      done();
    });
  });

  describe('Child Component', function () {

    describe('with nested models', function () {
      var newSpy;
      var NewComponent = React.createClass({
        mixins: [Backbone.React.Component.mixin],
        render: function () {
          if (newSpy) newSpy.call(this);
          return Component({model: this.getCollection().at(0)});
        }
      });

      beforeEach(function () {
        component = NewComponent({collection: collection1});
      });

      afterEach(function () {
        mountedComponent = React.render(component, el);
        if (newSpy) expect(newSpy).toHaveBeenCalled();
        if (spy) expect(spy).toHaveBeenCalled();
      });

      it('gets the model when inside the child component', function () {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getModel()).toEqual(model1);
        });
      });

      it('gets the same collection inside child and parent components', function () {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
        });
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
        });
      });
    });

    describe('with nested collections', function () {
      var newSpy;
      var NewComponent = React.createClass({
        mixins: [Backbone.React.Component.mixin],
        render: function () {
          if (newSpy) newSpy.call(this);
          return Component({collection: collection2});
        }
      });

      beforeEach(function () {
        component = NewComponent({collection: collection1});
        spy = newSpy = null;
      });

      it('gets the right collection inside the parent component', function () {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
        });
        mountedComponent = React.render(component, el);
        expect(newSpy).toHaveBeenCalled();
      });

      it('gets the right collection inside the child component', function () {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection2);
        });
        mountedComponent = React.render(component, el);
        expect(spy).toHaveBeenCalled();
      });

      it('has the right JSON data inside the parent props', function () {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(newSpy).toHaveBeenCalled();
      });

      it('has the right JSON data inside the child props', function () {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(spy).toHaveBeenCalled();
      });

      it('has the right JSON data inside the parent props after collection changes', function (done) {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(newSpy).toHaveBeenCalled();
        collection1.add(new Backbone.Model());
        _.defer(function () {
          expect(newSpy.calls.count()).toEqual(2);
          done();
        });
      });

      it('has the right JSON data inside the child props after collection changes', function (done) {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection2);
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(spy).toHaveBeenCalled();
        collection1.add(new Backbone.Model());
        _.defer(function () {
          expect(spy.calls.count()).toEqual(2);
          done();
        });
      });
    });
  });
});
<commit_msg>deprecate `@getOwner`<commit_after>describe('Parent Component', function () {
  'use strict';
  var Component = React.createClass({
    mixins: [Backbone.React.Component.mixin],
    render: function () {
      if (spy) spy.call(this);
      return React.DOM.div({}, this.props.hello);
    }
  });

  var component, el, mountedComponent, model1, model2, collection1, collection2, spy;

  beforeEach(function () {
    el = document.createElement('div');
    model1 = new Backbone.Model({hello: 'world!'});
    model2 = new Backbone.Model({goodbye: 'other world!'});
    collection1 = new Backbone.Collection([model1, model2]);
    collection2 = new Backbone.Collection([model2, model1]);
  });

  afterEach(function () {
    model1.stopListening();
    collection1.stopListening();
    React.unmountComponentAtNode(el);
  });

  it('gets the model(s)', function () {
    component = Component({model: model1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.getModel()).toEqual(model1);
  });

  it('gets the collection(s)', function () {
    component = Component({collection: collection1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.getCollection()).toEqual(collection1);
  });

  it('binds to a model', function (done) {
    component = Component({model: model1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.hello).toEqual('world!');
    model1.set('hello', 'again');
    // Defering because setting props is defered as well
    _.defer(function () {
      expect(mountedComponent.props.hello).toEqual('again');
      done();
    });
  });

  it('binds to multiple models', function () {
    component = Component({
      model: {
        firstModel: model1,
        secondModel: model2
      }
    });
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.firstModel.hello).toEqual('world!');
    expect(mountedComponent.props.secondModel.goodbye).toEqual('other world!');
  });

  it('binds to a collection', function (done) {
    component = Component({collection: collection1});
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.collection[0].hello).toEqual('world!');
    mountedComponent.getCollection().at(0).set('hello', 'again');
    // Defering because setting props is defered as well
    _.defer(function () {
      expect(mountedComponent.props.collection[0].hello).toEqual('again');
      done();
    });
  });

  it('binds to multiple collections', function (done) {
    component = Component({
      collection: {
        firstCollection: collection1,
        secondCollection: collection2
      }
    });
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.firstCollection[0].hello).toEqual('world!');
    expect(mountedComponent.props.secondCollection[0].goodbye).toEqual('other world!');
    mountedComponent.getCollection().firstCollection.at(0).set('hello', 'again');
    mountedComponent.getCollection().secondCollection.at(0).set('goodbye', 'other again');
    // Defering because setting props is defered as well
    _.defer(function () {
      expect(mountedComponent.props.firstCollection[0].hello).toEqual('again');
      expect(mountedComponent.props.secondCollection[0].goodbye).toEqual('other again');
      done();
    });
  });

  it('updates on collection reset', function (done) {
    component = Component({
        collection: collection1
    });
    mountedComponent = React.render(component, el);
    expect(mountedComponent.props.collection[0].hello).toEqual('world!');
    collection1.reset({hello: 'other world!'});
    _.defer(function () {
      expect(mountedComponent.props.collection[0].hello).toEqual('other world!');
      done();
    });
  });

  describe('Child Component', function () {

    describe('with nested models', function () {
      var newSpy;
      var NewComponent = React.createClass({
        mixins: [Backbone.React.Component.mixin],
        render: function () {
          if (newSpy) newSpy.call(this);
          return Component({model: this.getCollection().at(0)});
        }
      });

      beforeEach(function () {
        component = NewComponent({collection: collection1});
      });

      afterEach(function () {
        mountedComponent = React.render(component, el);
        if (newSpy) expect(newSpy).toHaveBeenCalled();
        if (spy) expect(spy).toHaveBeenCalled();
      });

      it('gets the model when inside the child component', function () {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getModel()).toEqual(model1);
        });
      });

      it('gets the same collection inside child and parent components', function () {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
        });
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
        });
      });
    });

    describe('with nested collections', function () {
      var newSpy;
      var NewComponent = React.createClass({
        mixins: [Backbone.React.Component.mixin],
        render: function () {
          if (newSpy) newSpy.call(this);
          return Component({collection: collection2});
        }
      });

      beforeEach(function () {
        component = NewComponent({collection: collection1});
        spy = newSpy = null;
      });

      it('gets the right collection inside the parent component', function () {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
        });
        mountedComponent = React.render(component, el);
        expect(newSpy).toHaveBeenCalled();
      });

      it('gets the right collection inside the child component', function () {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection2);
        });
        mountedComponent = React.render(component, el);
        expect(spy).toHaveBeenCalled();
      });

      it('has the right JSON data inside the parent props', function () {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(newSpy).toHaveBeenCalled();
      });

      it('has the right JSON data inside the child props', function () {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(spy).toHaveBeenCalled();
      });

      it('has the right JSON data inside the parent props after collection changes', function (done) {
        newSpy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection1);
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(newSpy).toHaveBeenCalled();
        collection1.add(new Backbone.Model());
        _.defer(function () {
          expect(newSpy.calls.count()).toEqual(2);
          done();
        });
      });

      it('has the right JSON data inside the child props after collection changes', function (done) {
        spy = jasmine.createSpy().and.callFake(function () {
          expect(this.getCollection()).toEqual(collection2);
          expect(this.props.collection instanceof Array).toBeTruthy();
          expect(this.props.collection).toEqual(this.getCollection().toJSON());
        });
        mountedComponent = React.render(component, el);
        expect(spy).toHaveBeenCalled();
        collection1.add(new Backbone.Model());
        _.defer(function () {
          expect(spy.calls.count()).toEqual(2);
          done();
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>var config = require('config'),
    path = require('path'),
    fs = require('fs'),
    LastfmAPI = require('lastfmapi'),
    shortid = require('shortid'),
    YTSearch = require('youtube-search'),
    ytdl = require('ytdl-core'),
    ffmpeg = require('ffmpeg');

var lastFM = new LastfmAPI({
    'api_key' : config.get('LastFM.key'),
    'secret' : config.get('LastFM.secret')
});

var YTSearchOpts = {
    maxResults: 10,
    regionCode: 'GB',
    type: 'video',
    safeSearch: 'none',
    key: config.get('YouTube.key')
}

var TrackManager = function() {
    this.tmpIDs = []; // Track songs that don't have mbid's supplied

    // RegEx matching MBID
    this.mbidRegex = /^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/;
}

/**
 * Lookup songs via last.fm api
 * @param {String} search query
 * @param {Function} callback [{String} JSON of search results]
 */
TrackManager.prototype.findSong = function(q, channel, callback) {
    if (!callback && typeof(callback) !== "function") {
        return;
    }

    if (!q) {
        callback([]);
        return;
    }

    var self = this;

    lastFM.track.search({
        'track' : q,
        'limit' : 10
    }, function (err, response) {
        if (err) {
            console.log('Error tm.fs.lastFM '.red + err);
            callback([]);
            return;
        }

        var results = [];

        if (response.trackmatches.track) {
            // Loop results
            var resultsLength = response.trackmatches.track.length;

            // No results found
            if (!resultsLength) {
                callback([]);
                return;
            }

            for (i = 0; i < resultsLength; i++) {
                if (!response.trackmatches.track[i]) continue;
                var item = response.trackmatches.track[i];

                var tmpItem = {};
                tmpItem.artist = item.artist;
                tmpItem.track = item.name;

                if (!tmpItem.artist || !tmpItem.track) {
                    continue; // skip track
                }

                // If item has mbid use that
                if (item.mbid) {
                    tmpItem.id = item.mbid;
                    tmpItem.added = Boolean(global.library.lookupTrackID(tmpItem.id, channel));
                } else {
                    // Generate an ID
                    tmpItem.id = shortid.generate();

                    // Store track in lookup
                    self.tmpIDs[tmpItem.id] = tmpItem;

                    tmpItem.added = Boolean(global.library.lookupTrack(item.track, item.artist, channel));
                }

                // TODO: correct channel added checks
                tmpItem.added = false;

                // Get image
                if (item.image) {
                    for (n = 0; n < item.image.length; n++) {
                        if (item.image[n].size == 'large') {
                            tmpItem.image = item.image[n]['#text'];
                        }
                    }
                }

                results.push(tmpItem);
            }
        }

        callback(results);
    });
}

/**
 * Add song to library
 * @param {String} mbid or search query
 * @param {Function} callback [{Boolean} track has been added]
 */
TrackManager.prototype.addSong = function(q, channel, callback) {
    if (!q) return;

    var self = this;

    // If it a mbid?
    var opts = {}
    if (this.mbidRegex.test(q)) {
        opts.mbid = q;
    } else if (this.tmpIDs[q]) {
        opts.track = this.tmpIDs[q].track
        opts.artist = this.tmpIDs[q].artist
    } else {
        console.log('Error tm.as.tmp: '.red + q + ' not in tmpIDs'.red);
        return;
    }

    // Load track details from last.fm
    lastFM.track.getInfo(opts, function (err, response) {
        if(err || !response) {
            console.log('Error tm.as.lookup:'.red, err, response);
            return;
        }

        // Check song is from an album
        // if (!response.album) return;

        var data = {};

        if (response.mbid) {
            data.id = response.mbid;
        } else {
            data.id = shortid.generate();
        }
        data.track = response.name;
        data.artist = response.artist.name;
        data.image = '';
        // data.outputPath = 'music/' + data.id + '.mp4';
        data.duration = response.duration;

        // Check if item is already in library
        if (global.library.lookupTrackID(q, channel)) {
            return;
        }

        // Get image
        if (response.album) {
            for (n = 0; n < response.album.image.length; n++) {
                if (response.album.image[n].size == 'large' && !data.image ||
                    response.album.image[n].size == 'extralarge') {
                    data.image = response.album.image[n]['#text'];
                }
            }
        } else {
            data.image = '';
        }

        self.findVideo(data, function(YTID) {
            data.YTID = YTID;
            global.library.addTrack(data);
            callback(data);
        });
    });
}

TrackManager.prototype.findVideo = function(data, callback) {
    var self = this;
    YTSearch(data.track + ' ' + data.artist, YTSearchOpts, function(err, results) {
        if(err || !results || !results.length) {
            console.log('Error tm.fv.yts:'.red, err);
            return;
        }

        // Loop through results until we find an appropriate file
        self.checkVideoResults(results, data, callback);
    });
}

TrackManager.prototype.checkVideoResults = function(results, data, callback, n) {
    if (typeof n === 'undefined') {
        n = 0;
    }

    // Run out of search results without finding suitable match
    if (n >= results.length || n > 10) {
        if (n < 99) {
            // Screw it lets try the first one
            data.duration = null;
            console.log('Removing duration');
            this.checkVideoResults(results, data, callback);
        } else {
            console.log('No source found'.red);
        }
        return;
    }

    var self = this,
        result = results[n];

    if (data.duration) {
        var duration = data.duration / 1000;
    }

    // Skip result if it doesn't exist
    if (!result || !result.id) {
        self.checkVideoResults(results, data, callback, ++n);
        return;
    }

    ytdl.getInfo('http://www.youtube.com/watch?v=' + result.id, {downloadURL: true}, function(err, info) {
        if (err) {
            console.log('Error tm.cvr.ytdl:'.red, err);
            self.checkVideoResults(results, data, callback, ++n);
            return;
        }

        // Check if duration is valid
        if (duration && (info.length_seconds < duration * 0.8 || info.length_seconds > duration * 1.5 || info.length_seconds > 600)) {
            console.log('Invalid duration: ' + info.length_seconds + ', target is: ' + duration);
            self.checkVideoResults(results, data, callback, ++n);
            return;
        } else if (info.length_seconds > 600) {
            self.checkVideoResults(results, data, callback, 999);
            return;
        }

        self.downloadVideo(result.id, data.id);
        callback(result.id); // Don't wait for download
    });
}

TrackManager.prototype.downloadVideo = function(YTID, trackID) {
    var outputPath = path.resolve(__dirname, '../data/music', trackID);

    try {
        // Download video from YT
        var stream = ytdl('http://www.youtube.com/watch?v=' + YTID);

        // Create file from video
        stream.pipe(fs.createWriteStream(outputPath + '.mp4'));

        // Once download has finished
        stream.on('end', function() {
                // Load in video
                var process = new ffmpeg(outputPath + '.mp4');
                process.then(function (video) {
                    // Export as mp3
                    video.fnExtractSoundToMP3(outputPath + '.mp3', function (error, file) {
                        if (!error) {
                            // Delete original mp4
                            fs.unlink(path.resolve(__dirname, outputPath + '.mp4'));
                        } else {
                            console.log(error);
                        }
                    });
                }, function (err) {
                    console.log('Error: ' + err);
                });
        });
    } catch (e) {
        console.log(e);
    }
}

module.exports = TrackManager;
<commit_msg>Removed check for track on addSong<commit_after>var config = require('config'),
    path = require('path'),
    fs = require('fs'),
    LastfmAPI = require('lastfmapi'),
    shortid = require('shortid'),
    YTSearch = require('youtube-search'),
    ytdl = require('ytdl-core'),
    ffmpeg = require('ffmpeg');

var lastFM = new LastfmAPI({
    'api_key' : config.get('LastFM.key'),
    'secret' : config.get('LastFM.secret')
});

var YTSearchOpts = {
    maxResults: 10,
    regionCode: 'GB',
    type: 'video',
    safeSearch: 'none',
    key: config.get('YouTube.key')
}

var TrackManager = function() {
    this.tmpIDs = []; // Track songs that don't have mbid's supplied

    // RegEx matching MBID
    this.mbidRegex = /^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/;
}

/**
 * Lookup songs via last.fm api
 * @param {String} search query
 * @param {Function} callback [{String} JSON of search results]
 */
TrackManager.prototype.findSong = function(q, channel, callback) {
    if (!callback && typeof(callback) !== "function") {
        return;
    }

    if (!q) {
        callback([]);
        return;
    }

    var self = this;

    lastFM.track.search({
        'track' : q,
        'limit' : 10
    }, function (err, response) {
        if (err) {
            console.log('Error tm.fs.lastFM '.red + err);
            callback([]);
            return;
        }

        var results = [];

        if (response.trackmatches.track) {
            // Loop results
            var resultsLength = response.trackmatches.track.length;

            // No results found
            if (!resultsLength) {
                callback([]);
                return;
            }

            for (i = 0; i < resultsLength; i++) {
                if (!response.trackmatches.track[i]) continue;
                var item = response.trackmatches.track[i];

                var tmpItem = {};
                tmpItem.artist = item.artist;
                tmpItem.track = item.name;

                if (!tmpItem.artist || !tmpItem.track) {
                    continue; // skip track
                }

                // If item has mbid use that
                if (item.mbid) {
                    tmpItem.id = item.mbid;
                    tmpItem.added = Boolean(global.library.lookupTrackID(tmpItem.id, channel));
                } else {
                    // Generate an ID
                    tmpItem.id = shortid.generate();

                    // Store track in lookup
                    self.tmpIDs[tmpItem.id] = tmpItem;

                    tmpItem.added = Boolean(global.library.lookupTrack(item.track, item.artist, channel));
                }

                // TODO: correct channel added checks
                tmpItem.added = false;

                // Get image
                if (item.image) {
                    for (n = 0; n < item.image.length; n++) {
                        if (item.image[n].size == 'large') {
                            tmpItem.image = item.image[n]['#text'];
                        }
                    }
                }

                results.push(tmpItem);
            }
        }

        callback(results);
    });
}

/**
 * Add song to library
 * @param {String} mbid or search query
 * @param {Function} callback [{Boolean} track has been added]
 */
TrackManager.prototype.addSong = function(q, channel, callback) {
    if (!q) return;

    var self = this;

    // If it a mbid?
    var opts = {}
    if (this.mbidRegex.test(q)) {
        opts.mbid = q;
    } else if (this.tmpIDs[q]) {
        opts.track = this.tmpIDs[q].track
        opts.artist = this.tmpIDs[q].artist
    } else {
        console.log('Error tm.as.tmp: '.red + q + ' not in tmpIDs'.red);
        return;
    }

    // Load track details from last.fm
    lastFM.track.getInfo(opts, function (err, response) {
        if(err || !response) {
            console.log('Error tm.as.lookup:'.red, err, response);
            return;
        }

        // Check song is from an album
        // if (!response.album) return;

        var data = {};

        if (response.mbid) {
            data.id = response.mbid;
        } else {
            data.id = shortid.generate();
        }
        data.track = response.name;
        data.artist = response.artist.name;
        data.image = '';
        // data.outputPath = 'music/' + data.id + '.mp4';
        data.duration = response.duration;

        // Check if item is already in library
        if (global.library.lookupTrackID(q, channel)) {
            // Just say it was done
            callback(data);
            return;
        }

        // Get image
        if (response.album) {
            for (n = 0; n < response.album.image.length; n++) {
                if (response.album.image[n].size == 'large' && !data.image ||
                    response.album.image[n].size == 'extralarge') {
                    data.image = response.album.image[n]['#text'];
                }
            }
        } else {
            data.image = '';
        }

        self.findVideo(data, function(YTID) {
            data.YTID = YTID;
            global.library.addTrack(data);
            callback(data);
        });
    });
}

TrackManager.prototype.findVideo = function(data, callback) {
    var self = this;
    YTSearch(data.track + ' ' + data.artist, YTSearchOpts, function(err, results) {
        if(err || !results || !results.length) {
            console.log('Error tm.fv.yts:'.red, err);
            return;
        }

        // Loop through results until we find an appropriate file
        self.checkVideoResults(results, data, callback);
    });
}

TrackManager.prototype.checkVideoResults = function(results, data, callback, n) {
    if (typeof n === 'undefined') {
        n = 0;
    }

    // Run out of search results without finding suitable match
    if (n >= results.length || n > 10) {
        if (n < 99) {
            // Screw it lets try the first one
            data.duration = null;
            console.log('Removing duration');
            this.checkVideoResults(results, data, callback);
        } else {
            console.log('No source found'.red);
        }
        return;
    }

    var self = this,
        result = results[n];

    if (data.duration) {
        var duration = data.duration / 1000;
    }

    // Skip result if it doesn't exist
    if (!result || !result.id) {
        self.checkVideoResults(results, data, callback, ++n);
        return;
    }

    ytdl.getInfo('http://www.youtube.com/watch?v=' + result.id, {downloadURL: true}, function(err, info) {
        if (err) {
            console.log('Error tm.cvr.ytdl:'.red, err);
            self.checkVideoResults(results, data, callback, ++n);
            return;
        }

        // Check if duration is valid
        if (duration && (info.length_seconds < duration * 0.8 || info.length_seconds > duration * 1.5 || info.length_seconds > 600)) {
            console.log('Invalid duration: ' + info.length_seconds + ', target is: ' + duration);
            self.checkVideoResults(results, data, callback, ++n);
            return;
        } else if (info.length_seconds > 600) {
            self.checkVideoResults(results, data, callback, 999);
            return;
        }

        self.downloadVideo(result.id, data.id);
        callback(result.id); // Don't wait for download
    });
}

TrackManager.prototype.downloadVideo = function(YTID, trackID) {
    var outputPath = path.resolve(__dirname, '../data/music', trackID);

    try {
        // Download video from YT
        var stream = ytdl('http://www.youtube.com/watch?v=' + YTID);

        // Create file from video
        stream.pipe(fs.createWriteStream(outputPath + '.mp4'));

        // Once download has finished
        stream.on('end', function() {
                // Load in video
                var process = new ffmpeg(outputPath + '.mp4');
                process.then(function (video) {
                    // Export as mp3
                    video.fnExtractSoundToMP3(outputPath + '.mp3', function (error, file) {
                        if (!error) {
                            // Delete original mp4
                            fs.unlink(path.resolve(__dirname, outputPath + '.mp4'));
                        } else {
                            console.log(error);
                        }
                    });
                }, function (err) {
                    console.log('Error: ' + err);
                });
        });
    } catch (e) {
        console.log(e);
    }
}

module.exports = TrackManager;
<|endoftext|>