/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.layout.client;
import static com.google.gwt.dom.client.Style.Unit.CM;
import static com.google.gwt.dom.client.Style.Unit.EM;
import static com.google.gwt.dom.client.Style.Unit.EX;
import static com.google.gwt.dom.client.Style.Unit.IN;
import static com.google.gwt.dom.client.Style.Unit.MM;
import static com.google.gwt.dom.client.Style.Unit.PC;
import static com.google.gwt.dom.client.Style.Unit.PCT;
import static com.google.gwt.dom.client.Style.Unit.PT;
import static com.google.gwt.dom.client.Style.Unit.PX;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.layout.client.Layout.Alignment;
import com.google.gwt.layout.client.Layout.Layer;
import com.google.gwt.user.client.ResizeHelper;
import com.google.gwt.user.client.Window;
/**
* Tests for the {@link Layout} class.
*/
public class LayoutTest extends GWTTestCase {
/**
* The amount of time to wait for asynchronous tests to finish.
*/
private static final int TEST_DELAY = 2000;
private static interface LayerInitializer {
void setupLayers(Layer l0, Layer l1);
}
private DivElement parent, child0, child1;
private Element wrapper0, wrapper1;
private Layout layout;
private Layer layer0, layer1;
@Override
public String getModuleName() {
return "com.google.gwt.layout.LayoutTest";
}
// All testAnimationTransitions_* tests are disabled because they are flaky
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_LTRB_PX_CM() {
testAnimationTransitions_LTWH_LTRB(PX, CM);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_LTRB_PX_EM() {
testAnimationTransitions_LTWH_LTRB(PX, EM);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_LTRB_PX_EX() {
testAnimationTransitions_LTWH_LTRB(PX, EX);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_LTRB_PX_PCT() {
testAnimationTransitions_LTWH_LTRB(PX, PCT);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_RBWH_PX_CM() {
testAnimationTransitions_LTWH_RBWH(PX, CM);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_RBWH_PX_EM() {
testAnimationTransitions_LTWH_RBWH(PX, EM);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_RBWH_PX_EX() {
testAnimationTransitions_LTWH_RBWH(PX, EX);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_LTWH_RBWH_PX_PCT() {
testAnimationTransitions_LTWH_RBWH(PX, PCT);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_RBWH_LTRB_PX_CM() {
testAnimationTransitions_RBWH_LTRB(PX, CM);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_RBWH_LTRB_PX_EM() {
testAnimationTransitions_RBWH_LTRB(PX, EM);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_RBWH_LTRB_PX_EX() {
testAnimationTransitions_RBWH_LTRB(PX, EX);
}
/**
* Tests animation constraint- and unit-transitions.
*/
public void notestAnimationTransitions_RBWH_LTRB_PX_PCT() {
testAnimationTransitions_RBWH_LTRB(PX, PCT);
}
/**
* Tests child alignment within a layer.
*/
@DoNotRunWith(Platform.HtmlUnitLayout)
public void testChildAlignment() {
layer0.setLeftWidth(0, PX, 128, PX);
layer0.setTopHeight(0, PX, 256, PX);
layer0.setChildHorizontalPosition(Alignment.STRETCH);
layer0.setChildVerticalPosition(Alignment.STRETCH);
layout.layout();
assertEquals(0, child0.getOffsetLeft());
assertEquals(0, child0.getOffsetTop());
assertEquals(128, child0.getOffsetWidth());
assertEquals(256, child0.getOffsetHeight());
child0.getStyle().setWidth(64, PX);
child0.getStyle().setHeight(128, PX);
layer0.setChildHorizontalPosition(Alignment.BEGIN);
layer0.setChildVerticalPosition(Alignment.BEGIN);
layout.layout();
assertEquals(0, child0.getOffsetLeft());
assertEquals(0, child0.getOffsetTop());
assertEquals(64, child0.getOffsetWidth());
assertEquals(128, child0.getOffsetHeight());
layer0.setChildHorizontalPosition(Alignment.END);
layer0.setChildVerticalPosition(Alignment.END);
layout.layout();
assertEquals(64, child0.getOffsetLeft());
assertEquals(128, child0.getOffsetTop());
assertEquals(64, child0.getOffsetWidth());
assertEquals(128, child0.getOffsetHeight());
}
/**
* Test that fillParent() works properly when the outer div is a child of
* another div, and that it correctly follows that div's size.
*/
@DoNotRunWith(Platform.HtmlUnitLayout)
public void testFillParent() {
// We don't use the default elements created in gwtSetUp() because we need
// to test the behavior when the layout is contained by an element other
// than the <body>.
Document doc = Document.get();
DivElement container = doc.createDivElement();
DivElement parent = doc.createDivElement();
DivElement child = doc.createDivElement();
child.setInnerHTML(" ");
doc.getBody().appendChild(container);
container.appendChild(parent);
// The container has to be position:relative so that it serves as an offset
// parent.
container.getStyle().setPosition(Position.RELATIVE);
container.getStyle().setWidth(128, PX);
container.getStyle().setHeight(256, PX);
Layout layout = new Layout(parent);
layout.onAttach();
Layer layer = layout.attachChild(child);
layer.setTopBottom(0, PX, 0, PX);
layer.setLeftRight(0, PX, 0, PX);
layout.fillParent();
layout.layout();
// Test 128x256.
assertEquals(128, container.getOffsetWidth());
assertEquals(256, container.getOffsetHeight());
assertEquals(128, parent.getOffsetWidth());
assertEquals(256, parent.getOffsetHeight());
assertEquals(128, child.getOffsetWidth());
assertEquals(256, child.getOffsetHeight());
// Expand to 256x256. The layout should react automatically.
container.getStyle().setWidth(256, PX);
container.getStyle().setHeight(128, PX);
assertEquals(256, container.getOffsetWidth());
assertEquals(256, parent.getOffsetWidth());
assertEquals(256, child.getOffsetWidth());
layout.onDetach();
}
/**
* Test that fillParent() works properly when the outer div is a child of the
* document body.
*/
@DoNotRunWith(Platform.HtmlUnitLayout)
public void testFillWindow() {
layer0.setTopBottom(0, PX, 0, PX);
layer0.setLeftRight(0, PX, 0, PX);
layout.layout();
int w = Window.getClientWidth();
int h = Window.getClientHeight();
assertEquals(w, parent.getOffsetWidth());
assertEquals(h, parent.getOffsetHeight());
assertEquals(w, child0.getOffsetWidth());
assertEquals(h, child0.getOffsetHeight());
}
/**
* Tests that the layout reacts to font-size changes.
*
* TODO(jgw): Enable this test when it is fixed for IE8.
*/
public void disabledTestFontSizeChange() {
layer0.setLeftWidth(0, PX, 1, EM);
layer0.setTopHeight(0, PX, 1, EM);
layout.layout();
parent.getStyle().setFontSize(12, PT);
int cw = child0.getOffsetWidth();
int ch = child0.getOffsetHeight();
parent.getStyle().setFontSize(24, PT);
int nw = child0.getOffsetWidth();
int nh = child0.getOffsetHeight();
assertTrue(nw > cw);
assertTrue(nh > ch);
parent.getStyle().clearFontSize();
}
/**
* Ensures that two children laid out using various units in such a way that
* they should abut one another actually do so.
*/
public void testLayoutStructure() {
testHorizontalSplit(CM);
testHorizontalSplit(EM);
testHorizontalSplit(EX);
testHorizontalSplit(IN);
testHorizontalSplit(MM);
testHorizontalSplit(PC);
testHorizontalSplit(PT);
testHorizontalSplit(PX);
testVerticalSplit(CM);
testVerticalSplit(EM);
testVerticalSplit(EX);
testVerticalSplit(IN);
testVerticalSplit(MM);
testVerticalSplit(PC);
testVerticalSplit(PT);
testVerticalSplit(PX);
}
/**
* Tests (left-right, left-width, right-width) x (top-bottom, top-height,
* bottom-height). Ok, so we don't test the *entire* cross-product, but enough
* to be comfortable.
*/
public void testStaticConstraints() {
// This test assumes enough size. Ignore it if size cannot be guaranteed.
if (!ResizeHelper.isResizeSupported()) {
return;
}
// left-right, top-bottom
layer0.setTopBottom(32, PX, 32, PX);
layer0.setLeftRight(32, PX, 32, PX);
layout.layout();
int w = parent.getClientWidth();
int h = parent.getClientHeight();
assertEquals(32, wrapper0.getOffsetLeft());
assertEquals(32, wrapper0.getOffsetTop());
assertEquals(w - 64, wrapper0.getOffsetWidth());
assertEquals(h - 64, wrapper0.getOffsetHeight());
// left-width, top-height
layer0.setTopHeight(16, PX, 128, PX);
layer0.setLeftWidth(16, PX, 128, PX);
layout.layout();
assertEquals(16, wrapper0.getOffsetLeft());
assertEquals(16, wrapper0.getOffsetTop());
assertEquals(128, wrapper0.getOffsetWidth());
assertEquals(128, wrapper0.getOffsetHeight());
// right-width, bottom-height
layer0.setBottomHeight(16, PX, 128, PX);
layer0.setRightWidth(16, PX, 128, PX);
layout.layout();
assertEquals(w - (16 + 128), wrapper0.getOffsetLeft());
assertEquals(h - (16 + 128), wrapper0.getOffsetTop());
assertEquals(128, wrapper0.getOffsetWidth());
assertEquals(128, wrapper0.getOffsetHeight());
}
/**
* Tests all unit types.
*/
public void testUnits() {
// This test assumes enough size. Ignore it if size cannot be guaranteed.
if (!ResizeHelper.isResizeSupported()) {
return;
}
// CM
layer0.setTopBottom(1, CM, 1, CM);
layer0.setLeftRight(1, CM, 1, CM);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// MM
layer0.setTopBottom(1, MM, 1, MM);
layer0.setLeftRight(1, MM, 1, MM);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// IN
layer0.setTopBottom(1, IN, 1, IN);
layer0.setLeftRight(1, IN, 1, IN);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// EM
layer0.setTopBottom(1, EM, 1, EM);
layer0.setLeftRight(1, EM, 1, EM);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// EX
layer0.setTopBottom(1, EX, 1, EX);
layer0.setLeftRight(1, EX, 1, EX);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// PC
layer0.setTopBottom(1, PC, 1, PC);
layer0.setLeftRight(1, PC, 1, PC);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// PT
layer0.setTopBottom(10, PT, 10, PT);
layer0.setLeftRight(10, PT, 10, PT);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
// PCT
layer0.setTopBottom(10, PCT, 10, PCT);
layer0.setLeftRight(10, PCT, 10, PCT);
layout.layout();
assertLeftRightTopBottomUnitsMakeSense(wrapper0);
}
/**
* Tests layout in the presence of decorations on the parent and child
* elements.
*/
@DoNotRunWith(Platform.HtmlUnitLayout)
public void testWithDecorations() {
layer0.setTopBottom(0, PX, 0, PX);
layer0.setLeftRight(0, PX, 0, PX);
layout.layout();
// Give each of the parent and child 1px margin and 1px border.
parent.getStyle().setMargin(1, PX);
parent.getStyle().setProperty("border", "1px solid black");
child0.getStyle().setMargin(1, PX);
child0.getStyle().setProperty("border", "1px solid black");
layout.layout();
int w = Window.getClientWidth();
int h = Window.getClientHeight();
int pw = parent.getOffsetWidth();
int ph = parent.getOffsetHeight();
// The parent's offsetSize should be 2px smaller than the window's client
// area, because of the margin (the border is *included* in the offsetSize).
assertEquals(w - 2, pw);
assertEquals(h - 2, ph);
// The child's offsetSize (actually that of its wrapper element), should be
// 2px smaller than the parent, for precisely the same reason.
assertEquals(pw - 2, wrapper0.getOffsetWidth());
assertEquals(ph - 2, wrapper0.getOffsetHeight());
}
@Override
protected void gwtSetUp() throws Exception {
// ensure enough sizes for this test
ResizeHelper.resizeTo(800, 600);
Window.enableScrolling(false);
Document doc = Document.get();
parent = doc.createDivElement();
child0 = doc.createDivElement();
child1 = doc.createDivElement();
doc.getBody().appendChild(parent);
layout = new Layout(parent);
layout.onAttach();
layout.fillParent();
layer0 = layout.attachChild(child0);
layer1 = layout.attachChild(child1);
wrapper0 = child0.getParentElement();
wrapper1 = child1.getParentElement();
}
@Override
protected void gwtTearDown() throws Exception {
Window.enableScrolling(true);
Document.get().getBody().removeChild(parent);
layout.onDetach();
}
private void assertLeftRightTopBottomUnitsMakeSense(Element elem) {
// Assume that the element has been laid out to (l, t, r, b) = (1u, 1u, 1u,
// 1u). Assert that the element's clientLeft/Top are non-zero, and that the
// clientLeft/Top/Width/Height are consistent with the parent's size.
int w = parent.getClientWidth();
int h = parent.getClientHeight();
int cl = elem.getOffsetLeft();
int ct = elem.getOffsetTop();
int cw = elem.getOffsetWidth();
int ch = elem.getOffsetHeight();
// Assert that the left-top unit came out at least non-zero size.
assertTrue(cl > 0);
assertTrue(ct > 0);
// Assert that the right-bottom also came out non-zero. We should be able
// to assert that it came out the same size as the top-left, but it turns
// out that this isn't quite reliable because of rounding errors.
assertTrue(w - (cl + cw) > 0);
assertTrue(h - (ct + ch) > 0);
}
// This method may only be called once per test, as it uses delayTestFinish()
// internally.
private void testAnimationTransitions_LTWH_LTRB(final Unit unit0,
final Unit unit1) {
testAnimationTransitionsHelper(new LayerInitializer() {
@Override
public void setupLayers(Layer l0, Layer l1) {
l0.setLeftWidth(0, unit0, 10, unit0);
l0.setTopHeight(0, unit0, 10, unit0);
l1.setLeftRight(1, unit1, 1, unit1);
l1.setTopBottom(1, unit1, 1, unit1);
}
}, new LayerInitializer() {
@Override
public void setupLayers(Layer l0, Layer l1) {
l1.setLeftWidth(0, unit0, 10, unit0);
l1.setTopHeight(0, unit0, 10, unit0);
l0.setLeftRight(1, unit1, 1, unit1);
l0.setTopBottom(1, unit1, 1, unit1);
}
});
}
// This method may only be called once per test, as it uses delayTestFinish()
// internally.
private void testAnimationTransitions_LTWH_RBWH(final Unit unit0,
final Unit unit1) {
testAnimationTransitionsHelper(new LayerInitializer() {
@Override
public void setupLayers(Layer l0, Layer l1) {
l0.setLeftWidth(0, unit0, 10, unit0);
l0.setTopHeight(0, unit0, 10, unit0);
l1.setRightWidth(0, unit1, 10, unit1);
l1.setBottomHeight(0, unit1, 10, unit1);
}
}, new LayerInitializer() {
@Override
public void setupLayers(Layer l0, Layer l1) {
l1.setLeftWidth(0, unit0, 10, unit0);
l1.setTopHeight(0, unit0, 10, unit0);
l0.setRightWidth(0, unit1, 10, unit1);
l0.setBottomHeight(0, unit1, 10, unit1);
}
});
}
// This method may only be called once per test, as it uses delayTestFinish()
// internally.
private void testAnimationTransitions_RBWH_LTRB(final Unit unit0,
final Unit unit1) {
testAnimationTransitionsHelper(new LayerInitializer() {
@Override
public void setupLayers(Layer l0, Layer l1) {
l0.setRightWidth(0, unit0, 10, unit0);
l0.setBottomHeight(0, unit0, 10, unit0);
l1.setLeftRight(1, unit1, 1, unit1);
l1.setTopBottom(1, unit1, 1, unit1);
}
}, new LayerInitializer() {
@Override
public void setupLayers(Layer l0, Layer l1) {
l1.setRightWidth(0, unit0, 10, unit0);
l1.setBottomHeight(0, unit0, 10, unit0);
l0.setLeftRight(1, unit1, 1, unit1);
l0.setTopBottom(1, unit1, 1, unit1);
}
});
}
// This method may only be called once per test, as it uses delayTestFinish()
// internally.
private void testAnimationTransitionsHelper(LayerInitializer before,
LayerInitializer after) {
before.setupLayers(layer0, layer1);
layout.layout();
final int l0 = wrapper0.getOffsetLeft();
final int t0 = wrapper0.getOffsetTop();
final int w0 = wrapper0.getOffsetWidth();
final int h0 = wrapper0.getOffsetHeight();
final int l1 = wrapper1.getOffsetLeft();
final int t1 = wrapper1.getOffsetTop();
final int w1 = wrapper1.getOffsetWidth();
final int h1 = wrapper1.getOffsetHeight();
after.setupLayers(layer0, layer1);
delayTestFinish(TEST_DELAY);
layout.layout(100, new Layout.AnimationCallback() {
@Override
public void onAnimationComplete() {
// Assert that the two layers have swapped positions.
assertEquals(l0, wrapper1.getOffsetLeft());
assertEquals(t0, wrapper1.getOffsetTop());
assertEquals(w0, wrapper1.getOffsetWidth());
assertEquals(h0, wrapper1.getOffsetHeight());
assertEquals(l1, wrapper0.getOffsetLeft());
assertEquals(t1, wrapper0.getOffsetTop());
assertEquals(w1, wrapper0.getOffsetWidth());
assertEquals(h1, wrapper0.getOffsetHeight());
finishTest();
}
@Override
public void onLayout(Layer layer, double progress) {
}
});
}
private void testHorizontalSplit(Unit unit) {
// Line them up horizontally, split at 5 units.
layer0.setTopBottom(0, PX, 0, PX);
layer0.setLeftWidth(0, PX, 5, unit);
layer1.setTopBottom(0, PX, 0, PX);
layer1.setLeftRight(5, unit, 0, PX);
layout.layout();
int child0Right = wrapper0.getOffsetWidth();
int child1Left = wrapper1.getOffsetLeft();
assertEquals(child0Right, child1Left);
}
private void testVerticalSplit(Unit unit) {
// Line them up vertically, split at 5em.
layer0.setTopHeight(0, PX, 5, unit);
layer0.setLeftRight(0, PX, 0, PX);
layer1.setTopBottom(5, unit, 0, PX);
layer1.setLeftRight(0, PX, 0, PX);
layout.layout();
int child0Bottom = wrapper0.getOffsetHeight();
int child1Top = wrapper1.getOffsetTop();
assertEquals(child0Bottom, child1Top);
}
}