You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by pi...@apache.org on 2014/06/02 23:15:26 UTC

[2/3] Migrate CompositionTest class to FU 4 Remove some unused imports

http://git-wip-us.apache.org/repos/asf/flex-tlf/blob/0d5ac0c1/automation_tests/src/UnitTest/Tests/CompositionTest.as
----------------------------------------------------------------------
diff --git a/automation_tests/src/UnitTest/Tests/CompositionTest.as b/automation_tests/src/UnitTest/Tests/CompositionTest.as
index 5ee155a..fdda766 100644
--- a/automation_tests/src/UnitTest/Tests/CompositionTest.as
+++ b/automation_tests/src/UnitTest/Tests/CompositionTest.as
@@ -18,888 +18,975 @@
 ////////////////////////////////////////////////////////////////////////////////
 package UnitTest.Tests
 {
-	import UnitTest.ExtendedClasses.TestDescriptor;
-	import UnitTest.ExtendedClasses.TestSuiteExtended;
-	import UnitTest.ExtendedClasses.VellumTestCase;
-	import UnitTest.Fixtures.TestConfig;
-	
-	import flash.display.DisplayObjectContainer;
-	import flash.geom.Rectangle;
-	import flash.text.engine.TextLine;
-	import flash.text.engine.TextLineValidity;
-	
-	import flashx.textLayout.*;
-	import flashx.textLayout.compose.IFlowComposer;
-	import flashx.textLayout.compose.StandardFlowComposer;
-	import flashx.textLayout.container.ContainerController;
-	import flashx.textLayout.edit.TextScrap;
-	import flashx.textLayout.elements.*;
-	import flashx.textLayout.events.CompositionCompleteEvent;
-	import flashx.textLayout.factory.StringTextLineFactory;
-	import flashx.textLayout.factory.TruncationOptions;
-	import flashx.textLayout.formats.BlockProgression;
-	import flashx.textLayout.formats.LineBreak;
-	import flashx.textLayout.formats.TextAlign;
-	import flashx.textLayout.formats.TextLayoutFormat;
-	import flashx.textLayout.formats.VerticalAlign;
+    import UnitTest.ExtendedClasses.TestDescriptor;
+    import UnitTest.ExtendedClasses.TestSuiteExtended;
+    import UnitTest.ExtendedClasses.VellumTestCase;
+    import UnitTest.Fixtures.TestConfig;
+
+    import flash.display.Sprite;
+    import flash.geom.Rectangle;
+    import flash.text.engine.TextLine;
+    import flash.text.engine.TextLineValidity;
+
+    import flashx.textLayout.*;
+    import flashx.textLayout.compose.IFlowComposer;
+    import flashx.textLayout.compose.StandardFlowComposer;
+    import flashx.textLayout.compose.TextFlowLine;
+    import flashx.textLayout.container.ContainerController;
+    import flashx.textLayout.edit.EditManager;
+    import flashx.textLayout.edit.TextScrap;
+    import flashx.textLayout.elements.*;
+    import flashx.textLayout.events.CompositionCompleteEvent;
+    import flashx.textLayout.factory.StringTextLineFactory;
+    import flashx.textLayout.factory.TruncationOptions;
+    import flashx.textLayout.formats.BlockProgression;
+    import flashx.textLayout.formats.LineBreak;
+    import flashx.textLayout.formats.TextAlign;
+    import flashx.textLayout.formats.TextLayoutFormat;
+    import flashx.textLayout.formats.VerticalAlign;
+
+    import mx.core.Container;
+    import mx.utils.UIDUtil;
 
     import org.flexunit.asserts.assertTrue;
     import org.flexunit.asserts.fail;
 
     use namespace tlf_internal;
 
-	import mx.utils.UIDUtil;
-	import flashx.textLayout.edit.EditManager;
-
-	import flashx.textLayout.factory.StringTextLineFactory;
-	import flashx.textLayout.compose.TextFlowLine;
-	import mx.core.Container;
-	import flash.display.Sprite;
-	import flashx.textLayout.formats.ITextLayoutFormat;
-
- 	public class CompositionTest extends VellumTestCase
-	{
-
-		public function CompositionTest(methodName:String, testID:String, testConfig:TestConfig, testXML:XML = null)
-		{
-			super (methodName, testID, testConfig);
-			if (methodName != "resizeController2644361")
-				TestData.fileName = "asknot.xml";
-			else
-				addDefaultTestSettings = false;
-
-			// Note: These must correspond to a Watson product area (case-sensitive)
-			metaData.productArea = "Text Composition";
-		}
-
-		public static function suite(testConfig:TestConfig, ts:TestSuiteExtended):void
-		{
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "checkParagraphShufflingTest", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "partialCompositionTest", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "releasedLineTest", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "composeOneScreen", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "truncationTest", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "CompositionCompleteEventTest", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "scrolledRedrawPartialCompose", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "multipleContainersWithPadding", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "deleteAtContainerStart", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "resizeController2644361", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "resizeEmptyController", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "emptyController", testConfig ) );
-			ts.addTestDescriptor( new TestDescriptor ( CompositionTest, "contentBoundsOnComposeFromMiddle", testConfig ) );
-		}
-
-		/**
-		 * First, find two back to back paragraphs. Second, record the first line of the
-		 * second paragraph; if the first paragraph is changed and the second gets recomposed
-		 * (i.e. what we don't want) this line will be re-created (also, the first line of
-		 * the second paragraph is the easiest to find). Third, make an insertion
-		 * point at the end of the first paragraph. Fourth, place a bunch of text at the end
-		 * of the paragraph to force it to recompose. Finally, find the first line in the
-		 * second paragraph again and see if it is the same as the line you recorded in step
-		 * (using "===").
-		 */
-		public function checkParagraphShufflingTest():void
-		{
-			var startLength:int = TestFrame.rootElement.textLength;
-
-			var flow1:FlowElement;
-			var flow2:FlowElement;
-
-			//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){
-					break;
-				}
-			}
-
-			assertTrue("either flow1 or flow2 are null", flow1 != null && flow2 != null);
-
-			var para1:ParagraphElement = flow1 as ParagraphElement;
-			var para2:ParagraphElement = flow2 as ParagraphElement;
-
-			var lines:Array = StandardFlowComposer(SelManager.textFlow.flowComposer).lines;
-
-			var refLine:Object;
-			for each (var line:TextFlowLine in lines){
-				if(line.paragraph == para2){
-					refLine = line;
-					break;
-				}
-			}
-
-			var para1end:int = para1.textLength - 1;
-			SelManager.selectRange(para1end,para1end);
-
-			var longString:String = "Far be it from me to interrupt such an important " +
-					"discussion, but it's come to my attention that the behavior of " +
-					"line shuffling has yet to be fully investigated within this context. " +
-					"So please allow me but a few lines with which to test whether or not " +
-					"the aforementioned is indeed working. Thank you.";
-			SelManager.insertText(longString);
-
-			SelManager.flushPendingOperations();
-
-			lines = StandardFlowComposer(SelManager.textFlow.flowComposer).lines;
-
-			for each (var line2:TextFlowLine in lines){
-				if(line2.paragraph == para2){
-					assertTrue("the next paragraph got recomposed instead of shuffling", line2 === refLine);
-					break;
-				}
-			}
-		}
-
-		/**
-		 * This very complicated test inserts some text in the middle of the flow after
-		 * determining which lines will be affected by the change (in terms of which
-		 * will need to recompose). It then checks to see if only those that should
-		 * be effected by the change have been changed.
-		 */
-		public function partialCompositionTest():void
-		{
-			var lines:Array = StandardFlowComposer(SelManager.textFlow.flowComposer).lines;
-
-			var linenum:int = lines.length / 2;
-			var initLength:int = lines.length;
-
-			var good:Boolean = false;
-			for(var i:int = 0; i < lines.length - 1; i++){
-				if(
-					(lines[linenum + i] as TextFlowLine).paragraph ==
-					(lines[linenum + i + 1] as TextFlowLine).paragraph
-				){
-					good = true;
-					linenum = linenum + i;
-					break;
-				}
-			}
-
-			if(!good){
-				for(var j:int = 0; j > 1; j--){
-					if(
-						(lines[linenum - j] as TextFlowLine).paragraph ==
-						(lines[linenum - j - 1] as TextFlowLine).paragraph
-					){
-						good = true;
-						linenum = linenum - j;
-						break;
-					}
-				}
-			}
-
-			if(!good){
-				fail("No starting place could be found");
-			}
-
-			//Register all the lines that shouldn't be damaged.
-			var undamagedUIDs:Array = new Array();
-			for(var k:int = 0; k < linenum; k++){
-				undamagedUIDs[k] = UIDUtil.getUID(lines[k]);
-			}
-
-			for(var l:int = lines.length - 1;
-				l > linenum &&
-				(lines[l] as TextFlowLine).paragraph != (lines[linenum] as TextFlowLine).paragraph;
-				l--)
-			{
-				undamagedUIDs[l] = UIDUtil.getUID(lines[l]);
-			}
-
-			//Register all the lines that should be damaged.
-			var damagedUIDs:Array = new Array();
-			for(var n:int = linenum;
-				 n < lines.length &&
-				(lines[n] as TextFlowLine).paragraph != null &&
-				(lines[n] as TextFlowLine).paragraph == (lines[linenum] as TextFlowLine).paragraph;
-				n++)
-			{
-				damagedUIDs[n] = UIDUtil.getUID(lines[n]);
-			}
-
-			var lineToDamage:TextFlowLine = lines[linenum] as TextFlowLine;
-			var ip:int = lineToDamage.absoluteStart + lineToDamage.textLength;
-
-			SelManager.selectRange(ip,ip+9);
-
-			var longString:String = "Line Break";
-			SelManager.insertText(longString);
-
-			SelManager.flushPendingOperations();
-
-			for(var m:int = 0; m < initLength; m++){
-				var UID:String = undamagedUIDs[m];
-
-				if(UID != null){
-					assertTrue("Expected line " + m + " not to recompose." +
-								" Break was at " + linenum + ".",
-								UID == UIDUtil.getUID(lines[m])
-					);
-				}else{
-					UID = damagedUIDs[m];
-					assertTrue("Expected line " + m + " to recompose." +
-								" Break was at " + linenum + ".",
-								UID != UIDUtil.getUID(lines[m])
-					);
-				}
-			}
-		}
-
-		private function createLineSummary(flowComposer:IFlowComposer):Object
-		{
-			// Lines that are referenced should go first
-			var releasedLineCount:int = 0;
-			var invalidLineCount:int = 0;
-			var validLineCount:int = 0;
-			var parentedLineCount:int = 0;
-			var nonexistentLineCount:int = 0;
-			var lineIndex:int = 0;
-			while (lineIndex < flowComposer.numLines)
-			{
-				var line:TextFlowLine = flowComposer.getLineAt(lineIndex);
-				if (line.validity == TextLineValidity.VALID)
-				{
-					assertTrue("Expecting valid referenced lines before invalid lines", invalidLineCount == 0);
-					var textLine:TextLine = line.peekTextLine();
-					assertTrue(!textLine || textLine.userData == line, "TextLine userData doesn't point back to TextFlowLine");
-					if (!textLine || !textLine.textBlock || textLine.textBlock.firstLine == null)
-						releasedLineCount++;
-					else if (textLine.parent)
-						parentedLineCount++;
-					else if (textLine.validity == TextLineValidity.VALID)
-						validLineCount++;
-					else assertTrue(false, "Found damaged unreleased TextLine for valid TextFlowLine");
-				}
-				else
-					invalidLineCount++;
-				lineIndex++;
-			}
-
-			var result:Object = new Object();
-			result["releasedLineCount"] = releasedLineCount;
-			result["invalidLineCount"] = invalidLineCount;
-			result["validLineCount"] = validLineCount;
-			result["parentedLineCount"] = parentedLineCount;
-			result["nonexistentLineCount"] = nonexistentLineCount;
-			return result;
-		}
-
-		// For benchmark: read in Alice and display one screenfull
-		public function composeOneScreen():void
-		{
-			loadTestFile("aliceExcerpt.xml");
-		}
-
-		// Tests that lines that aren't in view are released, and that composition didn't run to the end
-		public function releasedLineTest():void
-		{
-			loadTestFile("aliceExcerpt.xml");
-
-			var flowComposer:IFlowComposer = SelManager.textFlow.flowComposer;
-			assertTrue("Composed to the end, should leave text that is not in view uncomposed", flowComposer.damageAbsoluteStart < SelManager.textFlow.textLength);
-
-			var controller:ContainerController = flowComposer.getControllerAt(0);
-			var originalEstimatedHeight:Number = controller.contentHeight;
-			controller.verticalScrollPosition += 500;		// scroll ahead so we have some lines generated that can be released
-
-			var lineSummary:Object = createLineSummary(flowComposer);
-
-			assertTrue("Expected some invalid lines -- composition not complete", lineSummary["invalidLineCount"] > 0);
-			// NOTE: Released lines not in view can be garbage collected. This assertion is not necessarily valid.
-			assertTrue("Expected some released lines -- not all lines in view", lineSummary["releasedLineCount"] > 0);
-			assertTrue("Expected some valid and parented lines", lineSummary["parentedLineCount"] > 0);
-
-			// This will force composition
-			flowComposer.composeToPosition();
-			var actualContentHeight:Number = controller.contentHeight;
-			assertTrue("Expected full compose", flowComposer.damageAbsoluteStart == SelManager.textFlow.textLength);
-
-			var afterFullCompose:Object = createLineSummary(flowComposer);
-			assertTrue("Expected no invalid lines -- composition complete", afterFullCompose["invalidLineCount"] == 0);
-
-			assertTrue("Expected estimated is correct after full composition!", flowComposer.getControllerAt(0).contentHeight == actualContentHeight);
-
-	/*		Can't seem to get gc to release the textlines, although they get released when run through the profiler.
-			var eventCount:int = 0;
-			System.gc();System.gc();
-			var sprite:Sprite = Sprite(flowComposer.getControllerAt(0).container);
-			sprite.stage.addEventListener(Event.ENTER_FRAME, checkSummary);
-			// Wait for next enterFrame event, because gc is delayed
-
-			function checkSummary():void
-			{
-				if (eventCount > 50)
-				{
-					var afterGC:Object = createLineSummary(flowComposer);
-
-					// Test that lines are really getting gc'd
-					assertTrue("Expected lines to be gc'd!", afterGC["nonexistentLineCount"] > lineSummary["nonexistentLineCount"]);
-					assertTrue("Released lines expected 0", afterGC["releasedLineCount"] == 0);
-					sprite.stage.removeEventListener(Event.ENTER_FRAME, checkSummary);
-				}
-				System.gc();System.gc();
-				++eventCount;
-			} */
-		}
-
-		private var _lines:Array;
-		private var _textLen:int;
-		private function truncationTestCallback(textLine:TextLine):void
-		{
-			_textLen += textLine.rawTextLength;
-			_lines.push(textLine);
-		}
-
-		public function truncationTest():void
-		{
-			var bounds:Rectangle = new Rectangle();
-			var text:String = 'There are many such lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. ' +
-							  'Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers ' +
-							  'rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come.';
-
-			var rtlText:String ='مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة'+
-								'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة'+
-								'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة'+
-								'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة';
-
-			var accentedText:String = '\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
-											'\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
-											'\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
-											'\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
-											'\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A';
-
-			var formatForRtlTest:TextLayoutFormat = new TextLayoutFormat();
-			formatForRtlTest.fontFamily = 'Adobe Arabic';
-
-			// Get stats used later
-			_lines = new Array(); _textLen = 0;
-			bounds.width = 200;	bounds.height = NaN;
-			var factory:StringTextLineFactory = new StringTextLineFactory();
-			factory.text = text;
-			factory.compositionBounds = bounds;
-			factory.createTextLines(truncationTestCallback);
-			bounds = factory.getContentBounds();
-			assertTrue("[Not a code bug] Fix test case so that text occupies at least three lines when composed in specified bounds.", _lines.length >= 3);
-			var line0:TextLine = _lines[0] as TextLine;
-			var line0Extent:Number = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line0.y - line0.ascent : line0.y + line0.descent;
-			var line0TextLen:int = line0.rawTextLength;
-			var line1:TextLine = _lines[1] as TextLine;
-			var line1Extent:Number = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line1.y - line1.ascent : line1.y + line1.descent;
-			var line2:TextLine = _lines[2] as TextLine;
-			var line2Extent:Number = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line2.y - line2.ascent : line2.y + line2.descent;
-			var contentHeight:Number = bounds.height;
-			var contentTextLength:int = _textLen;
-
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.width = 200;	bounds.height = NaN;
-			factory.compositionBounds = bounds;
-			factory.text = rtlText;
-			factory.spanFormat = formatForRtlTest;
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("[Not a code bug] Fix test case so that RTL text occupies at least two lines when composed in specified bounds.", _lines.length >= 2);
-			var rtlLine0TextLen:int = _lines[0].rawTextLength;
-
-			_lines.splice(0); _textLen = 0; // Reset
-			bounds.width = 200;	bounds.height = NaN;
-			factory.compositionBounds = bounds;
-			factory.text = accentedText;
-			factory.spanFormat = null;
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("[Not a code bug] Fix test case so that accented text occupies at least two lines when composed in specified bounds.", _lines.length >= 2);
-
-			var line:TextLine;
-			var lineExtent:Number;
-			var truncationIndicatorIndex:int;
-			var originalContentPrefix:String;
-			var customTruncationIndicator:String;
-			var customFactory:StringTextLineFactory = new StringTextLineFactory();
-
-			// Verify that text is truncated even if width is not specified
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = NaN;	bounds.height = NaN;
-			factory.text = "A\nB"; // has an explicit new line character to ensure two lines
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions(null, 1);
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Did not truncate when width is unspecified", factory.isTruncated);
-
-			// Verify that text is truncated even if explicit line breaking is used
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = NaN;
-			var format:TextLayoutFormat = new TextLayoutFormat();
-			format.lineBreak = LineBreak.EXPLICIT;
-			factory.textFlowFormat = format;
-			factory.text = "A\nB"; // has an explicit new line character to ensure two lines
-			factory.compositionBounds = bounds;
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Did not truncate when explicit line breaking is used", factory.isTruncated);
-
-			// No lines case 1: compose height allows no line
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = line0Extent/2; // less than what one line requires
-			factory.textFlowFormat = null;
-			factory.text = text;
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions();
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Composed one or more lines when compose height allows none", _lines.length == 0 && factory.isTruncated);
-
-			// No lines case 2: 0 line count limit
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = contentHeight; // enough to fit all content
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions(null, 0);
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Composed one or more lines when line count limit is 0", _lines.length == 0 && factory.isTruncated);
-
-			// No lines case 3: truncation indicator is too large
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = contentHeight -1; // just shy of what the truncation indicator (same as original text) requires
-			factory.compositionBounds = bounds;
-			factory.text = text;
-			factory.truncationOptions = new TruncationOptions(text);
-			factory.textFlowFormat = null;
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Composed one or more lines when compose height does not allow truncation indicator itself to fit", _lines.length == 0 && factory.isTruncated);
-
-			// Verify truncation if composing to fit in bounds
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = line1Extent; // should fit two lines
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions();
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Invalid truncation results when composing to fit in bounds (lineCount)", _lines.length == 2 && factory.isTruncated);
-			line = _lines[1] as TextLine;
-			lineExtent = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line.y - line.ascent : line.y + line.descent;
-			assertTrue("Invalid truncation results when composing to fit in bounds", lineExtent <= line1Extent);
-
-			// Verify truncation if composing to fit in a line count limit
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.width = 200; bounds.height = NaN;
-			bounds.left = 0; bounds.top = 0;
-			factory.text = text;
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions(null, 2);
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("Invalid truncation results when composing to fit in a line count limit", _lines.length == 2 && factory.isTruncated);
-
-			// Verify truncation if composing to fit in bounds and a line count limit; the former dominates
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = line0Extent; // should fit one line
-			factory.text = text;
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions(null, 2);
-			factory.createTextLines(truncationTestCallback); // line count limit of 2
-			assertTrue("Invalid truncation results when multiple truncation criteria provided", _lines.length == 1 && factory.isTruncated);
-			line = _lines[0] as TextLine;
-			lineExtent = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line.y - line.ascent : line.y + line.descent;
-			assertTrue("Invalid truncation results when multiple truncation criteria provided", lineExtent <= line0Extent);
-
-			// Verify truncation if composing to fit in bounds and a line count limit; the latter dominates
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = line1Extent; // should fit two lines
-			factory.text = text;
-			factory.compositionBounds = bounds;
-			factory.truncationOptions = new TruncationOptions(null, 1);
-			factory.createTextLines(truncationTestCallback); // line count limit of 1
-			assertTrue("Invalid truncation results when multiple truncation criteria provided", _lines.length == 1 && factory.isTruncated);
-			line = _lines[0] as TextLine;
-			lineExtent = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line.y - line.ascent : line.y + line.descent;
-			assertTrue("Invalid truncation results when multiple truncation criteria provided", lineExtent <= line1Extent);
-
-			// Verify truncated text content with default truncation indicator (line count limit)
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = NaN; customFactory.text = text;
-			customFactory.compositionBounds = bounds;
-			customFactory.truncationOptions = new TruncationOptions(null, 2);
-			customFactory.createTextLines(truncationTestCallback);
-			truncationIndicatorIndex = customFactory.truncatedText.lastIndexOf(TruncationOptions.HORIZONTAL_ELLIPSIS);
-			assertTrue("Default truncation indicator not present at the end of the truncated string", truncationIndicatorIndex+TruncationOptions.HORIZONTAL_ELLIPSIS.length == customFactory.truncatedText.length && factory.isTruncated);
-			originalContentPrefix = customFactory.truncatedText.slice(0, truncationIndicatorIndex);
-			assertTrue("Original content before truncation indicator mangled", text.indexOf(originalContentPrefix) == 0);
-
-			// Verify truncated text content with default truncation indicator (fit in bounds)
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = line1Extent; // should fit two lines;
-			customFactory.text = text;
-			customFactory.compositionBounds = bounds;
-			customFactory.truncationOptions = new TruncationOptions();
-			customFactory.createTextLines(truncationTestCallback);
-			truncationIndicatorIndex = customFactory.truncatedText.lastIndexOf(TruncationOptions.HORIZONTAL_ELLIPSIS);
-			assertTrue("Default truncation indicator not present at the end of the truncated string", truncationIndicatorIndex+TruncationOptions.HORIZONTAL_ELLIPSIS.length == customFactory.truncatedText.length && factory.isTruncated);
-			originalContentPrefix = customFactory.truncatedText.slice(0, truncationIndicatorIndex);
-			assertTrue("Original content before truncation indicator mangled", text.indexOf(originalContentPrefix) == 0);
-
-			// Verify truncated text content with custom truncation indicator
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = NaN; customFactory.text = text;
-			customTruncationIndicator = "<SNIP>";
-			customFactory.compositionBounds = bounds;
-			customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 2);
-			customFactory.createTextLines(truncationTestCallback);
-			truncationIndicatorIndex = customFactory.truncatedText.lastIndexOf(customTruncationIndicator);
-			assertTrue("Truncation indicator not present at the end of the truncated string", truncationIndicatorIndex+customTruncationIndicator.length == customFactory.truncatedText.length && factory.isTruncated);
-			originalContentPrefix = customFactory.truncatedText.slice(0, truncationIndicatorIndex);
-			assertTrue("Original content before truncation indicator mangled", text.indexOf(originalContentPrefix) == 0);
-
-			// Verify original text replacement is optimal
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = NaN; customFactory.text = text;
-			customFactory.text = text;
-			customFactory.compositionBounds = bounds;
-			customTruncationIndicator = '\u200B'; // Zero-width space : should not require *any* original content that fits to be replaced
-			customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
-			customFactory.createTextLines(truncationTestCallback);
-			assertTrue("Replacing more original content than is neccessary", customFactory.truncatedText.length == line0TextLen+customTruncationIndicator.length && factory.isTruncated);
-
-			// Verify original text replacement is optimal (RTL text)
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = NaN; customFactory.text = rtlText;
-			customFactory.compositionBounds = bounds;
-			customTruncationIndicator = '\u200B'; // Zero-width space : should not require *any* original content that fits to be replaced
-			customFactory.spanFormat = formatForRtlTest;
-			customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
-			customFactory.createTextLines(truncationTestCallback);
-			assertTrue("Replacing more original content than is neccessary (RTL text)", customFactory.truncatedText.length == rtlLine0TextLen+customTruncationIndicator.length && factory.isTruncated);
-			customFactory.spanFormat = null;
-
-			// Verify truncation happens at atom boundaries
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = NaN; customFactory.text = accentedText;
-			customTruncationIndicator = '<' + '\u200A' /* Hair space */ + '>'; // what precedes and succeeds the hair space is irrelevant
-			customFactory.compositionBounds = bounds;
-			customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
-			customFactory.createTextLines(truncationTestCallback);
-			assertTrue("[Not a code bug] Fix test case so that truncation indicator itself fits", _lines.length == 1 && factory.isTruncated); // baseline
-
-			var initialTruncationPoint:int =  customFactory.truncatedText.length - customTruncationIndicator.length;
-			assertTrue("[Not a code bug] Fix test case so that some of the original content is left behind on first truncation attempt", initialTruncationPoint > 0); // baseline
-			assertTrue("Truncation in the middle of an atom!", initialTruncationPoint % 2 == 0);
-			var nextTruncationPoint:int;
-			do
-			{
-				bounds.height = NaN;
-				customTruncationIndicator = customTruncationIndicator.replace('\u200A', '\u200A\u200A'); // add another hair space in each iteration, making truncation indicator wider (ever so slightly)
-				customFactory.compositionBounds = bounds;
-				customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
-				customFactory.createTextLines(truncationTestCallback);
-
-				nextTruncationPoint =  customFactory.truncatedText.length - customTruncationIndicator.length;
-				if (nextTruncationPoint != initialTruncationPoint)
-				{
-					assertTrue("Truncation in the middle of an atom!", nextTruncationPoint % 2 == 0);
-					assertTrue("Sub-optimal replacement of original content?", nextTruncationPoint == initialTruncationPoint-2);
-					initialTruncationPoint = nextTruncationPoint;
-				}
-
-			} while (nextTruncationPoint);
-
-			// Verify scrolling behavior when truncation options are set
-			_lines.splice(0); _textLen = 0; // reset
-			bounds.left = 0; bounds.top = 0;
-			bounds.width = 200; bounds.height = line1Extent; // should fit two lines
-			factory.compositionBounds = bounds;
-			factory.verticalScrollPolicy = "on";
-			var vaFormat:TextLayoutFormat = new TextLayoutFormat();
-			vaFormat.verticalAlign = VerticalAlign.BOTTOM;
-			factory.textFlowFormat = vaFormat;
-			factory.truncationOptions = new TruncationOptions(); // should override scroll policy
-			factory.createTextLines(truncationTestCallback);
-			assertTrue("When verticalAlign is Bottom, and scrolling is on, but truncation options are set, only text that fits should be generated",
-				_textLen < contentTextLength && factory.isTruncated);
-		}
-
-		public function CompositionCompleteEventTest():void
-		{
-			var gotEvent:Boolean = false;
-			var textFlow:TextFlow = SelManager.textFlow;
-			textFlow.addEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, completionHandler);
-			var charFormat:TextLayoutFormat = new TextLayoutFormat();
-			charFormat.fontSize=48;
-			SelManager.selectAll();
-			(SelManager as EditManager).applyLeafFormat(charFormat);
-			assertTrue("Didn't get the CompositionCompleteEvent", gotEvent == true);
-
-			function completionHandler(event:CompositionCompleteEvent): void
-			{
-				gotEvent = true;
-				textFlow.removeEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, completionHandler);
-			}
-		}
-		
-		private function setUpMultipleLinkedContainers(numberOfContainers:int):Sprite
-		{
-			var flexContainer:Container;
-			var textFlow:TextFlow = SelManager.textFlow;
-			var flowComposer:IFlowComposer = textFlow.flowComposer;
-			var firstController:ContainerController = textFlow.flowComposer.getControllerAt(0);
-			var totalWidth:Number = firstController.compositionWidth;
-			var containerWidth:Number = totalWidth / numberOfContainers;
-			var containerHeight:Number = firstController.compositionHeight;
-			firstController.setCompositionSize(containerWidth, firstController.compositionHeight);
-			var containerParent:Sprite = firstController.container.parent as Sprite;
-			if (containerParent is Container)
-			{
-				flexContainer = Container(containerParent);
-				var newContainerParent:Sprite = new Sprite();
-				flexContainer.rawChildren.addChild(newContainerParent);
-				flexContainer.rawChildren.removeChild(firstController.container);
-				newContainerParent.addChild(firstController.container);
-				containerParent = newContainerParent;
-			}
-			var pos:int = containerWidth;
-			while (flowComposer.numControllers < numberOfContainers)
-			{
-				var s:Sprite = new Sprite();
-				s.x = pos;
-				pos += containerWidth;
-				containerParent.addChild(s);
-				flowComposer.addController(new ContainerController(s, containerWidth, containerHeight));
-			}
-			return containerParent;
-		}
-		
-		private function restoreToSingleContainer(containerParent:Sprite):void
-		{
-			var flexContainer:Container = containerParent.parent as Container;
-			
-			if (flexContainer)
-			{
-				flexContainer.rawChildren.removeChild(containerParent);
-				flexContainer.rawChildren.addChild(containerParent.getChildAt(0));
-			}
-			var flowComposer:IFlowComposer = SelManager.textFlow.flowComposer;
-			while (flowComposer.numControllers > 1)
-				flowComposer.removeControllerAt(flowComposer.numControllers - 1);			
-		}
-		
-		// Test case with multiple containers, where the last container is scrolled down, and update will cause a scroll
-		// Watson 2583969
-		public function scrolledRedrawPartialCompose():void
-		{
-			var multiContainerParent:Sprite;
-			
-			try 
-			{
-				var textFlow:TextFlow = SelManager.textFlow;
-				var flowComposer:IFlowComposer = textFlow.flowComposer;
-				multiContainerParent = setUpMultipleLinkedContainers(5);
-				
-				// Paste all the text again, so all containers are full, and there is text scrolled out
-				var textScrap:TextScrap = TextScrap.createTextScrap(new TextRange(textFlow, 0, textFlow.textLength));
-				EditManager(SelManager).pasteTextScrap(textScrap);
-				flowComposer.updateAllControllers();
-				
-	
-				// Set selection to the last two lines of the flow, and scroll to the new selection, and then delete the text
-				var lastController:ContainerController = flowComposer.getControllerAt(flowComposer.numControllers - 1);
-				flowComposer.composeToPosition();	// force all text to compose
-				var nextToLastLine:TextFlowLine = flowComposer.getLineAt(flowComposer.numLines - 2);
-				SelManager.selectRange(nextToLastLine.absoluteStart, textFlow.textLength);
-				lastController.scrollToRange(SelManager.absoluteStart, SelManager.absoluteEnd);
-				var firstVisibleChar:int = lastController.getFirstVisibleLine().absoluteStart; // save off the current scrolled-to text pos
-				flowComposer.updateAllControllers();
-				EditManager(SelManager).deleteText();
-				
-				// The delete (and subsequent redraw) should have caused a scroll during the ContainerController updateCompositionShapes.
-				// Check that this happened correctly.
-				var firstVisibleCharAfterPaste:int = lastController.getFirstVisibleLine().absoluteStart;
-				assertTrue("Expected scroll during update", firstVisibleChar != firstVisibleCharAfterPaste);
-			}
-			finally
-			{
-				// restore how containers were set up before
-				restoreToSingleContainer(multiContainerParent);
-			}
-		}
-
-		// Test case with multiple containers, where the last container is scrolled down, and update will cause a scroll
-		// Watson 2583969
-		public function multipleContainersWithPadding():void
-		{
-			var multiContainerParent:Sprite;
-			
-			try 
-			{
-				var textFlow:TextFlow = SelManager.textFlow;
-				var flowComposer:IFlowComposer = textFlow.flowComposer;
-				multiContainerParent = setUpMultipleLinkedContainers(2);
-				
-				var firstController:ContainerController = flowComposer.getControllerAt(0);
-				var format:TextLayoutFormat = new TextLayoutFormat(firstController.format);
-				format.paddingTop = firstController.compositionHeight;
-				firstController.format = format;
-				flowComposer.updateAllControllers();
-				
-				assertTrue("Expected no lines in first container", firstController.getFirstVisibleLine() == null && firstController.getLastVisibleLine() == null);
-			}
-			finally
-			{
-				// restore how containers were set up before
-				restoreToSingleContainer(multiContainerParent);
-			}
-		}
-		
-		private const numberOfLinesBack:int = 5;
-		public function deleteAtContainerStart():void
-		{
-			var multiContainerParent:Sprite;
-			
-			try 
-			{
-				var textFlow:TextFlow = SelManager.textFlow;
-				var flowComposer:IFlowComposer = textFlow.flowComposer;
-				multiContainerParent = setUpMultipleLinkedContainers(2);
-
-				flowComposer.composeToPosition();
-				var controller:ContainerController = flowComposer.getControllerAt(0);
-				
-				var lastLineIndex:int = flowComposer.findLineIndexAtPosition(controller.absoluteStart + controller.textLength);
-				var startIndex:int = flowComposer.getLineAt(lastLineIndex - numberOfLinesBack).absoluteStart;
-				SelManager.selectRange(startIndex, startIndex);
-				for (var i:int = 0; i < numberOfLinesBack + 1; ++i)
-					SelManager.splitParagraph();
-				flowComposer.updateAllControllers();
-				var textLengthBefore:int = controller.textLength;
-				
-				assertTrue("Selection should be at the start of the next container", SelManager.absoluteStart == controller.absoluteStart + controller.textLength);
-				SelManager.deletePreviousCharacter();
-				flowComposer.composeToPosition();
-				assertTrue("Expected first line of following container to be sucked in", controller.textLength > textLengthBefore);
-			}
-			finally
-			{
-				// restore how containers were set up before
-				restoreToSingleContainer(multiContainerParent);
-			}
-		}
-		
-		public function resizeController2644361():void
-		{
-			var textFlow:TextFlow = SelManager.textFlow;
-			var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
-			var originalWidth:Number = controller.compositionWidth;
-			var originalHeight:Number = controller.compositionHeight;
-			var scrap:TextScrap = TextScrap.createTextScrap(new TextRange(textFlow, 0, textFlow.textLength));
-			SelManager.selectRange(textFlow.textLength - 1, textFlow.textLength - 1);
-			SelManager.splitParagraph();
-			SelManager.pasteTextScrap(scrap);
-			SelManager.pasteTextScrap(scrap);
-			textFlow.flowComposer.updateAllControllers();
-			controller.setCompositionSize( 825 ,  471 )
-			SelManager.updateAllControllers();
-			controller.setCompositionSize( 808 ,  464 )
-			SelManager.updateAllControllers();
-			controller.setCompositionSize( 791 ,  462 )
-			SelManager.updateAllControllers();
-			controller.setCompositionSize( 768 ,  461 )
-			SelManager.updateAllControllers();
-		}
-		
-		public function resizeEmptyController():void
-		{
-			var textFlow:TextFlow = new TextFlow();
-			var p:ParagraphElement = new ParagraphElement();
-			textFlow.addChild(p);
-			
-			var span:SpanElement = new SpanElement();
-			span.text = "Hello world";
-			span.fontSize = 40;
-			p.addChild(span);
-			
-			var sprite1:Sprite = new Sprite();
-			var cc1:ContainerController = new ContainerController(sprite1,100,50);
-			sprite1.x = 100;
-			var sprite2:Sprite = new Sprite();
-			var cc2:ContainerController = new ContainerController(sprite2,100,50);
-			sprite2.x = 300;
-		//	addChild(sprite1);
-		//	addChild(sprite2);
-			textFlow.flowComposer.addController(cc1);
-			textFlow.flowComposer.addController(cc2);
-			textFlow.flowComposer.updateAllControllers();
-			var originalLength:int = cc1.textLength;
-			cc1.setCompositionSize(100,10);
-			textFlow.flowComposer.updateAllControllers();
-			cc1.setCompositionSize(100,50);
-			textFlow.flowComposer.updateAllControllers();
-			assertTrue("Expected text to recompose into controller", cc1.textLength == originalLength);
-		}
-
-		public function emptyController():void
-		{
-			var s:Sprite = new Sprite();
-			var textFlow:TextFlow = new TextFlow();
-			textFlow.flowComposer.addController(new ContainerController(s, 0, 0));
-			textFlow.flowComposer.updateAllControllers();
-		}
-		
-		// Check that the content bounds includes all parcels when composition starts from a column that is not the first
-		// See Watson 2769670
-		public function contentBoundsOnComposeFromMiddle():void
-		{
-			TestFrame.rootElement.blockProgression = writingDirection[0];
-			TestFrame.rootElement.direction        = writingDirection[1];
-			
-			var textFlow:TextFlow = SelManager.textFlow;
-			var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
-			var composeSpace:Rectangle = new Rectangle(0, 0, controller.compositionWidth, controller.compositionHeight);
-
-			var lastLine:TextFlowLine = controller.getLastVisibleLine();
-			var lastVisiblePosition:int = lastLine.absoluteStart + lastLine.textLength - 1;
-			var charPos:int = lastVisiblePosition - 100;
-			
-			// Trim off the unseen portion of the flow to a little before the end, so we aren't 
-			// affected by content height estimation, and so we can check that height from previous
-			// columns is included.
-			SelManager.selectRange(charPos, textFlow.textLength - 1);
-			SelManager.deleteText();
-			
-			// Change format to 3 columns justified text, and get the bounds. This time we composed from the start.
-			var format:TextLayoutFormat = new TextLayoutFormat(textFlow.format);
-			format.columnCount = 3;
-			format.textAlign = TextAlign.JUSTIFY;
-			textFlow.format = format;
-			textFlow.flowComposer.updateAllControllers();
-			var bounds:Rectangle = controller.getContentBounds();
-			
-			// Force partial composition in the last column. The bounds may be slightly different in height because we aren't
-			// iterating all the lines to get height. If it doesn't match, it should be equal to the (logical) compositionHeight.
-			charPos = textFlow.textLength - 3;
-			var leafFormat:TextLayoutFormat = new TextLayoutFormat();
-			leafFormat.color = 0xFF0000;
-			SelManager.selectRange(charPos, charPos + 1);
-			SelManager.applyLeafFormat(leafFormat);
-			var boundsAfterPartialCompose:Rectangle = controller.getContentBounds();
-
-			var boundsMatch:Boolean = boundsAfterPartialCompose.equals(bounds);
-			if (!boundsMatch && 
-				bounds.y == boundsAfterPartialCompose.y)
-			{
-				if (controller.effectiveBlockProgression == BlockProgression.TB)
-					boundsMatch = Math.abs(boundsAfterPartialCompose.x - bounds.x) < 1 && Math.abs(boundsAfterPartialCompose.width - bounds.width) < 1 && boundsAfterPartialCompose.height == controller.compositionHeight;
-				else
-					boundsMatch = Math.abs(boundsAfterPartialCompose.x - -controller.compositionWidth) < 1 && Math.abs(boundsAfterPartialCompose.height - bounds.height) < 1 && boundsAfterPartialCompose.width == controller.compositionWidth;
-			}
-			
-			assertTrue("Expected bounds after partial compose to match bounds from previous full compose", boundsMatch);
-		}
-	}
+
+    [RunWith("org.flexunit.runners.Parameterized")]
+    public class CompositionTest extends VellumTestCase
+    {
+        [Parameters]
+        public static var parameters:Array = [
+            ["simple.xml", false],
+            ["asknot.xml", true]
+        ];
+
+        private var _lines:Array;
+        private var _textLen:int;
+        private const numberOfLinesBack:int = 5;
+
+        public function CompositionTest(fileName:String, testSettings:Boolean)
+        {
+            super("", "CompositionTest", TestConfig.getInstance());
+
+            TestData.fileName = fileName;
+            addDefaultTestSettings = testSettings;
+
+            metaData = {};
+            // Note: These must correspond to a Watson product area (case-sensitive)
+            metaData.productArea = "Text Composition";
+        }
+
+        [Before]
+        override public function setUpTest():void
+        {
+            super.setUpTest();
+        }
+
+        [After]
+        override public function tearDownTest():void
+        {
+            super.tearDownTest();
+        }
+
+        /**
+         * First, find two back to back paragraphs. Second, record the first line of the
+         * second paragraph; if the first paragraph is changed and the second gets recomposed
+         * (i.e. what we don't want) this line will be re-created (also, the first line of
+         * the second paragraph is the easiest to find). Third, make an insertion
+         * point at the end of the first paragraph. Fourth, place a bunch of text at the end
+         * of the paragraph to force it to recompose. Finally, find the first line in the
+         * second paragraph again and see if it is the same as the line you recorded in step
+         * (using "===").
+         */
+        [Test]
+        public function checkParagraphShufflingTest():void
+        {
+            var startLength:int = TestFrame.rootElement.textLength;
+
+            var flow1:FlowElement;
+            var flow2:FlowElement;
+
+            //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)
+                {
+                    break;
+                }
+            }
+
+            assertTrue("either flow1 or flow2 are null", flow1 != null && flow2 != null);
+
+            var para1:ParagraphElement = flow1 as ParagraphElement;
+            var para2:ParagraphElement = flow2 as ParagraphElement;
+
+            var lines:Array = StandardFlowComposer(SelManager.textFlow.flowComposer).lines;
+
+            var refLine:Object;
+            for each (var line:TextFlowLine in lines)
+            {
+                if (line.paragraph == para2)
+                {
+                    refLine = line;
+                    break;
+                }
+            }
+
+            var para1end:int = para1.textLength - 1;
+            SelManager.selectRange(para1end, para1end);
+
+            var longString:String = "Far be it from me to interrupt such an important " +
+                    "discussion, but it's come to my attention that the behavior of " +
+                    "line shuffling has yet to be fully investigated within this context. " +
+                    "So please allow me but a few lines with which to test whether or not " +
+                    "the aforementioned is indeed working. Thank you.";
+            SelManager.insertText(longString);
+
+            SelManager.flushPendingOperations();
+
+            lines = StandardFlowComposer(SelManager.textFlow.flowComposer).lines;
+
+            for each (var line2:TextFlowLine in lines)
+            {
+                if (line2.paragraph == para2)
+                {
+                    assertTrue("the next paragraph got recomposed instead of shuffling", line2 === refLine);
+                    break;
+                }
+            }
+        }
+
+        /**
+         * This very complicated test inserts some text in the middle of the flow after
+         * determining which lines will be affected by the change (in terms of which
+         * will need to recompose). It then checks to see if only those that should
+         * be effected by the change have been changed.
+         */
+        [Test]
+        [Ignore]
+        public function partialCompositionTest():void
+        {
+            var lines:Array = StandardFlowComposer(SelManager.textFlow.flowComposer).lines;
+
+            var linenum:int = lines.length / 2;
+            var initLength:int = lines.length;
+
+            var good:Boolean = false;
+            for (var i:int = 0; i < lines.length - 1; i++)
+            {
+                if (
+                        (lines[linenum + i] as TextFlowLine).paragraph ==
+                                (lines[linenum + i + 1] as TextFlowLine).paragraph
+                        )
+                {
+                    good = true;
+                    linenum = linenum + i;
+                    break;
+                }
+            }
+
+            if (!good)
+            {
+                for (var j:int = 0; j > 1; j--)
+                {
+                    if (
+                            (lines[linenum - j] as TextFlowLine).paragraph ==
+                                    (lines[linenum - j - 1] as TextFlowLine).paragraph
+                            )
+                    {
+                        good = true;
+                        linenum = linenum - j;
+                        break;
+                    }
+                }
+            }
+
+            if (!good)
+            {
+                fail("No starting place could be found");
+            }
+
+            //Register all the lines that shouldn't be damaged.
+            var undamagedUIDs:Array = new Array();
+            for (var k:int = 0; k < linenum; k++)
+            {
+                undamagedUIDs[k] = UIDUtil.getUID(lines[k]);
+            }
+
+            for (var l:int = lines.length - 1;
+                 l > linenum &&
+                         (lines[l] as TextFlowLine).paragraph != (lines[linenum] as TextFlowLine).paragraph;
+                 l--)
+            {
+                undamagedUIDs[l] = UIDUtil.getUID(lines[l]);
+            }
+
+            //Register all the lines that should be damaged.
+            var damagedUIDs:Array = new Array();
+            for (var n:int = linenum;
+                 n < lines.length &&
+                         (lines[n] as TextFlowLine).paragraph != null &&
+                         (lines[n] as TextFlowLine).paragraph == (lines[linenum] as TextFlowLine).paragraph;
+                 n++)
+            {
+                damagedUIDs[n] = UIDUtil.getUID(lines[n]);
+            }
+
+            var lineToDamage:TextFlowLine = lines[linenum] as TextFlowLine;
+            var ip:int = lineToDamage.absoluteStart + lineToDamage.textLength;
+
+            SelManager.selectRange(ip, ip + 9);
+
+            var longString:String = "Line Break";
+            SelManager.insertText(longString);
+
+            SelManager.flushPendingOperations();
+
+            for (var m:int = 0; m < initLength; m++)
+            {
+                var UID:String = undamagedUIDs[m];
+
+                if (UID != null)
+                {
+                    assertTrue("Expected line " + m + " not to recompose." +
+                            " Break was at " + linenum + ".",
+                            UID == UIDUtil.getUID(lines[m])
+                    );
+                } else
+                {
+                    UID = damagedUIDs[m];
+                    assertTrue("Expected line " + m + " to recompose." +
+                            " Break was at " + linenum + ".",
+                            UID != UIDUtil.getUID(lines[m])
+                    );
+                }
+            }
+        }
+
+        // For benchmark: read in Alice and display one screenfull
+        [Test]
+        public function composeOneScreen():void
+        {
+            loadTestFile("aliceExcerpt.xml");
+        }
+
+        // Tests that lines that aren't in view are released, and that composition didn't run to the end
+        public function releasedLineTest():void
+        {
+            loadTestFile("aliceExcerpt.xml");
+
+            var flowComposer:IFlowComposer = SelManager.textFlow.flowComposer;
+            assertTrue("Composed to the end, should leave text that is not in view uncomposed", flowComposer.damageAbsoluteStart < SelManager.textFlow.textLength);
+
+            var controller:ContainerController = flowComposer.getControllerAt(0);
+            var originalEstimatedHeight:Number = controller.contentHeight;
+            controller.verticalScrollPosition += 500;		// scroll ahead so we have some lines generated that can be released
+
+            var lineSummary:Object = createLineSummary(flowComposer);
+
+            assertTrue("Expected some invalid lines -- composition not complete", lineSummary["invalidLineCount"] > 0);
+            // NOTE: Released lines not in view can be garbage collected. This assertion is not necessarily valid.
+            assertTrue("Expected some released lines -- not all lines in view", lineSummary["releasedLineCount"] > 0);
+            assertTrue("Expected some valid and parented lines", lineSummary["parentedLineCount"] > 0);
+
+            // This will force composition
+            flowComposer.composeToPosition();
+            var actualContentHeight:Number = controller.contentHeight;
+            assertTrue("Expected full compose", flowComposer.damageAbsoluteStart == SelManager.textFlow.textLength);
+
+            var afterFullCompose:Object = createLineSummary(flowComposer);
+            assertTrue("Expected no invalid lines -- composition complete", afterFullCompose["invalidLineCount"] == 0);
+
+            assertTrue("Expected estimated is correct after full composition!", flowComposer.getControllerAt(0).contentHeight == actualContentHeight);
+
+            /*		Can't seem to get gc to release the textlines, although they get released when run through the profiler.
+             var eventCount:int = 0;
+             System.gc();System.gc();
+             var sprite:Sprite = Sprite(flowComposer.getControllerAt(0).container);
+             sprite.stage.addEventListener(Event.ENTER_FRAME, checkSummary);
+             // Wait for next enterFrame event, because gc is delayed
+
+             function checkSummary():void
+             {
+             if (eventCount > 50)
+             {
+             var afterGC:Object = createLineSummary(flowComposer);
+
+             // Test that lines are really getting gc'd
+             assertTrue("Expected lines to be gc'd!", afterGC["nonexistentLineCount"] > lineSummary["nonexistentLineCount"]);
+             assertTrue("Released lines expected 0", afterGC["releasedLineCount"] == 0);
+             sprite.stage.removeEventListener(Event.ENTER_FRAME, checkSummary);
+             }
+             System.gc();System.gc();
+             ++eventCount;
+             } */
+        }
+
+        [Test]
+        public function truncationTest():void
+        {
+            var bounds:Rectangle = new Rectangle();
+            var text:String = 'There are many such lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. ' +
+                    'Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers ' +
+                    'rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come.';
+
+            var rtlText:String = 'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة' +
+                    'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة' +
+                    'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة' +
+                    'مدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسةمدرسة';
+
+            var accentedText:String = '\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
+                    '\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
+                    '\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
+                    '\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A' +
+                    '\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A\u0041\u030A';
+
+            var formatForRtlTest:TextLayoutFormat = new TextLayoutFormat();
+            formatForRtlTest.fontFamily = 'Adobe Arabic';
+
+            // Get stats used later
+            _lines = new Array();
+            _textLen = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            var factory:StringTextLineFactory = new StringTextLineFactory();
+            factory.text = text;
+            factory.compositionBounds = bounds;
+            factory.createTextLines(truncationTestCallback);
+            bounds = factory.getContentBounds();
+            assertTrue("[Not a code bug] Fix test case so that text occupies at least three lines when composed in specified bounds.", _lines.length >= 3);
+            var line0:TextLine = _lines[0] as TextLine;
+            var line0Extent:Number = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line0.y - line0.ascent : line0.y + line0.descent;
+            var line0TextLen:int = line0.rawTextLength;
+            var line1:TextLine = _lines[1] as TextLine;
+            var line1Extent:Number = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line1.y - line1.ascent : line1.y + line1.descent;
+            var line2:TextLine = _lines[2] as TextLine;
+            var line2Extent:Number = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line2.y - line2.ascent : line2.y + line2.descent;
+            var contentHeight:Number = bounds.height;
+            var contentTextLength:int = _textLen;
+
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.width = 200;
+            bounds.height = NaN;
+            factory.compositionBounds = bounds;
+            factory.text = rtlText;
+            factory.spanFormat = formatForRtlTest;
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("[Not a code bug] Fix test case so that RTL text occupies at least two lines when composed in specified bounds.", _lines.length >= 2);
+            var rtlLine0TextLen:int = _lines[0].rawTextLength;
+
+            _lines.splice(0);
+            _textLen = 0; // Reset
+            bounds.width = 200;
+            bounds.height = NaN;
+            factory.compositionBounds = bounds;
+            factory.text = accentedText;
+            factory.spanFormat = null;
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("[Not a code bug] Fix test case so that accented text occupies at least two lines when composed in specified bounds.", _lines.length >= 2);
+
+            var line:TextLine;
+            var lineExtent:Number;
+            var truncationIndicatorIndex:int;
+            var originalContentPrefix:String;
+            var customTruncationIndicator:String;
+            var customFactory:StringTextLineFactory = new StringTextLineFactory();
+
+            // Verify that text is truncated even if width is not specified
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = NaN;
+            bounds.height = NaN;
+            factory.text = "A\nB"; // has an explicit new line character to ensure two lines
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions(null, 1);
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Did not truncate when width is unspecified", factory.isTruncated);
+
+            // Verify that text is truncated even if explicit line breaking is used
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            var format:TextLayoutFormat = new TextLayoutFormat();
+            format.lineBreak = LineBreak.EXPLICIT;
+            factory.textFlowFormat = format;
+            factory.text = "A\nB"; // has an explicit new line character to ensure two lines
+            factory.compositionBounds = bounds;
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Did not truncate when explicit line breaking is used", factory.isTruncated);
+
+            // No lines case 1: compose height allows no line
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = line0Extent / 2; // less than what one line requires
+            factory.textFlowFormat = null;
+            factory.text = text;
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions();
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Composed one or more lines when compose height allows none", _lines.length == 0 && factory.isTruncated);
+
+            // No lines case 2: 0 line count limit
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = contentHeight; // enough to fit all content
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions(null, 0);
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Composed one or more lines when line count limit is 0", _lines.length == 0 && factory.isTruncated);
+
+            // No lines case 3: truncation indicator is too large
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = contentHeight - 1; // just shy of what the truncation indicator (same as original text) requires
+            factory.compositionBounds = bounds;
+            factory.text = text;
+            factory.truncationOptions = new TruncationOptions(text);
+            factory.textFlowFormat = null;
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Composed one or more lines when compose height does not allow truncation indicator itself to fit", _lines.length == 0 && factory.isTruncated);
+
+            // Verify truncation if composing to fit in bounds
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = line1Extent; // should fit two lines
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions();
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Invalid truncation results when composing to fit in bounds (lineCount)", _lines.length == 2 && factory.isTruncated);
+            line = _lines[1] as TextLine;
+            lineExtent = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line.y - line.ascent : line.y + line.descent;
+            assertTrue("Invalid truncation results when composing to fit in bounds", lineExtent <= line1Extent);
+
+            // Verify truncation if composing to fit in a line count limit
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.width = 200;
+            bounds.height = NaN;
+            bounds.left = 0;
+            bounds.top = 0;
+            factory.text = text;
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions(null, 2);
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("Invalid truncation results when composing to fit in a line count limit", _lines.length == 2 && factory.isTruncated);
+
+            // Verify truncation if composing to fit in bounds and a line count limit; the former dominates
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = line0Extent; // should fit one line
+            factory.text = text;
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions(null, 2);
+            factory.createTextLines(truncationTestCallback); // line count limit of 2
+            assertTrue("Invalid truncation results when multiple truncation criteria provided", _lines.length == 1 && factory.isTruncated);
+            line = _lines[0] as TextLine;
+            lineExtent = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line.y - line.ascent : line.y + line.descent;
+            assertTrue("Invalid truncation results when multiple truncation criteria provided", lineExtent <= line0Extent);
+
+            // Verify truncation if composing to fit in bounds and a line count limit; the latter dominates
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = line1Extent; // should fit two lines
+            factory.text = text;
+            factory.compositionBounds = bounds;
+            factory.truncationOptions = new TruncationOptions(null, 1);
+            factory.createTextLines(truncationTestCallback); // line count limit of 1
+            assertTrue("Invalid truncation results when multiple truncation criteria provided", _lines.length == 1 && factory.isTruncated);
+            line = _lines[0] as TextLine;
+            lineExtent = StringTextLineFactory.defaultConfiguration.overflowPolicy == OverflowPolicy.FIT_ANY ? line.y - line.ascent : line.y + line.descent;
+            assertTrue("Invalid truncation results when multiple truncation criteria provided", lineExtent <= line1Extent);
+
+            // Verify truncated text content with default truncation indicator (line count limit)
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            customFactory.text = text;
+            customFactory.compositionBounds = bounds;
+            customFactory.truncationOptions = new TruncationOptions(null, 2);
+            customFactory.createTextLines(truncationTestCallback);
+            truncationIndicatorIndex = customFactory.truncatedText.lastIndexOf(TruncationOptions.HORIZONTAL_ELLIPSIS);
+            assertTrue("Default truncation indicator not present at the end of the truncated string", truncationIndicatorIndex + TruncationOptions.HORIZONTAL_ELLIPSIS.length == customFactory.truncatedText.length && factory.isTruncated);
+            originalContentPrefix = customFactory.truncatedText.slice(0, truncationIndicatorIndex);
+            assertTrue("Original content before truncation indicator mangled", text.indexOf(originalContentPrefix) == 0);
+
+            // Verify truncated text content with default truncation indicator (fit in bounds)
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = line1Extent; // should fit two lines;
+            customFactory.text = text;
+            customFactory.compositionBounds = bounds;
+            customFactory.truncationOptions = new TruncationOptions();
+            customFactory.createTextLines(truncationTestCallback);
+            truncationIndicatorIndex = customFactory.truncatedText.lastIndexOf(TruncationOptions.HORIZONTAL_ELLIPSIS);
+            assertTrue("Default truncation indicator not present at the end of the truncated string", truncationIndicatorIndex + TruncationOptions.HORIZONTAL_ELLIPSIS.length == customFactory.truncatedText.length && factory.isTruncated);
+            originalContentPrefix = customFactory.truncatedText.slice(0, truncationIndicatorIndex);
+            assertTrue("Original content before truncation indicator mangled", text.indexOf(originalContentPrefix) == 0);
+
+            // Verify truncated text content with custom truncation indicator
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            customFactory.text = text;
+            customTruncationIndicator = "<SNIP>";
+            customFactory.compositionBounds = bounds;
+            customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 2);
+            customFactory.createTextLines(truncationTestCallback);
+            truncationIndicatorIndex = customFactory.truncatedText.lastIndexOf(customTruncationIndicator);
+            assertTrue("Truncation indicator not present at the end of the truncated string", truncationIndicatorIndex + customTruncationIndicator.length == customFactory.truncatedText.length && factory.isTruncated);
+            originalContentPrefix = customFactory.truncatedText.slice(0, truncationIndicatorIndex);
+            assertTrue("Original content before truncation indicator mangled", text.indexOf(originalContentPrefix) == 0);
+
+            // Verify original text replacement is optimal
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            customFactory.text = text;
+            customFactory.text = text;
+            customFactory.compositionBounds = bounds;
+            customTruncationIndicator = '\u200B'; // Zero-width space : should not require *any* original content that fits to be replaced
+            customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
+            customFactory.createTextLines(truncationTestCallback);
+            assertTrue("Replacing more original content than is neccessary", customFactory.truncatedText.length == line0TextLen + customTruncationIndicator.length && factory.isTruncated);
+
+            // Verify original text replacement is optimal (RTL text)
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            customFactory.text = rtlText;
+            customFactory.compositionBounds = bounds;
+            customTruncationIndicator = '\u200B'; // Zero-width space : should not require *any* original content that fits to be replaced
+            customFactory.spanFormat = formatForRtlTest;
+            customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
+            customFactory.createTextLines(truncationTestCallback);
+            assertTrue("Replacing more original content than is neccessary (RTL text)", customFactory.truncatedText.length == rtlLine0TextLen + customTruncationIndicator.length && factory.isTruncated);
+            customFactory.spanFormat = null;
+
+            // Verify truncation happens at atom boundaries
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = NaN;
+            customFactory.text = accentedText;
+            customTruncationIndicator = '<' + '\u200A' /* Hair space */ + '>'; // what precedes and succeeds the hair space is irrelevant
+            customFactory.compositionBounds = bounds;
+            customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
+            customFactory.createTextLines(truncationTestCallback);
+            assertTrue("[Not a code bug] Fix test case so that truncation indicator itself fits", _lines.length == 1 && factory.isTruncated); // baseline
+
+            var initialTruncationPoint:int = customFactory.truncatedText.length - customTruncationIndicator.length;
+            assertTrue("[Not a code bug] Fix test case so that some of the original content is left behind on first truncation attempt", initialTruncationPoint > 0); // baseline
+            assertTrue("Truncation in the middle of an atom!", initialTruncationPoint % 2 == 0);
+            var nextTruncationPoint:int;
+            do
+            {
+                bounds.height = NaN;
+                customTruncationIndicator = customTruncationIndicator.replace('\u200A', '\u200A\u200A'); // add another hair space in each iteration, making truncation indicator wider (ever so slightly)
+                customFactory.compositionBounds = bounds;
+                customFactory.truncationOptions = new TruncationOptions(customTruncationIndicator, 1);
+                customFactory.createTextLines(truncationTestCallback);
+
+                nextTruncationPoint = customFactory.truncatedText.length - customTruncationIndicator.length;
+                if (nextTruncationPoint != initialTruncationPoint)
+                {
+                    assertTrue("Truncation in the middle of an atom!", nextTruncationPoint % 2 == 0);
+                    assertTrue("Sub-optimal replacement of original content?", nextTruncationPoint == initialTruncationPoint - 2);
+                    initialTruncationPoint = nextTruncationPoint;
+                }
+
+            } while (nextTruncationPoint);
+
+            // Verify scrolling behavior when truncation options are set
+            _lines.splice(0);
+            _textLen = 0; // reset
+            bounds.left = 0;
+            bounds.top = 0;
+            bounds.width = 200;
+            bounds.height = line1Extent; // should fit two lines
+            factory.compositionBounds = bounds;
+            factory.verticalScrollPolicy = "on";
+            var vaFormat:TextLayoutFormat = new TextLayoutFormat();
+            vaFormat.verticalAlign = VerticalAlign.BOTTOM;
+            factory.textFlowFormat = vaFormat;
+            factory.truncationOptions = new TruncationOptions(); // should override scroll policy
+            factory.createTextLines(truncationTestCallback);
+            assertTrue("When verticalAlign is Bottom, and scrolling is on, but truncation options are set, only text that fits should be generated",
+                    _textLen < contentTextLength && factory.isTruncated);
+        }
+
+        [Test]
+        public function CompositionCompleteEventTest():void
+        {
+            var gotEvent:Boolean = false;
+            var textFlow:TextFlow = SelManager.textFlow;
+            textFlow.addEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, completionHandler);
+            var charFormat:TextLayoutFormat = new TextLayoutFormat();
+            charFormat.fontSize = 48;
+            SelManager.selectAll();
+            (SelManager as EditManager).applyLeafFormat(charFormat);
+            assertTrue("Didn't get the CompositionCompleteEvent", gotEvent == true);
+
+            function completionHandler(event:CompositionCompleteEvent):void
+            {
+                gotEvent = true;
+                textFlow.removeEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE, completionHandler);
+            }
+        }
+
+        private function setUpMultipleLinkedContainers(numberOfContainers:int):Sprite
+        {
+            var flexContainer:Container;
+            var textFlow:TextFlow = SelManager.textFlow;
+            var flowComposer:IFlowComposer = textFlow.flowComposer;
+            var firstController:ContainerController = textFlow.flowComposer.getControllerAt(0);
+            var totalWidth:Number = firstController.compositionWidth;
+            var containerWidth:Number = totalWidth / numberOfContainers;
+            var containerHeight:Number = firstController.compositionHeight;
+            firstController.setCompositionSize(containerWidth, firstController.compositionHeight);
+            var containerParent:Sprite = firstController.container.parent as Sprite;
+            if (containerParent is Container)
+            {
+                flexContainer = Container(containerParent);
+                var newContainerParent:Sprite = new Sprite();
+                flexContainer.rawChildren.addChild(newContainerParent);
+                flexContainer.rawChildren.removeChild(firstController.container);
+                newContainerParent.addChild(firstController.container);
+                containerParent = newContainerParent;
+            }
+            var pos:int = containerWidth;
+            while (flowComposer.numControllers < numberOfContainers)
+            {
+                var s:Sprite = new Sprite();
+                s.x = pos;
+                pos += containerWidth;
+                containerParent.addChild(s);
+                flowComposer.addController(new ContainerController(s, containerWidth, containerHeight));
+            }
+            return containerParent;
+        }
+
+        private function restoreToSingleContainer(containerParent:Sprite):void
+        {
+            var flexContainer:Container = containerParent.parent as Container;
+
+            if (flexContainer)
+            {
+                flexContainer.rawChildren.removeChild(containerParent);
+                flexContainer.rawChildren.addChild(containerParent.getChildAt(0));
+            }
+            var flowComposer:IFlowComposer = SelManager.textFlow.flowComposer;
+            while (flowComposer.numControllers > 1)
+                flowComposer.removeControllerAt(flowComposer.numControllers - 1);
+        }
+
+        // Test case with multiple containers, where the last container is scrolled down, and update will cause a scroll
+        // Watson 2583969
+        [Test]
+        public function scrolledRedrawPartialCompose():void
+        {
+            var multiContainerParent:Sprite;
+
+            try
+            {
+                var textFlow:TextFlow = SelManager.textFlow;
+                var flowComposer:IFlowComposer = textFlow.flowComposer;
+                multiContainerParent = setUpMultipleLinkedContainers(5);
+
+                // Paste all the text again, so all containers are full, and there is text scrolled out
+                var textScrap:TextScrap = TextScrap.createTextScrap(new TextRange(textFlow, 0, textFlow.textLength));
+                EditManager(SelManager).pasteTextScrap(textScrap);
+                flowComposer.updateAllControllers();
+
+
+                // Set selection to the last two lines of the flow, and scroll to the new selection, and then delete the text
+                var lastController:ContainerController = flowComposer.getControllerAt(flowComposer.numControllers - 1);
+                flowComposer.composeToPosition();	// force all text to compose
+                var nextToLastLine:TextFlowLine = flowComposer.getLineAt(flowComposer.numLines - 2);
+                SelManager.selectRange(nextToLastLine.absoluteStart, textFlow.textLength);
+                lastController.scrollToRange(SelManager.absoluteStart, SelManager.absoluteEnd);
+                var firstVisibleChar:int = lastController.getFirstVisibleLine().absoluteStart; // save off the current scrolled-to text pos
+                flowComposer.updateAllControllers();
+                EditManager(SelManager).deleteText();
+
+                // The delete (and subsequent redraw) should have caused a scroll during the ContainerController updateCompositionShapes.
+                // Check that this happened correctly.
+                var firstVisibleCharAfterPaste:int = lastController.getFirstVisibleLine().absoluteStart;
+                assertTrue("Expected scroll during update", firstVisibleChar != firstVisibleCharAfterPaste);
+            }
+            finally
+            {
+                // restore how containers were set up before
+                restoreToSingleContainer(multiContainerParent);
+            }
+        }
+
+        // Test case with multiple containers, where the last container is scrolled down, and update will cause a scroll
+        // Watson 2583969
+        [Test]
+        public function multipleContainersWithPadding():void
+        {
+            var multiContainerParent:Sprite;
+
+            try
+            {
+                var textFlow:TextFlow = SelManager.textFlow;
+                var flowComposer:IFlowComposer = textFlow.flowComposer;
+                multiContainerParent = setUpMultipleLinkedContainers(2);
+
+                var firstController:ContainerController = flowComposer.getControllerAt(0);
+                var format:TextLayoutFormat = new TextLayoutFormat(firstController.format);
+                format.paddingTop = firstController.compositionHeight;
+                firstController.format = format;
+                flowComposer.updateAllControllers();
+
+                assertTrue("Expected no lines in first container", firstController.getFirstVisibleLine() == null && firstController.getLastVisibleLine() == null);
+            }
+            finally
+            {
+                // restore how containers were set up before
+                restoreToSingleContainer(multiContainerParent);
+            }
+        }
+
+        [Test]
+        [Ignore]
+        public function deleteAtContainerStart():void
+        {
+            var multiContainerParent:Sprite;
+
+            try
+            {
+                var textFlow:TextFlow = SelManager.textFlow;
+                var flowComposer:IFlowComposer = textFlow.flowComposer;
+                multiContainerParent = setUpMultipleLinkedContainers(2);
+
+                flowComposer.composeToPosition();
+                var controller:ContainerController = flowComposer.getControllerAt(0);
+
+                var lastLineIndex:int = flowComposer.findLineIndexAtPosition(controller.absoluteStart + controller.textLength);
+                var startIndex:int = flowComposer.getLineAt(lastLineIndex - numberOfLinesBack).absoluteStart;
+                SelManager.selectRange(startIndex, startIndex);
+                for (var i:int = 0; i < numberOfLinesBack + 1; ++i)
+                    SelManager.splitParagraph();
+                flowComposer.updateAllControllers();
+                var textLengthBefore:int = controller.textLength;
+
+                assertTrue("Selection should be at the start of the next container", SelManager.absoluteStart == controller.absoluteStart + controller.textLength);
+                SelManager.deletePreviousCharacter();
+                flowComposer.composeToPosition();
+                assertTrue("Expected first line of following container to be sucked in", controller.textLength > textLengthBefore);
+            }
+            finally
+            {
+                // restore how containers were set up before
+                restoreToSingleContainer(multiContainerParent);
+            }
+        }
+
+        [Test]
+        public function resizeController2644361():void
+        {
+            var textFlow:TextFlow = SelManager.textFlow;
+            var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
+
+            var scrap:TextScrap = TextScrap.createTextScrap(new TextRange(textFlow, 0, textFlow.textLength));
+            SelManager.selectRange(textFlow.textLength - 1, textFlow.textLength - 1);
+            SelManager.splitParagraph();
+            SelManager.pasteTextScrap(scrap);
+            SelManager.pasteTextScrap(scrap);
+            textFlow.flowComposer.updateAllControllers();
+            controller.setCompositionSize(825, 471)
+            SelManager.updateAllControllers();
+            controller.setCompositionSize(808, 464)
+            SelManager.updateAllControllers();
+            controller.setCompositionSize(791, 462)
+            SelManager.updateAllControllers();
+            controller.setCompositionSize(768, 461)
+            SelManager.updateAllControllers();
+        }
+
+        [Test]
+        public function resizeEmptyController():void
+        {
+            var textFlow:TextFlow = new TextFlow();
+            var p:ParagraphElement = new ParagraphElement();
+            textFlow.addChild(p);
+
+            var span:SpanElement = new SpanElement();
+            span.text = "Hello world";
+            span.fontSize = 40;
+            p.addChild(span);
+
+            var sprite1:Sprite = new Sprite();
+            var cc1:ContainerController = new ContainerController(sprite1, 100, 50);
+            sprite1.x = 100;
+            var sprite2:Sprite = new Sprite();
+            var cc2:ContainerController = new ContainerController(sprite2, 100, 50);
+            sprite2.x = 300;
+            //	addChild(sprite1);
+            //	addChild(sprite2);
+            textFlow.flowComposer.addController(cc1);
+            textFlow.flowComposer.addController(cc2);
+            textFlow.flowComposer.updateAllControllers();
+            var originalLength:int = cc1.textLength;
+            cc1.setCompositionSize(100, 10);
+            textFlow.flowComposer.updateAllControllers();
+            cc1.setCompositionSize(100, 50);
+            textFlow.flowComposer.updateAllControllers();
+            assertTrue("Expected text to recompose into controller", cc1.textLength == originalLength);
+        }
+
+        [Test]
+        public function emptyController():void
+        {
+            var s:Sprite = new Sprite();
+            var textFlow:TextFlow = new TextFlow();
+            textFlow.flowComposer.addController(new ContainerController(s, 0, 0));
+            textFlow.flowComposer.updateAllControllers();
+        }
+
+        // Check that the content bounds includes all parcels when composition starts from a column that is not the first
+        // See Watson 2769670
+        [Test]
+        public function contentBoundsOnComposeFromMiddle():void
+        {
+            TestFrame.rootElement.blockProgression = writingDirection[0];
+            TestFrame.rootElement.direction = writingDirection[1];
+
+            var textFlow:TextFlow = SelManager.textFlow;
+            var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
+            var composeSpace:Rectangle = new Rectangle(0, 0, controller.compositionWidth, controller.compositionHeight);
+
+            var lastLine:TextFlowLine = controller.getLastVisibleLine();
+            var lastVisiblePosition:int = lastLine.absoluteStart + lastLine.textLength - 1;
+            var charPos:int = lastVisiblePosition - 100;
+
+            // Trim off the unseen portion of the flow to a little before the end, so we aren't
+            // affected by content height estimation, and so we can check that height from previous
+            // columns is included.
+            SelManager.selectRange(charPos, textFlow.textLength - 1);
+            SelManager.deleteText();
+
+            // Change format to 3 columns justified text, and get the bounds. This time we composed from the start.
+            var format:TextLayoutFormat 

<TRUNCATED>