| /* |
| * Copyright 2008 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. |
| * |
| */ |
| 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.getAbsoluteTop(outer)); |
| assertEquals(doc.getBodyOffsetTop(), DOM.getAbsoluteLeft(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. |
| * |
| * TODO(amitmanjhi): Remove DoNotRunWith after updating to HtmlUnit-2.7. |
| */ |
| // TODO (amitmanjhi): Remove annotation after updating HtmlUnit. kprobst says |
| // the issue has been fixed in htmlUnit trunk |
| @DoNotRunWith({Platform.HtmlUnit}) |
| 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")); |
| } |
| } |