diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 868db265..4e3543be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,9 +25,9 @@ Because we create a generator you should be specific as possible in your request Before you submit your pull request consider the following guidelines: -**Test** +**Test** Please add unit tests for every new feature or bug fix and make sure all tests pass before submitting pull requests. Generator tests are written in [Mocha](http://mochajs.org). [Karma](http://karma-runner.github.io/0.12/index.html) and [Protractor](http://angular.github.io/protractor) are used to run unit tests and e2e tests on generated app. -* Run `./node_modules/mocha/bin/_mocha ./test/*.js` to execute all tests instead of `npm test`. Currently all protractor tests in (2) are excluded from `npm test` due to Travis issue. +* Run `./node_modules/mocha/bin/_mocha ./test/**/*.js` to execute all tests instead of `npm test`. Currently all protractor tests in (2) are excluded from `npm test` due to Travis issue. You will need to have the ruby sass gem installed to run the full suite of tests. * Add tests into (1) and (2) if there are changes in generated project's structure. * Feel free to create new test file for new generator features. @@ -39,13 +39,13 @@ Please add unit tests for every new feature or bug fix and make sure all tests p | (4) `test-import-gen.js` | Test generator directory. | (5) `test-utils.js` | Unit tests for utils.js. -**Style Guide** +**Style Guide** Please brief yourself on [Idiomatic.js](https://github.com/rwldrn/idiomatic.js) style guide with two space indent. -**Documentation** +**Documentation** Add documentation for every new feature, directory structure change. Feel free to send corrections or better docs! -**Branch** +**Branch** Must be one of the following: * feat: A new feature diff --git a/README.md b/README.md index 731f3fb2..11c7eccd 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ yo gulp-angular ![Logo](docs/assets/bower.png) ![Logo](docs/assets/webpack.png) ![Logo](docs/assets/karma.png) +![Logo](docs/assets/cucumber.png) ![Logo](docs/assets/istanbul.png) ![Logo](docs/assets/browsersync.png) ![Logo](docs/assets/jasmine.png) diff --git a/docs/assets/cucmber.png b/docs/assets/cucmber.png new file mode 100644 index 00000000..9a58976f Binary files /dev/null and b/docs/assets/cucmber.png differ diff --git a/docs/assets/original/cucumber.png b/docs/assets/original/cucumber.png new file mode 100644 index 00000000..f8a386ed Binary files /dev/null and b/docs/assets/original/cucumber.png differ diff --git a/docs/usage.md b/docs/usage.md index 25caec8e..883a35ee 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -139,5 +139,6 @@ There is none at the generation but you can add `.jade`, `.haml` or `.hbs` (depe * *CSS pre-processor*: Less, Sass with Ruby and Node, Stylus, none * *JS preprocessor*: CoffeeScript, TypeScript, ECMAScript 6 (Traceur and Babel), none * *HTML preprocessor*: Jade, Haml, Handlebars, none +* *Protractor framework*: Jasmine, Cucumber * **TODO** Script loader: Require, Webpack, none * **TODO** Test framework: Jasmine, Mocha, Qunit diff --git a/docs/user-guide.md b/docs/user-guide.md index 110f5106..7419c43d 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -14,7 +14,7 @@ Its usage is described in the chapters [Development server](#development-server) ### `test` -For testing, a fully working test environment is shipped with some examples. It uses Karma (with `gulp test`) for the unit tests, and Protractor for the end-to-end tests (with `gulp protractor`). +For testing, a fully working test environment is shipped with some examples. It uses Karma (with `gulp test`) for the unit tests, and Protractor for the [end-to-end tests](#end-to-end-tests) (with `gulp protractor`). More information in the [Test environment configured](#test-environment-configured) chapter. @@ -147,6 +147,12 @@ To allow tests to load HTML partials especially for the directives tests, we use Other than that, we try to use as less Karma plugins as possible because they often duplicate process we already have inside Gulp. For sake of coherence and stability we're searching for a process centralized in Gulp and not duplicated in Karma or other tools like perhaps Webpack. +## End to end tests + +The generator provides end to end testing using [Protractor](https://angular.github.io/protractor/#/). By default, Protractor uses the Jasmine test framework for its testing interface. There is an option to change the testing framework to [Cucumber](https://cucumber.io/), a BBD testing framework that uses human readable feature tests to drive assertions. + +When the Cucumber options is selected, the [Chai](http://chaijs.com/) library loaded for assertions in your end-to-end tests. + ## Optimization process The central piece of the optimization process is the use of the [gulp-useref](https://github.com/jonkemp/gulp-useref). Its task is to concat files and rewriting the `index.html` file to points on the concat versions of the files. All works with HTML comments block starting with ``. diff --git a/generators/app/files.json b/generators/app/files.json index 90471df9..0ecc8734 100644 --- a/generators/app/files.json +++ b/generators/app/files.json @@ -4,14 +4,12 @@ ".editorconfig", "tsconfig.json", - "protractor.conf.js", "e2e/.eslintrc", - "e2e/main.spec.js", + "e2e/main.feature", "gulpfile.js", "gulp/.eslintrc", - "gulp/e2e-tests.js", "src/favicon.ico", "src/assets/images/yeoman.png" @@ -25,10 +23,13 @@ "typings.json", "karma.conf.js", + "protractor.conf.js", + "e2e/main.spec.js", "e2e/main.po.js", "gulp/conf.js", + "gulp/e2e-tests.js", "gulp/build.js", "gulp/inject.js", "gulp/markups.js", diff --git a/generators/app/prompts.json b/generators/app/prompts.json index 083098e8..d4af40bf 100644 --- a/generators/app/prompts.json +++ b/generators/app/prompts.json @@ -391,5 +391,26 @@ "name": "Handlebars (*.hbs)" } ] + }, + { + "type": "list", + "name": "protractorFramework", + "message": "Which Protractor test framework do you want?", + "choices": [ + { + "value": { + "key": "jasmine", + "extension": "js" + }, + "name": "None, Use the default protractor framework (Jasmine)." + }, + { + "value": { + "key": "cucumber", + "extension": "feature" + }, + "name": "Cucumber, a BDD testing framework that uses feature tests." + } + ] } ] diff --git a/generators/app/src/mock-prompts.js b/generators/app/src/mock-prompts.js index 6f2777b3..2d61251b 100644 --- a/generators/app/src/mock-prompts.js +++ b/generators/app/src/mock-prompts.js @@ -24,7 +24,8 @@ var questions = [ 'foundationComponents', 'cssPreprocessor', 'jsPreprocessor', - 'htmlPreprocessor' + 'htmlPreprocessor', + 'protractorFramework' ]; var model = {}; @@ -81,6 +82,10 @@ model.htmlPreprocessor.choices.forEach(function (choice) { model.htmlPreprocessor.values[choice.value.key] = choice.value; }); +model.protractorFramework.choices.forEach(function (choice) { + model.protractorFramework.values[choice.value.key] = choice.value; +}); + module.exports = { prompts: model, defaults: { @@ -94,6 +99,7 @@ module.exports = { foundationComponents: model.foundationComponents.values.noFoundationComponents, cssPreprocessor: model.cssPreprocessor.values['node-sass'], jsPreprocessor: model.jsPreprocessor.values.noJsPrepro, - htmlPreprocessor: model.htmlPreprocessor.values.noHtmlPrepro + htmlPreprocessor: model.htmlPreprocessor.values.noHtmlPrepro, + protractorFramework: model.protractorFramework.values.jasmine, } }; diff --git a/generators/app/src/preprocessors.js b/generators/app/src/preprocessors.js index 1d3c00a0..2854e8aa 100644 --- a/generators/app/src/preprocessors.js +++ b/generators/app/src/preprocessors.js @@ -20,7 +20,8 @@ module.exports = function (GulpAngularGenerator) { 'js', this.props.cssPreprocessor.extension, this.props.jsPreprocessor.extension, - this.props.htmlPreprocessor.extension + this.props.htmlPreprocessor.extension, + this.props.protractorFramework.extension ]; if (this.imageMin) { this.processedFileExtension = this.processedFileExtension.concat(['jpg', 'png', 'gif', 'svg']); @@ -75,6 +76,11 @@ module.exports = function (GulpAngularGenerator) { if (this.props.jsPreprocessor.key !== 'noJsPrepro') { rejectWithRegexp.call(this, /^(?!^e2e\/).*spec\.js/); } + + if (this.props.protractorFramework.key !== 'cucumber') { + rejectWithRegexp.call(this, /main\.feature/); + } + }; /** diff --git a/generators/app/src/techs.js b/generators/app/src/techs.js index c4573ab9..37f269ee 100644 --- a/generators/app/src/techs.js +++ b/generators/app/src/techs.js @@ -19,7 +19,8 @@ module.exports = function (GulpAngularGenerator) { this.props.foundationComponents.key, this.props.cssPreprocessor.key, this.props.jsPreprocessor.key, - this.props.htmlPreprocessor.key + this.props.htmlPreprocessor.key, + this.props.protractorFramework.key ]) .filter(_.isString) .filter(function (tech) { diff --git a/generators/app/techs.json b/generators/app/techs.json index 32545c28..5973bc95 100644 --- a/generators/app/techs.json +++ b/generators/app/techs.json @@ -157,5 +157,11 @@ "url": "http://handlebarsjs.com/", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration.", "logo": "handlebars.png" + }, + "cucumber": { + "title": "Cucumber", + "url": "https://cucumber.io/", + "description": "Cucumber, the popular Behaviour-Driven Development tool, brought to your JavaScript stack.", + "logo": "cucumber.png" } } diff --git a/generators/app/templates/_package.json b/generators/app/templates/_package.json index bb08e5a8..18195d63 100644 --- a/generators/app/templates/_package.json +++ b/generators/app/templates/_package.json @@ -78,6 +78,12 @@ "wiredep": "~2.2.2", "karma": "~0.13.10", "karma-jasmine": "~0.3.6", +<% if (props.protractorFramework.key === 'cucumber') { -%> + "cucumber": "~0.10.2", + "protractor-cucumber-framework": "~0.5.0", + "chai": "~3.5.0", + "chai-as-promised": "~5.3.0", +<% } -%> <% if(props.jsPreprocessor.key === 'traceur') { -%> "karma-chrome-launcher": "~0.2.1", <% } else { -%> diff --git a/generators/app/templates/protractor.conf.js b/generators/app/templates/_protractor.conf.js similarity index 62% rename from generators/app/templates/protractor.conf.js rename to generators/app/templates/_protractor.conf.js index 862d068e..c2462c13 100644 --- a/generators/app/templates/protractor.conf.js +++ b/generators/app/templates/_protractor.conf.js @@ -17,11 +17,25 @@ exports.config = { // Spec patterns are relative to the current working directory when // protractor is called. - specs: [paths.e2e + '/**/*.js'], + specs: [paths.e2e + '/**/*.<%- props.protractorFramework.extension %>'], +<% if (props.protractorFramework.key === 'cucumber') { -%> + framework: 'custom', + // path relative to the current config file + frameworkPath: require.resolve('protractor-cucumber-framework'), + + // relevant cucumber command line options + cucumberOpts: { + require: [ + paths.e2e + '/**/*.js' + ], + format: "pretty" + } +<% } else { -%> // Options to be passed to Jasmine-node. jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000 } +<% } -%> }; diff --git a/generators/app/templates/e2e/_main.spec.js b/generators/app/templates/e2e/_main.spec.js new file mode 100644 index 00000000..ee69df5b --- /dev/null +++ b/generators/app/templates/e2e/_main.spec.js @@ -0,0 +1,52 @@ +'use strict'; + +<% if (props.protractorFramework.key === 'cucumber') { -%> +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); +var expect = chai.expect; +var page = require('./main.po'); + +chai.use(chaiAsPromised); + +module.exports = function () { + this.Given('a demo app', function (done) { + done(); + }); + + this.When('I load the page', function (done) { + browser.get('/index.html'); + done(); + }); + + this.Then(/^I should see the jumbotron with correct data$/, function () { + expect(page.h1El.getText()).to.eventually.equal('\'Allo, \'Allo!'); + expect(page.imgEl.getAttribute('src')).to.eventually.have.string('assets/images/yeoman.png'); + return expect(page.imgEl.getAttribute('alt')).to.eventually.equal('I\'m Yeoman'); + }); + + this.Then(/^I should see a list of more than (\d+) awesome things$/, function (itemNumber) { + return expect(page.thumbnailEls.count()).to.eventually.be.at.least(itemNumber); + }); +}; +<% } else { -%> +describe('The main view', function () { + var page; + + beforeEach(function () { + browser.get('/index.html'); + page = require('./main.po'); + }); + + it('should include jumbotron with correct data', function() { + expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); + expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); + expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); + }); + + it('should list more than 5 awesome things', function () { + expect(page.thumbnailEls.count()).toBeGreaterThan(5); + }); + +}); +<% } -%> + diff --git a/generators/app/templates/e2e/main.feature b/generators/app/templates/e2e/main.feature new file mode 100644 index 00000000..383e37eb --- /dev/null +++ b/generators/app/templates/e2e/main.feature @@ -0,0 +1,7 @@ +Feature: The project introduction page + + Scenario: The main view + Given a demo app + When I load the page + Then I should see the jumbotron with correct data + And I should see a list of more than 5 awesome things \ No newline at end of file diff --git a/generators/app/templates/e2e/main.spec.js b/generators/app/templates/e2e/main.spec.js deleted file mode 100644 index ef2e5c18..00000000 --- a/generators/app/templates/e2e/main.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -describe('The main view', function () { - var page; - - beforeEach(function () { - browser.get('/index.html'); - page = require('./main.po'); - }); - - it('should include jumbotron with correct data', function() { - expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); - expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); - expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); - }); - - it('should list more than 5 awesome things', function () { - expect(page.thumbnailEls.count()).toBeGreaterThan(5); - }); - -}); diff --git a/generators/app/templates/gulp/e2e-tests.js b/generators/app/templates/gulp/_e2e-tests.js similarity index 91% rename from generators/app/templates/gulp/e2e-tests.js rename to generators/app/templates/gulp/_e2e-tests.js index 3a66702a..06218002 100644 --- a/generators/app/templates/gulp/e2e-tests.js +++ b/generators/app/templates/gulp/_e2e-tests.js @@ -17,7 +17,7 @@ function runProtractor (done) { var params = process.argv; var args = params.length > 3 ? [params[3], params[4]] : []; - gulp.src(path.join(conf.paths.e2e, '/**/*.js')) + gulp.src(path.join(conf.paths.e2e, '/**/*.<%- props.protractorFramework.extension %>')) .pipe($.protractor.protractor({ configFile: 'protractor.conf.js', args: args diff --git a/generators/app/templates/src/assets/images/cucumber.png b/generators/app/templates/src/assets/images/cucumber.png new file mode 100644 index 00000000..f8a386ed Binary files /dev/null and b/generators/app/templates/src/assets/images/cucumber.png differ diff --git a/test/inception/test-inception.js b/test/inception/test-inception.js index b9df6d72..35140e0e 100644 --- a/test/inception/test-inception.js +++ b/test/inception/test-inception.js @@ -35,7 +35,7 @@ describe('gulp-angular generator inception tests', function () { }); }); - describe('with [angular 1.3.x, jQuery 2.x.x, Restangular, UI-Router, Foundation, angular-foundation, CSS, Coffee, Jade]', function () { + describe('with [angular 1.3.x, jQuery 2.x.x, Restangular, UI-Router, Foundation, angular-foundation, CSS, Coffee, Jade, cucmber]', function () { before(function () { return inception.prepare({}, { angularVersion: prompts.angularVersion.values['1.3'], @@ -46,7 +46,8 @@ describe('gulp-angular generator inception tests', function () { foundationComponents: prompts.foundationComponents.values['angular-foundation'], cssPreprocessor: prompts.cssPreprocessor.values.noCssPrepro, jsPreprocessor: prompts.jsPreprocessor.values.coffee, - htmlPreprocessor: prompts.htmlPreprocessor.values.jade + htmlPreprocessor: prompts.htmlPreprocessor.values.jade, + protractorFramework: prompts.protractorFramework.values.cucumber }).then(function (generator) { gulpAngular = generator; }); diff --git a/test/node/test-preprocessors.js b/test/node/test-preprocessors.js index bc61cc97..da1b9f64 100644 --- a/test/node/test-preprocessors.js +++ b/test/node/test-preprocessors.js @@ -24,7 +24,8 @@ describe('gulp-angular generator preprocessors script', function () { { src: 'gulp/scripts.js' }, { src: 'gulp/markups.js' }, { src: 'index.constants.js' }, - { src: 'tsd.json' } + { src: 'tsd.json' }, + { src: 'e2e/main.feature' } ]; }); @@ -33,12 +34,14 @@ describe('gulp-angular generator preprocessors script', function () { generator.props = { cssPreprocessor: { extension: null }, jsPreprocessor: { extension: 'js' }, - htmlPreprocessor: { extension: 'jade' } + htmlPreprocessor: { extension: 'jade' }, + protractorFramework: { extension: 'feature' } }; generator.imageMin = true; generator.computeProcessedFileExtension(); - generator.processedFileExtension.should.be.equal('html,css,js,jade,jpg,png,gif,svg'); + generator.processedFileExtension.should.be.equal('html,css,js,jade,feature,jpg,png,gif,svg'); + generator.props.protractorFramework.extension = 'js'; generator.imageMin = false; generator.computeProcessedFileExtension(); generator.processedFileExtension.should.be.equal('html,css,js,jade'); @@ -74,7 +77,8 @@ describe('gulp-angular generator preprocessors script', function () { generator.props = { cssPreprocessor: { key: 'noCssPrepro' }, jsPreprocessor: { key: 'noJsPrepro' }, - htmlPreprocessor: { key: 'noHtmlPrepro' } + htmlPreprocessor: { key: 'noHtmlPrepro' }, + protractorFramework: { key: 'jasmine' } }; generator.rejectFiles(); generator.files.length.should.be.equal(3); @@ -84,10 +88,11 @@ describe('gulp-angular generator preprocessors script', function () { generator.props = { cssPreprocessor: { key: 'not none' }, jsPreprocessor: { key: 'typescript' }, - htmlPreprocessor: { key: 'not none' } + htmlPreprocessor: { key: 'not none' }, + protractorFramework: { key: 'cucumber' } }; generator.rejectFiles(); - generator.files.length.should.be.equal(4); + generator.files.length.should.be.equal(5); }); }); @@ -97,7 +102,7 @@ describe('gulp-angular generator preprocessors script', function () { jsPreprocessor: { key: 'coffee' } }; generator.lintCopies(); - generator.files[5].src.should.match(/coffeelint/); + generator.files[6].src.should.match(/coffeelint/); }); it('should add tslint for typescript preprocessor', function () { @@ -105,7 +110,7 @@ describe('gulp-angular generator preprocessors script', function () { jsPreprocessor: { key: 'typescript' } }; generator.lintCopies(); - generator.files[5].src.should.match(/tslint/); + generator.files[6].src.should.match(/tslint/); }); }); }); diff --git a/test/node/test-techs.js b/test/node/test-techs.js index 960dcbf2..7b8f59f2 100644 --- a/test/node/test-techs.js +++ b/test/node/test-techs.js @@ -36,7 +36,8 @@ describe('gulp-angular generator techs script', function () { foundationComponents: { key: 'noFoundationComponents' }, cssPreprocessor: { extension: 'default' }, jsPreprocessor: { extension: 'css' }, - htmlPreprocessor: { extension: 'official' } + htmlPreprocessor: { extension: 'official' }, + protractorFramework: { extension: 'js' } }; generator.files = []; generator.computeTechs();