//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package UnitTest.Tests { import UnitTest.ExtendedClasses.TestDescriptor; import UnitTest.ExtendedClasses.TestSuiteExtended; import UnitTest.ExtendedClasses.VellumTestCase; import UnitTest.Fixtures.TestConfig; import UnitTest.Fixtures.TestEditManager; import flash.display.Sprite; import flash.events.*; import flash.text.engine.FontPosture; import flash.text.engine.FontWeight; import flash.ui.Keyboard; import flash.utils.getDefinitionByName; import flash.utils.getQualifiedClassName; import flashx.textLayout.container.ContainerController; import flashx.textLayout.conversion.ITextImporter; import flashx.textLayout.conversion.TextConverter; import flashx.textLayout.edit.EditManager; import flashx.textLayout.elements.*; import flashx.textLayout.events.FlowOperationEvent; import flashx.textLayout.events.SelectionEvent; import flashx.textLayout.events.StatusChangeEvent; import flashx.textLayout.formats.*; import flashx.textLayout.operations.FlowOperation; import flashx.textLayout.operations.UndoOperation; import flashx.textLayout.tlf_internal; import flashx.undo.UndoManager; use namespace tlf_internal; public class GeneralFunctionsTest extends VellumTestCase { public var SelectionChanged:Boolean; private var callback:Boolean; public function GeneralFunctionsTest(methodName:String, testID:String, testConfig:TestConfig, testXML:XML = null) { super(methodName, testID, testConfig); // Note: These must correspond to a Watson product area (case-sensitive) metaData.productArea = "Editing"; } public static function suite(testConfig:TestConfig, ts:TestSuiteExtended):void { ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "arrowLeft", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "arrowRight", testConfig) ); //ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "arrowDown", testConfig) ); //ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "arrowUp", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "replaceSelectionTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "backSpaceSelectionTest", testConfig) ); // KJT ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "backSpaceInsertionPointTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "backSpaceLowerLimitTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "deleteSelectionTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "deleteInsertionPointTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "deleteUpperLimitTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "splitParagraphInsertionPointTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "splitParagraphSelectionTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "splitParagraphFormatTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "unDoOp", testConfig) ); // undo redo// KJT ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "setSelectionRangeTest", testConfig) );// select range and listen for change event // KJT ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "insertTextTest", testConfig) ); // KJT ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "joinedBackspaceUndoTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "joinedDeleteUndoTest", testConfig) ); /* Waiting for troubleshooting -*/ ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "joinedInsertUndoTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "joinedSplitParagraphUndoTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "undoLimitTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "transParagraphDeleteTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "emptyParagraphTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "splitUTF16Test", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "splitUTF32Test", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "undoWithModelChangeTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "doubleClickTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "doubleClickHyphenTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "doubleClickPunctuationTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "undoRedoStackTest", testConfig) ); if (testConfig.writingDirection[0] == BlockProgression.TB && testConfig.writingDirection[1] == Direction.LTR) { ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "replaceChildrenFlowTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "resolveInlinesTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "nestingTest", testConfig) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "errorThrowing", testConfig ) ); ts.addTestDescriptor (new TestDescriptor(GeneralFunctionsTest, "AllNestingTest", testConfig ) ); } } /** * selecting text from 10 to 20 */ public function selChange(e:Event):void //KJT selection change event method { SelectionChanged = true; } public function setSelectionRangeTest():void //KJT test selection range and change event { var startIndx:int = 800; var endIndx:int = 933; //trace('CALL SET SELECTION'); SelManager.textFlow.addEventListener(SelectionEvent.SELECTION_CHANGE, selChange, false, 0, true); SelManager.selectRange(startIndx,endIndx); assertTrue("beginning index should be 800, but is " + SelManager.anchorPosition, SelManager.anchorPosition == 800); assertTrue("end index should be 933, but is " + SelManager.activePosition, SelManager.activePosition == 933); assertTrue("SelectionChanged returned false", SelectionChanged == true); } /** * Select 40 characters and replaces them with new text */ public function replaceSelectionTest():void { var startIndx:int = 10; var endIndx:int = 50; var attributes:TextLayoutFormat = new TextLayoutFormat(); attributes.fontWeight = FontWeight.BOLD; attributes.lineThrough = true; SelManager.selectRange(startIndx,endIndx); SelManager.applyLeafFormat(attributes); SelManager.insertText("New"); SelManager.flushPendingOperations(); var leaf:FlowLeafElement = SelManager.textFlow.findLeaf(startIndx); var attributesNew:ITextLayoutFormat = leaf.format; assertTrue("Invalid point format after block deletion", TextLayoutFormat.isEqual(attributes, attributesNew)); } /** * Select 40 characters and delete them. */ public function backSpaceSelectionTest():void { var startIndx:int = 10; var endIndx:int = 50; var attributes:TextLayoutFormat = new TextLayoutFormat(); attributes.fontWeight = FontWeight.BOLD; attributes.lineThrough = true; SelManager.selectRange(startIndx,endIndx); SelManager.applyLeafFormat(attributes); var initLength:uint = SelManager.textFlow.textLength; SelManager.deletePreviousCharacter(); var endLength:uint = SelManager.textFlow.textLength; assertTrue("deleting the selection should have removed " +(endIndx - startIndx) + " characters. It actually removed " + (initLength - endLength), endLength == initLength - (endIndx - startIndx) ); SelManager.insertText("New"); SelManager.flushPendingOperations(); var leaf:FlowLeafElement = SelManager.textFlow.findLeaf(startIndx); var attributesNew:ITextLayoutFormat = leaf.format; assertTrue("Invalid point format after block deletion", TextLayoutFormat.isEqual(attributes, attributesNew)); } /** * Set the cursor position back ten characters and delete the first ten characters. */ public function backSpaceInsertionPointTest():void { var n:int = 10; SelManager.selectRange(n,n); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i < n; i++){ SelManager.deletePreviousCharacter(); } var endLength:uint = SelManager.textFlow.textLength; assertTrue("should have removed " + n + " characters. Actually removed " + (initLength - endLength) , endLength == initLength - n); } /** * Set the cursor position back nine characters and delete the first ten characters. * Ensure that only nine characters are deleted. */ public function backSpaceLowerLimitTest():void { var n:int = 9; SelManager.selectRange(n,n); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i <= n; i++){ SelManager.deletePreviousCharacter(); } var endLength:uint = SelManager.textFlow.textLength; assertTrue("should have removed " + n + " characters. Actually removed " + (initLength - endLength) , endLength == initLength - n); } /** * Select 40 characters and delete them. */ public function deleteSelectionTest():void { var startIndx:int = 10; var endIndx:int = 50; SelManager.selectRange(startIndx,endIndx); var initLength:uint = SelManager.textFlow.textLength; SelManager.deleteNextCharacter(); var endLength:uint = SelManager.textFlow.textLength; assertTrue("should have removed " + (endIndx - startIndx) + " characters. Actually removed " + (initLength - endLength) , endLength == initLength - (endIndx - startIndx) ); } /** * Delete the first ten characters. */ public function deleteInsertionPointTest():void { var n:int = 10; SelManager.selectRange(0,0); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i < n; i++){ SelManager.deleteNextCharacter(); } var endLength:uint = SelManager.textFlow.textLength; assertTrue("should have removed " + n + " characters. Actually removed " + (initLength - endLength), endLength == initLength - n); } /** * Set the cursor position back nine characters and delete the first ten characters. * Ensure that only nine characters are deleted. */ public function deleteUpperLimitTest():void { var n:int = 9; var initLength:uint = SelManager.textFlow.textLength; SelManager.selectRange(initLength - (n+1), initLength - (n+1)); for(var i:int = 0; i < n; i++){ SelManager.deleteNextCharacter(); } var endLength:uint = SelManager.textFlow.textLength; assertTrue("should have removed " + n + " characters. Actually removed " + (initLength - endLength) , endLength == initLength - n); } /** * Splits the paragraph after the first fifty characters. * Verifies that there is one more line in the textFlow than it started with. * Verifies that the first leaf has only 50 characters in it. */ public function splitParagraphInsertionPointTest():void { var insertPoint:int = 50; SelManager.selectRange(insertPoint, insertPoint); var initLines:int = SelManager.textFlow.flowComposer.numLines; SelManager.splitParagraph(); var endLines:int = SelManager.textFlow.flowComposer.numLines; assertTrue("expected one additional line after splitting the paragraph. Actually found " + (endLines - initLines), initLines = endLines - 1); var lengthFirst:int = SelManager.textFlow.getFirstLeaf().textLength; assertTrue("expected the first paragraph would have " + insertPoint + " characters. Actually found " + lengthFirst, lengthFirst = insertPoint); } /** * 1. Sets attributes before (A, B), after (B', C) and at (B'', D) at split point. * Splits paragraphs, adds new text and verifies that it has attributes (A, B'', C, D) * 2. Tests special case for splitting a paragraph at the end of a link; point format is reset in this case */ public function splitParagraphFormatTest():void { var insertPoint:int = 50; var attributes:TextLayoutFormat = new TextLayoutFormat(); attributes.fontWeight = FontWeight.BOLD; attributes.lineThrough = true; SelManager.selectRange(insertPoint-5, insertPoint); SelManager.applyLeafFormat(attributes); attributes.fontWeight = undefined; attributes.textDecoration = flashx.textLayout.formats.TextDecoration.UNDERLINE; attributes.fontSize = 72; SelManager.selectRange(insertPoint, insertPoint+5); SelManager.applyLeafFormat(attributes); attributes.textDecoration = flashx.textLayout.formats.TextDecoration.UNDERLINE; attributes.lineThrough = true; attributes.fontFamily = "Verdana"; SelManager.selectRange(insertPoint, insertPoint); SelManager.applyLeafFormat(attributes); SelManager.splitParagraph(); SelManager.insertText("New"); SelManager.flushPendingOperations(); var leaf:FlowLeafElement = SelManager.textFlow.findLeaf(insertPoint+1); attributes = new TextLayoutFormat(leaf.format); assertTrue("Invalid point format for start of a new paragraph", attributes.fontWeight = FontWeight.BOLD); assertTrue("Invalid point format for start of a new paragraph", attributes.fontSize = 72); assertTrue("Invalid point format for start of a new paragraph", attributes.fontFamily = "Verdana"); assertTrue("Invalid point format for start of a new paragraph", attributes.textDecoration == flashx.textLayout.formats.TextDecoration.UNDERLINE && attributes.lineThrough); insertPoint = 40; SelManager.selectRange(insertPoint-10, insertPoint); SelManager.applyLink("http://www.google.com", "_self", false); SelManager.selectRange(insertPoint, insertPoint); SelManager.splitParagraph(); SelManager.insertText("New"); SelManager.flushPendingOperations(); leaf = SelManager.textFlow.findLeaf(insertPoint+1); attributes = new TextLayoutFormat(leaf.format); assertTrue("Invalid point format for start of a new paragraph after a link", TextLayoutFormat.isEqual(attributes, TextLayoutFormat.emptyTextLayoutFormat)); } /** * Selects the characters between the 10th and 50th characters and splits the paragraph. * Verifies that there is one more line in the textFlow than it started with. * Verifies that the first leaf has only 10 characters in it. * Verifies that the final length has 39 less characters in it. */ public function splitParagraphSelectionTest():void { var startIndx:int = 10; var endIndx:int = 50; SelManager.selectRange(startIndx, endIndx); var initLength:int = SelManager.textFlow.textLength; var initLines:int = SelManager.textFlow.flowComposer.numLines; SelManager.splitParagraph(); var endLength:int = SelManager.textFlow.textLength; var endLines:int = SelManager.textFlow.flowComposer.numLines; assertTrue("expected one additional line after splitting the paragraph. Actually found " + (endLines - initLines), initLines = endLines - 1); var lengthFirst:int = SelManager.textFlow.getFirstLeaf().textLength; assertTrue("expected the first paragraph would have " + startIndx + " characters. Actually found " + lengthFirst, lengthFirst = startIndx); assertTrue("expected the entire flow would have " + (initLength - (endIndx - startIndx) + 1) + " characters. Actually found " + endLength, endLength == initLength - (endIndx - startIndx) + 1); } /** * Creates a selection area of six characters at the last index of the flow root * then inserts " BOOGA" into the selection area and verifies the length of the * flow root has increased by 6. */ public function insertTextTest():void //KJT { var textLength:uint = SelManager.textFlow.textLength; SelManager.selectRange(textLength - 1, textLength + 5); SelManager.insertText(" BOOGA"); SelManager.selectRange(textLength - 1,textLength + 5); assertTrue("expected to find " + (textLength + 6) + " characters. Actually found " + SelManager.textFlow.textLength, SelManager.textFlow.textLength == textLength + 6); } /** * Sets the insertion point at 50, then generates the "0" keyboard event * and verifies that the selBegIdx is 49. */ public function arrowLeft():void { var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, Keyboard.LEFT); SelManager.selectRange(50,50); SelManager.keyDownHandler(event); if(SelManager.textFlow.computedFormat.blockProgression == BlockProgression.RL) { // assertTrue("expected insertion point at position 87. Actually found + " + // SelManager.anchorPosition, SelManager.anchorPosition == 87); } else { if(SelManager.textFlow.computedFormat.direction == Direction.LTR) { assertTrue("expected insertion point at position 49. Actually found + " + SelManager.activePosition, SelManager.activePosition == 49); } else { assertTrue("expected insertion point at position 51. Actually found + " + SelManager.activePosition, SelManager.activePosition == 51); } } } /** * Sets the insertion point at 50, then generates the "0" keyboard event * and verifies that the selBegIdx is 51. */ public function arrowRight():void { var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, Keyboard.RIGHT); SelManager.selectRange(50,50); SelManager.keyDownHandler(event); if(SelManager.textFlow.computedFormat.blockProgression == BlockProgression.RL) { // assertTrue("expected insertion point at position 11. Actually found + " + // SelManager.anchorPosition, SelManager.anchorPosition == 11); } else { if(SelManager.textFlow.computedFormat.direction == Direction.LTR) { assertTrue("expected insertion point at position 51. Actually found " + SelManager.activePosition, SelManager.activePosition == 51); } else { assertTrue("expected insertion point at position 49. Actually found " + SelManager.activePosition, SelManager.activePosition == 49); } } } /** * Sets the insertion point at 0, then generates the "40" keyboard event * and verifies that the cursor is on the second line. */ public function arrowDown():void { var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_UP, true, false,40); SelManager.selectRange(0,0); SelManager.keyDownHandler(event); if(SelManager.textFlow.computedFormat.blockProgression == BlockProgression.TB) { //NYI } else { var ap:int = SelManager.activePosition; var index:int = SelManager.textFlow.findChildIndexAtPosition(ap); assertTrue("expected index at 2. Actually found " + index, index == 2); } } /** * Sets the insertion point at the last char, then generates the "38" keyboard event * and verifies that the cursor is on the second to last line. */ public function arrowUp():void { var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_UP, true, false,38); var lastChar:int = SelManager.absoluteEnd; var lastLine:int = SelManager.textFlow.findChildIndexAtPosition(lastChar); SelManager.selectRange(lastChar,lastChar); SelManager.keyDownHandler(event); if(SelManager.textFlow.computedFormat.blockProgression == BlockProgression.TB) { //NYI } else { var index:int = SelManager.textFlow.findChildIndexAtPosition(SelManager.activePosition); assertTrue("expected index at " + (lastChar - 1) + ". Actually found " + index, index == (lastChar - 1) ); } } /** * Tests the undo and redo stacks by applying typeface changes by undoing * and redoing them. */ public function unDoOp():void //KJT { var ca:TextLayoutFormat = new TextLayoutFormat(); ca.fontWeight = FontWeight.BOLD; SelManager.applyLeafFormat(ca); SelManager.undo(); Redo(); ca.fontStyle = FontPosture.ITALIC; SelManager.applyLeafFormat(ca); SelManager.undo(); SelManager.undo(); } /** * Performs successive backspaces and one undo to see if all the deleted content * is returned, then performs a redo to see if the content is correctly removed * again. */ public function joinedBackspaceUndoTest():void { var n:int = 10; SelManager.selectRange(10,10); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i < n; i++){ var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 8); dispatchEvent(event); } var endLength:uint = SelManager.textFlow.textLength; SelManager.undo(); var undoLength:uint = SelManager.textFlow.textLength; assertTrue("expected text length before undo to be " + n + " less. Was actually " + (endLength - initLength) + " less.", endLength == initLength - n ); assertTrue("expected text length after undo to be identical to the start, but it was off by " + (undoLength-initLength), undoLength == initLength); Redo(); var redoLength:uint = SelManager.textFlow.textLength; assertTrue("expected redo length to equal undo length, but found " + redoLength + " and " + endLength, redoLength == endLength); } /** * Performs successive deletes and one undo to see if all the deleted content * is returned, then performs a redo to see if the content is correctly removed * again. */ public function joinedDeleteUndoTest():void { var n:int = 10; SelManager.selectRange(10,10); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i < n; i++){ var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 127, Keyboard.DELETE); dispatchEvent(event); } SelManager.flushPendingOperations(); var endLength:uint = SelManager.textFlow.textLength; SelManager.undo(); var undoLength:uint = SelManager.textFlow.textLength; assertTrue("expected text length before undo to be " + n + " less. Was actually " + (endLength - initLength) + " less.", endLength == initLength - n ); assertTrue("expected text length after undo to be identical to the start, but it was off by " + (undoLength-initLength), undoLength == initLength); Redo(); var redoLength:uint = SelManager.textFlow.textLength; assertTrue("expected redo length to equal undo length, but found " + redoLength + " and " + endLength, redoLength == endLength); } /** * Places a series of letter a's in and does one undo to see if all the a's * are removed, then performs a redo to see if they're returned. */ public function joinedInsertUndoTest():void { var n:int = 10; SelManager.selectRange(10,10); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i < n; i++){ var event:TextEvent = new TextEvent(TextEvent.TEXT_INPUT,false,false,"a"); dispatchEvent(event); } SelManager.flushPendingOperations(); var endLength:uint = SelManager.textFlow.textLength; SelManager.undo(); var undoLength:uint = SelManager.textFlow.textLength; assertTrue("expected text length before undo to be " + n + " more. Was actually " + (endLength - initLength) + " less.", endLength == initLength + n ); assertTrue("expected text length after undo to be identical to the start, but it was off by " + (undoLength-initLength), undoLength == initLength); Redo(); var redoLength:uint = SelManager.textFlow.textLength; assertTrue("expected redo length to equal undo length, but found " + redoLength + " and " + endLength, redoLength == endLength); } /** * Performs successive enters and one undo to see if all paragraph splits are * undone, then performs a redo to see if they're all redone. */ public function joinedSplitParagraphUndoTest():void { var n:int = 10; SelManager.selectRange(10,10); var initLength:uint = SelManager.textFlow.textLength; for(var i:int = 0; i < n; i++){ var event:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 13); dispatchEvent(event); } var endLength:uint = SelManager.textFlow.textLength; SelManager.undo(); var undoLength:uint = SelManager.textFlow.textLength; assertTrue("expected text length before undo to be " + n + " more. Was actually " + (endLength - initLength) + " less.", endLength == initLength + n ); assertTrue("expected text length after undo to be identical to the start, but it was off by " + (undoLength-initLength), undoLength == initLength); Redo(); var redoLength:uint = SelManager.textFlow.textLength; assertTrue("expected redo length to equal undo length, but found " + redoLength + " and " + endLength, redoLength == endLength); } /** * Create a new undo manager with an undo limit of ten, then perform eleven * undoable operations and see how many times it will let you undo. */ public function undoLimitTest():void { var undo:UndoManager = new UndoManager(); var n:int = 10; undo.undoAndRedoItemLimit = n; var newSM:EditManager = new EditManager(undo); SelManager = newSM; TestFrame.rootElement.getTextFlow().interactionManager = newSM; //SelManager.selectRange(n,n); for(var i:int = 0; i < n + 1; i++){ SelManager.selectRange(n,n+i); var event:KeyboardEvent = (i % 2 == 0) ? new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 13): new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 8); dispatchEvent(event); SelManager.selectRange(-1,-1); } var u:int = 0; while(undo.canUndo()){ SelManager.undo(); u++; } assertTrue("expected only " + n + " undos but found " + u, u == n); } public function undoRedoStackTest():void { var undo:UndoManager = new UndoManager(); var n:int = 10; undo.undoAndRedoItemLimit = n; var newSM:EditManager = new EditManager(undo); SelManager = newSM; TestFrame.rootElement.getTextFlow().interactionManager = newSM; //do n operations for(var i:int = 0; i < n + 1; i++){ SelManager.selectRange(n,n+i); var event:KeyboardEvent = (i % 2 == 0) ? new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 13): new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 8); dispatchEvent(event); SelManager.selectRange(-1,-1); } var testManager:TestEditManager = new TestEditManager(undo); var stackItemNum:int = testManager.UndoRedoEntireStack(undo); assertTrue("expected only " + n + " undos but found " + stackItemNum, stackItemNum == n); } /** * Select a section of text that spans paragraphs then delete it and see if the * size of the flow goes down by the appropriate amount. */ public function transParagraphDeleteTest():void { var startLength:int = TestFrame.rootElement.textLength; var flow1:FlowElement; var flow2:FlowElement; var start:int = 0; var finish:int = 0; var origParas:int = TestFrame.rootElement.numChildren; //Look for two back to back paragraphs. for(var i:int = 0; i < TestFrame.rootElement.numChildren-1; i++){ flow1 = TestFrame.rootElement.getChildAt(i); flow2 = TestFrame.rootElement.getChildAt(i+1); if(flow1 is ParagraphElement && flow2 is ParagraphElement){ finish = start + flow1.textLength + flow2.textLength/2; start = start + flow1.textLength/2; break; }else{ start = start + flow1.textLength; } } assertTrue("expected finish to be in the middle of the second para, but it's still 0", finish != 0); var para1:ParagraphElement = flow1 as ParagraphElement; var para2:ParagraphElement = flow2 as ParagraphElement; var attrib1:ITextLayoutFormat = para1.computedFormat; var pa:TextLayoutFormat = new TextLayoutFormat(); if(attrib1.textAlign == TextAlign.CENTER) pa.textAlign = TextAlign.LEFT; else pa.textAlign = TextAlign.CENTER; SelManager.selectRange(para2.getAbsoluteStart(),para2.getAbsoluteStart() + 1); SelManager.applyParagraphFormat(pa); var attrib2:ITextLayoutFormat = para2.computedFormat; assertTrue("para1 and para2 paraAttrs must be different for this test to work!", !TextLayoutFormat.isEqual(attrib1,attrib2) ); SelManager.selectRange(start,finish); SelManager.deleteNextCharacter(); var finishLength:int = TestFrame.rootElement.textLength; assertTrue("expected " + (finish-start) + " less characters but found " + (startLength - finishLength) + " less", startLength - finishLength == finish - start); /* * TODO: this tests current behavior. Proper behavior would be only one * remaining paragraph. */ assertTrue("number of paragraphs should have decreased!", origParas > TestFrame.rootElement.numChildren); var finalPara:ParagraphElement = TestFrame.rootElement.getChildAt(i) as ParagraphElement; var finalAttrs:ITextLayoutFormat = finalPara.computedFormat; assertTrue("paragraph attributes changed and should be the same!",TextLayoutFormat.isEqual(attrib1,finalAttrs)); } /** * Create an empty paragraph and select the 0 position in it. * Then delete all the content on the flow and see if the position selected is 0. */ public function emptyParagraphTest():void { SelManager.selectRange(0,0); var returnevent:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 13); dispatchEvent(returnevent); SelManager.selectRange(0,0); assertTrue("did not start with selection at 0,0", SelManager.anchorPosition == 0 && SelManager.activePosition == 0); SelManager.selectAll(); SelManager.deleteNextCharacter(); assertTrue("did not end with selection at 0,0 after delete all", SelManager.anchorPosition == 0 && SelManager.activePosition == 0); } public function splitUTF16Test():void { // remove all text SelManager.selectAll(); SelManager.deleteNextCharacter(); var a:uint = 0xD866; var b:uint = 0xDDC1; var c:uint = 0xD869; var d:uint = 0xDED6; var e:uint = 0xD8BF; var f:uint = 0xDFFD; var y:String = String.fromCharCode(a,b,c,d,e,f); SelManager.insertText(y); SelManager.flushPendingOperations(); var begin:int = 0; var end:int = SelManager.textFlow.textLength; var middle:int = ((end-1) - begin)/2; SelManager.selectRange(middle, middle); var gotRangeError:Boolean = false; try { SelManager.splitParagraph(); SelManager.flushPendingOperations(); } catch ( e:RangeError ) { gotRangeError = true; } /* See bug #1798067 */ assertTrue( "Spliting a surrogate pair did not throw an error", gotRangeError ); SelManager.selectAll(); var charAttr:TextLayoutFormat = new TextLayoutFormat(); charAttr.fontFamily = "Adobe Song Std L"; SelManager.applyLeafFormat(charAttr); SelManager.flushPendingOperations(); //trace(begin + ", " + middle + ", " + end); } public function splitUTF32Test():void { // remove all text SelManager.selectAll(); SelManager.deleteNextCharacter(); var y:String = String.fromCharCode(0x00028023); y = y + y + y; SelManager.insertText(y); SelManager.flushPendingOperations(); var begin:int = 0; var end:int = SelManager.textFlow.textLength; var middle:int = ((end-1) - begin)/2; SelManager.selectRange(middle, middle); SelManager.splitParagraph(); SelManager.flushPendingOperations(); SelManager.selectAll(); var charAttr:TextLayoutFormat = new TextLayoutFormat(); charAttr.fontFamily = "Adobe Song Std L"; SelManager.applyLeafFormat(charAttr); SelManager.flushPendingOperations(); //trace(begin + ", " + middle + ", " + end); } // pick up the actual operation private var flowOp:FlowOperation; private function listenForOpBegin(opEvent:FlowOperationEvent):void { flowOp = opEvent.operation; } /** * Performs an operation, clear the selection manager and then make sure the operation doesn't undo */ public function undoWithModelChangeTest():void { var tf:TextFlow = SelManager.textFlow; var selManager:EditManager = SelManager; selManager.selectAll(); var initLength:uint = SelManager.textFlow.textLength; flowOp = null; tf.addEventListener(FlowOperationEvent.FLOW_OPERATION_BEGIN,listenForOpBegin,false,0,true); // now do an operation var ca:TextLayoutFormat = new TextLayoutFormat(); ca.color = 0xff; selManager.applyFormatToElement(tf, ca); assertTrue("no begin operation event",flowOp != null); assertTrue("textFlow characterFormat.color should be 0xff",tf.format && tf.format.color == 0xff); // clear the selection manager tf.interactionManager = null; // now I've got a hold of the operation. do an out of bounds model modification and then attempt an undo tf.color = 0xff00; assertTrue("textFlow characterFormat.color should be 0xff",tf.format.color == 0xff00); // now restore the interactionManager tf.interactionManager = selManager; // CONFIG::debug { trace("GeneralFunctionsTest.undoWithModelChangeTest: expect to see message regarding skipping undo due to mismatched generation numbers."); } // try to do an undo selManager.undo(); // it should be skipped assertTrue("no begin operation event",flowOp != null); assertTrue("wrong begin operation event",flowOp is UndoOperation); assertTrue("textFlow characterFormat.color should be 0xff00",tf.format && tf.format.color == 0xff00); selManager.selectRange(0,0); // prevent assert in tearDown } private function Redo():void // KJT { try { SelManager.redo(); } catch(e:Error){ fail("Redo operation failed."); } } private function dispatchEvent(event:Event):void { // assume containers support dispatchEvent. Otherwise we get an error TestFrame.container["dispatchEvent"](event); } public function doubleClickTest():void { SelManager.selectRange( 68, 68 ); var mEvent:MouseEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK ); TestFrame.container["dispatchEvent"](mEvent); assertTrue( "Double click failed to correctly select word", SelManager.anchorPosition == 65 && SelManager.activePosition == 72 ); SelManager.selectRange( 955, 955 ); mEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK ); TestFrame.container["dispatchEvent"](mEvent); assertTrue( "Double click failed to correctly select word", SelManager.anchorPosition == 954 && SelManager.activePosition == 957 ); } public function doubleClickHyphenTest():void { SelManager.selectRange( 24, 24 ); var mEvent:MouseEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK ); TestFrame.container["dispatchEvent"](mEvent); assertTrue( "Double click failed on a hyphen", SelManager.anchorPosition == 24 && SelManager.activePosition == 25 ); SelManager.selectRange( 498, 498 ); mEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK ); TestFrame.container["dispatchEvent"](mEvent); assertTrue( "Double click failed on a hyphenated word", SelManager.anchorPosition == 496 && SelManager.activePosition == 502 ); } public function doubleClickPunctuationTest():void { SelManager.selectRange( 951, 951 ); var mEvent:MouseEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK ); TestFrame.container["dispatchEvent"](mEvent); assertTrue( "Double click failed on a word next to punctuation", SelManager.anchorPosition == 945 && SelManager.activePosition == 952 ); SelManager.selectRange( 1964, 1964 ); mEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK ); TestFrame.container["dispatchEvent"](mEvent); assertTrue( "Double click failed on punctuation", SelManager.anchorPosition == 1963 && SelManager.activePosition == 1966 ); } public function replaceChildrenFlowTest():void { var caught:Boolean = false; try { SelManager.textFlow.replaceChildren( 0,0, new TextFlow() ); } catch ( err:ArgumentError ) { caught = true; } assertTrue( "TextFlow.replaceChildren with a TextFlow argument should throw an error", caught ); } private function resolveInlines(ilg:InlineGraphicElement):Object { return ilg.source == "placeholder" ? new Sprite() : ilg.source; } private var _ilgStatus:String; private function graphicStatusChangeEvent(e:StatusChangeEvent):void { _ilgStatus = e.status; } public function resolveInlinesTest():void { var textFlow:TextFlow; const markup:String = ""; var config:Configuration = new Configuration(); config.inlineGraphicResolverFunction = resolveInlines; textFlow = TextConverter.importToFlow(markup, TextConverter.TEXT_LAYOUT_FORMAT, config); textFlow.addEventListener(StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE,graphicStatusChangeEvent); textFlow.flowComposer.addController(new ContainerController(new Sprite(), 400, 200)); textFlow.flowComposer.updateAllControllers(); assertTrue( "Inline Graphic resolution failed", _ilgStatus == InlineGraphicElementStatus.READY ); // } public function nestingTest():void { var link1:LinkElement = new LinkElement(); var tcy1:TCYElement = new TCYElement(); var link2:LinkElement = new LinkElement(); var exceptionThrown:Boolean = false; try { link1.addChild(link2); } catch (e:*) { exceptionThrown = true; } assertTrue("Should not be able to add a link child to a link", exceptionThrown); exceptionThrown = false; link1.addChild(tcy1); try { tcy1.addChild(link2); } catch (e:*) { exceptionThrown = true; } assertTrue("Should not be able to add a link child to a TCY if the latter is contained in a link", exceptionThrown); exceptionThrown = false; link1.removeChild (tcy1); tcy1.addChild(link2); try { link1.addChild(tcy1); } catch (e:*) { exceptionThrown = true; } assertTrue("Should not be able to add a TCY child to a link if the former contains a link", exceptionThrown); } private static const customResourceDict:Object = { invalidFlowElementConstruct_custom: "Attempted construct of invalid FlowElement subclass", badMXMLChildrenArgument_custom: "Bad element of type {0} passed to mxmlChildren", malformedTag: "Malformed tag

", XMLParserFailure: "TypeError: Error #1090: XML parser failure: element is malformed." } tlf_internal static function customResourceStringFunction(resourceName:String, parameters:Array = null):String { var value:String = String(customResourceDict[resourceName]); if (value == null) { value = String(customResourceDict["missingStringResource"]); parameters = [ resourceName ]; } if (parameters) value = customSubstitute(value, parameters); return value; } /** @private */ tlf_internal static function customSubstitute(str:String, ... rest):String { if (str == null) return ''; // Replace all of the parameters in the msg string. var len:uint = rest.length; var args:Array; if (len == 1 && rest[0] is Array) { args = rest[0] as Array; len = args.length; } else { args = rest; } for (var i:int = 0; i < len; i++) { str = str.replace(new RegExp("\\{"+i+"\\}", "g"), args[i]); } return str; } /** Generate errors and make sure we get the right string */ public function errorThrowing():void { var exceptionThrown:Boolean; //use defaultResourceStringFunction to check the error message // Invalid flowElement exceptionThrown = false; try { new FlowGroupElement(); } catch (e:Error) { exceptionThrown = true; var invalidFlowElementConstruct_message:String = e.message; assertTrue("errorThrowing: Error thrown but message is incorrect",e.message == GlobalSettings.resourceStringFunction("invalidFlowElementConstruct")); } assertTrue("errorThrowing: Expected error on new FlowGroupElement did not occur", exceptionThrown); // invalid mxmlchildren assignment exceptionThrown = false; try { var s:Sprite = new Sprite(); new DivElement().mxmlChildren = [ s ]; } catch (e:Error) { exceptionThrown = true; var badMXMLChildrenArgument_message:String = e.message; assertTrue("errorThrowing: Error thrown but message is incorrect",e.message == GlobalSettings.resourceStringFunction("badMXMLChildrenArgument",[ getQualifiedClassName(s) ])); } assertTrue("errorThrowing: Expected error on bad MXMLChildren argument did not occur", exceptionThrown); //use customResourceStringFunction to check the error message try { GlobalSettings.resourceStringFunction= customResourceStringFunction; //check Invalid flowElement and invalid mxmlchildren assignment assertTrue("errorThrowing: Error thrown but message is incorrect", GlobalSettings.resourceStringFunction("invalidFlowElementConstruct_custom") == invalidFlowElementConstruct_message); assertTrue("errorThrowing: Error thrown but message is incorrect", GlobalSettings.resourceStringFunction("badMXMLChildrenArgument_custom",[ getQualifiedClassName(s) ]) == badMXMLChildrenArgument_message); //use custom resource string function to test HTML importer error exceptionThrown = false; var textImporter:ITextImporter = TextConverter.getImporter(TextConverter.TEXT_FIELD_HTML_FORMAT); var textFlow:TextFlow; var markup:String = '

Malformed tag next

'; textFlow = textImporter.importToFlow(markup); if (textImporter.errors) { exceptionThrown = true; assertTrue("errorThrowing: Error thrown but message is incorrect",textImporter.errors == GlobalSettings.resourceStringFunction("malformedTag")); } assertTrue("errorThrowing: Expected error on importer did not occur", exceptionThrown); //use custom resource string function to test Text importer error exceptionThrown = false; markup = "Ethan Brand

"; textImporter = TextConverter.getImporter(TextConverter.TEXT_LAYOUT_FORMAT); textFlow = textImporter.importToFlow(markup); if (textImporter.errors) { exceptionThrown = true; assertTrue("errorThrowing: Error thrown but message is incorrect",textImporter.errors == GlobalSettings.resourceStringFunction("XMLParserFailure")); } assertTrue("errorThrowing: Expected error on XML Parser Failure did not occur", exceptionThrown); } finally { GlobalSettings.resourceStringFunction = GlobalSettings.defaultResourceStringFunction; } } public static const childParentTable:Array = [ [ "child\parent", TextFlow, DivElement, ParagraphElement, LinkElement, TCYElement, SpanElement, InlineGraphicElement, BreakElement, TabElement, ListElement, ListItemElement, SubParagraphGroupElement ], [ TextFlow, "no", "no", "no", "no", "no", "no", "no", "no", "no", "no", "no", "no" ], [ DivElement, "yes", "yes", "no", "no", "no", "no", "no", "no", "no", "yes", "yes", "no" ], [ ParagraphElement, "yes", "yes", "no", "no", "no", "no", "no", "no", "no", "yes", "yes", "no" ], [ LinkElement, "no", "no", "yes", "no", "yes", "no", "no", "no", "no", "no", "no", "yes" ], [ TCYElement, "no", "no", "yes", "yes", "no", "no", "no", "no", "no", "no", "no", "yes" ], [ SpanElement, "no", "no", "yes", "yes", "yes", "no", "no", "no", "no", "no", "no", "yes" ], [ InlineGraphicElement, "no", "no", "yes", "yes", "yes", "no", "no", "no", "no", "no", "no", "yes" ], [ BreakElement, "no", "no", "yes", "yes", "yes", "no", "no", "no", "no", "no", "no", "yes" ], [ TabElement, "no", "no", "yes", "yes", "yes", "no", "no", "no", "no", "no", "no", "yes" ], [ ListElement, "yes", "yes", "no", "no", "no", "no", "no", "no", "no", "yes", "yes", "no" ], [ ListItemElement, "no", "no", "no", "no", "no", "no", "no", "no", "no", "yes", "no", "no" ], [ SubParagraphGroupElement, "no", "no", "yes", "yes", "yes", "no", "no", "no", "no", "no", "no", "yes" ] ]; /** Using above table for permitted child/parent relationships verify that canOwnFlowElement matches the table. */ public function AllNestingTest():void { var parentRow:Array = childParentTable[0].slice(1); var childRow:Array; var childClassName:String; var childClass:Class; for each (childRow in childParentTable.slice(1)) { childClassName = "flashx.textLayout.elements." + childRow[0]; childClass = childRow[0] as Class; var childElement:FlowElement = new childClass as FlowElement; assertTrue(childElement.defaultTypeName != null,"Bad defaultTypeName in " + childClassName); childRow = childRow.slice(1); for (var idx:int = 0; idx < childRow.length; idx++) { var parentClass:Class = parentRow[idx] as Class var parentElement:FlowElement = new parentClass as FlowElement; // trace(parentClass.toString(),childClass.toString(),childRow[idx]); if (parentElement is FlowGroupElement) { if (FlowGroupElement(parentElement).canOwnFlowElement(childElement)) assertTrue("Bad canOwnFlowElement value for expected allowed "+parentClass.toString()+" "+childClass.toString(),childRow[idx] == "yes"); else assertTrue("Bad canOwnFlowElement value for expected not allowed "+parentClass.toString()+" "+childClass.toString(), childRow[idx] == "no"); } else assertTrue("Bad canOwnFlowElement value for a non FlowGroupElement parent "+parentClass.toString()+" "+childClass.toString(),childRow[idx] == "no"); } } // Check for indirect nesting of SubParagraphGroupElements var textFlow:TextFlow = new TextFlow(); var paragraph:ParagraphElement = new ParagraphElement(); textFlow.replaceChildren(0, 0, paragraph); var parentGroup:SubParagraphGroupElement = new SubParagraphGroupElement(); paragraph.replaceChildren(0, 0, parentGroup); for each (childRow in childParentTable.slice(1)) { childClassName = "flashx.textLayout.elements." + childRow[0]; childClass = childRow[0] as Class; var spgeChildElement:SubParagraphGroupElementBase = new childClass as SubParagraphGroupElementBase; if (spgeChildElement) { var groupElement:SubParagraphGroupElement = new SubParagraphGroupElement(); groupElement.replaceChildren(0, 0, new SubParagraphGroupElement()); groupElement.replaceChildren(0, 0, spgeChildElement); assertTrue (parentGroup.canOwnFlowElement(groupElement), "Expected this nesting to work"); if (!spgeChildElement.allowNesting) { var nestingParent:SubParagraphGroupElementBase = new childClass as SubParagraphGroupElementBase; parentGroup.replaceChildren(parentGroup.numChildren, parentGroup.numChildren, nestingParent); var nestingGroup:SubParagraphGroupElement = new SubParagraphGroupElement(); nestingParent.replaceChildren(nestingParent.numChildren, nestingParent.numChildren, nestingGroup); assertTrue("Expected this nesting to fail because of nesting of Link or TCY", !nestingGroup.canOwnFlowElement(groupElement)); } } } } } }