| package spark.components.gridClasses { |
| import flash.geom.Matrix; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| |
| import mx.collections.ArrayCollection; |
| |
| import org.flexunit.assertThat; |
| |
| import org.flexunit.asserts.assertEquals; |
| import org.flexunit.runners.Parameterized; |
| import org.fluint.uiImpersonation.UIImpersonator; |
| |
| import spark.components.DataGrid; |
| import spark.components.GridColumnHeaderGroup; |
| |
| /* |
| Example for two-column table (with no horizontal scroll): |
| |
| [pl]: GridColumnHeaderGroup padding left |
| [lch]: still part of last column header, but beyond last column width |
| c0: first column header starts here |
| c1: first column header ends here, second column header (if it exists) starts, and column separator is here |
| c2: second column header ends here; second column separator is here |
| ... |
| cx: last column header ends here; last column separator is here |
| |
| d0: first column ends at this x-axis coordinate (but the first header doesn't, due to [pl]) |
| d1: second column ends at this x-axis coordinate |
| ... |
| dx: last column ends at this x-axis coordinate |
| |
| e: table ends at this x-axis coordinate |
| |
| f0: header ends and grid starts at this y-coordinate |
| f1: first column ends here and second column (if it exists) begins here |
| f2: second column ends here and third column (if it exists) begins here |
| ... |
| fx: last column ends here |
| |
| g0: bottom-left point of first column header |
| g1: bottom-right point of first column header |
| g2: bottom-right point of second column header |
| ... |
| gx: bottom-right point of (last column header - [lch]) |
| |
| h: bottom-right point of last column header and x-coordinate at end of data grid |
| |
| And for each point we generate the 8 adjacent points: |
| (x+1, y), (x+1, y+1), (x+1, y-1), |
| (x-1, y), (x-1, y+1), (x-1, y-1), |
| (x, y-1), (x, y-1). For easier comprehension we mark them |
| using cardinal points: N, NE, E, SE, S, SW, W, NW. |
| ...and we check various boundaries against all of them |
| |
| a (0, 0) |
| ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
| ░░░░░b════c0══════d0═══c1════════════════════════════d1══c2═════════e░░░░░░░ |
| ░░░░░║▓▓▓▓║▓▓▓▓▓▓▓▓▓▓▓▓║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓║▓▓▓▓▓▓▓▓▓▓║░░░░░░░ |
| ░░░░░║[pl]║▓▓▓INDEX▓▓▓▓║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓NAME▓▓▓▓▓▓▓▓▓▓▓▓▓║▓▓▓[lch]▓▓║░░░░░░░ |
| ░░░░░║▓▓▓▓║▓▓▓▓▓▓▓▓▓▓▓▓║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓║▓▓▓▓▓▓▓▓▓▓║░░░░░░░ |
| ░░░░░f0═══g0══════f1═══g1════════════════════════════f2══g2═════════h░░░░░░░ |
| ░░░░░║ 01 ║ John ║ ║░░░░░░░ |
| ░░░░░║ 02 ║ Jane ║ ║░░░░░░░ |
| ░░░░░║ 03 ║ Judy ║ ║░░░░░░░ |
| ░░░░░║ 04 ║ James ║ ║░░░░░░░ |
| ░░░░░╚════════════╩══════════════════════════════════╩══════════════╝░░░░░░░ |
| ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
| ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
| */ |
| |
| [RunWith("org.flexunit.runners.Parameterized")] |
| public class GridHeaderViewLayout_FLEX_35260_Tests |
| { |
| private static var foo:Parameterized; |
| private static const N:Matrix = new Matrix(1, 0, 0, 1, 0, -1); |
| private static const NE:Matrix = new Matrix(1, 0, 0, 1, 1, -1); |
| private static const E:Matrix = new Matrix(1, 0, 0, 1, 1, 0); |
| private static const SE:Matrix = new Matrix(1, 0, 0, 1, 1, 1); |
| private static const S:Matrix = new Matrix(1, 0, 0, 1, 0, 1); |
| private static const SW:Matrix = new Matrix(1, 0, 0, 1, -1, 1); |
| private static const W:Matrix = new Matrix(1, 0, 0, 1, -1, 0); |
| private static const NW:Matrix = new Matrix(1, 0, 0, 1, -1, -1); |
| private static const ITSELF:Matrix = new Matrix(1, 0, 0, 1, 0, 0); //the point, unmodified |
| private static const directions:Array = [ITSELF, N, NE, E, SE, S, SW, W, NW]; |
| |
| private static var _dataGrid:DataGrid; |
| |
| private var _keyRectangles:Array; |
| private var _keyPoints:Array; |
| |
| //@TODO add cases with horizontal scroll, and also with fixed columns |
| public static var dimensions:Array = [/*x, y, width, header padding left, header padding top, [column widths] */ |
| [[10, 0, 300, 5, 0, [25, 150]]], |
| [[0, 0, 300, 5, 0, [25, 150]]] |
| ]; |
| |
| |
| [BeforeClass] |
| public static function setUpBeforeClass():void |
| { |
| _dataGrid = new DataGrid(); |
| _dataGrid.setStyle("borderVisible", false); //to not deal with the complications of Scroller.minViewportInset |
| UIImpersonator.addChild(_dataGrid); |
| } |
| |
| [AfterClass] |
| public static function tearDownAfterClass():void |
| { |
| UIImpersonator.removeAllChildren(); |
| |
| _dataGrid = null; |
| } |
| |
| [Before] |
| public function setUp():void |
| { |
| |
| } |
| |
| [After] |
| public function tearDown():void |
| { |
| _keyRectangles = null; |
| _keyPoints = null; |
| } |
| |
| /* |
| [Ignore] |
| [Test] |
| public function test_column_index(globalPoint:Array, expectedColumnIndex:int):void |
| { |
| //given |
| var localPoint:Point = _dataGrid.grid.globalToLocal(new Point(globalPoint[0], globalPoint[1])); |
| |
| //when |
| var columnIndex:int = _dataGrid.grid.getColumnIndexAt(localPoint.x, 0); |
| |
| //then |
| assertEquals(expectedColumnIndex, columnIndex); |
| } |
| |
| [Ignore] |
| [Test] |
| public function test_header_contains_global_coordinates(globalPoint:Array, headerShouldContainThisPoint:Boolean):void |
| { |
| //when |
| var doesHeaderContainThisPoint:Boolean = _sut.containsGlobalCoordinates(new Point(globalPoint[0], globalPoint[1])); |
| |
| //then |
| assertEquals(headerShouldContainThisPoint, doesHeaderContainThisPoint); |
| }*/ |
| |
| [Test(dataProvider="dimensions")] |
| public function test_with_no_scroll(dimensions:Array):void |
| { |
| //given |
| _dataGrid.width = getWidth(dimensions); |
| _dataGrid.x = getX(dimensions); |
| _dataGrid.y = getY(dimensions); |
| |
| _dataGrid.columnHeaderGroup.setStyle("paddingLeft", getHeaderPaddingLeft(dimensions)); |
| _dataGrid.columnHeaderGroup.setStyle("paddingTop", getHeaderPaddingTop(dimensions)); |
| |
| var gridColumns:Array = []; |
| for (var i:int = 0; i < getColumnWidths(dimensions).length; i++) |
| { |
| var column:GridColumn = new GridColumn(); |
| column.width = getColumnWidth(dimensions, i); |
| gridColumns.push(column); |
| } |
| _dataGrid.columns = new ArrayCollection(gridColumns); |
| |
| _dataGrid.validateNow(); |
| |
| _keyPoints = generateKeyPoints(dimensions, _dataGrid); |
| _keyRectangles = generateKeyRectangles(_keyPoints, dimensions); |
| |
| //then |
| //first, make sure that the dataGrid was rendered correctly |
| assertThat("The dataGrid has not yet been correctly rendered on stage", getActualHeaderHeight(_dataGrid) > 0); |
| |
| for (var pointName:String in _keyPoints) |
| { |
| for (var i:int = 0; i < directions.length; i++) |
| { |
| //given |
| var pointToTest:Point = getAdjacentPoint(_keyPoints[pointName], directions[i]); |
| |
| //when |
| var expectedHeaderIndex:int = getHeaderIndexAssumption(pointToTest); |
| var actualHeaderIndex:int = getHeaderIndexAtGlobalPoint(pointToTest); |
| const errorMessageHeaderIndex:String = getHeaderIndexErrorMessage(pointName, directions[i], pointToTest, expectedHeaderIndex, actualHeaderIndex); |
| |
| var shouldBeContainedInHeader:Boolean = getHeaderContainsPointAssumption(pointToTest); |
| var actuallyContainedInHeader:Boolean = _sut.areCoordinatesOverAHeaderView(pointToTest); |
| const errorMessageHeaderContainsPoint:String = getHeaderContainsPointErrorMessage(pointName, directions[i], pointToTest, shouldBeContainedInHeader, actuallyContainedInHeader); |
| |
| //then |
| assertEquals(errorMessageHeaderIndex, expectedHeaderIndex, actualHeaderIndex); |
| assertEquals(errorMessageHeaderContainsPoint, shouldBeContainedInHeader, actuallyContainedInHeader); |
| } |
| } |
| } |
| |
| private function getHeaderIndexErrorMessage(pointName:String, direction:Matrix, transformedPoint:Point, expectedHeaderIndex:int, actualHeaderIndex:int):String |
| { |
| return "The point " + pointName + " transformed with Matrix " + direction + " (resulting in " + transformedPoint + ") should be " |
| + (expectedHeaderIndex == -1 ? "outside any header bounds" : "inside the header with index " + expectedHeaderIndex) |
| + " but was mistakenly found to be " |
| + (actualHeaderIndex == -1 ? "outside any header bounds" : "inside the header with index " + actualHeaderIndex |
| + "\n DEBUG INFO: headerRectangles=" + headerRectangles); |
| } |
| |
| private function getHeaderContainsPointErrorMessage(pointName:String, direction:Matrix, transformedPoint:Point, shouldBeContainedInHeader:Boolean, isActuallyContainedInHeader:Boolean):String |
| { |
| return "The point " + pointName + " transformed with Matrix " + direction + " (resulting in " + transformedPoint + ") should be " |
| + (shouldBeContainedInHeader ? "within " : "outside ") + "header bounds" |
| + " but was mistakenly found to be " |
| + (isActuallyContainedInHeader ? "within" : "outside") |
| + "\n DEBUG INFO: headerRectangle=" + headerRectangle; |
| } |
| |
| private function getHeaderIndexAtGlobalPoint(globalPoint:Point):int |
| { |
| var localPoint:Point = _sut.globalToLocal(globalPoint); |
| return _sut.getHeaderIndexAt(localPoint.x, localPoint.y); |
| } |
| |
| private function getHeaderContainsPointAssumption(point:Point):Boolean |
| { |
| return rectangleContainsPoint(headerRectangle, point); |
| } |
| |
| private function getHeaderIndexAssumption(point:Point):int |
| { |
| return getIndexOfHeaderRectangleWhichContainsPoint(point, headerRectangles); |
| } |
| |
| private function getAdjacentPoint(point:Point, direction:Matrix):Point |
| { |
| return direction.transformPoint(point); |
| } |
| |
| private function getIndexOfHeaderRectangleWhichContainsPoint(point:Point, rectangles:Array):int |
| { |
| for (var i:int = 0; i < rectangles.length; i++) |
| { |
| if(rectangleContainsPoint(rectangles[i], point)) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| private function rectangleContainsPoint(rectangle:Rectangle, point:Point):Boolean |
| { |
| return rectangle.containsPoint(point); |
| } |
| |
| private function generateKeyPoints(dimensions:Array, grid:DataGrid):Array |
| { |
| var keyPoints:Array = []; |
| //TODO this code does not yet account for horizontal scrolling! |
| |
| keyPoints["a"] = new Point(0, 0); |
| keyPoints["b"] = new Point(getX(dimensions), getY(dimensions)); |
| keyPoints["c0"] = new Point(getX(dimensions) + getHeaderPaddingLeft(dimensions), getY(dimensions)); |
| generateColumnIntermediates(keyPoints, dimensions, "c0"); |
| keyPoints["d0"] = new Point(getX(dimensions) + getColumnWidths(dimensions)[0], getY(dimensions)); |
| generateColumnIntermediates(keyPoints, dimensions, "d0"); |
| keyPoints["e"] = new Point(getX(dimensions) + getWidth(dimensions), getY(dimensions)); |
| |
| const yUnderHeader:Number = getY(dimensions) + getActualHeaderHeight(grid); |
| keyPoints["f0"] = new Point(getX(dimensions), yUnderHeader); |
| generateColumnIntermediates(keyPoints, dimensions, "f0"); |
| keyPoints["g0"] = new Point(getX(dimensions) + getHeaderPaddingLeft(dimensions), yUnderHeader); |
| generateColumnIntermediates(keyPoints, dimensions, "g0"); |
| keyPoints["h"] = new Point(getX(dimensions) + getWidth(dimensions), yUnderHeader); |
| |
| return keyPoints; |
| } |
| |
| private function generateColumnIntermediates(keyPoints:Array, dimensions:Array, initialPointName:String):void |
| { |
| const initialPoint:Point = keyPoints[initialPointName]; |
| const y:Number = initialPoint.y; |
| var currentX:Number = initialPoint.x; |
| const initialPointFirstCharacter:String = initialPointName.charAt(0); |
| for (var i:int = 0; i < getColumnWidths(dimensions).length; i++) |
| { |
| currentX += getColumnWidth(dimensions, i); |
| keyPoints[initialPointFirstCharacter + (i+1)] = new Point(currentX, y); |
| } |
| } |
| |
| private function generateKeyRectangles(keyPoints:Array, dimensions:Array):Array |
| { |
| var keyRectangles:Array = []; |
| |
| keyRectangles["headerRectangles"] = generateHeaderRectangles(keyPoints, dimensions); |
| keyRectangles["headerRectangle"] = generateVisibleHeaderRectangle(keyPoints, dimensions); |
| |
| return keyRectangles; |
| } |
| |
| private function generateVisibleHeaderRectangle(keyPoints:Array, dimensions:Array):Rectangle |
| { |
| const topLeftCorner:Point = keyPoints["b"]; |
| return new Rectangle(topLeftCorner.x, topLeftCorner.y, getHeaderWidthFromKeyPoints(keyPoints), getHeaderHeightFromKeyPoints(keyPoints)); |
| } |
| private function generateHeaderRectangles(keyPoints:Array, dimensions:Array):Array |
| { |
| var headerRectangles:Array = []; |
| |
| const headerHeight:Number = getHeaderHeightFromKeyPoints(keyPoints); |
| for (var i:int = 0; i < getColumnWidths(dimensions).length; i++) |
| { |
| var topLeft:Point = keyPoints["c" + i]; |
| var topRight:Point = keyPoints["c" + (i+1)]; |
| headerRectangles.push(new Rectangle(topLeft.x, topLeft.y, topRight.x - topLeft.x, headerHeight)); |
| } |
| |
| //correct last header rectangle to extend to grid boundaries. This is one of the issues which prompted |
| //this unit test in the first place. |
| var lastHeaderRectangle:Rectangle = headerRectangles[headerRectangles.length - 1]; |
| lastHeaderRectangle.width = Point(keyPoints["e"]).x - lastHeaderRectangle.x; |
| |
| return headerRectangles; |
| } |
| |
| private function get headerRectangles():Array |
| { |
| return _keyRectangles["headerRectangles"]; |
| } |
| |
| private function get headerRectangle():Rectangle |
| { |
| return _keyRectangles["headerRectangle"] as Rectangle; |
| } |
| |
| private function getColumnWidthFromKeyPoints(keyPoints:Array, columnIndex:int):Number |
| { |
| //we're assuming columnIndex has a valid value |
| return Point(keyPoints["c" + (columnIndex + 1)]).x - Point(keyPoints["c" + columnIndex]).x; |
| } |
| |
| private function getX(dimensions:Array):Number |
| { |
| return dimensions[0]; |
| } |
| |
| private function getY(dimensions:Array):Number |
| { |
| return dimensions[1]; |
| } |
| |
| private function getWidth(dimensions:Array):Number |
| { |
| return dimensions[2]; |
| } |
| |
| private function getHeaderPaddingLeft(dimensions:Array):Number |
| { |
| return dimensions[3]; |
| } |
| |
| private function getHeaderPaddingTop(dimensions:Array):Number |
| { |
| return dimensions[4]; |
| } |
| |
| private function getActualHeaderHeight(grid:DataGrid):Number |
| { |
| //Note that we're assuming the grid is on stage and validated by this point! |
| return grid.columnHeaderGroup.height; |
| } |
| |
| private function getHeaderHeightFromKeyPoints(keyPoints:Array):Number |
| { |
| return Point(keyPoints["h"]).y - Point(keyPoints["e"]).y; |
| } |
| |
| private function getHeaderWidthFromKeyPoints(keyPoints:Array):Number |
| { |
| return Point(keyPoints["e"]).x - Point(keyPoints["b"]).x; |
| } |
| |
| private function getColumnWidths(dimensions:Array):Array |
| { |
| return dimensions[5]; |
| } |
| |
| private function getColumnWidth(dimensions:Array, columnIndex:int):Number |
| { |
| return getColumnWidths(dimensions)[columnIndex]; |
| } |
| |
| private function getTotalColumnWidths(dimensions:Array):Number |
| { |
| var sum:Number = 0; |
| getColumnWidths(dimensions).forEach(function (item:*, index:int, arr:Array):void {sum += item;}); |
| return sum; |
| } |
| |
| |
| private function get _sut():GridColumnHeaderGroup |
| { |
| return _dataGrid.columnHeaderGroup; |
| } |
| } |
| } |