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>