From 6f8676dd741624eb0d9b3d1016a2551c92bd57f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Thu, 12 Jan 2017 16:09:31 +0100 Subject: [PATCH] Add karma, Jasmine, deps and karma.conf.js. Fixes #1291 (#1293) * Add karma, Jasmine, deps and karma.conf.js. Fixes #1291 * Improved service worker code * npm start script executes gulp watch task. * Written a gulp task to compile templates AOT. Get rid of loading deps in unit tests and a preprocessor. * PhantomJS package is deprecated. Use phantomjs-prebuilt instead. * Adjust `npm run test`. --- .gitignore | 5 +- gulpfile.js | 31 ++++++ karma.conf.js | 111 ++++++++++++++++++++++ package.json | 9 +- test/unit/AppLangSelectControllerSpec.js | 50 ++++++++++ test/unit/AppWelcomeControllerSpec.js | 50 ++++++++++ test/unit/PhonebookContactsServiceSpec.js | 56 +++++++++++ test/unit/chatTitleFilterSpec.js | 31 ++++++ test/unit/myHeadDirective.js | 23 +++++ test/unit/myLangFooterDirective.js | 30 ++++++ test/unit/userFirstNameFilterSpec.js | 42 ++++++++ test/unit/userNameFilterSpec.js | 42 ++++++++ 12 files changed, 478 insertions(+), 2 deletions(-) create mode 100644 karma.conf.js create mode 100644 test/unit/AppLangSelectControllerSpec.js create mode 100644 test/unit/AppWelcomeControllerSpec.js create mode 100644 test/unit/PhonebookContactsServiceSpec.js create mode 100644 test/unit/chatTitleFilterSpec.js create mode 100644 test/unit/myHeadDirective.js create mode 100644 test/unit/myLangFooterDirective.js create mode 100644 test/unit/userFirstNameFilterSpec.js create mode 100644 test/unit/userNameFilterSpec.js diff --git a/.gitignore b/.gitignore index 3f5b292a..073d836c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .idea .publish *.sublime-workspace +*.*~ +*.swp # Node.js package manager node_modules @@ -12,4 +14,5 @@ releases webogram*.zip app/js/templates.js app/css -cldr \ No newline at end of file +cldr +coverage/ diff --git a/gulpfile.js b/gulpfile.js index 8ac491b0..07d511a3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,6 +8,7 @@ var st = require('st') var del = require('del') var runSequence = require('run-sequence') var swPrecache = require('sw-precache') +var Server = require('karma').Server // The generated file is being created at src @@ -21,6 +22,7 @@ gulp.task('templates', function () { })) .pipe(gulp.dest('app/js')) }) + gulp.task('clean-templates', function () { return del(['app/js/templates.js']) }) @@ -275,6 +277,35 @@ gulp.task('bump', ['bump-version-manifests', 'bump-version-config'], function () gulp.start('bump-version-comments') }) +// Single run of karma +gulp.task('karma-single', function (done) { + new Server({ + configFile: __dirname + '/karma.conf.js', + singleRun: true + }, done).start(); +}); + +// Continuous testing with karma by watching for changes +gulp.task('karma-tdd', function (done) { + new Server({ + configFile: __dirname + '/karma.conf.js', + }, done).start(); +}); + +gulp.task('test', function (callback) { + runSequence( + ['templates', 'karma-single'], + callback + ) +}); + +gulp.task('tdd', function (callback) { + runSequence( + ['templates', 'karma-tdd'], + callback + ) +}); + gulp.task('build', ['clean'], function (callback) { runSequence( ['less', 'templates'], diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 00000000..359fa2cf --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,111 @@ +// Karma configuration +// Generated on Thu Dec 15 2016 21:12:39 GMT+0100 (CET) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: 'app', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + // Load files to access global variables + files: [ + {pattern: 'js/lib/polyfill.js', watched: false}, + {pattern: 'vendor/jquery/jquery.min.js', watched: false}, + {pattern: 'js/lib/config.js', watched: false}, + {pattern: 'vendor/jquery.nanoscroller/nanoscroller.js', watched: false}, + {pattern: 'vendor/angular/angular.js', watched: false}, + {pattern: 'vendor/angular/angular-mocks.js', watched: false}, + {pattern: 'vendor/angular/angular-route.js', watched: false}, + {pattern: 'vendor/angular/angular-animate.js', watched: false}, + {pattern: 'vendor/angular/angular-sanitize.js', watched: false}, + {pattern: 'vendor/angular/angular-touch.js', watched: false}, + {pattern: 'vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.js', watched: false}, + {pattern: 'vendor/angular-media-player/angular-media-player.js', watched: false}, + {pattern: 'vendor/jsbn/jsbn_combined.js', watched: false}, + {pattern: 'vendor/cryptoJS/crypto.js', watched: false}, + {pattern: 'vendor/rusha/rusha.js', watched: false}, + {pattern: 'vendor/zlib/gunzip.min.js', watched: false}, + {pattern: 'vendor/closure/long.js', watched: false}, + {pattern: 'vendor/leemon_bigint/bigint.js', watched: false}, + {pattern: 'vendor/libwebpjs/libwebp-0.2.0.js', watched: false}, + {pattern: 'vendor/angularjs-toaster/toaster.js', watched: false}, + {pattern: 'vendor/clipboard/clipboard.js', watched: false}, + {pattern: 'js/lib/utils.js', watched: false}, + {pattern: 'js/lib/bin_utils.js', watched: false}, + {pattern: 'js/lib/tl_utils.js', watched: false}, + {pattern: 'js/lib/ng_utils.js', watched: false}, + {pattern: 'js/lib/i18n.js', watched: false}, + {pattern: 'js/lib/mtproto.js', watched: false}, + {pattern: 'js/lib/mtproto_wrapper.js', watched: false}, + 'js/templates.js', // Generated by `gulp templates` + 'js/services.js', + 'js/filters.js', + 'js/controllers.js', + 'js/directives.js', + '../test/unit/*.js' + ], + + + // list of files to exclude + exclude: [ + 'js/background.js' + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'js/*.js': ['coverage'], + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress', 'coverage'], + + + coverageReporter: { + type: 'text', + dir: 'coverage/' + }, + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + }) +} diff --git a/package.json b/package.json index 0b7f7634..8af925b8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "show": true }, "scripts": { - "start": "node server.js" + "start": "gulp watch", + "test": "gulp test" }, "repository": { "type": "git", @@ -65,6 +66,12 @@ "gulp-util": "^3.0.7", "gulp-zip": "^0.1.2", "http": "0.0.0", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-coverage": "^1.1.1", + "karma-jasmine": "^1.1.0", + "karma-phantomjs-launcher": "^1.0.2", + "phantomjs-prebuilt": "^2.1.14", "run-sequence": "^1.0.2", "st": "^0.5.2", "sw-precache": "^3.2.0" diff --git a/test/unit/AppLangSelectControllerSpec.js b/test/unit/AppLangSelectControllerSpec.js new file mode 100644 index 00000000..4466efa2 --- /dev/null +++ b/test/unit/AppLangSelectControllerSpec.js @@ -0,0 +1,50 @@ +describe('AppLangSelectController', function () { + var $controller, $scope; + + beforeEach(module('ui.bootstrap')); + beforeEach(module('myApp.services')); + beforeEach(module('myApp.controllers')); + + beforeEach(function () { + inject(function (_$controller_, _$rootScope_, _, Storage, ErrorService, AppRuntimeManager) { + $controller = _$controller_; + $scope = _$rootScope_.$new() + $controller('AppLangSelectController', { + $scope: $scope, + _: _, + Storage: Storage, + ErrorService: ErrorService, + AppRuntimeManager: AppRuntimeManager + }); + }); + }); + + it('holds the supportedLocales', function () { + expect($scope.supportedLocales).toBeDefined(); + }); + + it('holds langNames', function () { + expect($scope.langNames).toBeDefined(); + }); + + it('holds the current locale', function () { + expect($scope.curLocale).toBeDefined(); + }); + + it('has a locale form', function () { + expect($scope.form).toBeDefined(); + expect($scope.form.locale).toBeDefined(); + }); + + it('allows to select a locale', function () { + expect($scope.localeSelect).toBeDefined(); + }); + + describe('when the user switches the locale', function () { + describe('and confirms the dialogue', function () { + xit('reloads the app', function (done) { + done(); + }); + }); + }); +}); diff --git a/test/unit/AppWelcomeControllerSpec.js b/test/unit/AppWelcomeControllerSpec.js new file mode 100644 index 00000000..68a56414 --- /dev/null +++ b/test/unit/AppWelcomeControllerSpec.js @@ -0,0 +1,50 @@ +describe('AppWelcomeController', function () { + var $controller, $rootScope, $scope, $location, MtApiManager, ErrorService, + ChangelogNotifyService, LayoutSwitchService; + + beforeEach(module('myApp.controllers')); + + beforeEach(function () { + ChangelogNotifyService = { + checkUpdate: function () {} + }; + + LayoutSwitchService = { + start: function () {} + }; + + MtpApiManager = { + getUserID: function () { + return { + then: function () {} + }; + } + }; + + module(function ($provide) { + $provide.value('MtpApiManager', MtpApiManager); + }); + + inject(function (_$controller_, _$rootScope_, _$location_) { + $controller = _$controller_; + $rootScope = _$rootScope_; + $location = _$location_; + + $scope = $rootScope.$new(); + $controller('AppWelcomeController', { + $scope: $scope, + $location: $location, + MtpApiManager: MtpApiManager, + ErrorService: ErrorService, + ChangelogNotifyService: ChangelogNotifyService, + LayoutSwitchService: LayoutSwitchService + }); + }); + }); + + // https://stackoverflow.com/a/36460924 + it('executes a dummy spec', function (done) { + expect(true).toBe(true); + done(); + }); +}); diff --git a/test/unit/PhonebookContactsServiceSpec.js b/test/unit/PhonebookContactsServiceSpec.js new file mode 100644 index 00000000..48607aa7 --- /dev/null +++ b/test/unit/PhonebookContactsServiceSpec.js @@ -0,0 +1,56 @@ +describe('PhonebookContactsService', function () { + var PhonebookContactsService; + + beforeEach(module('ui.bootstrap')); + beforeEach(module('myApp.services')); + + beforeEach(inject(function (_PhonebookContactsService_) { + PhonebookContactsService = _PhonebookContactsService_; + })); + + describe('Public API:', function () { + it('checks availability', function () { + expect(PhonebookContactsService.isAvailable).toBeDefined(); + }); + + it('open the phonebook for import', function () { + expect(PhonebookContactsService.openPhonebookImport).toBeDefined(); + }); + + it('get phonebook contacts', function () { + expect(PhonebookContactsService.getPhonebookContacts).toBeDefined(); + }); + + describe('usage', function () { + describe('of isAvailable()', function () { + it('returns false in most cases', function (done) { + expect(PhonebookContactsService.isAvailable()).toBe(false); + done(); + }); + }); + + describe('of openPhonebookImport()', function () { + beforeEach(function() { + $modal = { + open: jasmine.createSpy('open') + }; + }); + + xit('opens a modal', function () { + PhonebookContactsService.openPhonebookImport(); + expect($modal.open).toHaveBeenCalled(); + }); + }); + + describe('of getPhonebookContacts()', function () { + xit('will get rejected in most cases', function (done) { + promise = PhonebookContactsService.getPhonebookContacts(); + promise.finally(function () { + expect(promise.isFullfilled()).toBe(true); + done(); + }); + }); + }); + }); + }); +}); diff --git a/test/unit/chatTitleFilterSpec.js b/test/unit/chatTitleFilterSpec.js new file mode 100644 index 00000000..1ba7a9c9 --- /dev/null +++ b/test/unit/chatTitleFilterSpec.js @@ -0,0 +1,31 @@ +describe('chatTitle filter', function () { + var $filter, _, chatTitleFilter; + + beforeEach(module('myApp.filters')); + + beforeEach(inject(function (_$filter_, ___) { + $filter = _$filter_; + _ = ___; + })); + + beforeEach(function () { + chatTitleFilter = $filter('chatTitle'); + }); + + it('displays chat title deleted', function () { + var expected = _('chat_title_deleted'); + var actual = chatTitleFilter(null); + + expect(actual).toEqual(expected); + }); + + it('displays the chat title', function () { + var chat = { + title: 'Telegraph is hot!' + }; + var expected = chat.title; + var actual = chatTitleFilter(chat); + + expect(actual).toEqual(expected); + }); +}); diff --git a/test/unit/myHeadDirective.js b/test/unit/myHeadDirective.js new file mode 100644 index 00000000..a379b0e4 --- /dev/null +++ b/test/unit/myHeadDirective.js @@ -0,0 +1,23 @@ +describe('myHead directive', function () { + var $compile, $rootScope; + + beforeEach(module('myApp.templates')); + beforeEach(module('myApp.directives')); + + beforeEach(inject(function (_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + })); + + it('compiles a my-head attribute', function () { + var compiledElement = $compile('
')($rootScope); + $rootScope.$digest(); // Fire watchers + expect(compiledElement.html()).toContain('tg_page_head'); + }); + + it('compiles a my-head element', function () { + var compiledElement = $compile('')($rootScope); + $rootScope.$digest(); // Fire watchers + expect(compiledElement.html()).toContain('tg_page_head'); + }); +}); diff --git a/test/unit/myLangFooterDirective.js b/test/unit/myLangFooterDirective.js new file mode 100644 index 00000000..36cb0051 --- /dev/null +++ b/test/unit/myLangFooterDirective.js @@ -0,0 +1,30 @@ +describe('myLangFooter directive', function () { + var $compile, $rootScope; + + beforeEach(module('ui.bootstrap')); + beforeEach(module('myApp.templates')); + // ErrorServiceProvider in myApp.services is needed by + // AppLangSelectController in myApp.controllers + beforeEach(module('myApp.services')); + beforeEach(module('myApp.controllers')); + beforeEach(module('myApp.directives')); + + beforeEach(inject(function (_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + })); + + it('compiles a my-lang-footer attribute', function () { + var compiledElement = $compile('
')($rootScope); + $rootScope.$digest(); // Fire watchers + expect(compiledElement.html()).toContain('footer_lang_link'); + expect(compiledElement.html()).toContain('AppLangSelectController'); + }); + + it('compiles a my-lang-footer element', function () { + var compiledElement = $compile('')($rootScope); + $rootScope.$digest(); // Fire watchers + expect(compiledElement.html()).toContain('footer_lang_link'); + expect(compiledElement.html()).toContain('AppLangSelectController'); + }); +}); diff --git a/test/unit/userFirstNameFilterSpec.js b/test/unit/userFirstNameFilterSpec.js new file mode 100644 index 00000000..8f6dd63a --- /dev/null +++ b/test/unit/userFirstNameFilterSpec.js @@ -0,0 +1,42 @@ +describe('userFirstName filter', function () { + var $filter, _, userFirstNameFilter; + + beforeEach(module('myApp.filters')); + + beforeEach(inject(function (_$filter_, ___) { + $filter = _$filter_; + _ = ___; + })); + + beforeEach(function () { + userFirstNameFilter = $filter('userFirstName'); + }); + + it('displays user first name deleted', function () { + var expected = _('user_first_name_deleted'); + var actual = userFirstNameFilter(null); + + expect(actual).toEqual(expected); + }); + + it('displays the first name', function () { + var user = { + first_name: 'John' + }; + var expected = user.first_name; + var actual = userFirstNameFilter(user); + + expect(actual).toEqual(expected); + }); + + it('displays the last name alternatively', function () { + var user = { + last_name: 'Doe' + }; + var expected = user.last_name; + var actual = userFirstNameFilter(user); + + expect(actual).toEqual(expected); + }); + +}); diff --git a/test/unit/userNameFilterSpec.js b/test/unit/userNameFilterSpec.js new file mode 100644 index 00000000..78ccfe14 --- /dev/null +++ b/test/unit/userNameFilterSpec.js @@ -0,0 +1,42 @@ +describe('userName filter', function () { + var $filter, _, userNameFilter; + + beforeEach(module('myApp.filters')); + + beforeEach(inject(function (_$filter_, ___) { + $filter = _$filter_; + _ = ___; + })); + + beforeEach(function () { + userNameFilter = $filter('userName'); + }); + + it('displays user name deleted', function () { + var expected = _('user_name_deleted'); + var actual = userNameFilter(null); + + expect(actual).toEqual(expected); + }); + + it('displays the first name', function () { + var user = { + first_name: 'John' + }; + var expected = user.first_name; + var actual = userNameFilter(user); + + expect(actual).toEqual(expected); + }); + + it('displays both, the first and the last name', function () { + var user = { + first_name: 'John', + last_name: 'Doe' + }; + var expected = user.first_name + ' ' + user.last_name; + var actual = userNameFilter(user); + + expect(actual).toEqual(expected); + }); +});