UNPKG

node-webodf

Version:

WebODF - JavaScript Document Engine http://webodf.org/

714 lines (613 loc) 35.6 kB
/** * Copyright (C) 2013 KO GmbH <copyright@kogmbh.com> * * @licstart * This file is part of WebODF. * * WebODF is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License (GNU AGPL) * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * WebODF 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 WebODF. If not, see <http://www.gnu.org/licenses/>. * @licend * * @source: http://www.webodf.org/ * @source: https://github.com/kogmbh/WebODF/ */ /*global runtime, core, gui, odf, ops, Node, NodeFilter, xmldom*/ /** * @constructor * @param {core.UnitTestRunner} runner * @implements {core.UnitTest} */ ops.OdtDocumentTests = function OdtDocumentTests(runner) { "use strict"; var r = runner, t, testarea, inputMemberId = "Joe", /**@const*/ PREVIOUS = core.StepDirection.PREVIOUS, prefixToNamespace = { fo: odf.Namespaces.namespaceMap.fo, text: odf.Namespaces.namespaceMap.text, style: odf.Namespaces.namespaceMap.style, office: odf.Namespaces.namespaceMap.office, draw: odf.Namespaces.namespaceMap.draw, e: "urn:webodf:names:editinfo", c: "urn:webodf:names:cursor", html: "http://www.w3.org/1999/xhtml" }; /** * Class that filters runtime specific nodes from the DOM. * @constructor * @implements {xmldom.LSSerializerFilter} */ function OdfOrCursorNodeFilter() { var odfFilter = new odf.OdfNodeFilter(); /** * @param {!Node} node * @return {!number} */ this.acceptNode = function (node) { if (node.namespaceURI === prefixToNamespace.c || node.namespaceURI === prefixToNamespace.e || node.namespaceURI === prefixToNamespace.html) { return NodeFilter.FILTER_ACCEPT; } return odfFilter.acceptNode(node); }; } /** * Trying to avoid having to load a complete document for these tests. Mocking ODF * canvas allows some simplification in the testing setup * @param {Element} node * @extends {odf.OdfCanvas} Well.... we don't really, but please shut your face closure compiler :) * @constructor */ function MockOdfCanvas(node) { var self = this; this.odfContainer = function () { return self; }; this.getContentElement = function () { return node.getElementsByTagNameNS(odf.Namespaces.officens, 'text')[0]; }; this.rootElement = node; } function appendCssRule(rule) { t.styles.insertRule(rule, t.styles.cssRules.length); } function createOdtDocument(xml) { var domDocument = testarea.ownerDocument, doc = core.UnitTest.createOdtDocument("<office:text>" + xml + "</office:text>", prefixToNamespace), node = /**@type{!Element}*/(domDocument.importNode(doc.documentElement, true)); testarea.appendChild(node); t.root = node; t.odtDocument = new ops.OdtDocument(new MockOdfCanvas(t.root)); t.cursor = new ops.OdtCursor(inputMemberId, t.odtDocument); t.odtDocument.addCursor(t.cursor); t.range = t.root.ownerDocument.createRange(); t.filter = t.odtDocument.getPositionFilter(); appendCssRule("text|p { font-family: monospace; }\n"); // Ensure font chars are always monospaced return node; } /** * @param {!number} stepsToAnchor Number of steps to advance the cursor * @param {!number=} stepsDiffToFocus */ function setCursorPosition(stepsToAnchor, stepsDiffToFocus) { var newRangeSelection = t.odtDocument.convertCursorToDomRange(stepsToAnchor, stepsDiffToFocus); t.cursor.setSelectedRange(newRangeSelection, stepsDiffToFocus >= 0); } /** * Returns true if both the anchor and focus nodes are in a walkable step * @returns {!boolean} */ function isCursorSelectionInWalkablePositions() { var iterator = t.odtDocument.createStepIterator(t.cursor.getNode(), 0, [t.filter], t.root); if (iterator.isStep()) { iterator.setPosition(t.cursor.getAnchorNode(), 0); if (iterator.isStep()) { return true; } } return false; } /** * Returns true if the supplied step exists in the current document. Otherwise returns false. * @param {!number} step * @returns {!boolean} */ function documentHasStep(step) { try { t.odtDocument.convertCursorToDomRange(step, 0); return true; } catch(e) { return false; } } /** * Test cursor iteration over a document fragment. Each supported cursor position should be indicated in * the fragment using the pipe character ('|'). This exercises both forwards and backwards iteration. * @param {!string} documentString Document fragment with cursor positions indicated using a vertical pipe '|' */ /*jslint regexp:true*/ function testCursorPositions(documentString) { var segments = documentString.split("|"), serializer = new xmldom.LSSerializer(), cursorSerialized = /<c:cursor[^>]*><\/c:cursor>/, position, step = 0, documentRoot; serializer.filter = new OdfOrCursorNodeFilter(); t.segmentCount = segments.length; t.expectedCursorPositionsCount = segments.length - 1; // e.g., <a>|</a> has two segments, but only one cursor position r.shouldBe(t, "t.segmentCount > 1", "true"); documentRoot = createOdtDocument(segments.join("")); t.documentLength = t.odtDocument.convertDomPointToCursorStep(documentRoot, documentRoot.childNodes.length, PREVIOUS); r.shouldBe(t, "t.expectedCursorPositionsCount", "(t.documentLength+1)"); // Test iteration forward for (position = 1; position < segments.length; position += 1) { setCursorPosition(step); t.currentDocLength = t.odtDocument.convertDomPointToCursorStep(documentRoot, documentRoot.childNodes.length, PREVIOUS); r.shouldBe(t, "t.currentDocLength", "t.documentLength"); t.lastValidStep = step; t.expected = "<office:text>" + segments.slice(0, position).join("") + "|" + segments.slice(position, segments.length).join("") + "</office:text>"; t.result = serializer.writeToString(t.root.firstChild, prefixToNamespace); t.result = t.result.replace(cursorSerialized, "|"); r.shouldBe(t, "t.result", "t.expected"); step += 1; } // Ensure there are no other walkable positions in the document t.isLastStep = documentHasStep(step) === false; r.shouldBe(t, "t.isLastStep", "true"); step = t.lastValidStep; // Test iteration backward for (position = segments.length - 1; position > 0; position -= 1) { setCursorPosition(step); t.currentDocLength = t.odtDocument.convertDomPointToCursorStep(documentRoot, documentRoot.childNodes.length, PREVIOUS); r.shouldBe(t, "t.currentDocLength", "t.documentLength"); t.expected = "<office:text>" + segments.slice(0, position).join("") + "|" + segments.slice(position, segments.length).join("") + "</office:text>"; t.result = serializer.writeToString(t.root.firstChild, prefixToNamespace); t.result = t.result.replace(cursorSerialized, "|"); r.shouldBe(t, "t.result", "t.expected"); step -= 1; } // Ensure there are no other walkable positions in the document t.isFirstStep = documentHasStep(step) === false; r.shouldBe(t, "t.isFirstStep", "true"); } /** * Wraps the supplied node in a parent div * @param {!Node} node */ function wrapInDiv(node) { var container = t.root.ownerDocument.createElement("div"); node.parentNode.insertBefore(container, node); container.appendChild(node); } function testFixCursorPositions_AnchorInInvalidPlace() { createOdtDocument("<text:p>ABCD</text:p>"); setCursorPosition(1, 2); wrapInDiv(t.cursor.getAnchorNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "1"); r.shouldBe(t, "t.rootToFocus", "3"); } function testFixCursorPositions_Range_CursorInInvalidPlace() { createOdtDocument("<text:p>ABCD</text:p>"); setCursorPosition(1, 2); wrapInDiv(t.cursor.getNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "1"); r.shouldBe(t, "t.rootToFocus", "3"); } function testFixCursorPositions_Range_AnchorAndCursorInInvalidPlace() { createOdtDocument("<text:p>ABCD</text:p>"); setCursorPosition(1, 2); wrapInDiv(t.cursor.getNode()); wrapInDiv(t.cursor.getAnchorNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "1"); r.shouldBe(t, "t.rootToFocus", "3"); } function testFixCursorPositions_Range_AnchorAndCursorInInvalidPlace_OverAnnotation() { createOdtDocument("<text:p>AB<office:annotation><text:p>#</text:p></office:annotation>CD</text:p>"); setCursorPosition(1, 4); wrapInDiv(t.cursor.getNode()); wrapInDiv(t.cursor.getAnchorNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "1"); r.shouldBe(t, "t.rootToFocus", "5"); } function testFixCursorPositions_CursorAndAnchorNearParagraphStart() { createOdtDocument("<text:p>A</text:p><text:p>BC</text:p><text:p>DE</text:p>"); setCursorPosition(2, 3); wrapInDiv(t.cursor.getNode()); wrapInDiv(t.cursor.getAnchorNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); t.selectedText = t.cursor.getSelectedRange().toString(); r.shouldBe(t, "t.selectedText", "'BC'"); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "2"); r.shouldBe(t, "t.rootToFocus", "5"); } function testFixCursorPositions_CursorNearParagraphStart_ForwardSelection() { createOdtDocument("<text:p>A</text:p><text:p>BCD</text:p><text:p>E</text:p>"); setCursorPosition(2, 3); wrapInDiv(t.cursor.getNode()); wrapInDiv(t.cursor.getAnchorNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); t.selectedText = t.cursor.getSelectedRange().toString(); r.shouldBe(t, "t.selectedText", "'BCD'"); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "2"); r.shouldBe(t, "t.rootToFocus", "5"); } function testFixCursorPositions_CursorNearParagraphStart_ReverseSelection() { createOdtDocument("<text:p>A</text:p><text:p>BCD</text:p><text:p>E</text:p>"); setCursorPosition(5, -3); wrapInDiv(t.cursor.getNode()); wrapInDiv(t.cursor.getAnchorNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToAnchor = t.odtDocument.convertDomPointToCursorStep(t.cursor.getAnchorNode(), 0, PREVIOUS); t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); t.selectedText = t.cursor.getSelectedRange().toString(); r.shouldBe(t, "t.selectedText", "'BCD'"); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToAnchor", "5"); r.shouldBe(t, "t.rootToFocus", "2"); } function testFixCursorPositions_Collapsed_CursorInInvalidPlace() { createOdtDocument("<text:p>ABCD</text:p>"); setCursorPosition(1); wrapInDiv(t.cursor.getNode()); t.odtDocument.fixCursorPositions(); t.isWalkable = isCursorSelectionInWalkablePositions(); t.anchorInDiv = t.cursor.getAnchorNode().parentNode.localName === "div"; t.cursorInDiv = t.cursor.getNode().parentNode.localName === "div"; t.rootToFocus = t.odtDocument.convertDomPointToCursorStep(t.cursor.getNode(), 0, PREVIOUS); r.shouldBe(t, "t.isWalkable", "true"); r.shouldBe(t, "t.anchorInDiv", "false"); r.shouldBe(t, "t.cursorInDiv", "false"); r.shouldBe(t, "t.rootToFocus", "1"); } function getTextNodeAtStep_BeginningOfTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; t.domPosition = t.odtDocument.getTextNodeAtStep(0); // paragraph children are: <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[1]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "'ABCD'"); r.shouldBe(t, "t.domPosition.offset", "0"); } function getTextNodeAtStep_EndOfTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; t.domPosition = t.odtDocument.getTextNodeAtStep(4); // paragraph children are: <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[1]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "'ABCD'"); r.shouldBe(t, "t.domPosition.offset", "4"); } function getTextNodeAtStep_PositionWithNoTextNode_AddsNewNode_Front() { var doc = createOdtDocument("<text:p><text:s> </text:s></text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; t.domPosition = t.odtDocument.getTextNodeAtStep(0); // paragraph children are: <cursor>, #text, <s> r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[1]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); } function getTextNodeAtStep_PositionWithNoTextNode_AddsNewNode_Back() { var doc = createOdtDocument("<text:p><text:s> </text:s></text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; t.domPosition = t.odtDocument.getTextNodeAtStep(1); // paragraph children are: <cursor>, <s>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[2]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); } function getTextNodeAtStep_At0_PutsTargetMemberCursor_AfterTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(0); t.domPosition = t.odtDocument.getTextNodeAtStep(0, inputMemberId); // paragraph children are: #text, <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[0]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().previousSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_At1_PutsTargetMemberCursor_AfterTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(1); t.domPosition = t.odtDocument.getTextNodeAtStep(1, inputMemberId); // paragraph children are: #text, <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[0]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "'A'"); r.shouldBe(t, "t.domPosition.offset", "1"); r.shouldBe(t, "t.cursor.getNode().previousSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_At4_PutsTargetMemberCursor_AfterTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(4); t.domPosition = t.odtDocument.getTextNodeAtStep(4, inputMemberId); // paragraph children are: #text, <cursor> r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[0]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "'ABCD'"); r.shouldBe(t, "t.domPosition.offset", "4"); r.shouldBe(t, "t.cursor.getNode().previousSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_AfterNonText_PutsTargetMemberCursor_AfterTextNode() { var doc = createOdtDocument("<text:p>ABC<text:s> </text:s></text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(4); t.domPosition = t.odtDocument.getTextNodeAtStep(4, inputMemberId); // paragraph children are: #text, <s>, #text, <cursor> r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[2]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().previousSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_RearrangesExistingCursors_MovesMemberAfterTextNode() { var doc = createOdtDocument("<text:p/>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; t.odtDocument.addCursor(new ops.OdtCursor("new 1", t.odtDocument)); t.odtDocument.addCursor(new ops.OdtCursor("new 2", t.odtDocument)); t.domPosition = t.odtDocument.getTextNodeAtStep(0, inputMemberId); // paragraph children are: <cursor>, <cursor>, #text, <cursor member="Joe"> r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[2]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().previousSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_At0_PutsTargetMemberCursor_BeforeTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(0); t.domPosition = t.odtDocument.getTextNodeAtStep(0); // paragraph children are: <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[1]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "'ABCD'"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().nextSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_At1_PutsTargetMemberCursor_BeforeTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(1); t.domPosition = t.odtDocument.getTextNodeAtStep(1); // paragraph children are: #text, <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[2]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "'BCD'"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().nextSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_At4_PutsTargetMemberCursor_BeforeTextNode() { var doc = createOdtDocument("<text:p>ABCD</text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(4); t.domPosition = t.odtDocument.getTextNodeAtStep(4); // paragraph children are: #text, <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[2]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().nextSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_AfterNonText_PutsTargetMemberCursor_BeforeTextNode() { var doc = createOdtDocument("<text:p>ABC<text:s> </text:s></text:p>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; setCursorPosition(4); t.domPosition = t.odtDocument.getTextNodeAtStep(4); // paragraph children are: #text, <s>, <cursor>, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[3]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.cursor.getNode().nextSibling", "t.domPosition.textNode"); } function getTextNodeAtStep_EmptyP_MovesAllCursors_BeforeTextNode() { var doc = createOdtDocument("<text:p/>"); t.paragraph = doc.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]; t.odtDocument.addCursor(new ops.OdtCursor("new 1", t.odtDocument)); t.odtDocument.addCursor(new ops.OdtCursor("new 2", t.odtDocument)); t.domPosition = t.odtDocument.getTextNodeAtStep(0); // paragraph children are: <cursor>, <cursor>, <cursor member="Joe">, #text r.shouldBe(t, "t.domPosition.textNode", "t.paragraph.childNodes[3]"); r.shouldBe(t, "t.domPosition.textNode.textContent", "''"); r.shouldBe(t, "t.domPosition.offset", "0"); r.shouldBe(t, "t.domPosition.textNode.nextSibling", "null"); r.shouldBe(t, "t.domPosition.textNode.previousSibling.localName", "'cursor'"); } function cursorPositionTests() { // Examples from README_cursorpositions.txt return [ // *************** Empty Paragraph *************** // // Examples from README_cursorpositions.txt "<text:p>|</text:p>", // TODO behaviour is different from README_cursorpositions // "<text:p><text:span>|</text:span></text:p>", // TODO behaviour is different from README_cursorpositions // "<text:p><text:span>|</text:span><text:span></text:span></text:p>", // TODO behaviour is different from README_cursorpositions // "<text:p><text:span>|<text:span></text:span></text:span></text:p>", "<text:p>| </text:p>", // TODO behaviour is different from README_cursorpositions // "<text:p> <text:span>| </text:span> </text:p>", // TODO behaviour is different from README_cursorpositions // "<text:p> <text:span>| </text:span> <text:span> <text:span> </text:span> </text:span> </text:p>", // *************** Simple Text Nodes *************** // "<text:p>|A|B|C|</text:p>", // *************** Mixed spans *************** // "<text:p><text:span>|A|B|</text:span>C|</text:p>", "<text:p>|A|<text:span>B|</text:span>C|</text:p>", "<text:p>|A|<text:span>B|C|</text:span></text:p>", "<text:p><text:span>|A|<text:span>B|</text:span></text:span>C|</text:p>", "<text:p>|A|<text:span><text:span>B|</text:span>C|</text:span></text:p>", // *************** Whitespace *************** // "<text:p>|A| |B|C|</text:p>", "<text:p> |A| </text:p>", "<text:p>|A| |B|</text:p>", "<text:p> |A| | B| </text:p>", "<text:p> <text:span> |a| </text:span> </text:p>", "<text:p> <text:span>|a| | </text:span> <text:span> b|</text:span> </text:p>", // TODO behaviour is different from README_cursorpositions // cursorPositionsTest("<text:p> <text:span> |a<text:span>|</text:span> </text:span> </text:p>", "<text:p> <text:span> |a|<text:span></text:span> </text:span> </text:p>", "<text:p><text:span></text:span> |a|</text:p>", // *************** Space elements *************** // "<text:p>|A|<text:s> </text:s>|B|C|</text:p>", // Unexpanded spaces - Not really supported, but interesting to test "<text:p>|A|<text:s></text:s>|B|C|</text:p>", // Slight span nesting and positioning "<text:p><text:span>|<text:s> </text:s>|</text:span></text:p>", // TODO behaviour is different from README_cursorpositions // cursorPositionsTest("<text:p> <text:span>|A| |</text:span> <text:s></text:s>| <text:span><text:s> </text:s>|B|</text:span> </text:p>", "<text:p>|<text:tab> </text:tab>|<text:s> </text:s>|<text:s> </text:s>|</text:p>", "<text:p>|<text:tab> </text:tab>| |<text:s> </text:s>|</text:p>", "<text:p>|a| | <text:s> </text:s>| </text:p>", "<text:p><e:editinfo></e:editinfo><text:span></text:span>|a|<text:s> </text:s>|<text:s> </text:s>|<text:span></text:span><text:span></text:span></text:p>", // *************** Draw elements *************** // "<text:p>|<draw:frame text:anchor-type=\"as-char\"><draw:image><office:binary-data>data</office:binary-data></draw:image></draw:frame>|</text:p>", "<text:p><text:span>|<draw:frame text:anchor-type=\"as-char\"><draw:image><office:binary-data>data</office:binary-data></draw:image></draw:frame>|</text:span></text:p>", // *************** Annotations *************** // '<text:p>|a|b|<office:annotation><text:list><text:list-item><text:p>|</text:p></text:list-item></text:list></office:annotation>|c|d|<office:annotation-end></office:annotation-end>1|2|</text:p>', '<text:p>|a|<office:annotation><text:list><text:list-item><text:p>|b|</text:p></text:list-item></text:list></office:annotation>|c|<office:annotation-end></office:annotation-end>1|2|</text:p>', '<text:p>|a|<office:annotation><text:list><text:list-item><text:p>|b|</text:p></text:list-item></text:list></office:annotation>|<office:annotation-end></office:annotation-end>1|2|</text:p>', '<text:p>|<office:annotation><text:list><text:list-item><text:p>|</text:p></text:list-item></text:list></office:annotation><text:span>|</text:span></text:p>', '<text:p>|<office:annotation><text:list><text:list-item><text:p>|</text:p></text:list-item></text:list></office:annotation><e:editinfo></e:editinfo><text:span>|</text:span></text:p>', // *************** Annotations with highlighting *************** // '<text:p>|a|b|<office:annotation><text:list><text:list-item><text:p>|</text:p></text:list-item></text:list></office:annotation><text:span>|c|d|</text:span><office:annotation-end></office:annotation-end>1|2|</text:p>', '<text:p>|a|<html:div class="annotationWrapper"><office:annotation><text:list><text:list-item><text:p>|b|</text:p></text:list-item></text:list></office:annotation></html:div><html:span class="webodf-annotationHighlight">|c|</html:span><office:annotation-end></office:annotation-end>1|2|</text:p>', '<text:p>|a|<html:div class="annotationWrapper"><office:annotation><text:list><text:list-item><text:p>|b|</text:p></text:list-item></text:list></office:annotation></html:div><html:span class="webodf-annotationHighlight">|</html:span><office:annotation-end></office:annotation-end>1|2|</text:p>' ].map(function(testInput) { return { name: "cursorPositions " + testInput, f: testCursorPositions.bind(undefined, testInput) }; }); } function testMemberIds() { createOdtDocument('<text:p></text:p>'); t.odtDocument.addMember(new ops.Member('alice', /**@type{!ops.MemberProperties}*/({ fullName: 'Alice' }))); t.odtDocument.addMember(new ops.Member('bob', /**@type{!ops.MemberProperties}*/({ fullName: 'Bob' }))); t.odtDocument.addMember(new ops.Member('eve', /**@type{!ops.MemberProperties}*/({ fullName: 'Eve' }))); r.shouldBe(t, 't.odtDocument.getMemberIds().join()', '"alice,bob,eve"'); } this.setUp = function () { var doc, stylesElement; testarea = core.UnitTest.provideTestAreaDiv(); doc = testarea.ownerDocument; stylesElement = doc.createElement("style"); stylesElement.setAttribute("type", "text/css"); stylesElement.appendChild(doc.createTextNode("@namespace text url(urn:oasis:names:tc:opendocument:xmlns:text:1.0);\n")); stylesElement.appendChild(doc.createTextNode("text|p { display: block; }\n")); // Make text:p behave as normal paragraphs doc.getElementsByTagName("head")[0].appendChild(stylesElement); t = { doc: doc, stylesElement: stylesElement, styles: stylesElement.sheet }; }; this.tearDown = function () { core.UnitTest.cleanupTestAreaDiv(); t.stylesElement.parentNode.removeChild(t.stylesElement); t = {}; }; this.tests = function () { return r.name([ testFixCursorPositions_AnchorInInvalidPlace, testFixCursorPositions_Range_CursorInInvalidPlace, testFixCursorPositions_Range_AnchorAndCursorInInvalidPlace, testFixCursorPositions_Collapsed_CursorInInvalidPlace, testFixCursorPositions_Range_AnchorAndCursorInInvalidPlace_OverAnnotation, testFixCursorPositions_CursorAndAnchorNearParagraphStart, testFixCursorPositions_CursorNearParagraphStart_ForwardSelection, testFixCursorPositions_CursorNearParagraphStart_ReverseSelection, getTextNodeAtStep_BeginningOfTextNode, getTextNodeAtStep_EndOfTextNode, getTextNodeAtStep_PositionWithNoTextNode_AddsNewNode_Front, getTextNodeAtStep_PositionWithNoTextNode_AddsNewNode_Back, getTextNodeAtStep_At0_PutsTargetMemberCursor_AfterTextNode, getTextNodeAtStep_At1_PutsTargetMemberCursor_AfterTextNode, getTextNodeAtStep_At4_PutsTargetMemberCursor_AfterTextNode, getTextNodeAtStep_AfterNonText_PutsTargetMemberCursor_AfterTextNode, getTextNodeAtStep_RearrangesExistingCursors_MovesMemberAfterTextNode, getTextNodeAtStep_At0_PutsTargetMemberCursor_BeforeTextNode, getTextNodeAtStep_At1_PutsTargetMemberCursor_BeforeTextNode, getTextNodeAtStep_At4_PutsTargetMemberCursor_BeforeTextNode, getTextNodeAtStep_AfterNonText_PutsTargetMemberCursor_BeforeTextNode, getTextNodeAtStep_EmptyP_MovesAllCursors_BeforeTextNode, testMemberIds ]).concat(cursorPositionTests()); }; this.asyncTests = function () { return [ ]; }; }; ops.OdtDocumentTests.prototype.description = function () { "use strict"; return "Test the OdtDocument class."; };