diff --git a/gulp/build.js b/gulp/build.js new file mode 100644 index 0000000..f22f042 --- /dev/null +++ b/gulp/build.js @@ -0,0 +1,11 @@ +var gulp = require('gulp'); +var bower = require('gulp-bower'); + +// Bower install. +gulp.task('bower', function() { + return bower(); +}); + + +// Build Choko. +gulp.task('build', ['bower']); diff --git a/gulp/publish.js b/gulp/publish.js new file mode 100644 index 0000000..b8b73ea --- /dev/null +++ b/gulp/publish.js @@ -0,0 +1,27 @@ +var gulp = require('gulp'); +var git = require('gulp-git'); +var spawn = require('child_process').spawn; + +// Publish a new release to npm. +gulp.task('publish', ['build'], function(done) { + git.status({ + // Use short format. + args: '-s' + }, + function (err, stdout) { + if (err) { + throw err; + } + if (!stdout) { + // We have a clean working directory, proceed. + spawn('npm', ['publish'], { + stdio: 'inherit' + }).on('close', done); + } + else { + // Working directory not clean, abort. + console.log('Working directory not clean, aborting...'); + done(); + } + }); +}); diff --git a/gulp/server.js b/gulp/server.js new file mode 100644 index 0000000..e82ba00 --- /dev/null +++ b/gulp/server.js @@ -0,0 +1,31 @@ +/** + * Server related tasks. + */ + +var fs = require('fs'), + gulp = require('gulp'), async = require('async'), + choko = require('../lib/application'), + server; + +/** + * Initiate El-Tracker with a custom testing database. + */ +gulp.task('server:test', function (done) { + + // Create a server. + module.exports.server = server = new choko('./test/applications/test-app'); + + // Start the server. + server.start(3000, function (error, server) { + var droppers = Object.keys(server.collections).map(function (collection) { + return function (next) { + server.collections[collection].drop(next); + }; + }); + + // @todo: handle erros on droppers. + async.parallel(droppers, function (err) { + done(); + }); + }); +}); diff --git a/gulp/test.js b/gulp/test.js new file mode 100644 index 0000000..1477a26 --- /dev/null +++ b/gulp/test.js @@ -0,0 +1,46 @@ +/** + * Test related tasks. + */ + +var gulp = require('gulp'), + mocha = require('gulp-spawn-mocha'), + sequence = require('gulp-sequence'), + spawn = require('child_process').spawn, + app = require('./server'); + +/** + * Print buffer string. + */ +function logBuffer(data) { + console.log(data.toString()); +} + +/** + * E2E test: uses Protractor to perform e2e tests. + */ +gulp.task('test:e2e', ['server:test'], function (done) { + var protractor = spawn('protractor', ['test/e2e/protractor.conf.js']); + + protractor.stdout.on('data', logBuffer); + protractor.stderr.on('data', logBuffer); + + protractor.on('exit', function (code) { + + // Stop server. + app.server.stop(done); + + // @todo: fix Choko stop issue. + process.exit(); + }); +}); + +/** + * Unit test: uses Mocha to perform unit tests. + */ +gulp.task('test:unit', function() { + return gulp + .src(['test/unit/*.test.js']) + .pipe(mocha()); +}); + +gulp.task('test', sequence('test:unit', 'test:e2e')); diff --git a/gulpfile.js b/gulpfile.js index 39e9a0a..3e6391e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,36 +1,4 @@ -var gulp = require('gulp'); -var bower = require('gulp-bower'); -var git = require('gulp-git'); -var spawn = require('child_process').spawn; +var requireAll = require('require-dir'); -// Bower install. -gulp.task('bower', function() { - return bower(); -}); - -// Publish a new release to npm. -gulp.task('publish', ['build'], function(done) { - git.status({ - // Use short format. - args: '-s' - }, - function (err, stdout) { - if (err) { - throw err; - } - if (!stdout) { - // We have a clean working directory, proceed. - spawn('npm', ['publish'], { - stdio: 'inherit' - }).on('close', done); - } - else { - // Working directory not clean, abort. - console.log('Working directory not clean, aborting...'); - done(); - } - }); -}); - -// Build Choko. -gulp.task('build', ['bower']); +// Load all tasks. +requireAll('./gulp'); diff --git a/package.json b/package.json index e0f3889..14c25f1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "0.0.6", "description": "High performance and high level web application framework.", "scripts": { - "test": "mocha test" + "test": "gulp test", + "webdriver-update": "node ./node_modules/protractor/bin/webdriver-manager update" }, "keywords": [ "framework", @@ -39,10 +40,16 @@ }, "devDependencies": { "bower": "*", - "gulp": "*", + "gulp": "^3.9.1", "gulp-bower": "*", "gulp-git": "*", + "gulp-protractor": "^2.3.0", + "gulp-sequence": "^0.4.5", + "gulp-spawn-mocha": "^2.2.2", + "jasmine-spec-reporter": "^2.4.0", "mocha": "*", + "protractor": "3.0.0", + "require-dir": "^0.3.0", "supertest": "0.13.x" }, "bin": { diff --git a/test/applications/test-app/settings.json b/test/applications/test-app/settings.json index 6abaccd..3400cc1 100644 --- a/test/applications/test-app/settings.json +++ b/test/applications/test-app/settings.json @@ -1,5 +1,5 @@ { - "database": "mongodb://localhost/test-application", + "database": "mongodb://172.17.0.2/test-application", "sessionSecret": "no-secrets", "application": { "name": "Test application" @@ -7,4 +7,4 @@ "extensions": { "test-extension": {} } -} \ No newline at end of file +} diff --git a/test/e2e/page-objects/create-account-form.po.js b/test/e2e/page-objects/create-account-form.po.js new file mode 100644 index 0000000..f467a50 --- /dev/null +++ b/test/e2e/page-objects/create-account-form.po.js @@ -0,0 +1,11 @@ +var CreateAccountForm = function() { + + this.emailField = element(by.id('element-create-account-email')); + this.usernameField = element(by.id('element-create-account-username')); + this.passwordField = element(by.id('element-create-account-password')); + this.confirmPasswordField = element(by.id('element-create-account-password-confirm')); + this.submitButton = element(by.id('element-create-account-submit')); + +}; + +module.exports = CreateAccountForm; diff --git a/test/e2e/page-objects/create-blog-post-form.po.js b/test/e2e/page-objects/create-blog-post-form.po.js new file mode 100644 index 0000000..49adc67 --- /dev/null +++ b/test/e2e/page-objects/create-blog-post-form.po.js @@ -0,0 +1,7 @@ +var CreateBlogPostForm = function() { + + this.saveButton = element(by.id('element-type-blog-submit')); + +}; + +module.exports = CreateBlogPostForm; diff --git a/test/e2e/page-objects/home.po.js b/test/e2e/page-objects/home.po.js new file mode 100644 index 0000000..efd71bb --- /dev/null +++ b/test/e2e/page-objects/home.po.js @@ -0,0 +1,12 @@ +var Home = function() { + + this.signInLink = element(by.css('.btn-link')); + this.createAccountLink = element.all(by.css('.btn-primary')).first(); + + this.visit = function() { + browser.get(''); + }; + +}; + +module.exports = Home; diff --git a/test/e2e/page-objects/partials/messages.po.js b/test/e2e/page-objects/partials/messages.po.js new file mode 100644 index 0000000..fd6fad8 --- /dev/null +++ b/test/e2e/page-objects/partials/messages.po.js @@ -0,0 +1,7 @@ +var Messages = function() { + + this.error = element(by.css('.alert-danger')); + +}; + +module.exports = Messages; diff --git a/test/e2e/page-objects/sign-in-form.po.js b/test/e2e/page-objects/sign-in-form.po.js new file mode 100644 index 0000000..1bf92c1 --- /dev/null +++ b/test/e2e/page-objects/sign-in-form.po.js @@ -0,0 +1,9 @@ +var SignInForm = function() { + + this.usernameField = element(by.id('element-sign-in-username')); + this.passwordField = element(by.id('element-sign-in-password')); + this.submitButton = element(by.id('element-sign-in-submit')); + +}; + +module.exports = SignInForm; diff --git a/test/e2e/protractor.conf.js b/test/e2e/protractor.conf.js new file mode 100644 index 0000000..585389a --- /dev/null +++ b/test/e2e/protractor.conf.js @@ -0,0 +1,48 @@ +var SpecReporter = require('jasmine-spec-reporter'); + +exports.config = { + // Uses browser's own webdriver. + directConnect: true, + + // Spec patterns are relative to the location of this config. + specs: [ + 'specs/*.spec.js' + ], + + + capabilities: { + 'browserName': 'chrome', + 'chromeOptions': {'args': ['--disable-extensions']}, + + // Used for running all test files in paralel. + // Comment it to run all tests in one browser. + shardTestFiles: true, + maxInstances: 4, + }, + + // Define things that will happen before start testing. + onPrepare: function() { + // Add better test report on console. + jasmine.getEnv().addReporter(new SpecReporter({ + displayFailuresSummary: true, + displayFailedSpec: true, + displaySuiteNumber: true, + displaySpecDuration: true + })); + + browser.driver.manage().window().maximize(); + }, + + + // A base URL for your application under test. Calls to protractor.get() + // with relative paths will be prepended with this. + baseUrl: 'http://localhost:3000/', + + jasmineNodeOpts: { + onComplete: null, + isVerbose: false, + showColors: true, + includeStackTrace: true, + defaultTimeoutInterval: 999999 + } +}; diff --git a/test/e2e/specs/create-account.spec.js b/test/e2e/specs/create-account.spec.js new file mode 100644 index 0000000..41ba6f4 --- /dev/null +++ b/test/e2e/specs/create-account.spec.js @@ -0,0 +1,45 @@ +var Home = require('../page-objects/home.po'); +var CreateAccountForm = require('../page-objects/create-account-form.po'); +var Messages = require('../page-objects/partials/messages.po'); + +describe( 'Create account', function() { + + var home = new Home(); + var createAccountForm = new CreateAccountForm(); + var messages = new Messages(); + + beforeEach(function() { + home.visit(); + home.createAccountLink.click(); + }); + + it( 'navigate to create an account', function() { + + browser.getCurrentUrl().then(function(url) { + expect(url).toEqual('http://localhost:3000/create-account'); + }); + + }); + + it( 'try to create an account without filling any field', function() { + + createAccountForm.submitButton.click(); + + expect(messages.error.isDisplayed()).toBe(true); + + }); + + it( 'password must match', function() { + + createAccountForm.emailField.sendKeys('valid@email.com'); + createAccountForm.usernameField.sendKeys('joe'); + createAccountForm.passwordField.sendKeys('abc'); + createAccountForm.confirmPasswordField.sendKeys('def'); + + createAccountForm.submitButton.click(); + + expect(messages.error.isDisplayed()).toBe(true); + + }); + +}); diff --git a/test/e2e/specs/sign-in.spec.js b/test/e2e/specs/sign-in.spec.js new file mode 100644 index 0000000..c6ebdc8 --- /dev/null +++ b/test/e2e/specs/sign-in.spec.js @@ -0,0 +1,42 @@ +var Home = require('../page-objects/home.po'); +var Messages = require('../page-objects/partials/messages.po'); +var SignInForm = require('../page-objects/sign-in-form.po'); + +describe( 'Sign in', function() { + + var home = new Home(); + var messages = new Messages(); + var signInForm = new SignInForm(); + + beforeEach(function() { + home.visit(); + home.signInLink.click(); + }); + + it( 'navigate to sign in', function() { + + browser.getCurrentUrl().then(function(url) { + expect(url).toEqual('http://localhost:3000/sign-in'); + }); + + }); + + it( 'try to sign in without filling user and password', function() { + + signInForm.submitButton.click(); + + expect(messages.error.isDisplayed()).toBe(true); + + }); + + it( 'try to sign in with invalid user and password', function() { + + signInForm.usernameField.sendKeys('invalid'); + signInForm.passwordField.sendKeys('invalid'); + signInForm.submitButton.click(); + + expect(messages.error.isDisplayed()).toBe(true); + + }); + +}); diff --git a/test/mocha.opts b/test/mocha.opts index 748f8d8..65b4e04 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,2 +1,3 @@ +test/unit --recursive --timeout 10000 diff --git a/test/init.test.js b/test/unit/init.test.js similarity index 80% rename from test/init.test.js rename to test/unit/init.test.js index b4eed6e..4effd7e 100644 --- a/test/init.test.js +++ b/test/unit/init.test.js @@ -1,11 +1,11 @@ -var Application = require('../lib/application'); +var Application = require('../../lib/application'); var express = require('express'); var async = require('async'); var path = require('path'); var app; before(function(done) { - app = new Application(path.normalize(__dirname + '/applications/test-app')); + app = new Application(path.normalize(__dirname + '/../applications/test-app')); this.getApp = function() { return app; }; diff --git a/test/lib/userHelper.js b/test/unit/lib/userHelper.js similarity index 100% rename from test/lib/userHelper.js rename to test/unit/lib/userHelper.js diff --git a/test/rest/rest.test.js b/test/unit/rest/rest.test.js similarity index 100% rename from test/rest/rest.test.js rename to test/unit/rest/rest.test.js diff --git a/test/user/user.test.js b/test/unit/user/user.test.js similarity index 100% rename from test/user/user.test.js rename to test/unit/user/user.test.js