| /* |
| * 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.dom.client; |
| |
| import com.google.gwt.core.client.JavaScriptObject; |
| 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.user.client.Command; |
| import com.google.gwt.user.client.DeferredCommand; |
| |
| /** |
| * Element tests (many stolen from DOMTest). |
| */ |
| public class ElementTest extends GWTTestCase { |
| |
| @Override |
| public String getModuleName() { |
| return "com.google.gwt.dom.DOMTest"; |
| } |
| |
| public void testAddRemoveReplaceClassName() { |
| DivElement div = Document.get().createDivElement(); |
| |
| div.setClassName("foo"); |
| assertEquals("foo", div.getClassName()); |
| |
| div.addClassName("bar"); |
| assertEquals("foo bar", div.getClassName()); |
| |
| div.addClassName("baz"); |
| assertEquals("foo bar baz", div.getClassName()); |
| |
| div.replaceClassName("bar", "tintin"); |
| assertTrue(div.getClassName().contains("tintin")); |
| assertFalse(div.getClassName().contains("bar")); |
| } |
| |
| /** |
| * firstChildElement, nextSiblingElement. |
| */ |
| public void testChildElements() { |
| Document doc = Document.get(); |
| DivElement parent = doc.createDivElement(); |
| DivElement div0 = doc.createDivElement(); |
| DivElement div1 = doc.createDivElement(); |
| |
| parent.appendChild(doc.createTextNode("foo")); |
| parent.appendChild(div0); |
| parent.appendChild(doc.createTextNode("bar")); |
| parent.appendChild(div1); |
| |
| Element fc = parent.getFirstChildElement(); |
| Element ns = fc.getNextSiblingElement(); |
| assertEquals(div0, fc); |
| assertEquals(div1, ns); |
| } |
| |
| /** |
| * Test round-trip of the 'disabled' property. |
| */ |
| public void testDisabled() { |
| ButtonElement button = Document.get().createPushButtonElement(); |
| assertFalse(button.isDisabled()); |
| button.setDisabled(true); |
| assertTrue(button.isDisabled()); |
| |
| InputElement input = Document.get().createTextInputElement(); |
| assertFalse(input.isDisabled()); |
| input.setDisabled(true); |
| assertTrue(input.isDisabled()); |
| } |
| |
| /** |
| * [get|set|remove]Attribute. |
| */ |
| public void testElementAttribute() { |
| DivElement div = Document.get().createDivElement(); |
| div.setAttribute("class", "testClass"); |
| String cssClass = div.getAttribute("class"); |
| assertEquals("testClass", cssClass); |
| div.removeAttribute("class"); |
| cssClass = div.getAttribute("class"); |
| assertEquals("", cssClass); |
| } |
| |
| /** |
| * Ensure that the return type of an attribute is always a string. IE should |
| * not return a numeric attribute based on the element property. See issue |
| * 3238. |
| */ |
| public void testElementAttributeNumeric() { |
| DivElement div = Document.get().createDivElement(); |
| Document.get().getBody().appendChild(div); |
| div.setInnerText("Hello World"); |
| div.getAttribute("offsetWidth").length(); |
| div.getAttribute("offsetWidth").trim().length(); |
| Document.get().getBody().removeChild(div); |
| } |
| |
| public void testEmptyClassNameAssertion() { |
| DivElement div = Document.get().createDivElement(); |
| |
| if (getClass().desiredAssertionStatus()) { |
| div.setClassName("primary"); |
| try { |
| div.addClassName(""); |
| fail(); |
| } catch (AssertionError e) { |
| // This *should* throw. |
| } |
| |
| try { |
| div.addClassName(" "); |
| fail(); |
| } catch (AssertionError e) { |
| // This *should* throw. |
| } |
| |
| try { |
| div.addClassName(null); |
| fail(); |
| } catch (AssertionError e) { |
| // This *should* throw. |
| } |
| |
| try { |
| div.removeClassName(""); |
| fail(); |
| } catch (AssertionError e) { |
| // This *should* throw. |
| } |
| |
| try { |
| div.removeClassName(" "); |
| fail(); |
| } catch (AssertionError e) { |
| // This *should* throw. |
| } |
| |
| try { |
| div.removeClassName(null); |
| fail(); |
| } catch (AssertionError e) { |
| // This *should* throw. |
| } |
| |
| assertEquals("primary", div.getClassName()); |
| } |
| } |
| |
| /** |
| * getAbsolute[Left|Top|Right|Bottom]. |
| */ |
| public void testGetAbsolutePosition() { |
| final int border = 8; |
| final int margin = 9; |
| final int padding = 10; |
| |
| final int top = 15; |
| final int left = 14; |
| final int width = 128; |
| final int height = 64; |
| |
| final Document doc = Document.get(); |
| final DivElement elem = doc.createDivElement(); |
| doc.getBody().appendChild(elem); |
| |
| elem.getStyle().setProperty("position", "absolute"); |
| elem.getStyle().setProperty("border", border + "px solid #000"); |
| elem.getStyle().setProperty("padding", padding + "px"); |
| elem.getStyle().setProperty("margin", margin + "px"); |
| |
| elem.getStyle().setPropertyPx("top", top - doc.getBodyOffsetLeft()); |
| elem.getStyle().setPropertyPx("left", left - doc.getBodyOffsetTop()); |
| elem.getStyle().setPropertyPx("width", width); |
| elem.getStyle().setPropertyPx("height", height); |
| |
| DeferredCommand.addCommand(new Command() { |
| public void execute() { |
| int absLeft = left + margin; |
| int absTop = top + margin; |
| int interiorDecorations = (border * 2) + (padding * 2); |
| |
| assertEquals(absLeft, elem.getAbsoluteLeft()); |
| assertEquals(absTop, elem.getAbsoluteTop()); |
| |
| if (isIE6or7() && !doc.isCSS1Compat()) { |
| // In IE/quirk, the interior decorations are considered part of the |
| // width/height, so there's no need to account for them here. |
| assertEquals(absLeft + width, elem.getAbsoluteRight()); |
| assertEquals(absTop + height, elem.getAbsoluteBottom()); |
| } else { |
| assertEquals(absLeft + width + interiorDecorations, |
| elem.getAbsoluteRight()); |
| assertEquals(absTop + height + interiorDecorations, |
| elem.getAbsoluteBottom()); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * scroll[Left|Top], getAbsolute[Left|Top]. |
| */ |
| @DoNotRunWith({Platform.HtmlUnit}) |
| public void testGetAbsolutePositionWhenBodyScrolled() { |
| Document doc = Document.get(); |
| BodyElement body = doc.getBody(); |
| |
| DivElement div = doc.createDivElement(); |
| body.appendChild(div); |
| |
| div.setInnerText("foo"); |
| div.getStyle().setPosition(Position.ABSOLUTE); |
| div.getStyle().setLeft(1000, Unit.PX); |
| div.getStyle().setTop(1000, Unit.PX); |
| |
| DivElement fixedDiv = doc.createDivElement(); |
| body.appendChild(fixedDiv); |
| fixedDiv.setInnerText("foo"); |
| fixedDiv.getStyle().setPosition(Position.FIXED); |
| |
| // Get the absolute position of the element when the body is unscrolled. |
| int absLeft = div.getAbsoluteLeft(); |
| int absTop = div.getAbsoluteTop(); |
| |
| // Scroll the body as far down and to the right as possible. |
| body.setScrollLeft(10000); |
| body.setScrollTop(10000); |
| |
| // Make sure the absolute position hasn't changed (this has turned out to |
| // be a common error in getAbsoluteLeft/Top() implementations). |
| // |
| // HACK: Firefox 2 has a bug that causes its getBoxObjectFor() to become |
| // off-by-one at times when scrolling. It's not clear how to make this go |
| // away, and doesn't seem to be worth the trouble to implement |
| // getAbsoluteLeft/Top() yet again for FF2. |
| assertTrue(Math.abs(absLeft - div.getAbsoluteLeft()) <= 1); |
| assertTrue(Math.abs(absTop - div.getAbsoluteTop()) <= 1); |
| |
| // Ensure that the 'position:fixed' div's absolute position includes the |
| // body's scroll position. |
| // |
| // Don't do this on IE6/7 or old Gecko, which don't support position:fixed. |
| if (!isIE6or7() && !isOldGecko()) { |
| assertTrue(fixedDiv.getAbsoluteLeft() >= body.getScrollLeft()); |
| assertTrue(fixedDiv.getAbsoluteTop() >= body.getScrollTop()); |
| } |
| } |
| |
| /** |
| * getElementsByTagName. |
| */ |
| public void testGetElementsByTagName() { |
| DivElement div = Document.get().createDivElement(); |
| div.setInnerHTML("<span><button>foo</button><span><button>bar</button></span></span>"); |
| |
| NodeList<Element> nodes = div.getElementsByTagName("button"); |
| assertEquals(2, nodes.getLength()); |
| assertEquals("foo", nodes.getItem(0).getInnerText()); |
| assertEquals("bar", nodes.getItem(1).getInnerText()); |
| } |
| |
| public void testHasAttribute() { |
| DivElement div = Document.get().createDivElement(); |
| |
| // Assert that a raw element doesn't incorrectly report that it has any |
| // unspecified built-in attributes (this is a problem on IE<8 if you're not |
| // careful in implementing hasAttribute()). |
| assertFalse(div.hasAttribute("class")); |
| assertFalse(div.hasAttribute("style")); |
| assertFalse(div.hasAttribute("title")); |
| assertFalse(div.hasAttribute("id")); |
| |
| // Ensure that setting HTML-defined attributes is properly reported by |
| // hasAttribute(). |
| div.setId("foo"); |
| assertTrue(div.hasAttribute("id")); |
| |
| // Ensure that setting *custom* attributes is properly reported by |
| // hasAttribute(). |
| assertFalse(div.hasAttribute("foo")); |
| div.setAttribute("foo", "bar"); |
| assertTrue(div.hasAttribute("foo")); |
| |
| // Ensure that a null attribute argument always returns null. |
| assertFalse(div.hasAttribute(null)); |
| } |
| |
| /** |
| * Tests HeadingElement.as() (it has slightly more complex assertion logic |
| * than most). |
| */ |
| public void testHeadingElementAs() { |
| DivElement placeHolder = Document.get().createDivElement(); |
| |
| for (int i = 0; i < 6; ++i) { |
| placeHolder.setInnerHTML("<H" + (i + 1) + "/>"); |
| assertNotNull(HeadingElement.as(placeHolder.getFirstChildElement())); |
| } |
| |
| if (getClass().desiredAssertionStatus()) { |
| Element notHeading = Document.get().createDivElement(); |
| try { |
| HeadingElement.as(notHeading); |
| fail("Expected assertion failure"); |
| } catch (AssertionError e) { |
| // this *should* happen. |
| } |
| } |
| } |
| |
| /** |
| * Tests Element.is() and Element.as(). |
| */ |
| public void testIsAndAs() { |
| assertFalse(Element.is(Document.get())); |
| |
| Node div = Document.get().createDivElement(); |
| assertTrue(Element.is(div)); |
| assertEquals("div", Element.as(div).getTagName().toLowerCase()); |
| |
| // Element.is(null) is allowed and should return false. |
| assertFalse(Element.is(null)); |
| } |
| |
| /** |
| * Document.createElement('ns:tag'), getTagName(). |
| */ |
| public void testNamespaces() { |
| Element elem = Document.get().createElement("myns:elem"); |
| assertEquals("myns:elem", elem.getTagName().toLowerCase()); |
| } |
| |
| /** |
| * className, id, tagName, title, dir, lang. |
| */ |
| public void testNativeProperties() { |
| DivElement div = Document.get().createDivElement(); |
| |
| assertEquals("div", div.getTagName().toLowerCase()); |
| |
| div.setClassName("myClass"); |
| assertEquals(div.getClassName(), "myClass"); |
| |
| div.setId("myId"); |
| assertEquals(div.getId(), "myId"); |
| |
| div.setTitle("myTitle"); |
| assertEquals(div.getTitle(), "myTitle"); |
| |
| div.setDir("rtl"); |
| assertEquals(div.getDir(), "rtl"); |
| |
| div.setLang("fr-FR"); |
| assertEquals(div.getLang(), "fr-FR"); |
| } |
| |
| /** |
| * offset[Left|Top|Width|Height], offsetParent. |
| */ |
| @DoNotRunWith({Platform.HtmlUnit}) |
| public void testOffsets() { |
| DivElement outer = Document.get().createDivElement(); |
| DivElement middle = Document.get().createDivElement(); |
| DivElement inner = Document.get().createDivElement(); |
| |
| Document.get().getBody().appendChild(outer); |
| outer.appendChild(middle); |
| middle.appendChild(inner); |
| |
| outer.getStyle().setProperty("position", "absolute"); |
| inner.getStyle().setProperty("position", "relative"); |
| inner.getStyle().setPropertyPx("left", 19); |
| inner.getStyle().setPropertyPx("top", 23); |
| inner.getStyle().setPropertyPx("width", 29); |
| inner.getStyle().setPropertyPx("height", 31); |
| |
| assertEquals(outer, inner.getOffsetParent()); |
| assertEquals(19, inner.getOffsetLeft()); |
| assertEquals(23, inner.getOffsetTop()); |
| assertEquals(29, inner.getOffsetWidth()); |
| assertEquals(31, inner.getOffsetHeight()); |
| } |
| |
| /** |
| * setProperty*, getProperty*. |
| */ |
| public void testProperties() { |
| DivElement div = Document.get().createDivElement(); |
| |
| div.setPropertyString("foo", "bar"); |
| assertEquals("bar", div.getPropertyString("foo")); |
| |
| div.setPropertyInt("foo", 42); |
| assertEquals(42, div.getPropertyInt("foo")); |
| |
| div.setPropertyBoolean("foo", true); |
| div.setPropertyBoolean("bar", false); |
| assertEquals(true, div.getPropertyBoolean("foo")); |
| assertEquals(false, div.getPropertyBoolean("bar")); |
| |
| Object obj = new Object(); |
| div.setPropertyObject("baz", obj); |
| assertEquals(obj, div.getPropertyObject("baz")); |
| |
| JavaScriptObject jso = createTrivialJSO(); |
| div.setPropertyJSO("tintin", jso); |
| assertEquals(jso, div.getPropertyJSO("tintin")); |
| } |
| |
| /** |
| * scroll[Left|Top], scrollIntoView. |
| */ |
| public void testScrollIntoView() { |
| final DivElement outer = Document.get().createDivElement(); |
| final DivElement inner = Document.get().createDivElement(); |
| |
| outer.getStyle().setProperty("position", "absolute"); |
| outer.getStyle().setProperty("top", "0px"); |
| outer.getStyle().setProperty("left", "0px"); |
| outer.getStyle().setProperty("overflow", "auto"); |
| outer.getStyle().setProperty("width", "200px"); |
| outer.getStyle().setProperty("height", "200px"); |
| |
| inner.getStyle().setProperty("marginTop", "800px"); |
| inner.getStyle().setProperty("marginLeft", "800px"); |
| |
| outer.appendChild(inner); |
| Document.get().getBody().appendChild(outer); |
| inner.setInnerText(":-)"); |
| inner.scrollIntoView(); |
| |
| // Ensure that we are scrolled. |
| assertTrue(outer.getScrollTop() > 0); |
| assertTrue(outer.getScrollLeft() > 0); |
| |
| outer.setScrollLeft(0); |
| outer.setScrollTop(0); |
| |
| // Ensure that we are no longer scrolled. |
| assertEquals(outer.getScrollTop(), 0); |
| assertEquals(outer.getScrollLeft(), 0); |
| } |
| |
| /** |
| * Tests that scrollLeft behaves as expected in RTL mode. |
| */ |
| @DoNotRunWith({Platform.HtmlUnit}) |
| public void testScrollLeftInRtl() { |
| final DivElement outer = Document.get().createDivElement(); |
| final DivElement inner = Document.get().createDivElement(); |
| |
| outer.getStyle().setProperty("position", "absolute"); |
| outer.getStyle().setProperty("top", "0px"); |
| outer.getStyle().setProperty("left", "0px"); |
| outer.getStyle().setProperty("overflow", "auto"); |
| outer.getStyle().setProperty("width", "200px"); |
| outer.getStyle().setProperty("height", "200px"); |
| |
| // Force scrolling on the outer div, because WebKit doesn't do this |
| // correctly in RTL mode. |
| outer.getStyle().setProperty("overflow", "scroll"); |
| |
| inner.getStyle().setProperty("position", "absolute"); |
| inner.getStyle().setProperty("top", "0px"); |
| inner.getStyle().setProperty("left", "0px"); |
| inner.getStyle().setProperty("right", "0px"); |
| inner.getStyle().setProperty("marginTop", "800px"); |
| inner.getStyle().setProperty("marginRight", "800px"); |
| |
| outer.appendChild(inner); |
| Document.get().getBody().appendChild(outer); |
| outer.setDir("rtl"); |
| |
| // FF2 does not render scroll bars reliably in RTL, so we set a large |
| // content to force the scroll bars. |
| String content = "ssssssssssssssssssssssssssssssssssssssssssssssssssss" |
| + "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" |
| + "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"; |
| inner.setInnerText(content); |
| |
| // The important thing is that setting and retrieving scrollLeft values in |
| // RTL mode works only for negative numbers, and that they round-trip |
| // correctly. |
| outer.setScrollLeft(-32); |
| assertEquals(-32, outer.getScrollLeft()); |
| |
| outer.setScrollLeft(32); |
| assertEquals(0, outer.getScrollLeft()); |
| } |
| |
| /** |
| * innerHTML. |
| */ |
| public void testSetInnerHTML() { |
| DivElement div = Document.get().createDivElement(); |
| div.setInnerHTML("<button><img src='foo.gif'></button>"); |
| |
| Element button = div.getFirstChildElement(); |
| Element img = button.getFirstChildElement(); |
| |
| assertEquals("button", button.getTagName().toLowerCase()); |
| assertEquals("img", img.getTagName().toLowerCase()); |
| assertTrue(((ImageElement) img).getSrc().endsWith("foo.gif")); |
| } |
| |
| /** |
| * innerText. |
| * |
| * 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() { |
| Document doc = Document.get(); |
| |
| TableElement tableElem = doc.createTableElement(); |
| TableRowElement trElem = doc.createTRElement(); |
| TableCellElement tdElem = doc.createTDElement(); |
| tdElem.setInnerText("Some Table Heading Data"); |
| |
| // Add a <em> element as a child to the td element |
| Element emElem = doc.createElement("em"); |
| emElem.setInnerText("Some emphasized text"); |
| tdElem.appendChild(emElem); |
| |
| trElem.appendChild(tdElem); |
| tableElem.appendChild(trElem); |
| doc.getBody().appendChild(tableElem); |
| tdElem.setInnerText(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(tdElem.getChildNodes().getLength() == 0); |
| } |
| |
| /** |
| * style. |
| */ |
| public void testStyle() { |
| DivElement div = Document.get().createDivElement(); |
| |
| div.getStyle().setProperty("color", "black"); |
| assertEquals("black", div.getStyle().getProperty("color")); |
| |
| div.getStyle().setPropertyPx("width", 42); |
| assertEquals("42px", div.getStyle().getProperty("width")); |
| } |
| |
| /** |
| * Test that styles only allow camelCase. |
| */ |
| public void testStyleCamelCase() { |
| DivElement div = Document.get().createDivElement(); |
| |
| // Use a camelCase property |
| div.getStyle().setProperty("backgroundColor", "black"); |
| assertEquals("black", div.getStyle().getProperty("backgroundColor")); |
| div.getStyle().setPropertyPx("marginLeft", 10); |
| assertEquals("10px", div.getStyle().getProperty("marginLeft")); |
| |
| // Use a hyphenated style |
| if (Style.class.desiredAssertionStatus()) { |
| try { |
| div.getStyle().setProperty("background-color", "red"); |
| fail("Expected assertion error: background-color should be in camelCase"); |
| } catch (AssertionError e) { |
| // expected |
| } |
| try { |
| div.getStyle().setPropertyPx("margin-left", 20); |
| fail("Expected assertion error: margin-left should be in camelCase"); |
| } catch (AssertionError e) { |
| // expected |
| } |
| try { |
| div.getStyle().getProperty("margin-right"); |
| fail("Expected assertion error: margin-right should be in camelCase"); |
| } catch (AssertionError e) { |
| // expected |
| } |
| } |
| } |
| |
| private native JavaScriptObject createTrivialJSO() /*-{ |
| return {}; |
| }-*/; |
| |
| // Stolen from UserAgent.gwt.xml. |
| private native boolean isIE6or7() /*-{ |
| var ua = navigator.userAgent.toLowerCase(); |
| if (ua.indexOf("msie") != -1) { |
| if (document.documentMode >= 8) { |
| return false; |
| } |
| return true; |
| } |
| return false; |
| }-*/; |
| |
| // Stolen from UserAgent.gwt.xml. |
| private native boolean isOldGecko() /*-{ |
| var makeVersion = function(result) { |
| return (parseInt(result[1]) * 1000) + parseInt(result[2]); |
| }; |
| |
| var ua = navigator.userAgent.toLowerCase(); |
| if (ua.indexOf("gecko") != -1) { |
| var result = /rv:([0-9]+)\.([0-9]+)/.exec(ua); |
| if (result && result.length == 3) { |
| if (makeVersion(result) >= 1008) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| }-*/; |
| } |