Merge "Refactor angular cloud services utilities"

This commit is contained in:
Jenkins 2015-08-25 13:08:51 +00:00 committed by Gerrit Code Review
commit ab659d20e0
21 changed files with 589 additions and 641 deletions

@ -0,0 +1,143 @@
/**
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
angular
.module('horizon.framework.util.promise-toggle')
.directive('hzPromiseToggleTemplate', hzPromiseToggleTemplate);
/**
* @ngdoc directive
* @name horizon.framework.util.promise-toggle:directive:hzPromiseToggleTemplate
* @module horizon.framework.util.promise-toggle
* @description
*
* A template directive that can be used for creating additional directives
* which will only compile their transcluded content if all of the promises
* associated with the directive resolve.
*
* This supports declarative directives that compile in their
* content only after the related promises resolve. This is typically
* intended to be used for very fast checks (often cached) of whether or
* not a particular setting or required service is enabled. This should not
* be used for checks that have frequently changing content, because this
* is evaluated once per page load.
*
* The actual name of whatever directive this is after being extended
* (angular.extend) should be set to the input that will be passed
* into the promise resolver. The promise resolver will either resolve
* or reject the promise. If it resolves, the content inside of the
* element will be linked in. Otherwise, it will be removed completely.
* When the input is an array, each element of the array will be treated
* as a distinct input and a single promise resolver will be invoked for
* each input element. When that is done, all promises must resolve in
* order for the content to be linked in. If any of them are rejected,
* the element will be removed.
*
* When extending, the name and singlePromiseResolver must be specified.
* Other properties may also be overridden for additional customization.
*
* @example
*
* To use this, simply create a concrete directive using angular.extend:
*
* angular
* .module('horizon.framework.util.promise-toggle')
* .directive('hzPromiseToggleMock', hzPromiseToggleMock);
*
* hzPromiseToggleMock.$inject = [
* 'hzPromiseToggleTemplateDirective',
* 'mockService'
* ];
*
* function hzPromiseToggleMock(hzPromiseToggleTemplateDirective, mockService) {
* return angular.extend(
* hzPromiseToggleTemplateDirective[0],
* {
* singlePromiseResolver: mockService.mockResolver,
* name: 'hzPromiseToggleMock'
* }
* );
* }
*
* Then in the HMTL:
*
* <div hz-promize-toggle-mock='["Hello"]'>Your content </div>
* For single input / single promise resolution:
*
* <div hz-promize-toggle-mock='["Hello", "World"]'>Your content </div>
* For multiple input / multiple promise resolution:
*
*/
function hzPromiseToggleTemplate($q, $parse) {
var directive = {
name: null,
singlePromiseResolver: null,
transclude: 'element',
priority: 2000,
terminal: true,
restrict: 'A',
compile: compile,
$$tlb: true
};
return directive;
////////////////
function compile(element, attrs, linker) {
var input = $parse(attrs[this.name]);
var singlePromiseResolver = this.singlePromiseResolver;
return resolvePromises;
////////////////
function resolvePromises(scope, iterStartElement) {
var resolvedInput = input(scope);
var promiseResolver = angular.isArray(resolvedInput) ?
multiPromiseResolver(singlePromiseResolver, resolvedInput) :
singlePromiseResolver(resolvedInput);
promiseResolver.then(linkContent, removeContent);
function linkContent() {
linker(scope, function (clone) {
iterStartElement.after(clone);
});
}
function removeContent() {
element.remove();
}
function multiPromiseResolver(resolver, arrayInput) {
// Resolves each individual input against the promise resolver.
// If any fail, all will fail.
return $q.all(
arrayInput.map(function (singleInput) {
return resolver(singleInput);
})
);
}
}
}
}
})();

@ -0,0 +1,62 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
angular
.module('horizon.framework.util.promise-toggle')
.directive('hzPromiseToggleMock', hzPromiseToggleMock);
hzPromiseToggleMock.$inject = [
'hzPromiseToggleTemplateDirective',
'mockService'
];
/**
* @ngdoc directive
* @name horizon.framework.util.promise-toggle:hzPromiseToggleMock
* @module horizon.framework.util.promise-toggle
* @description
*
* This allows testing the promise toggle directive in the way
* that it is intended to be used. It also provides a usage example.
*
* @example
*
```html
<div hz-promize-toggle-mock='["config_drive"]'>
<div class="checkbox customization-script-source">
<label>
<input type="checkbox"
ng-model="model.newInstanceSpec.config_drive">
{$ ::label.configurationDrive $}
</label>
</div>
</div>
```
*/
function hzPromiseToggleMock(hzPromiseToggleTemplateDirective, mockService) {
return angular.extend(
hzPromiseToggleTemplateDirective[0],
{
singlePromiseResolver: mockService.mockResolver,
name: 'hzPromiseToggleMock'
}
);
}
})();

@ -0,0 +1,145 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.framework.util.promise-toggle', function () {
describe('directive:hz-promise-toggle-mock', function () {
var $compile, $q, $scope, mockService, baseElement;
var $mockHtmlInput = '$mock-input';
var baseHtml = [
'<div>',
'<div hz-promise-toggle-mock=\'$mock-input\'>',
'<div class="child-element">',
'</div>',
'</div>',
'</div>'
].join('');
beforeEach(function() {
mockService = {
mockResolver: function(shouldResolve) {
var deferred = $q.defer();
if (shouldResolve === 'true') {
deferred.resolve();
} else {
deferred.reject();
}
return deferred.promise;
}
};
spyOn(mockService, 'mockResolver').and.callThrough();
module('horizon.framework.util.promise-toggle', function ($provide) {
$provide.value('mockService', mockService);
});
inject(function (_$compile_, _$q_, _$rootScope_) {
$compile = _$compile_;
$q = _$q_;
$scope = _$rootScope_.$new();
baseElement = null;
});
});
it('should evaluate the given attribute name', function () {
$scope.test = {
retVal: 'true'
};
var params = 'test.retVal';
var template = baseHtml.replace($mockHtmlInput, params);
baseElement = $compile(template)($scope);
shouldHaveCompiledContent(true);
expect(mockService.mockResolver).toHaveBeenCalledWith('true');
});
it('should be compiled for one resolved promise', function () {
var params = '\"true\"';
var template = baseHtml.replace($mockHtmlInput, params);
baseElement = $compile(template)($scope);
shouldHaveCompiledContent(true);
expect(mockService.mockResolver).toHaveBeenCalledWith('true');
});
it('should be compiled for multiple resolved promises', function () {
var params = '[\"true\", \"true\"]';
var template = baseHtml.replace($mockHtmlInput, params);
baseElement = $compile(template)($scope);
shouldHaveCompiledContent(true);
expect(mockService.mockResolver).toHaveBeenCalledWith('true');
expect(mockService.mockResolver.calls.count()).toEqual(2);
});
it('should not be compiled for one rejected promise', function () {
var params = '\"false\"';
var template = baseHtml.replace($mockHtmlInput, params);
baseElement = $compile(template)($scope);
shouldHaveCompiledContent(false);
expect(mockService.mockResolver).toHaveBeenCalledWith('false');
});
it('should not be compiled for mixed resolved & rejected promise', function () {
var params = '[\"true\", \"false\", \"true\"]';
var template = baseHtml.replace($mockHtmlInput, params);
baseElement = $compile(template)($scope);
shouldHaveCompiledContent(false);
expect(mockService.mockResolver).toHaveBeenCalledWith('true');
expect(mockService.mockResolver).toHaveBeenCalledWith('false');
expect(mockService.mockResolver.calls.count()).toEqual(3);
});
function shouldHaveCompiledContent(shouldInclude) {
$scope.$apply();
var baseElementChildren = baseElement.children();
if (shouldInclude) {
var includedContent = baseElementChildren.first();
expect(includedContent.hasClass('ng-scope')).toBe(true);
expect(includedContent.children().first().hasClass('child-element')).toBe(true);
} else {
expect(baseElementChildren.length).toBe(0);
}
}
});
});
})();

@ -0,0 +1,33 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
/**
* @ngdoc overview
* @name horizon.framework.util.promise-toggle
* @description
*
* This supports declarative directives that compile in their
* content only after the related promises resolve. This is typically
* intended to be used for very fast checks (often cached) of whether or
* not a particular setting or required service is enabled.
*/
angular
.module('horizon.framework.util.promise-toggle', []);
})();

@ -7,6 +7,7 @@
'horizon.framework.util.filters',
'horizon.framework.util.http',
'horizon.framework.util.i18n',
'horizon.framework.util.promise-toggle',
'horizon.framework.util.tech-debt',
'horizon.framework.util.workflow',
'horizon.framework.util.validators'

@ -9,7 +9,7 @@
key="user_data">
</load-edit>
<nova-extension required-extensions='["DiskConfig"]'>
<div hz-nova-extensions='["DiskConfig"]'>
<div class="form-group disk-partition">
<label for="launch-instance-disk-partition">
{$ ::config.label.diskPartition $}
@ -20,9 +20,9 @@
ng-options="option.value as option.text for option in config.diskConfigOptions">
</select>
</div>
</nova-extension>
</div>
<nova-extension required-extensions='["ConfigDrive"]'>
<div hz-nova-extensions='["ConfigDrive"]'>
<div class="checkbox customization-script-source">
<label>
<input type="checkbox"
@ -30,7 +30,7 @@
{$ ::config.label.configurationDrive $}
</label>
</div>
</nova-extension>
</div>
</div>
</div>

@ -109,7 +109,7 @@
</div>
</div>
<settings-service required-settings='["OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point"]'
<div hz-settings-toggle='["OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point"]'
ng-if="model.newInstanceSpec.vol_create === true">
<div class="col-xs-12 col-sm-3">
<div class="form-field">
@ -119,7 +119,7 @@
type="text">
</div>
</div>
</settings-service>
</div>
<div class="col-xs-12 col-sm-2 volume-size-wrapper" ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-field volume-size"

@ -1,78 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
angular
.module('horizon.app.core.cloud-services')
.factory('horizon.app.core.cloud-services.cloudServices', cloudServices);
cloudServices.$inject = [
'horizon.app.core.openstack-service-api.cinder',
'horizon.app.core.openstack-service-api.glance',
'horizon.app.core.openstack-service-api.keystone',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.nova',
'horizon.app.core.openstack-service-api.novaExtensions',
'horizon.app.core.openstack-service-api.security-group',
'horizon.app.core.openstack-service-api.serviceCatalog',
'horizon.app.core.openstack-service-api.settings'
];
/**
* @ngdoc factory
* @name horizon.app.core.cloud-services:factory:cloudServices
* @module horizon.app.core.cloud-services
* @kind hash table
* @description
*
* Provides a hash table contains all the cloud services so that:
*
* 1) Easy to inject all the services since they are injected with one dependency.
* 2) Provides a way to look up a service by name programmatically.
*
* The use of this is currently limited to existing API services. Use at
* your own risk for extensibility purposes at this time. The API will
* be evolving in the coming release and backward compatibility is not
* guaranteed. This also makes no guarantee that the back-end service
* is actually enabled.
*/
function cloudServices(
cinderAPI,
glanceAPI,
keystoneAPI,
neutronAPI,
novaAPI,
novaExtensions,
securityGroup,
serviceCatalog,
settingsService) {
return {
cinder: cinderAPI,
glance: glanceAPI,
keystone: keystoneAPI,
neutron: neutronAPI,
nova: novaAPI,
novaExtensions: novaExtensions,
securityGroup: securityGroup,
serviceCatalog: serviceCatalog,
settingsService: settingsService
};
}
})();

@ -1,75 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services', function () {
describe('factory:cloudServices', function () {
var cloudServices;
beforeEach(module('horizon.app.core.cloud-services', function ($provide) {
$provide.value('horizon.app.core.openstack-service-api.cinder', {});
$provide.value('horizon.app.core.openstack-service-api.glance', {});
$provide.value('horizon.app.core.openstack-service-api.keystone', {});
$provide.value('horizon.app.core.openstack-service-api.neutron', {});
$provide.value('horizon.app.core.openstack-service-api.nova', {});
$provide.value('horizon.app.core.openstack-service-api.novaExtensions', {});
$provide.value('horizon.app.core.openstack-service-api.security-group', {});
$provide.value('horizon.app.core.openstack-service-api.serviceCatalog', {});
$provide.value('horizon.app.core.openstack-service-api.settings', {});
}));
beforeEach(inject(function ($injector) {
cloudServices = $injector.get('horizon.app.core.cloud-services.cloudServices');
}));
it('should have `cloudServices` defined', function () {
expect(cloudServices).toBeDefined();
});
it('should have `cloudServices.cinder` defined', function () {
expect(cloudServices.cinder).toBeDefined();
});
it('should have `cloudServices.glance` defined', function () {
expect(cloudServices.glance).toBeDefined();
});
it('should have `cloudServices.keystone` defined', function () {
expect(cloudServices.keystone).toBeDefined();
});
it('should have `cloudServices.neutron` defined', function () {
expect(cloudServices.neutron).toBeDefined();
});
it('should have `cloudServices.nova` defined', function () {
expect(cloudServices.nova).toBeDefined();
});
it('should have `cloudServices.novaExtensions` defined', function () {
expect(cloudServices.novaExtensions).toBeDefined();
});
it('should have `cloudServices.settingsService` defined', function () {
expect(cloudServices.settingsService).toBeDefined();
});
});
});
})();

@ -1,81 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
var fromJson = angular.fromJson;
var isArray = angular.isArray;
angular
.module('horizon.app.core.cloud-services')
.factory('horizon.app.core.cloud-services.createDirectiveSpec', createDirectiveSpec);
createDirectiveSpec.$inject = ['horizon.app.core.cloud-services.ifFeaturesEnabled'];
/**
* @ngdoc factory
* @name horizon.app.core.cloud-services:factory:createDirectiveSpec
* @module horizon.app.core.cloud-services
* @kind function
* @description
*
* A normalized function that can create a directive specification object
* based on `serviceName`.
*
* @param String serviceName The name of the service, e.g. `novaExtensions`.
* @param String attrName The name of the attribute in the service.
* @return Object a directive specification object that can be used to
* create an angular directive.
*/
function createDirectiveSpec(ifFeaturesEnabled) {
return function createDirectiveSpec(serviceName, attrName) {
var directive = {
link: link,
restrict: 'E',
transclude: true
};
return directive;
/////////////////
function link(scope, element, attrs, ctrl, transclude) {
element.addClass('ng-hide');
var features = fromJson(attrs[attrName]);
if (isArray(features)) {
ifFeaturesEnabled(serviceName, features).then(featureEnabled, featureDisabled);
}
transclude(scope, function(clone) {
element.append(clone);
});
// if the feature is enabled:
function featureEnabled() {
element.removeClass('ng-hide');
}
// if the feature is not enabled:
function featureDisabled() {
element.remove();
}
}
};
}
})();

@ -1,71 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services', function () {
describe('factory:createDirectiveSpec', function () {
var createDirectiveSpec,
ifFeaturesEnabled;
beforeEach(module('horizon.app.core.cloud-services', function ($provide) {
ifFeaturesEnabled = function () {
return {
then: function () {
}
};
};
$provide.value('horizon.app.core.cloud-services.ifFeaturesEnabled', ifFeaturesEnabled);
}));
beforeEach(inject(function ($injector) {
createDirectiveSpec = $injector.get('horizon.app.core.cloud-services.createDirectiveSpec');
}));
it('should have `createDirectiveSpec` defined as a function', function () {
expect(createDirectiveSpec).toBeDefined();
expect(angular.isFunction(createDirectiveSpec)).toBe(true);
});
describe('When called, the returned object', function () {
var directiveSpec;
beforeEach(function () {
directiveSpec = createDirectiveSpec('someService', 'someFeature');
});
it('should be defined', function () {
expect(directiveSpec).toBeDefined();
});
it('should have "restrict" property "E"', function () {
expect(directiveSpec.restrict).toBe('E');
});
it('should have "transclude" property true', function () {
expect(directiveSpec.transclude).toBe(true);
});
it('should have "link" property as a function', function () {
expect(directiveSpec.link).toEqual(jasmine.any(Function));
});
});
});
});
})();

@ -1,5 +1,5 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,14 +19,17 @@
angular
.module('horizon.app.core.cloud-services')
.directive('novaExtension', novaExtension);
.directive('hzNovaExtensions', hzNovaExtensions);
novaExtension.$inject = ['horizon.app.core.cloud-services.createDirectiveSpec'];
hzNovaExtensions.$inject = [
'hzPromiseToggleTemplateDirective',
'horizon.app.core.openstack-service-api.novaExtensions'
];
/**
* @ngdoc directive
* @name horizon.app.core.cloud-services:directive:novaExtension
* @module horizon.app.core.cloud-services
* @name hz.api:directive:hzNovaExtensions
* @module hz.api
* @description
*
* This is to enable specifying conditional UI in a declarative way.
@ -36,7 +39,7 @@
* @example
*
```html
<nova-extension required-extensions='["config_drive"]'>
<div hz-nova-extensions='["config_drive"]'>
<div class="checkbox customization-script-source">
<label>
<input type="checkbox"
@ -44,24 +47,17 @@
{$ ::label.configurationDrive $}
</label>
</div>
</nova-extension>
<nova-extension required-extensions='["disk_config"]'>
<div class="form-group disk-partition">
<label for="launch-instance-disk-partition">
{$ ::label.diskPartition $}
</label>
<select class="form-control"
id="launch-instance-disk-partition"
ng-model="model.newInstanceSpec.disk_config"
ng-options="option.value as option.text for option in diskConfigOptions">
</select>
</div>
</nova-extension>
</div>
```
*/
function novaExtension(createDirectiveSpec) {
return createDirectiveSpec('novaExtensions', 'requiredExtensions');
function hzNovaExtensions(hzPromiseToggleTemplateDirective, novaExtensions) {
return angular.extend(
hzPromiseToggleTemplateDirective[0],
{
singlePromiseResolver: novaExtensions.ifNameEnabled,
name: 'hzNovaExtensions'
}
);
}
})();

@ -0,0 +1,81 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* Copyright 2015 ThoughtWorks 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services.hzNovaExtension', function () {
var $compile, $scope, deferred, novaExtensionsAPI;
var template = [
'<div>',
'<div hz-nova-extensions=\'\"ext_name\"\'>',
'<div class="child-element">',
'</div>',
'</div>',
'</div>'
].join('');
beforeEach(function() {
novaExtensionsAPI = {
ifNameEnabled: function(extensionName) {
return deferred.promise;
}
};
spyOn(novaExtensionsAPI, 'ifNameEnabled').and.callThrough();
module('horizon.app.core.cloud-services');
module('horizon.framework.util.promise-toggle');
module('horizon.app.core.openstack-service-api', function($provide) {
$provide.value(
'horizon.app.core.openstack-service-api.novaExtensions', novaExtensionsAPI
);
});
inject(function (_$compile_, _$q_, _$rootScope_) {
$compile = _$compile_;
deferred = _$q_.defer();
$scope = _$rootScope_.$new();
});
});
// Please note, this code is primarily intended to verify that the
// directive specifies the correct name and that it uses the settings
// service API. Testing of the variations on inputs being resolved
// are tested in the hz-promise-toggle spec.
it('should evaluate child elements when extension is enabled', function () {
var element = $compile(template)($scope);
deferred.resolve({
success: function(callback) {
callback();
}
});
expect(element.children().length).toBe(0);
expect(novaExtensionsAPI.ifNameEnabled).toHaveBeenCalledWith('ext_name');
$scope.$apply();
expect(element.children().length).toBe(1);
});
});
})();

@ -1,5 +1,5 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,30 +19,38 @@
angular
.module('horizon.app.core.cloud-services')
.directive('settingsService', settingsService);
.directive('hzSettingsToggle', hzSettingsToggle);
settingsService.$inject = ['horizon.app.core.cloud-services.createDirectiveSpec'];
hzSettingsToggle.$inject = [
'hzPromiseToggleTemplateDirective',
'horizon.app.core.openstack-service-api.settings'
];
/**
* @ngdoc directive
* @name horizon.app.core.cloud-services:directive:settingsService
* @name horizon.app.core.cloud-services:directive:hzSettingsToggle
* @module horizon.app.core.cloud-services
* @description
*
* This is to enable specifying conditional UI in a declarative way.
* Some UI components should be showing only when some certain settings
* are enabled on `settingsService` service.
*
* are enabled on `hzSettingsToggle` service.
* @example
*
```html
<settings-service required-settings='["something"]'>
<!-- ui code here -->
</settings-service>
<div hz-settings-toggle='["something"]'>
<!-- ui code here -->
</div>
```
*/
function settingsService(createDirectiveSpec) {
return createDirectiveSpec('settingsService', 'requiredSettings');
function hzSettingsToggle(hzPromiseToggleTemplate, settingsService) {
return angular.extend(
hzPromiseToggleTemplate[0],
{
singlePromiseResolver: settingsService.ifEnabled,
name: 'hzSettingsToggle'
}
);
}
})();

@ -0,0 +1,80 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services.hzSettingsToggle', function () {
var $compile, $scope, deferred, settingsServiceAPI;
var template = [
'<div>',
'<div hz-settings-toggle=\'\"setting_name\"\'>',
'<div class="child-element">',
'</div>',
'</div>',
'</div>'
].join('');
beforeEach(function() {
settingsServiceAPI = {
ifEnabled: function(settingsName) {
return deferred.promise;
}
};
spyOn(settingsServiceAPI, 'ifEnabled').and.callThrough();
module('horizon.app.core.cloud-services');
module('horizon.framework.util.promise-toggle');
module('horizon.app.core.openstack-service-api', function($provide) {
$provide.value(
'horizon.app.core.openstack-service-api.settings', settingsServiceAPI
);
});
inject(function (_$compile_, _$q_, _$rootScope_) {
$compile = _$compile_;
deferred = _$q_.defer();
$scope = _$rootScope_.$new();
});
});
// Please note, this code is primarily intended to verify that the
// directive specifies the correct name and that it uses the settings
// service API. Testing of the variations on inputs being resolved
// are tested in the hz-promise-toggle spec.
it('should evaluate child elements when settings is enabled', function () {
var element = $compile(template)($scope);
deferred.resolve({
success: function(callback) {
callback();
}
});
expect(element.children().length).toBe(0);
expect(settingsServiceAPI.ifEnabled).toHaveBeenCalledWith('setting_name');
$scope.$apply();
expect(element.children().length).toBe(1);
});
});
})();

@ -1,56 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
angular
.module('horizon.app.core.cloud-services')
.factory('horizon.app.core.cloud-services.ifFeaturesEnabled', ifFeaturesEnabled);
ifFeaturesEnabled.$inject = ['$q', 'horizon.app.core.cloud-services.cloudServices'];
/**
* @ngdoc factory
* @name horizon.app.core.cloud-services:factory:ifFeaturesEnabled
* @module horizon.app.core.cloud-services
* @kind function
* @description
*
* Check to see if all the listed features are enabled on a certain service,
* which is described by the service name.
*
* This is an asynchronous operation.
*
* @param String serviceName The name of the service, e.g. `novaExtensions`.
* @param Array<String> features A list of feature's names.
* @return Promise the promise of the deferred task that gets resolved
* when all the sub-tasks are resolved.
*/
function ifFeaturesEnabled($q, cloudServices) {
return function ifFeaturesEnabled(serviceName, features) {
// each cloudServices[serviceName].ifEnabled(feature) is an asynchronous
// operation which returns a promise, thus requiring the use of $q.all
// to defer.
return $q.all(
features.map(function mapFeatureEnabled(feature) {
return cloudServices[serviceName].ifEnabled(feature);
})
);//return
};//return
}
})();

@ -1,97 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services', function () {
describe('factory:ifFeaturesEnabled', function () {
var ifFeaturesEnabled,
$q,
cloudServices;
beforeEach(module('horizon.app.core.cloud-services', function ($provide) {
$q = {
all: function () {
return {
then: function () {}
};
}
};
cloudServices = {
'someService': {
ifEnabled: function () {}
}
};
spyOn(cloudServices.someService, 'ifEnabled');
spyOn($q, 'all');
$provide.value('$q', $q);
$provide.value('horizon.app.core.cloud-services.cloudServices', cloudServices);
}));
beforeEach(inject(function ($injector) {
ifFeaturesEnabled = $injector.get('horizon.app.core.cloud-services.ifFeaturesEnabled');
}));
it('should have `ifFeaturesEnabled` defined as a function', function () {
expect(ifFeaturesEnabled).toBeDefined();
expect(angular.isFunction(ifFeaturesEnabled)).toBe(true);
});
it('should call $q.all() and someService.ifEnabled() when invoking ifFeaturesEnabled()',
function () {
var extensions = ['ext1', 'ext2'];
ifFeaturesEnabled('someService', extensions);
expect($q.all).toHaveBeenCalled();
expect(cloudServices.someService.ifEnabled).toHaveBeenCalled();
}
);
it('should not throw when passing in an empty extensions list', function () {
expect(function () {
ifFeaturesEnabled('someService', []);
}).not.toThrow();
});
it('should throw when extensions is null or undefined or not an array', function () {
expect(function () {
ifFeaturesEnabled('someService', null);
}).toThrow();
expect(function () {
ifFeaturesEnabled('someService');
}).toThrow();
expect(function () {
ifFeaturesEnabled('123');
}).toThrow();
});
it('should not throw when the provided serviceName is not a key in the services hash table',
function () {
expect(function () {
ifFeaturesEnabled('invlidServiceName', []);
}).not.toThrow();
}
);
});
});
})();

@ -1,69 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services', function () {
describe('directive:novaExtension', function () {
var $timeout, $scope, element, html;
html = [
'<nova-extension required-extensions=\'["config_drive"]\'>',
'<div class="child-element">',
'</div>',
'</nova-extension>'
].join('');
beforeEach(module('horizon.app.core.cloud-services', function ($provide) {
$provide.value('horizon.app.core.cloud-services.ifFeaturesEnabled', function () {
return {
then: function (successCallback) {
$timeout(successCallback);
}
};
});
}));
beforeEach(inject(function ($injector) {
var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
$timeout = $injector.get('$timeout');
element = $compile(html)($scope);
}));
it('should be compiled', function () {
expect(element.hasClass('ng-scope')).toBe(true);
});
it('should have class name `ng-hide` by default', function () {
expect(element.hasClass('ng-hide')).toBe(true);
});
it('should have no class name `ng-hide` after an asyncs callback', function () {
$timeout(function () {
expect(element.hasClass('ng-hide')).toBe(false);
});
$timeout.flush();
});
it('should have the right child element', function () {
expect(element.children().first().hasClass('child-element')).toBe(true);
});
});
});
})();

@ -1,69 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
(function () {
'use strict';
describe('horizon.app.core.cloud-services', function () {
describe('directive:settingsService', function () {
var $timeout, $scope, html, element;
html = [
'<settings-service required-settings=\'["something"]\'>',
'<div class="child-element">',
'</div>',
'</settings-service>'
].join('');
beforeEach(module('horizon.app.core.cloud-services', function ($provide) {
$provide.value('horizon.app.core.cloud-services.ifFeaturesEnabled', function () {
return {
then: function (successCallback) {
$timeout(successCallback);
}
};
});
}));
beforeEach(inject(function ($injector) {
var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
$timeout = $injector.get('$timeout');
element = $compile(html)($scope);
}));
it('should be compiled', function () {
expect(element.hasClass('ng-scope')).toBe(true);
});
it('should have class name `ng-hide` by default', function () {
expect(element.hasClass('ng-hide')).toBe(true);
});
it('should have no class name `ng-hide` after an asyncs callback', function () {
$timeout(function () {
expect(element.hasClass('ng-hide')).toBe(false);
});
$timeout.flush();
});
it('should have the right child element', function () {
expect(element.children().first().hasClass('child-element')).toBe(true);
});
});
});
})();

@ -46,9 +46,7 @@
{capacity: 1}
),
get: get,
ifNameEnabled: ifNameEnabled,
// This is an alias to support the extension directive default interface
ifEnabled: ifNameEnabled
ifNameEnabled: ifNameEnabled
};
return service;

@ -80,9 +80,6 @@
expect(deferred.reject).toHaveBeenCalledWith('Cannot get the Nova extension list.');
});
it("defines .ifEnabled", function() {
expect(factory.ifEnabled).toBeDefined();
});
});
})();