From e9e750ef4c0628ef993d0a527f8ca35220f4f37f Mon Sep 17 00:00:00 2001 From: Timur Sufiev Date: Thu, 23 Apr 2015 21:35:51 +0300 Subject: [PATCH] Add unit-tests for merlin directives To simplify templates serving the plugin karma-ng-html2js-preprocessor has been added to Karma and project dependencies. Change-Id: If947acd2e9d7e64838c31e406d04fa9c1c67c770 Implements: blueprint merlin-unittests --- extensions/mistral/test/js/workbookSpec.js | 4 + karma-unit.conf.js | 85 +++++---- merlin/static/merlin/js/merlin.init.js | 21 ++- merlin/test/js/directivesSpec.js | 195 +++++++++++++++++++++ merlin/test/js/templatesSpec.js | 2 +- package.json | 3 +- 6 files changed, 265 insertions(+), 45 deletions(-) create mode 100644 merlin/test/js/directivesSpec.js diff --git a/extensions/mistral/test/js/workbookSpec.js b/extensions/mistral/test/js/workbookSpec.js index bc0fca0..b8b6d9d 100644 --- a/extensions/mistral/test/js/workbookSpec.js +++ b/extensions/mistral/test/js/workbookSpec.js @@ -26,6 +26,8 @@ describe('workbook model logic', function() { }); function getWorkflow(workflowID) { + // once workflow is recreated with JSON, old instance is no longer + // valid, thus we need to get it this way return workbook.get('workflows').getByID(workflowID); } @@ -62,6 +64,8 @@ describe('workbook model logic', function() { taskID = 'task1'; function getTask(taskID) { + // once task is recreated with JSON, old instance is no longer + // valid, thus we need to get it this way return getWorkflow(workflowID).get('tasks').getByID(taskID); } diff --git a/karma-unit.conf.js b/karma-unit.conf.js index d30d296..b210872 100644 --- a/karma-unit.conf.js +++ b/karma-unit.conf.js @@ -15,50 +15,63 @@ */ module.exports = function (config) { - 'use strict'; + 'use strict'; - config.set({ + config.set({ - port: 9876, + port: 9876, - basePath: '', + basePath: '', - frameworks: ['jasmine'], + frameworks: ['jasmine'], - browsers: [ 'PhantomJS'], + browsers: [ 'PhantomJS'], - plugins: [ - 'karma-jasmine', - 'karma-phantomjs-launcher', - ], + plugins: [ + 'karma-jasmine', + 'karma-phantomjs-launcher', + 'karma-ng-html2js-preprocessor' + ], - files: [ - 'bower_components/angular/angular.js', - 'bower_components/angular-mocks/angular-mocks.js', - 'merlin/static/merlin/js/lib/underscore-min.js', - 'merlin/static/merlin/js/merlin.init.js', - 'merlin/static/merlin/js/merlin.templates.js', - 'merlin/static/merlin/js/merlin.directives.js', - 'merlin/static/merlin/js/merlin.filters.js', - 'merlin/static/merlin/js/merlin.field.models.js', - 'merlin/static/merlin/js/merlin.panel.models.js', - 'merlin/static/merlin/js/merlin.utils.js', - 'merlin/static/merlin/js/lib/angular-filter.js', - 'merlin/static/merlin/js/lib/barricade.js', - 'merlin/static/merlin/js/lib/js-yaml.js', - 'merlin/test/js/utilsSpec.js', - 'merlin/test/js/templatesSpec.js', - 'merlin/test/js/filtersSpec.js', + files: [ + 'bower_components/angular/angular.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'merlin/static/merlin/js/lib/underscore-min.js', + 'merlin/static/merlin/js/merlin.init.js', + 'merlin/static/merlin/js/merlin.templates.js', + 'merlin/static/merlin/js/merlin.directives.js', + 'merlin/static/merlin/js/merlin.filters.js', + 'merlin/static/merlin/js/merlin.field.models.js', + 'merlin/static/merlin/js/merlin.panel.models.js', + 'merlin/static/merlin/js/merlin.utils.js', + 'merlin/static/merlin/js/lib/angular-filter.js', + 'merlin/static/merlin/js/lib/barricade.js', + 'merlin/static/merlin/js/lib/js-yaml.js', + 'merlin/static/merlin/templates/**/*.html', +// 'merlin/static/merlin/templates/fields/*.html', + 'merlin/test/js/utilsSpec.js', + 'merlin/test/js/templatesSpec.js', + 'merlin/test/js/filtersSpec.js', + 'merlin/test/js/directivesSpec.js', - 'extensions/mistral/static/mistral/js/mistral.init.js', - 'extensions/mistral/static/mistral/js/mistral.workbook.models.js', - 'extensions/mistral/static/mistral/js/mistral.workbook.controllers.js', - 'extensions/mistral/test/js/workbookSpec.js' - ], + 'extensions/mistral/static/mistral/js/mistral.init.js', + 'extensions/mistral/static/mistral/js/mistral.workbook.models.js', + 'extensions/mistral/static/mistral/js/mistral.workbook.controllers.js', + 'extensions/mistral/test/js/workbookSpec.js' + ], - exclude: [ - ], + preprocessors: { + 'merlin/static/merlin/templates/**/*.html': ['ng-html2js'] + }, - singleRun: true - }); + ngHtml2JsPreprocessor: { + stripPrefix: 'merlin', + moduleName: 'preprocessedTemplates' + }, + + exclude: [ + ], + + singleRun: true +}); }; diff --git a/merlin/static/merlin/js/merlin.init.js b/merlin/static/merlin/js/merlin.init.js index a4d6345..4ae70e1 100644 --- a/merlin/static/merlin/js/merlin.init.js +++ b/merlin/static/merlin/js/merlin.init.js @@ -5,12 +5,19 @@ 'use strict'; angular.module('merlin', []) - .run(['merlin.templates', function(templates) { - templates.prefetch('/static/merlin/templates/fields/', - ['dictionary', 'frozendict', 'list', 'string', 'text', 'group', 'number', - 'choices' - ] - ); - }]) + .config(function($interpolateProvider) { + // Replacing the default angular symbol + // allow us to mix angular with django templates + $interpolateProvider.startSymbol('{$'); + $interpolateProvider.endSymbol('$}'); + }) + // move these 2 values out of run section to change them in unit-tests + .value('fieldTemplatesUrl', '/static/merlin/templates/fields/') + .value('fieldTemplates', ['dictionary', 'frozendict', 'list', + 'string', 'text', 'group', 'number', 'choices']) + .run(['merlin.templates', 'fieldTemplatesUrl', 'fieldTemplates', + function(templates, rootUrl, fieldList) { + templates.prefetch(rootUrl, fieldList); + }]) })(); \ No newline at end of file diff --git a/merlin/test/js/directivesSpec.js b/merlin/test/js/directivesSpec.js new file mode 100644 index 0000000..da6579a --- /dev/null +++ b/merlin/test/js/directivesSpec.js @@ -0,0 +1,195 @@ + + /* Copyright (c) 2015 Mirantis, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. +*/ +describe('merlin directives', function() { + 'use strict'; + + var $compile, $scope, $httpBackend; + + beforeEach(function() { + module('merlin', function($provide) { + $provide.value('fieldTemplates', ['number', 'text']); + }); + module('preprocessedTemplates'); + }); + + beforeEach(inject(function(_$compile_, _$rootScope_, _$httpBackend_, _$templateCache_) { + $compile = _$compile_; + $scope = _$rootScope_.$new(); + $httpBackend = _$httpBackend_; + $httpBackend.whenGET('/static/merlin/templates/fields/text.html').respond( + 200, _$templateCache_.get('/static/merlin/templates/fields/text.html')); + $httpBackend.whenGET('/static/merlin/templates/fields/number.html').respond( + 200, _$templateCache_.get('/static/merlin/templates/fields/number.html')); + })); + + describe('', function() { + function getPanelHeading(panelElem) { + var div = panelElem.children().children().eq(0); + return div.hasClass('panel-heading') && div; + } + + function getPanelRemoveButton(panelElem) { + var iTag = panelElem.find('i').eq(0); + return iTag.hasClass('fa-times-circle') && iTag; + } + + function getPanelBody(panelElem) { + var div = panelElem.children().children().eq(1).children(); + return div.hasClass('panel-body') && div; + } + + function makePanelElem(contents) { + return $compile('')($scope); + } + + it('shows panel heading when and only when title is passed via attr', function() { + var title = 'My Panel', + element1 = makePanelElem('title="' + title +'"'), + element2 = makePanelElem(''); + $scope.$digest(); + + expect(getPanelHeading(element1).hasClass('ng-hide')).toBe(false); + expect(element1.html()).toContain(title); + expect(getPanelHeading(element2).hasClass('ng-hide')).toBe(true); + }); + + it('requires both `title` and `removable` to be removable', function() { + var title = 'My Panel', + element1, element2; + + element1 = makePanelElem('title="' + title +'" removable="true"'); + element2 = makePanelElem('title="' + title +'"'); + $scope.$digest(); + + expect(getPanelRemoveButton(element1).hasClass('ng-hide')).toBe(false); + expect(getPanelRemoveButton(element2).hasClass('ng-hide')).toBe(true); + }); + + it('with `on-remove`, but without `removable` is not removable', function() { + var title = 'My Panel', + element1, element2; + + $scope.remove = function() {}; + element1 = makePanelElem( + 'title="' + title +'" removable="true" on-remove="remove()"'); + element2 = makePanelElem('title="' + title +'" on-remove="remove()"'); + $scope.$digest(); + + expect(getPanelRemoveButton(element1).hasClass('ng-hide')).toBe(false); + expect(getPanelRemoveButton(element2).hasClass('ng-hide')).toBe(true); + }); + + it('contents are inserted into div.panel-body tag', function() { + var element = $compile('')($scope); + $scope.$digest(); + + expect(getPanelBody(element).find('span').hasClass('inner')).toBe(true); + }); + + }); + + describe('', function() { + function getGroupBody(groupElem) { + var div = groupElem.children().children().eq(1); + return div.hasClass('collapse') && div; + } + + function getGroupRemoveBtn(groupElem) { + var div = groupElem.children().children().eq(0).children().eq(2); + return div.hasClass('add-btn') && div; + } + + function getGroupAddBtn(groupElem) { + var div = groupElem.children().children().eq(0).children().eq(1); + return div.hasClass('add-btn') && div; + } + + function makeGroupElement(contents) { + return $compile( + '')($scope); + } + + it('requires to specify just `on-remove` to make group removable', function() { + var element1, element2; + $scope.remove = function() {}; + element1 = makeGroupElement(''); + element2 = makeGroupElement('on-remove="remove()"'); + $scope.$digest(); + + expect(getGroupRemoveBtn(element1).hasClass('ng-hide')).toBe(true); + expect(getGroupRemoveBtn(element2).hasClass('ng-hide')).toBe(false); + }); + + it('requires to specify `on-add` to make group additive', function() { + var element1, element2; + $scope.add = function() {}; + element1 = makeGroupElement(''); + element2 = makeGroupElement('on-add="add()"'); + $scope.$digest(); + + expect(getGroupAddBtn(element1).hasClass('ng-hide')).toBe(true); + expect(getGroupAddBtn(element2).hasClass('ng-hide')).toBe(false); + }); + + it('`additive` attribute set explicitly to `false` makes group not additive', function() { + var element; + $scope.add = function() {}; + element = makeGroupElement('on-add="add()" additive="false"'); + $scope.$digest(); + + expect(getGroupAddBtn(element).hasClass('ng-hide')).toBe(true); + }); + + it('contents are inserted into div.collapse tag', function() { + var element = $compile( + '')($scope); + $scope.$digest(); + + expect(getGroupBody(element).find('span').hasClass('inner')).toBe(true); + }); + + }); + + describe('', function() { + function makeFieldElem(contents) { + return $compile( + '
')($scope); + } + + it('type of resulting field is determined by `type` attribute', function() { + var element1, element2; + $scope.value1 = {type: 'text'}; + $scope.value2 = {type: 'number'}; + element1 = makeFieldElem('value="value1" type="{$ value1.type $}"'); + element2 = makeFieldElem('value="value2" type="{$ value2.type $}"'); + $httpBackend.flush(); + $scope.$digest(); + + expect(element1.html()).toContain('