UNPKG

medium-editor

Version:
722 lines (604 loc) 32.4 kB
/*global selectElementContents, selectElementContentsAndFire */ describe('MediumEditor.util', function () { 'use strict'; beforeEach(function () { setupTestHelpers.call(this); }); afterEach(function () { this.cleanupTest(); }); describe('Exposure', function () { it('is exposed on the MediumEditor ctor', function () { expect(MediumEditor.util).toBeTruthy(); }); }); describe('Extend', function () { it('should overwrite values from right to left', function () { var objOne = { one: 'one' }, objTwo = { one: 'two', three: 'three' }, objThree = { three: 'four', five: 'six' }, objFour, result = MediumEditor.util.extend({}, objOne, objTwo, objThree, objFour); expect(result).toEqual({ one: 'two', three: 'four', five: 'six' }); }); }); describe('Defaults', function () { it('should overwrite values from left to right', function () { var objOne = { one: 'one' }, objTwo = { one: 'two', three: 'three' }, objThree = { three: 'four', five: 'six' }, objFour, result = MediumEditor.util.defaults({}, objOne, objTwo, objThree, objFour); expect(result).toEqual({ one: 'one', three: 'three', five: 'six' }); }); it('should overwrite nothing without args', function () { var result = MediumEditor.util.defaults(); expect(result).toEqual({}); }); }); describe('Deprecated', function () { it('should warn when a method is deprecated', function () { var testObj = { newMethod: function () {} }; spyOn(testObj, 'newMethod').and.callThrough(); spyOn(MediumEditor.util, 'warn').and.callThrough(); MediumEditor.util.deprecatedMethod.call(testObj, 'test', 'newMethod', ['arg1', true], 'some version'); expect(testObj.newMethod).toHaveBeenCalledWith('arg1', true); expect(MediumEditor.util.warn).toHaveBeenCalledWith( 'test is deprecated, please use newMethod instead. Will be removed in some version' ); }); it('should warn when an option is deprecated', function () { spyOn(MediumEditor.util, 'warn').and.callThrough(); MediumEditor.util.deprecated('oldOption', 'sub.newOption'); expect(MediumEditor.util.warn).toHaveBeenCalledWith( 'oldOption is deprecated, please use sub.newOption instead.' ); }); it('should allow passing a version when the removal will happen', function () { spyOn(MediumEditor.util, 'warn').and.callThrough(); MediumEditor.util.deprecated('old', 'new', '11tybillion'); expect(MediumEditor.util.warn).toHaveBeenCalledWith( 'old is deprecated, please use new instead. Will be removed in 11tybillion' ); }); }); describe('settargetblank', function () { it('sets target blank on a A element from a A element', function () { var el = this.createElement('a', '', 'lorem ipsum'); el.attributes.href = 'http://0.0.0.0/bar.html'; MediumEditor.util.setTargetBlank(el); expect(el.target).toBe('_blank'); }); it('sets target blank on a A element from a DIV element', function () { var el = this.createElement('div', '', '<a href="http://1.1.1.1/foo.html">foo</a> <a href="http://0.0.0.0/bar.html">bar</a>'); MediumEditor.util.setTargetBlank(el, 'http://0.0.0.0/bar.html'); var nodes = el.getElementsByTagName('a'); expect(nodes[0].target).not.toBe('_blank'); expect(nodes[1].target).toBe('_blank'); }); }); describe('removetargetblank', function () { it('removes target blank from a A element', function () { var el = this.createElement('a', '', 'lorem ipsum'); el.attributes.href = 'http://0.0.0.0/bar.html'; el.attributes.target = '_blank'; MediumEditor.util.removeTargetBlank(el, 'http://0.0.0.0/bar.html'); expect(el.target).toBe(''); }); it('removes target blank on a A element from a DIV element', function () { var el = this.createElement('div', '', '<a href="http://1.1.1.1/foo.html" target="_blank">foo</a> <a href="http://0.0.0.0/bar.html" target="_blank">bar</a>'); MediumEditor.util.removeTargetBlank(el, 'http://0.0.0.0/bar.html'); var nodes = el.getElementsByTagName('a'); expect(nodes[0].target).toBe('_blank'); expect(nodes[1].target).toBe(''); }); }); describe('addClassToAnchors', function () { it('add class to anchors on a A element from a A element', function () { var el = this.createElement('a', '', 'lorem ipsum'); el.attributes.href = 'http://0.0.0.0/bar.html'; MediumEditor.util.addClassToAnchors(el, 'firstclass'); expect(el.classList.length).toBe(1); expect(el.classList.contains('firstclass')).toBe(true); }); it('add class to anchors on a A element from a DIV element', function () { var el = this.createElement('div', '', '<a href="http://1.1.1.1/foo.html">foo</a> <a href="http://0.0.0.0/bar.html">bar</a>'); MediumEditor.util.addClassToAnchors(el, 'firstclass'); var nodes = el.getElementsByTagName('a'); expect(nodes[0].classList.length).toBe(1); expect(nodes[1].classList.length).toBe(1); expect(nodes[0].classList.contains('firstclass')).toBe(true); expect(nodes[1].classList.contains('firstclass')).toBe(true); }); }); describe('warn', function () { it('exists', function () { expect(typeof MediumEditor.util.warn).toBe('function'); }); it('ends up calling console.warn', function () { // IE9 mock for SauceLabs if (window.console === undefined) { window.console = { warn: function (msg) { return msg; } }; } else if (typeof window.console.warn !== 'function') { window.console.warn = function (msg) { return msg; }; } var spy = spyOn(window.console.warn, 'apply').and.callThrough(); MediumEditor.util.warn('message'); expect(spy).toHaveBeenCalled(); }); }); describe('splitOffDOMTree', function () { /* start: * * <div> * / | \ * <span> <span> <span> * / \ / \ / \ * 1 2 3 4 5 6 * * result: * * <div> <div>' * / \ / \ * <span> <span> <span>' <span> * / \ | | / \ * 1 2 3 4 5 6 */ it('should split a complex tree correctly when splitting off right part of tree', function () { var el = this.createElement('div', '', '<span><b>1</b><i>2</i></span><span><b>3</b><u>4</u></span><span><b>5</b><i>6</i></span>'), splitOn = el.querySelector('u').firstChild, result = MediumEditor.util.splitOffDOMTree(el, splitOn); expect(el.outerHTML).toBe('<div><span><b>1</b><i>2</i></span><span><b>3</b></span></div>'); expect(result.outerHTML).toBe('<div><span><u>4</u></span><span><b>5</b><i>6</i></span></div>'); }); /* start: * * <div> * / | \ * <span> <span> <span> * / \ / \ / \ * 1 2 3 4 5 6 * * result: * * <div>' <div> * / \ | * <span> <span> <span> * /\ /\ /\ * 1 2 3 4 5 6 */ it('should split a complex tree correctly when splitting off left part of tree', function () { var el = this.createElement('div', '', '<span><b>1</b><i>2</i></span><span><b>3</b><u>4</u></span><span><b>5</b><i>6</i></span>'), splitOn = el.querySelector('u').firstChild, result = MediumEditor.util.splitOffDOMTree(el, splitOn, true); expect(el.outerHTML).toBe('<div><span><b>5</b><i>6</i></span></div>'); expect(result.outerHTML).toBe('<div><span><b>1</b><i>2</i></span><span><b>3</b><u>4</u></span></div>'); }); }); describe('moveTextRangeIntoElement', function () { it('should return false and bail if no elements are passed', function () { expect(MediumEditor.util.moveTextRangeIntoElement(null, null)).toBe(false); }); it('should return false and bail if elemenets do not share a root', function () { var el = this.createElement('div', '', 'text'), elTwo = this.createElement('div', '', 'more text', true), temp = this.createElement('div', ''); expect(MediumEditor.util.moveTextRangeIntoElement(el, elTwo, temp)).toBe(false); expect(temp.innerHTML).toBe(''); }); it('should create a parent element that spans multiple root elements', function () { var el = this.createElement('div', '', '<span>Link = http</span>' + '<span>://</span>' + '<span>www.exam</span>' + '<span>ple.com</span>' + '<span>:443/</span>' + '<span>path/to</span>' + '<span>somewhere#</span>' + '<span>index notLink</span>'), firstText = el.firstChild.firstChild.splitText('Link = '.length), lastText = el.lastChild.firstChild, para = this.createElement('p', ''); lastText.splitText('index'.length); MediumEditor.util.moveTextRangeIntoElement(firstText, lastText, para); expect(el.innerHTML).toBe( '<span>Link = </span>' + '<p>' + '<span>http</span>' + '<span>://</span>' + '<span>www.exam</span>' + '<span>ple.com</span>' + '<span>:443/</span>' + '<span>path/to</span>' + '<span>somewhere#</span>' + '<span>index</span>' + '</p>' + '<span> notLink</span>' ); }); }); describe('getClosestTag', function () { it('should get closed tag', function () { var el = this.createElement('div', '', '<span>my <b>text</b></span>'), tag = el.querySelector('b').firstChild, closestTag = MediumEditor.util.getClosestTag(tag, 'span'); expect(closestTag.nodeName.toLowerCase()).toBe('span'); }); it('should not get closed tag with data-medium-editor-element', function () { var el = this.createElement('div', '', '<p>youpi<span data-medium-editor-element="true">my <b>text</b></span></p>'), tag = el.querySelector('b').firstChild, closestTag = MediumEditor.util.getClosestTag(tag, 'p'); expect(closestTag).toBe(false); }); it('should not get closed tag from empty element', function () { var closestTag = MediumEditor.util.getClosestTag(false, 'span'); expect(closestTag).toBe(false); }); }); describe('isListItem', function () { it('should be a list item but inside a ul', function () { var el = this.createElement('ul', '', '<li>test</li>'), result = MediumEditor.util.isListItem(el); expect(result).toBe(false); }); it('should be a list item', function () { var el = this.createElement('ul', '', '<li>test</li>'), li = el.querySelector('li'), result = MediumEditor.util.isListItem(li); expect(result).toBe(true); }); it('should not be a list item', function () { var result = MediumEditor.util.isListItem(false); expect(result).toBe(false); }); it('should not be a list item', function () { var el = this.createElement('p', '', '<b>test</b>'), result = MediumEditor.util.isListItem(el); expect(result).toBe(false); }); }); describe('isKey', function () { it('should return true no matter how the key associated to the event is defined', function () { var event; event = { which: 13, keyCode: null, charCode: null }; expect(MediumEditor.util.isKey(event, 13)).toBeTruthy(); event = { which: null, keyCode: 13, charCode: null }; expect(MediumEditor.util.isKey(event, 13)).toBeTruthy(); event = { which: null, keyCode: null, charCode: 13 }; expect(MediumEditor.util.isKey(event, 13)).toBeTruthy(); }); it('should return true when a key associated to event is listed to one we are looking for', function () { var event = { which: 13 }; expect(MediumEditor.util.isKey(event, 13)).toBeTruthy(); expect(MediumEditor.util.isKey(event, [13])).toBeTruthy(); expect(MediumEditor.util.isKey(event, [13, 12])).toBeTruthy(); expect(MediumEditor.util.isKey(event, [12, 13])).toBeTruthy(); }); it('should return false when a key associated to event is NOT listed to one we are looking for', function () { var event = { which: 13 }; expect(MediumEditor.util.isKey(event, 65)).toBeFalsy(); expect(MediumEditor.util.isKey(event, [65])).toBeFalsy(); expect(MediumEditor.util.isKey(event, [65, 66])).toBeFalsy(); }); }); describe('execFormatBlock', function () { it('should execute indent command when called with blockquote when selection is inside a nested block element within a blockquote', function () { var el = this.createElement('div', '', '<blockquote><p>Some <b>Text</b></p></blockquote>'); el.setAttribute('contenteditable', true); selectElementContents(el.querySelector('b')); spyOn(document, 'execCommand'); MediumEditor.util.execFormatBlock(document, 'blockquote'); expect(document.execCommand).toHaveBeenCalledWith('outdent', false, null); }); it('should execute indent command when called with blockquote when isIE is true', function () { var origIsIE = MediumEditor.util.isIE, el = this.createElement('div', '', '<p>Some <b>Text</b></p>'); MediumEditor.util.isIE = true; el.setAttribute('contenteditable', true); selectElementContents(el.querySelector('b')); spyOn(document, 'execCommand'); MediumEditor.util.execFormatBlock(document, 'blockquote'); expect(document.execCommand).toHaveBeenCalledWith('indent', false, 'blockquote'); MediumEditor.util.isIE = origIsIE; }); }); describe('insertHTMLCommand', function () { it('should not remove the contenteditable element when calling insert into an empty contenteditable element', function () { var el = this.createElement('div', 'editable', ''), origQCS = document.queryCommandSupported; // Force our custom implementation to run spyOn(document, 'queryCommandSupported').and.callFake(function (command) { if (command === 'insertHTML') { return false; } return origQCS.apply(document, arguments); }); // Mimic an editor element el.setAttribute('contenteditable', true); el.setAttribute('data-medium-editor-element', true); // Make sure the element has 0 child nodes while (el.firstChild) { el.remove(el.firstChild); } selectElementContents(el); MediumEditor.util.insertHTMLCommand(document, '<p>some pasted html</p>', true); expect(el.innerHTML).toBe('<p>some pasted html</p>'); expect(document.body.contains(el)).toBe(true, 'The editor element has been removed from the page'); }); // https://github.com/yabwe/medium-editor/issues/992 it('should trigger editableInput when using custom insertHTML implementation and contenteditable does not support input event', function () { // Ensure custom implementation var originalInputSupport = MediumEditor.Events.prototype.InputEventOnContenteditableSupported; MediumEditor.Events.prototype.InputEventOnContenteditableSupported = false; var el = this.createElement('div', 'editable', '<p>orig text</p>'), origQCS = document.queryCommandSupported; // Force our custom implementation to run spyOn(document, 'queryCommandSupported').and.callFake(function (command) { if (command === 'insertHTML') { return false; } return origQCS.apply(document, arguments); }); var editor = this.newMediumEditor('.editable'), spy = jasmine.createSpy('handler'); editor.subscribe('editableInput', spy); selectElementContentsAndFire(el.firstChild); MediumEditor.util.insertHTMLCommand(document, '<p>some pasted html</p>', true); var obj = { target: el, currentTarget: el }; expect(spy).toHaveBeenCalledWith(obj, el); expect(el.innerHTML).toBe('<p>some pasted html</p>'); expect(document.body.contains(el)).toBe(true, 'The editor element has been removed from the page'); MediumEditor.Events.prototype.InputEventOnContenteditableSupported = originalInputSupport; }); }); describe('isDescendant', function () { it('should return true for an element which is a descendant of another', function () { var parent = this.createElement('div'), child = parent.appendChild(document.createTextNode('text')); expect(MediumEditor.util.isDescendant(parent, child)).toBe(true); }); it('should return false for an element which is not a descendant of another', function () { var parent = this.createElement('div'), child = document.createTextNode('text'); expect(MediumEditor.util.isDescendant(parent, child)).toBe(false); }); it('should return false when checking the same element', function () { var parent = this.createElement('div'); expect(MediumEditor.util.isDescendant(parent, parent)).toBe(false); }); it('should return true when checking the same element but using the equality param', function () { var parent = this.createElement('div'); expect(MediumEditor.util.isDescendant(parent, parent, true)).toBe(true); }); it('should return false when the elements are null', function () { var parent = this.createElement('div'); expect(MediumEditor.util.isDescendant(parent, null)).toBe(false); expect(MediumEditor.util.isDescendant(null, parent)).toBe(false); expect(MediumEditor.util.isDescendant(null, null)).toBe(false); }); it('should return false when the parent element is a text node', function () { var parent = document.createTextNode('text'), child = this.createElement('div'); expect(MediumEditor.util.isDescendant(parent, child)).toBe(false); }); }); describe('splitByBlockElements', function () { it('should return block elements without block elements as children', function () { var el = this.createElement('div'); el.innerHTML = '' + '<blockquote><ol>' + '<li><div><table><thead><tr><th>Head</th></tr></thead></table></div></li>' + '<li>List Item</li>' + '</ol>' + '<p>paragraph</p>' + '</blockquote>'; var parts = MediumEditor.util.splitByBlockElements(el); expect(parts.length).toBe(3); // <th>Head</th> expect(parts[0].nodeName.toLowerCase()).toBe('th'); expect(parts[0].textContent).toBe('Head'); // <li>List Item</li> expect(parts[1].nodeName.toLowerCase()).toBe('li'); expect(parts[1].textContent).toBe('List Item'); // <p>paragraph</p> expect(parts[2].nodeName.toLowerCase()).toBe('p'); expect(parts[2].textContent).toBe('paragraph'); }); it('should return inline elements and text nodes if they are siblings of blocks', function () { var el = this.createElement('div'); el.innerHTML = '' + '<blockquote>' + '<span>Text <b>bold <i>bold + italics</i></b> <u>underlined</u></span>' + '<ol><li>List Item</li></ol>' + 'Text Node' + '</blockquote>'; var parts = MediumEditor.util.splitByBlockElements(el); expect(parts.length).toBe(3); // <span>Text <b>bold <i>bold + italics</i></b> <u>underlined</u></span> expect(parts[0].nodeName.toLowerCase()).toBe('span'); expect(parts[0].textContent).toBe('Text bold bold + italics underlined'); // <li>List Item</li> expect(parts[1].nodeName.toLowerCase()).toBe('li'); expect(parts[1].textContent).toBe('List Item'); // Text Node expect(parts[2].nodeName.toLowerCase()).toBe('#text'); expect(parts[2].textContent).toBe('Text Node'); }); it('should ignore comments', function () { var comment = document.createComment('comment'), parts = MediumEditor.util.splitByBlockElements(comment); expect(parts.length).toBe(0); }); it('should ignore nested comments', function () { var el = this.createElement('div'); el.innerHTML = '' + '<p>Text</p>' + '<!---->'; var parts = MediumEditor.util.splitByBlockElements(el); expect(parts.length).toBe(1); }); }); describe('getClosestBlockContainer', function () { it('should return the closest block container', function () { var el = this.createElement('div', '', '<blockquote><p>paragraph</p><ul><li><span>list item</span></li></ul></blockquote>'), span = el.querySelector('span'), container = MediumEditor.util.getClosestBlockContainer(span); expect(container).toBe(el.querySelector('li')); }); it('should return the parent editable if element is a text node child of the editor', function () { var el = this.createElement('div', 'editable', ' <p>text</p>'), emptyTextNode = el.firstChild; this.newMediumEditor('.editable'); var container = MediumEditor.util.getClosestBlockContainer(emptyTextNode); expect(container).toBe(el); }); }); describe('getTopBlockContainer', function () { it('should return the highest level block container', function () { var el = this.createElement('div', '', '<blockquote><p>paragraph</p><ul><li><span>list item</span></li></ul></blockquote>'), span = el.querySelector('span'), container = MediumEditor.util.getTopBlockContainer(span); expect(container).toBe(el.querySelector('blockquote')); }); it('should return the parent editable if element is a text node child of the editor', function () { var el = this.createElement('div', 'editable', ' <p>text</p>'), emptyTextNode = el.firstChild; this.newMediumEditor('.editable'); var container = MediumEditor.util.getTopBlockContainer(emptyTextNode); expect(container).toBe(el); }); }); describe('findPreviousSibling', function () { it('should return the previous sibling of an element if it exists', function () { var el = this.createElement('div', '', '<p>first <b>second </b><i>third</i></p><ul><li>fourth</li></ul>'), second = el.querySelector('b'), third = el.querySelector('i'), prevSibling = MediumEditor.util.findPreviousSibling(third); expect(prevSibling).toBe(second); }); it('should return the previous sibling on an ancestor if a previous sibling does not exist', function () { var el = this.createElement('div', '', '<p>first <b>second </b><i>third</i></p><ul><li>fourth</li></ul>'), fourth = el.querySelector('li').firstChild, p = el.querySelector('p'), prevSibling = MediumEditor.util.findPreviousSibling(fourth); expect(prevSibling).toBe(p); }); it('should not find a previous sibling if the element is at the beginning of an editor element', function () { var el = this.createElement('div', 'editable', '<p>first <b>second </b><i>third</i></p><ul><li>fourth</li></ul>'), first = el.querySelector('p').firstChild; this.newMediumEditor('.editable'); var prevSibling = MediumEditor.util.findPreviousSibling(first); expect(prevSibling).toBeFalsy(); }); }); describe('findOrCreateMatchingTextNodes', function () { it('should return text nodes within an element', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 0, end: el.textContent.length }); expect(textNodes.length).toBe(11); expect(textNodes[0].nodeValue).toBe('Plain '); expect(textNodes[9].nodeValue).toBe('span1 '); expect(textNodes[10].nodeValue).toBe('span2'); }); it('should return text nodes within an element given a start and end range', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 22 }); expect(textNodes.length).toBe(3); expect(textNodes[0].nodeValue).toBe('link'); expect(textNodes[1].nodeValue).toBe(' '); expect(textNodes[2].nodeValue).toBe('italic'); }); it('should split text nodes if start and end range are in the middle of a text node', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 13, end: 19 }); expect(el.querySelector('a').childNodes.length).toBe(2); expect(el.querySelector('i').childNodes.length).toBe(2); expect(textNodes.length).toBe(3); expect(textNodes[0].nodeValue).toBe('nk'); expect(textNodes[1].nodeValue).toBe(' '); expect(textNodes[2].nodeValue).toBe('ita'); }); it('should return an image when it falls within the specified range', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#">li<img src="../demo/img/medium-editor.jpg" />nk</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 15 }); expect(textNodes.length).toBe(3); expect(textNodes[0].nodeValue).toBe('li'); expect(textNodes[1].nodeName.toLowerCase()).toBe('img'); expect(textNodes[2].nodeValue).toBe('nk'); }); it('should return an image when it is at the end of the specified range', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link<img src="../demo/img/medium-editor.jpg" /></a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 15 }); expect(textNodes.length).toBe(2); expect(textNodes[0].nodeValue).toBe('link'); expect(textNodes[1].nodeName.toLowerCase()).toBe('img'); }); it('should return an image when it is the only content in the specified range', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#"><img src="../demo/img/medium-editor.jpg" /></a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 11 }); expect(textNodes.length).toBe(1); expect(textNodes[0].nodeName.toLowerCase()).toBe('img'); }); it('should return images when they are at the beginning of the specified range', function () { var el = this.createElement('div'); el.innerHTML = '<p>Plain <b>bold</b> <a href="#"><img src="../demo/img/medium-editor.jpg" /><img src="../demo/img/roman.jpg" />link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>'; var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 15 }); expect(textNodes.length).toBe(3); expect(textNodes[0].nodeName.toLowerCase()).toBe('img'); expect(textNodes[1].nodeName.toLowerCase()).toBe('img'); expect(textNodes[2].nodeValue).toBe('link'); }); }); // TODO: Remove these tests when getFirstTextNode is deprecated in 6.0.0 describe('getFirstTextNode', function () { it('should find the first text node within an element', function () { var el = this.createElement('div', '', '<p><b><i><u><a href="#">First</a> text</u> in</i> editor</b>!</p>'), anchorText = el.querySelector('a').firstChild, firstText = MediumEditor.util.getFirstTextNode(el); expect(firstText).toBe(anchorText); }); it('should return the text node if passed a text node', function () { var el = this.createElement('div', '', '<p>text</p>'), textNode = el.querySelector('p').firstChild, firstText = MediumEditor.util.getFirstTextNode(textNode); expect(firstText).toBe(textNode); }); it('should return null if no text node exists in element', function () { var el = this.createElement('div'), firstText = MediumEditor.util.getFirstTextNode(el); expect(firstText).toBeNull(); }); }); });