diff --git a/src/js/commands/bold.js b/src/js/commands/bold.js index a669148e2..b8a6cc821 100644 --- a/src/js/commands/bold.js +++ b/src/js/commands/bold.js @@ -15,7 +15,7 @@ export default class BoldCommand extends TextFormatCommand { } exec() { let markerRange = this.editor.cursor.offsets; - if (!markerRange.leftRenderNode || !markerRange.rightRenderNode) { + if (!markerRange.headSection || !markerRange.tailSection) { return; } let markers = this.editor.run((postEditor) => { diff --git a/src/js/commands/italic.js b/src/js/commands/italic.js index b3618a3e7..c7032fa79 100644 --- a/src/js/commands/italic.js +++ b/src/js/commands/italic.js @@ -15,7 +15,7 @@ export default class ItalicCommand extends TextFormatCommand { } exec() { let markerRange = this.editor.cursor.offsets; - if (!markerRange.leftRenderNode || !markerRange.rightRenderNode) { + if (!markerRange.headSection || !markerRange.tailSection) { return; } let markers = this.editor.run((postEditor) => { diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 222874164..dab9577d4 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -40,7 +40,7 @@ import { import { getData, setData } from '../utils/element-utils'; import mixin from '../utils/mixin'; import EventListenerMixin from '../utils/event-listener'; -import Cursor from '../models/cursor'; +import Cursor from '../utils/cursor'; import PostNodeBuilder from '../models/post-node-builder'; export const EDITOR_ELEMENT_CLASS_NAME = 'ck-editor'; @@ -338,10 +338,10 @@ class Editor { this.cursor.moveToSection(offsets.headSection, offsets.headSectionOffset); } else { let results = this.run(postEditor => { - const {headMarker, headOffset} = offsets; + const {headMarker, headMarkerOffset} = offsets; const key = Key.fromEvent(event); - const deletePosition = {marker: headMarker, offset: headOffset}, + const deletePosition = {marker: headMarker, offset: headMarkerOffset}, direction = key.direction; return postEditor.deleteFrom(deletePosition, direction); }); @@ -354,7 +354,7 @@ class Editor { // if there's no left/right nodes, we are probably not in the editor, // or we have selected some non-marker thing like a card - if (!offsets.leftRenderNode || !offsets.rightRenderNode) { + if (!offsets.headSection || !offsets.tailSection) { return; } @@ -533,16 +533,18 @@ class Editor { get activeMarkers() { const { - startMarker, - endMarker + headMarker, + tailMarker } = this.cursor.offsets; - if (!(startMarker && endMarker)) { - return []; + let activeMarkers = []; + + if (headMarker && tailMarker) { + this.post.markersFrom(headMarker, tailMarker, m => { + activeMarkers.push(m); + }); } - let activeMarkers = []; - this.post.markersFrom(startMarker, endMarker, m => activeMarkers.push(m)); return activeMarkers; } diff --git a/src/js/editor/post.js b/src/js/editor/post.js index 7b15387d4..c1940b5e5 100644 --- a/src/js/editor/post.js +++ b/src/js/editor/post.js @@ -26,17 +26,17 @@ class PostEditor { * let marker = editor.post.sections.head.markers.head; * editor.run((postEditor) => { * postEditor.deleteRange({ - * headMarker: marker, - * headOffset: 2, - * tailMarker: marker, - * tailOffset: 4, + * headSection: section, + * headSectionOffset: 2, + * tailSection: section, + * tailSectionOffset: 4, * }); * }); * * `deleteRange` accepts the value of `this.cursor.offsets` for deletion. * * @method deleteRange - * @param {Object} markerRange Object with offsets, {headMarker, headOffset, tailMarker, tailOffset} + * @param {Object} markerRange Object with offsets, {headSection, headSectionOffset, tailSection, tailSectionOffset} * @return {Object} {currentMarker, currentOffset} for cursor * @public */ @@ -56,8 +56,10 @@ class PostEditor { // -- cursor goes at end of marker before the selection start // markerRange should be akin to this.cursor.offset - const {headSection, headSectionOffset, tailSection, tailSectionOffset} = markerRange; - const {post} = this.editor; + const { + headSection, headSectionOffset, tailSection, tailSectionOffset + } = markerRange; + const { post } = this.editor; if (headSection === tailSection) { this.cutSection(headSection, headSectionOffset, tailSectionOffset); @@ -88,9 +90,9 @@ class PostEditor { } cutSection(section, headSectionOffset, tailSectionOffset) { - const {marker:headMarker, offset:headOffset} = section.markerPositionAtOffset(headSectionOffset); - const {marker:tailMarker, offset:tailOffset} = section.markerPositionAtOffset(tailSectionOffset); - const markers = this.splitMarkers({headMarker, headOffset, tailMarker, tailOffset}); + const {marker:headMarker, offset:headMarkerOffset} = section.markerPositionAtOffset(headSectionOffset); + const {marker:tailMarker, offset:tailMarkerOffset} = section.markerPositionAtOffset(tailSectionOffset); + const markers = this.splitMarkers({headMarker, headMarkerOffset, tailMarker, tailMarkerOffset}); section.markers.removeBy(m => { return markers.indexOf(m) !== -1; }); @@ -271,11 +273,11 @@ class PostEditor { * provided. Markers on the outside of the split may also have been modified. * * @method splitMarkers - * @param {Object} markerRange Object with offsets, {headMarker, headOffset, tailMarker, tailOffset} + * @param {Object} markerRange Object with offsets, {headMarker, headMarkerOffset, tailMarker, tailMarkerOffset} * @return {Array} of markers that are inside the split * @public */ - splitMarkers({headMarker, headOffset, tailMarker, tailOffset}) { + splitMarkers({headMarker, headMarkerOffset, tailMarker, tailMarkerOffset}) { const { post } = this.editor; let selectedMarkers = []; @@ -291,27 +293,27 @@ class PostEditor { tailMarker.section.renderNode.markDirty(); if (headMarker === tailMarker) { - let markers = headSection.splitMarker(headMarker, headOffset, tailOffset); + let markers = headSection.splitMarker(headMarker, headMarkerOffset, tailMarkerOffset); selectedMarkers = post.markersInRange({ headMarker: markers[0], tailMarker: markers[markers.length-1], - headOffset, - tailOffset + headMarkerOffset, + tailMarkerOffset }); } else { - let newHeadMarkers = headSection.splitMarker(headMarker, headOffset); + let newHeadMarkers = headSection.splitMarker(headMarker, headMarkerOffset); let selectedHeadMarkers = post.markersInRange({ headMarker: newHeadMarkers[0], tailMarker: newHeadMarkers[newHeadMarkers.length-1], - headOffset + headMarkerOffset }); - let newTailMarkers = tailSection.splitMarker(tailMarker, 0, tailOffset); + let newTailMarkers = tailSection.splitMarker(tailMarker, 0, tailMarkerOffset); let selectedTailMarkers = post.markersInRange({ headMarker: newTailMarkers[0], tailMarker: newTailMarkers[newTailMarkers.length-1], - headOffset: 0, - tailOffset + headMarkerOffset: 0, + tailMarkerOffset }); let newHeadMarker = selectedHeadMarkers[0], @@ -348,8 +350,8 @@ class PostEditor { * let marker = editor.post.sections.head.marker.head; * editor.run((postEditor) => { * postEditor.splitSection({ - * headMarker: marker, - * headOffset: 3 + * headSection: section, + * headSectionOffset: 3 * }); * }); * // Will result in the marker and its old section being removed from @@ -358,20 +360,20 @@ class PostEditor { * * The return value will be the two new sections. One or both of these * sections can be blank (contain only a blank marker), for example if the - * headOffset is 0. + * headMaOffset is 0. * * @method splitMarkers - * @param {Object} markerRange Object with offsets, {headMarker, headOffset, tailMarker, tailOffset} + * @param {Object} markerRange Object with offsets, {headSection, headSectionOffset} * @return {Array} of new sections, one for the first half and one for the second * @public */ splitSection({headSection: section, headSectionOffset}) { let { marker: headMarker, - offset: headOffset + offset: headMarkerOffset } = section.markerPositionAtOffset(headSectionOffset); - const [beforeSection, afterSection] = section.splitAtMarker(headMarker, headOffset); + const [beforeSection, afterSection] = section.splitAtMarker(headMarker, headMarkerOffset); this._replaceSection(section, [beforeSection, afterSection]); @@ -406,7 +408,7 @@ class PostEditor { * value as `splitMarkers`. * * @method applyMarkupToMarkers - * @param {Object} markerRange Object with offsets, {headMarker, headOffset, tailMarker, tailOffset} + * @param {Object} markerRange Object with offsets * @param {Object} markup A markup post abstract node * @return {Array} of markers that are inside the split * @public @@ -443,7 +445,7 @@ class PostEditor { * value as `splitMarkers`. * * @method removeMarkupFromMarkers - * @param {Object} markerRange Object with offsets, {headMarker, headOffset, tailMarker, tailOffset} + * @param {Object} markerRange Object with offsets * @param {Object} markup A markup post abstract node * @return {Array} of markers that are inside the split * @public diff --git a/src/js/models/post.js b/src/js/models/post.js index b4f1b6a32..6caf6d89f 100644 --- a/src/js/models/post.js +++ b/src/js/models/post.js @@ -10,18 +10,18 @@ export default class Post { }); } - markersInRange({headMarker, headOffset, tailMarker, tailOffset}) { + markersInRange({headMarker, headMarkerOffset, tailMarker, tailMarkerOffset}) { let offset = 0; let foundMarkers = []; - let toEnd = tailOffset === undefined; - if (toEnd) { tailOffset = 0; } + let toEnd = tailMarkerOffset === undefined; + if (toEnd) { tailMarkerOffset = 0; } this.markersFrom(headMarker, tailMarker, marker => { if (toEnd) { - tailOffset += marker.length; + tailMarkerOffset += marker.length; } - if (offset >= headOffset && offset < tailOffset) { + if (offset >= headMarkerOffset && offset < tailMarkerOffset) { foundMarkers.push(marker); } @@ -74,20 +74,21 @@ export default class Post { return {changedSections, removedSections}; } /** - * Invoke `callbackFn` for all markers between the startMarker and endMarker (inclusive), + * Invoke `callbackFn` for all markers between the headMarker and tailMarker (inclusive), * across sections */ - markersFrom(startMarker, endMarker, callbackFn) { - let currentMarker = startMarker; + markersFrom(headMarker, tailMarker, callbackFn) { + let currentMarker = headMarker; while (currentMarker) { callbackFn(currentMarker); - if (currentMarker === endMarker) { + if (currentMarker === tailMarker) { currentMarker = null; } else if (currentMarker.next) { currentMarker = currentMarker.next; } else { let nextSection = currentMarker.section.next; + // FIXME: This will fail across cards currentMarker = nextSection && nextSection.markers.head; } } diff --git a/src/js/models/cursor.js b/src/js/utils/cursor.js similarity index 54% rename from src/js/models/cursor.js rename to src/js/utils/cursor.js index 70fb10819..9e7bdf8cf 100644 --- a/src/js/models/cursor.js +++ b/src/js/utils/cursor.js @@ -1,17 +1,15 @@ -import { - detect -} from '../utils/array-utils'; - +import { detect } from '../utils/array-utils'; import { isSelectionInElement, clearSelection } from '../utils/selection-utils'; - import { detectParentNode, isTextNode, walkDOM } from '../utils/dom-utils'; +import Position from "./cursor/position"; +import Range from "./cursor/range"; function findOffsetInParent(parentElement, targetElement, targetOffset) { let offset = 0; @@ -31,8 +29,10 @@ function findOffsetInParent(parentElement, targetElement, targetOffset) { } function findSectionContaining(sections, childNode) { - const {result:section} = detectParentNode(childNode, node => { - return detect(sections, s => s.renderNode.element === node); + const { result: section } = detectParentNode(childNode, node => { + return detect(sections, section => { + return section.renderNode.element === node; + }); }); return section; } @@ -98,87 +98,29 @@ export default class Cursor { get offsets() { const { sections } = this.post; - const selection = this.selection; - const { isCollapsed, rangeCount } = selection; - const range = rangeCount > 0 && selection.getRangeAt(0); + const { selection } = this; - if (!range) { + if (selection.rangeCount === 0 || !selection.getRangeAt(0)) { return {}; } + let { + leftNode, leftOffset, rightNode, rightOffset + } = comparePosition(selection); - let {leftNode, leftOffset, rightNode, rightOffset} = comparePosition(selection); - - // The selection should contain two text nodes, but may contain a P - // tag if the section only has a blank br marker or on - // Chrome/Safari using shift+ can create a selection with - // a tag rather than a text node. This fixes that. - // See https://github.com/bustlelabs/content-kit-editor/issues/56 - - let leftRenderNode = this.renderTree.getElementRenderNode(leftNode), - rightRenderNode = this.renderTree.getElementRenderNode(rightNode); + let headPosition = Position.fromNode( + this.renderTree, sections, leftNode, leftOffset + ); + let tailPosition = Position.fromNode( + this.renderTree, sections, rightNode, rightOffset + ); - if (!rightRenderNode) { - let rightSection = findSectionContaining(sections, rightNode); - let rightMarker = rightSection.markers.head; - rightRenderNode = rightMarker.renderNode; - rightNode = rightRenderNode.element; - rightOffset = 0; - } - - if (!leftRenderNode) { - let leftSection = findSectionContaining(sections, leftNode); - let leftMarker = leftSection.markers.head; - leftRenderNode = leftMarker.renderNode; - leftNode = leftRenderNode.element; - leftOffset = 0; - } - - const startMarker = leftRenderNode && leftRenderNode.postNode, - endMarker = rightRenderNode && rightRenderNode.postNode; - - const startSection = startMarker && startMarker.section; - const endSection = endMarker && endMarker.section; - - const headSectionOffset = startSection && - startMarker && - startMarker.offsetInParent(leftOffset); - - const tailSectionOffset = endSection && - endMarker && - endMarker.offsetInParent(rightOffset); - - return { - leftNode, - rightNode, - leftOffset, - rightOffset, - leftRenderNode, - rightRenderNode, - startMarker, - endMarker, - startSection, - endSection, - - // FIXME: this should become the public API - headMarker: startMarker, - tailMarker: endMarker, - headOffset: leftOffset, - tailOffset: rightOffset, - headNode: leftNode, - tailNode: rightNode, - - headSection: startSection, - tailSection: endSection, - headSectionOffset, - tailSectionOffset, - isCollapsed - }; + return Range.fromPositions(headPosition, tailPosition); } get activeSections() { const { sections } = this.post; - const selection = this.selection; + const { selection } = this; const { rangeCount } = selection; const range = rangeCount > 0 && selection.getRangeAt(0); @@ -187,10 +129,10 @@ export default class Cursor { } const { startContainer, endContainer } = range; - const startSection = findSectionContaining(sections, startContainer); - const endSection = findSectionContaining(sections, endContainer); + const headSection = findSectionContaining(sections, startContainer); + const tailSection = findSectionContaining(sections, endContainer); - return sections.readRange(startSection, endSection); + return sections.readRange(headSection, tailSection); } // moves cursor to the start of the section @@ -210,27 +152,24 @@ export default class Cursor { } selectSections(sections) { - const startSection = sections[0], - endSection = sections[sections.length - 1]; + const headSection = sections[0], + tailSection = sections[sections.length - 1]; - const startNode = startSection.markers.head.renderNode.element, - endNode = endSection.markers.tail.renderNode.element; + const headMarker = headSection.markers.head, + tailMarker = tailSection.markers.tail; - const startOffset = 0, - endOffset = endNode.textContent.length; + const headMarkerOffset = 0, + tailMarkerOffset = tailMarker.length; - this.moveToNode(startNode, startOffset, endNode, endOffset); + this.moveToMarker(headMarker, headMarkerOffset, tailMarker, tailMarkerOffset); } selectMarkers(markers) { - const startMarker = markers[0], - endMarker = markers[markers.length - 1]; - - const startNode = startMarker.renderNode.element, - endNode = endMarker.renderNode.element; - const startOffset = 0, endOffset = endMarker.length; - - this.moveToNode(startNode, startOffset, endNode, endOffset); + const headMarker = markers[0], + tailMarker = markers[markers.length - 1], + headOffset = 0, + tailOffset = tailMarker.length; + this.moveToMarker(headMarker, headOffset, tailMarker, tailOffset); } /** diff --git a/src/js/utils/cursor/position.js b/src/js/utils/cursor/position.js new file mode 100644 index 000000000..0cfbe22e2 --- /dev/null +++ b/src/js/utils/cursor/position.js @@ -0,0 +1,60 @@ +import { detect } from 'content-kit-editor/utils/array-utils'; +import { detectParentNode } from 'content-kit-editor/utils/dom-utils'; + +function findSectionContaining(sections, childNode) { + const { result: section } = detectParentNode(childNode, node => { + return detect(sections, section => { + return section.renderNode.element === node; + }); + }); + return section; +} + +export default class Position { + constructor(section, offsetInSection=0) { + let marker = null, + offsetInMarker = null; + + if (section !== null && offsetInSection !== null) { + let markerPosition = section.markerPositionAtOffset( + offsetInSection + ); + marker = markerPosition.marker; + offsetInMarker = markerPosition.offset; + } + + this.section = section; + this.offsetInSection = offsetInSection; + this.marker = marker; + this.offsetInMarker = offsetInMarker; + } + isEqual(position) { + return this.section === position.section && + this.offsetInSection === position.offsetInSection; + } + static fromNode(renderTree, sections, node, offsetInNode) { + // Only markers are registered into the element/renderNode map + let markerRenderNode = renderTree.getElementRenderNode(node); + + let section = null, offsetInSection = null; + if (markerRenderNode) { + let marker = markerRenderNode.postNode; + section = marker.section; + offsetInSection = marker.offsetInParent(offsetInNode); + } + + if (!section) { + // The selection should contain two text nodes, but may contain a P + // tag if the section only has a blank br marker or on + // Chrome/Safari using shift+ can create a selection with + // a tag rather than a text node. This fixes that. + // See https://github.com/bustlelabs/content-kit-editor/issues/56 + section = findSectionContaining(sections, node); + if (section) { + offsetInSection = 0; + } + } + + return new Position(section, offsetInSection); + } +} diff --git a/src/js/utils/cursor/range.js b/src/js/utils/cursor/range.js new file mode 100644 index 000000000..d76d063a7 --- /dev/null +++ b/src/js/utils/cursor/range.js @@ -0,0 +1,39 @@ +export default class Range { + constructor(head, tail) { + this.head = head; + this.tail = tail; + } + + // "legacy" APIs + get headSection() { + return this.head.section; + } + get tailSection() { + return this.tail.section; + } + get headSectionOffset() { + return this.head.offsetInSection; + } + get tailSectionOffset() { + return this.tail.offsetInSection; + } + get isCollapsed() { + return this.head.isEqual(this.tail); + } + get headMarker() { + return this.head.marker; + } + get tailMarker() { + return this.tail.marker; + } + get headMarkerOffset() { + return this.head.offsetInMarker; + } + get tailMarkerOffset() { + return this.tail.offsetInMarker; + } + + static fromPositions(head, tail) { + return new Range(head, tail); + } +} diff --git a/tests/acceptance/basic-editor-test.js b/tests/acceptance/basic-editor-test.js index a679c57c0..454123e12 100644 --- a/tests/acceptance/basic-editor-test.js +++ b/tests/acceptance/basic-editor-test.js @@ -1,4 +1,5 @@ import { Editor } from 'content-kit-editor'; +import Helpers from '../test-helpers'; const { test, module } = QUnit; @@ -17,8 +18,6 @@ module('Acceptance: editor: basic', { }); test('sets element as contenteditable', (assert) => { - let innerHTML = `

Hello

`; - editorElement.innerHTML = innerHTML; editor = new Editor(); editor.render(editorElement); @@ -30,8 +29,6 @@ test('sets element as contenteditable', (assert) => { }); test('#disableEditing before render is meaningful', (assert) => { - let innerHTML = `

Hello

`; - editorElement.innerHTML = innerHTML; editor = new Editor(); editor.disableEditing(); editor.render(editorElement); @@ -45,8 +42,6 @@ test('#disableEditing before render is meaningful', (assert) => { }); test('#disableEditing and #enableEditing toggle contenteditable', (assert) => { - let innerHTML = `

Hello

`; - editorElement.innerHTML = innerHTML; editor = new Editor(); editor.render(editorElement); @@ -62,3 +57,27 @@ test('#disableEditing and #enableEditing toggle contenteditable', (assert) => { 'true', 'element is contenteditable'); }); + +test('clicking outside the editor does not raise an error', (assert) => { + editor = new Editor({autofocus: false}); + editor.render(editorElement); + + let secondEditorElement = document.createElement('div'); + document.body.appendChild(secondEditorElement); + + let secondEditor = new Editor(); // This editor will be focused + secondEditor.render(secondEditorElement); + + Helpers.dom.triggerEvent(editorElement, 'click'); + + // Embed intent uses setTimeout, so this assertion must + // setTimeout after it to catch the exception during failure + // cases. + let done = assert.async(); + setTimeout(() => { + assert.ok(true, 'can click external item without error'); + done(); + secondEditor.destroy(); + document.body.removeChild(secondEditorElement); + }, 1); +}); diff --git a/tests/acceptance/editor-sections-test.js b/tests/acceptance/editor-sections-test.js index 159f7917c..75309bc12 100644 --- a/tests/acceptance/editor-sections-test.js +++ b/tests/acceptance/editor-sections-test.js @@ -374,14 +374,23 @@ test('when selection incorrectly contains P end tag, editor reports correct sele setTimeout(() => { assert.ok(true, 'No error should occur'); - let firstSectionTextNode = editor.element.childNodes[0].childNodes[0]; - - let {leftNode, rightNode, leftOffset, rightOffset} = editor.cursor.offsets; - - assert.equal(leftNode, firstSectionTextNode, 'returns first section text node as left'); - assert.equal(rightNode, secondSectionTextNode, 'returns second section text node as right'); - assert.equal(leftOffset, 0, 'leftOffset correct'); - assert.equal(rightOffset, 0, 'rightOffset correct'); + let { + headSection, tailSection, headMarker, tailMarker, + headSectionOffset, tailSectionOffset, headMarkerOffset, tailMarkerOffset + } = editor.cursor.offsets; + + assert.equal(headSection, editor.post.sections.objectAt(0), + 'returns first section head'); + assert.equal(tailSection, editor.post.sections.objectAt(1), + 'returns second section tail'); + assert.equal(headMarker, editor.post.sections.objectAt(0).markers.head, + 'returns first section marker head'); + assert.equal(tailMarker, editor.post.sections.objectAt(1).markers.head, + 'returns second section marker tail'); + assert.equal(headMarkerOffset, 0, 'headMarkerOffset correct'); + assert.equal(tailMarkerOffset, 0, 'tailMarkerOffset correct'); + assert.equal(headSectionOffset, 0, 'headSectionOffset correct'); + assert.equal(tailSectionOffset, 0, 'tailSectionOffset correct'); done(); }); @@ -395,7 +404,6 @@ test('when selection incorrectly contains P start tag, editor reports correct se let firstSectionTextNode = editor.element.childNodes[0].firstChild; let secondSectionPNode = editor.element.childNodes[1]; - let secondSectionTextNode = secondSectionPNode.childNodes[0]; Helpers.dom.moveCursorTo(firstSectionTextNode, 0, secondSectionPNode, 0); @@ -404,14 +412,23 @@ test('when selection incorrectly contains P start tag, editor reports correct se setTimeout(() => { assert.ok(true, 'No error should occur'); - let firstSectionTextNode = editor.element.childNodes[0].childNodes[0]; - - let {leftNode, rightNode, leftOffset, rightOffset} = editor.cursor.offsets; - - assert.equal(leftNode, firstSectionTextNode, 'returns first section text node as left'); - assert.equal(rightNode, secondSectionTextNode, 'returns second section p node as right'); - assert.equal(leftOffset, 0, 'leftOffset correct'); - assert.equal(rightOffset, 0, 'rightOffset correct'); + let { + headSection, tailSection, headMarker, tailMarker, + headSectionOffset, tailSectionOffset, headMarkerOffset, tailMarkerOffset + } = editor.cursor.offsets; + + assert.equal(headSection, editor.post.sections.objectAt(0), + 'returns first section head'); + assert.equal(tailSection, editor.post.sections.objectAt(1), + 'returns second section tail'); + assert.equal(headMarker, editor.post.sections.objectAt(0).markers.head, + 'returns first section marker head'); + assert.equal(tailMarker, editor.post.sections.objectAt(1).markers.head, + 'returns second section marker tail'); + assert.equal(headMarkerOffset, 0, 'headMarkerOffset correct'); + assert.equal(tailMarkerOffset, 0, 'tailMarkerOffset correct'); + assert.equal(headSectionOffset, 0, 'headSectionOffset correct'); + assert.equal(tailSectionOffset, 0, 'tailSectionOffset correct'); done(); });