diff --git a/js/app/service/hashService.js b/js/app/service/hashService.js new file mode 100644 index 0000000000..af976f4e71 --- /dev/null +++ b/js/app/service/hashService.js @@ -0,0 +1,90 @@ +/** + * Calendar App + * + * @author Georg Ehrke + * @copyright 2016 Georg Ehrke + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ + +app.service('HashService', function ($location) { + 'use strict'; + + const context = { + callbacks: {} + }; + + /** + * register a handler for a certain hash id + * @param {string} id + * @param {function} callback + */ + this.register = (id, callback) => { + if (context.callbacks[id]) { + throw new Error('A callback for this id was already registered in the HashService'); + } + + context.callbacks[id] = callback; + }; + + /** + * unregister a handler for a certain hash + * @param {string} id + */ + this.unregister = (id) => { + delete context.callbacks[id]; + }; + + /** + * calls a registered handler if necessary + */ + this.call = () => { + let hash = $location.hash(); + + if (!hash || hash === '') { + // nothing to do + return false; + } + + if (hash.startsWith('#')) { + hash = hash.substr(1); + } + + // the hashes must comply with the following convention + // #id?param1=value1¶m2=value2& ... ¶mN=valueN + // e.g.: + // #go_to_date?date=2016-12-31 + // #go_to_event?calendar=work&uri=Nextcloud-23as123asf12.ics + // #search?term=foobar + // #subscribe_to_webcal?url=https%3A%2F%2Fwww.foo.bar%2F + // + // hashes without a question mark after the id will be ignored + + if (!hash.includes('?')) { + return false; + } + + const questionMarkPosition = hash.indexOf('?'); + const identifier = hash.substr(0, questionMarkPosition); + const parameters = hash.substr(questionMarkPosition + 1); + + if (context.callbacks[identifier]) { + context.callbacks[identifier](parameters); + return true; + } else { + return false; + } + }; +}); diff --git a/tests/js/unit/services/hashServiceSpec.js b/tests/js/unit/services/hashServiceSpec.js new file mode 100644 index 0000000000..899eb3d0cd --- /dev/null +++ b/tests/js/unit/services/hashServiceSpec.js @@ -0,0 +1,130 @@ +/** + * Calendar App + * + * @author Georg Ehrke + * @copyright 2016 Georg Ehrke + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ + +describe('HashService', () => { + 'use strict'; + + let HashService, $location; + + beforeEach(module('Calendar', function ($provide) { + $location = {}; + $location.hash = jasmine.createSpy(); + + $provide.value('$location', $location); + })); + + beforeEach(inject(function (_HashService_) { + HashService = _HashService_; + })); + + it ('should not allow an identifier to be registered twice', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + const callback3 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + expect(() => HashService.register('fancy_id', callback3)).toThrowError(Error, 'A callback for this id was already registered in the HashService'); + }); + + it ('should allow identifiers to be unregistered', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + const callback3 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + HashService.unregister('fancy_id'); + HashService.register('fancy_id', callback3); + }); + + it ('should return false when the hash is undefined or empty', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + + $location.hash.and.returnValue(undefined); + expect(HashService.call()).toEqual(false); + expect($location.hash).toHaveBeenCalled(); + + $location.hash.and.returnValue(null); + expect(HashService.call()).toEqual(false); + expect($location.hash).toHaveBeenCalled(); + + $location.hash.and.returnValue(''); + expect(HashService.call()).toEqual(false); + expect($location.hash).toHaveBeenCalled(); + }); + + it ('should return call registered callbacks', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + + $location.hash.and.returnValue('fancy_id?param1=value1¶m2=value2'); + expect(HashService.call()).toEqual(true); + expect(callback1).toHaveBeenCalledWith('param1=value1¶m2=value2'); + expect(callback2).not.toHaveBeenCalled(); + }); + + it ('should handle hashes beginning with #', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + + $location.hash.and.returnValue('#fancy_id?param1=value1¶m2=value2'); + expect(HashService.call()).toEqual(true); + expect(callback1).toHaveBeenCalledWith('param1=value1¶m2=value2'); + expect(callback2).not.toHaveBeenCalled(); + }); + + it ('should return false when no registered callbacks are available', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + + $location.hash.and.returnValue('super_fancy_id?param1=value1¶m2=value2'); + expect(HashService.call()).toEqual(false); + expect(callback1).not.toHaveBeenCalled(); + expect(callback2).not.toHaveBeenCalled(); + }); + + it ('should return false when hash contains no ?', () => { + const callback1 = jasmine.createSpy(); + const callback2 = jasmine.createSpy(); + + HashService.register('fancy_id', callback1); + HashService.register('another_fancy_id', callback2); + + $location.hash.and.returnValue('fancy_id'); + expect(HashService.call()).toEqual(false); + expect(callback1).not.toHaveBeenCalled(); + expect(callback2).not.toHaveBeenCalled(); + }); +});