From 6367dd8a577e5d1eee1c3d938e2f2349627461f4 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 10 Mar 2016 10:57:37 -0400 Subject: [PATCH] Implement NotifierService This service provides an easy-to-use and safe (regarding to memory leaks) way to emit data from services to controllers. This component will be used in `ImageWriterService` to emit the progress state instead of accepting an `onProgress` callback. --- lib/browser/modules/notifier.js | 74 ++++++++++++++++++++++++++ tests/browser/modules/notifier.spec.js | 50 +++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 lib/browser/modules/notifier.js create mode 100644 tests/browser/modules/notifier.spec.js diff --git a/lib/browser/modules/notifier.js b/lib/browser/modules/notifier.js new file mode 100644 index 00000000..7295a09f --- /dev/null +++ b/lib/browser/modules/notifier.js @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Resin.io + * + * 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. + */ + +'use strict'; + +/** + * @module Etcher.notifier + */ + +const angular = require('angular'); +const notifier = angular.module('Etcher.notifier', []); + +/* + * Based on: + * http://www.codelord.net/2015/05/04/angularjs-notifying-about-changes-from-services-to-controllers/ + */ + +notifier.service('NotifierService', function($rootScope) { + + /** + * @summary Safely subscribe to an event + * @function + * @public + * + * @description + * We say "safely" since this subscribe function will listen + * to the scope's `$destroy` event and unbind itself automatically. + * + * @param {Object} scope - angular scope + * @param {String} name - event name + * @param {Function} callback - callback + * + * @example + * NotifierService.subscribe($scope, 'my-event', function() { + * console.log('Event received!'); + * }); + */ + this.subscribe = function(scope, name, callback) { + const handler = $rootScope.$on(name, function(event, data) { + return callback(data); + }); + + scope.$on('$destroy', handler); + }; + + /** + * @summary Emit an event + * @function + * @public + * + * @param {String} name - event name + * @param {*} data - event data + * + * @example + * NotifierService.emit('my-event', 'Foo'); + */ + this.emit = function(name, data) { + $rootScope.$emit(name, data); + }; + +}); diff --git a/tests/browser/modules/notifier.spec.js b/tests/browser/modules/notifier.spec.js new file mode 100644 index 00000000..8af60d63 --- /dev/null +++ b/tests/browser/modules/notifier.spec.js @@ -0,0 +1,50 @@ +'use strict'; + +const m = require('mochainon'); +const angular = require('angular'); +require('angular-mocks'); +require('../../../lib/browser/modules/notifier'); + +describe('Browser: Notifier', function() { + + beforeEach(angular.mock.module('Etcher.notifier')); + + describe('NotifierService', function() { + + let $rootScope; + let NotifierService; + + beforeEach(angular.mock.inject(function(_$rootScope_, _NotifierService_) { + $rootScope = _$rootScope_; + NotifierService = _NotifierService_; + })); + + it('should be able to emit an event without data', function() { + let spy = m.sinon.spy(); + NotifierService.subscribe($rootScope, 'foobar', spy); + NotifierService.emit('foobar'); + m.chai.expect(spy).to.have.been.calledOnce; + m.chai.expect(spy).to.have.been.calledWith(undefined); + }); + + it('should be able to emit an event with data', function() { + let spy = m.sinon.spy(); + NotifierService.subscribe($rootScope, 'foobar', spy); + NotifierService.emit('foobar', 'Hello'); + m.chai.expect(spy).to.have.been.calledOnce; + m.chai.expect(spy).to.have.been.calledWith('Hello'); + }); + + it('should emit the correct event', function() { + let spy1 = m.sinon.spy(); + let spy2 = m.sinon.spy(); + NotifierService.subscribe($rootScope, 'foobar', spy1); + NotifierService.subscribe($rootScope, 'foobaz', spy2); + NotifierService.emit('foobar'); + m.chai.expect(spy1).to.have.been.calledOnce; + m.chai.expect(spy2).to.not.have.been.called; + }); + + }); + +});