blob: 9c4e72f681f1a439616d789bd790b0342fc11051 [file] [log] [blame]
/*
* Copyright 2011 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.builder.shared;
import com.google.gwt.dom.builder.client.DomBuilderFactory;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
/**
* Base tests for subclasses of {@link ElementBuilderBase}.
*
* @param <T> the type of builder being tested
*/
public abstract class ElementBuilderTestBase<T extends ElementBuilderBase<?>> extends GWTTestCase {
/**
* A command that operates on a builder.
*/
protected interface BuilderCommand<T extends ElementBuilderBase<?>> {
/**
* Execute an action.
*
* @param builder the builder being tested
*/
void execute(T builder);
}
/**
* The GWT module name when running tests on the client.
*/
protected static final String GWT_MODULE_NAME = "com.google.gwt.dom.builder.DomBuilder";
/**
* Indicates whether or not the element under test supports child elements.
*/
private boolean isChildElementSupported;
/**
* Indicates whether or not the element under test supports an end tag.
*/
private boolean isEndTagForbidden;
/**
* Indicates whether or not the element under test supports inner html.
*/
private boolean isInnerHtmlSupported;
/**
* Indicates whether or not the element under test supports inner text.
*/
private boolean isInnerTextSupported;
@Override
public String getModuleName() {
// Default to JVM implementation.
return null;
}
/**
* Test that you cannot append text, html, or elements after setting the text.
*/
public void testAppendAfterHtml() {
// Skip this test if inner html is not supported.
if (!isInnerHtmlSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
builder.html(SafeHtmlUtils.fromString("Hello World"));
try {
builder.text("moretext");
fail("Expected IllegalStateException: setting text after setting html");
} catch (IllegalStateException e) {
// Expected.
}
try {
builder.html(SafeHtmlUtils.fromString("morehtml"));
fail("Expected IllegalStateException: setting html twice");
} catch (IllegalStateException e) {
// Expected.
}
try {
builder.startDiv();
fail("Expected IllegalStateException: appending a div after setting html");
} catch (IllegalStateException e) {
// Expected.
}
}
}
/**
* Test that you cannot append text, html, or elements after setting the text.
*/
public void testAppendAfterText() {
// Skip this test if inner text is not supported.
if (!isInnerTextSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
builder.text("Hello World");
try {
builder.text("moretext");
fail("Expected IllegalStateException: setting text twice");
} catch (IllegalStateException e) {
// Expected.
}
if (isInnerHtmlSupported) {
try {
builder.html(SafeHtmlUtils.fromString("morehtml"));
fail("Expected IllegalStateException: setting html after setting text");
} catch (IllegalStateException e) {
// Expected.
}
}
if (isChildElementSupported) {
try {
builder.startDiv();
fail("Expected IllegalStateException: appending a div after setting text");
} catch (IllegalStateException e) {
// Expected.
}
}
}
}
public void testAttributeAfterAppendHtml() {
// String value.
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.attribute("name", "value");
}
}, "Cannot add attribute after appending html");
// Int value.
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.attribute("name", 1);
}
}, "Cannot add attribute after appending html");
}
public void testAttributeAfterEnd() {
assertActionFailsAfterEnd(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.attribute("name", "value");
}
}, "Cannot add attribute after adding a child element");
}
public void testClassNameAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.className("value");
}
}, "Cannot add attribute after appending html");
}
public void testDirNameAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.dir("value");
}
}, "Cannot add attribute after appending html");
}
public void testDraggableNameAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.draggable("value");
}
}, "Cannot add attribute after appending html");
}
public void testEnd() {
for (ElementBuilderFactory factory : getFactories()) {
// Test that a builder can be ended if it comes directly from the factory.
{
T builder = createElementBuilder(factory);
builder.id("myid");
endElement(builder);
}
/*
* Test that a builder can be ended if it was started from another
* builder.
*
* Skip this test if child elements are not supported.
*/
if (isChildElementSupported) {
T builder = createElementBuilder(factory);
T elem = startElement(builder);
endElement(elem);
builder.end();
}
}
}
public void testEndUnmatchedTagName() {
// Skip this test if child elements are not supported.
if (!isChildElementSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
T child = startElement(builder);
try {
child.end("notamatch");
fail("Expected IllegalStateException: end tag does not match start tag");
} catch (IllegalStateException e) {
// Expected.
}
}
}
public void testHtmlAfterAppend() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.html(SafeHtmlUtils.fromString("hello world"));
}
}, "Cannot set html after appending a child element");
}
public void testHtmlAfterEnd() {
if (!isInnerHtmlSupported) {
return;
}
assertActionFailsAfterEnd(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.html(SafeHtmlUtils.fromString("hello world"));
}
}, "Cannot set html after adding a child element");
}
/**
* Test that HTML can be set after ending one element and starting another.
*/
public void testHtmlAfterRestart() {
if (!isInnerHtmlSupported || !isChildElementSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
builder.startDiv().id("test").html(SafeHtmlUtils.fromString("test")).end();
// Should not cause any errors.
builder.startDiv().html(SafeHtmlUtils.fromString("hello"));
}
}
/**
* Test that all implementations of a builder are consistent in their support
* of setting inner html.
*/
public void testHtmlConsistent() {
boolean expected = false;
ElementBuilderFactory[] factories = getFactories();
for (int i = 0; i < factories.length; i++) {
T builder = createElementBuilder(factories[0]);
boolean isSupported = true;
try {
builder.html(SafeHtmlUtils.EMPTY_SAFE_HTML);
} catch (UnsupportedOperationException e) {
// Child elements are not supported.
isSupported = false;
}
assertEquals(isInnerHtmlSupported, isSupported);
}
}
public void testIdNameAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.id("value");
}
}, "Cannot add attribute after appending html");
}
public void testIsEndTagForbidden() {
// Skip this test if the end tag is allowed.
if (!isEndTagForbidden) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
try {
T builder = createElementBuilder(factory);
builder.html(SafeHtmlUtils.fromString("html is not allowed"));
fail("Expected UnsupportedOperationException: cannot set html if end tag is forbidden");
} catch (UnsupportedOperationException e) {
// Expected.
}
try {
T builder = createElementBuilder(factory);
builder.text("text is not allowed");
fail("Expected UnsupportedOperationException: cannot set text if end tag is forbidden");
} catch (UnsupportedOperationException e) {
// Expected.
}
try {
T builder = createElementBuilder(factory);
builder.startDiv();
fail("Expected UnsupportedOperationException: "
+ "cannot append a child if end tag is forbidden");
} catch (UnsupportedOperationException e) {
// Expected.
}
}
}
/**
* Test that all implementations of a builder return the same value from
* {@link ElementBuilderBase#isEndTagForbidden()}.
*/
public void testIsEndTagForbiddenConsistent() {
boolean expected = false;
ElementBuilderFactory[] factories = getFactories();
for (int i = 0; i < factories.length; i++) {
T builder = createElementBuilder(factories[0]);
assertEquals(isEndTagForbidden, builder.isEndTagForbidden());
}
}
public void testLangAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.lang("value");
}
}, "Cannot add attribute after appending html");
}
/**
* Test that all implementations of a builder are consistent in their support
* of appending child elements.
*/
public void testStartConsistent() {
boolean expected = false;
ElementBuilderFactory[] factories = getFactories();
for (int i = 0; i < factories.length; i++) {
T builder = createElementBuilder(factories[0]);
assertEquals(isChildElementSupported, builder.isChildElementSupported());
}
}
public void testStartSecondTopLevelElement() {
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
builder.end(); // Close the top level attribute.
try {
startElement(builder);
fail("Expected IllegalStateException: Cannot start multiple top level attributes");
} catch (IllegalStateException e) {
// Expected.
}
}
}
public void testStylePropertyAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
// Accessing the style builder is allowed.
StylesBuilder style;
try {
style = builder.style();
} catch (RuntimeException e) {
fail("Accessing StyleBuilder should not trigger an error: " + e.getMessage());
throw e;
}
// Using it is not.
style.trustedProperty("name", "value");
}
}, "Cannot access StyleBuilder appending html");
}
public void testStylePropertyAfterEnd() {
assertActionFailsAfterEnd(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
// Accessing the style builder is allowed.
StylesBuilder style;
try {
style = builder.style();
} catch (RuntimeException e) {
fail("Accessing StyleBuilder should not trigger an error: " + e.getMessage());
throw e;
}
// Using it is not.
style.trustedProperty("name", "value");
}
}, "Cannot access StyleBuilder appending html");
}
/**
* Test that you cannot add style properties after interrupting them with an
* attribute.
*/
public void testStyleTwice() {
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
// Access style first time.
StylesBuilder style = builder.style().borderWidth(1.0, Unit.PX).fontSize(10.0, Unit.PX);
// Access style again, without interruption.
builder.style().trustedColor("red");
// Add an attribute.
builder.id("id");
// Accessing style after interruption is allowed.
StylesBuilder style0 = builder.style();
// Using it is not.
try {
style0.left(1.0, Unit.PX);
fail("Expected IllegalStateException: Cannot access StyleBuilder after interruption");
} catch (IllegalStateException e) {
// Expected.
}
// Reuse existing style after interruption.
try {
style.left(1.0, Unit.PX);
fail("Expected IllegalStateException: Cannot access StyleBuilder after interruption");
} catch (IllegalStateException e) {
// Expected.
}
}
}
public void testTabIndexAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.tabIndex(1);
}
}, "Cannot add attribute after appending html");
}
public void testTextAfterAppend() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.text("hello world");
}
}, "Cannot set html after appending a child element");
}
public void testTextAfterEnd() {
if (!isInnerTextSupported) {
return;
}
assertActionFailsAfterEnd(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.text("hello world");
}
}, "Cannot set text after adding a child element");
}
/**
* Test that text can be set after ending one element and starting another.
*/
public void testTextAfterRestart() {
if (!isInnerTextSupported || !isChildElementSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
builder.startDiv().id("test").text("test").end();
// Should not cause any errors.
builder.startDiv().text("hello");
}
}
/**
* Test that all implementations of a builder are consistent in their support
* of setting inner text.
*/
public void testTextConsistent() {
boolean expected = false;
ElementBuilderFactory[] factories = getFactories();
for (int i = 0; i < factories.length; i++) {
T builder = createElementBuilder(factories[0]);
boolean isSupported = true;
try {
builder.text("");
} catch (UnsupportedOperationException e) {
// Child elements are not supported.
isSupported = false;
}
assertEquals(isInnerTextSupported, isSupported);
}
}
public void testTitleAfterAppendHtml() {
assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
@Override
public void execute(T builder) {
builder.title("value");
}
}, "Cannot add attribute after appending html");
}
/**
* Test that the specified command triggers an {@link IllegalStateException}
* after appending html to the element.
*
* @param action the command to execute
* @param message the failure message if the test fails
*/
protected void assertActionFailsAfterAppendHtml(BuilderCommand<T> action, String message) {
// Skip this test if inner html is not supported.
if (!isInnerHtmlSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
builder.html(SafeHtmlUtils.EMPTY_SAFE_HTML);
try {
action.execute(builder);
fail("Expected IllegalStateException: " + message);
} catch (IllegalStateException e) {
// Expected.
}
}
}
/**
* Test that the specified command triggers an {@link IllegalStateException}
* after ending a child element.
*
* @param action the command to execute
* @param message the failure message if the test fails
*/
protected void assertActionFailsAfterEnd(BuilderCommand<T> action, String message) {
// Skip this test if child elements are not supported.
if (!isChildElementSupported) {
return;
}
for (ElementBuilderFactory factory : getFactories()) {
T builder = createElementBuilder(factory);
// Add a child.
builder.startDiv().end();
try {
action.execute(builder);
fail("Expected IllegalStateException: " + message);
} catch (IllegalStateException e) {
// Expected.
}
}
}
/**
* Create an {@link ElementBuilderBase} to test.
*
* @param factory the {@link ElementBuilderFactory} used to create the element
*/
protected abstract T createElementBuilder(ElementBuilderFactory factory);
/**
* End the element within an existing builder.
*
* @param builder the existing builder
*/
protected abstract void endElement(ElementBuilderBase<?> builder);
/**
* Get the array of factories to test.
*
* @return an array of factories to test.
*/
protected ElementBuilderFactory[] getFactories() {
if (getModuleName() == null) {
// JRE tests only work with HtmlBuilderFactory.
return new ElementBuilderFactory[]{HtmlBuilderFactory.get()};
} else {
// GWT tests work with both implementations.
return new ElementBuilderFactory[]{HtmlBuilderFactory.get(), DomBuilderFactory.get()};
}
}
@Override
protected void gwtSetUp() throws Exception {
isChildElementSupported = createElementBuilder(getFactories()[0]).isChildElementSupported();
isEndTagForbidden = createElementBuilder(getFactories()[0]).isEndTagForbidden();
isInnerHtmlSupported = true;
isInnerTextSupported = true;
try {
createElementBuilder(getFactories()[0]).html(SafeHtmlUtils.EMPTY_SAFE_HTML);
} catch (UnsupportedOperationException e) {
isInnerHtmlSupported = false;
}
try {
createElementBuilder(getFactories()[0]).text("");
} catch (UnsupportedOperationException e) {
isInnerTextSupported = false;
}
}
protected boolean isInnerHtmlSupported() {
return true;
}
/**
* Start a new element within an existing builder.
*
* @param builder the existing builder
* @return the builder for the new element
*/
protected abstract T startElement(ElementBuilderBase<?> builder);
}