diff --git a/app/controllers/forms.server.controller.js b/app/controllers/forms.server.controller.js index 005841bb..c511bcf0 100644 --- a/app/controllers/forms.server.controller.js +++ b/app/controllers/forms.server.controller.js @@ -88,17 +88,15 @@ exports.listSubmissions = function(req, res) { } res.json(_submissions); }); - }; /** * Create a new form */ exports.create = function(req, res) { - debugger; - + if(!req.body.form){ - return res.status(401).send({ + return res.status(400).send({ message: 'Invalid Input' }); } diff --git a/app/models/form.server.model.js b/app/models/form.server.model.js index b20d1a9e..f9139f26 100644 --- a/app/models/form.server.model.js +++ b/app/models/form.server.model.js @@ -71,7 +71,6 @@ var VisitorDataSchema = new Schema({ userAgent: { type: String } - }); var formSchemaOptions = { @@ -219,7 +218,7 @@ FormSchema.virtual('analytics.fields').get(function () { var visitors = this.analytics.visitors; var that = this; - if(this.form_fields.length === 0) { + if(!this.form_fields || this.form_fields.length === 0) { return null; } diff --git a/app/tests/form.server.routes.test.js b/app/tests/form.server.routes.test.js index e1aa2654..93c5e6cb 100644 --- a/app/tests/form.server.routes.test.js +++ b/app/tests/form.server.routes.test.js @@ -9,7 +9,8 @@ var should = require('should'), User = mongoose.model('User'), Form = mongoose.model('Form'), Field = mongoose.model('Field'), - FormSubmission = mongoose.model('FormSubmission'); + FormSubmission = mongoose.model('FormSubmission'), + async = require('async'); /** * Globals @@ -68,7 +69,7 @@ describe('Form Routes Unit tests', function() { .send({form: myForm}) .expect(401) .end(function(FormSaveErr, FormSaveRes) { - + console.log(FormSaveRes.text); // Call the assertion callback done(FormSaveErr); }); @@ -83,7 +84,7 @@ describe('Form Routes Unit tests', function() { }); }); - it(' > should be able to read/get a Form if not signed in', function(done) { + it(' > should be able to read/get a live Form if not signed in', function(done) { // Create new Form model instance var FormObj = new Form(myForm); @@ -105,6 +106,23 @@ describe('Form Routes Unit tests', function() { }); }); + it(' > should be able to read/get a non-live Form if not signed in', function(done) { + // Create new Form model instance + var FormObj = new Form(myForm); + FormObj.isLive = false; + + // Save the Form + FormObj.save(function(err, form) { + if(err) return done(err); + + userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render') + .expect(401, {message: 'Form is Not Public'}) + .end(function(err, res) { + done(err); + }); + }); + }); + it(' > should not be able to delete an Form if not signed in', function(done) { // Set Form user myForm.admin = user; @@ -146,6 +164,16 @@ describe('Form Routes Unit tests', function() { }); }); + it(' > should not be able to create a Form if body is empty', function(done) { + loginSession.post('/forms') + .send({form: null}) + .expect(400, {"message":"Invalid Input"}) + .end(function(FormSaveErr, FormSaveRes) { + // Call the assertion callback + done(FormSaveErr); + }); + }); + it(' > should not be able to save a Form if no title is provided', function(done) { // Set Form with a invalid title field myForm.title = ''; @@ -165,10 +193,22 @@ describe('Form Routes Unit tests', function() { done(); }); - }); - it(' > should be able to update a Form if signed in', function(done) { + it(' > should be able to create a Form if form_fields are undefined', function(done) { + myForm.analytics = null; + myForm.form_fields = null; + + loginSession.post('/forms') + .send({form: myForm}) + .expect(200) + .end(function(FormSaveErr, FormSaveRes) { + // Call the assertion callback + done(FormSaveErr); + }); + }); + + it(' > should be able to update a Form if signed in and Form is valid', function(done) { // Save a new Form loginSession.post('/forms') @@ -182,7 +222,7 @@ describe('Form Routes Unit tests', function() { } // Update Form title - myForm.title = 'WHY YOU GOTTA BE SO MEAN?'; + myForm.title = 'WHY YOU GOTTA BE SO FORMULAIC?'; // Update an existing Form loginSession.put('/forms/' + FormSaveRes.body._id) @@ -197,13 +237,12 @@ describe('Form Routes Unit tests', function() { // Set assertions (FormUpdateRes.body._id).should.equal(FormSaveRes.body._id); - (FormUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?'); + (FormUpdateRes.body.title).should.match(myForm.title); // Call the assertion callback done(); }); }); - }); it(' > should be able to delete a Form if signed in', function(done) { @@ -238,10 +277,9 @@ describe('Form Routes Unit tests', function() { done(); }); }); - }); - it('should be able to save new form while logged in', function(done){ + it(' > should be able to save new form while logged in', function(done){ // Save a new Form authenticatedSession.post('/forms') .send({form: myForm}) @@ -271,12 +309,70 @@ describe('Form Routes Unit tests', function() { }); }); + it(' > should be able to get list of users\' forms sorted by date created while logged in', function(done) { + var myForm1 = { + title: 'First Form', + language: 'en', + admin: user.id, + form_fields: [ + new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), + new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), + new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) + ], + isLive: true + }; + + var myForm2 = { + title: 'Second Form', + language: 'en', + admin: user.id, + form_fields: [ + new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}), + new Field({'fieldType':'checkbox', 'title':'formula one', 'fieldValue': ''}), + new Field({'fieldType':'checkbox', 'title':'football', 'fieldValue': ''}) + ], + isLive: true + }; + + var FormObj1 = new Form(myForm1); + var FormObj2 = new Form(myForm2); + + async.waterfall([ + function(callback) { + FormObj1.save(function(err){ + callback(err); + }); + }, + function(callback) { + FormObj2.save(function(err){ + callback(err); + }); + }, + function(callback) { + loginSession.get('/forms') + .expect(200) + .end(function(err, res) { + res.body.length.should.equal(2); + res.body[0].title.should.equal('Second Form'); + res.body[1].title.should.equal('First Form'); + + // Call the assertion callback + callback(err); + }); + } + ], function (err) { + done(err); + }); + }); + afterEach('should be able to signout user', function(done){ authenticatedSession.get('/auth/signout') .expect(200) .end(function(signoutErr, signoutRes) { // Handle signout error - if (signoutErr) return done(signoutErr); + if (signoutErr) { + return done(signoutErr); + } authenticatedSession.destroy(); done(); }); diff --git a/app/tests/form_submission.model.test.js b/app/tests/form_submission.model.test.js index f60e599e..09442c81 100644 --- a/app/tests/form_submission.model.test.js +++ b/app/tests/form_submission.model.test.js @@ -199,6 +199,7 @@ describe('FormSubmission Model Unit Tests:', function() { it('should preserve deleted form_fields that have submissions without any problems', function(done) { + var fieldPropertiesToOmit = ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title']; var old_fields = myForm.toObject().form_fields; var new_form_fields = _.clone(myForm.toObject().form_fields); new_form_fields.splice(0, 1); @@ -210,8 +211,8 @@ describe('FormSubmission Model Unit Tests:', function() { should.not.exist(err); should.exist(_form.form_fields); - var actual_fields = _.deepOmit(_form.toObject().form_fields, ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId']); - old_fields = _.deepOmit(old_fields, ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId']); + var actual_fields = _.deepOmit(_form.toObject().form_fields, fieldPropertiesToOmit); + old_fields = _.deepOmit(old_fields, fieldPropertiesToOmit); should.deepEqual(actual_fields, old_fields, 'old form_fields not equal to newly saved form_fields'); done(); diff --git a/app/views/500.server.view.pug b/app/views/500.server.view.pug index 688a9af0..3c6fc1b1 100644 --- a/app/views/500.server.view.pug +++ b/app/views/500.server.view.pug @@ -5,7 +5,7 @@ block content div.row.valign h3.col-md-12.text-center=__('500_HEADER') div.col-md-4.col-md-offset-4 - if process.env.NODE_ENV == 'development' + if process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'test' div.col-md-12.text-center(style="padding-bottom: 50px;") | #{error} else diff --git a/config/express.js b/config/express.js index 1da3f4a1..902beb6f 100755 --- a/config/express.js +++ b/config/express.js @@ -148,8 +148,6 @@ module.exports = function(db) { // reassign url req.url = subdomainPath; - req.userId = user._id; - // Q.E.D. return next(); }); @@ -200,7 +198,7 @@ module.exports = function(db) { app.use(morgan(logger.getLogFormat(), logger.getMorganOptions())); // Environment dependent middleware - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { // Disable views cache app.set('view cache', false); } else if (process.env.NODE_ENV === 'production') { @@ -263,9 +261,13 @@ module.exports = function(db) { //Visitor Language Detection app.use(function(req, res, next) { var acceptLanguage = req.headers['accept-language']; - var languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || []; + var languages, supportedLanguage; + + if(acceptLanguage){ + languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || []; + supportedLanguage = containsAnySupportedLanguages(languages); + } - var supportedLanguage = containsAnySupportedLanguages(languages); if(!req.user && supportedLanguage !== null){ var currLanguage = res.cookie('userLang'); @@ -288,7 +290,7 @@ module.exports = function(db) { app.use(function (req, res, next) { // Website you wish to allow to connect - res.setHeader('Access-Control-Allow-Origin', 'https://sentry.polydaic.com'); + res.setHeader('Access-Control-Allow-Origin', 'https://sentry.io'); // Request methods you wish to allow res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); @@ -320,16 +322,10 @@ module.exports = function(db) { // Log it client.captureError(err); - if(process.env.NODE_ENV === 'production'){ - res.status(500).render('500', { - error: 'Internal Server Error' - }); - } else { - // Error page - res.status(500).render('500', { - error: err.stack - }); - } + // Error page + res.status(500).render('500', { + error: err.stack + }); }); // Assume 404 since no middleware responded diff --git a/config/strategies/local.js b/config/strategies/local.js index 319324a6..174a0007 100755 --- a/config/strategies/local.js +++ b/config/strategies/local.js @@ -14,8 +14,6 @@ module.exports = function () { passwordField: 'password' }, function (username, password, done) { - console.log('\n\n\n\n\nusername: '+username); - console.log('password: '+password) User.findOne({ $or: [ {'username': username.toLowerCase()}, diff --git a/package.json b/package.json index 3700f6b0..1cb7e1f1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ }, "dependencies": { "async": "^1.4.2", - "bcrypt": "^0.8.7", "body-parser": "~1.14.1", "bower": "~1.6.5", "chalk": "^1.1.3", @@ -74,9 +73,7 @@ "socket.io-redis": "^1.0.0", "swig": "~1.4.1", "uuid-token-generator": "^0.5.0", - "wildcard-subdomains": "github:tellform/wildcard-subdomains", - "winston": "^2.3.1", - "winston-logrotate": "^1.2.0" + "winston": "^2.3.1" }, "devDependencies": { "all-contributors-cli": "^4.3.0", diff --git a/public/dist/form_populate_template_cache.js b/public/dist/form_populate_template_cache.js index 3e094dc7..7c806969 100644 --- a/public/dist/form_populate_template_cache.js +++ b/public/dist/form_populate_template_cache.js @@ -12,7 +12,7 @@ angular.module('TellForm-Form.form_templates', []).run(['$templateCache', functi $templateCache.put("form_modules/forms/base/views/directiveViews/field/date.html", "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}

"); $templateCache.put("form_modules/forms/base/views/directiveViews/field/dropdown.html", - "
0\">

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + "
0\">

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); $templateCache.put("form_modules/forms/base/views/directiveViews/field/hidden.html", ""); $templateCache.put("form_modules/forms/base/views/directiveViews/field/legal.html", diff --git a/public/form_modules/forms/base/views/directiveViews/field/dropdown.html b/public/form_modules/forms/base/views/directiveViews/field/dropdown.html index 21151b22..64d2863c 100755 --- a/public/form_modules/forms/base/views/directiveViews/field/dropdown.html +++ b/public/form_modules/forms/base/views/directiveViews/field/dropdown.html @@ -26,8 +26,7 @@ ng-change="nextField()"> - + diff --git a/public/modules/core/img/loaders/page-loader.gif b/public/modules/core/img/loaders/page-loader.gif new file mode 100644 index 00000000..ae90a507 Binary files /dev/null and b/public/modules/core/img/loaders/page-loader.gif differ diff --git a/public/modules/core/tests/unit/controllers/header.client.controller.test.js b/public/modules/core/tests/unit/controllers/header.client.controller.test.js index 76ee4fb4..f2cebdc1 100755 --- a/public/modules/core/tests/unit/controllers/header.client.controller.test.js +++ b/public/modules/core/tests/unit/controllers/header.client.controller.test.js @@ -6,9 +6,38 @@ var scope, HeaderController; + var sampleUser = { + firstName: 'Full', + lastName: 'Name', + email: 'test@test.com', + username: 'test@test.com', + language: 'en', + password: 'password', + provider: 'local', + roles: ['user'], + _id: 'ed873933b1f1dea0ce12fab9' + }; + // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); + //Mock Authentication Service + beforeEach(module(function($provide) { + $provide.service('Auth', function() { + return { + ensureHasCurrentUser: function() { + return sampleUser; + }, + isAuthenticated: function() { + return true; + }, + getUserState: function() { + return true; + } + }; + }); + })); + beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); diff --git a/public/modules/forms/admin/config/i18n/german.js b/public/modules/forms/admin/config/i18n/german.js index 0ea8d351..77d40107 100644 --- a/public/modules/forms/admin/config/i18n/german.js +++ b/public/modules/forms/admin/config/i18n/german.js @@ -84,7 +84,6 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid ADD_OPTION: 'Option hinzufügen', NUM_OF_STEPS: 'Anzahl der Schritte', CLICK_FIELDS_FOOTER: 'Klicken Sie auf Felder, um sie hier hinzuzufügen', - FORM: 'Formular', IF_THIS_FIELD: 'Wenn dieses Feld', IS_EQUAL_TO: 'ist gleich', IS_NOT_EQUAL_TO: 'ist nicht gleich', diff --git a/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js b/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js index de630bfa..e405da62 100644 --- a/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js +++ b/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js @@ -10,13 +10,33 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', myform: '=' }, controller: function($scope){ - $scope.table = { masterChecker: false, rows: [] }; - var getSubmissions = function(){ + $scope.deletionInProgress = false; + $scope.waitingForDeletion = false; + + //Waits until deletionInProgress is false before running getSubmissions + $scope.$watch("deletionInProgress",function(newVal, oldVal){ + if(newVal === oldVal) return; + + if(newVal === false && $scope.waitingForDeletion) { + $scope.getSubmissions(); + $scope.waitingForDeletion = false; + } + }); + + $scope.handleSubmissionsRefresh = function(){ + if(!$scope.deletionInProgress) { + $scope.getSubmissions(); + } else { + $scope.waitingForDeletion = true; + } + }; + + $scope.getSubmissions = function(cb){ $http({ method: 'GET', url: '/forms/'+$scope.myform._id+'/submissions' @@ -36,10 +56,19 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', } $scope.table.rows = submissions; - }); + + if(cb && typeof cb === 'function'){ + cb(); + } + }, function errorCallback(err){ + console.error(err); + if(cb && typeof cb === 'function'){ + cb(err); + } + }); }; - var getVisitors = function(){ + $scope.getVisitors = function(){ $http({ method: 'GET', url: '/forms/'+$scope.myform._id+'/visitors' @@ -52,8 +81,23 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', }); }; - getSubmissions(); - getVisitors(); + $scope.handleSubmissionsRefresh(); + $scope.getVisitors(); + + //Fetch submissions and visitor data every 1.67 min + var updateSubmissions = $interval($scope.handleSubmissionsRefresh, 100000); + var updateVisitors = $interval($scope.getVisitors, 1000000); + + //Prevent $intervals from running after directive is destroyed + $scope.$on('$destroy', function() { + if (updateSubmissions) { + $interval.cancel($scope.updateSubmissions); + } + + if (updateVisitors) { + $interval.cancel($scope.updateVisitors); + } + }); /* ** Analytics Functions @@ -72,14 +116,48 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', return (totalTime/numSubmissions).toFixed(0); })(); - var updateFields = $interval(getSubmissions, 100000); - var updateFields = $interval(getVisitors, 1000000); + $scope.DeviceStatistics = (function(){ + var newStatItem = function(){ + return { + visits: 0, + responses: 0, + completion: 0, + average_time: 0, + total_time: 0 + }; + }; - $scope.$on('$destroy', function() { - if (updateFields) { - $interval.cancel($scope.updateFields); + var stats = { + desktop: newStatItem(), + tablet: newStatItem(), + phone: newStatItem(), + other: newStatItem() + }; + + if($scope.myform.analytics && $scope.myform.analytics.visitors) { + var visitors = $scope.myform.analytics.visitors; + for (var i = 0; i < visitors.length; i++) { + var visitor = visitors[i]; + var deviceType = visitor.deviceType; + + stats[deviceType].visits++; + + if (visitor.isSubmitted) { + stats[deviceType].total_time = stats[deviceType].total_time + visitor.timeElapsed; + stats[deviceType].responses++; + } + + if(stats[deviceType].visits) { + stats[deviceType].completion = 100*(stats[deviceType].responses / stats[deviceType].visits).toFixed(2); + } + + if(stats[deviceType].responses){ + stats[deviceType].average_time = (stats[deviceType].total_time / stats[deviceType].responses).toFixed(0); + } + } } - }); + return stats; + })(); /* ** Table Functions @@ -109,25 +187,24 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', //Delete selected submissions of Form $scope.deleteSelectedSubmissions = function(){ + $scope.deletionInProgress = true; var delete_ids = _.chain($scope.table.rows).filter(function(row){ return !!row.selected; }).pluck('_id').value(); - $http({ url: '/forms/'+$scope.myform._id+'/submissions', + return $http({ url: '/forms/'+$scope.myform._id+'/submissions', method: 'DELETE', data: {deleted_submissions: delete_ids}, headers: {'Content-Type': 'application/json;charset=utf-8'} }).success(function(data, status){ + $scope.deletionInProgress = true; //Remove deleted ids from table - var tmpArray = []; - for(var i=0; i<$scope.table.rows.length; i++){ - if(!$scope.table.rows[i].selected){ - tmpArray.push($scope.table.rows[i]); - } - } - $scope.table.rows = tmpArray; + $scope.table.rows = $scope.table.rows.filter(function(field){ + return !field.selected; + }); }) .error(function(err){ + $scope.deletionInProgress = true; console.error(err); }); }; diff --git a/public/modules/forms/tests/unit/controllers/admin-form.client.controller.test.js b/public/modules/forms/tests/unit/controllers/admin-form.client.controller.test.js index ba92adaf..d49d8914 100644 --- a/public/modules/forms/tests/unit/controllers/admin-form.client.controller.test.js +++ b/public/modules/forms/tests/unit/controllers/admin-form.client.controller.test.js @@ -57,22 +57,7 @@ _id: '525a8422f6d0f87f0e407a33' }; - var newFakeModal = function(){ - var result = { - opened: true, - close: function( item ) { - //The user clicked OK on the modal dialog, call the stored confirm callback with the selected item - this.opened = false; - }, - dismiss: function( type ) { - //The user clicked cancel on the modal dialog, call the stored cancel callback - this.opened = false; - } - }; - return result; - }; - - //Mock Users Service + //Mock myForm Service beforeEach(module(function($provide) { $provide.service('myForm', function($q) { return sampleForm; @@ -159,6 +144,27 @@ }); })); + var newFakeModal = function(){ + var modal = { + opened: true, + close: function( item ) { + //The user clicked OK on the modal dialog, call the stored confirm callback with the selected item + this.opened = false; + }, + dismiss: function( type ) { + //The user clicked cancel on the modal dialog, call the stored cancel callback + this.opened = false; + }, + result: { + then: function (cb) { + if(cb && typeof cb === 'function'){ + cb(); + } + } + } + }; + return modal; + }; //Mock $uibModal beforeEach(inject(function($uibModal) { @@ -199,7 +205,7 @@ expect(scope.myform).toEqualData(sampleForm); }); - it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', function() { + it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', inject(function($uibModal) { var controller = createAdminFormController(); //Set $state transition @@ -214,7 +220,7 @@ $httpBackend.flush(); $state.ensureAllTransitionsHappened(); - }); + })); it('$scope.update() should send a PUT request with the id of form', function() { var controller = createAdminFormController(); diff --git a/public/modules/forms/tests/unit/controllers/list-forms.client.controller.test.js b/public/modules/forms/tests/unit/controllers/list-forms.client.controller.test.js index 6f591280..2190264b 100644 --- a/public/modules/forms/tests/unit/controllers/list-forms.client.controller.test.js +++ b/public/modules/forms/tests/unit/controllers/list-forms.client.controller.test.js @@ -86,7 +86,6 @@ }); })); - // 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. @@ -106,25 +105,13 @@ // Initialize the Forms controller. createListFormsController = function(){ - return $controller('ListFormsController', { $scope: scope }); + return $controller('ListFormsController', { + $scope: scope, + myForms: sampleFormList + }); }; })); - it('$scope.findAll() should query all User\'s Forms', inject(function() { - - 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() { var dupSampleForm = sampleFormList[2], @@ -135,12 +122,6 @@ 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 @@ -164,13 +145,6 @@ var controller = createListFormsController(); - // Set GET response - $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList); - - // Run controller functionality - scope.findAll(); - $httpBackend.flush(); - // Set GET response $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, delSampleForm); diff --git a/public/modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js b/public/modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js index d1418c2e..75ac6b0d 100644 --- a/public/modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js +++ b/public/modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js @@ -2,7 +2,7 @@ (function() { // Forms Controller Spec - describe('EditSubmissions Directive-Controller Tests', function() { + describe('EditFormSubmissions Directive-Controller Tests', function() { // Initialize global variables var el, scope, controller, $httpBackend; @@ -10,13 +10,25 @@ firstName: 'Full', lastName: 'Name', email: 'test@test.com', - username: 'test@test.com', + username: 'test1234', password: 'password', provider: 'local', roles: ['user'], _id: 'ed873933b1f1dea0ce12fab9' }; + var sampleVisitors = [{ + socketId: '33b1f1dea0ce12fab9ed8739', + referrer: 'https://tellform.com/examples', + lastActiveField: 'ed873933b0ce121f1deafab9', + timeElapsed: 100000, + isSubmitted: true, + language: 'en', + ipAddr: '192.168.1.1', + deviceType: 'desktop', + userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4' + }] + var sampleForm = { title: 'Form Title', admin: 'ed873933b1f1dea0ce12fab9', @@ -27,7 +39,18 @@ {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ], analytics: { - visitors: [] + visitors: sampleVisitors, + conversionRate: 80.5, + fields: [ + { + dropoffViews: 0, + responses: 1, + totalViews: 1, + continueRate: 100, + dropoffRate: 0, + field: {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'} + } + ] }, submissions: [], startPage: { @@ -61,7 +84,8 @@ ], admin: sampleUser, form: sampleForm, - timeElapsed: 10.33 + timeElapsed: 10.33, + selected: false }, { form_fields: [ @@ -71,7 +95,8 @@ ], admin: sampleUser, form: sampleForm, - timeElapsed: 2.33 + timeElapsed: 2.33, + selected: false }, { form_fields: [ @@ -81,7 +106,8 @@ ], admin: sampleUser, form: sampleForm, - timeElapsed: 11.11 + timeElapsed: 11.11, + selected: false }]; // The $resource service augments the response object with methods for updating and deleting the resource. @@ -118,10 +144,12 @@ $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); $httpBackend.whenGET('/forms').respond(200, sampleForm); $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); - //Instantiate directive. + $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/submissions$/).respond(200, sampleSubmissions); + $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/visitors$/).respond(200, sampleVisitors); + + //Instantiate directive. var tmp_scope = $rootScope.$new(); tmp_scope.myform = sampleForm; - tmp_scope.myform.submissions = sampleSubmissions; tmp_scope.user = sampleUser; //gotacha: Controller and link functions will execute. @@ -141,6 +169,7 @@ it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){ //Run Controller Logic to Test + scope.table.rows = sampleSubmissions; scope.table.masterChecker = true; scope.toggleAllCheckers(); @@ -151,6 +180,7 @@ }); it('$scope.isAtLeastOneChecked should return true when at least one checkbox is selected', function(){ + scope.table.rows = sampleSubmissions; scope.table.masterChecker = true; scope.toggleAllCheckers(); @@ -161,16 +191,22 @@ }); it('$scope.deleteSelectedSubmissions should delete all submissions that are selected', function(){ + $httpBackend.expect('GET', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions); scope.table.masterChecker = true; - scope.toggleAllCheckers(); + scope.getSubmissions(function(err){ + scope.toggleAllCheckers(); - $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200); + $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200); - //Run Controller Logic to Test - scope.deleteSelectedSubmissions(); + //Run Controller Logic to Test + scope.deleteSelectedSubmissions().then(function(){ + expect(scope.table.rows.length).toEqual(0); + }); + expect(err).not.toBeDefined(); + }); $httpBackend.flush(); - expect(scope.table.rows.length).toEqual(0); + }); }); diff --git a/public/modules/forms/tests/unit/directives/edit-form.client.directive.test.js b/public/modules/forms/tests/unit/directives/edit-form.client.directive.test.js index fe7e2452..5ad0047b 100644 --- a/public/modules/forms/tests/unit/directives/edit-form.client.directive.test.js +++ b/public/modules/forms/tests/unit/directives/edit-form.client.directive.test.js @@ -62,6 +62,52 @@ beforeEach(module('module-templates')); beforeEach(module('stateMock')); + //Mock FormFields Service + beforeEach(module(function($provide) { + $provide.service('FormFields', function() { + return { + types: [ + { + name : 'textfield', + value : 'Short Text' + }, + { + name : 'email', + value : 'Email' + } + ] + }; + }); + })); + + var newFakeModal = function(){ + var modal = { + opened: true, + close: function( item ) { + //The user clicked OK on the modal dialog, call the stored confirm callback with the selected item + this.opened = false; + }, + dismiss: function( type ) { + //The user clicked cancel on the modal dialog, call the stored cancel callback + this.opened = false; + }, + result: { + then: function (cb) { + if(cb && typeof cb === 'function'){ + cb(); + } + } + } + }; + return modal; + }; + + //Mock $uibModal + beforeEach(inject(function($uibModal) { + var modal = newFakeModal(); + spyOn($uibModal, 'open').and.returnValue(modal); + })); + beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) { //Instantiate directive. var tmp_scope = $rootScope.$new(); @@ -97,26 +143,12 @@ scope.myform = _.cloneDeep(sampleForm); }); - it('$scope.addNewField() should ADD a new field to $scope.myform.form_fields', function() { + it('$scope.addNewField() should open the new field modal', function() { //Run controller methods - scope.addNewField(true, 'textfield'); + scope.addNewField('textfield'); - var expectedFormField = { - title: 'Short Text2', - fieldType: 'textfield', - fieldValue: '', - required: true, - disabled: false, - deletePreserved: false, - logicJump: Object({ }) - }; - - 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); + expect(scope.editFieldModal.opened).toBeTruthy(); }); it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() { diff --git a/public/modules/forms/tests/unit/directives/field-icon.client.directive.test.js b/public/modules/forms/tests/unit/directives/field-icon.client.directive.test.js index fbb89193..7dfc6823 100644 --- a/public/modules/forms/tests/unit/directives/field-icon.client.directive.test.js +++ b/public/modules/forms/tests/unit/directives/field-icon.client.directive.test.js @@ -5,7 +5,6 @@ describe('FieldIcon Directive Tests', function() { // Initialize global variables var scope, - FormFields, faClasses = { 'textfield': 'fa fa-pencil-square-o', 'dropdown': 'fa fa-th-list', @@ -28,10 +27,68 @@ // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); + //Mock FormFields Service + var FormFields = { + types: [ + { + name : 'textfield', + value : 'Short Text' + }, + { + name : 'email', + value : 'Email' + }, + { + name : 'radio', + value : 'Muliple Choice' + }, + { + name : 'dropdown', + value : 'Dropdown' + }, + { + name : 'date', + value : 'Date' + }, + { + name : 'textarea', + value : 'Paragraph', + }, + { + name : 'yes_no', + value : 'Yes/No', + }, + { + name : 'legal', + value : 'Legal', + }, + { + name : 'rating', + value : 'Rating', + }, + { + name : 'link', + value : 'Link', + }, + { + name : 'number', + value : 'Numbers', + }, + { + name : 'statement', + value : 'Statement' + } + ] + }; + beforeEach(module(function($provide) { + $provide.service('FormFields', function() { + return FormFields; + }); + })); + 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; diff --git a/public/modules/forms/tests/unit/directives/field.client.directive.test.js b/public/modules/forms/tests/unit/directives/field.client.directive.test.js index 462951ca..d5d3c89d 100644 --- a/public/modules/forms/tests/unit/directives/field.client.directive.test.js +++ b/public/modules/forms/tests/unit/directives/field.client.directive.test.js @@ -5,11 +5,63 @@ describe('Field Directive Tests', function() { // Initialize global variables var scope, - FormFields, $templateCache, $httpBackend, $compile; + var FormFields = { + types: [ + { + name : 'textfield', + value : 'Short Text' + }, + { + name : 'email', + value : 'Email' + }, + { + name : 'radio', + value : 'Muliple Choice' + }, + { + name : 'dropdown', + value : 'Dropdown' + }, + { + name : 'date', + value : 'Date' + }, + { + name : 'textarea', + value : 'Paragraph', + }, + { + name : 'yes_no', + value : 'Yes/No', + }, + { + name : 'legal', + value : 'Legal', + }, + { + name : 'rating', + value : 'Rating', + }, + { + name : 'link', + value : 'Link', + }, + { + name : 'number', + value : 'Numbers', + }, + { + name : 'statement', + value : 'Statement' + } + ] + }; + var sampleUser = { firstName: 'Full', lastName: 'Name', @@ -65,9 +117,15 @@ beforeEach(module('ngSanitize', 'ui.select')); + //Mock FormFields Service + beforeEach(module(function($provide) { + $provide.service('FormFields', function() { + return FormFields; + }); + })); + beforeEach(inject(function($rootScope, _FormFields_, _$compile_, _$httpBackend_) { scope = $rootScope.$new(); - FormFields = _FormFields_; // Point global variables to injected services $httpBackend = _$httpBackend_; @@ -76,6 +134,7 @@ $compile = _$compile_; })); + it('should be able to render all field types in html', inject(function($rootScope) { scope.fields = sampleFields; diff --git a/public/modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js b/public/modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js index 188022c4..9f48ab6a 100644 --- a/public/modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js +++ b/public/modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js @@ -4,15 +4,73 @@ // Forms Controller Spec describe('onFinishRender Directive Tests', function() { // Initialize global variables - var scope, - FormFields; + var scope; + + var FormFields = { + types: [ + { + name : 'textfield', + value : 'Short Text' + }, + { + name : 'email', + value : 'Email' + }, + { + name : 'radio', + value : 'Muliple Choice' + }, + { + name : 'dropdown', + value : 'Dropdown' + }, + { + name : 'date', + value : 'Date' + }, + { + name : 'textarea', + value : 'Paragraph', + }, + { + name : 'yes_no', + value : 'Yes/No', + }, + { + name : 'legal', + value : 'Legal', + }, + { + name : 'rating', + value : 'Rating', + }, + { + name : 'link', + value : 'Link', + }, + { + name : 'number', + value : 'Numbers', + }, + { + name : 'statement', + value : 'Statement' + } + ] + }; // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(inject(function ($rootScope, _FormFields_) { + //Mock FormFields Service + beforeEach(module(function($provide) { + $provide.service('FormFields', function() { + return FormFields; + }); + })); + + beforeEach(inject(function ($rootScope) { scope = $rootScope.$new(); - FormFields = _FormFields_; spyOn($rootScope, '$broadcast'); })); diff --git a/public/modules/users/tests/unit/controllers/authentication.client.controller.test.js b/public/modules/users/tests/unit/controllers/authentication.client.controller.test.js deleted file mode 100644 index 363f1f37..00000000 --- a/public/modules/users/tests/unit/controllers/authentication.client.controller.test.js +++ /dev/null @@ -1,181 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('Authentication Controller Tests', function() { - // Initialize global variables - var AuthenticationController, - 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}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} - ], - _id: '525a8422f6d0f87f0e407a33' - }; - - var expectedForm = { - 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} - ], - visible_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 sampleCredentials = { - username: sampleUser.username, - password: sampleUser.password, - }; - - - - // 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; - } - }; - }); - })); - - // 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(); - scope.abc = 'hello'; - - // 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. - AuthenticationController = $controller('AuthenticationController', { $scope: scope }); - })); - - it('$scope.signin should sigin in user with valid credentials', inject(function(Auth) { - - //Set $state transition - // $state.expectTransitionTo('listForms'); - //Set POST response - // $httpBackend.expect('POST', '/auth/signin', sampleCredentials).respond(200, sampleUser); - - scope.abc = 'sampleCredentials'; - //Run Controller Logic to Test - scope.signin(); - - // $httpBackend.flush(); - - // Test scope value - // expect(Auth.ensureHasCurrentUser()).toEqualData(sampleUser); - })); - - - }); -}()); \ No newline at end of file