├── .gitignore ├── Gruntfile.js ├── README.md ├── bower.json ├── karma.conf.js ├── package.json ├── public ├── data │ ├── config.json │ └── freewind.json ├── index.html └── js │ ├── app.js │ ├── appConfig.js │ ├── config.js │ ├── controllers.js │ ├── hello.js │ ├── hello2.js │ └── jquery-private.js └── test ├── e2e ├── controller_spec.js └── juliemr_spec.js ├── protractor-conf.js ├── test-main.js └── unit ├── controller_spec.js ├── hello2_spec.js └── hello_spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | lib 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | var protractorDir = 'node_modules/protractor/bin/'; 5 | 6 | // Project configuration. 7 | grunt.initConfig({ 8 | // Metadata. 9 | pkg: grunt.file.readJSON('package.json'), 10 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 11 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 12 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 13 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 14 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n', 15 | // Task configuration. 16 | concat: { 17 | options: { 18 | banner: '<%= banner %>', 19 | stripBanners: true 20 | }, 21 | dist: { 22 | src: ['lib/<%= pkg.name %>.js'], 23 | dest: 'dist/<%= pkg.name %>.js' 24 | } 25 | }, 26 | uglify: { 27 | options: { 28 | banner: '<%= banner %>' 29 | }, 30 | dist: { 31 | src: '<%= concat.dist.dest %>', 32 | dest: 'dist/<%= pkg.name %>.min.js' 33 | } 34 | }, 35 | jshint: { 36 | options: { 37 | curly: true, 38 | eqeqeq: true, 39 | immed: true, 40 | latedef: true, 41 | newcap: true, 42 | noarg: true, 43 | sub: true, 44 | undef: true, 45 | unused: true, 46 | boss: true, 47 | eqnull: true, 48 | browser: true, 49 | globals: { 50 | jQuery: true 51 | } 52 | }, 53 | gruntfile: { 54 | src: 'Gruntfile.js' 55 | }, 56 | lib_test: { 57 | src: ['lib/**/*.js', 'test/**/*.js'] 58 | } 59 | }, 60 | qunit: { 61 | files: ['test/**/*.html'] 62 | }, 63 | watch: { 64 | gruntfile: { 65 | files: '<%= jshint.gruntfile.src %>', 66 | tasks: ['jshint:gruntfile'] 67 | }, 68 | lib_test: { 69 | files: '<%= jshint.lib_test.src %>', 70 | tasks: ['jshint:lib_test', 'qunit'] 71 | } 72 | }, 73 | bower: { 74 | install: { 75 | options: { 76 | targetDir: './public/js/lib', 77 | layout: 'byComponent', 78 | install: true, 79 | verbose: true, 80 | cleanTargetDir: true, 81 | cleanBowerDir: true, 82 | bowerOptions: {} 83 | } 84 | } 85 | }, 86 | 'http-server': { 87 | dev: { 88 | root: "./", 89 | port: 8282, 90 | host: "127.0.0.1", 91 | cache: 10000, 92 | showDir : true, 93 | autoIndex: true, 94 | defaultExt: "html", 95 | runInBackground: true 96 | } 97 | }, 98 | protractor_webdriver: { 99 | alive: { 100 | options: { 101 | path: protractorDir, 102 | keepAlive: true 103 | } 104 | }, 105 | dead: { 106 | options: { 107 | path: protractorDir 108 | } 109 | } 110 | }, 111 | protractor: { 112 | options: { 113 | configFile: "node_modules/protractor/referenceConf.js", // Default config file 114 | keepAlive: false, // If false, the grunt process stops when the test fails. 115 | noColor: false, // If true, protractor will not use colors in its output. 116 | args: { 117 | // Arguments passed to the command 118 | } 119 | }, 120 | test: { 121 | options: { 122 | configFile: "test/protractor-conf.js", // Target-specific config file 123 | args: {} // Target-specific arguments 124 | } 125 | }, 126 | }, 127 | karma: { 128 | unit: { 129 | configFile: 'karma.conf.js' 130 | } 131 | }, 132 | shell: { 133 | protractor: { 134 | options: { 135 | stdout: true 136 | }, 137 | command: protractorDir + 'webdriver-manager update --standalone --chrome' 138 | }, 139 | serverForTesting: { 140 | options: { 141 | async: true, 142 | stdout: true, 143 | failOnError: true 144 | }, 145 | command: './node_modules/http-server/bin/http-server . -p 8899' 146 | }, 147 | server: { 148 | options: { 149 | stdout: true, 150 | async: false, 151 | failOnError: true, 152 | cache: 0 153 | }, 154 | command: './node_modules/http-server/bin/http-server . -p 8900' 155 | }, 156 | ls: { 157 | options: { 158 | stdout: true 159 | }, 160 | command: 'ls' 161 | } 162 | }, 163 | waitServer: { 164 | server: { 165 | options: { 166 | url: 'http://localhost:8899', 167 | fail: function () {console.error('the server had not start'); }, 168 | timeout: 20 * 1000, 169 | isforce: false, 170 | interval: 1000, 171 | print: true 172 | } 173 | } 174 | } 175 | }); 176 | 177 | // These plugins provide necessary tasks. 178 | grunt.loadNpmTasks('grunt-contrib-concat'); 179 | grunt.loadNpmTasks('grunt-contrib-uglify'); 180 | grunt.loadNpmTasks('grunt-contrib-qunit'); 181 | grunt.loadNpmTasks('grunt-contrib-jshint'); 182 | grunt.loadNpmTasks('grunt-contrib-watch'); 183 | grunt.loadNpmTasks('grunt-bower-task'); 184 | grunt.loadNpmTasks('grunt-http-server'); 185 | grunt.loadNpmTasks('grunt-protractor-runner'); 186 | grunt.loadNpmTasks('grunt-protractor-webdriver'); 187 | grunt.loadNpmTasks('grunt-karma'); 188 | grunt.loadNpmTasks('grunt-shell-spawn'); 189 | grunt.loadNpmTasks('grunt-wait-server'); 190 | 191 | // Default task. 192 | grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); 193 | grunt.registerTask('unit-test', ['karma:unit']); 194 | grunt.registerTask('server', ['shell:server']); 195 | grunt.registerTask('e2e-test', [ 196 | 'http-server', 197 | 'shell:protractor', 198 | 'protractor_webdriver:alive', 199 | 'protractor:test']); 200 | grunt.registerTask('extern-server', ['shell:serverForTesting', 'waitServer:server', 'shell:ls']); 201 | }; 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a simple repository to demo how to use 2 | 3 | grunt + bower + requirejs + angular 4 | 5 | together, and also how to write unit tests and e2e tests for them. 6 | 7 | ## run unit tests 8 | 9 | karma start karma.conf.js 10 | 11 | ## run e2e tests 12 | 13 | webdriver-manager start 14 | npm start 15 | protractor test/protractor-conf.js 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-bower-angular-demo", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/freewind/grunt-bower-angular-demo", 5 | "authors": [ 6 | "Peng Li " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "angularjs": "~1.2.20", 18 | "jquery": "~2.1.1", 19 | "requirejs": "~2.1.14", 20 | "angular-mocks": "~1.2.21", 21 | "requirejs-plugins": "~1.0.2" 22 | }, 23 | "devDependencies": { 24 | "requirejs-text": "~2.0.12" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Jul 30 2014 13:58:11 GMT+0800 (CST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine', 'requirejs'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'test/test-main.js', 19 | {pattern: 'public/js/**/*.js', included: false}, 20 | {pattern: 'test/unit/**/*_spec.js', included: false} 21 | ], 22 | 23 | 24 | // list of files to exclude 25 | exclude: [ 26 | ], 27 | 28 | 29 | // preprocess matching files before serving them to the browser 30 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 31 | preprocessors: { 32 | }, 33 | 34 | 35 | // test results reporter to use 36 | // possible values: 'dots', 'progress' 37 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 38 | reporters: ['progress'], 39 | 40 | 41 | // web server port 42 | port: 9876, 43 | 44 | 45 | // enable / disable colors in the output (reporters and logs) 46 | colors: true, 47 | 48 | 49 | // level of logging 50 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 51 | logLevel: config.LOG_INFO, 52 | 53 | 54 | // enable / disable watching file and executing tests whenever any file changes 55 | autoWatch: true, 56 | 57 | 58 | // start these browsers 59 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 60 | browsers: ['Chrome'], 61 | 62 | 63 | // Continuous Integration mode 64 | // if true, Karma captures browsers, runs the tests and exits 65 | singleRun: true 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">= 0.10.0" 4 | }, 5 | "devDependencies": { 6 | "bower": "^1.3.8", 7 | "grunt": "~0.4.5", 8 | "grunt-bower": "^0.13.4", 9 | "grunt-bower-task": "^0.4.0", 10 | "grunt-contrib-concat": "~0.4.0", 11 | "grunt-contrib-jshint": "~0.10.0", 12 | "grunt-contrib-qunit": "~0.5.2", 13 | "grunt-contrib-uglify": "~0.5.0", 14 | "grunt-contrib-watch": "~0.6.1", 15 | "grunt-http-server": "0.0.5", 16 | "grunt-karma": "^0.8.3", 17 | "grunt-protractor-runner": "^1.1.0", 18 | "grunt-protractor-webdriver": "^0.1.8", 19 | "grunt-shell": "^0.7.0", 20 | "grunt-shell-spawn": "^0.3.0", 21 | "grunt-wait-server": "^0.1.2", 22 | "http-server": "^0.6.1", 23 | "karma": "^0.12.19", 24 | "karma-chrome-launcher": "^0.1.4", 25 | "karma-ie-launcher": "^0.1.5", 26 | "karma-jasmine": "^0.1.5", 27 | "karma-requirejs": "^0.2.2", 28 | "protractor": "^1.0.0", 29 | "requirejs": "^2.1.14" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freewind" 3 | } -------------------------------------------------------------------------------- /public/data/freewind.json: -------------------------------------------------------------------------------- 1 | { 2 | "age": 123 3 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | {{name}} 9 | Age: {{age}} 10 |
11 |
12 | {{config}} 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- 1 | define(['angular', 'app.controllers', 'appConfig'], function(angular, xxx, appConfig) { 2 | 3 | var myApp = angular.module('myApp', ['app.controllers']) 4 | .constant('appConfig', appConfig); 5 | 6 | angular.element(document).ready(function() { 7 | angular.bootstrap(document, ['myApp']); 8 | }); 9 | 10 | return myApp; 11 | }); 12 | -------------------------------------------------------------------------------- /public/js/appConfig.js: -------------------------------------------------------------------------------- 1 | define(['json!../data/config.json'], function(config) { 2 | console.dir(config); 3 | return config; 4 | }); -------------------------------------------------------------------------------- /public/js/config.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | baseUrl: '/public/js', 3 | paths: { 4 | app: 'app', 5 | hello: 'hello', 6 | hello2: 'hello2', 7 | jquery: 'lib/jquery/jquery', 8 | angular: 'lib/angularjs/angular', 9 | text: 'lib/requirejs-text/text', 10 | json: 'lib/requirejs-plugins/json', 11 | appConfig: 'appConfig', 12 | 'app.controllers': 'controllers' 13 | }, 14 | shim: { 15 | angular : { exports : 'angular'}, 16 | hello : { exports: 'hello' } 17 | }, 18 | priority: ["angular"], 19 | map: { 20 | '*': { 'jquery': 'jquery-private'}, 21 | 'jquery-private': { 'jquery': 'jquery'} 22 | } 23 | }); 24 | 25 | requirejs(['app']); 26 | -------------------------------------------------------------------------------- /public/js/controllers.js: -------------------------------------------------------------------------------- 1 | define(['angular'], function(angular) { 2 | return angular.module('app.controllers', []) 3 | .controller('MyController', ['$rootScope', '$scope', '$http', 'appConfig', 4 | function($rootScope, $scope, $http, appConfig) { 5 | $scope.name = 'Change the name'; 6 | $scope.age = 0; 7 | $http.get('/public/data/' + appConfig.name + ".json") 8 | .success(function(data) { 9 | $scope.age=data.age; 10 | }) 11 | }]) 12 | .controller('ConfigController', ['$scope', function($scope) { 13 | 14 | }]); 15 | }); 16 | -------------------------------------------------------------------------------- /public/js/hello.js: -------------------------------------------------------------------------------- 1 | function hello() { 2 | return "hello, world"; 3 | } 4 | -------------------------------------------------------------------------------- /public/js/hello2.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | name: "freewind" 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /public/js/jquery-private.js: -------------------------------------------------------------------------------- 1 | define(['jquery'], function(jq) { 2 | return jQuery.noConflict(true); 3 | }); 4 | -------------------------------------------------------------------------------- /test/e2e/controller_spec.js: -------------------------------------------------------------------------------- 1 | describe('input', function() { 2 | it('should update the label aside', function() { 3 | browser.get('http://localhost:8081/public/index.html'); 4 | var nameInput = element(by.model('name')); 5 | nameInput.clear(); 6 | nameInput.sendKeys('test-value'); 7 | expect(element(by.binding('name')).getText()).toEqual("test-value"); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/e2e/juliemr_spec.js: -------------------------------------------------------------------------------- 1 | describe('angular homepage', function() { 2 | it('should have a title', function() { 3 | browser.get('http://juliemr.github.io/protractor-demo/'); 4 | expect(browser.getTitle()).toEqual('Super Calculator'); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /test/protractor-conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | seleniumAddress: 'http://localhost:4444/wd/hub', 3 | specs: ['e2e/*_spec.js'] 4 | } 5 | -------------------------------------------------------------------------------- /test/test-main.js: -------------------------------------------------------------------------------- 1 | var allTestFiles = []; 2 | var TEST_REGEXP = /(spec|test)\.js$/i; 3 | 4 | var pathToModule = function(path) { 5 | return path.replace(/^\/base\//, '').replace(/\.js$/, ''); 6 | }; 7 | 8 | Object.keys(window.__karma__.files).forEach(function(file) { 9 | if (TEST_REGEXP.test(file)) { 10 | // Normalize paths to RequireJS module names. 11 | allTestFiles.push(pathToModule(file)); 12 | } 13 | }); 14 | 15 | require.config({ 16 | // Karma serves files under /base, which is the basePath from your config file 17 | baseUrl: '/base/', 18 | 19 | paths: { 20 | app: 'public/js/app', 21 | hello: 'public/js/hello', 22 | hello2: 'public/js/hello2', 23 | jquery: 'public/js/lib/jquery/jquery', 24 | angular: 'public/js/lib/angular/angular', 25 | 'angularMocks': 'public/js/lib/angular-mocks/angular-mocks', 26 | 'app.controllers': 'public/js/controllers' 27 | }, 28 | shim: { 29 | angular : { exports : 'angular'}, 30 | hello : { exports: 'hello' }, 31 | 'angularMocks': { 32 | deps:['angular'], 33 | 'exports':'angular.mock' 34 | } 35 | }, 36 | priority: ["angular"], 37 | map: { 38 | '*': { 'jquery': 'jquery-private'}, 39 | 'jquery-private': { 'jquery': 'jquery'} 40 | }, 41 | 42 | // dynamically load all test files 43 | deps: allTestFiles, 44 | 45 | // we have to kickoff jasmine, as it is asynchronous 46 | callback: window.__karma__.start 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/controller_spec.js: -------------------------------------------------------------------------------- 1 | define(['angular', 'angularMocks', 'app.controllers'], function(angular, mocks, appModule) { 2 | describe('MyController', function() { 3 | 4 | var MyController, scope; 5 | 6 | beforeEach(function() { 7 | mocks.module('app.controllers'); 8 | mocks.inject(function($rootScope, $controller) { 9 | scope = $rootScope.$new(); 10 | MyController = $controller('MyController', { 11 | $scope: scope 12 | }); 13 | }); 14 | }); 15 | 16 | it('should have added name to $scope', function() { 17 | expect(scope.name).toBe("Change the name"); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/unit/hello2_spec.js: -------------------------------------------------------------------------------- 1 | define(['hello2'], function(hello2) { 2 | describe("hello2", function() { 3 | it("will return name of freewind", function() { 4 | expect(hello2.name).toBe("freewind"); 5 | }); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /test/unit/hello_spec.js: -------------------------------------------------------------------------------- 1 | define(['hello'], function(he) { 2 | describe('hello', function() { 3 | 4 | it('hello should be true', function() { 5 | expect(he()).toBe("hello, world"); 6 | }); 7 | 8 | }); 9 | }); 10 | --------------------------------------------------------------------------------