blob: db8ba28476f8f02c1d90c98a55995308a87f4103 [file] [log] [blame]
/*
* 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.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
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 java.util.Locale;
/**
* Element tests (many stolen from DOMTest).
*/
public class ElementTest extends GWTTestCase {
@Override
public String getModuleName() {
return "com.google.gwt.dom.DOMTest";
}
public void testAddClassName() {
DivElement div = Document.get().createDivElement();
div.setClassName("foo");
assertTrue(div.addClassName("bar"));
assertEquals("foo bar", div.getClassName());
assertTrue(div.addClassName("baz"));
assertEquals("foo bar baz", div.getClassName());
assertFalse(div.addClassName("baz"));
assertEquals("foo bar baz", div.getClassName());
}
public void testRemoveClassName() {
DivElement div = Document.get().createDivElement();
div.setClassName("foo bar baz");
assertTrue(div.removeClassName("bar"));
assertEquals("foo baz", div.getClassName());
assertFalse(div.removeClassName("bar"));
assertEquals("foo baz", div.getClassName());
assertTrue(div.removeClassName("baz"));
assertEquals("foo", div.getClassName());
assertTrue(div.removeClassName("foo"));
assertEquals("", div.getClassName());
}
public void testHasClassName() {
DivElement div = Document.get().createDivElement();
div.setClassName("foo bar");
assertTrue(div.hasClassName("bar"));
assertTrue(div.hasClassName("foo"));
div.setClassName("bar");
assertFalse(div.hasClassName("foo"));
assertTrue(div.hasClassName("bar"));
}
public void testToggleClassName() {
DivElement div = Document.get().createDivElement();
div.setClassName("foo bar baz");
div.toggleClassName("bar");
assertEquals("foo baz", div.getClassName());
div.toggleClassName("bar");
assertEquals("foo baz bar", div.getClassName());
}
public void testReplaceClassName() {
DivElement div = Document.get().createDivElement();
div.setClassName("foo bar baz");
div.replaceClassName("bar", "tintin");
assertEquals("foo baz tintin", div.getClassName());
div.replaceClassName("bar", "tintin2");
assertEquals("foo baz tintin tintin2", div.getClassName());
}
public void testIndexOfName() {
assertEquals(-1, Element.indexOfName("", "foo"));
assertEquals(-1, Element.indexOfName("foo", "fo"));
assertEquals(-1, Element.indexOfName("foo", "fool"));
assertEquals(-1, Element.indexOfName("bar fool", "foo"));
assertEquals(-1, Element.indexOfName("bar fool baz", "foo"));
assertEquals(0, Element.indexOfName("foo", "foo"));
assertEquals(0, Element.indexOfName("foo bar", "foo"));
assertEquals(4, Element.indexOfName("bar foo", "foo"));
assertEquals(4, Element.indexOfName("bar foo baz", "foo"));
}
/**
* firstChildElement, nextSiblingElement, previousSiblingElement.
*/
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();
Element ps = ns.getPreviousSiblingElement();
assertEquals(div0, fc);
assertEquals(div1, ns);
assertEquals(div0, ps);
assertNull(fc.getPreviousSiblingElement());
assertNull(ns.getNextSiblingElement());
}
/**
* 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());
SelectElement select = Document.get().createSelectElement();
assertFalse(select.isDisabled());
select.setDisabled(true);
assertTrue(select.isDisabled());
OptGroupElement optgroup = Document.get().createOptGroupElement();
assertFalse(optgroup.isDisabled());
optgroup.setDisabled(true);
assertTrue(optgroup.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("");
throw new Error();
} catch (AssertionError e) {
// This *should* throw.
}
try {
div.addClassName(" ");
throw new Error();
} catch (AssertionError e) {
// This *should* throw.
}
try {
div.addClassName(null);
throw new Error();
} catch (AssertionError e) {
// This *should* throw.
}
try {
div.removeClassName("");
throw new Error();
} catch (AssertionError e) {
// This *should* throw.
}
try {
div.removeClassName(" ");
throw new Error();
} catch (AssertionError e) {
// This *should* throw.
}
try {
div.removeClassName(null);
throw new Error();
} 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);
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
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.HtmlUnitLayout)
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, which doesn't support position:fixed.
if (!isIE6or7()) {
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));
}
public void testHasTagName() {
DivElement div = Document.get().createDivElement();
// hasTagName is case-insensitive
assertTrue(div.hasTagName("div"));
assertTrue(div.hasTagName("DIV"));
assertTrue(div.hasTagName(DivElement.TAG));
assertTrue(div.hasTagName(div.getTagName()));
assertFalse(div.hasTagName("dove"));
}
/**
* 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);
throw new Error("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(Locale.ROOT));
// Element.is(null) is allowed and should return false.
assertFalse(Element.is(null));
// Element sub-classes like DivElement have is(...) and as(...) too
assertFalse(DivElement.is(Document.get()));
assertTrue(DivElement.is(div));
assertEquals("div", DivElement.as(div).getTagName().toLowerCase(Locale.ROOT));
assertFalse(DivElement.is(null));
}
/**
* Document.createElement('ns:tag'), getTagName().
*/
public void testNamespaces() {
Element elem = Document.get().createElement("myns:elem");
assertEquals("myns:elem", elem.getTagName().toLowerCase(Locale.ROOT));
}
/**
* className, id, tagName, title, dir, lang.
*/
public void testNativeProperties() {
DivElement div = Document.get().createDivElement();
assertEquals("div", div.getTagName().toLowerCase(Locale.ROOT));
div.setClassName("myClass");
assertEquals("myClass", div.getClassName());
div.setId("myId");
assertEquals("myId", div.getId());
div.setTitle("myTitle");
assertEquals("myTitle", div.getTitle());
div.setDir("rtl");
assertEquals("rtl", div.getDir());
div.setLang("fr-FR");
assertEquals("fr-FR", div.getLang());
}
/**
* offset[Left|Top|Width|Height], offsetParent.
*/
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.
*/
@DoNotRunWith({Platform.HtmlUnitLayout})
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(0, outer.getScrollTop());
assertEquals(0, outer.getScrollLeft());
}
/**
* Tests that scrollLeft behaves as expected in RTL mode.
* Failed in all modes due to HtmlUnit bug:
* https://sourceforge.net/tracker/?func=detail&aid=2941255&group_id=47038&atid=448266
*/
@DoNotRunWith({Platform.HtmlUnitLayout})
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());
}
@DoNotRunWith({Platform.HtmlUnitLayout})
public void testDocumentScrollLeftInRtl() {
Document.get().getDocumentElement().setDir("rtl");
Document.get().getBody().getStyle().setProperty("direction", "rtl");
final DivElement bigdiv = Document.get().createDivElement();
bigdiv.getStyle().setProperty("position", "absolute");
bigdiv.getStyle().setProperty("top", "0px");
bigdiv.getStyle().setProperty("right", "0px");
bigdiv.getStyle().setProperty("width", "10000px"); // Bigger than window size.
bigdiv.getStyle().setProperty("height", "400px");
Document.get().getBody().appendChild(bigdiv);
// The important thing is that setting and retrieving scrollLeft values in
// RTL mode works only for negative numbers, and that they round-trip
// correctly.
try {
assertEquals(0, Document.get().getScrollLeft());
Document.get().setScrollLeft(-32);
assertEquals(-32, Document.get().getScrollLeft());
Document.get().setScrollLeft(32);
assertEquals(0, Document.get().getScrollLeft());
} finally {
// Restore direction unconditionally to not break all other tests.
Document.get().getBody().removeChild(bigdiv);
Document.get().getBody().getStyle().setProperty("direction", "ltr");
Document.get().getDocumentElement().setDir("ltr");
}
}
/**
* 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(Locale.ROOT));
assertEquals("img", img.getTagName().toLowerCase(Locale.ROOT));
assertTrue(((ImageElement) img).getSrc().endsWith("foo.gif"));
}
/**
* innerText.
*/
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");
throw new Error("Expected assertion error: background-color should be in camelCase");
} catch (AssertionError e) {
// expected
}
try {
div.getStyle().setPropertyPx("margin-left", 20);
throw new Error("Expected assertion error: margin-left should be in camelCase");
} catch (AssertionError e) {
// expected
}
try {
div.getStyle().getProperty("margin-right");
throw new Error("Expected assertion error: margin-right should be in camelCase");
} catch (AssertionError e) {
// expected
}
}
}
private native JavaScriptObject createTrivialJSO() /*-{
return {};
}-*/;
// Stolen from UserAgentPropertyGenerator
private native boolean isIE6or7() /*-{
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("msie") != -1) {
if ($doc.documentMode >= 8) {
return false;
}
return true;
}
return false;
}-*/;
}