/* * Copyright 2010 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.user.client.ui; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.dom.client.BodyElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.junit.DoNotRunWith; import com.google.gwt.junit.Platform; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DeferredCommand; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import java.util.ArrayList; import java.util.List; /** * Tests standard DOM operations in the {@link DOM} class. */ public class DOMTest extends GWTTestCase { public static void assertEndsWith(String ending, String testStr) { if (ending != testStr && (testStr == null || !testStr.endsWith(ending))) { fail("expected ending=" + ending + " actual=" + testStr); } } /** * Helper method to return the denormalized child count of a DOM Element. For * example, child nodes which have a nodeType of Text are included in the * count, whereas <code>DOM.getChildCount(Element parent)</code> only counts * the child nodes which have a nodeType of Element. * * @param elem the DOM element to check the child count for * @return The number of child nodes */ public static native int getDenormalizedChildCount(Element elem) /*-{ return (elem.childNodes.length); }-*/; @Override public String getModuleName() { return "com.google.gwt.user.UserTest"; } /** * Test DOM.get/set/removeElementAttribute() methods. */ public void testElementAttribute() { Element div = DOM.createDiv(); DOM.setElementAttribute(div, "class", "testClass"); String cssClass = DOM.getElementAttribute(div, "class"); assertEquals("testClass", cssClass); DOM.removeElementAttribute(div, "class"); cssClass = DOM.getElementAttribute(div, "class"); assertEquals("", cssClass); } /** * Tests that {@link DOM#eventGetCurrentEvent()} returns the event to the * {@link UncaughtExceptionHandler}. */ public void testEventGetCurrentEventOnException() { Button button = new Button("test", new ClickHandler() { public void onClick(ClickEvent event) { // Intentionally trigger an error throw new IllegalArgumentException(); } }); RootPanel.get().add(button); // Verify the exception is captured final List<String> ret = new ArrayList<String>(); GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { public void onUncaughtException(Throwable e) { Event event = DOM.eventGetCurrentEvent(); if (event == null) { ret.add("Event is null"); return; } if (event.getTypeInt() != Event.ONCLICK) { ret.add("Event is not a click event"); return; } ret.add("Success"); } }); NativeEvent clickEvent = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false); button.getElement().dispatchEvent(clickEvent); assertEquals(1, ret.size()); assertEquals("Success", ret.get(0)); RootPanel.get().remove(button); GWT.setUncaughtExceptionHandler(null); } /** * Tests {@link DOM#getAbsoluteLeft(Element)} and * {@link DOM#getAbsoluteTop(Element)}. */ public void testGetAbsolutePosition() { final int border = 8; final int margin = 9; final int padding = 10; final int top = 15; final int left = 14; final Element elem = DOM.createDiv(); DOM.appendChild(RootPanel.getBodyElement(), elem); DOM.setStyleAttribute(elem, "position", "absolute"); DOM.setStyleAttribute(elem, "border", border + "px solid #000"); DOM.setStyleAttribute(elem, "padding", padding + "px"); DOM.setStyleAttribute(elem, "margin", margin + "px"); Document doc = Document.get(); DOM.setStyleAttribute(elem, "top", (top - doc.getBodyOffsetLeft()) + "px"); DOM.setStyleAttribute(elem, "left", (left - doc.getBodyOffsetTop()) + "px"); delayTestFinish(1000); DeferredCommand.addCommand(new Command() { public void execute() { assertEquals(top + margin, DOM.getAbsoluteTop(elem)); assertEquals(left + margin, DOM.getAbsoluteLeft(elem)); finishTest(); } }); } /** * Tests {@link DOM#getAbsoluteTop(Element)} and * {@link DOM#getAbsoluteLeft(Element)} for consistency when the element * contains children and has scrollbars. See issue #1093 for more details. * */ @DoNotRunWith(Platform.HtmlUnitLayout) public void testGetAbsolutePositionWhenScrolled() { final Element outer = DOM.createDiv(); final Element inner = DOM.createDiv(); DOM.setStyleAttribute(outer, "position", "absolute"); DOM.setStyleAttribute(outer, "top", "0px"); DOM.setStyleAttribute(outer, "left", "0px"); DOM.setStyleAttribute(outer, "overflow", "auto"); DOM.setStyleAttribute(outer, "width", "200px"); DOM.setStyleAttribute(outer, "height", "200px"); DOM.setStyleAttribute(inner, "marginTop", "800px"); DOM.setStyleAttribute(inner, "marginLeft", "800px"); DOM.appendChild(outer, inner); DOM.appendChild(RootPanel.getBodyElement(), outer); DOM.setInnerText(inner, ":-)"); DOM.scrollIntoView(inner); // Ensure that we are scrolled. assertTrue(DOM.getElementPropertyInt(outer, "scrollTop") > 0); assertTrue(DOM.getElementPropertyInt(outer, "scrollLeft") > 0); Document doc = Document.get(); assertEquals(doc.getBodyOffsetLeft(), DOM.getAbsoluteLeft(outer)); assertEquals(doc.getBodyOffsetTop(), DOM.getAbsoluteTop(outer)); } /** * Tests {@link DOM#getAbsoluteTop(Element)} and * {@link DOM#getAbsoluteLeft(Element)} for consistency when the parent * element has a border. * */ public void testGetAbsolutePositionWithPixelBorders() { final Element outer = DOM.createDiv(); final Element inner = DOM.createDiv(); outer.getStyle().setProperty("position", "relative"); outer.getStyle().setProperty("width", "200px"); outer.getStyle().setProperty("height", "200px"); inner.getStyle().setProperty("position", "absolute"); inner.getStyle().setProperty("top", "30px"); inner.getStyle().setProperty("left", "40px"); inner.setInnerText("inner"); outer.appendChild(inner); RootPanel.getBodyElement().appendChild(outer); // Get the position without a border int absTop = inner.getAbsoluteTop(); int absLeft = inner.getAbsoluteLeft(); // Get the position with a border outer.getStyle().setProperty("border", "2px solid blue"); assertEquals(2, inner.getAbsoluteTop() - absTop); assertEquals(2, inner.getAbsoluteLeft() - absLeft); } /** * Tests getAbsoluteLeft/Top() for the document.body element. This used to * cause exceptions to be thrown on Opera (see issue 1556). */ public void testGetBodyAbsolutePosition() { try { // The body's absolute left/top depends upon the browser, but we just // need to make sure nothing goes wrong reading them. BodyElement body = Document.get().getBody(); body.getAbsoluteLeft(); body.getAbsoluteTop(); } catch (Throwable e) { fail("Exception occurred getting the absolute position of the body"); } } /** * Tests the ability to do a parent-ward walk in the DOM. */ public void testGetParent() { Element element = RootPanel.get().getElement(); int i = 0; while (i < 10 && element != null) { element = DOM.getParent(element); i++; } // If we got here we looped "forever" or passed, as no exception was thrown. if (i == 10) { fail("Cyclic parent structure detected."); } // If we get here, we pass, because we encountered no errors going to the // top of the parent hierarchy. } /** * Tests {@link DOM#insertChild(Element, Element, int)}. * */ public void testInsertChild() { Element parent = RootPanel.get().getElement(); Element div = DOM.createDiv(); DOM.insertChild(parent, div, Integer.MAX_VALUE); Element child = DOM.getChild(RootPanel.get().getElement(), DOM.getChildCount(parent) - 1); assertTrue(div == child); } /** * Tests that {@link DOM#isOrHasChild(Element, Element)} works consistently * across browsers. */ public void testIsOrHasChild() { Element div = DOM.createDiv(); Element childDiv = DOM.createDiv(); assertFalse(DOM.isOrHasChild(div, childDiv)); assertTrue(DOM.isOrHasChild(div, div)); DOM.appendChild(div, childDiv); assertTrue(DOM.isOrHasChild(div, childDiv)); assertFalse(DOM.isOrHasChild(childDiv, div)); DOM.appendChild(RootPanel.getBodyElement(), div); assertTrue(DOM.isOrHasChild(div, childDiv)); assertTrue(DOM.isOrHasChild(div, div)); assertFalse(DOM.isOrHasChild(childDiv, div)); } /** * Tests that {@link DOM#setInnerText(Element, String)} works consistently * across browsers. */ public void testSetInnerText() { Element tableElem = DOM.createTable(); Element trElem = DOM.createTR(); Element tdElem = DOM.createTD(); DOM.setInnerText(tdElem, "Some Table Heading Data"); // Add a <em> element as a child to the td element Element emElem = DOM.createElement("em"); DOM.setInnerText(emElem, "Some emphasized text"); DOM.appendChild(tdElem, emElem); DOM.appendChild(trElem, tdElem); DOM.appendChild(tableElem, trElem); DOM.appendChild(RootPanel.getBodyElement(), tableElem); DOM.setInnerText(tdElem, null); // Once we set the inner text on an element to null, all of the element's // child nodes // should be deleted, including any text nodes, for all supported browsers. assertTrue(getDenormalizedChildCount(tdElem) == 0); } /** * Tests the correctness of setting the <code>src</code> attribute on * images. The reason for these complicated tests is that DOMImplIE6 has a * complex delay-load strategy to address the fact that loading multiple * images of the same type on IE6 can cause multiple redundant requests when * the image is not already cached. * * This tests the following transformation, where letters refer to URLs and * number refer to indexes. * * <pre> * 0:A -> 0:B * <pre> */ public void testSetSrc0() { final Element image = DOM.createImg(); DOM.setImgSrc(image, "a0.gif"); assertEndsWith("a0.gif", DOM.getImgSrc(image)); DOM.setImgSrc(image, "b0.gif"); assertEndsWith("b0.gif", DOM.getImgSrc(image)); delayTestFinish(2000); new Timer() { @Override public void run() { assertEndsWith("b0.gif", DOM.getElementProperty(image, "src")); finishTest(); } }.schedule(1000); } /** * Tests the correctness of setting the <code>src</code> attribute on * images. The reason for these complicated tests is that DOMImplIE6 has a * complex delay-load strategy to address the fact that loading multiple * images of the same type on IE6 can cause multiple redundant requests when * the image is not already cached. * * This tests the following transformation, where letters refer to URLs and * number refer to indexes. * * <pre> * 0:A -> 0:A 1:B * | | * |---| | * 1:A 2:A 2:A * <pre> */ public void testSetSrc1() { final Element[] images = new Element[] { DOM.createImg(), DOM.createImg(), DOM.createImg()}; DOM.setImgSrc(images[0], "a1.gif"); DOM.setImgSrc(images[1], "a1.gif"); DOM.setImgSrc(images[2], "a1.gif"); assertEndsWith("a1.gif", DOM.getImgSrc(images[0])); assertEndsWith("a1.gif", DOM.getImgSrc(images[1])); assertEndsWith("a1.gif", DOM.getImgSrc(images[2])); DOM.setImgSrc(images[1], "b1.gif"); assertEndsWith("a1.gif", DOM.getImgSrc(images[0])); assertEndsWith("b1.gif", DOM.getImgSrc(images[1])); assertEndsWith("a1.gif", DOM.getImgSrc(images[2])); delayTestFinish(2000); new Timer() { @Override public void run() { assertEndsWith("a1.gif", DOM.getElementProperty(images[0], "src")); assertEndsWith("b1.gif", DOM.getElementProperty(images[1], "src")); assertEndsWith("a1.gif", DOM.getElementProperty(images[2], "src")); finishTest(); } }.schedule(1000); } /** * Tests the correctness of setting the <code>src</code> attribute on * images. The reason for these complicated tests is that DOMImplIE6 has a * complex delay-load strategy to address the fact that loading multiple * images of the same type on IE6 can cause multiple redundant requests when * the image is not already cached. * * This tests the following transformation, where letters refer to URLs and * number refer to indexes. * * <pre> * 0:A -> 1:A 0:B * | | * |---| | * 1:A 2:A 2:A * <pre> */ public void testSetSrc2() { final Element[] images = new Element[] { DOM.createImg(), DOM.createImg(), DOM.createImg()}; DOM.setImgSrc(images[0], "a2.gif"); DOM.setImgSrc(images[1], "a2.gif"); DOM.setImgSrc(images[2], "a2.gif"); assertEndsWith("a2.gif", DOM.getImgSrc(images[0])); assertEndsWith("a2.gif", DOM.getImgSrc(images[1])); assertEndsWith("a2.gif", DOM.getImgSrc(images[2])); DOM.setImgSrc(images[0], "b2.gif"); assertEndsWith("b2.gif", DOM.getImgSrc(images[0])); assertEndsWith("a2.gif", DOM.getImgSrc(images[1])); assertEndsWith("a2.gif", DOM.getImgSrc(images[2])); delayTestFinish(3000); new Timer() { @Override public void run() { assertEndsWith("b2.gif", DOM.getElementProperty(images[0], "src")); assertEndsWith("a2.gif", DOM.getElementProperty(images[1], "src")); assertEndsWith("a2.gif", DOM.getElementProperty(images[2], "src")); finishTest(); } }.schedule(2000); } /** * Tests the correctness of setting the <code>src</code> attribute on * images. The reason for these complicated tests is that DOMImplIE6 has a * complex delay-load strategy to address the fact that loading multiple * images of the same type on IE6 can cause multiple redundant requests when * the image is not already cached. * * This tests the following transformation, where letters refer to URLs and * number refer to indexes. * * <pre> * 0:A 3:B -> 1:A 3:B * | | | * |---| | | * 1:A 2:A 2:A 0:B * <pre> */ public void testSetSrc3() { final Element[] images = new Element[] { DOM.createImg(), DOM.createImg(), DOM.createImg(), DOM.createImg()}; DOM.setImgSrc(images[0], "a3.gif"); DOM.setImgSrc(images[1], "a3.gif"); DOM.setImgSrc(images[2], "a3.gif"); DOM.setImgSrc(images[3], "b3.gif"); assertEndsWith("a3.gif", DOM.getImgSrc(images[0])); assertEndsWith("a3.gif", DOM.getImgSrc(images[1])); assertEndsWith("a3.gif", DOM.getImgSrc(images[2])); assertEndsWith("b3.gif", DOM.getImgSrc(images[3])); DOM.setImgSrc(images[0], "b3.gif"); assertEndsWith("b3.gif", DOM.getImgSrc(images[0])); assertEndsWith("a3.gif", DOM.getImgSrc(images[1])); assertEndsWith("a3.gif", DOM.getImgSrc(images[2])); assertEndsWith("b3.gif", DOM.getImgSrc(images[3])); delayTestFinish(2000); new Timer() { @Override public void run() { assertEndsWith("b3.gif", DOM.getElementProperty(images[0], "src")); assertEndsWith("a3.gif", DOM.getElementProperty(images[1], "src")); assertEndsWith("a3.gif", DOM.getElementProperty(images[2], "src")); assertEndsWith("b3.gif", DOM.getElementProperty(images[3], "src")); finishTest(); } }.schedule(1000); } /** * Tests the correctness of setting the <code>src</code> attribute on * images. The reason for these complicated tests is that DOMImplIE6 has a * complex delay-load strategy to address the fact that loading multiple * images of the same type on IE6 can cause multiple redundant requests when * the image is not already cached. * * This tests the following transformation, where letters refer to URLs and * number refer to indexes. * * <pre> * 0:A 3:B -> 0:A 3:B * | | | | * |---| | | |---| * 1:A 2:A 4:B 1:A 4:B 2:B * <pre> */ public void testSetSrc4() { final Element[] images = new Element[] { DOM.createImg(), DOM.createImg(), DOM.createImg(), DOM.createImg(), DOM.createImg()}; DOM.setImgSrc(images[0], "a4.gif"); DOM.setImgSrc(images[1], "a4.gif"); DOM.setImgSrc(images[2], "a4.gif"); DOM.setImgSrc(images[3], "b4.gif"); DOM.setImgSrc(images[4], "b4.gif"); assertEndsWith("a4.gif", DOM.getImgSrc(images[0])); assertEndsWith("a4.gif", DOM.getImgSrc(images[1])); assertEndsWith("a4.gif", DOM.getImgSrc(images[2])); assertEndsWith("b4.gif", DOM.getImgSrc(images[3])); assertEndsWith("b4.gif", DOM.getImgSrc(images[4])); DOM.setImgSrc(images[2], "b4.gif"); assertEndsWith("a4.gif", DOM.getImgSrc(images[0])); assertEndsWith("a4.gif", DOM.getImgSrc(images[1])); assertEndsWith("b4.gif", DOM.getImgSrc(images[2])); assertEndsWith("b4.gif", DOM.getImgSrc(images[3])); assertEndsWith("b4.gif", DOM.getImgSrc(images[4])); delayTestFinish(2000); new Timer() { @Override public void run() { assertEndsWith("a4.gif", DOM.getElementProperty(images[0], "src")); assertEndsWith("a4.gif", DOM.getElementProperty(images[1], "src")); assertEndsWith("b4.gif", DOM.getElementProperty(images[2], "src")); assertEndsWith("b4.gif", DOM.getElementProperty(images[3], "src")); assertEndsWith("b4.gif", DOM.getElementProperty(images[4], "src")); finishTest(); } }.schedule(1000); } /** * Tests {@link DOM#toString(Element)} against likely failure points. */ public void testToString() { Button b = new Button("abcdef"); assertTrue(b.toString().indexOf("abcdef") != -1); assertTrue(b.toString().toLowerCase().indexOf("button") != -1); // Test <img src="http://.../logo.gif" /> Element image = DOM.createImg(); String imageUrl = "http://www.google.com/images/logo.gif"; DOM.setImgSrc(image, imageUrl); String imageToString = DOM.toString(image).trim().toLowerCase(); assertTrue(imageToString.startsWith("<img")); assertTrue(imageToString.indexOf(imageUrl) != -1); // Test <input name="flinks" /> Element input = DOM.createInputText(); DOM.setElementProperty(input, "name", "flinks"); final String inputToString = DOM.toString(input).trim().toLowerCase(); assertTrue(inputToString.startsWith("<input")); // Test <select><option>....</select> Element select = DOM.createSelect(); for (int i = 0; i < 10; i++) { final Element option = DOM.createElement("option"); DOM.appendChild(select, option); DOM.setInnerText(option, "item #" + i); } String selectToString = DOM.toString(select).trim().toLowerCase(); assertTrue(selectToString.startsWith("<select")); for (int i = 0; i < 10; i++) { assertTrue(selectToString.indexOf("item #" + i) != -1); } // Test <meta name="robots" /> Element meta = DOM.createElement("meta"); DOM.setElementProperty(meta, "name", "robots"); String metaToString = DOM.toString(meta).trim().toLowerCase(); assertTrue(metaToString.startsWith("<meta")); } }