From 654de87ff7d695b9ff8d2b91b43f0b860d6c7e56 Mon Sep 17 00:00:00 2001 From: kingcody Date: Mon, 22 Sep 2014 06:37:09 -0400 Subject: [PATCH] feat(gen): unify testing framework Changes: - add prompt for Jasmine or Mocha - if Mocha choosen, prompt for Expect or Should - use `<%= does() %>` to dynamically insert assertions (expect|should) - add mocha variants for protractor tests - add mocha options to protractor.conf - remove `test/fixtures/(bower|package).json` from repo - move runE2E functionality to runTest and simplify switch - comment generator test functions - use node `0.11.13` in travis due to issues with `0.11.14`+ Note: Server-side jasmine test are needed to fully unify testing frameworks. Once Jasmine tests are included for server, mocha dep can be removed fully when selecting Jasmine. --- .travis.yml | 2 +- app/index.js | 133 +++++++++++++----- app/templates/_package.json | 18 ++- .../main/main.controller.spec(coffee).coffee | 5 +- .../app/main/main.controller.spec(js).js | 5 +- .../{login.spec.js => login.spec(jasmine).js} | 0 .../account(auth)/login/login.spec(mocha).js | 73 ++++++++++ ...logout.spec.js => logout.spec(jasmine).js} | 0 .../logout/logout.spec(mocha).js | 50 +++++++ ...signup.spec.js => signup.spec(jasmine).js} | 0 .../signup/signup.spec(mocha).js | 72 ++++++++++ .../{main.spec.js => main.spec(jasmine).js} | 0 app/templates/e2e/main/main.spec(mocha).js | 16 +++ app/templates/karma.conf.js | 13 +- app/templates/mocha.conf.js | 16 ++- app/templates/protractor.conf.js | 26 +++- script-base.js | 11 +- test/fixtures/bower.json | 25 ---- test/fixtures/package.json | 104 -------------- test/test-file-creation.js | 85 ++++++++--- 20 files changed, 438 insertions(+), 216 deletions(-) rename app/templates/e2e/account(auth)/login/{login.spec.js => login.spec(jasmine).js} (100%) create mode 100644 app/templates/e2e/account(auth)/login/login.spec(mocha).js rename app/templates/e2e/account(auth)/logout/{logout.spec.js => logout.spec(jasmine).js} (100%) create mode 100644 app/templates/e2e/account(auth)/logout/logout.spec(mocha).js rename app/templates/e2e/account(auth)/signup/{signup.spec.js => signup.spec(jasmine).js} (100%) create mode 100644 app/templates/e2e/account(auth)/signup/signup.spec(mocha).js rename app/templates/e2e/main/{main.spec.js => main.spec(jasmine).js} (100%) create mode 100644 app/templates/e2e/main/main.spec(mocha).js delete mode 100644 test/fixtures/bower.json delete mode 100644 test/fixtures/package.json diff --git a/.travis.yml b/.travis.yml index 676afbb43..e1abfbd26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js node_js: - '0.10' - - '0.11' + - '0.11.13' env: global: - SAUCE_USERNAME=fullstack_ci diff --git a/app/index.js b/app/index.js index 6e4724801..7dcee4d55 100644 --- a/app/index.js +++ b/app/index.js @@ -24,6 +24,15 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ this.pkg = require('../package.json'); this.filters = {}; + + // dynamic assertion statement + this.does = this.is = function(foo) { + if (this.filters.should) { + return foo + '.should'; + } else { + return 'expect(' + foo + ').to'; + } + }.bind(this); }, info: function () { @@ -36,9 +45,9 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ if(this.config.get('filters')) { this.prompt([{ - type: "confirm", - name: "skipConfig", - message: "Existing .yo-rc configuration found, would you like to use it?", + type: 'confirm', + name: 'skipConfig', + message: 'Existing .yo-rc configuration found, would you like to use it?', default: true, }], function (answers) { this.skipConfig = answers.skipConfig; @@ -66,10 +75,10 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ this.log('# Client\n'); this.prompt([{ - type: "list", - name: "script", - message: "What would you like to write scripts with?", - choices: [ "JavaScript", "CoffeeScript"], + type: 'list', + name: 'script', + message: 'What would you like to write scripts with?', + choices: [ 'JavaScript', 'CoffeeScript'], filter: function( val ) { var filterMap = { 'JavaScript': 'js', @@ -79,33 +88,33 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ return filterMap[val]; } }, { - type: "list", - name: "markup", - message: "What would you like to write markup with?", - choices: [ "HTML", "Jade"], + type: 'list', + name: 'markup', + message: 'What would you like to write markup with?', + choices: [ 'HTML', 'Jade'], filter: function( val ) { return val.toLowerCase(); } }, { - type: "list", - name: "stylesheet", + type: 'list', + name: 'stylesheet', default: 1, - message: "What would you like to write stylesheets with?", - choices: [ "CSS", "Sass", "Stylus", "Less"], + message: 'What would you like to write stylesheets with?', + choices: [ 'CSS', 'Sass', 'Stylus', 'Less'], filter: function( val ) { return val.toLowerCase(); } }, { - type: "list", - name: "router", + type: 'list', + name: 'router', default: 1, - message: "What Angular router would you like to use?", - choices: [ "ngRoute", "uiRouter"], + message: 'What Angular router would you like to use?', + choices: [ 'ngRoute', 'uiRouter'], filter: function( val ) { return val.toLowerCase(); } }, { - type: "confirm", - name: "bootstrap", - message: "Would you like to include Bootstrap?" + type: 'confirm', + name: 'bootstrap', + message: 'Would you like to include Bootstrap?' }, { - type: "confirm", - name: "uibootstrap", - message: "Would you like to include UI Bootstrap?", + type: 'confirm', + name: 'uibootstrap', + message: 'Would you like to include UI Bootstrap?', when: function (answers) { return answers.bootstrap; } @@ -116,7 +125,7 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ this.filters[answers.router] = true; this.filters.bootstrap = !!answers.bootstrap; this.filters.uibootstrap = !!answers.uibootstrap; - cb(); + cb(); }.bind(this)); }, @@ -128,13 +137,13 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ this.log('\n# Server\n'); this.prompt([{ - type: "confirm", - name: "mongoose", - message: "Would you like to use mongoDB with Mongoose for data modeling?" + type: 'confirm', + name: 'mongoose', + message: 'Would you like to use mongoDB with Mongoose for data modeling?' }, { - type: "confirm", - name: "auth", - message: "Would you scaffold out an authentication boilerplate?", + type: 'confirm', + name: 'auth', + message: 'Would you scaffold out an authentication boilerplate?', when: function (answers) { return answers.mongoose; } @@ -163,9 +172,9 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ } ] }, { - type: "confirm", - name: "socketio", - message: "Would you like to use socket.io?", + type: 'confirm', + name: 'socketio', + message: 'Would you like to use socket.io?', // to-do: should not be dependent on mongoose when: function (answers) { return answers.mongoose; @@ -186,6 +195,47 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ }.bind(this)); }, + projectPrompts: function() { + if(this.skipConfig) return; + var cb = this.async(); + var self = this; + + this.log('\n# Project\n'); + + this.prompt([{ + type: 'list', + name: 'testing', + message: 'What would you like to write tests with?', + choices: [ 'Jasmine', 'Mocha + Chai + Sinon'], + filter: function( val ) { + var filterMap = { + 'Jasmine': 'jasmine', + 'Mocha + Chai + Sinon': 'mocha' + }; + + return filterMap[val]; + } + }, { + type: 'list', + name: 'chai', + message: 'What would you like to write Chai assertions with?', + choices: ['Expect', 'Should'], + filter: function( val ) { + return val.toLowerCase(); + }, + when: function( answers ) { + return answers.testing === 'mocha'; + } + }], function (answers) { + this.filters[answers.testing] = true; + if (this.filters.mocha) { + this.filters[answers.chai] = true; + } + + cb(); + }.bind(this)); + }, + saveSettings: function() { if(this.skipConfig) return; this.config.set('insertRoutes', true); @@ -207,10 +257,15 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ if(this.skipConfig) return; var appPath = 'client/app/'; var extensions = []; - var filters = []; + var filters = [ + 'ngroute', + 'uirouter', + 'jasmine', + 'mocha', + 'expect', + 'should' + ].filter(function(v) {return this.filters[v];}, this); - if(this.filters.ngroute) filters.push('ngroute'); - if(this.filters.uirouter) filters.push('uirouter'); if(this.filters.coffee) extensions.push('coffee'); if(this.filters.js) extensions.push('js'); if(this.filters.html) extensions.push('html'); @@ -249,7 +304,7 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ if(this.filters.uirouter) angModules.push("'ui.router'"); if(this.filters.uibootstrap) angModules.push("'ui.bootstrap'"); - this.angularModules = "\n " + angModules.join(",\n ") +"\n"; + this.angularModules = '\n ' + angModules.join(',\n ') +'\n'; }, generate: function() { diff --git a/app/templates/_package.json b/app/templates/_package.json index 1912903fa..08784aa6c 100644 --- a/app/templates/_package.json +++ b/app/templates/_package.json @@ -31,7 +31,6 @@ "socketio-jwt": "^2.0.2"<% } %> }, "devDependencies": { - "chai-as-promised": "^4.1.1", "grunt": "~0.4.4", "grunt-autoprefixer": "~0.7.2", "grunt-wiredep": "~1.8.0", @@ -62,9 +61,7 @@ "grunt-protractor-runner": "^1.1.0", "grunt-asset-injector": "^0.1.0", "grunt-karma": "~0.8.2", - "grunt-build-control": "DaftMonk/grunt-build-control", - "grunt-mocha-test": "~0.10.2", - "grunt-mocha-istanbul": "^2.0.0",<% if(filters.sass) { %> + "grunt-build-control": "DaftMonk/grunt-build-control",<% if(filters.sass) { %> "grunt-contrib-sass": "^0.7.3",<% } %><% if(filters.stylus) { %> "grunt-contrib-stylus": "latest",<% } %> "jit-grunt": "^0.5.0", @@ -74,12 +71,19 @@ "open": "~0.0.4", "jshint-stylish": "~0.1.5", "connect-livereload": "~0.4.0", + "grunt-mocha-test": "~0.10.2", + "grunt-mocha-istanbul": "^2.0.0", + "chai-as-promised": "^4.1.1", + "chai-things": "^0.2.0", + "sinon-chai": "^2.5.0",<% if(filters.mocha) { %> + "karma-mocha": "^0.1.9", + "karma-chai-plugins": "^0.2.3",<% } if(filters.jasmine) { %> + "karma-jasmine": "~0.1.5",<% } %> "karma-ng-scenario": "~0.1.0", "karma-firefox-launcher": "~0.1.3", "karma-script-launcher": "~0.1.0", "karma-html2js-preprocessor": "~0.1.0", "karma-ng-jade2js-preprocessor": "^0.1.2", - "karma-jasmine": "~0.1.5", "karma-chrome-launcher": "~0.1.3", "requirejs": "~2.1.11", "karma-requirejs": "~0.2.1", @@ -88,9 +92,9 @@ "karma-phantomjs-launcher": "~0.1.4", "karma": "~0.12.9", "karma-ng-html2js-preprocessor": "~0.1.0", + "karma-spec-reporter": "0.0.13", "proxyquire": "^1.0.1", - "supertest": "~0.11.0", - "sinon-chai": "^2.5.0" + "supertest": "~0.11.0" }, "engines": { "node": ">=0.10.0" diff --git a/app/templates/client/app/main/main.controller.spec(coffee).coffee b/app/templates/client/app/main/main.controller.spec(coffee).coffee index f974a081d..5bdf4f840 100644 --- a/app/templates/client/app/main/main.controller.spec(coffee).coffee +++ b/app/templates/client/app/main/main.controller.spec(coffee).coffee @@ -27,5 +27,6 @@ describe 'Controller: MainCtrl', -> $scope: scope it 'should attach a list of things to the scope', -> - $httpBackend.flush() - expect(scope.awesomeThings.length).toBe 4 + $httpBackend.flush()<% if (filters.jasmine) { %> + expect(scope.awesomeThings.length).toBe 4 <% } if (filters.mocha) { %> + <%= does("scope.awesomeThings.length") %>.equal 4<% } %> diff --git a/app/templates/client/app/main/main.controller.spec(js).js b/app/templates/client/app/main/main.controller.spec(js).js index 21b8aba70..dc048b4af 100644 --- a/app/templates/client/app/main/main.controller.spec(js).js +++ b/app/templates/client/app/main/main.controller.spec(js).js @@ -26,7 +26,8 @@ describe('Controller: MainCtrl', function () { })); it('should attach a list of things to the scope', function () { - $httpBackend.flush(); - expect(scope.awesomeThings.length).toBe(4); + $httpBackend.flush();<% if (filters.jasmine) { %> + expect(scope.awesomeThings.length).toBe(4);<% } if (filters.mocha) { %> + <%= does("scope.awesomeThings.length") %>.equal(4);<% } %> }); }); diff --git a/app/templates/e2e/account(auth)/login/login.spec.js b/app/templates/e2e/account(auth)/login/login.spec(jasmine).js similarity index 100% rename from app/templates/e2e/account(auth)/login/login.spec.js rename to app/templates/e2e/account(auth)/login/login.spec(jasmine).js diff --git a/app/templates/e2e/account(auth)/login/login.spec(mocha).js b/app/templates/e2e/account(auth)/login/login.spec(mocha).js new file mode 100644 index 000000000..a2c986f32 --- /dev/null +++ b/app/templates/e2e/account(auth)/login/login.spec(mocha).js @@ -0,0 +1,73 @@ +'use strict'; + +var config = protractor.getInstance().params; +var UserModel = require(config.serverConfig.root + '/server/api/user/user.model'); + +describe('Login View', function() { + var page; + + var loadPage = function() { + browser.get('/login'); + page = require('./login.po'); + }; + + var testUser = { + name: 'Test User', + email: 'test@test.com', + password: 'test' + }; + + before(function() { + return UserModel + .removeAsync() + .then(function() { + return UserModel.createAsync(testUser); + }) + .then(loadPage); + }); + + after(function() { + return UserModel.removeAsync(); + }); + + it('should include login form with correct inputs and submit button', function() { + <%= does("page.form.email.getAttribute('type')") %>.eventually.equal('email'); + <%= does("page.form.email.getAttribute('name')") %>.eventually.equal('email'); + <%= does("page.form.password.getAttribute('type')") %>.eventually.equal('password'); + <%= does("page.form.password.getAttribute('name')") %>.eventually.equal('password'); + <%= does("page.form.submit.getAttribute('type')") %>.eventually.equal('submit'); + <%= does("page.form.submit.getText()") %>.eventually.equal('Login'); + }); + + describe('with local auth', function() { + + it('should login a user and redirecting to "/"', function() { + page.login(testUser); + + var navbar = require('../../components/navbar/navbar.po'); + + <%= does("browser.getLocationAbsUrl()") %>.eventually.equal(config.baseUrl + '/'); + <%= does("navbar.navbarAccountGreeting.getText()") %>.eventually.equal('Hello ' + testUser.name); + }); + + describe('and invalid credentials', function() { + before(function() { + return loadPage(); + }) + + it('should indicate login failures', function() { + page.login({ + email: testUser.email, + password: 'badPassword' + }); + + <%= does("browser.getLocationAbsUrl()") %>.eventually.equal(config.baseUrl + '/login'); + + var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding')); + <%= does("helpBlock.getText()") %>.eventually.equal('This password is not correct.'); + }); + + }); + + }); +}); diff --git a/app/templates/e2e/account(auth)/logout/logout.spec.js b/app/templates/e2e/account(auth)/logout/logout.spec(jasmine).js similarity index 100% rename from app/templates/e2e/account(auth)/logout/logout.spec.js rename to app/templates/e2e/account(auth)/logout/logout.spec(jasmine).js diff --git a/app/templates/e2e/account(auth)/logout/logout.spec(mocha).js b/app/templates/e2e/account(auth)/logout/logout.spec(mocha).js new file mode 100644 index 000000000..3adba51f8 --- /dev/null +++ b/app/templates/e2e/account(auth)/logout/logout.spec(mocha).js @@ -0,0 +1,50 @@ +'use strict'; + +var config = protractor.getInstance().params; +var UserModel = require(config.serverConfig.root + '/server/api/user/user.model'); + +describe('Logout View', function() { + var login = function(user) { + browser.get('/login'); + require('../login/login.po').login(user); + }; + + var testUser = { + name: 'Test User', + email: 'test@test.com', + password: 'test' + }; + + beforeEach(function() { + return UserModel + .removeAsync() + .then(function() { + return UserModel.createAsync(testUser); + }) + .then(function() { + return login(testUser); + }); + }); + + after(function() { + return UserModel.removeAsync(); + }) + + describe('with local auth', function() { + + it('should logout a user and redirecting to "/"', function() { + var navbar = require('../../components/navbar/navbar.po'); + + <%= does("browser.getLocationAbsUrl()") %>.eventually.equal(config.baseUrl + '/'); + <%= does("navbar.navbarAccountGreeting.getText()") %>.eventually.equal('Hello ' + testUser.name); + + browser.get('/logout'); + + navbar = require('../../components/navbar/navbar.po'); + + <%= does("browser.getLocationAbsUrl()") %>.eventually.equal(config.baseUrl + '/'); + <%= does("navbar.navbarAccountGreeting.isDisplayed()") %>.eventually.equal(false); + }); + + }); +}); diff --git a/app/templates/e2e/account(auth)/signup/signup.spec.js b/app/templates/e2e/account(auth)/signup/signup.spec(jasmine).js similarity index 100% rename from app/templates/e2e/account(auth)/signup/signup.spec.js rename to app/templates/e2e/account(auth)/signup/signup.spec(jasmine).js diff --git a/app/templates/e2e/account(auth)/signup/signup.spec(mocha).js b/app/templates/e2e/account(auth)/signup/signup.spec(mocha).js new file mode 100644 index 000000000..6a6b0a775 --- /dev/null +++ b/app/templates/e2e/account(auth)/signup/signup.spec(mocha).js @@ -0,0 +1,72 @@ +'use strict'; + +var config = protractor.getInstance().params; +var UserModel = require(config.serverConfig.root + '/server/api/user/user.model'); + +describe('Signup View', function() { + var page; + + var loadPage = function() { + browser.get('/signup'); + page = require('./signup.po'); + }; + + var testUser = { + name: 'Test', + email: 'test@test.com', + password: 'test' + }; + + before(function() { + return loadPage(); + }); + + after(function() { + return UserModel.removeAsync(); + }); + + it('should include signup form with correct inputs and submit button', function() { + <%= does("page.form.name.getAttribute('type')") %>.eventually.equal('text'); + <%= does("page.form.name.getAttribute('name')") %>.eventually.equal('name'); + <%= does("page.form.email.getAttribute('type')") %>.eventually.equal('email'); + <%= does("page.form.email.getAttribute('name')") %>.eventually.equal('email'); + <%= does("page.form.password.getAttribute('type')") %>.eventually.equal('password'); + <%= does("page.form.password.getAttribute('name')") %>.eventually.equal('password'); + <%= does("page.form.submit.getAttribute('type')") %>.eventually.equal('submit'); + <%= does("page.form.submit.getText()") %>.eventually.equal('Sign up'); + }); + + describe('with local auth', function() { + + it('should signup a new user, log them in, and redirecting to "/"', function(done) { + UserModel.remove(function() { + page.signup(testUser); + + var navbar = require('../../components/navbar/navbar.po'); + + <%= does("browser.getLocationAbsUrl()") %>.eventually.equal(config.baseUrl + '/'); + <%= does("navbar.navbarAccountGreeting.getText()") %>.eventually.equal('Hello ' + testUser.name); + + done(); + }); + }); + + describe('and invalid credentials', function() { + before(function() { + return loadPage(); + }); + + it('should indicate signup failures', function() { + page.signup(testUser); + + <%= does("browser.getLocationAbsUrl()") %>.eventually.equal(config.baseUrl + '/signup'); + <%= does("page.form.email.getAttribute('class')") %>.eventually.contain('ng-invalid-mongoose'); + + var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding')); + <%= does("helpBlock.getText()") %>.eventually.equal('The specified email address is already in use.'); + }); + + }); + + }); +}); diff --git a/app/templates/e2e/main/main.spec.js b/app/templates/e2e/main/main.spec(jasmine).js similarity index 100% rename from app/templates/e2e/main/main.spec.js rename to app/templates/e2e/main/main.spec(jasmine).js diff --git a/app/templates/e2e/main/main.spec(mocha).js b/app/templates/e2e/main/main.spec(mocha).js new file mode 100644 index 000000000..1962ba765 --- /dev/null +++ b/app/templates/e2e/main/main.spec(mocha).js @@ -0,0 +1,16 @@ +'use strict'; + +describe('Main View', function() { + var page; + + beforeEach(function() { + browser.get('/'); + page = require('./main.po'); + }); + + it('should include jumbotron with correct data', function() { + <%= does("page.h1El.getText()") %>.eventually.equal('\'Allo, \'Allo!'); + <%= does("page.imgEl.getAttribute('src')") %>.eventually.match(/assets\/images\/yeoman.png$/); + <%= does("page.imgEl.getAttribute('alt')") %>.eventually.equal('I\'m Yeoman'); + }); +}); diff --git a/app/templates/karma.conf.js b/app/templates/karma.conf.js index 57b3fa6f2..6b6c6c477 100644 --- a/app/templates/karma.conf.js +++ b/app/templates/karma.conf.js @@ -6,8 +6,9 @@ module.exports = function(config) { // base path, that will be used to resolve files and exclude basePath: '', - // testing framework to use (jasmine/mocha/qunit/...) - frameworks: ['jasmine'], + // testing framework to use (jasmine/mocha/qunit/...)<% if(filters.jasmine) { %> + frameworks: ['jasmine'],<% } if(filters.mocha) { %> + frameworks: ['mocha', 'chai', 'sinon-chai', 'chai-as-promised', 'chai-things'],<% } %> // list of files / patterns to load in the browser files: [ @@ -58,6 +59,14 @@ module.exports = function(config) { // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG logLevel: config.LOG_INFO, + // reporter types: + // - dots + // - progress (default) + // - spec (karma-spec-reporter) + // - junit + // - growl + // - coverage + reporters: ['spec'], // enable / disable watching file and executing tests whenever any file changes autoWatch: false, diff --git a/app/templates/mocha.conf.js b/app/templates/mocha.conf.js index 497d43b2c..54e33bb6f 100644 --- a/app/templates/mocha.conf.js +++ b/app/templates/mocha.conf.js @@ -1,14 +1,16 @@ 'use strict'; var chai = require('chai'); -var sinon = require('sinon'); -var sinonChai = require('sinon-chai'); -var chaiAsPromised = require('chai-as-promised'); +// Load Chai assertions global.expect = chai.expect; global.assert = chai.assert; -global.sinon = sinon; - chai.should(); -chai.use(sinonChai); -chai.use(chaiAsPromised); + +// Load Sinon +global.sinon = require('sinon'); + +// Initialize Chai plugins +chai.use(require('sinon-chai')); +chai.use(require('chai-as-promised')); +chai.use(require('chai-things')) diff --git a/app/templates/protractor.conf.js b/app/templates/protractor.conf.js index 18b9ba6af..14f704b3d 100644 --- a/app/templates/protractor.conf.js +++ b/app/templates/protractor.conf.js @@ -43,24 +43,40 @@ var config = { // Jasmine and Cucumber are fully supported as a test and assertion framework. // Mocha has limited beta support. You will need to include your own // assertion framework if working with mocha. - framework: 'jasmine', - + framework: '<% if(filters.jasmine) { %>jasmine<% } if(filters.mocha) { %>mocha<% } %>', +<% if(filters.jasmine) { %> // ----- Options to be passed to minijasminenode ----- // // See the full list at https://github.com/juliemr/minijasminenode jasmineNodeOpts: { defaultTimeoutInterval: 30000 - }, + },<% } if(filters.mocha) { %> + // ----- Options to be passed to mocha ----- + mochaOpts: { + reporter: 'spec', + timeout: 30000, + defaultTimeoutInterval: 30000 + },<% } %> // Prepare environment for tests params: { serverConfig: require('./server/config/environment') }, - onPrepare: function() { + onPrepare: function() {<% if(filters.mocha) { %> + // Load Mocha and Chai + plugins + require('./mocha.conf'); + + // Expose should assertions (see https://github.com/angular/protractor/issues/633) + Object.defineProperty( + protractor.promise.Promise.prototype, + 'should', + Object.getOwnPropertyDescriptor(Object.prototype, 'should') + ); +<% } %> var serverConfig = config.params.serverConfig; - // Setup mongo tests + // Setup mongo for tests var mongoose = require('mongoose-bird')(); mongoose.connect(serverConfig.mongo.uri, serverConfig.mongo.options); // Connect to database } diff --git a/script-base.js b/script-base.js index 2f44ce09f..5d6c7dd2e 100644 --- a/script-base.js +++ b/script-base.js @@ -18,8 +18,17 @@ var Generator = module.exports = function Generator() { this.cameledName = this._.camelize(this.name); this.classedName = this._.classify(this.name); + // dynamic assertion statement + this.does = this.is = function(foo) { + if (this.filters.should) { + return foo + '.should'; + } else { + return 'expect(' + foo + ').to'; + } + }.bind(this); + this.filters = this.config.get('filters'); this.sourceRoot(path.join(__dirname, '/templates')); }; -util.inherits(Generator, yeoman.generators.NamedBase); \ No newline at end of file +util.inherits(Generator, yeoman.generators.NamedBase); diff --git a/test/fixtures/bower.json b/test/fixtures/bower.json deleted file mode 100644 index 7d9aae354..000000000 --- a/test/fixtures/bower.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tempApp", - "version": "0.0.0", - "dependencies": { - "angular": ">=1.2.*", - "json3": "~3.3.1", - "es5-shim": "~3.0.1", - "jquery": "~1.11.0", - "bootstrap-sass-official": "~3.1.1", - "bootstrap": "~3.1.1", - "angular-resource": ">=1.2.*", - "angular-cookies": ">=1.2.*", - "angular-sanitize": ">=1.2.*", - "angular-route": ">=1.2.*", - "angular-bootstrap": "~0.11.0", - "font-awesome": ">=4.1.0", - "lodash": "~2.4.1", - "angular-socket-io": "~0.6.0", - "angular-ui-router": "~0.2.10" - }, - "devDependencies": { - "angular-mocks": ">=1.2.*", - "angular-scenario": ">=1.2.*" - } -} diff --git a/test/fixtures/package.json b/test/fixtures/package.json deleted file mode 100644 index 48d48607e..000000000 --- a/test/fixtures/package.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "name": "tempApp", - "version": "0.0.0", - "main": "server/app.js", - "dependencies": { - "express": "~4.0.0", - "morgan": "~1.0.0", - "body-parser": "~1.5.0", - "method-override": "~1.0.0", - "serve-favicon": "~2.0.1", - "cookie-parser": "~1.0.1", - "express-session": "~1.0.2", - "errorhandler": "~1.0.0", - "compression": "~1.0.1", - "lodash": "~2.4.1", - "jade": "~1.2.0", - "ejs": "~0.8.4", - "mongoose": "~3.8.8", - "mongoose-bird": "~0.0.1", - "jsonwebtoken": "^0.3.0", - "express-jwt": "^0.1.3", - "passport": "~0.2.0", - "passport-local": "~0.1.6", - "passport-facebook": "latest", - "passport-twitter": "latest", - "passport-google-oauth": "latest", - "composable-middleware": "^0.3.0", - "connect-mongo": "^0.4.1", - "socket.io": "^1.0.6", - "socket.io-client": "^1.0.6", - "socketio-jwt": "^2.0.2" - }, - "devDependencies": { - "chai-as-promised": "^4.1.1", - "grunt": "~0.4.4", - "grunt-autoprefixer": "~0.7.2", - "grunt-wiredep": "~1.8.0", - "grunt-concurrent": "~0.5.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-concat": "~0.4.0", - "grunt-contrib-copy": "~0.5.0", - "grunt-contrib-cssmin": "~0.9.0", - "grunt-contrib-htmlmin": "~0.2.0", - "grunt-contrib-imagemin": "~0.7.1", - "grunt-contrib-jshint": "~0.10.0", - "grunt-contrib-uglify": "~0.4.0", - "grunt-contrib-watch": "~0.6.1", - "grunt-contrib-coffee": "^0.10.1", - "grunt-contrib-jade": "^0.11.0", - "grunt-contrib-less": "^0.11.4", - "grunt-google-cdn": "~0.4.0", - "grunt-newer": "~0.7.0", - "grunt-ng-annotate": "^0.2.3", - "grunt-rev": "~0.1.0", - "grunt-svgmin": "~0.4.0", - "grunt-usemin": "~2.1.1", - "grunt-env": "~0.4.1", - "grunt-node-inspector": "~0.1.5", - "grunt-nodemon": "~0.2.0", - "grunt-angular-templates": "^0.5.4", - "grunt-dom-munger": "^3.4.0", - "grunt-protractor-runner": "^1.1.0", - "grunt-asset-injector": "^0.1.0", - "grunt-karma": "~0.8.2", - "grunt-build-control": "DaftMonk/grunt-build-control", - "grunt-mocha-test": "~0.10.2", - "grunt-mocha-istanbul": "^2.0.0", - "grunt-contrib-sass": "^0.7.3", - "grunt-contrib-stylus": "latest", - "jit-grunt": "^0.5.0", - "time-grunt": "~0.3.1", - "grunt-express-server": "~0.4.17", - "grunt-open": "~0.2.3", - "open": "~0.0.4", - "jshint-stylish": "~0.1.5", - "connect-livereload": "~0.4.0", - "karma-ng-scenario": "~0.1.0", - "karma-firefox-launcher": "~0.1.3", - "karma-script-launcher": "~0.1.0", - "karma-html2js-preprocessor": "~0.1.0", - "karma-ng-jade2js-preprocessor": "^0.1.2", - "karma-jasmine": "~0.1.5", - "karma-chrome-launcher": "~0.1.3", - "requirejs": "~2.1.11", - "karma-requirejs": "~0.2.1", - "karma-coffee-preprocessor": "~0.2.1", - "karma-jade-preprocessor": "0.0.11", - "karma-phantomjs-launcher": "~0.1.4", - "karma": "~0.12.9", - "karma-ng-html2js-preprocessor": "~0.1.0", - "proxyquire": "^1.0.1", - "supertest": "~0.11.0", - "sinon-chai": "^2.5.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "start": "node server/app.js", - "test": "grunt test", - "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" - }, - "private": true -} diff --git a/test/test-file-creation.js b/test/test-file-creation.js index 31ed71ab2..7db1d4afe 100644 --- a/test/test-file-creation.js +++ b/test/test-file-creation.js @@ -14,6 +14,8 @@ describe('angular-fullstack generator', function () { markup: 'html', stylesheet: 'sass', router: 'uirouter', + testing: 'mocha', + chai: 'expect', bootstrap: true, uibootstrap: true, mongoose: true, @@ -35,9 +37,18 @@ describe('angular-fullstack generator', function () { }); } + /** + * Assert that only an array of files exist at a given path + * + * @param {Array} expectedFiles - array of files + * @param {Function} done - callback(error{Error}) + * @param {String} path - top level path to assert files at (optional) + * @param {Array} skip - array of paths to skip/ignore (optional) + * + */ function assertOnlyFiles(expectedFiles, done, path, skip) { path = path || './'; - skip = skip || ['e2e', 'node_modules', 'client/bower_components']; + skip = skip || ['node_modules', 'client/bower_components']; recursiveReadDir(path, skip, function(err, actualFiles) { if (err) { return done(err); } @@ -61,16 +72,16 @@ describe('angular-fullstack generator', function () { }); } - function runE2E(self, done) { - self.timeout(240000); // 4 minutes - gen.run({}, function () { - exec('grunt test:e2e', function (error, stdout, stderr) { - expect(stdout, 'Client tests failed \n' + stdout).to.contain('0 failures'); - done(); - }); - }); - }; - + /** + * Exec a command and run test assertion(s) based on command type + * + * @param {String} cmd - the command to exec + * @param {Object} self - context of the test + * @param {Function} cb - callback() + * @param {String} endpoint - endpoint to generate before exec (optional) + * @param {Number} timeout - timeout for the exec and test (optional) + * + */ function runTest(cmd, self, cb) { var args = Array.prototype.slice.call(arguments), endpoint = (args[3] && typeof args[3] === 'string') ? args.splice(3, 1)[0] : null, @@ -81,15 +92,14 @@ describe('angular-fullstack generator', function () { var execFn = function() { exec(cmd, function(error, stdout, stderr) { switch(cmd) { - case 'grunt test:client': - expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1\u001b[32m SUCCESS\u001b'); - break; case 'grunt jshint': - expect(stdout).to.contain('Done, without errors.'); - break; case 'grunt jscs': expect(stdout).to.contain('Done, without errors.'); break; + case 'grunt test:client': + case 'grunt test:e2e': + expect(stdout, 'Client tests failed \n' + stdout).to.contain('Done, without errors.'); + break; case 'grunt test:server': expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.'); break; @@ -108,6 +118,13 @@ describe('angular-fullstack generator', function () { } } + /** + * Generate an array of files to expect from a set of options + * + * @param {Object} ops - generator options + * @return {Array} - array of files + * + */ function genFiles(ops) { var mapping = { stylesheet: { @@ -127,6 +144,13 @@ describe('angular-fullstack generator', function () { }, files = []; + /** + * Generate an array of OAuth files based on type + * + * @param {String} type - type of oauth + * @return {Array} - array of files + * + */ var oauthFiles = function(type) { return [ 'server/auth/' + type + '/index.js', @@ -139,6 +163,7 @@ describe('angular-fullstack generator', function () { markup = mapping.markup[ops.markup], stylesheet = mapping.stylesheet[ops.stylesheet]; + /* Core Files */ files = files.concat([ 'client/.htaccess', 'client/.jshintrc', @@ -172,6 +197,9 @@ describe('angular-fullstack generator', function () { 'server/config/environment/production.js', 'server/config/environment/test.js', 'server/views/404.' + markup, + 'e2e/main/main.po.js', + 'e2e/main/main.spec.js', + 'e2e/components/navbar/navbar.po.js', '.bowerrc', '.buildignore', '.editorconfig', @@ -187,12 +215,14 @@ describe('angular-fullstack generator', function () { 'protractor.conf.js' ]); + /* Ui-Router */ if (ops.router === 'uirouter') { files = files.concat([ 'client/components/ui-router/ui-router.mock.' + script ]); } + /* Ui-Bootstrap */ if (ops.uibootstrap) { files = files.concat([ 'client/components/modal/modal.' + markup, @@ -201,6 +231,7 @@ describe('angular-fullstack generator', function () { ]); } + /* Mongoose */ if (ops.mongoose) { files = files.concat([ 'server/api/thing/thing.model.js', @@ -208,6 +239,7 @@ describe('angular-fullstack generator', function () { ]); } + /* Authentication */ if (ops.auth) { files = files.concat([ 'client/app/account/account.' + script, @@ -234,16 +266,23 @@ describe('angular-fullstack generator', function () { 'server/auth/index.js', 'server/auth/auth.service.js', 'server/auth/local/index.js', - 'server/auth/local/passport.js' + 'server/auth/local/passport.js', + 'e2e/account/login/login.po.js', + 'e2e/account/login/login.spec.js', + 'e2e/account/logout/logout.spec.js', + 'e2e/account/signup/signup.po.js', + 'e2e/account/signup/signup.spec.js' ]); } + /* OAuth (see oauthFiles function above) */ if (ops.oauth) { ops.oauth.forEach(function(type, i) { files = files.concat(oauthFiles(type.replace('Auth', ''))); }); } + /* Socket.IO */ if (ops.socketio) { files = files.concat([ 'client/components/socket/socket.service.' + script, @@ -361,7 +400,7 @@ describe('angular-fullstack generator', function () { }); it('should run e2e tests successfully', function(done) { - runE2E(this, done); + runTest('grunt test:e2e', this, done, 240000); }); }); @@ -371,6 +410,7 @@ describe('angular-fullstack generator', function () { markup: 'jade', stylesheet: 'less', router: 'uirouter', + testing: 'jasmine', mongoose: true, auth: true, oauth: ['twitterAuth', 'facebookAuth', 'googleAuth'], @@ -417,7 +457,7 @@ describe('angular-fullstack generator', function () { }); it('should run e2e tests successfully', function(done) { - runE2E(this, done); + runTest('grunt test:e2e', this, done, 240000); }); }); @@ -427,6 +467,8 @@ describe('angular-fullstack generator', function () { markup: 'jade', stylesheet: 'stylus', router: 'ngroute', + testing: 'mocha', + chai: 'should', mongoose: false, auth: false, oauth: [], @@ -474,7 +516,7 @@ describe('angular-fullstack generator', function () { }); it('should run e2e tests successfully', function(done) { - runE2E(this, done); + runTest('grunt test:e2e', this, done, 240000); }); }); @@ -484,6 +526,7 @@ describe('angular-fullstack generator', function () { markup: 'html', stylesheet: 'css', router: 'ngroute', + testing: 'jasmine', mongoose: false, auth: false, oauth: [], @@ -523,7 +566,7 @@ describe('angular-fullstack generator', function () { }); it('should run e2e tests successfully', function(done) { - runE2E(this, done); + runTest('grunt test:e2e', this, done, 240000); }); }); });