+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% for bowerJSFile in bowerJSFiles %}
+
+{% endfor %}
+
+
+
+
+{% for jsFile in formJSFiles %}
+
+{% endfor %}
+
+
+{% if process.env.NODE_ENV === 'development' %}
+
+
+{% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/views/layout.server.view.html b/app/views/layout.server.view.html
index 212a3b84..bbec12de 100755
--- a/app/views/layout.server.view.html
+++ b/app/views/layout.server.view.html
@@ -31,16 +31,16 @@
-
+
-
+
{% for bowerCssFile in bowerCssFiles %}
{% endfor %}
-
-
-
+
+
+
+
diff --git a/public/form_modules/forms/config/forms.client.config.js b/public/form_modules/forms/config/forms.client.config.js
new file mode 100644
index 00000000..188c56e5
--- /dev/null
+++ b/public/form_modules/forms/config/forms.client.config.js
@@ -0,0 +1,29 @@
+'use strict';
+
+// Configuring the Forms drop-down menus
+angular.module('view-form').filter('formValidity',
+ function(){
+ return function(formObj){
+ if(formObj && formObj.form_fields && formObj.visible_form_fields){
+
+ //get keys
+ var formKeys = Object.keys(formObj);
+
+ //we only care about things that don't start with $
+ var fieldKeys = formKeys.filter(function(key){
+ return key[0] !== '$';
+ });
+
+ var fields = formObj.form_fields;
+
+ var valid_count = fields.filter(function(field){
+ if(typeof field === 'object' && field.fieldType !== 'statement' && field.fieldType !== 'rating'){
+ return !!(field.fieldValue);
+ }
+
+ }).length;
+ return valid_count - (formObj.form_fields.length - formObj.visible_form_fields.length);
+ }
+ return 0;
+ };
+});
diff --git a/public/form_modules/forms/config/forms.client.routes.js b/public/form_modules/forms/config/forms.client.routes.js
new file mode 100644
index 00000000..b0e0efb5
--- /dev/null
+++ b/public/form_modules/forms/config/forms.client.routes.js
@@ -0,0 +1,22 @@
+'use strict';
+
+// Setting up route
+angular.module('view-form').config(['$stateProvider',
+
+ function($stateProvider) {
+ // Forms state routing
+ $stateProvider.
+ state('submitForm', {
+ url: '/forms/:formId',
+ templateUrl: '/static/form_modules/forms/base/views/submit-form.client.view.html',
+ resolve: {
+ Forms: 'Forms',
+ myForm: function (Forms, $stateParams) {
+ return Forms.get({formId: $stateParams.formId}).$promise;
+ }
+ },
+ controller: 'SubmitFormController',
+ controllerAs: 'ctrl'
+ })
+ }
+]);
diff --git a/public/form_modules/forms/directives/analytics-service.client.directive.js b/public/form_modules/forms/directives/analytics-service.client.directive.js
new file mode 100644
index 00000000..2015eb2e
--- /dev/null
+++ b/public/form_modules/forms/directives/analytics-service.client.directive.js
@@ -0,0 +1,43 @@
+(function () {
+ 'use strict';
+
+ // Create the SendVisitorData service
+ angular
+ .module('view-form')
+ .factory('SendVisitorData', SendVisitorData);
+
+ SendVisitorData.$inject = ['Socket', '$state'];
+
+ function SendVisitorData(Socket, $state) {
+
+ // Create a controller method for sending visitor data
+ function send(form, lastActiveIndex, timeElapsed) {
+
+ // Create a new message object
+ var visitorData = {
+ referrer: document.referrer,
+ isSubmitted: form.submitted,
+ formId: form._id,
+ lastActiveField: form.form_fields[lastActiveIndex]._id,
+ timeElapsed: timeElapsed
+ };
+ Socket.emit('form-visitor-data', visitorData);
+ }
+
+ function init(){
+ // Make sure the Socket is connected
+ if (!Socket.socket) {
+ Socket.connect();
+ }
+ }
+
+ var service = {
+ send: send
+ };
+
+ init();
+ return service;
+
+ }
+}());
+
diff --git a/public/form_modules/forms/directives/key-to-option.client.directive.js b/public/form_modules/forms/directives/key-to-option.client.directive.js
new file mode 100644
index 00000000..2a4a7de8
--- /dev/null
+++ b/public/form_modules/forms/directives/key-to-option.client.directive.js
@@ -0,0 +1,26 @@
+'use strict';
+
+angular.module('view-form').directive('keyToOption', function(){
+ return {
+ restrict: 'A',
+ scope: {
+ field: '='
+ },
+ link: function($scope, $element, $attrs, $select) {
+ $element.bind('keydown keypress', function(event) {
+
+ var keyCode = event.which || event.keyCode;
+ var index = parseInt(String.fromCharCode(keyCode))-1;
+ //console.log($scope.field);
+
+ if (index < $scope.field.fieldOptions.length) {
+ event.preventDefault();
+ $scope.$apply(function () {
+ $scope.field.fieldValue = $scope.field.fieldOptions[index].option_value;
+ });
+ }
+
+ });
+ }
+ };
+});
diff --git a/public/form_modules/forms/directives/key-to-truthy.client.directive.js b/public/form_modules/forms/directives/key-to-truthy.client.directive.js
new file mode 100644
index 00000000..175bf36d
--- /dev/null
+++ b/public/form_modules/forms/directives/key-to-truthy.client.directive.js
@@ -0,0 +1,30 @@
+'use strict';
+
+angular.module('view-form').directive('keyToTruthy', ['$rootScope', function($rootScope){
+ return {
+ restrict: 'A',
+ scope: {
+ field: '='
+ },
+ link: function($scope, $element, $attrs) {
+ $element.bind('keydown keypress', function(event) {
+ var keyCode = event.which || event.keyCode;
+ var truthyKeyCode = $attrs.keyCharTruthy.charCodeAt(0) - 32;
+ var falseyKeyCode = $attrs.keyCharFalsey.charCodeAt(0) - 32;
+
+ if(keyCode === truthyKeyCode ) {
+ event.preventDefault();
+ $scope.$apply(function() {
+ $scope.field.fieldValue = 'true';
+ });
+ }else if(keyCode === falseyKeyCode){
+ event.preventDefault();
+ $scope.$apply(function() {
+ $scope.field.fieldValue = 'false';
+ });
+ }
+ });
+ }
+ };
+}]);
+
diff --git a/public/form_modules/forms/forms.client.module.js b/public/form_modules/forms/forms.client.module.js
new file mode 100644
index 00000000..c56beb5f
--- /dev/null
+++ b/public/form_modules/forms/forms.client.module.js
@@ -0,0 +1,7 @@
+'use strict';
+
+// Use Application configuration module to register a new module
+ApplicationConfiguration.registerModule('view-form', [
+ 'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
+ 'angular-input-stars', 'pascalprecht.translate'
+]);//, 'colorpicker.module' @TODO reactivate this module
diff --git a/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js b/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js
new file mode 100644
index 00000000..7530812f
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js
@@ -0,0 +1,256 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('AdminForm Controller Tests', function() {
+ // Initialize global variables
+ var AdminFormController,
+ createAdminFormController,
+ scope,
+ $httpBackend,
+ $stateParams,
+ $location,
+ $state;
+
+ var sampleUser = {
+ firstName: 'Full',
+ lastName: 'Name',
+ email: 'test@test.com',
+ username: 'test@test.com',
+ password: 'password',
+ provider: 'local',
+ roles: ['user'],
+ _id: 'ed873933b1f1dea0ce12fab9'
+ };
+
+ var sampleForm = {
+ title: 'Form Title',
+ admin: 'ed873933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'}
+ ],
+ _id: '525a8422f6d0f87f0e407a33'
+ };
+
+ var expectedForm = {
+ title: 'Form Title',
+ admin: 'ed873933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'}
+ ],
+ visible_form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'}
+ ],
+ _id: '525a8422f6d0f87f0e407a33'
+ };
+
+ var newFakeModal = function(){
+ var result = {
+ opened: true,
+ result: {
+ then: function(confirmCallback, cancelCallback) {
+ //Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
+ this.confirmCallBack = confirmCallback;
+ this.cancelCallback = cancelCallback;
+ }
+ },
+ close: function( item ) {
+ //The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
+ this.opened = false;
+ this.result.confirmCallBack( item );
+ },
+ dismiss: function( type ) {
+ //The user clicked cancel on the modal dialog, call the stored cancel callback
+ this.opened = false;
+ this.result.cancelCallback( type );
+ }
+ };
+ return result;
+ };
+
+ //Mock Users Service
+ beforeEach(module(function($provide) {
+ $provide.service('myForm', function($q) {
+ return sampleForm;
+ });
+ }));
+
+
+ // The $resource service augments the response object with methods for updating and deleting the resource.
+ // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
+ // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
+ // When the toEqualData matcher compares two objects, it takes only object properties into
+ // account and ignores methods.
+ beforeEach(function() {
+ jasmine.addMatchers({
+ toEqualData: function(util, customEqualityTesters) {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: angular.equals(actual, expected)
+ };
+ }
+ };
+ }
+ });
+ });
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ beforeEach(module('stateMock'));
+
+ //Mock Users Service
+ beforeEach(module(function($provide) {
+ $provide.service('User', function($q) {
+ return {
+ getCurrent: function() {
+ var deferred = $q.defer();
+ deferred.resolve( JSON.stringify(sampleUser) );
+ return deferred.promise;
+ },
+ login: function(credentials) {
+ var deferred = $q.defer();
+ if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){
+ deferred.resolve( JSON.stringify(sampleUser) );
+ }else {
+ deferred.resolve('Error: User could not be loggedin');
+ }
+
+ return deferred.promise;
+ },
+ logout: function() {
+ var deferred = $q.defer();
+ deferred.resolve(null);
+ return deferred.promise;
+ },
+ signup: function(credentials) {
+ var deferred = $q.defer();
+ if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){
+ deferred.resolve( JSON.stringify(sampleUser) );
+ }else {
+ deferred.resolve('Error: User could not be signed up');
+ }
+
+ return deferred.promise;
+ }
+ };
+ });
+ }));
+
+ //Mock Authentication Service
+ beforeEach(module(function($provide) {
+ $provide.service('Auth', function() {
+ return {
+ ensureHasCurrentUser: function() {
+ return sampleUser;
+ },
+ isAuthenticated: function() {
+ return true;
+ },
+ getUserState: function() {
+ return true;
+ }
+ };
+ });
+ }));
+
+
+ //Mock $uibModal
+ beforeEach(inject(function($uibModal) {
+ var modal = newFakeModal();
+ spyOn($uibModal, 'open').and.returnValue(modal);
+ //spyOn($uibModal, 'close').and.callFake(modal.close());
+ }));
+
+ // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
+ // This allows us to inject a service but then attach it to a variable
+ // with the same name as the service.
+ beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) {
+ // Set a new global scope
+ scope = $rootScope.$new();
+
+ //Set CurrentForm
+ CurrentForm.setForm(sampleForm);
+
+ // Point global variables to injected services
+ $stateParams = _$stateParams_;
+ $httpBackend = _$httpBackend_;
+ $location = _$location_;
+ $state = _$state_;
+
+ $httpBackend.whenGET(/\.html$/).respond('');
+ $httpBackend.whenGET('/users/me/').respond('');
+
+ // Initialize the Forms controller.
+ createAdminFormController = function(){
+ return $controller('AdminFormController', { $scope: scope });
+ };
+ }));
+
+ it('AdminFormController should fetch current Form when instantiated', function() {
+ // Run controller functionality
+ var controller = createAdminFormController();
+
+ // Test scope value
+ expect(scope.myform).toEqualData(sampleForm);
+ });
+
+ it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', function() {
+ var controller = createAdminFormController();
+
+ //Set $state transition
+ $state.expectTransitionTo('listForms');
+
+ // Set DELETE response
+ $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
+
+ //Run controller functionality
+ scope.openDeleteModal();
+ scope.removeCurrentForm();
+
+ $httpBackend.flush();
+ $state.ensureAllTransitionsHappened();
+ });
+
+ it('$scope.update() should send a PUT request with the id of form', function() {
+ var controller = createAdminFormController();
+
+ //Set PUT response
+ $httpBackend.expect('PUT', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
+
+ //Run controller functionality
+ scope.update(false, null);
+
+ $httpBackend.flush();
+ });
+
+ it('$scope.openDeleteModal() should open scope.deleteModal', function() {
+ var controller = createAdminFormController();
+
+ //Run controller functionality
+ scope.openDeleteModal();
+ console.log(scope.deleteModal);
+ expect(scope.deleteModal.opened).toEqual(true);
+ });
+
+ it('$scope.cancelDeleteModal() should close $scope.deleteModal', inject(function($uibModal) {
+ var controller = createAdminFormController();
+
+ //Run controller functionality
+ scope.openDeleteModal();
+
+ //Run controller functionality
+ scope.cancelDeleteModal();
+ expect( scope.deleteModal.opened ).toEqual(false);
+ }));
+ });
+}());
\ No newline at end of file
diff --git a/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js b/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js
new file mode 100644
index 00000000..c89e73cf
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js
@@ -0,0 +1,224 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('ListForms Controller Tests', function() {
+ // Initialize global variables
+ var ListFormsController,
+ createListFormsController,
+ scope,
+ $httpBackend,
+ $stateParams,
+ $location,
+ $state;
+
+ var sampleForm = {
+ title: 'Form Title',
+ admin: 'ed873933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
+ ],
+ _id: '525a8422f6d0f87f0e407a33'
+ };
+
+ var sampleFormList = [{
+ title: 'Form Title1',
+ admin: 'ed873933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
+ ],
+ _id: '525a8422f6d0f87f0e407a33'
+ },{
+ title: 'Form Title2',
+ admin: '39223933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
+ ],
+ _id: '52f6d0f87f5a407a384220e3'
+ },{
+ title: 'Form Title3',
+ admin: '2fab9ed873937f0e1dea0ce1',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
+ ],
+ _id: '922f6d0f87fed8730e4e1233'
+ }
+ ];
+
+
+ // The $resource service augments the response object with methods for updating and deleting the resource.
+ // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
+ // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
+ // When the toEqualData matcher compares two objects, it takes only object properties into
+ // account and ignores methods.
+ beforeEach(function() {
+ jasmine.addMatchers({
+ toEqualData: function(util, customEqualityTesters) {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: angular.equals(actual, expected)
+ };
+ }
+ };
+ }
+ });
+ });
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ beforeEach(module('stateMock'));
+
+ //Mock Users Service
+ beforeEach(module(function($provide) {
+ $provide.service('myForm', function($q) {
+ return sampleForm;
+ });
+ }));
+
+
+ // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
+ // This allows us to inject a service but then attach it to a variable
+ // with the same name as the service.
+ beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) {
+ // Set a new global scope
+ scope = $rootScope.$new();
+
+ //Set CurrentForm
+ CurrentForm.setForm(sampleForm);
+
+ // Point global variables to injected services
+ $stateParams = _$stateParams_;
+ $httpBackend = _$httpBackend_;
+ $location = _$location_;
+ $state = _$state_;
+
+ $httpBackend.whenGET(/\.html$/).respond('');
+ $httpBackend.whenGET('/users/me/').respond('');
+
+ // Initialize the Forms controller.
+ createListFormsController = function(){
+ return $controller('ListFormsController', { $scope: scope });
+ };
+ }));
+
+ it('$scope.findAll() should query all User\'s Forms', inject(function(Forms) {
+
+ var controller = createListFormsController();
+
+ // Set GET response
+ $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
+
+ // Run controller functionality
+ scope.findAll();
+ $httpBackend.flush();
+
+ // Test scope value
+ expect( scope.myforms ).toEqualData(sampleFormList);
+ }));
+
+ it('$scope.duplicateForm() should duplicate a Form', inject(function(Forms) {
+
+ var dupSampleForm = sampleFormList[2],
+ dupSampleForm_index = 3,
+ newSampleFormList = _.clone(sampleFormList);
+ dupSampleForm._id = 'a02df75b44c1d26b6a5e05b8';
+ newSampleFormList.splice(3, 0, dupSampleForm);
+
+ var controller = createListFormsController();
+
+ // Set GET response
+ $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
+ // Run controller functionality
+ scope.findAll();
+ $httpBackend.flush();
+
+ // Set GET response
+ $httpBackend.expect('POST', '/forms').respond(200, dupSampleForm);
+ // Run controller functionality
+ scope.duplicateForm(2);
+ $httpBackend.flush();
+
+ // Test scope value
+ expect( scope.myforms.length ).toEqual(newSampleFormList.length);
+ for(var i=0; i');
+ $compile(el)(tmp_scope);
+ $rootScope.$digest();
+
+ // Point global variables to injected services
+ $httpBackend = _$httpBackend_;
+
+ // $httpBackend.whenGET(/.+\.html$/).respond('');
+ $httpBackend.whenGET('/users/me/').respond('');
+
+ //Grab controller instance
+ controller = el.controller();
+
+ //Grab scope. Depends on type of scope.
+ //See angular.element documentation.
+ scope = el.isolateScope() || el.scope();
+
+ }));
+
+ it('$scope.uploadPDF() should upload a pdf file', function() {
+ // expect(scope.isInitialized).toBeDefined()
+ // expect(scope.log).toEqual('');
+
+ expect(scope.pdfLoading).toBe(false);
+
+ //Set POST response
+ $httpBackend.when('POST', '/upload/pdf').respond(pdfObj);
+
+ var files = [{}];
+ scope.uploadPDF(files);
+
+ $httpBackend.flush();
+ expect(scope.myform.pdf).toEqualData(pdfObj);
+ });
+
+ it('$scope.removePDF() should removed uploaded pdf file', function() {
+ // expect(scope.isInitialized).toBeDefined()
+ // expect(scope.log).toEqual('');
+
+ scope.myform.pdf = pdfObj;
+ scope.myform.isGenerated = true;
+ scope.myform.autofillPDFs = true;
+
+ scope.removePDF();
+
+ expect(scope.myform.pdf).toEqual(null);
+ expect(scope.myform.isGenerated).toBe(false);
+ expect(scope.myform.autofillPDFs).toBe(false);
+ });
+ });
+}());
diff --git a/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js
new file mode 100644
index 00000000..63520f48
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js
@@ -0,0 +1,200 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('EditSubmissions Directive-Controller Tests', function() {
+ // Initialize global variables
+ var el, scope, controller, $httpBackend;
+
+ var sampleUser = {
+ firstName: 'Full',
+ lastName: 'Name',
+ email: 'test@test.com',
+ username: 'test@test.com',
+ password: 'password',
+ provider: 'local',
+ roles: ['user'],
+ _id: 'ed873933b1f1dea0ce12fab9'
+ };
+
+ var pdfObj = {
+ fieldname:'file',
+ originalname:'test.pdf',
+ name:'1440112660375.pdf',
+ encoding:'7bit',
+ mimetype:'application/pdf',
+ path:'uploads/tmp/test@test.com/1440112660375.pdf',
+ extension:'pdf',
+ size:56223,
+ truncated:false,
+ buffer:null
+ };
+
+ var sampleForm = {
+ title: 'Form Title',
+ admin: 'ed873933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'},
+ {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
+ {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
+ ],
+ pdf: {},
+ pdfFieldMap: {},
+ startPage: {
+ showStart: false
+ },
+ hideFooter: false,
+ isGenerated: false,
+ isLive: false,
+ autofillPDFs: false,
+ _id: '525a8422f6d0f87f0e407a33'
+ };
+
+ var sampleSubmission = {
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: 0, deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 17.55
+ };
+
+ var sampleSubmissions = [{
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'The Terminator', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: 0, deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: 1, deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 10.33
+ },
+ {
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: 0, deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 2.33
+ },
+ {
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'Jane Doe', deletePreserved: false},
+ {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false},
+ {fieldType:'checkbox', title:'hockey', fieldValue: 1, deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 11.11
+ }];
+
+ // The $resource service augments the response object with methods for updating and deleting the resource.
+ // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
+ // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
+ // When the toEqualData matcher compares two objects, it takes only object properties into
+ // account and ignores methods.
+ beforeEach(function() {
+ jasmine.addMatchers({
+ toEqualData: function(util, customEqualityTesters) {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: angular.equals(actual, expected)
+ };
+ }
+ };
+ }
+ });
+ });
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+ beforeEach(module('module-templates'));
+ beforeEach(module('stateMock'));
+
+ beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) {
+
+ // Point global variables to injected services
+ $httpBackend = _$httpBackend_;
+
+ $httpBackend.whenGET('/users/me/').respond('');
+ $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions);
+
+ //Instantiate directive.
+ var tmp_scope = $rootScope.$new();
+ tmp_scope.myform = sampleForm;
+ tmp_scope.user = sampleUser;
+
+ //gotacha: Controller and link functions will execute.
+ el = angular.element('');
+ $compile(el)(tmp_scope);
+ $rootScope.$digest();
+
+ //Grab controller instance
+ controller = el.controller();
+
+ //Grab scope. Depends on type of scope.
+ //See angular.element documentation.
+ scope = el.isolateScope() || el.scope();
+ }));
+
+ it('$scope.initFormSubmissions() should fetch all relevant form submissions', function() {
+ $httpBackend.expectGET(/^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions);
+ scope.initFormSubmissions();
+ $httpBackend.flush();
+ scope.$digest();
+ });
+
+ describe('Form Table Methods', function(){
+
+ it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){
+ scope.initFormSubmissions();
+ $httpBackend.flush();
+
+ //Run Controller Logic to Test
+ scope.table.masterChecker = true;
+ scope.toggleAllCheckers();
+
+ for(var i=0; i');
+ $compile(el)(tmp_scope);
+ $rootScope.$digest();
+
+ // Point global variables to injected services
+ $httpBackend = _$httpBackend_;
+
+ //$httpBackend.whenGET(/.+\.html$/).respond('');
+ $httpBackend.whenGET('/users/me/').respond('');
+
+ //Grab controller instance
+ controller = el.controller();
+
+ //Grab scope. Depends on type of scope.
+ //See angular.element documentation.
+ scope = el.isolateScope() || el.scope();
+
+ }));
+
+ describe('> Form Field >',function(){
+
+ beforeEach(function(){
+ scope.myform = _.cloneDeep(sampleForm);
+ });
+
+ it('$scope.addNewField() should ADD a new field to $scope.myform.form_fields', function() {
+
+ //Run controller methods
+ scope.addNewField(true, 'textfield');
+
+ var expectedFormField = {
+ title:'Short Text2',
+ fieldType:'textfield',
+ fieldValue: '',
+ required: true,
+ disabled: false,
+ deletePreserved: false
+ };
+
+ var actualFormField = _.cloneDeep(_.last(scope.myform.form_fields));
+ delete actualFormField._id;
+
+ expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1);
+ expect(actualFormField).toEqualData(expectedFormField);
+ });
+
+ it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() {
+
+ //Run controller methods
+ scope.deleteField(0);
+
+ expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length-1);
+ expect(_.first(scope.myform.form_fields)).toEqualData(sampleForm.form_fields[1]);
+ });
+
+ it('$scope.duplicateField() should DUPLICATE a field and update $scope.myform.form_fields', function() {
+
+ //Run controller methods
+ scope.duplicateField(0);
+
+ var originalField = _.cloneDeep(scope.myform.form_fields[0]);
+ originalField.title += ' copy';
+
+ delete originalField._id;
+ var copyField = _.cloneDeep(scope.myform.form_fields[1]);
+ delete copyField._id;
+
+ expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1);
+ expect(originalField).toEqualData(copyField);
+ });
+
+ });
+
+ describe('> Form Field Button >',function(){
+
+ it('$scope.addButton() should ADD a button to $scope.myform.startPage.buttons', function() {
+
+ var expectedStartPageBtn = {
+ bgColor:'#ddd',
+ color:'#ffffff',
+ text: 'Button'
+ };
+
+ //Run controller methods
+ scope.addButton();
+ var actualStartPageBtn = _.cloneDeep(_.last(scope.myform.startPage.buttons));
+ delete actualStartPageBtn._id;
+
+ expect(scope.myform.startPage.buttons.length).toEqual(sampleForm.startPage.buttons.length+1);
+ expect(actualStartPageBtn).toEqualData(expectedStartPageBtn);
+ });
+
+ it('$scope.deleteButton() should DELETE a button from $scope.myform.startPage.buttons', function() {
+ //Run controller methods
+ scope.deleteButton(scope.myform.startPage.buttons[0]);
+
+ expect(scope.myform.startPage.buttons.length).toEqual(0);
+ });
+ });
+
+ describe('> Form Field Option >',function(){
+ it('$scope.addOption() should ADD a new option to a field.fieldOptions', function() {
+ var originalOptionLen = scope.myform.form_fields[1].fieldOptions.length;
+
+ //Run controller methods
+ scope.addOption(1);
+
+ expect(originalOptionLen+1).toEqual(scope.myform.form_fields[1].fieldOptions.length);
+ expect(scope.myform.form_fields[1].fieldOptions[0].option_title).toEqualData('Option 0');
+ expect(scope.myform.form_fields[1].fieldOptions[0].option_value).toEqualData('Option 0');
+ });
+
+ it('$scope.deleteOption() should DELETE remove option from field.fieldOptions', function() {
+ //Run controller methods
+ scope.deleteOption(1, scope.myform.form_fields[1].fieldOptions[0]);
+
+ expect(scope.myform.form_fields[0].fieldOptions.length).toEqual(0);
+ expect(scope.myform.form_fields[0].fieldOptions[0]).not.toBeDefined();
+ });
+ });
+ });
+}());
\ No newline at end of file
diff --git a/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js
new file mode 100644
index 00000000..e24cfc48
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js
@@ -0,0 +1,93 @@
+// 'use strict';
+
+// (function() {
+// // Forms Controller Spec
+// describe('entryPage Directive Tests', function() {
+// // Initialize global variables
+// var scope,
+// $templateCache,
+// $httpBackend,
+// $compile;
+
+// var sampleStartPage = {
+// showStart: true,
+// introTitle: 'Welcome to Form',
+// introParagraph: 'Sample intro paragraph',
+// buttons:[
+// {
+// url: 'http://google.com',
+// action: '',
+// text: 'Google',
+// bgColor: '#ffffff',
+// color: '#000000',
+// },
+// {
+// url: 'http://facebook.com',
+// action: '',
+// text: 'Facebook',
+// bgColor: '#0000ff',
+// color: '#000000',
+// }
+// ]
+// };
+
+
+// // The $resource service augments the response object with methods for updating and deleting the resource.
+// // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
+// // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
+// // When the toEqualData matcher compares two objects, it takes only object properties into
+// // account and ignores methods.
+// beforeEach(function() {
+// jasmine.addMatchers({
+// toEqualData: function(util, customEqualityTesters) {
+// return {
+// compare: function(actual, expected) {
+// return {
+// pass: angular.equals(actual, expected)
+// };
+// }
+// };
+// }
+// });
+// });
+
+// // Load the main application module
+// beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+// beforeEach(inject(function($rootScope, _$compile_, _$httpBackend_) {
+// scope = $rootScope.$new();
+// $compile = _$compile_;
+
+// // Point global variables to injected services
+// $httpBackend = _$httpBackend_;
+// }));
+
+
+// it('should be able to render entryPage in html', function() {
+// scope.myStartPage = _.cloneDeep(sampleStartPage);
+// console.log(scope.myStartPage);
+// var element = angular.element('');
+// $compile(element)(scope);
+// scope.$digest();
+
+// // console.log(element.html());
+// expect(element.html()).not.toEqual('
Start Page
');
+// });
+
+// // it('exitStartPage should work for "startPage" type of entryPage', inject(function($rootScope) {
+// // scope.myPage = _.cloneDeep(sampleStartPage);
+// // var el = angular.element('');
+// // $compile(el)(scope);
+// // scope.$digest();
+
+// // $httpBackend.whenGET(/.+\.html$/).respond('');
+// // $httpBackend.whenGET('/users/me/').respond('');
+
+// // scope = el.isolateScope() || el.scope();
+
+// // scope.exitStartPage();
+// // // expect(scope.myStartPage.showStart).toBe(false);
+// // expect(el.html()).not.toEqual('
Start Page
');
+// // }));
+// });
+// }());
\ No newline at end of file
diff --git a/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js
new file mode 100644
index 00000000..32784832
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js
@@ -0,0 +1,54 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('FieldIcon Directive Tests', function() {
+ // Initialize global variables
+ var scope,
+ FormFields,
+ faClasses = {
+ 'textfield': 'fa fa-pencil-square-o',
+ 'dropdown': 'fa fa-th-list',
+ 'date': 'fa fa-calendar',
+ 'checkbox': 'fa fa-check-square-o',
+ 'radio': 'fa fa-dot-circle-o',
+ 'email': 'fa fa-envelope-o',
+ 'textarea': 'fa fa-pencil-square',
+ 'legal': 'fa fa-legal',
+ 'file': 'fa fa-cloud-upload',
+ 'rating': 'fa fa-star-half-o',
+ 'link': 'fa fa-link',
+ 'scale': 'fa fa-sliders',
+ 'stripe': 'fa fa-credit-card',
+ 'statement': 'fa fa-quote-left',
+ 'yes_no': 'fa fa-toggle-on',
+ 'number': 'fa fa-slack'
+ };
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ beforeEach(inject(function ($rootScope, _FormFields_) {
+ scope = $rootScope.$new();
+ FormFields = _FormFields_;
+ }));
+
+ it('should be able render all field-icon types', inject(function($compile) {
+ var currType, currClass;
+
+ for(var i=0; i')(scope);
+ scope.$digest();
+
+ expect(currClass).toBeDefined();
+
+ expect(element.find('i')).not.toBe(null);
+ expect(element.find('i').hasClass(currClass)).toBe(true);
+ }
+
+ }));
+ });
+}());
\ No newline at end of file
diff --git a/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js
new file mode 100644
index 00000000..b5527d62
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js
@@ -0,0 +1,97 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('Field Directive Tests', function() {
+ // Initialize global variables
+ var scope,
+ FormFields,
+ $templateCache,
+ $httpBackend,
+ $compile;
+
+ var sampleUser = {
+ firstName: 'Full',
+ lastName: 'Name',
+ email: 'test@test.com',
+ username: 'test@test.com',
+ password: 'password',
+ provider: 'local',
+ roles: ['user'],
+ _id: 'ed873933b1f1dea0ce12fab9',
+ };
+
+ var sampleFields = [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'AoeuName', deletePreserved: false, required: true, disabled: false},
+ {fieldType:'email', title:'Email', fieldValue: 'aoeu@aoeu.com', deletePreserved: false, required: true, disabled: false},
+ {fieldType:'yes_no', title:'Do you Play Hockey?', fieldValue: 'true', deletePreserved: false, required: true, disabled: false},
+ {fieldType:'url', title:'Github Account', fieldValue: 'http://github.com/aoeu', deletePreserved: false, required: true, disabled: false},
+ {fieldType:'textarea', title:'Bio', fieldValue: 'This is my bio.', deletePreserved: false, required: true, disabled: false},
+ {fieldType:'number', title:'Phone #', fieldValue: 5325325325, deletePreserved: false, required: true, disabled: false},
+ {fieldType:'legal', title:'You agree to terms and conditions', description:'By selecting \'I agree\' you are agreeing under Canadian law that you have read and accept terms and conditions outlayed below', fieldValue: '', deletePreserved: false, required: true, disabled: false},
+ {fieldType:'dropdown', title:'Your Sex', fieldValue: '', fieldOptions:[ { 'option_id': 0, 'option_title': 'M', 'option_value': 'male' }, { 'option_id': 1, 'option_title': 'F', 'option_value': 'female' }], deletePreserved: false, required: true, disabled: false},
+ {fieldType:'radio', title:'Your Sexual Orientation', fieldValue: '', fieldOptions:[ { 'option_id': 0, 'option_title': 'Heterosexual', 'option_value': 'hetero' }, { 'option_id': 1, 'option_title': 'Homosexual', 'option_value': 'homo' }, { 'option_id': 2, 'option_title': 'Bisexual', 'option_value': 'bi' }, { 'option_id': 3, 'option_title': 'Asexual', 'option_value': 'asex' }], deletePreserved: false, required: true, disabled: false},
+ {fieldType:'rating', title:'Your Current Happiness', fieldValue: '0', deletePreserved: false, required: true, disabled: false},
+ ];
+
+
+ // The $resource service augments the response object with methods for updating and deleting the resource.
+ // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
+ // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
+ // When the toEqualData matcher compares two objects, it takes only object properties into
+ // account and ignores methods.
+ beforeEach(function() {
+ jasmine.addMatchers({
+ toEqualData: function(util, customEqualityTesters) {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: angular.equals(actual, expected)
+ };
+ }
+ };
+ }
+ });
+ });
+
+ beforeEach(module(function ($sceProvider) {
+ $sceProvider.enabled(false);
+ }));
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+ beforeEach(module('stateMock'));
+ beforeEach(module('module-templates'));
+
+ beforeEach(module('ngSanitize', 'ui.select'));
+
+ beforeEach(inject(function($rootScope, _FormFields_, _$compile_) {
+ scope = $rootScope.$new();
+ FormFields = _FormFields_;
+
+ $compile = _$compile_;
+ }));
+
+ it('should be able to render all field types in html', inject(function($rootScope) {
+ scope.fields = sampleFields;
+
+ for(var i=0; i');
+ $compile(element)(scope);
+ scope.$digest();
+
+ console.log('Actual: ');
+ console.log(element.html());
+
+ console.log('\nExpected: ');
+
+ console.log('
'+field.title+'
');
+ }
+ }));
+ });
+}());
diff --git a/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js
new file mode 100644
index 00000000..274f4acb
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js
@@ -0,0 +1,46 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('onFinishRender Directive Tests', function() {
+ // Initialize global variables
+ var scope,
+ FormFields;
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ beforeEach(inject(function ($rootScope, _FormFields_) {
+ scope = $rootScope.$new();
+ FormFields = _FormFields_;
+ spyOn($rootScope, '$broadcast');
+
+ }));
+
+ it('should emit Custom "Finished" and "Started" events on ng-repeat', inject(function($compile, $rootScope) {
+
+ scope.myfields = FormFields.types;
+
+ var e = $compile('
{{item.name}}
')(scope);
+ scope.$digest();
+
+ //run code to test
+ expect($rootScope.$broadcast).toHaveBeenCalledWith('editFormFields Started');
+ expect(scope.$broadcast).toHaveBeenCalledWith('editFormFields Finished');
+ }));
+
+ it('should emit "ngRepeat Finished" and "ngRepeat Started" events on ng-repeat when attr is not set to string', inject(function($compile, $rootScope) {
+
+ // console.log(FormFields.types);
+ scope.myfields = FormFields.types;
+
+ var e = $compile('
{{item.name}}
')(scope);
+ scope.$digest();
+
+ //run code to test
+ expect($rootScope.$broadcast).toHaveBeenCalledWith('ngRepeat Started');
+ expect(scope.$broadcast).toHaveBeenCalledWith('ngRepeat Finished');
+ }));
+
+ });
+}());
\ No newline at end of file
diff --git a/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js
new file mode 100644
index 00000000..db46b05c
--- /dev/null
+++ b/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js
@@ -0,0 +1,203 @@
+'use strict';
+
+(function() {
+ // Forms Controller Spec
+ describe('SubmitForm Directive-Controller Tests', function() {
+ // Initialize global variables
+ var scope, controller, $httpBackend;
+
+ var sampleUser = {
+ firstName: 'Full',
+ lastName: 'Name',
+ email: 'test@test.com',
+ username: 'test@test.com',
+ password: 'password',
+ provider: 'local',
+ roles: ['user'],
+ _id: 'ed873933b1f1dea0ce12fab9'
+ };
+
+ var pdfObj = {
+ fieldname:'file',
+ originalname:'test.pdf',
+ name:'1440112660375.pdf',
+ encoding:'7bit',
+ mimetype:'application/pdf',
+ path:'uploads/tmp/test@test.com/1440112660375.pdf',
+ extension:'pdf',
+ size:56223,
+ truncated:false,
+ buffer:null
+ };
+
+ var sampleForm = {
+ title: 'Form Title',
+ admin: 'ed873933b1f1dea0ce12fab9',
+ language: 'english',
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'},
+ {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
+ {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ],
+ visible_form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'},
+ {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
+ {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ],
+ pdf: {},
+ pdfFieldMap: {},
+ startPage: {
+ showStart: false
+ },
+ hideFooter: false,
+ isGenerated: false,
+ isLive: false,
+ autofillPDFs: false,
+ _id: '525a8422f6d0f87f0e407a33'
+ };
+
+ var sampleSubmission = {
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'},
+ {fieldType:'yes_no', title:'Do you like nascar', fieldValue: true, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
+ {fieldType:'yes_no', title:'Do you like hockey', fieldValue: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 17.55
+ };
+
+ var sampleSubmissions = [{
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'The Terminator', deletePreserved: false},
+ {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'true', deletePreserved: false},
+ {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'false', deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 10.33
+ },
+ {
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false},
+ {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'true', deletePreserved: false},
+ {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'true', deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 2.33
+ },
+ {
+ form_fields: [
+ {fieldType:'textfield', title:'First Name', fieldValue: 'Jane Doe', deletePreserved: false},
+ {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'false', deletePreserved: false},
+ {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'false', deletePreserved: false}
+ ],
+ admin: sampleUser,
+ form: sampleForm,
+ timeElapsed: 11.11
+ }];
+
+ // The $resource service augments the response object with methods for updating and deleting the resource.
+ // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
+ // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
+ // When the toEqualData matcher compares two objects, it takes only object properties into
+ // account and ignores methods.
+ beforeEach(function() {
+ jasmine.addMatchers({
+ toEqualData: function(util, customEqualityTesters) {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: angular.equals(actual, expected)
+ };
+ }
+ };
+ }
+ });
+ });
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+ beforeEach(module('module-templates'));
+ beforeEach(module('stateMock'));
+
+ beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) {
+
+ // Point global variables to injected services
+ $httpBackend = _$httpBackend_;
+ $httpBackend.whenGET('/users/me/').respond('');
+
+ //Instantiate directive.
+ var tmp_scope = $rootScope.$new();
+ tmp_scope.myform = sampleForm;
+
+ //gotacha: Controller and link functions will execute.
+ var el = angular.element('');
+ $compile(el)(tmp_scope);
+ tmp_scope.$digest();
+ $rootScope.$digest();
+
+ //Grab controller instance
+ controller = el.controller();
+
+ //Grab scope. Depends on type of scope.
+ //See angular.element documentation.
+ scope = el.isolateScope() || el.scope();
+
+ console.log(scope);
+ }));
+
+ var Validator = (function() {
+ return {
+ hasMinimumFields: function(entry) {
+ return !_.isEmpty(entry._id) && !_.isEmpty(entry.title);
+ },
+ isNewForm: function(entry) {
+ return this.hasMinimumFields(entry);
+ }
+ };
+ })();
+
+
+ it('$scope.submitForm() should submit valid form', function(){
+ //Initialize variables
+ scope.myform.form_fields = sampleSubmissions[0].form_fields;
+
+ var expectedForm = _.cloneDeep(sampleForm);
+ expectedForm.form_fields = sampleSubmissions[0].form_fields;
+ delete expectedForm.visible_form_fields;
+
+ var data = function(data) {
+ var form = angular.fromJson(data);
+ var compareForm = _.cloneDeep(form);
+ delete compareForm.timeElapsed;
+ delete compareForm.percentageComplete;
+
+ return Validator.isNewForm(form) && _.isEqual(compareForm, expectedForm);
+ };
+
+ //Set expected HTTP requests
+ $httpBackend.expect('POST',/^(\/forms\/)([0-9a-fA-F]{24})$/, data).respond(200);
+
+ //Run Controller Logic to Test
+ scope.submitForm();
+
+ $httpBackend.flush();
+
+ setTimeout(function(){
+ expect(scope.myform.submitted).toBe(true);
+ expect(scope.error).toEqual('');
+ }, 25);
+ });
+
+ it('$scope.reloadForm() should reset and reload form', function(){
+ scope.submitForm();
+ scope.reloadForm();
+
+ expect(scope.myform.submitted).toBe(false);
+ for(var i=0; i 0){
+ var expectedState = this.expectedTransitions.shift();
+ if(expectedState !== stateName){
+ throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName );
+ }
+ }else{
+ throw Error('No more transitions were expected! Tried to transition to '+ stateName );
+ }
+ console.log('Mock transition to: ' + stateName);
+ var deferred = $q.defer();
+ var promise = deferred.promise;
+ deferred.resolve();
+ return promise;
+ };
+
+ this.go = this.transitionTo;
+ this.expectTransitionTo = function(stateName){
+ this.expectedTransitions.push(stateName);
+ };
+
+ this.ensureAllTransitionsHappened = function(){
+ if(this.expectedTransitions.length > 0){
+ throw Error('Not all transitions happened!');
+ }
+ };
+});
\ No newline at end of file
diff --git a/public/humans.txt b/public/humans.txt
index 08d7ac69..3600069a 100755
--- a/public/humans.txt
+++ b/public/humans.txt
@@ -4,13 +4,13 @@
# TEAM
David Baldwynn -- Developer -- @davidbaldwynn
+ Samuel Laulhau -- Developer -- @_samuel_
# THANKS
Grace Lam
-
# TECHNOLOGY COLOPHON
HTML5, CSS3, AngularJS
- jQuery, Modernizr, ExpressJS
+ jQuery, ExpressJS, NodeJS
diff --git a/public/modules/core/config/core.client.routes.js b/public/modules/core/config/core.client.routes.js
index 765db68f..149b3900 100755
--- a/public/modules/core/config/core.client.routes.js
+++ b/public/modules/core/config/core.client.routes.js
@@ -7,3 +7,61 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
$urlRouterProvider.otherwise('/forms');
}
]);
+
+angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', '$state', '$stateParams',
+ function($rootScope, Auth, $state, $stateParams) {
+
+ $rootScope.$state = $state;
+ $rootScope.$stateParams = $stateParams;
+
+ // add previous state property
+ $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState) {
+ $state.previous = fromState;
+ //console.log('toState: '+toState.name);
+
+ var statesToIgnore = ['home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
+
+ //Redirect to listForms if user is authenticated
+ if(statesToIgnore.indexOf(toState.name) > 0){
+ if(Auth.isAuthenticated()){
+ event.preventDefault(); // stop current execution
+ //console.log('go to forms');
+ $state.go('listForms'); // go to listForms page
+ }
+ }
+ //Redirect to 'signup' route if user is not authenticated
+ else if(toState.name !== 'access_denied' && !Auth.isAuthenticated() && toState.name !== 'submitForm'){
+ console.log('go to signup');
+ event.preventDefault(); // stop current execution
+ $state.go('listForms'); // go to listForms page
+ }
+
+ });
+
+ }
+]);
+
+//Page access/authorization logic
+angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'User', 'Authorizer', '$state', '$stateParams',
+ function($rootScope, Auth, User, Authorizer, $state, $stateParams) {
+ $rootScope.$on('$stateChangeStart', function(event, next) {
+ var authenticator, permissions, user;
+ permissions = next && next.data && next.data.permissions ? next.data.permissions : null;
+
+ Auth.ensureHasCurrentUser(User);
+ user = Auth.currentUser;
+
+ if(user){
+ authenticator = new Authorizer(user);
+ //console.log('access denied: '+!authenticator.canAccess(permissions));
+ //console.log(permissions);
+ if( (permissions != null) ){
+ if( !authenticator.canAccess(permissions) ){
+ event.preventDefault();
+ //console.log('access denied');
+ $state.go('access_denied');
+ }
+ }
+ }
+ });
+ }]);
diff --git a/public/modules/core/services/subdomain.client.service.js b/public/modules/core/services/subdomain.client.service.js
new file mode 100644
index 00000000..eaa9c90b
--- /dev/null
+++ b/public/modules/core/services/subdomain.client.service.js
@@ -0,0 +1,9 @@
+'use strict';
+
+angular.module('core').factory('subdomain', ['$location', function ($location) {
+ var host = $location.host();
+ if (host.indexOf('.') < 0)
+ return null;
+ else
+ return host.split('.')[0];
+}]);
diff --git a/public/modules/users/controllers/authentication.client.controller.js b/public/modules/users/controllers/authentication.client.controller.js
index eaede4b1..f075f817 100755
--- a/public/modules/users/controllers/authentication.client.controller.js
+++ b/public/modules/users/controllers/authentication.client.controller.js
@@ -8,7 +8,6 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
$scope.error = '';
$scope.signin = function() {
- $scope.credentials.email = $scope.credentials.username;
User.login($scope.credentials).then(
function(response) {
Auth.login(response);
diff --git a/public/modules/users/views/authentication/signup.client.view.html b/public/modules/users/views/authentication/signup.client.view.html
index 5989d4fb..f7e5b8e8 100644
--- a/public/modules/users/views/authentication/signup.client.view.html
+++ b/public/modules/users/views/authentication/signup.client.view.html
@@ -48,6 +48,10 @@
+