UNPKG

textangular

Version:

A radically powerful Text-Editor/Wysiwyg editor for Angular.js

544 lines (499 loc) 23.1 kB
describe('textAngularManager', function(){ 'use strict'; beforeEach(module('textAngular')); describe('getVersion', function(){ it('should return a valid version in the correct format!', inject(function(textAngularManager){ expect(/v\d+.\d+.\d+/i.test(textAngularManager.getVersion())).toBe(true); })); }); describe('toolbar', function(){ describe('registration', function(){ it('should require a scope object', inject(function(textAngularManager){ expect(textAngularManager.registerToolbar).toThrow("textAngular Error: A toolbar requires a scope"); })); it('should require a name', inject(function(textAngularManager){ expect(function(){textAngularManager.registerToolbar({});}).toThrow("textAngular Error: A toolbar requires a name"); expect(function(){textAngularManager.registerToolbar({name: ''});}).toThrow("textAngular Error: A toolbar requires a name"); })); it('should require a unique name', inject(function(textAngularManager){ textAngularManager.registerToolbar({name: 'test'}); expect(function(){textAngularManager.registerToolbar({name: 'test'});}).toThrow('textAngular Error: A toolbar with name "test" already exists'); })); }); describe('retrieval', function(){ it('should be undefined for no registered toolbar', inject(function(textAngularManager){ expect(textAngularManager.retrieveToolbar('test')).toBeUndefined(); })); it('should get the correct toolbar', inject(function(textAngularManager){ var scope = {name: 'test'}; textAngularManager.registerToolbar(scope); expect(textAngularManager.retrieveToolbar('test')).toBe(scope); })); it('should get the correct toolbar via editor', inject(function(textAngularManager){ var scope = {name: 'test'}; textAngularManager.registerToolbar(scope); textAngularManager.registerEditor('testeditor', {}, ['test']); expect(textAngularManager.retrieveToolbarsViaEditor('testeditor')[0]).toBe(scope); })); }); describe('unregister', function(){ it('should get the correct toolbar', inject(function(textAngularManager){ textAngularManager.registerToolbar({name: 'test'}); textAngularManager.unregisterToolbar('test'); expect(textAngularManager.retrieveToolbar('test')).toBeUndefined(); })); }); describe('unregister-cleanup', function(){ it('should not have any toolbarScopes left', inject(function(textAngularManager){ textAngularManager.registerEditor('testeditor', {}, ['test', 'test1']); textAngularManager.registerToolbar({name: 'test'}); textAngularManager.registerToolbar({name: 'test1'}); expect(textAngularManager.getToolbarScopes().length).toBe(2); // note: unregisterToolbar does nothing to the toolbarScopes textAngularManager.unregisterToolbar('test'); expect(textAngularManager.getToolbarScopes().length).toBe(2); textAngularManager.unregisterToolbar('test1'); expect(textAngularManager.getToolbarScopes().length).toBe(2); textAngularManager.unregisterEditor('testeditor'); // this will have deleted the toolbarScopes held in the editor expect(textAngularManager.getToolbarScopes().length).toBe(0); })); }); describe('modification', function(){ var $rootScope, toolbar1, toolbar2, textAngularManager; beforeEach(inject(function(_textAngularManager_){ textAngularManager = _textAngularManager_; })); beforeEach(inject(function (_$compile_, _$rootScope_) { $rootScope = _$rootScope_; toolbar1 = jQuery(_$compile_('<text-angular-toolbar name="test1"></text-angular-toolbar>')($rootScope)[0]); toolbar2 = jQuery(_$compile_('<text-angular-toolbar name="test2"></text-angular-toolbar>')($rootScope)[0]); $rootScope.$digest(); })); describe('throws error on no toolbar', function(){ it('when update tool', function(){ expect(function(){ textAngularManager.updateToolbarToolDisplay('test', 'h1', {iconclass: 'test-icon-class'}); }).toThrow('textAngular Error: No Toolbar with name "test" exists'); }); it('when reset tool', function(){ expect(function(){ textAngularManager.resetToolbarToolDisplay('test', 'h1'); }).toThrow('textAngular Error: No Toolbar with name "test" exists'); }); }); describe('single toolbar', function(){ // we test these by adding an icon with a specific class and then testing for it's existance it('should update only one button on one toolbar', function(){ textAngularManager.updateToolbarToolDisplay('test1', 'h1', {iconclass: 'test-icon-class'}); expect(jQuery('i.test-icon-class', toolbar1).length).toBe(1); expect(jQuery('i.test-icon-class', toolbar2).length).toBe(0); }); it('should reset one toolbar button on one toolbar', function(){ textAngularManager.updateToolbarToolDisplay('test1', 'h1', {iconclass: 'test-icon-class'}); textAngularManager.updateToolbarToolDisplay('test1', 'h2', {iconclass: 'test-icon-class2'}); textAngularManager.resetToolbarToolDisplay('test1', 'h1'); expect(jQuery('[name="h1"] i.test-icon-class', toolbar1).length).toBe(0); expect(jQuery('[name="h1"] i.test-icon-class', toolbar2).length).toBe(0); expect(jQuery('[name="h2"] i.test-icon-class2', toolbar1).length).toBe(1); }); }); describe('multi toolbar', function(){ it('should update only one button on multiple toolbars', function(){ textAngularManager.updateToolDisplay('h1', {iconclass: 'test-icon-class'}); expect(jQuery('[name="h1"] i.test-icon-class', toolbar1).length).toBe(1); expect(jQuery('[name="h1"] i.test-icon-class', toolbar2).length).toBe(1); }); it('should reset one toolbar button', function(){ textAngularManager.updateToolDisplay('h1', {iconclass: 'test-icon-class'}); textAngularManager.updateToolDisplay('h2', {iconclass: 'test-icon-class2'}); textAngularManager.resetToolDisplay('h1'); expect(jQuery('[name="h1"] i.test-icon-class', toolbar1).length).toBe(0); expect(jQuery('[name="h1"] i.test-icon-class', toolbar2).length).toBe(0); expect(jQuery('[name="h2"] i.test-icon-class2', toolbar1).length).toBe(1); }); it('should update multiple buttons on multiple toolbars', function(){ textAngularManager.updateToolsDisplay({'h1': {iconclass: 'test-icon-class'},'h2': {iconclass: 'test-icon-class2'}}); expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar1).length).toBe(2); expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar2).length).toBe(2); }); it('should reset all toolbar buttons', function(){ textAngularManager.updateToolsDisplay({'h1': {iconclass: 'test-icon-class'},'h2': {iconclass: 'test-icon-class2'}}); textAngularManager.resetToolsDisplay(); expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar1).length).toBe(0); expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar2).length).toBe(0); }); }); describe('dynamically add tool', function(){ it('to all toolbars', function(){ textAngularManager.addTool('newtool', {buttontext: 'Test Add Tool'}, 0, 0); expect(toolbar1.find('[name="newtool"]').length).toBe(1); expect(toolbar2.find('[name="newtool"]').length).toBe(1); }); it('specifically to one toolbar', function(){ textAngularManager.addToolToToolbar('newtool', {buttontext: 'Test Add Tool'}, 'test1', 0, 0); expect(toolbar1.find('[name="newtool"]').length).toBe(1); expect(toolbar2.find('[name="newtool"]').length).toBe(0); }); }); describe('dynamically remove tool', function(){ it('from all toolbars', function(){ textAngularManager.addTool('newtool', {buttontext: 'Test Add Tool'}, 0, 0); textAngularManager.removeTool('newtool'); expect(toolbar1.find('[name="newtool"]').length).toBe(0); expect(toolbar2.find('[name="newtool"]').length).toBe(0); }); it('when only on one toolbar', function(){ textAngularManager.addToolToToolbar('newtool', {buttontext: 'Test Add Tool'}, 'test1', 0, 0); textAngularManager.removeTool('newtool'); expect(toolbar1.find('[name="newtool"]').length).toBe(0); expect(toolbar2.find('[name="newtool"]').length).toBe(0); }); }); }); }); describe('editor', function(){ describe('registration', function(){ it('should require a name', inject(function(textAngularManager){ expect(textAngularManager.registerEditor).toThrow("textAngular Error: An editor requires a name"); expect(function(){textAngularManager.registerEditor('');}).toThrow("textAngular Error: An editor requires a name"); })); it('should require a scope object', inject(function(textAngularManager){ expect(function(){textAngularManager.registerEditor('test');}).toThrow("textAngular Error: An editor requires a scope"); })); it('should require a unique name', inject(function(textAngularManager){ textAngularManager.registerEditor('test', {}); expect(function(){textAngularManager.registerEditor('test', {});}).toThrow('textAngular Error: An Editor with name "test" already exists'); })); it('should return a disable function', inject(function(textAngularManager){ expect(textAngularManager.registerEditor('test', {}).disable).toBeDefined(); })); it('should return a enable function', inject(function(textAngularManager){ expect(textAngularManager.registerEditor('test', {}).enable).toBeDefined(); })); it('should return a focus function', inject(function(textAngularManager){ expect(textAngularManager.registerEditor('test', {}).focus).toBeDefined(); })); it('should return a unfocus function', inject(function(textAngularManager){ expect(textAngularManager.registerEditor('test', {}).unfocus).toBeDefined(); })); it('should return a updateSelectedStyles function', inject(function(textAngularManager){ expect(textAngularManager.registerEditor('test', {}).updateSelectedStyles).toBeDefined(); })); }); describe('retrieval', function(){ it('should be undefined for no registered editor', inject(function(textAngularManager){ expect(textAngularManager.retrieveEditor('test')).toBeUndefined(); })); it('should get the correct editor', inject(function(textAngularManager){ var scope = {}; textAngularManager.registerEditor('test', scope); expect(textAngularManager.retrieveEditor('test').scope).toBe(scope); })); }); describe('unregister', function(){ it('should get the correct editor', inject(function(textAngularManager){ textAngularManager.registerEditor('test', {}); textAngularManager.unregisterEditor('test'); expect(textAngularManager.retrieveEditor('test')).toBeUndefined(); })); }); describe('interacting', function(){ var $rootScope, textAngularManager, editorFuncs, testbar1, testbar2, testbar3; var editorScope = {}; var $log; beforeEach(inject(function(_textAngularManager_, _$log_){ textAngularManager = _textAngularManager_; $log = _$log_; })); afterEach(inject(function(){ // turn on to display log messages when needed //console.log($log.debug.logs); })); describe('active state', function(){ beforeEach(inject(function (_$rootScope_) { $rootScope = _$rootScope_; textAngularManager.registerToolbar((testbar1 = {name: 'testbar1', disabled: true})); textAngularManager.registerToolbar((testbar2 = {name: 'testbar2', disabled: true})); textAngularManager.registerToolbar((testbar3 = {name: 'testbar3', disabled: true})); editorFuncs = textAngularManager.registerEditor('test', editorScope, ['testbar1','testbar2']); $rootScope.$digest(); })); describe('focus', function(){ beforeEach(function(){ editorFuncs.focus(); $rootScope.$digest(); }); it('should set disabled to false on toolbars', function(){ expect(!testbar1.disabled); expect(!testbar2.disabled); expect(testbar3.disabled); }); it('should set the active editor to the editor', function(){ expect(testbar1._parent).toBe(editorScope); expect(testbar2._parent).toBe(editorScope); expect(testbar3._parent).toNotBe(editorScope); }); }); describe('unfocus', function(){ beforeEach(function(){ editorFuncs.unfocus(); $rootScope.$digest(); }); it('should set disabled to false on toolbars', function(){ expect(testbar1.disabled); expect(testbar2.disabled); expect(!testbar3.disabled); }); }); describe('disable', function(){ beforeEach(function(){ editorFuncs.disable(); $rootScope.$digest(); }); it('should set disabled to false on toolbars', function(){ expect(testbar1.disabled).toBe(true); expect(testbar2.disabled).toBe(true); expect(testbar3.disabled).toBe(true); }); }); describe('enable', function(){ beforeEach(function(){ editorFuncs.disable(); $rootScope.$digest(); editorFuncs.enable(); $rootScope.$digest(); }); it('should set disabled to false on toolbars', function(){ expect(testbar1.disabled).toBe(false); expect(testbar2.disabled).toBe(false); expect(testbar3.disabled).toBe(true); }); }); }); describe('actions passthrough', function(){ var editorScope, element; beforeEach(inject(function(taRegisterTool, taOptions, _$rootScope_, _$compile_){ // add a tool that is ALLWAYS active taRegisterTool('activeonrangyrange', { buttontext: 'Active On Rangy Rangy', action: function(){ return this.$element.attr('hit-this', 'true'); }, commandKeyCode: 21, activeState: function(rangyrange){ return rangyrange !== undefined; } }); taRegisterTool('inactiveonrangyrange', { buttontext: 'Inactive On Rangy Rangy', action: function(){ return this.$element.attr('hit-this', 'true'); }, commandKeyCode: 23, activeState: function(rangyrange){ return rangyrange === undefined; } }); taRegisterTool('noactivestate', { buttontext: 'Shouldnt error, Shouldnt be active either', action: function(){ return this.$element.attr('hit-this', 'true'); } }); taRegisterTool('onselect', { buttontext: 'Active on element select', action: function(){ return this.$element.attr('hit-this', 'true'); }, onElementSelect: { element: 'i', action: function(event, element, editorScope){ return this.$element.attr('hit-this', 'true'); } } }); taRegisterTool('onselectattr', { buttontext: 'Active on element with attr select', action: function(){ return this.$element.attr('hit-this', 'true'); }, onElementSelect: { element: 'i', onlyWithAttrs: ['src'], action: function(event, element, editorScope){ return this.$element.attr('hit-this', 'true'); } } }); taRegisterTool('onselectattr_specific', { buttontext: 'Active on element select', action: function(){ return this.$element.attr('hit-this', 'true'); }, onElementSelect: { element: 'i', onlyWithAttrs: ['src','href'], action: function(event, element, editorScope){ return this.$element.attr('hit-this', 'true'); } } }); taRegisterTool('specialkey', { buttontext: 'specialkey', action: function(){ return this.$element.attr('hit-this', 'true'); }, commandKeyCode: 'TabKey', onElementSelect: { element: 'i', onlyWithAttrs: ['src','href'], action: function(event, element, editorScope){ return this.$element.attr('hit-this', 'true'); } } }); taRegisterTool('unused', { buttontext: 'Button Is Not Used', action: function(){ throw('Error Should Not Run'); }, commandKeyCode: 42, onElementSelect: { element: 'b', action: function(event, element, editorScope){ throw('Error Should Not Run'); } } }); taOptions.toolbar = [['noactivestate','activeonrangyrange','inactiveonrangyrange','onselect','onselectattr','onselectattr_specific', 'specialkey']]; $rootScope = _$rootScope_; element = jQuery(_$compile_('<text-angular name="test"><p>Test Content</p></text-angular>')($rootScope)[0]); $rootScope.$digest(); editorScope = textAngularManager.retrieveEditor('test'); })); describe('updateSelectedStyles', function(){ describe('should activate buttons correctly', function(){ it('without rangyrange passed through', function(){ editorScope.editorFunctions.updateSelectedStyles(); $rootScope.$digest(); expect(element.find('.ta-toolbar button.active').length).toBe(1); }); it('with rangyrange passed through', function(){ editorScope.editorFunctions.updateSelectedStyles({}); $rootScope.$digest(); expect(element.find('.ta-toolbar button.active').length).toBe(1); }); }); }); describe('updateSelectedStyles through textAngularMananger', function(){ describe('should activate buttons correctly', function(){ it('without rangyrange passed through', function(){ textAngularManager.updateStyles(); $rootScope.$digest(); expect(element.find('.ta-toolbar button.active').length).toBe(1); }); it('with rangyrange passed through', function(){ textAngularManager.updateStyles({}); $rootScope.$digest(); expect(element.find('.ta-toolbar button.active').length).toBe(1); }); }); }); describe('sendKeyCommand', function(){ it('should return true if there is a relevantCommandKeyCode on a tool', function(){ expect(editorScope.editorFunctions.sendKeyCommand({metaKey: true, which: 21})).toBe(true); }); it('should call the action of the specified tool', function(){ editorScope.editorFunctions.sendKeyCommand({metaKey: true, which: 21}); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBe('true'); }); it('should react only when modifiers present', function(){ editorScope.editorFunctions.sendKeyCommand({which: 21}); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBeUndefined(); }); it('should react to metaKey', function(){ editorScope.editorFunctions.sendKeyCommand({metaKey: true, which: 21}); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBe('true'); }); it('should react to ctrlKey', function(){ editorScope.editorFunctions.sendKeyCommand({ctrlKey: true, which: 21}); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBe('true'); }); it('should react to tabKey', function(){ var event = {shiftKey: false, which: 9, specialKey: 'TabKey', preventDefault: function () {}}; var fakeEditorScope = { updateSelectedStyles: function() {}}; textAngularManager.sendKeyCommand(angular.extend(editorScope.scope, fakeEditorScope), event); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=specialkey]').attr('hit-this')).toBe('true'); }); }); describe('onElementSelect', function(){ it('should do nothing if button not added', function(){ expect(function(){ editorScope.editorFunctions.triggerElementSelect({}, '<b>'); }).not.toThrow(); }); it('should return true if there is a relevant select element on a tool', function(){ expect(editorScope.editorFunctions.triggerElementSelect({}, '<i>')).toBe(true); }); it('should call the onElementSelect.action handler of defined tools', function(){ editorScope.editorFunctions.triggerElementSelect({}, '<i>'); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=onselect]').attr('hit-this')).toBe('true'); }); it('should call the onElementSelect.action handler of defined tools with attr', function(){ editorScope.editorFunctions.triggerElementSelect({}, '<i src="test">'); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=onselect]').attr('hit-this')).toBeUndefined(); expect(element.find('.ta-toolbar button[name=onselectattr]').attr('hit-this')).toBe('true'); }); it('should call only the most specific handler when ambigious', function(){ editorScope.editorFunctions.triggerElementSelect({}, '<i src="test" href="test">'); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=onselectattr_specific]').attr('hit-this')).toBe('true'); expect(element.find('.ta-toolbar button[name=onselectattr]').attr('hit-this')).toBeUndefined(); }); it('should not call more specific', function(){ editorScope.editorFunctions.triggerElementSelect({}, '<i src="test">'); $rootScope.$digest(); expect(element.find('.ta-toolbar button[name=onselectattr_specific]').attr('hit-this')).toBeUndefined(); expect(element.find('.ta-toolbar button[name=onselectattr]').attr('hit-this')).toBe('true'); }); }); }); }); describe('linking toolbar to existing editor', function(){ it('should link when referenced', inject(function(textAngularManager) { var scope = {name: 'test'}; textAngularManager.registerEditor('testeditor', {}, ['test']); textAngularManager.registerToolbar(scope); expect(textAngularManager.retrieveToolbarsViaEditor('testeditor')[0]).toBe(scope); })); it('should not link when not referenced', inject(function(textAngularManager) { var scope = {name: 'test'}; textAngularManager.registerEditor('testeditor', {}, []); textAngularManager.registerToolbar(scope); expect(textAngularManager.retrieveToolbarsViaEditor('testeditor').length).toBe(0); })); }); describe('updating', function(){ var $rootScope, element; beforeEach(inject(function (_$compile_, _$rootScope_) { $rootScope = _$rootScope_; $rootScope.htmlcontent = '<p>Test Content</p>'; element = _$compile_('<text-angular name="test" ng-model="htmlcontent"></text-angular>')($rootScope); $rootScope.$digest(); })); it('should throw error for named editor that doesn\'t exist', inject(function(textAngularManager){ expect(function(){textAngularManager.refreshEditor('non-editor');}).toThrow('textAngular Error: No Editor with name "non-editor" exists'); })); it('should update from text view to model', inject(function(textAngularManager){ jQuery('.ta-text .ta-bind', element[0]).append('<div>Test 2 Content</div>'); textAngularManager.refreshEditor('test'); expect($rootScope.htmlcontent).toBe('<p>Test Content</p><div>Test 2 Content</div>'); })); }); }); });