<commit_before>app.directive('expenseItemForm', ['moment', '$filter', '$timeout', '$translate', '$uibModal', 'USER', 'spinnerService', 'globalMessagesService', 'expenseItemsRestService', 'costCategorySortService',

function(moment, $filter, $timeout, $translate, $uibModal, USER, spinnerService, globalMessagesService, expenseItemsRestService, costCategorySortService) {
	"use strict";

	return {
		restrict: 'E',
		replace: true,
		templateUrl: 'expense-items/expense-item-form.directive.tpl.html',
		scope: {
			expenseItem: '=',
			validate: '=',
			form: '=',
			editable: '='
		},
		link: function($scope) {

			$scope.form = $scope.form || {};
			$scope.USER = USER;
			$scope.projectFieldRequired = ($scope.USER.hasRole('PROF') || $scope.USER.hasRole('DEPARTMENT_MANAGER') || $scope.USER.hasRole('FINANCE_ADMIN')) ? true : false;

			// pass the hasAttachment property from the child directive to the parent
			$scope.attachment = {};
			$scope.$watch('attachment.hasAttachment', function() {
				$scope.hasAttachment = $scope.attachment.hasAttachment;
			});

			$scope.calculatedAmount = $filter('number')(0, 2);

			$timeout(function() {
				spinnerService.show('spinnerExpenseItemForm');

				expenseItemsRestService.getCostCategories().then(function(response) {
					$scope.costCategories = costCategorySortService.sort(response.data);

					expenseItemsRestService.getSupportedCurrencies().then(function(response) {
						$scope.currencies = response.data;

						if(!$scope.editable) {
							$scope.staticCalculatedAmount = $scope.expenseItem.calculatedAmount;
						}
						else {
							$scope.currentCostCategoryIsInactive = true;
							for(var i = 0; i < $scope.costCategories.length; i++) {
								if($scope.costCategories[i].uid === $scope.expenseItem.costCategory.uid) {
									$scope.currentCostCategoryIsInactive = false;
									break;
								}
							}
							if($scope.currentCostCategoryIsInactive) {
								$scope.costCategories.push($scope.expenseItem.costCategory);
							}
						}

						$scope.form.date = $filter('date')($scope.expenseItem.date, 'dd.MM.yyyy');
						$scope.form.costCategoryUid = $scope.expenseItem.costCategory.uid;
						$scope.form.originalAmount = $scope.expenseItem.originalAmount;
						$scope.form.project = $scope.expenseItem.project;
						$scope.form.explanation = $scope.expenseItem.explanation;

						$timeout(function() {
							// make sure the currencies are completely loaded before setting the default
							$scope.form.currency = $scope.expenseItem.currency;
						});

						if($scope.editable) {
							$scope.calculateAmount();
						}

						spinnerService.hide('spinnerExpenseItemForm');

					}, function() {
						spinnerService.hide('spinnerExpenseItemForm');
					});
				}, function() {
					spinnerService.hide('spinnerExpenseItemForm');
				});
			}, function() {
				spinnerService.hide('spinnerExpenseItemForm');
			});

			if($scope.editable) {

				var invalidDate = "";
				var invalidAmount = "";

				$translate(['reimbursement.expenseItem.invalidDate', 'reimbursement.expenseItem.invalidAmount']).then(function(translations) {
					invalidDate = translations['reimbursement.expenseItem.invalidDate'];
					invalidAmount = translations['reimbursement.expenseItem.invalidAmount'];
				}, function() {
					globalMessagesService.showGeneralError();
				});

				var exchangeRates = null;
				var exchangeRateDate = null;
				$scope.calculateAmount = function() {

					function calculate() {
						var exchangeRate = exchangeRates.rates[$scope.form.currency];
						if(isNaN(window.parseFloat($scope.form.originalAmount)) || window.parseFloat($scope.form.originalAmount) < 0) {
							$scope.calculatedAmount = invalidAmount;
						}
						else {
							if(typeof exchangeRate !== "undefined") {
								$scope.calculatedAmount = $filter('number')(window.parseFloat($scope.form.originalAmount) / exchangeRate, 2);
							}
							else {
								$scope.calculatedAmount = $filter('number')(window.parseFloat($scope.form.originalAmount), 2);
							}
						}
					}

					if(($scope.form.date !== "" && $scope.form.date !== undefined && $scope.$parent.formExpenseItem.originalAmount.$valid) || (!$scope.$parent.formExpenseItem.originalAmount.$valid && !$scope.$parent.formExpenseItem.originalAmount.$touched)) {
						// only make back-end calls if necessary
						if(exchangeRateDate !== $filter('getISODate')($scope.form.date)) {
							var date = $filter('getISODate')($scope.form.date);

							expenseItemsRestService.getExchangeRates(date).then(function(response) {
								exchangeRates = response.data;
								exchangeRateDate = $scope.form.date;
								calculate();
							}, function(response) {
								response.errorHandled = true;
								$scope.calculatedAmount = invalidDate;
							});
						}
						else {
							calculate();
						}
					}
					else {
						$scope.calculatedAmount = $filter('number')(0, 2);
					}
				};

				$scope.validate = function() {
					if(typeof $scope.form.date === "undefined" || $scope.form.date === null || !moment($scope.form.date, 'dd.MM.yyyy').isValid()) {
						return false;
					}
					if(typeof $scope.form.costCategoryUid === "undefined" || $scope.form.costCategoryUid === null || $scope.form.costCategoryUid === "") {
						return false;
					}
					if(typeof $scope.form.originalAmount === "undefined" || $scope.form.originalAmount === null || !jQuery.isNumeric($scope.form.originalAmount) || parseFloat($scope.form.originalAmount) <= 0) {
						return false;
					}
					if(typeof $scope.form.currency === "undefined" || $scope.form.currency === null || $scope.form.currency === "") {
						return false;
					}
					if(typeof $scope.attachment.hasAttachment === "undefined" || $scope.attachment.hasAttachment === null || $scope.attachment.hasAttachment === false) {
						return false;
					}
					return true;
				};

				$timeout(function() {
					jQuery('.datepicker').datetimepicker({
						format: 'DD.MM.YYYY',
						viewMode: 'months',
						allowInputToggle: true,
						maxDate: moment(),
						calendarWeeks: true
					}).on('dp.hide', function() {
						$scope.form.date = jQuery('.datepicker').find('input').first().val();
						$scope.calculateAmount();
					});
				});

				$scope.openCostCategoryModal = function() {
					var modalInstance = $uibModal.open({
						templateUrl: 'expense-items/show-cost-category.modal.tpl.html',
						controller: 'ShowCostCategoryController',
						resolve: {
							costCategories: function() {
								return $scope.costCategories;
							},
							costCategoryUid: function() {
								return $scope.form.costCategoryUid;
							},
							editable: function() {
								return $scope.editable;
							}
						}
					});

					modalInstance.result.then(function(response) {
						$scope.form.costCategoryUid = response;
					});
				};
			}

			if(!$scope.editable) {
				$scope.openViewOnlyCostCategoryModal = function() {
					$uibModal.open({
						templateUrl: 'expense-items/show-cost-category.modal.tpl.html',
						controller: 'ShowCostCategoryController',
						resolve: {
							costCategories: function() {
								return $scope.costCategories;
							},
							costCategoryUid: function() {
								return $scope.form.costCategoryUid;
							},
							editable: function() {
								return $scope.editable;
							}
						}
					});
				};
			}
		}
	};

}]);
<commit_msg>Set start date to 1.1.2000<commit_after>app.directive('expenseItemForm', ['moment', '$filter', '$timeout', '$translate', '$uibModal', 'USER', 'spinnerService', 'globalMessagesService', 'expenseItemsRestService', 'costCategorySortService',

function(moment, $filter, $timeout, $translate, $uibModal, USER, spinnerService, globalMessagesService, expenseItemsRestService, costCategorySortService) {
	"use strict";

	return {
		restrict: 'E',
		replace: true,
		templateUrl: 'expense-items/expense-item-form.directive.tpl.html',
		scope: {
			expenseItem: '=',
			validate: '=',
			form: '=',
			editable: '='
		},
		link: function($scope) {

			$scope.form = $scope.form || {};
			$scope.USER = USER;
			$scope.projectFieldRequired = ($scope.USER.hasRole('PROF') || $scope.USER.hasRole('DEPARTMENT_MANAGER') || $scope.USER.hasRole('FINANCE_ADMIN')) ? true : false;

			// pass the hasAttachment property from the child directive to the parent
			$scope.attachment = {};
			$scope.$watch('attachment.hasAttachment', function() {
				$scope.hasAttachment = $scope.attachment.hasAttachment;
			});

			$scope.calculatedAmount = $filter('number')(0, 2);

			$timeout(function() {
				spinnerService.show('spinnerExpenseItemForm');

				expenseItemsRestService.getCostCategories().then(function(response) {
					$scope.costCategories = costCategorySortService.sort(response.data);

					expenseItemsRestService.getSupportedCurrencies().then(function(response) {
						$scope.currencies = response.data;

						if(!$scope.editable) {
							$scope.staticCalculatedAmount = $scope.expenseItem.calculatedAmount;
						}
						else {
							$scope.currentCostCategoryIsInactive = true;
							for(var i = 0; i < $scope.costCategories.length; i++) {
								if($scope.costCategories[i].uid === $scope.expenseItem.costCategory.uid) {
									$scope.currentCostCategoryIsInactive = false;
									break;
								}
							}
							if($scope.currentCostCategoryIsInactive) {
								$scope.costCategories.push($scope.expenseItem.costCategory);
							}
						}

						$scope.form.date = $filter('date')($scope.expenseItem.date, 'dd.MM.yyyy');
						$scope.form.costCategoryUid = $scope.expenseItem.costCategory.uid;
						$scope.form.originalAmount = $scope.expenseItem.originalAmount;
						$scope.form.project = $scope.expenseItem.project;
						$scope.form.explanation = $scope.expenseItem.explanation;

						$timeout(function() {
							// make sure the currencies are completely loaded before setting the default
							$scope.form.currency = $scope.expenseItem.currency;
						});

						if($scope.editable) {
							$scope.calculateAmount();
						}

						spinnerService.hide('spinnerExpenseItemForm');

					}, function() {
						spinnerService.hide('spinnerExpenseItemForm');
					});
				}, function() {
					spinnerService.hide('spinnerExpenseItemForm');
				});
			}, function() {
				spinnerService.hide('spinnerExpenseItemForm');
			});

			if($scope.editable) {

				var invalidDate = "";
				var invalidAmount = "";

				$translate(['reimbursement.expenseItem.invalidDate', 'reimbursement.expenseItem.invalidAmount']).then(function(translations) {
					invalidDate = translations['reimbursement.expenseItem.invalidDate'];
					invalidAmount = translations['reimbursement.expenseItem.invalidAmount'];
				}, function() {
					globalMessagesService.showGeneralError();
				});

				var exchangeRates = null;
				var exchangeRateDate = null;
				$scope.calculateAmount = function() {

					function calculate() {
						var exchangeRate = exchangeRates.rates[$scope.form.currency];
						if(isNaN(window.parseFloat($scope.form.originalAmount)) || window.parseFloat($scope.form.originalAmount) < 0) {
							$scope.calculatedAmount = invalidAmount;
						}
						else {
							if(typeof exchangeRate !== "undefined") {
								$scope.calculatedAmount = $filter('number')(window.parseFloat($scope.form.originalAmount) / exchangeRate, 2);
							}
							else {
								$scope.calculatedAmount = $filter('number')(window.parseFloat($scope.form.originalAmount), 2);
							}
						}
					}

					if(($scope.form.date !== "" && $scope.form.date !== undefined && $scope.$parent.formExpenseItem.originalAmount.$valid) || (!$scope.$parent.formExpenseItem.originalAmount.$valid && !$scope.$parent.formExpenseItem.originalAmount.$touched)) {
						// only make back-end calls if necessary
						if(exchangeRateDate !== $filter('getISODate')($scope.form.date)) {
							var date = $filter('getISODate')($scope.form.date);

							expenseItemsRestService.getExchangeRates(date).then(function(response) {
								exchangeRates = response.data;
								exchangeRateDate = $scope.form.date;
								calculate();
							}, function(response) {
								response.errorHandled = true;
								$scope.calculatedAmount = invalidDate;
							});
						}
						else {
							calculate();
						}
					}
					else {
						$scope.calculatedAmount = $filter('number')(0, 2);
					}
				};

				$scope.validate = function() {
					if(typeof $scope.form.date === "undefined" || $scope.form.date === null || !moment($scope.form.date, 'dd.MM.yyyy').isValid()) {
						return false;
					}
					if(typeof $scope.form.costCategoryUid === "undefined" || $scope.form.costCategoryUid === null || $scope.form.costCategoryUid === "") {
						return false;
					}
					if(typeof $scope.form.originalAmount === "undefined" || $scope.form.originalAmount === null || !jQuery.isNumeric($scope.form.originalAmount) || parseFloat($scope.form.originalAmount) <= 0) {
						return false;
					}
					if(typeof $scope.form.currency === "undefined" || $scope.form.currency === null || $scope.form.currency === "") {
						return false;
					}
					if(typeof $scope.attachment.hasAttachment === "undefined" || $scope.attachment.hasAttachment === null || $scope.attachment.hasAttachment === false) {
						return false;
					}
					return true;
				};

				$timeout(function() {
					jQuery('.datepicker').datetimepicker({
						format: 'DD.MM.YYYY',
						viewMode: 'months',
						allowInputToggle: true,
						maxDate: moment(),
						minDate: moment('01/01/2000'),
						calendarWeeks: true
					}).on('dp.hide', function() {
						$scope.form.date = jQuery('.datepicker').find('input').first().val();
						$scope.calculateAmount();
					});
				});

				$scope.openCostCategoryModal = function() {
					var modalInstance = $uibModal.open({
						templateUrl: 'expense-items/show-cost-category.modal.tpl.html',
						controller: 'ShowCostCategoryController',
						resolve: {
							costCategories: function() {
								return $scope.costCategories;
							},
							costCategoryUid: function() {
								return $scope.form.costCategoryUid;
							},
							editable: function() {
								return $scope.editable;
							}
						}
					});

					modalInstance.result.then(function(response) {
						$scope.form.costCategoryUid = response;
					});
				};
			}

			if(!$scope.editable) {
				$scope.openViewOnlyCostCategoryModal = function() {
					$uibModal.open({
						templateUrl: 'expense-items/show-cost-category.modal.tpl.html',
						controller: 'ShowCostCategoryController',
						resolve: {
							costCategories: function() {
								return $scope.costCategories;
							},
							costCategoryUid: function() {
								return $scope.form.costCategoryUid;
							},
							editable: function() {
								return $scope.editable;
							}
						}
					});
				};
			}
		}
	};

}]);
<|endoftext|>


<commit_before>/**
 * @class AbstractBaseDao
 *
 * @author: darryl.west@raincitysoftware.com
 * @created: 9/2/14
 */
const should = require('chai').should(),
    dash = require('lodash' ),
    MockLogger = require('simple-node-logger' ).mocks.MockLogger,
    Dataset = require('../fixtures/TestDataset'),
    MockClient = require('mock-redis-client'),
    AbstractBaseModel = require('../../lib/models/AbstractBaseModel' ),
    AbstractBaseDao = require('../../lib/dao/AbstractBaseDao');

describe('AbstractBaseDao', function() {
    'use strict';

    const createOptions = function() {
        const opts = {};

        opts.log = MockLogger.createLogger('AbstractBaseDao');
        opts.domain = 'MyDomainName';

        opts.createType = function(model) {
            return new AbstractBaseModel( model );
        };

        return opts;
    };

    const MockDao = function(options) {
        AbstractBaseDao.extend( this, options );
    };

    describe('#instance', function() {
        var dao = new AbstractBaseDao( createOptions() ),
            methods = [
                'createModelId',
                'createDomainKey',
                'query',
                'findById',
                'insert',
                'update',
                'prepareUpdate',
                'parseModel'
            ];

        it('should create an instance of AbstractBaseDao', function() {
            should.exist( dao );
            dao.should.be.instanceof( AbstractBaseDao );

            AbstractBaseDao.extend.should.be.a( 'function' );
        });

        it('should have all known methods by size and type', function() {
            dash.functionsIn( dao ).length.should.equal( methods.length );
            methods.forEach(function(method) {
                dao[ method ].should.be.a( 'function' );
            });
        });

        it('should extend an object to inherit all public methods', function() {
            var dao = new MockDao( createOptions() );

            should.exist( dao );
            dash.functionsIn( dao ).length.should.equal( methods.length );
        });
    });

    describe('createDomainKey', function() {
        var opts = createOptions(),
            dao = new AbstractBaseDao( opts );

        it('should create a domain key suitable for simple key/value stores', function() {
            var id = '12345',
                key = dao.createDomainKey( id );

            key.should.equal('MyDomainName:12345');

            // if the key is passed in, its simply returned
            dao.createDomainKey( key ).should.equal( key );
        });
    });

    describe('createModelId', function() {
        var dao = new AbstractBaseDao( createOptions() );

        it('should create a standard model id guid', function() {
            var id = dao.createModelId();

            // console.log( id );
            should.exist( id );
            dash.size( id ).should.equal( 32 );
        });
    });

    describe('query', function() {
        var dao = new AbstractBaseDao( createOptions()),
            client = new MockClient(),
            list = new Dataset().createModelList(25);

        beforeEach(function(done) {
            var mlist = [];

            list.forEach(function(model) {
                var key = dao.createDomainKey( model.id );

                mlist.push( key );
                model.name = 'flarb';

                mlist.push( JSON.stringify( model ));
            });

            client.mset( mlist, done );
        });

        it('should return a list of model objects', function(done) {
            var callback = function(err, models) {
                should.not.exist( err );
                should.exist( models );

                list.length.should.equal( list.length );

                done();
            };

            dao.query( client, callback );
        });
    });

    describe('findById', function() {
        var dao = new AbstractBaseDao( createOptions()),
            client = new MockClient(),
            list = new Dataset().createModelList(5);

        beforeEach(function(done) {
            var mlist = [];

            list.forEach(function(model) {
                var key = dao.createDomainKey( model.id );

                mlist.push( key );
                mlist.push( JSON.stringify( model ));
            });

            client.mset( mlist, done );
        });

        it('should find and return a known model by id', function(done) {
            var ref = list[0],
                callback;

            callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                model.dateCreated.getTime().should.equal( ref.dateCreated.getTime() );
                model.lastUpdated.getTime().should.equal( ref.lastUpdated.getTime() );
                model.version.should.equal( ref.version );

                model.should.be.instanceof( AbstractBaseModel );

                done();
            };

            // console.log( model );
            dao.findById( client, ref.id, callback );
        });

        it('should not find an unknown id', function(done) {
            var id = 'bad-id',
                callback;

            callback = function(err, model) {
                should.not.exist( err );
                should.not.exist( model );

                done();
            };

            // console.log( model );
            dao.findById( client, id, callback );
        });
    });

    describe('prepareUpdate', function() {
        const dao = new AbstractBaseDao( createOptions());

        it('should prepare a model for update', function() {
            const ref = new Dataset().createModel();
            ref.dateCreated = new Date("2015-01-01");
            ref.lastUpdated = new Date( Date.now() - 500 );
            ref.version = 25;

            const model = dao.prepareUpdate( ref );

            model.id.should.equal( ref.id );
            model.version.should.equal( ref.version );
            model.dateCreated.should.equal( ref.dateCreated );
            model.lastUpdated.getTime().should.be.above( ref.lastUpdated.getTime() );
        });

        it('should prepare a new model for insert');
    });

    describe('insert', function() {
        const dao = new AbstractBaseDao( createOptions()),
            client = new MockClient();

        it('should insert a new model with base properties', function(done) {
            const ref = new Dataset().createModel();

            const callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                model.id.should.equal( ref.id );
                model.dateCreated.getTime().should.equal( ref.dateCreated.getTime() );
                // model.lastUpdated.getTime().should.equal( ref.lastUpdated.getTime() );
                model.version.should.equal( ref.version );

                done();
            };

            dao.insert( client, ref, callback );
        });

        it('should insert a new model with base properties', function(done) {
            const ref = new AbstractBaseModel();

            const callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                should.exist( model.id );
                should.exist( model.dateCreated );
                should.exist( model.lastUpdated );
                model.version.should.equal( 0 );

                done();
            };

            dao.insert( client, ref, callback );
        });
    });

    describe('update', function() {
        var dao = new AbstractBaseDao( createOptions()),
            client = new MockClient(),
            list = new Dataset().createModelList(5);

        beforeEach(function(done) {
            var mlist = [];

            list.forEach(function(model) {
                var key = dao.createDomainKey( model.id );

                mlist.push( key );
                model.name = 'flarb';

                mlist.push( JSON.stringify( model ));
            });

            client.mset( mlist, done );
        });

        it('should update an existing model', function(done) {
            var ref = list[0],
                callback;

            ref.name = 'newberg';

            callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                model.id.should.equal( ref.id );
                model.dateCreated.getTime().should.equal( ref.dateCreated.getTime() );
                model.lastUpdated.getTime().should.not.equal( ref.lastUpdated.getTime() );
                model.version.should.equal( ref.version + 1 );
                model.name.should.equal( ref.name );

                done();
            };

            dao.update( client, ref, callback );
        });
    });
});
<commit_msg>added prepare update tests for insert<commit_after>/**
 * @class AbstractBaseDao
 *
 * @author: darryl.west@raincitysoftware.com
 * @created: 9/2/14
 */
const should = require('chai').should(),
    dash = require('lodash' ),
    MockLogger = require('simple-node-logger' ).mocks.MockLogger,
    Dataset = require('../fixtures/TestDataset'),
    MockClient = require('mock-redis-client'),
    AbstractBaseModel = require('../../lib/models/AbstractBaseModel' ),
    AbstractBaseDao = require('../../lib/dao/AbstractBaseDao');

describe('AbstractBaseDao', function() {
    'use strict';

    const createOptions = function() {
        const opts = {};

        opts.log = MockLogger.createLogger('AbstractBaseDao');
        opts.domain = 'MyDomainName';

        opts.createType = function(model) {
            return new AbstractBaseModel( model );
        };

        return opts;
    };

    const MockDao = function(options) {
        AbstractBaseDao.extend( this, options );
    };

    describe('#instance', function() {
        var dao = new AbstractBaseDao( createOptions() ),
            methods = [
                'createModelId',
                'createDomainKey',
                'query',
                'findById',
                'insert',
                'update',
                'prepareUpdate',
                'parseModel'
            ];

        it('should create an instance of AbstractBaseDao', function() {
            should.exist( dao );
            dao.should.be.instanceof( AbstractBaseDao );

            AbstractBaseDao.extend.should.be.a( 'function' );
        });

        it('should have all known methods by size and type', function() {
            dash.functionsIn( dao ).length.should.equal( methods.length );
            methods.forEach(function(method) {
                dao[ method ].should.be.a( 'function' );
            });
        });

        it('should extend an object to inherit all public methods', function() {
            var dao = new MockDao( createOptions() );

            should.exist( dao );
            dash.functionsIn( dao ).length.should.equal( methods.length );
        });
    });

    describe('createDomainKey', function() {
        var opts = createOptions(),
            dao = new AbstractBaseDao( opts );

        it('should create a domain key suitable for simple key/value stores', function() {
            var id = '12345',
                key = dao.createDomainKey( id );

            key.should.equal('MyDomainName:12345');

            // if the key is passed in, its simply returned
            dao.createDomainKey( key ).should.equal( key );
        });
    });

    describe('createModelId', function() {
        var dao = new AbstractBaseDao( createOptions() );

        it('should create a standard model id guid', function() {
            var id = dao.createModelId();

            // console.log( id );
            should.exist( id );
            dash.size( id ).should.equal( 32 );
        });
    });

    describe('query', function() {
        var dao = new AbstractBaseDao( createOptions()),
            client = new MockClient(),
            list = new Dataset().createModelList(25);

        beforeEach(function(done) {
            var mlist = [];

            list.forEach(function(model) {
                var key = dao.createDomainKey( model.id );

                mlist.push( key );
                model.name = 'flarb';

                mlist.push( JSON.stringify( model ));
            });

            client.mset( mlist, done );
        });

        it('should return a list of model objects', function(done) {
            var callback = function(err, models) {
                should.not.exist( err );
                should.exist( models );

                list.length.should.equal( list.length );

                done();
            };

            dao.query( client, callback );
        });
    });

    describe('findById', function() {
        var dao = new AbstractBaseDao( createOptions()),
            client = new MockClient(),
            list = new Dataset().createModelList(5);

        beforeEach(function(done) {
            var mlist = [];

            list.forEach(function(model) {
                var key = dao.createDomainKey( model.id );

                mlist.push( key );
                mlist.push( JSON.stringify( model ));
            });

            client.mset( mlist, done );
        });

        it('should find and return a known model by id', function(done) {
            var ref = list[0],
                callback;

            callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                model.dateCreated.getTime().should.equal( ref.dateCreated.getTime() );
                model.lastUpdated.getTime().should.equal( ref.lastUpdated.getTime() );
                model.version.should.equal( ref.version );

                model.should.be.instanceof( AbstractBaseModel );

                done();
            };

            // console.log( model );
            dao.findById( client, ref.id, callback );
        });

        it('should not find an unknown id', function(done) {
            var id = 'bad-id',
                callback;

            callback = function(err, model) {
                should.not.exist( err );
                should.not.exist( model );

                done();
            };

            // console.log( model );
            dao.findById( client, id, callback );
        });
    });

    describe('prepareUpdate', function() {
        const dao = new AbstractBaseDao( createOptions());

        it('should prepare a model for update', function() {
            const ref = new Dataset().createModel();
            ref.dateCreated = new Date("2015-01-01");
            ref.lastUpdated = new Date( Date.now() - 500 );
            ref.version = 25;

            const model = dao.prepareUpdate( ref );

            model.id.should.equal( ref.id );
            model.version.should.equal( ref.version );
            model.dateCreated.should.equal( ref.dateCreated );
            model.lastUpdated.getTime().should.be.above( ref.lastUpdated.getTime() );
        });

        it('should prepare a new model for insert', function() {
            const now = Date.now() - 1;
            const ref = new AbstractBaseModel();

            const model = dao.prepareUpdate( ref );

            should.exist( model.id );
            model.id.length.should.equal( 32 );
            model.version.should.equal( 0 );
            model.dateCreated.getTime().should.be.above( now );
            model.lastUpdated.getTime().should.be.above( now );
        });
    });

    describe('insert', function() {
        const dao = new AbstractBaseDao( createOptions()),
            client = new MockClient();

        it('should insert a new model with base properties', function(done) {
            const ref = new Dataset().createModel();

            const callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                model.id.should.equal( ref.id );
                model.dateCreated.getTime().should.equal( ref.dateCreated.getTime() );
                // model.lastUpdated.getTime().should.equal( ref.lastUpdated.getTime() );
                model.version.should.equal( ref.version );

                done();
            };

            dao.insert( client, ref, callback );
        });

        it('should insert a new model with base properties', function(done) {
            const ref = new AbstractBaseModel();

            const callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                should.exist( model.id );
                should.exist( model.dateCreated );
                should.exist( model.lastUpdated );
                model.version.should.equal( 0 );

                done();
            };

            dao.insert( client, ref, callback );
        });
    });

    describe('update', function() {
        var dao = new AbstractBaseDao( createOptions()),
            client = new MockClient(),
            list = new Dataset().createModelList(5);

        beforeEach(function(done) {
            var mlist = [];

            list.forEach(function(model) {
                var key = dao.createDomainKey( model.id );

                mlist.push( key );
                model.name = 'flarb';

                mlist.push( JSON.stringify( model ));
            });

            client.mset( mlist, done );
        });

        it('should update an existing model', function(done) {
            var ref = list[0],
                callback;

            ref.name = 'newberg';

            callback = function(err, model) {
                should.not.exist( err );
                should.exist( model );

                model.id.should.equal( ref.id );
                model.dateCreated.getTime().should.equal( ref.dateCreated.getTime() );
                model.lastUpdated.getTime().should.not.equal( ref.lastUpdated.getTime() );
                model.version.should.equal( ref.version + 1 );
                model.name.should.equal( ref.name );

                done();
            };

            dao.update( client, ref, callback );
        });
    });
});
<|endoftext|>


<commit_before>// # Socket Plugin Module

// Module dependencies

var express = require('express')
  , socketplugin = require('../../core/server/plugins/GhostPlugin')
  , store = new express.session.MemoryStore()
  , cookie, session, io, GhostPlugin;

// ### Show Room
// 'myroom' handler.
function showRoom(req, res, next) {
    if (req.session.user) {
        res.render('teacherRoom');
    }else{
		res.render('studentRoom');
	}

    next();
}

GhostPlugin = function (ghost) {
    this.app = ghost;
};

GhostPlugin.prototype.activate = function (ghost) {

	var server = ghost.server;
	var httpserver = require('http').createServer(server);
	//cookie = express.cookieParser(ghost.dbHash);
	//session = express.cookieSession({store: store});

	//server.use(cookie);
	//server.use(session);

    //server.get('/myroom/roomId/', showRoom);
	io = require('socket.io').listen(httpserver);
	io.set('authorization', function(data, accept) {
		cookie(data, {}, function(err) {
			if (!err) {
				var sessionID = data.signedCookies[KEY];
				store.get(sessionID, function(err, session) {
					if (err || !session) {
						accept(null, false);
					} else {
						data.session = session;
						accept(null, true);
					}
				});
			} else {
				accept(null, false);
			}
		});
	});

	io.sockets.on('connection', function (client) {
		var session = client.handshake.session
		, nome = session.nome;
		client.on('toServer', function (msg) {
			msg = ""+nome+": "+msg+"
";
			client.emit('toClient', msg);
			client.broadcast.emit('toClient', msg);
		});
	});
	return;
};

/**
 * A method that will be called on uninstallation.
 * Can optionally return a promise if async.
 * @parameter {Ghost} The current Ghost app instance
 */
GhostPlugin.prototype.uninstall = function (ghost) {
    return;
};

/**
 * A method that will be called when the plugin is enabled.
 * Can optionally return a promise if async.
 * @parameter {Ghost} The current Ghost app instance
 */
GhostPlugin.prototype.install = function (ghost) {

    return;
};

/**
 * A method that will be called when the plugin is disabled.
 * Can optionally return a promise if async.
 * @parameter {Ghost} The current Ghost app instance
 */
GhostPlugin.prototype.deactivate = function (ghost) {
    return;
};

module.exports = GhostPlugin;

<commit_msg>Add hundler<commit_after>// # Socket Plugin Module

// Module dependencies

var express = require('express')
  , socketplugin = require('../../core/server/plugins/GhostPlugin')
  , store = new express.session.MemoryStore()
  , cookie, session, io, GhostPlugin;

// ### Show Room
// 'myroom' handler.
function showRoom(req, res, next) {
    if (req.session.user) {
        res.render('teacher');
    }else{
		res.render('student');
	}

    next();
}

GhostPlugin = function (ghost) {
    this.app = ghost;
};

GhostPlugin.prototype.activate = function (ghost) {

	var server = ghost.server;
	var httpserver = require('http').createServer(server);
	//cookie = express.cookieParser(ghost.dbHash);
	//session = express.cookieSession({store: store});

	//server.use(cookie);
	//server.use(session);

    server.get('/myroom/roomId/', showRoom);
	io = require('socket.io').listen(httpserver);
	io.set('authorization', function(data, accept) {
		cookie(data, {}, function(err) {
			if (!err) {
				var sessionID = data.signedCookies[KEY];
				store.get(sessionID, function(err, session) {
					if (err || !session) {
						accept(null, false);
					} else {
						data.session = session;
						accept(null, true);
					}
				});
			} else {
				accept(null, false);
			}
		});
	});

	io.sockets.on('connection', function (client) {
		var session = client.handshake.session
		, nome = session.nome;
		client.on('toServer', function (msg) {
			msg = ""+nome+": "+msg+"
";
			client.emit('toClient', msg);
			client.broadcast.emit('toClient', msg);
		});
	});
	return;
};

/**
 * A method that will be called on uninstallation.
 * Can optionally return a promise if async.
 * @parameter {Ghost} The current Ghost app instance
 */
GhostPlugin.prototype.uninstall = function (ghost) {
    return;
};

/**
 * A method that will be called when the plugin is enabled.
 * Can optionally return a promise if async.
 * @parameter {Ghost} The current Ghost app instance
 */
GhostPlugin.prototype.install = function (ghost) {

    return;
};

/**
 * A method that will be called when the plugin is disabled.
 * Can optionally return a promise if async.
 * @parameter {Ghost} The current Ghost app instance
 */
GhostPlugin.prototype.deactivate = function (ghost) {
    return;
};

module.exports = GhostPlugin;

<|endoftext|>


<commit_before>var table
  , hash  = getHash()
  , load  = true

  , colormap = {}
  , column= [ 'cmid', 'pmid', 'pre', 'term', 'post', 'wdid', 'ftid' ]
  , desc  = {
    cmid: function (title) {var item;return(
      ''+title+' is an object identifier issued by the ContentMine.'+
      ' It is a' + ('aeiou'.indexOf(
	(item=title.split('.')[1].split(/\d/)[0].split(/(?=[A-Z])/).join(' ').toLowerCase())[0])>-1
      ?'n':'') + ' ' + item +
      ' (

)'
    )}
  , pmid: function (title) {return(
      ''+title + ' is an article identifier issued by Pubmed Central. '+
      'Here\'s the article'
    )}
  , wdid: function (title) {return(
      ''+title + ' is an object identifier issued by Wikidata. '+
      'Here\'s the item'
    )}
  , ftid: function (title) {return(
      ''+title + ' is a fact identifier issued by the ContentMine.'
    )}
  }

var apiHost = 'tools.wmflabs.org'

window.onhashchange = checkHash

function getHash () {
  var param = decodeURIComponent( window.location.hash.replace( /^#/, '' ) )
    , parts = param.split( '=' )

  return {
    key: parts[ 0 ],
    val: parts[ 1 ]
  }
}

function clearFilter() {
  $('#desc,#backButton').hide()
  window.location.hash = ''
}

function checkHash() {
  hash = getHash()

  var index
    , value
    , columns = true
    , title   = hash.val||hash.key
    , text

  if ( hash.val ) {
      index = column.indexOf( hash.key )
    , value = hash.val

    if (title&&desc.hasOwnProperty(hash.key)) text = desc[ hash.key ]( title )
  } else if ( hash.key ) {
    if ( /^CM\.[a-z]+([A-Z][a-z]*)*\d*$/.test( hash.key ) ) {
      index = column.indexOf( 'cmid' )
    , value = hash.key

      if ( title ) text = desc[ 'cmid' ]( title )
    } else if ( /^PMC\d+$/.test( hash.key ) ) {
      index = column.indexOf( 'pmid' )
    , value = hash.key

      if ( title ) text = desc[ 'pmid' ]( title )
    } else if ( /^Q\d+$/.test( hash.key ) ) {
      index = column.indexOf( 'wdid' )
    , value = hash.key

      if ( title ) text = desc[ 'wdid' ]( title )
    } else {
      columns = false
      table.columns( 2, 3, 4 ).search( hash.key )
    }
  }

  table
    .search( '' )
    .columns().search( '' )

  if ( columns )
    table.column( index ).search( value )

  if ( title&&text ) {
    $('#desc-title').html(title)
    $('#desc-text').html(text)
    $('#desc,#backButton').show()
  }

  table.draw()
}

function getColorNumber (cmid) {
  var dictionary = cmid.split('.')[1].split(/[0-9]/)[0]
  if (!(dictionary in colormap)) {
    var newColorValue=Math.max(1, ... _.values(colormap))+1
    if (Number.isInteger(newColorValue)) {
      colormap[dictionary] = newColorValue
    }
  }
  return colors[colormap[dictionary]]
}

function loadFile() {
  if (load) {
    var input = document.getElementById('fileinput');
    var file = input.files[0];
    var fr = new FileReader();
    function dummyRemoveXMLFromZDump (file) {
      removeXMLFromZDump(file, receivedText)
    }
    fr.onload = dummyRemoveXMLFromZDump;
    fr.readAsText(file);
  } load = false
}

function loadDefault() {
  if (load) $.get('sample.json', function(file) {
    removeXMLFromZDump(file, receivedText)
  }).fail((err) => {
    var e = {}
    e.target = {}
    e.target.result = err.responseText
    receivedText(e)
  }); load = false
}

function loadDate() {
  var date = document.getElementById('datepicker').value
  $.ajax('https://'+apiHost+'/wikifactmine-api/api/date/'+date, {
    dataType: 'json',
    jsonp: false,
    success: function (data) {
      receivedText(data)
    }
  })
}

function removeXML (str) {
  return str/*.replace(/</g,'<').replace(/>/g,'>').replace(/&?(#x)?.+?;/g,'')*/.replace(
    /^[^<]*>|<.+?>|<[^>]*$/g,
    ' '
  )
}

function removeXMLFromZDump(e, cb) {
  lines = e.target.result.split('\n');
  var newArr = []
  for(var line = 0; line < lines.length; line++){
    try{
      var obj = JSON.parse(lines[line])
      obj.prefix = _.escape( removeXML( obj._source.prefix ) )
      obj.post   = _.escape( removeXML( obj._source.post   ) )
      obj.term   = _.escape(            obj._source.term     )
      obj.identifiers = _.clone(obj._source.identifiers, true)
      obj.cprojectID = obj._source.cprojectID
      obj.documentID = obj._source.documentID
      newArr.push(obj)
    } catch(err) {

    }
  }
  if(cb) {
    cb(newArr)
  }
  else {
   return newArr
  }
}

function receivedText(newArr) {

  var html = ''

  function contentmineID (cmid) {
    return (
      ''+
	     '

' +
	      '' + cmid + '' +
        ''
    )
  }

  function factID ( ftid ) {
    return (
      '' +
	     'link' + ftid +
      ''
    )
  }

  function wikidataID (wid) {
    if (wid)
      return ( ''+wid+'' )
    else
      return ''
  }

  function prependEnWiki (name) {
    return 'https://en.wikipedia.org/wiki/'+name
  }

  //return the value of the property of the string name if the target is an item
  function returnTargetOfProperty (response, wid, pid, cb) {
    try {
      var target = response.entities[wid].claims[pid][0].mainsnak.datavalue.value.amount
      if(response.entities[wid].claims[pid][0].mainsnak.datatype == "wikibase-item") {

      }
      cb(target)
    }
    catch (err) {}
  }

  function getWikiBaseItem (wid, cb) {
    $.ajax({
      url: wdk.getEntities(wid),
      jsonp: "callback",
      dataType: "jsonp",
      success: cb
    })
  }

  function createdRowFunc (row, data, dataIndex) {
    var color = getColorNumber(data[0])
    $(row).find('td.cmid .icon').css('background-color', color)
    var wid = $(data[5]).text()
    if (wid) {
      getWikiBaseItem(wid, function (response) {
	  var title = response.entities[wid].sitelinks.enwiki.title
	  if (title) {
	    var url = prependEnWiki(title)
	    var html = ` <a href="${url}"><img src="W_icon.png" alt="wikipedia_icon"</a>`//
	    $(row).find('td.term').append(html)

	  }
    try {
      desc = response.entities[wid].descriptions.en.value
    }
    catch (err) {}
    if (desc) {
      $(row).find('td.term').attr('title', desc)
    }
	  returnTargetOfProperty(response, wid, 'P1082', function (target) {
      var population = target.replace('+', '')
      var oldtitle = $(this).attr("title") ? ' ' + $(this).attr("title") : ''
	    if (target) $(row).find('td.term').attr('title', `${oldtitle}Population of: ${population}`).attr('data-toggle', 'tooltip').attr('data-container', 'body')
      $('[data-toggle="tooltip"]').tooltip();
	  })
	})
    }
  }

  $('#front-loading-matter').css('display', 'none')

  $.each(newArr, function(index, value) {
    if(value) {
      value.prefix=_.escape(value.prefix)
      value.post=_.escape(value.post)
      html +=
      '
' +
        ''+ contentmineID(value.identifiers.contentmine) +'	' +
        ''+ value.cprojectID[0]+'	' +
        '' + value.prefix.replace(/(\&|\&)\#x.*?\;/g,'')+'	' + //This removes the url encoded text from the snippets
        ''+ value.term+'	' +
        ''+ value.post.replace(/(\&|\&)\#x.*?\;/g,'')+'	' +
        ''+ wikidataID(value.identifiers.wikidata)+'	' +
        ''+ factID(value._id) +'	' +
      '
';
    }
  })
  $('tbody').append(html)

  if ( $.fn.dataTable.isDataTable( '#mytable' ) ) {
    table = $('#mytable').DataTable();
  } else {
    table = $('#mytable').DataTable( {
      ordering  : true
    , order     : [ [ 1, 'asc' ], [ 0, 'asc' ] ]
    , createdRow: createdRowFunc
    , autoWidth : false
    , columnDefs: [
	{ targets: 0, width: '200px' }
      , { targets: 1, width: '110px' }
      , { targets: 3, width: '200px' }
      , { targets: 5, width: '100px' }
      ]
    , search: {
	caseInsensitive: false
      }
    , dom:
	"<'row r-control'<'col-sm-6 c-length'l><'col-sm-6 c-search'f>>" +
	"<'row r-table'<'col-sm-12'tr>>" +
	"<'row r-page'<'col-sm-5 c-info'i><'col-sm-7 c-pagination'p>>"
    } );
  }

  $('#mytable_filter input').on( 'input' , function () {
    window.location.hash = table.search()
  } )

  $('#mytable').css('display', 'table')

  $('#mytable_filter label').append(
    ' <button onclick="clearFilter()">Clear Filter</button>'
  )

  clearFilter()
}
<commit_msg>use removeXMLFromDump even on ajax failure<commit_after>var table
  , hash  = getHash()
  , load  = true

  , colormap = {}
  , column= [ 'cmid', 'pmid', 'pre', 'term', 'post', 'wdid', 'ftid' ]
  , desc  = {
    cmid: function (title) {var item;return(
      ''+title+' is an object identifier issued by the ContentMine.'+
      ' It is a' + ('aeiou'.indexOf(
	(item=title.split('.')[1].split(/\d/)[0].split(/(?=[A-Z])/).join(' ').toLowerCase())[0])>-1
      ?'n':'') + ' ' + item +
      ' (

)'
    )}
  , pmid: function (title) {return(
      ''+title + ' is an article identifier issued by Pubmed Central. '+
      'Here\'s the article'
    )}
  , wdid: function (title) {return(
      ''+title + ' is an object identifier issued by Wikidata. '+
      'Here\'s the item'
    )}
  , ftid: function (title) {return(
      ''+title + ' is a fact identifier issued by the ContentMine.'
    )}
  }

var apiHost = 'tools.wmflabs.org'

window.onhashchange = checkHash

function getHash () {
  var param = decodeURIComponent( window.location.hash.replace( /^#/, '' ) )
    , parts = param.split( '=' )

  return {
    key: parts[ 0 ],
    val: parts[ 1 ]
  }
}

function clearFilter() {
  $('#desc,#backButton').hide()
  window.location.hash = ''
}

function checkHash() {
  hash = getHash()

  var index
    , value
    , columns = true
    , title   = hash.val||hash.key
    , text

  if ( hash.val ) {
      index = column.indexOf( hash.key )
    , value = hash.val

    if (title&&desc.hasOwnProperty(hash.key)) text = desc[ hash.key ]( title )
  } else if ( hash.key ) {
    if ( /^CM\.[a-z]+([A-Z][a-z]*)*\d*$/.test( hash.key ) ) {
      index = column.indexOf( 'cmid' )
    , value = hash.key

      if ( title ) text = desc[ 'cmid' ]( title )
    } else if ( /^PMC\d+$/.test( hash.key ) ) {
      index = column.indexOf( 'pmid' )
    , value = hash.key

      if ( title ) text = desc[ 'pmid' ]( title )
    } else if ( /^Q\d+$/.test( hash.key ) ) {
      index = column.indexOf( 'wdid' )
    , value = hash.key

      if ( title ) text = desc[ 'wdid' ]( title )
    } else {
      columns = false
      table.columns( 2, 3, 4 ).search( hash.key )
    }
  }

  table
    .search( '' )
    .columns().search( '' )

  if ( columns )
    table.column( index ).search( value )

  if ( title&&text ) {
    $('#desc-title').html(title)
    $('#desc-text').html(text)
    $('#desc,#backButton').show()
  }

  table.draw()
}

function getColorNumber (cmid) {
  var dictionary = cmid.split('.')[1].split(/[0-9]/)[0]
  if (!(dictionary in colormap)) {
    var newColorValue=Math.max(1, ... _.values(colormap))+1
    if (Number.isInteger(newColorValue)) {
      colormap[dictionary] = newColorValue
    }
  }
  return colors[colormap[dictionary]]
}

function loadFile() {
  if (load) {
    var input = document.getElementById('fileinput');
    var file = input.files[0];
    var fr = new FileReader();
    function dummyRemoveXMLFromZDump (file) {
      removeXMLFromZDump(file, receivedText)
    }
    fr.onload = dummyRemoveXMLFromZDump;
    fr.readAsText(file);
  } load = false
}

function loadDefault() {
  if (load) $.get('sample.json', function(file) {
    removeXMLFromZDump(file, receivedText)
  }).fail((err) => {
    var e = {}
    e.target = {}
    e.target.result = err.responseText
    removeXMLFromZDump(e, receivedText)
  }); load = false
}

function loadDate() {
  var date = document.getElementById('datepicker').value
  $.ajax('https://'+apiHost+'/wikifactmine-api/api/date/'+date, {
    dataType: 'json',
    jsonp: false,
    success: function (data) {
      receivedText(data)
    }
  })
}

function removeXML (str) {
  return str/*.replace(/</g,'<').replace(/>/g,'>').replace(/&?(#x)?.+?;/g,'')*/.replace(
    /^[^<]*>|<.+?>|<[^>]*$/g,
    ' '
  )
}

function removeXMLFromZDump(e, cb) {
  lines = e.target.result.split('\n');
  var newArr = []
  for(var line = 0; line < lines.length; line++){
    try{
      var obj = JSON.parse(lines[line])
      obj.prefix = _.escape( removeXML( obj._source.prefix ) )
      obj.post   = _.escape( removeXML( obj._source.post   ) )
      obj.term   = _.escape(            obj._source.term     )
      obj.identifiers = _.cloneDeep(obj._source.identifiers)
      obj.cprojectID = obj._source.cprojectID
      obj.documentID = obj._source.documentID
      newArr.push(obj)
    } catch(err) {

    }
  }
  if(cb) {
    cb(newArr)
  }
  else {
   return newArr
  }
}

function receivedText(newArr) {

  var html = ''

  function contentmineID (cmid) {
    return (
      ''+
	     '

' +
	      '' + cmid + '' +
        ''
    )
  }

  function factID ( ftid ) {
    return (
      '' +
	     'link' + ftid +
      ''
    )
  }

  function wikidataID (wid) {
    if (wid)
      return ( ''+wid+'' )
    else
      return ''
  }

  function prependEnWiki (name) {
    return 'https://en.wikipedia.org/wiki/'+name
  }

  //return the value of the property of the string name if the target is an item
  function returnTargetOfProperty (response, wid, pid, cb) {
    try {
      var target = response.entities[wid].claims[pid][0].mainsnak.datavalue.value.amount
      if(response.entities[wid].claims[pid][0].mainsnak.datatype == "wikibase-item") {

      }
      cb(target)
    }
    catch (err) {}
  }

  function getWikiBaseItem (wid, cb) {
    $.ajax({
      url: wdk.getEntities(wid),
      jsonp: "callback",
      dataType: "jsonp",
      success: cb
    })
  }

  function createdRowFunc (row, data, dataIndex) {
    var color = getColorNumber(data[0])
    $(row).find('td.cmid .icon').css('background-color', color)
    var wid = $(data[5]).text()
    if (wid) {
      getWikiBaseItem(wid, function (response) {
	  var title = response.entities[wid].sitelinks.enwiki.title
	  if (title) {
	    var url = prependEnWiki(title)
	    var html = ` <a href="${url}"><img src="W_icon.png" alt="wikipedia_icon"</a>`//
	    $(row).find('td.term').append(html)

	  }
    try {
      desc = response.entities[wid].descriptions.en.value
    }
    catch (err) {}
    if (desc) {
      $(row).find('td.term').attr('title', desc)
    }
	  returnTargetOfProperty(response, wid, 'P1082', function (target) {
      var population = target.replace('+', '')
      var oldtitle = $(this).attr("title") ? ' ' + $(this).attr("title") : ''
	    if (target) $(row).find('td.term').attr('title', `${oldtitle}Population of: ${population}`).attr('data-toggle', 'tooltip').attr('data-container', 'body')
      $('[data-toggle="tooltip"]').tooltip();
	  })
	})
    }
  }

  $('#front-loading-matter').css('display', 'none')

  $.each(newArr, function(index, value) {
    if(value) {
      value.prefix=_.escape(value.prefix)
      value.post=_.escape(value.post)
      html +=
      '
' +
        ''+ contentmineID(value.identifiers.contentmine) +'	' +
        ''+ value.cprojectID[0]+'	' +
        '' + value.prefix.replace(/(\&|\&)\#x.*?\;/g,'')+'	' + //This removes the url encoded text from the snippets
        ''+ value.term+'	' +
        ''+ value.post.replace(/(\&|\&)\#x.*?\;/g,'')+'	' +
        ''+ wikidataID(value.identifiers.wikidata)+'	' +
        ''+ factID(value._id) +'	' +
      '
';
    }
  })
  $('tbody').append(html)

  if ( $.fn.dataTable.isDataTable( '#mytable' ) ) {
    table = $('#mytable').DataTable();
  } else {
    table = $('#mytable').DataTable( {
      ordering  : true
    , order     : [ [ 1, 'asc' ], [ 0, 'asc' ] ]
    , createdRow: createdRowFunc
    , autoWidth : false
    , columnDefs: [
	{ targets: 0, width: '200px' }
      , { targets: 1, width: '110px' }
      , { targets: 3, width: '200px' }
      , { targets: 5, width: '100px' }
      ]
    , search: {
	caseInsensitive: false
      }
    , dom:
	"<'row r-control'<'col-sm-6 c-length'l><'col-sm-6 c-search'f>>" +
	"<'row r-table'<'col-sm-12'tr>>" +
	"<'row r-page'<'col-sm-5 c-info'i><'col-sm-7 c-pagination'p>>"
    } );
  }

  $('#mytable_filter input').on( 'input' , function () {
    window.location.hash = table.search()
  } )

  $('#mytable').css('display', 'table')

  $('#mytable_filter label').append(
    ' <button onclick="clearFilter()">Clear Filter</button>'
  )

  clearFilter()
}
<|endoftext|>


<commit_before>// Wraps utility functions useful throughout project
var util = (function() {
	/*
  	Return Date object 'date' with time set to exactly midnight local time.
	*/
	function zeroDateTime(date) {
		date.setHours(0, 0, 0, 0);

		return date;
	}

	/*
		Strip all leading and trailing whitespace from string 'str'
	*/
	function strip(str) {
		var result = /^\s*(\S(?:.*\S)?)\s*$/.exec(str);

		return !result ? '': result[1];
	}

	return {
		strip: strip,
		zeroDateTime: zeroDateTime
	}
})();

angular.module('helpers.toggle', [])

/*
	Attaching this controller to an element makes it toggleable
  via the toggle() function or setting the 'on' attribute.
*/
.controller('ToggleCtrl', ['$scope', function($scope) {
	$scope.on = false;

	// Always return 1 (so function may easily be chained with && in view)
	$scope.toggle = function() {
		$scope.on = !$scope.on;

		return 1;
	}
}])

/*

*/
.directive('toggleLink', ['$timeout', function($timeout) {
	return {
		restrict: 'E',
		transclude: true,
		replace: true,
		link: function(scope, elm, attrs) {
			var toggle = function() {
				scope.toggle();
				$timeout(function() {
					scope.focusId(attrs.focusTo);
  			}, 10);
			}

			elm.on('mouseup', function(event) {
				if (event.which === 1)
					toggle();
			})

			elm.on('keydown', function(event) {
				if (event.which === 32 || event.keyCode === 32 || event.key === 'Spacebar')
					toggle();

				if (event.which === 13 || event.keyCode === 13 || event.key === 'Enter')
					toggle();
			});
		},
		template: ''
	};
}])

/*
	Turns the scope's ToggleCtrl off when the event whose
	name is attached to the directive is caught.

	Note the event name is evaluated as an angular expression;
	to use a string literal, wrap the name in single quotes.

 	USAGE:
  	<ANY ng-controller="ToggleCtrl" off-on-event="someExpression">...</ANY>
 */
.directive('offOnEvent', function() {
	return {
		restrict: 'A',
		link: function(scope, elm, attrs) {
			var eventName = scope.$eval(attrs.offOnEvent);

			scope.$on(eventName, function(event) {
				if (scope.on) scope.toggle();
			});
		}
	};
});

angular.module('helpers.misc', [])

// Evaluate the angular expression attached to the directive
// when an element (presumed to be an  but it's not necessary)
// is interacted with like a button, i.e. when left-clicked
// or when spacebar is pressed while element is in focus.
//
//
// Usage:
//   <ANY on-press="someExpression"></ANY>
.directive('onPress', function() {
	return {
		restrict: 'A',
		link: function(scope, elm, attrs) {
			var action = function() {
				scope.$eval(attrs.onPress);
			};

			elm.on('keydown', function(event) {
				if (event.which === 32 || event.keyCode === 32 || event.key === 'Spacebar') {
					action()

				if (event.which === 13 || event.keyCode === 13 || event.key === 'Enter')
					action();
				}
			});

			elm.on('mouseup', function(event) {
				if (event.button === 0)
					action();
			});
		}
	};
})

// Naively capitalizes only first character of input string
//
// Note CSS text-transform: capitalize is preferable, but seems to not
// work in Chrome on <option> elements
.filter('capitalize', function() {
	return function(input) {
		return input[0].toUpperCase() + input.slice(1);
	};
})

// If input value is undefined, null, or empty string return a message
// indicating value is not set. Argument to filter is message;
// If not passed, a default message is used.
//
// Usage:
//   <ANY>{{ someVar | emptyMessage[:'Custom Message'] }}</ANY>
.filter('emptyMessage', function() {
	return function(input, msg) {
		msg = msg ? msg : '(Not set)';

		return ((typeof input === 'undefined') || input === null || input === '') ? msg : input;
	};
});
<commit_msg>Add docstring for toggle-link directive<commit_after>// Wraps utility functions useful throughout project
var util = (function() {
	/*
  	Return Date object 'date' with time set to exactly midnight local time.
	*/
	function zeroDateTime(date) {
		date.setHours(0, 0, 0, 0);

		return date;
	}

	/*
		Strip all leading and trailing whitespace from string 'str'
	*/
	function strip(str) {
		var result = /^\s*(\S(?:.*\S)?)\s*$/.exec(str);

		return !result ? '': result[1];
	}

	return {
		strip: strip,
		zeroDateTime: zeroDateTime
	}
})();

angular.module('helpers.toggle', [])

/*
	Attaching this controller to an element makes it toggleable
  via the toggle() function or setting the 'on' attribute.
*/
.controller('ToggleCtrl', ['$scope', function($scope) {
	$scope.on = false;

	// Always return 1 (so function may easily be chained with && in view)
	$scope.toggle = function() {
		$scope.on = !$scope.on;

		return 1;
	}
}])

/*
	Replaces element directive with  that functions as button that toggle's
	ToggleCtrl on left-click, or enter/space press. role="button" is also added
	for compatibility.

	Optional focus-to attribute will set focus on a particular id when button pressed.

	Combine with on-press to extend behavior on interaction.

	EXAMPLE USAGE:
		<ANY ng-controller="ToggleCtrl">

Some text
	  		<toggle-link class="some-class" on-press="expr" focus-to="anId">
	  			Link Text
	  		</toggle-link>

	  </ANY>

	  rendered as:

		<ANY ng-controller="ToggleCtrl">

Some text

					Link Text

		</ANY>
*/
.directive('toggleLink', ['$timeout', function($timeout) {
	return {
		restrict: 'E',
		transclude: true,
		replace: true,
		link: function(scope, elm, attrs) {
			var toggle = function() {
				scope.toggle();
				if (!attrs.focusTo) return;

				$timeout(function() {
					scope.focusId(attrs.focusTo);
  			}, 10);
			}

			elm.on('mouseup', function(event) {
				if (event.which === 1)
					toggle();
			})

			elm.on('keydown', function(event) {
				if (event.which === 32 || event.keyCode === 32 || event.key === 'Spacebar')
					toggle();

				if (event.which === 13 || event.keyCode === 13 || event.key === 'Enter')
					toggle();
			});
		},
		template: ''
	};
}])

/*
	Turns the scope's ToggleCtrl off when the event whose
	name is attached to the directive is caught.

	Note the event name is evaluated as an angular expression;
	to use a string literal, wrap the name in single quotes.

 	USAGE:
  	<ANY ng-controller="ToggleCtrl" off-on-event="someExpression">...</ANY>
 */
.directive('offOnEvent', function() {
	return {
		restrict: 'A',
		link: function(scope, elm, attrs) {
			var eventName = scope.$eval(attrs.offOnEvent);

			scope.$on(eventName, function(event) {
				if (scope.on) scope.toggle();
			});
		}
	};
});

angular.module('helpers.misc', [])

// Evaluate the angular expression attached to the directive
// when an element (presumed to be an  but it's not necessary)
// is interacted with like a button, i.e. when left-clicked
// or when spacebar is pressed while element is in focus.
//
//
// Usage:
//   <ANY on-press="someExpression"></ANY>
.directive('onPress', function() {
	return {
		restrict: 'A',
		link: function(scope, elm, attrs) {
			var action = function() {
				scope.$eval(attrs.onPress);
			};

			elm.on('keydown', function(event) {
				if (event.which === 32 || event.keyCode === 32 || event.key === 'Spacebar') {
					action()

				if (event.which === 13 || event.keyCode === 13 || event.key === 'Enter')
					action();
				}
			});

			elm.on('mouseup', function(event) {
				if (event.button === 0)
					action();
			});
		}
	};
})

// Naively capitalizes only first character of input string
//
// Note CSS text-transform: capitalize is preferable, but seems to not
// work in Chrome on <option> elements
.filter('capitalize', function() {
	return function(input) {
		return input[0].toUpperCase() + input.slice(1);
	};
})

// If input value is undefined, null, or empty string return a message
// indicating value is not set. Argument to filter is message;
// If not passed, a default message is used.
//
// Usage:
//   <ANY>{{ someVar | emptyMessage[:'Custom Message'] }}</ANY>
.filter('emptyMessage', function() {
	return function(input, msg) {
		msg = msg ? msg : '(Not set)';

		return ((typeof input === 'undefined') || input === null || input === '') ? msg : input;
	};
});
<|endoftext|>


<commit_before>/*
  gulpfile.js
  ===========
  Rather than manage one giant configuration file responsible
  for creating multiple tasks, each task has been broken out into
  its own file in gulp/tasks. Any files in that directory get
  automatically required below.
  To add a new task, simply add a new task file that directory.
  gulp/tasks/default.js specifies the default set of tasks to run
  when you run `gulp`.
*/

var requireDir = require('require-dir');

if (process.env.NODE_ENV !== 'production') {
  // Require all tasks in gulp/tasks, including subfolders
  requireDir('./gulp/tasks', { recurse: true });
} else {
  // If in production only require tasks necessary for producion build
  require('./gulp/tasks/config.js');
  requireDir('./gulp/tasks/production', { recurse: true });
}
<commit_msg>Fix gulp config require in production<commit_after>/*
  gulpfile.js
  ===========
  Rather than manage one giant configuration file responsible
  for creating multiple tasks, each task has been broken out into
  its own file in gulp/tasks. Any files in that directory get
  automatically required below.
  To add a new task, simply add a new task file that directory.
  gulp/tasks/default.js specifies the default set of tasks to run
  when you run `gulp`.
*/

var requireDir = require('require-dir');

if (process.env.NODE_ENV !== 'production') {
  // Require all tasks in gulp/tasks, including subfolders
  requireDir('./gulp/tasks', { recurse: true });
} else {
  // If in production only require tasks necessary for producion build
  requireDir('./gulp/tasks/production', { recurse: true });
}
<|endoftext|>


<commit_before>/**
 * modules/analytics data store: properties tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import { STORE_NAME } from './constants';
import {
	createTestRegistry,
	muteConsole,
	subscribeUntil,
	unsubscribeFromAll,
} from 'tests/js/utils';
import * as fixtures from './__fixtures__';

describe( 'modules/analytics properties', () => {
	let registry;

	beforeAll( () => {
		API.setUsingCache( false );
	} );

	beforeEach( () => {
		registry = createTestRegistry();
		// Receive empty settings to prevent unexpected fetch by resolver.
		registry.dispatch( STORE_NAME ).receiveGetSettings( {} );
	} );

	afterAll( () => {
		API.setUsingCache( true );
	} );

	afterEach( () => {
		unsubscribeFromAll( registry );
	} );

	describe( 'actions', () => {
		describe( 'createProperty', () => {
			it( 'creates a property and adds it to the store ', async () => {
				const accountID = fixtures.createProperty.accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				fetchMock.post(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{ body: fixtures.createProperty, status: 200 }
				);

				registry.dispatch( STORE_NAME ).createProperty( accountID );
				// Ensure the proper parameters were passed.
				expect( fetchMock ).toHaveFetched(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{
						body: { data: { accountID } },
					}
				);

				await subscribeUntil( registry,
					() => registry.select( STORE_NAME ).isDoingCreateProperty( accountID ) === false
				);

				const properties = registry.select( STORE_NAME ).getProperties( accountID );
				expect( properties ).toMatchObject( [ fixtures.createProperty ] );
			} );

			it( 'sets isDoingCreateProperty ', async () => {
				const accountID = fixtures.createProperty.accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				fetchMock.post(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{ body: fixtures.createProperty, status: 200 }
				);

				registry.dispatch( STORE_NAME ).createProperty( accountID );
				expect( registry.select( STORE_NAME ).isDoingCreateProperty( accountID ) ).toEqual( true );
			} );

			it( 'dispatches an error if the request fails ', async () => {
				const accountID = fixtures.createProperty.accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const response = {
					code: 'internal_server_error',
					message: 'Internal server error',
					data: { status: 500 },
				};
				fetchMock.post(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{ body: response, status: 500 }
				);
				// Add additional mock to avoid unmatched request warning.
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{ body: {}, status: 200 }
				);

				muteConsole( 'error' );
				registry.dispatch( STORE_NAME ).createProperty( accountID );

				await subscribeUntil( registry,
					() => registry.select( STORE_NAME ).isDoingCreateProperty( accountID ) === false
				);

				expect( registry.select( STORE_NAME ).getErrorForAction( 'createProperty', [ accountID ] ) ).toMatchObject( response );
				fetchMock.get(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{ body: fixtures.propertiesProfiles, status: 200 }
				);

				const properties = registry.select( STORE_NAME ).getProperties( accountID );
				// No properties should have been added yet, as the property creation
				// failed.
				expect( properties ).toEqual( undefined );
			} );
		} );

		describe( 'selectProperty', () => {
			it( 'requires a valid propertyID', () => {
				expect( () => {
					registry.dispatch( STORE_NAME ).selectProperty();
				} ).toThrow( 'A valid propertyID selection is required.' );
			} );

			it( 'returns if the accountID is not set', () => {
				const accountID = fixtures.propertiesProfiles.properties[ 0 ].accountId;
				const propertyID = fixtures.propertiesProfiles.properties[ 0 ].id;

				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );
				registry.dispatch( STORE_NAME ).receiveGetProfiles( fixtures.propertiesProfiles.profiles, { accountID, propertyID } );

				expect( registry.select( STORE_NAME ).getPropertyID() ).toBeUndefined();
				registry.dispatch( STORE_NAME ).selectProperty( propertyID );
				expect( registry.select( STORE_NAME ).getPropertyID() ).toBeUndefined();
			} );

			it( 'selects the property and its default profile when set', async () => {
				const accountID = fixtures.propertiesProfiles.properties[ 0 ].accountId;
				const propertyID = fixtures.propertiesProfiles.properties[ 0 ].id;

				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );
				registry.dispatch( STORE_NAME ).receiveGetProfiles( fixtures.propertiesProfiles.profiles, { accountID, propertyID } );
				await registry.dispatch( STORE_NAME ).setAccountID( accountID );
				await registry.dispatch( STORE_NAME ).selectProperty( propertyID );

				expect( registry.select( STORE_NAME ).getPropertyID() ).toMatch( propertyID );
				expect( registry.select( STORE_NAME ).getInternalWebPropertyID() ).toEqual( fixtures.propertiesProfiles.properties[ 0 ].internalWebPropertyId );
				expect( registry.select( STORE_NAME ).getProfileID() ).toEqual( fixtures.propertiesProfiles.properties[ 0 ].defaultProfileId );
			} );

			it( 'does not set the profileID if property has defaultProfileId that is not in state', async () => {
				const nonExistentProfileID = '1234567890';
				const propertiesProfiles = {
					...fixtures.propertiesProfiles,
					properties: fixtures.propertiesProfiles.properties.map( ( property ) => {
						return { ...property, defaultProfileId: nonExistentProfileID };
					} ),
				};

				const accountID = propertiesProfiles.properties[ 0 ].accountId;
				const propertyID = propertiesProfiles.properties[ 0 ].id;

				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );
				registry.dispatch( STORE_NAME ).receiveGetProfiles( fixtures.propertiesProfiles.profiles, { accountID, propertyID } );

				await registry.dispatch( STORE_NAME ).setAccountID( accountID );
				await registry.dispatch( STORE_NAME ).selectProperty( propertyID );

				expect( registry.select( STORE_NAME ).getProfiles( accountID, propertyID ).some( ( { id } ) => id === nonExistentProfileID ) ).toBe( false );
				expect( registry.select( STORE_NAME ).getProfileID() ).not.toBe( nonExistentProfileID );
			} );
		} );
	} );

	describe( 'selectors', () => {
		describe( 'getProperties', () => {
			it( 'uses a resolver to make a network request', async () => {
				fetchMock.get(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{ body: fixtures.propertiesProfiles, status: 200 }
				);

				const accountID = fixtures.propertiesProfiles.properties[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const propertyID = fixtures.propertiesProfiles.profiles[ 0 ].webPropertyId; // Capitalization rule exception: `webPropertyId` is a property of an API returned value.

				const initialProperties = registry.select( STORE_NAME ).getProperties( accountID );

				// Ensure the proper parameters were passed.
				expect( fetchMock ).toHaveFetched(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{
						query: { accountID },
					}
				);

				expect( initialProperties ).toEqual( undefined );
				await subscribeUntil( registry,
					() => (
						registry.select( STORE_NAME ).getProperties( accountID ) !== undefined
					),
				);

				const properties = registry.select( STORE_NAME ).getProperties( accountID );

				expect( fetchMock ).toHaveFetchedTimes( 1 );

				// Profiles should also have been received by this action.
				const profiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID );

				expect( properties ).toEqual( fixtures.propertiesProfiles.properties );
				expect( properties ).toHaveLength( 17 );
				expect( profiles ).toEqual( fixtures.propertiesProfiles.profiles );
			} );

			it( 'does not make a network request if properties for this account are already present', async () => {
				const testAccountID = fixtures.profiles[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const accountID = testAccountID;

				// Load data into this store so there are matches for the data we're about to select,
				// even though the selector hasn't fulfilled yet.
				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );

				const properties = registry.select( STORE_NAME ).getProperties( testAccountID );

				await subscribeUntil( registry, () => registry
					.select( STORE_NAME )
					.hasFinishedResolution( 'getProperties', [ testAccountID ] )
				);

				// It _may_ make a request for profiles internally if not loaded,
				// so we only care that it did not fetch properties here.
				expect( fetchMock ).not.toHaveFetched(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
				);
				expect( properties ).toEqual( fixtures.propertiesProfiles.properties );
				expect( properties ).toHaveLength( 17 );
			} );

			it( 'dispatches an error if the request fails', async () => {
				const response = {
					code: 'internal_server_error',
					message: 'Internal server error',
					data: { status: 500 },
				};
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{ body: response, status: 500 }
				);

				const fakeAccountID = '777888999';
				muteConsole( 'error' );
				registry.select( STORE_NAME ).getProperties( fakeAccountID );
				await subscribeUntil( registry,
					() => registry.select( STORE_NAME ).isDoingGetProperties( fakeAccountID ) === false,
				);

				expect( fetchMock ).toHaveFetchedTimes( 1 );

				const properties = registry.select( STORE_NAME ).getProperties( fakeAccountID );
				expect( properties ).toEqual( undefined );
			} );
		} );
		describe( 'getPropertyByID', () => {
			it( 'returns the property object by its ID when present in the store', () => {
				const { properties } = fixtures.propertiesProfiles;
				const testAccountID = fixtures.profiles[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const accountID = testAccountID;

				registry.dispatch( STORE_NAME ).receiveGetProperties( properties, { accountID } );

				const findProperty = properties[ 1 ];
				const foundProperty = registry.select( STORE_NAME ).getPropertyByID( findProperty.id );

				expect( foundProperty ).toEqual( findProperty );
			} );

			it( 'returns undefined when the property is not present in the store', () => {
				const { properties } = fixtures.propertiesProfiles;
				const accountID = fixtures.profiles[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.

				registry.dispatch( STORE_NAME ).receiveGetProperties( [], { accountID } );

				const findProperty = properties[ 1 ];
				const foundProperty = registry.select( STORE_NAME ).getPropertyByID( findProperty.id );

				expect( foundProperty ).toEqual( undefined );
			} );
		} );
	} );
} );
<commit_msg>Update other properties tests to fix silent errors.<commit_after>/**
 * modules/analytics data store: properties tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import { STORE_NAME } from './constants';
import {
	createTestRegistry,
	muteConsole,
	muteFetch,
	subscribeUntil,
	unsubscribeFromAll,
} from 'tests/js/utils';
import * as fixtures from './__fixtures__';

describe( 'modules/analytics properties', () => {
	let registry;

	beforeAll( () => {
		API.setUsingCache( false );
	} );

	beforeEach( () => {
		registry = createTestRegistry();
		// Receive empty settings to prevent unexpected fetch by resolver.
		registry.dispatch( STORE_NAME ).receiveGetSettings( {} );
	} );

	afterAll( () => {
		API.setUsingCache( true );
	} );

	afterEach( () => {
		unsubscribeFromAll( registry );
	} );

	describe( 'actions', () => {
		describe( 'createProperty', () => {
			it( 'creates a property and adds it to the store ', async () => {
				const accountID = fixtures.createProperty.accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				fetchMock.post(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{ body: fixtures.createProperty, status: 200 }
				);

				await registry.dispatch( STORE_NAME ).createProperty( accountID );
				// Ensure the proper parameters were passed.
				expect( fetchMock ).toHaveFetched(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{
						body: { data: { accountID } },
					}
				);

				const properties = registry.select( STORE_NAME ).getProperties( accountID );
				expect( properties ).toMatchObject( [ fixtures.createProperty ] );
			} );

			it( 'sets isDoingCreateProperty ', async () => {
				const accountID = fixtures.createProperty.accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				fetchMock.post(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{ body: fixtures.createProperty, status: 200 }
				);

				registry.dispatch( STORE_NAME ).createProperty( accountID );
				expect( registry.select( STORE_NAME ).isDoingCreateProperty( accountID ) ).toEqual( true );
			} );

			it( 'dispatches an error if the request fails ', async () => {
				const accountID = fixtures.createProperty.accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const response = {
					code: 'internal_server_error',
					message: 'Internal server error',
					data: { status: 500 },
				};
				fetchMock.post(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/create-property/,
					{ body: response, status: 500 }
				);

				muteConsole( 'error' );
				await registry.dispatch( STORE_NAME ).createProperty( accountID );

				expect( registry.select( STORE_NAME ).getErrorForAction( 'createProperty', [ accountID ] ) ).toMatchObject( response );

				// The response isn't important for the test here and we intentionally don't wait for it,
				// but the fixture is used to prevent an invariant error as the received properties are required to be an array.
				muteFetch( /^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/, fixtures.propertiesProfiles );
				const properties = registry.select( STORE_NAME ).getProperties( accountID );
				// No properties should have been added yet, as the property creation
				// failed.
				expect( properties ).toEqual( undefined );
			} );
		} );

		describe( 'selectProperty', () => {
			it( 'requires a valid propertyID', () => {
				expect( () => {
					registry.dispatch( STORE_NAME ).selectProperty();
				} ).toThrow( 'A valid propertyID selection is required.' );
			} );

			it( 'returns if the accountID is not set', () => {
				const accountID = fixtures.propertiesProfiles.properties[ 0 ].accountId;
				const propertyID = fixtures.propertiesProfiles.properties[ 0 ].id;

				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );
				registry.dispatch( STORE_NAME ).receiveGetProfiles( fixtures.propertiesProfiles.profiles, { accountID, propertyID } );

				expect( registry.select( STORE_NAME ).getPropertyID() ).toBeUndefined();
				registry.dispatch( STORE_NAME ).selectProperty( propertyID );
				expect( registry.select( STORE_NAME ).getPropertyID() ).toBeUndefined();
			} );

			it( 'selects the property and its default profile when set', async () => {
				const accountID = fixtures.propertiesProfiles.properties[ 0 ].accountId;
				const propertyID = fixtures.propertiesProfiles.properties[ 0 ].id;

				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );
				registry.dispatch( STORE_NAME ).receiveGetProfiles( fixtures.propertiesProfiles.profiles, { accountID, propertyID } );
				await registry.dispatch( STORE_NAME ).setAccountID( accountID );
				await registry.dispatch( STORE_NAME ).selectProperty( propertyID );

				expect( registry.select( STORE_NAME ).getPropertyID() ).toMatch( propertyID );
				expect( registry.select( STORE_NAME ).getInternalWebPropertyID() ).toEqual( fixtures.propertiesProfiles.properties[ 0 ].internalWebPropertyId );
				expect( registry.select( STORE_NAME ).getProfileID() ).toEqual( fixtures.propertiesProfiles.properties[ 0 ].defaultProfileId );
			} );

			it( 'does not set the profileID if property has defaultProfileId that is not in state', async () => {
				const nonExistentProfileID = '1234567890';
				const propertiesProfiles = {
					...fixtures.propertiesProfiles,
					properties: fixtures.propertiesProfiles.properties.map( ( property ) => {
						return { ...property, defaultProfileId: nonExistentProfileID };
					} ),
				};

				const accountID = propertiesProfiles.properties[ 0 ].accountId;
				const propertyID = propertiesProfiles.properties[ 0 ].id;

				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );
				registry.dispatch( STORE_NAME ).receiveGetProfiles( fixtures.propertiesProfiles.profiles, { accountID, propertyID } );

				await registry.dispatch( STORE_NAME ).setAccountID( accountID );
				await registry.dispatch( STORE_NAME ).selectProperty( propertyID );

				expect( registry.select( STORE_NAME ).getProfiles( accountID, propertyID ).some( ( { id } ) => id === nonExistentProfileID ) ).toBe( false );
				expect( registry.select( STORE_NAME ).getProfileID() ).not.toBe( nonExistentProfileID );
			} );
		} );
	} );

	describe( 'selectors', () => {
		describe( 'getProperties', () => {
			it( 'uses a resolver to make a network request', async () => {
				fetchMock.get(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{ body: fixtures.propertiesProfiles, status: 200 }
				);

				const accountID = fixtures.propertiesProfiles.properties[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const propertyID = fixtures.propertiesProfiles.profiles[ 0 ].webPropertyId; // Capitalization rule exception: `webPropertyId` is a property of an API returned value.

				const initialProperties = registry.select( STORE_NAME ).getProperties( accountID );

				// Ensure the proper parameters were passed.
				expect( fetchMock ).toHaveFetched(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{
						query: { accountID },
					}
				);

				expect( initialProperties ).toEqual( undefined );
				await subscribeUntil( registry,
					() => (
						registry.select( STORE_NAME ).getProperties( accountID ) !== undefined
					),
				);

				const properties = registry.select( STORE_NAME ).getProperties( accountID );

				expect( fetchMock ).toHaveFetchedTimes( 1 );

				// Profiles should also have been received by this action.
				const profiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID );

				expect( properties ).toEqual( fixtures.propertiesProfiles.properties );
				expect( properties ).toHaveLength( 17 );
				expect( profiles ).toEqual( fixtures.propertiesProfiles.profiles );
			} );

			it( 'does not make a network request if properties for this account are already present', async () => {
				const testAccountID = fixtures.profiles[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const accountID = testAccountID;

				// Load data into this store so there are matches for the data we're about to select,
				// even though the selector hasn't fulfilled yet.
				registry.dispatch( STORE_NAME ).receiveGetProperties( fixtures.propertiesProfiles.properties, { accountID } );

				const properties = registry.select( STORE_NAME ).getProperties( testAccountID );

				await subscribeUntil( registry, () => registry
					.select( STORE_NAME )
					.hasFinishedResolution( 'getProperties', [ testAccountID ] )
				);

				// It _may_ make a request for profiles internally if not loaded,
				// so we only care that it did not fetch properties here.
				expect( fetchMock ).not.toHaveFetched(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
				);
				expect( properties ).toEqual( fixtures.propertiesProfiles.properties );
				expect( properties ).toHaveLength( 17 );
			} );

			it( 'dispatches an error if the request fails', async () => {
				const response = {
					code: 'internal_server_error',
					message: 'Internal server error',
					data: { status: 500 },
				};
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/analytics\/data\/properties-profiles/,
					{ body: response, status: 500 }
				);

				const fakeAccountID = '777888999';
				muteConsole( 'error' );
				registry.select( STORE_NAME ).getProperties( fakeAccountID );
				await subscribeUntil( registry,
					() => registry.select( STORE_NAME ).isDoingGetProperties( fakeAccountID ) === false,
				);

				expect( fetchMock ).toHaveFetchedTimes( 1 );

				const properties = registry.select( STORE_NAME ).getProperties( fakeAccountID );
				expect( properties ).toEqual( undefined );
			} );
		} );
		describe( 'getPropertyByID', () => {
			it( 'returns the property object by its ID when present in the store', () => {
				const { properties } = fixtures.propertiesProfiles;
				const testAccountID = fixtures.profiles[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.
				const accountID = testAccountID;

				registry.dispatch( STORE_NAME ).receiveGetProperties( properties, { accountID } );

				const findProperty = properties[ 1 ];
				const foundProperty = registry.select( STORE_NAME ).getPropertyByID( findProperty.id );

				expect( foundProperty ).toEqual( findProperty );
			} );

			it( 'returns undefined when the property is not present in the store', () => {
				const { properties } = fixtures.propertiesProfiles;
				const accountID = fixtures.profiles[ 0 ].accountId; // Capitalization rule exception: `accountId` is a property of an API returned value.

				registry.dispatch( STORE_NAME ).receiveGetProperties( [], { accountID } );

				const findProperty = properties[ 1 ];
				const foundProperty = registry.select( STORE_NAME ).getPropertyByID( findProperty.id );

				expect( foundProperty ).toEqual( undefined );
			} );
		} );
	} );
} );
<|endoftext|>